Skip to main content

Phase 10. mochi.lock integration

FieldValue
MEPMEP-74 §Phases
StatusLANDED
Started2026-05-29 23:47 (GMT+7)
Landed2026-05-29 23:55 (GMT+7)
Tracking issue(pending)
Tracking PR(pending)
Commit(pending)

Gate

TestPhase10LockfileSentinel in package3/go/lockfile/phase10_test.go walks the cobra + pflag fixture through the full encode -> decode -> drift-check cycle and verifies:

  • the encoded body covers every field MEP-74 spec §3 requires (module, version, source inline-table, zip-blake3, zip-h1, sumdb-verified, sumdb-tree-size, sumdb-record-hash, api-surface-sha256, wrapper-sha256, capabilities-declared, dependencies);
  • the encoded body is byte-deterministic across 16 runs and under slice permutation;
  • DecodeString round-trips the structure (including the integer sumdb-tree-size and the string-array fields);
  • CheckDrift(pkgs, pkgs) returns no drifts;
  • mutating each tracked field surfaces the matching drift kind (zip-hash, sumdb, api-surface, wrapper, capabilities);
  • dropping a live package surfaces stale, dropping a locked package surfaces missing.

Plus 30 unit tests in lockfile_test.go:

  • encode coverage / sort behaviour (TestEncodeBasic, TestEncodeDeterministicSort, TestEncodeOmitsEmptyOptionals, TestEncodeEmptyReturnsEmpty),
  • per-Source-kind rendering (TestSourceGitRendered, TestSourcePathRendered),
  • round-trip (TestRoundTrip),
  • decode error paths (TestDecodeRejectsMissingEq, TestDecodeRejectsBadSource, TestDecodeRejectsBadBool, TestDecodeRejectsBadInt, TestDecodeRejectsSourceMissingKind),
  • forward-compat tolerance (TestDecodeToleratesUnknownKey),
  • empty arrays + helper coverage (TestDecodeEmptyArraysOk, TestParseIntHandlesNegative, TestSplitTopLevelRespectsNesting),
  • every drift kind (TestCheckDriftDetectsMissing, TestCheckDriftDetectsStale, TestCheckDriftDetectsVersion, TestCheckDriftDetectsZipDrift, TestCheckDriftDetectsApiSurface, TestCheckDriftDetectsWrapper, TestCheckDriftDetectsSumdb, TestCheckDriftDetectsCapabilities),
  • multi-set invariance, order-stability, and false-positive guards (TestCheckDriftCapabilitiesOrderInsensitive, TestCheckDriftNoFalsePositives, TestCheckDriftDeterministicOrder),
  • diagnostic rendering (TestDriftStringNoDetail, TestDriftStringWithDetail).

Lowering decisions

The lockfile package is layering-conservative: it imports no other package3/go/* module and depends only on the Go stdlib (fmt, io, sort, strings). Callers in the build pipeline compose a []GoPackage from their own state, hand it to Encode, and read back the same shape via Decode / DecodeString. The package is the single source of truth for the [[go-package]] serialisation, so future MEP-57 lockfile-merger work can compose Encode(pkgs) with the [[rust-package]] body that package3/rust/lockfile produces without either side knowing about the other.

Source-kind enum mirrors the MEP-73 lockfile package. Three kinds (module-proxy, git, path) cover the spec §3 source table. The git kind carries url + optional rev; the module-proxy kind carries the proxy URL; the path kind carries the relative directory. Inline-table rendering matches the spec example byte-for-byte:

source = { kind = "module-proxy", proxy = "https://proxy.golang.org" }
source = { kind = "git", url = "[email protected]:internal/foo.git", rev = "abcdef0" }
source = { kind = "path", path = "../fork/local" }

Encode sorts by (module, version) for byte-stable output. The caller's slice order does not affect the rendered TOML; permuting the input produces identical bytes. This is the same invariant the MEP-73 lockfile holds; it makes git diff mochi.lock show only the genuine changes rather than slice-permutation noise.

Decode is line-oriented and tolerates unknown keys. Unknown keys are silently dropped (forward-compat: the bridge can add a field in a future phase without breaking older readers); missing required keys do not error (the spec allows partial entries during in-progress lock work); structural errors (no =, bad source inline-table, non-bool sumdb-verified, non-int sumdb-tree-size) do error so a hand-edited mochi.lock with a typo surfaces immediately rather than silently dropping the entry.

Drift kinds are a closed set, sorted by (module, version, kind). Eight kinds total: missing, stale, version, zip-hash, sumdb, api-surface, wrapper, capabilities. Each kind maps to one MEP-74 spec §3 field cluster: zip-hash covers the zip-blake3 + zip-h1 pair (a divergence in either flips the kind); sumdb covers verified + tree-size + record-hash (any change is suspicious); capabilities is multi-set-insensitive so a ["fs", "net"] -> ["net", "fs"] reorder is not a drift.

sumdb-verified always renders, even when false. This is load-bearing: the field is a security claim about a public module. A mochi.lock that omits sumdb-verified could be silently down- graded by a hand-edit; the spec requires the field be explicit. The encoder unconditionally emits sumdb-verified = true|false.

Empty optional fields are omitted, not rendered as empty. The spec allows a [[go-package]] entry to omit dependencies, build-tags, capabilities-declared when none apply. Rendering dependencies = [] would be technically equivalent but bloats the file and adds line-noise to git diff. Decode treats an explicit = [] as a nil slice (TestDecodeEmptyArraysOk).

CheckDrift is the one place that compares lockfile state to live state. Phase 10 deliberately does not implement the fetch side (that lives in the existing phases 1-2 module-proxy / sumdb clients) or the wrapper-hash side (that lives in phase 6's emitter). The caller stitches the pieces together: re-fetch each module, recompute the hashes, build a fresh []GoPackage, and pass both slices to CheckDrift. The check is symmetric: missing entries from the lockfile surface as DriftMissing; stale entries not claimed by any live module surface as DriftStale.

Files changed

FilePurpose
package3/go/lockfile/lockfile.goGoPackage, Source, Encode, Decode, DecodeString, Drift, DriftKind, CheckDrift.
package3/go/lockfile/lockfile_test.go30 unit tests over encode / decode / drift behaviour.
package3/go/lockfile/phase10_test.goTestPhase10LockfileSentinel end-to-end cobra+pflag fixture.
website/docs/implementation/0074/phase-10-lockfile.md(this page)

Test set

  • TestPhase10LockfileSentinel (6 sub-tests)
  • 30 unit tests in lockfile_test.go

Local run on darwin-arm64:

$ go test ./package3/go/lockfile/...
ok mochi/package3/go/lockfile 0.5s
$ go test ./package3/go/...
ok mochi/package3/go/apisurface (cached)
ok mochi/package3/go/build (cached)
ok mochi/package3/go/cmd/go-ingest (cached)
ok mochi/package3/go/emit (cached)
ok mochi/package3/go/errors (cached)
ok mochi/package3/go/lockfile (cached)
ok mochi/package3/go/moduleproxy (cached)
ok mochi/package3/go/semver (cached)
ok mochi/package3/go/sumdb (cached)
ok mochi/package3/go/typemap (cached)
ok mochi/package3/go/wrapper (cached)

Closeout notes

Phase 10 lands the lockfile schema as a leaf module; the CLI wiring (mochi pkg lock / mochi pkg lock --check extending MEP-57 to walk [go-dependencies]) is reserved for phase 10.1 once the MEP-57 driver gains the language-tag dispatch hook (today MEP-57 walks [[rust-package]] only; the same dispatch pattern extends to [[go-package]]). The schema is fixed; the CLI work will not require changes to this package beyond a small adapter that translates manifest entries into a fresh []GoPackage for CheckDrift.

The CheckDrift output is the surface the LSP / IDE quick-fix keys on: a --check failure prints one <module>@<version>: <kind> drift (<detail>) line per drift, sorted, so downstream tools can pattern-match on the kind without parsing free-form text.

Future phase 10.x reservations:

  • 10.1 mochi pkg lock driver integration (read [go-dependencies] from mochi.toml, walk module proxy, write [[go-package]] entries).
  • 10.2 mochi pkg lock --sumdb-consistency (Merkle consistency proof on top of the existing sumdb client).
  • 10.3 Cross-MEP lockfile merger (interleave [[go-package]] with [[rust-package]] and other language-tag tables in a stable order).