docs(specs): add S08 remote repository contracts

Adds docs/specs/S08-remote-contracts.md, the contract the per-format remote children are implemented and reviewed against, and the S04 amendments it commits to.

Closes #48 (closed). Parent: #47 (closed).

Why

Every read row in the current catalog seeds its own fixture with a write: OCI pushes into conformance/{runID}/{slug}, Maven deploys com.gitlab.conformance:conformance-{runID}-{slug}, npm publishes @conformance/conformance-{runID}-{slug}. On a remote repository every one of those writes answers 405, so narrowing the catalog with --filter does not produce a runnable remote suite: the read rows are the ones that write first.

What S08 pins

  • Repository kinds. hosted and remote; virtual is out of scope and keeps the catalog's ## Virtual section as its inventory.
  • Fixture seeding. Push to --upstream-url, read the same coordinate back through --registry-url. Run-ID coordinates make the first read a guaranteed cache miss, make the expected bytes known, and make a mismatch attributable to the repository under test.
  • Catalog split. A ## Remote section and <format>.remote.* slugs. All 20 rows in today's ## Virtual sections are classified: pull-through and cache rows move, rows that presume several upstreams stay. A second kind-independent section, ## Protocol baseline, takes oci.version.check, npm.meta.ping, and npm.meta.whoami out of ## Local so a remote run still negotiates GET /v2/ and npm's service routes. ## Negative auth is kind-independent as before.
  • Per-format surface, all three formats. Read availability, the write-refusal routes with their Allow values, and the three behaviors only a remote run exercises: Maven sidecars are the proxy's own arithmetic, npm rewrites dist.tarball, OCI listings may be proxied live.
  • Row inventory. 9 Maven, 12 npm, 15 OCI slugs. Each carries its assertion and its assertion class, except the five re-filed cache rows, which land optional and outside the class taxonomy. This is the children's scope.
  • Assertion sources. Protocol-mandated assertions separated from target-specific ones, so onboarding a second target is a scoped edit rather than a catalog re-derivation.

S04 amendments

Location Edit
run flag table, flag inventory, validation rules --repository-kind, --upstream-url, --upstream-free-only added
list flag table --repository-kind accepted; both upstream flags stay run-only
Where validated values go, Config struct, Config.Validate The three fields and their rules
Error types repository-kind, upstream-url, upstream-free-only added to ConfigError.Field
Module interface purity clause RepositoryKind is the one Config field a descriptor set may depend on
TestDescriptor NeedsUpstream bool, so the runner owns the upstream skip instead of each Fn
TestCase SetupFailure error, the channel a row uses to end the run; the row still reports StatusSkip
Entry points SelectDescriptors(m, cfg) exported, plus the ErrNothingEstablished sentinel
Execution order, steps 8 and 9 Loop short-circuits on SetupFailure; a loop that completes is checked for the all-skip verdict
Report struct Interrupted widened off "always ctx.Err()"
--timeout registration, validation, and where it lands Registered with no default, so a remote run derives one from --settle-timeout and the settling-row count
Run-ID resolution, AC #25 (closed) Widened to 16 hex characters
Security Considerations Seeding and read-back response bodies are content-scanned at construction
AC #5 (closed), AC #11 (closed), AC #13 (closed) List-equals-run promise holds per kind; list accepts the kind

pkg/conformance gains exported API here, not only fields: SelectDescriptors, ErrNothingEstablished, and two struct fields. AC #12 (closed) is unaffected: selecting a descriptor set needs no I/O, so list still contacts nothing.

Decisions worth a reviewer's attention

  • All three formats in one spec. A remote read's request and response semantics are identical to the hosted ones, so a per-format spec would mostly restate S05 / S06 / S07, while what does differ is shared across formats.
  • The preflight is a test row, not a tool error. Reporting an unlinked pair of URLs at env setup would need network I/O before S04's execution-order step 7 and would turn a transient upstream outage into exit 2.
  • A remote run must name a fixture source or declare it has none. It supplies exactly one of --upstream-url or --upstream-free-only; omitting both is a configuration error. --upstream-free-only runs the absence and write-refusal rows and skips every row needing a fixture, because those two classes need no upstream and are the whole of what is assertable against a remote repository whose upstream the operator cannot write to. Its exit 0 is not proxying evidence, which the README now says as well. This resolves the contradiction in #47 (closed), which asked for both a required flag and a skip gate on its absence: the declaration is required, and the skip gate is what the operator opts into.
  • A run that establishes nothing does not exit 0. A seeding failure is a skip, never a fail, because a fixture that never reached the upstream says nothing about the repository under test. But two cases end the run with exit 2 rather than reporting green: a cause that belongs to the run rather than the row (a revoked credential, or a transient shape that fails its one re-attempt), and a completed run in which every seeding row skipped. The second is the reused---run-id case, where every write answers 409, each skip is correctly row-attributable, and the run would otherwise relay nothing and still pass a CI gate.
  • Target-specific assertions are required rows, not optional ones. npm and Maven have no standard error vocabulary, so there is no neutral spelling; the GitLab monolith reaches read-only by not routing writes at all. The Notes column names the spec requiring each one.

Prerequisites this spec assumes, both on the Artifact Registry side

  1. A kind=2 repository whose upstream is a hosted repository on the same instance. scripts/conformance/provision.sh seeds kind=0 today.
  2. The instance's own host in outbound_allowlist. S13's upstream dialer denies loopback, RFC1918, link-local, and the host's own interface addresses, and the allowlist is the documented bypass.

Ownership of both is open on #47 (closed).

Next

#49 (closed) turns this into docs/plans/2026-08-20-remote-conformance.md and opens the three format children.

Edited by Sylvia Shen

Merge request reports

Loading
Loading