Skip to content

feat(registry): per-repo blob membership index — substrate (cloister-7c0a0b, ADR-0029 slice 1)#89

Closed
jamestexas wants to merge 1 commit into
mainfrom
feat/registry-membership-index-7c0a0b
Closed

feat(registry): per-repo blob membership index — substrate (cloister-7c0a0b, ADR-0029 slice 1)#89
jamestexas wants to merge 1 commit into
mainfrom
feat/registry-membership-index-7c0a0b

Conversation

@jamestexas
Copy link
Copy Markdown
Contributor

First implementation slice of ADR-0029 (PR #88). Ships the storage layer ONLY — table, helpers, TrustStore RPC. Pull-side gate + push-side record land as separate beads.

Changes

  • src/storage/registry-membership.ts — helpers (recordMembership, hasMembership, listReposWithMembership) + schema DDL. Mirrors registry-tags.ts.
  • src/trust-store.ts — wires the table into the schema migration; exposes 3 RPC methods.
  • test/storage/registry-membership.test.ts — 10 tests against real workerd SqlStorage. Includes the CROSS-TENANT GUARANTEE test (recording under REPO_A does NOT grant REPO_B membership) — the core ADR-0029 invariant.

Test plan

  • task lint → 1136/1136 (was 1126 on main, +10)
  • Cross-tenant guarantee test passes

🤖 Generated with Claude Code

…bstrate (cloister-7c0a0b, ADR-0029)

First implementation slice of ADR-0029 (per-repo membership boundary for
the OCI registry surface). This PR ships the storage layer ONLY — the
membership table, helpers, and TrustStore RPC surface. Two follow-ups:
the pull-side gate (consult before BlobStore.get) and the push-side
write (record after BlobStore.put) land as separate beads.

Why split into slices: per ADR-0029, the design has multiple coupling
seams (pull-side check, push-side record, lazy backfill for pre-existing
blobs, manifest-walk transitive grant, peerFp propagation, _catalog
auth-gate). Each is independently testable and reviewable. Substrate
first; the seams that consume it land next.

Changes:

1. **src/storage/registry-membership.ts** — pure-function helpers over
   an injected SQL executor, mirroring src/storage/registry-tags.ts:
   - SCHEMA_REGISTRY_BLOB_MEMBERSHIP — table DDL with (repo, digest,
     kind) PK + idx_membership_digest secondary index
   - recordMembership(sql, repo, digest, kind, nowMs, peerFp?) —
     UPSERT-on-conflict refreshes recorded_at + recorded_by in place
   - hasMembership(sql, repo, digest, kind): boolean — the pull-side
     probe; must return constant-shape 404 when false per §9.4 / ADR-0024
   - listReposWithMembership(sql, digest) — diagnostic helper for GC
     reachability scans and operator forensics

2. **src/trust-store.ts** — wires the table into the TrustStore schema
   migration and exposes three RPC methods: recordRegistryMembership,
   hasRegistryMembership, listRegistryReposWithMembership. RPC surface
   thin-wraps the helpers. Sibling shape to upsertRegistryTag et al.

3. **test/storage/registry-membership.test.ts** — 10 tests covering:
   - round-trip (record then has)
   - empty-substrate has-check returns false
   - **CROSS-TENANT GUARANTEE** — recording under REPO_A does NOT grant
     REPO_B membership (the core ADR-0029 invariant; if this ever
     accidentally passes, the multi-tenant story is broken)
   - blob/manifest kinds independent
   - idempotency on (repo, digest, kind) — no duplicate-row error
   - UPSERT refreshes recorded_at + recorded_by
   - dev-mode null peerFp accepted
   - listReposWithMembership lex-ordered, no double-count across kinds,
     empty-when-empty

Tests exercise real workerd SqlStorage (via runInDurableObject on a
BEAD_STORE stub) — same pattern as test/storage/workerd.test.ts —
so PRIMARY KEY, ON CONFLICT, ORDER BY semantics are validated against
the actual engine, not an in-memory shim.

Verification: task lint → 1136/1136 tests pass (was 1126 on main, +10
from this PR's new file).

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