Skip to content

design(adr): ADR-0029 — per-repo membership boundary for OCI registry (cloister-7c0a0b)#88

Closed
jamestexas wants to merge 1 commit into
mainfrom
feat/adr-0029-oci-tenant-boundary
Closed

design(adr): ADR-0029 — per-repo membership boundary for OCI registry (cloister-7c0a0b)#88
jamestexas wants to merge 1 commit into
mainfrom
feat/adr-0029-oci-tenant-boundary

Conversation

@jamestexas
Copy link
Copy Markdown
Contributor

Independent of the implementation stack (#83#87) — pure docs, mergeable on its own.

Closes the design half of cloister-7c0a0b — the P0 architectural finding from cloister-667ea6 adversarial review.

Why this is a separate PR

The implementation stack (#83-#87) closed 4 of 5 cloister-667ea6 findings via code. The 5th — cross-tenant blob/manifest disclosure — needs a design decision before code can land, because of an architectural constraint:

Per src/blob-store.ts lines 14-20, the singleton BlobStore is load-bearing for ADR-0003. Partitioning storage per-repo would break the content-addressed monoid axiom that the cross-DO orchestrator (BlobStore.put → BeadStore.bead_create → TrustStore.applyAttestation) depends on.

So the fix can't partition storage. The ADR proposes lifting the boundary orthogonal to storage: a (repo, blob_digest) membership index on TrustStore, consulted by the OCI pull surface before BlobStore.get. Constant-shape 404 (matching the §9.4 "exists but not yours" precedent already established by ADR-0024) for misses.

What this PR contains

  • docs/adr/0029-oci-per-repo-membership-boundary.md — 115 lines.
  • No code changes.

What it enables (separate beads / PRs)

  1. cloister-7c0a0b implementation — schema + pull-side check + push-side record
  2. Lazy backfill of pre-existing blobs via registry_tags inference
  3. Manifest-walk grant at push (transitive membership for config + layers)
  4. peerFp propagation through UploadSession (closes a related cloister-667ea6 finding)
  5. Auth-gate /v2/_catalog + /v2/<name>/tags/list

Each is independently shippable once this design is reviewed.

🤖 Generated with Claude Code

…y for OCI registry surface (cloister-7c0a0b)

Drafts the architectural fix for the P0 finding from cloister-667ea6
adversarial review: the OCI pull surface has no per-repo membership
boundary, so an unauthenticated caller can probe HEAD/GET against any
digest and learn whether *any* tenant pushed it (then pull the bytes).

Architectural constraint that shapes the design: per src/blob-store.ts
docstring (lines 14-20), the singleton BlobStore is LOAD-BEARING for
ADR-0003 — partitioning storage per-repo would break the content-
addressed monoid axiom that the cross-DO orchestrator (BlobStore.put →
BeadStore.bead_create → TrustStore.applyAttestation) depends on.

The proposal therefore lifts the boundary ORTHOGONAL to storage: a
`(repo, blob_digest)` membership index on TrustStore, consulted by the
OCI pull surface before BlobStore.get. Pulls without membership return
constant-shape 404 (matches the §9.4 "exists but not yours" precedent
from threat-model.md / ADR-0024). Pushes write membership as a side
effect under the ADR-0012 cross-DO handoff discipline.

The ADR also enumerates the four follow-up beads (lazy backfill,
manifest-walk grant, peerFp propagation, catalog auth-gate) that
together close the cluster of related findings.

Status: Proposed. No code changes — this PR is the design pass that
PR #87 recommended. Implementation lands as separate beads once the
ADR is reviewed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jamestexas
Copy link
Copy Markdown
Contributor Author

Superseded by #91 (consolidated ADR-0029 + membership substrate). Closing to reduce review load.

@jamestexas jamestexas closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant