carlos / platform Public

Complete maintenance worker for Oficina provisioning open

On branch feat-maintenance-worker

Resuming the authorized public gateway/provisioning work. New isolated branch feat-maintenance-worker from current main006e7825; first source slice387908f1 is the strict private launch-context v1/v2 codec and canonical checkpoint binding, with exact integer, field, UTF-8, bound and transplant refusal tests. It grants no invocation authority and mounts no command. Full native gate running; actual Fable review next. Remaining worker/consumer/owner workflow and real restore/provisioning evidence tracked in implementation/maintenance-worker-progress.json. Existing app previews remain live. No platform configuration, deployment or live database writes in this slice.

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.0 and 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

  1. 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's generate-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 verify 3dd1eda2… 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.
  2. Binding-change test proves syntax refusal, not digest refusal. At launch_test.go:109 string 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:197 requires completed_at strictly 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:153 silently 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. DecodeOperatorLaunch refuses version 2 and DecodeCheckerLaunch refuses 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.

Started actual Claude Fable 5.1 read-only review of frozen 01b82bde37694e3eb1348b383a53491eaf9003c8. Separate verdicts requested for launch codec remediation and inert Linux subprocess supervisor. Focused race tests pass on this revision (maintenance and maintenanceexec). Review evidence directory: /home/paulca/.cache/oficina-provisioning-20260911/worker-review-r2. No command mounted, no deployment, no live maintenance operation. Owner workflow, retained worker/checkpoint proofs, Home adapter and native integration remain required before provisioning.

Actual Fable 5.1 review completed on 01b82bde: launch codec APPROVE; subprocess supervisor REQUEST_CHANGES. Findings: surviving descendants must refuse success, expired lease must not suppress emergency termination, and subprocess environment/descriptors/closure tests need stronger assertions. Full verdict preserved at /home/paulca/.cache/oficina-provisioning-20260911/worker-review-r2/review.json. Implementing fixes on the same branch; no mounting or deployment. The descendant test added during review initially expected successful cleanup; amended to require refusal, following the reviewed execution contract.

Fable 5.1 follow-up APPROVE for bounded inert supervisor and cgroup.Populated API at 717bdd87b311df6a990a9399f5d87bc87f08bd6e. Full native go build ./... and make test passed at that exact revision. Focused race tests passed. Branch pushed to Amadan; CI being checked. Review evidence /home/paulca/.cache/oficina-provisioning-20260911/worker-review-r3/review.json. Approval does not cover native worker integration, Home adapter or mounting. Advisory: expiry test has only 200ms preflight slack; if CI proves too slow increase it. Prior refusal and all fixes remain in the branch discussion.