docs(specs): add S16 container remote repositories spec
Summary
Adds docs/specs/S16-container-remote.md — the S16 spec for container
remote repositories (kind=2): a proxy-and-cache of a single external
container registry, layered on the S13 virtual/remote foundation and the S12
hosted OCI spec.
Scoped to the remote kind only, per the remote/virtual split in !900 (merged). This MR extracts the remote half of the pre-split combined draft; the container virtual slice is S32 (#239), specified separately.
What the spec covers
- The container remote database schema (
container_remote_*), owned here since S13 defers per-format schema ownership to the format slices. It follows ADR-007 and names its divergences from it: thecontainer_remote_blobscolumns, the index refinements, andcontainer_remote_repositories.auth_statuswith itsCHECK, all reconciled by a follow-up handbook MR - OCI auth-challenge discovery and token-exchange against upstreams requiring a
Bearer token: the memoized discovery verdict on
auth_status,auth_urlcaching, an in-process bearer-token cache, retry-on-401, timing isolation from S13'sprobe_timeout, and single-flight coalescing on concurrent cold requests - Manifest and blob proxy dispatch on top of S13's Lookup/Probe/Fetch, reusing S12's content-negotiation contract
- Cache freshness: stale-tag revalidation (
If-None-Match,304bump,200manifest upsert plus tag re-point) — tags are the only container cache row with a freshness window — and clientHEADsemantics, where a miss fills like aGET - Digest verification on cache-fill (S16 owns the digest check S13 delegates to the owning slice)
- Tag listing and referrers, live-proxied to the single upstream
ContainerRemoteConfig(token-exchange timeout, token cache TTL bounds)- Error mapping onto S12's OCI error envelope, including the
UNAUTHORIZED/DENIEDcodes for propagating upstream auth decisions
Key decisions
- New remote tables use UUIDv7 ids, extending work item #185 (closed)'s scope rather than
ADR-007-as-published's stale
bigint+sequence text for these tables - Token cache TTL honors the upstream's
expires_in(clamped), not a flat TTL - Config message named
ContainerRemoteConfig/container_remote(renamed fromContainerVirtualConfig— these are remote-repository settings) - A client
HEADon a cache miss fills like aGET, following S14's discipline, so no upstreamHEADis issued for an artifact - A proxied manifest takes the same two-phase session path as a blob, not
S12's
StoreBlob. A raw S13 Fetch offers only the session, the verification it needs already exists on the S06Commitseam, and both siblings leave the storage path to Fetch without naming an S06 call. Reversible with no migration if the extra object-store operations prove to matter - The challenge-discovery verdict is memoized on a three-state
auth_statusrather than re-derived from a nullableauth_url, whoseNULLmeant both "not discovered" and "no token needed" — which made every request against an anonymous upstream re-run the discoveryHEAD. Followslast_health_status's shape on the same table. Container-only: neither sibling negotiates upstream auth
S13-side dependencies recorded, not assumed
Four S13 changes this spec needs are gated in Follow-ups rather than treated as
present: the no-transparent-decode switch, the Content-Length allowlist
exemption, an expected digest reaching the existing cache-fill Commit argument,
and Docker-Content-Digest made readable with a pre-commit comparison. Plus an
S13 spec-text amendment: its behavioral-columns table and clear-on-url-change
rule both name auth_url explicitly and need auth_status alongside it. The
criteria that depend on unlanded pieces are marked gated.
Re-baselined against !909 (merged) (merged)
Aligns with the S13 corrections from !909 (merged), now in main: digest-verification-on-fill ownership, single-flight follower-model naming, and cross-origin redirect credential stripping (S13 cover 8, which resolves the W6 thread). Branch is merged up to date with main.
Related to #283 (closed)