MEP-75 Phase 8: mochi.lock [[php-package]] Integration + --check Mode
Status: LANDED 2026-05-30 00:00 (GMT+7)
Goal
Implement the mochi.lock [[php-package]] table under package3/php/lock. Each entry records the Composer package name, version, dist-sha256 (content-addressed cache key), reflection-sha256, PSR-4 autoload map, PHP version constraint, and direct dependencies.
Also implements --check mode: drift detection that compares locked SHA-256 values against on-disk state.
Design
PhpPackage struct
Each [[php-package]] entry stores:
Name: Composer package name (vendor/package)Version+VersionNormalized: resolved versionDistURL+DistSHA256: content-addressed cache keyReflectionSHA256: SHA-256 of reflection CLI outputPHPConstraint: php requirement from composer.jsonPSR4: namespace prefix -> source directoryRequire: direct runtime dependencies
Check() -- drift detection
Check(entries []PhpPackage, hashes map[string]OnDiskHashes) compares:
- If package not in hashes map: DriftEntry with "(not cached)"
- If
DistSHA256differs: DriftEntry for "dist-sha256" - If
ReflectionSHA256differs: DriftEntry for "reflection-sha256" - Empty locked hash: no drift reported (opt-out)
Returns (true, nil) when all match; (false, []DriftEntry) on drift.
Format() -- TOML rendering
Format([]PhpPackage) renders entries as TOML [[php-package]] blocks for appending to mochi.lock.
Files Landed
package3/php/lock/lock.go-- PhpPackage, Check(), Format(), Vendor(), Pkg()package3/php/lock/lock_test.go-- 11 test functions
Test Coverage
- All match: Check returns ok=true
- Dist-sha256 drift: detected and reported
- Reflection-sha256 drift: detected separately
- Not-cached package: drift with "(not cached)"
- Empty locked hash: no false-positive drift
- Multiple packages: only drifted ones reported
- Format single + multiple entries
- Vendor() and Pkg() helpers
- DriftEntry.String() rendering