feat(oci): manifest push parsing, validation, and handler (S12 Step 12, 2/3)
Why
Second of three MRs splitting the OCI manifest push endpoint (S12 Step 12), stacked on the datastore layer in feat(datastore): container manifest persistence... (!518 - merged) • Hayley Swimelar. This slice is the format-layer logic: untrusted-payload parsing, media-type detection, the push-flow state machine, and the ManifestHandler running validation steps 1 to 5. This is the security-review slice (untrusted JSON parsing, fuzzing).
The original combined MR is feat(oci): manifest push endpoint (S12 Step 12) (!511 - closed) • Hayley Swimelar; the final slice (wiring and e2e) is feat(oci): wire manifest push endpoint with dat... (!520 - merged) • Hayley Swimelar.
What (non-obvious)
push_flow.gois a pure state machine: it accumulates inputs, outputs, and errors and performs no I/O. Side effects (SHA-256, existence queries, storage writes) live in the handler, which calls the flow for transitions, so the flow is unit-testable without fixtures.- The
ErrManifestLimitExceeded/ErrTagLimitExceededsentinels live here because the handlererrors.Is-checks them. The concrete persister in the wiring MR references them from this layer, which is whystore.go's diff there carries the cap-error construction but not the sentinel declarations. FuzzManifestParseis wired into thefuzz:ociCI job.
Test plan
go test ./internal/format/oci/... covers the handler ACs, the rapid state-machine and tree-size property tests, and the fuzz seed corpus. go vet (default and integration tags) and golangci-lint 2.12 pass. The full S12 spec-coverage matrix is in feat(oci): manifest push endpoint (S12 Step 12) (!511 - closed) • Hayley Swimelar.
Related to #19 (closed)