Verdict: REQUEST_CHANGES (bounded codec source only, commit 387908f1, reviewer model claude-fable-5-1, read-only, no commands run). The decoder itself has no accept-side defect I could find. The changes needed are in the acceptance tests, which the approved contracts make a gate.
Codec behaviour checked, all correct
- Strict fields. Both levels use token-level parsing with exact name sets, duplicate detection, null refusal and required EOF after the closing brace. Case aliases and unknown keys fail on count. Trailing data fails on the EOF token check.
- Integers. Every numeric field is decoded from its literal by the typed struct unmarshal, so
1.0,1e3,-0,2.0and uint64 overflow all refuse without a float64 intermediate. The digest marshals typed int64 and uint64 values, so the vector's maximum integers stay exact. - Surrogates. The pre-scan refuses every case where encoding/json would substitute U+FFFD, and cannot false-accept because JSON validity is checked afterwards. Escaped pairs decode to the same Go string as raw UTF-8, so both spellings hash identically, which matches the contract's "Marshal of values" rule.
- Time. Parse then reformat catches offsets,
+00:00, trailing fractional zeros and lowercase letters. The length bound holds. - Size. Decode refuses above the bound. Read consumes bound plus one and the test proves exactly one byte is left unread.
- Authority. Nothing in the package mounts or executes. Decoded context is plain data.
Required changes
- The "independent vector" is never asserted. The test at
launch_test.go:37-43(frozen source) only checks two substrings exist in the binding file. It never compares the file bytes to the marshalled array and never hashes it against the fixture's evidence digest. There is no generator script, unlike the envelope vector'sgenerate-vector.mjs. So the fixture digest was necessarily produced by the Go code under test, and a reordering of the array plus a regenerated fixture would pass. The contract's acceptance list requires frozen canonical digest vectors. I could not verify3dd1eda2…without execution. Add a stdlib-only Python generator that emits the array with Go escaping (<,>,&, U+2028, U+2029 escaped,ensure_ascii=False, no spaces), commit its output, and make the test assert byte equality with the Go marshal and SHA-256 equality with the fixture. - Binding-change test proves syntax refusal, not digest refusal. At
launch_test.go:109string fields get+"x", which makes every ID, digest and the deadline malformed. Ten of the seventeen bound fields are therefore refused before the digest is compared. Mutate to a different valid value instead (another 43-char ID, another 64-hex digest, an earlier canonical timestamp).
Smaller gaps, fix alongside
- Equal timestamps.
launch.go:197requirescompleted_atstrictly before the deadline. The contract says "not later than", so equality should pass. Safe direction, but either align the code or amend the contract wording. - Nested checkpoint fields have no per-field missing, null, case-alias, duplicate or unknown tests. The top-level loop should also run over the checkpoint object.
- Negative and zero forms for the four integers are untested. Non-canonical timestamp spellings are untested.
- The invalid-UTF-8 surrogate case at
launch_test.go:153silently continues if marshal fails, so it can pass vacuously. Build those payloads by byte splice instead. - Escaped key spellings such as
"\u0076ersion"are accepted as the exact key. Not a contract breach, but document it or reject non-ASCII-escaped keys if byte-canonical wire is intended.
Packaging advisory for the Home consumer
Home is a separate module and cannot import internal/maintenance. Exporting that package whole would drag the envelope, input and result code Home must never call, plus the platform module's full requirement graph (AWS SDK, litestream, modernc, pion) into Home's build list and go.sum.
Recommended shape: a nested, stdlib-only module inside the platform repo, for example github.com/carlosframework/platform/launchctx with its own go.mod. It holds the two structs, decode and read, the checkpoint digest, the shared vectors, and the six validators the codec needs (account, app, label, host shape, ID, digest). Platform's internal/controlname and maintenanceguard become thin re-exports of it so there is one source of truth. Home pins a tag, so any contract change is an explicit version bump on both sides.
Two API points to add there rather than leave to callers:
- Split decode by mode.
DecodeOperatorLaunchrefuses version 2 andDecodeCheckerLaunchrefuses version 1, so the contract's "never accepts" rule is enforced by which function is called, not by a post-check Home might forget. - Keep encode platform-side. Home has no reason to produce contexts, and the smaller surface is easier to review.
The blocking-reader caveat on ReadLaunch still applies to Home. The adapter must wrap the pipe with its own deadline.
Coordinator response: the initial vector was generated by an inline Python script, not Go; the review could not establish that provenance because the generator was not committed. Added the standalone Python generator, exact binding-byte equality and SHA256 assertion. All outer binding mutations now use valid replacement values and prove recomputed evidence passes. Added nested ambiguity, lexical integer/timestamp and direct invalid UTF8 tests; completion equal to deadline now follows the contract. Codec remains unmounted. Separately implementing the bounded mutable process launcher; checker execution remains refused until readonly namespace/preparation proof exists.