feat(npm): publish Step 4 coherence check (S11 Step 15, 3/3)

📦 What

S11 Step 15 (publish handler - stream + coherence + CAS) ships as a three-MR stack, in dependency order:

  • !695 (merged) - streampub CAS sink + attachment gate (library seam).
  • !696 (merged) - publish handler streaming/CAS staging (Spec Step 3).
  • !697 (merged) - publish Step 4 coherence check (shasum / integrity / manifest). <- this MR

Both parents (!695 (merged), !696 (merged)) have merged, so this MR now targets main (rebased onto it). As the final slice it completes Step 15 in the npm-hosted plan (amends the Step 15 design with the gate / pre-check-before-stream note).

Slice 3 - the Spec Step 4 coherence check on top of the staging stage. After the upload session commits, stagePublish validates the staged tarball against the envelope, all from the in-memory values streampub captured during the pass (no CAS read):

  • per-version versions["{ver}"].name/.version == envelope top-level name + versions-map key, else 422 manifest_coherence_failed.
  • tarball package.json name/version == envelope, else 422 manifest_coherence_failed.
  • SHA-1 == versions["{ver}"].dist.shasum, else 422 shasum_mismatch.
  • SHA-512 == versions["{ver}"].dist.integrity when present, matched by token so a multi-hash SRI whose sha512 component is correct still passes; a wrong sha512 is 422 integrity_mismatch, while a non-sha512 (unverifiable) SRI is 422 integrity_unverifiable.

Per the spec a coherence failure returns 422 while the committed CAS blob stands as a GC-reclaimable orphan (no attachment row is written), distinct from a Step 3 stream failure (which aborts the session before commit). A malformed or type-mismatched per-version object is also a client 422 (streampub re-serializes it faithfully but does not type-check it).

Spec coverage

Behaviour Test
AC 4 - tarball SHA-1 != dist.shasum -> 422 shasum_mismatch TestPublishStreamIntegration_ShasumMismatch
AC 52 - tarball SHA-512 (SRI) != dist.integrity -> 422 integrity_mismatch TestPublishStreamIntegration_IntegrityMismatch
AC 5 - tarball package.json contradicts the envelope -> 422 manifest_coherence_failed TestPublishStreamIntegration_ManifestCoherenceFailed

⚠️ Known gap - route not yet mounted

Same as !696 (merged): the publish route serves the 501 placeholder until the Step 23 mount (closed via a plan amendment), so the integration tests inject the Resolution directly. With Step 15 complete, a coherent publish stages + commits the blob and returns an interim 200; the npm rows + the spec's 201 publish response land in Step 16. Deliberately out of scope.

🧪 Testing

  • go test ./internal/format/npm/... - pass
  • golangci-lint run ./internal/format/npm/... - 0 issues
  • go vet -tags integration ./internal/format/npm/... - clean
  • Integration tests (//go:build integration) drive the handler against the S06 PgBlobStore over testcontainers; run in the merged-results pipeline.

Related to #122 (closed)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading
Loading