chum
← journal

Ratifying SP0 — choosing the auth and substrate ADRs

SP0 · 2026-06-06

SP0 shipped no production code. Its artifacts are three ratified Architecture Decision Records in chum/docs/decisions/ — records that had been stubs for weeks while we built scaffolding, and that had to close before any Track C sub-project (SP-4 real auth, SP-5 durable substrate, SP-6 deletion) could start. This post reconstructs the decision process for both.

The question behind ADR-0001

Chum’s auth seam — object.Authenticator / object.Authorizer in internal/object/types.go — was intentionally thin scaffolding during the initial build: an X-Chum-Did header that gates owner writes, no verification, no delegation. That is correct for a first cut and a testable seam. What it deferred was a choice that shapes the entire identity story: does Chum authenticate the data path via DID-signed service-auth JWTs, or via OAuth through the requester’s PDS?

The two options are not symmetric. Service-auth JWTs — the JWT is signed by the requester’s DID key and verified against that DID document’s verification method — mean no PDS round-trip per request. Low latency, and critically: Chum becomes a first-class AT Protocol application, not a service that borrows ATProto identity without ever verifying it. The ConOps §5.3 recommendation was explicit on this point.

The PDS-OAuth path would have been simpler to implement against a single PDS operator. But Chum’s claim — “the record is the proof” — only holds if Chum is actually checking who is signing the record. Delegating authentication to a PDS on every request would mean Chum trusts the PDS’s opinion rather than the DID’s own key material. That is not content-addressing; it is a key-value store with a PDS as its IAM.

ADR-0001 ratified the two-tier model: DID-signed service-auth JWTs on the data path; PDS-backed OAuth for delegation grants. The delegation path (ConOps §4.4 — scoped, revocable capability grants to delegate DIDs) is explicitly off the hot path. SP-4 will implement verification and OAuth behind the seam, replacing auth/devauth. The devauth scaffold ignores DID verification entirely and is honest about that — its log line says “dev auth: owner-writes only.”

The consequence that matters most: Authorizer.Can must gain a grant-lookup path for delegate DIDs. That work is SP-4’s scope, not SP0’s. SP0 ratifying ADR-0001 simply closed the decision so SP-4 could start from a specification rather than an open question.

The question behind ADR-0002

ADR-0002 governed the object.Substrate seam — the layer where immutable, content-addressed bytes actually live. The options were a managed PDS blobstore, a standalone CID-keyed blob store, or a hybrid.

The managed PDS path has a real appeal: blob storage is solved infrastructure, and if Chum’s bytes live in a PDS blobstore, the durability story is someone else’s problem. The problem is that PDS blobstores impose blob-size limits unsuitable for a general object store, and more importantly: putting bytes in a managed blobstore interposes a host between the bytes and their content address. Chum’s core guarantee is address == content. If a third party holds the bytes and provides the content-length metadata, the guarantee rests on trusting that third party to have kept them intact. That is not content-addressing — that is integrity-by-faith.

A standalone CID-keyed blob store keeps the guarantee end-to-end. The address is the SHA-256 of the bytes; anyone can re-hash to verify. No host trust required.

ADR-0002 ratified the standalone CID-keyed store. substrate/fscas — a filesystem content-addressed store, already shipped — is the dev default. SP-5 replaces it behind the seam with a production backend (S3-compatible object storage), and the verify decorator wraps whatever backend is in use to enforce re-hashing on every Get. That re-hash is not optional: a non-self-verifying backend (S3) cannot be trusted on read without it, and internal/object/service.go already flags this requirement at GetByCID.

The ADR’s consequences are honest about the tradeoff: durability and replication are owned by Chum, not ceded to a PDS operator. That is more work than delegating. But it is also the only way the integrity guarantee is real rather than asserted.

ADR-0005 closes automatically

ADR-0005 governed public-object exposure: once the substrate decision was settled, how should public objects be served? The stub recommended resolving alongside ADR-0002. With ADR-0002 ratified, the answer was immediate: serve through the existing Cache → Substrate read path, with the cache warmed on miss (ConOps §7.2). No separate direct-substrate exposure path, no new ingress surface.

The rationale is that integrity is intrinsic — address == content, so a public read needs no extra trust beyond the substrate guarantee. One read path is simpler to harden (SP-13) and means public reads still benefit from the cache, which is keyed by CID.

What the ratification process looked like

SP0’s plan (docs/superpowers/plans/2026-06-06-sp0-ratify-auth-substrate-adrs.md) required presenting a decision brief for each ADR and obtaining explicit ratification before writing the Accepted record. The briefs were grounded in ConOps sections and the existing seam code, not invented rationale. Each gate ran a grep confirming no OPEN markers remained and the exact date string Accepted 2026-06-06 was present.

ADR-0003 (multipart final digest) received a forward-note — “Merkle root of part CIDs, formal ratification deferred to SP-7 when the Assembler seam is added” — and stayed Open. ADR-0004 (pointer-chain storage) stayed Open for SP-1. Both were confirmed by the Task 4 gate grep.

The practical result: ADR-0001, ADR-0002, and ADR-0005 are Accepted. SP-4, SP-5, and SP-6 have clean starting conditions. The decisions that shape what Chum is — a first-class ATProto app that owns its integrity guarantee — are on record and not up for renegotiation as the implementation deepens.

Get early access →