ADR: compiled-in extension registry

Copy Markdown View Source

Status: accepted (2026-08-20); closes the package-boundary contradiction of record (see below).

Context

The extension registry could ship as a data file loaded at runtime, or as compiled-in module data. The package-boundary decision's early draft listed a registry directory gaining a seat in the Hex archive — a line that predates the registry decision itself, leaving two contradictory statements of record.

Decision

The registry is COMPILED-IN data (module attributes in lib/agent_blueprint_protocol/extension_registry.ex): registry content is a code release. No registry directory exists, none ships in the archive, and drift between shipped code and shipped registry is unrepresentable. A beam-census gate asserts no shipped module touches the filesystem, which a file-loading registry would violate.

Contradiction closure: the early "package.files gains priv/registry" line is superseded by this decision. The archive's registry-relevant addition is exactly spec/protocol.md (the normative protocol document) and the previously frozen set; the requirement map records the reconciliation.

Registry pinning for artifact verification uses build_identities (kind extension, digest-exact) — an artifact stays verifiable against a registry that legitimately gained entries after it was produced. The registry's own digest is bound into the corpus index, not into artifacts.

Consequences

Entry changes (add, promote, deprecate, retire) are owner-made code changes: changelog-recorded, ADR-recorded, and reviewable as diffs. Owners register namespaces when real; no placeholder third-party entries ship. The compiled form also gives the second-language verifier a stable, corpus-anchored view of registry semantics.

Amendment 2026-08-23 — owner identity superseded

The original decision published real owner identities (project and domain names) as first-release registry content. Superseded by user directive the same release cycle: the package is public, and public content reveals no internal names. Registry data now uses RFC 2606 example-class values (com.example.* namespaces, Example* owners, example.com declaration URIs); the real names are banned bytes in the publish guard. Everything else in this ADR (compiled-in form, digest binding, state machine) stands unchanged. The 2026-08-23 registry edit moved ExtensionRegistry.digest/0's pinned value consciously, per the corpus-registry binding test.

Amendment — governance-canonical source (2026-08-26)

spec/registry/registry.json is the governance-canonical registry source: registry changes are made THERE first, then mirrored into the two compiled twins, which remain hand-written (no codegen — the deferred technical trigger stands: a third implementation, or an unported registry change surviving a cycle). The binding is the LOCAL offline digest-equality gate (mix registry.equality, a quality-battery step): the canonical digest over registry.json's entries — projected exactly as the compiled digest derives them (entries keyed by namespace, the declared field set, JCS, the extension_registry domain separator) — must equal the Elixir twin's digest, the TypeScript twin's digest, and the corpus index's pinned registry digest.

The one-time identical-serialization assertion, quoted from the gate's founding run:

registry equality: ok (6 entries; governance json, Elixir twin,
TypeScript twin, and corpus index all carry
sha-256:FG2f38K0hba8tTP7iUaw7vHjgcnN_5F5Mp0v4G6UDVs)

A hand-edit on any carrier reds: declared-field edits via the digest comparison (both directions red-proven — a twin edit, a governance-json edit), and any other byte of the governance json via the gate's closed-world shape check (the format member and undeclared entry members red; red-proven) — the file's own bytes are additionally covered end-to-end by the release identity chain's specification digest.