Maven remote band: eight spec-amendment candidates (Maven 1-8)
Ask
Amend S05 and S08 for the eight spec gaps the Maven remote band recorded as Maven 1 to 8 in docs/plans/2026-08-21-remote-conformance.md §Spec-amendment candidates. None blocks a step MR; each one forks an implementation, which is why they are filed rather than assumed.
This is the Maven band's equivalent of #67, which carries the Band B three from Step 19, and #70, which carries the OCI band's eight.
The eight
Maven 1: SeedSpec confirms one coordinate, but a row may seed several. SeedSpec carries a single ReadBack, so SeedAndSettle polls exactly one coordinate however many the row's Write seeded. S08 §Seeding and asynchronous upstreams wants the poll to establish readability before the proxy is asked, and an unpolled coordinate still propagating when the row reads it through the remote gets its miss cached, which is what the poll exists to prevent. Step 10 mitigates it without changing the signature by seeding the polled artifact last, so every other coordinate has had strictly more time when the poll succeeds. That makes one poll a sound proxy rather than a guarantee: an upstream propagating out of order, or per artifact rather than per write, can still leave an earlier coordinate unreadable. The real fix is a SeedSpec that can confirm every coordinate it seeded, which is Step 5a's shared surface. Two shapes are worth weighing together: a ReadBack taking the artifact to confirm and called once per coordinate, or a slice-valued read-back. Either has to be costed against AC #30 (closed)'s 2 x d per-descriptor timeout budget. Affects AC #14 (closed), AC #30 (closed) and S08 §Seeding and asynchronous upstreams; reaches npm at Step 21 and OCI at Steps 31 and 32.
Maven 2: AC #37 byte-compares a fixture the upstream is allowed to rewrite. AC #37 makes a resumed settle's post-condition byte identity against SeedSpec.Fixture, and pkg/conformance/seed.go §satisfies justifies the comparison on the grounds that the suite authored the fixture. For the artifact-level maven-metadata.xml that premise is false: S05 §Operation: PUT maven-metadata.xml states the server may regenerate the document, so the comparison can never hold against a registry doing what the spec permits. Two shapes: a SeedSpec that can declare an artifact's bytes server-authored, so the post-condition becomes readability plus a row-supplied structural predicate; or AC #37 restated over "the coordinate holds this run's document", with the format defining what that means, which is what the relay assertion already does structurally. The first is a shared-surface change and lands with Maven 1; the second is a spec edit alone. Affects AC #37 and S08 §Fixture seeding model. Maven is the only band that hits it today, because npm's packument and OCI's manifests are stored as written.
Maven 3: AC #13 (closed) does not reach <latest>/<release>, so a stale-tag document relays clean. maven.remote.metadata-relay requires the served document to list the seeded version and requires that version to resolve. AC #13 (closed)'s clause is "lists the seeded version", so a proxy relaying the document it served before re-tagging satisfies both halves while doc.Versioning.Latest and doc.Versioning.Release still name a stale version: the row passes a document whose own summary fields contradict the version list it was passed for. The assertion is not added in the row because AC #13 (closed) does not state it. The comparison would be safe if the AC asked for it: Latest and Release are structural fields rather than bytes, so comparing them against the seeded version does not depend on the upstream storing what was written, which is the same ground the version-list clause already stands on. Affects AC #13 (closed) and S08 §Per-format rows' maven.remote.metadata-relay entry. Raised in review of !282 (merged).
Maven 4: the S05 GET header contract is unstated at four remote rows' coordinates, and one clause is unstated at the fifth. assertRelayGet pins Content-Length for maven.remote.artifact-relay, on AC #13 (closed)'s "the format's own integrity metadata agrees with them". The reads in maven.remote.repeat-read, maven.remote.cold-fill-header-subset, maven.remote.sidecar-synthesis and maven.remote.metadata-relay assert neither header, so a 200 whose Content-Length disagrees with the body it carries passes all four.
Content-Type is a fifth false pass rather than a fourth instance of this one, and the entry claimed it was pinned when it is not. No row compares Content-Type against the value the coordinate was seeded with: the only assertion over it is assertRelayHeadMatchesGet's GET/HEAD parity, which is satisfied by any value a proxy serves consistently on both verbs. A proxy answering text/plain for a .jar therefore passes the relay row. Informal testing during the goal run found Content-Type identical across cache states against a live target; that finding is not committed to the repository, so it supports adding the clause without settling what value it should assert, and the amendment has to say that as well as where the clause lives. It belongs with the GET-header clauses above, in the same amendment.
AC #16 (closed) is stated only over byte identity between the two reads, and AC #23 (closed) only over the presence of ETag and X-Checksum-*; neither mentions either header. The clauses are therefore not added at those rows in this batch, for the reason Maven 3 gives. The same informal testing found Content-Length identical across both cache states; that finding is likewise not committed, so it supports rather than settles the assertion.
Affects AC #13 (closed), AC #16 (closed), AC #20 (closed) and AC #23 (closed), and the S08 §Per-format rows entries for the four rows named above plus maven.remote.artifact-relay for the Content-Type clause. AC #16 (closed) and AC #23 (closed) are where the missing clauses would be stated for repeat-read and cold-fill; AC #13 (closed) and AC #20 (closed) are named because metadata-relay and sidecar-synthesis sit under them and an amendment has to say whether their reads carry the clauses too, not because either AC is itself silent in the way AC #16 (closed) and AC #23 (closed) are. Raised in review of !282 (merged).
Maven 5: S05's empty-body HEAD clause is unobservable, so it is a silent false pass. S05 §Operation: HEAD artifact pins the success body as empty. No row asserts it and none can: net/http never surfaces a HEAD response body to a client, so a registry that sends one is invisible at every API layer the suite reads through. maven.remote.artifact-relay asserts the other half of the contract, the Content-Type and Content-Length parity with the GET, and stops there. TestHeadArtifact_carriesTheHeaderSetWithoutABody is not the missing assertion and should not be read as one: it pins that HeadArtifact surfaces no body and that the header set survives, which is a statement about this client's construction rather than about what the registry sent. Closing it needs a client that can observe the wire, a change of layer rather than a clause: a raw *http.Response read, or a transport-level hook counting the bytes after the headers. Whether the clause is worth that is the amendment's question, and the honest alternative is for S05 to record it as unverifiable by this suite so no later reader re-files it. Affects S05 §Operation: HEAD artifact and AC #13 (closed). Raised in review of !282 (merged).
Maven 6: a settle read-back that answers a 2xx other than 200 now short-circuits, on a map written for failed read-backs. remoteSeedReadBack rejects a non-200 and returns an error, which routes the status through pkg/conformance/seed.go §settlePollShortCircuits. That map terminates the loop on any status other than 404 and 5xx, and it was written for read-backs that failed: it answers "can further polling recover this?" for an error, not for a success carrying the wrong status.
Applied to a 204 the two questions come apart. An upstream that answers 204 while its write propagates and 200 a second later is recoverable within the cap, and the loop now exits 2 on the first poll instead of waiting for it. The alternative reading, treating a non-200 2xx as keep-polling like a 404, costs an upstream that answers 204 permanently a full settle window before failing.
The short-circuit reading is the one implemented, and deliberately: it names the status in the failure, and a 204 is a conformance answer rather than a propagation delay, so failing fast on it reports the defect the operator can act on. Recorded here so it is a decision rather than an implicature, since nothing in S05 or S08 states which reading applies to a successful-but-wrong status. The narrowest amendment is one sentence saying so.
Affects AC #14 (closed) and S05 §Async metadata sync polling. Raised in review of !282 (merged).
Maven 7: AC #37's byte identity and S05's permission to regenerate maven-metadata.xml cannot both hold for a metadata settle. AC #37 requires a resumed settle to confirm the run's fixture only on byte-identical content, and seed.go implements that by comparing the read-back against SeedSpec.Fixture. S05 §Operation: PUT maven-metadata.xml permits a registry to regenerate the artifact-level document server-side and treat the uploaded body as informational.
Against such a registry the two are contradictory. maven.remote.metadata-relay settles on the document it seeded, so a resumed settle — reached through a Preexisting probe hit or a duplicate refusal, which is the retry path after a lost write — compares the suite's bytes against regenerated bytes, mismatches, and reports "a collision: a foreign writer, or a suite version that changed the fixture builder". That ends the run at exit 2 and blames the operator's registry for behaviour S05 explicitly allows.
The band did not change the settle artifact, because the obvious alternative trades one gap for another: settling on the .jar makes the identity check sound, and gives up confirming the propagation of the one document S08 §Seeding and asynchronous upstreams names as the slowest and most likely to exceed the default --settle-timeout.
Two shapes worth weighing: AC #37 gaining a per-format carve-out for documents a registry may regenerate, with structural equivalence standing in for byte identity; or SeedSpec carrying a comparator rather than a fixture, so a format supplies the equivalence its own spec admits. The second composes with Maven 1, since both are about SeedSpec being too narrow.
Affects AC #37 and S05 §Operation: PUT maven-metadata.xml. npm's packument and OCI's tag list have the same regeneration question and should be checked rather than assumed distinct. Full reasoning in the Maven band's validation/decisions.md D29. Raised in the Maven band's own adversarial pass, !283 (merged).
Maven 8: no Maven relay row asserts AC #13 (closed)'s integrity-metadata half at the coordinate where it asserts byte identity. AC #13 (closed) requires a relay row to pass "only when the bytes are identical and the format's own integrity metadata agrees with them", and §Per-format rows classifies maven.remote.sidecar-synthesis as a Relay row.
Maven satisfies the two halves at two different coordinates. maven.remote.artifact-relay asserts bytes, Content-Length and HEAD parity at its own coordinate and reads no sidecar. maven.remote.sidecar-synthesis digests the primary as served at its coordinate and, by AC #20 (closed)'s explicit requirement, must not compare against what it seeded, so it cannot also assert byte identity there. The conjunction AC #13 (closed) states therefore holds at no coordinate, and AC #20 (closed) is what prevents the sidecar row from closing it.
Two shapes: amend AC #13 (closed) to say a format may satisfy its two halves across a pair of Relay rows, naming the pair per format; or have artifact-relay read one sidecar at its own coordinate, which closes it in code and costs one extra read. The first looks right, because the split is a consequence of AC #20 (closed)'s rule rather than an accident of this band's implementation.
This overlaps #67's Band B 2, which raised the same classification question for Maven (while analysing npm's parallel case) and proposes the same two shapes in substance. Resolve the two together: whichever spec MR lands first should absorb the other rather than leave AC #13 (closed) amended twice from two directions.
Affects AC #13 (closed), AC #20 (closed) and §Per-format rows. npm's tarball-relay and dist.integrity, and OCI's blob digest, should be checked for the same split. Full reasoning in the Maven band's validation/decisions.md D30. Raised in the Maven band's own adversarial pass, !283 (merged).
Why one issue
One issue per band is this repo's existing shape, with #67 for npm's and #70 for OCI's, and the eight here are one spec MR's worth of edits against S05 and S08.
Maven 1 and 2 were raised at Steps 10 and 11. Maven 3, 4, 5 and 6 came out of !282 (merged)'s review and were recorded in the plan first so the findings were not lost, which is what left the band without an issue until now. Maven 7 and 8 came out of !283 (merged)'s own adversarial pass, after this issue already existed, so they widen it rather than start a new one — the same widening this issue itself already took once, from five to six.
Maven 1 and 2 are the two that are not narrow: both name a change to the shared SeedSpec surface that reaches npm and OCI, so they may want splitting out if the shared-surface work is scheduled separately from the S05/S08 wording.