ACDP Architecture

This document is non-normative. It describes how ACDP fits together so an implementer can build a registry or client without re-reading every RFC.

1. Three roles, asymmetric

RoleDoes
ProducerConstructs a body, computes content_hash, signs, publishes.
RegistryValidates publish requests, assigns ctx_id / lineage_id / origin_registry / created_at, persists bodies, derives status, serves retrieval and search.
ConsumerRetrieves contexts, verifies signatures end-to-end, walks derived_from chains across registries.

A single agent may play more than one role across different operations (a consumer that synthesizes its findings becomes a producer for the synthesized context). The same DID is used in all roles.

2. The flows

2.1 Publication

Producer ──POST /contexts──▶ Registry
Producer ◀── 201 Created ──── Registry
                            (ctx_id, lineage_id, status)

Registry processing in order (RFC-ACDP-0003 §2.1):

  1. schema validation
  2. payload-size validation
  3. embedded-size validation
  4. hash recomputation (before any signature work — verifying a signature against an untrusted submitted hash proves nothing)
  5. algorithm check
  6. key-id binding and key resolution
  7. signature verification (against the registry-recomputed hash)
  8. identifier assignment (ctx_id, origin_registry, created_at)
  9. lineage computation
  10. supersession validation
  11. visibility validation
  12. persistence
  13. response

Steps 1–7 are "validation"; the registry MUST complete all of them before any persistent state changes.

2.2 Retrieval

Consumer ──GET /contexts/{ctx_id}──▶ Registry
Consumer ◀── body + registry_state ── Registry

Consumer recomputes content_hash, verifies signature against
producer DID document. Verification is local and stateless
(no third-party call required after the producer DID is resolved).

Registries advertising the optional trust profiles attach additional top-level members to the retrieval envelope — registry_receipt (0.2.0), lineage_head_receipt on /current (0.3.0), log_inclusion (0.3.0), witness_signatures (0.4.0 Draft). Each carries its own verification procedure and its verdict is reported separately from the body verdict; a consumer that ignores them under the RFC-ACDP-0001 §6 unknown-field rule is unaffected. See §2.6.

2.3 Cross-registry lineage walk

Consumer holds context C with derived_from = [c1, c2, c3].
For each ci:
  parse acdp://<authority>/<uuid>
  GET https://<authority>/.well-known/acdp.json
  GET https://<authority>/contexts/{ci}
  verify producer signature
  recurse into ci.derived_from if needed

Each step is independent. The serving registry is not a trust anchor — only availability.

Cross-registry resolution is public-only in v0.1.0 (RFC-ACDP-0006 §4.4). The walk carries no caller credentials to the remote registry — there is no bearer-token forwarding or token exchange — so a restricted/private context on another registry resolves to not_found (HTTP 404), indistinguishable from a genuinely missing one. A consumer that needs a non-public predecessor MUST authenticate to that registry directly, out of band. Authenticated remote retrieval is reserved for a future version (RFC-ACDP-0009 §2.6).

Every <authority> here comes from a producer-controlled acdp:// reference, so each GET is an attacker-influenced outbound request. Cross-registry resolution MUST apply the SSRF protections of RFC-ACDP-0006 §7 — the same posture ACDP requires for producer did:web resolution (RFC-ACDP-0008 §4.8) and external data_refs[].location fetches (RFC-ACDP-0008 §4.9): HTTPS-only, DNS-level IP-range filtering on every resolved address, IP pinning, and same-authority redirect caps.

2.4 Discovery

Consumer ──GET /contexts/search?... ──▶ Registry
Consumer ◀── matches[], next_cursor ── Registry

Cursor pagination is opaque. Consumers loop until next_cursor is absent — not until the first empty page. Because visibility scoping and other per-requester filters are applied after a storage page is read, a registry MAY return an empty matches[] together with a non-empty next_cursor when a storage page held only hidden rows; the consumer follows the cursor to reach still-pending visible results (RFC-ACDP-0005 §2.3). Treating an empty page as the end of results would silently truncate the result set.

2.5 Supersession

Producer publishes v2 with supersedes = ctx_id_v1.
Registry verifies:
  - same agent_id
  - same lineage_id (computed from v1)
  - version = v1.version + 1
  - no other context already supersedes v1

v1's status becomes "superseded" on the next status query.
v1's body is unchanged.

2.6 The trust arc (0.2.0 – 0.4.0, optional profiles)

In v0.1.0 the producer signature covers ProducerContent only; the registry-assigned fields (ctx_id, lineage_id, origin_registry, created_at) and the registry's serving behavior are bound by registry honesty. Four optional, additive profiles close that gap in sequence — each one attests what the previous layer still left to trust:

LayerProfileAttestsSpec
Registry receiptacdp-registry-receiptsThe registry-assigned fields and which producer key verified, signed by the registry at publish time.RFC-ACDP-0010 (0.2.0)
Lineage-head receiptacdp-registry-head-receipts"This context was the lineage head as of as_of" — a serve-time freshness attestation.RFC-ACDP-0011 (0.3.0)
Transparency logacdp-registry-transparency-logThe registry's publish history is append-only: Merkle inclusion proofs per context, consistency proofs between signed checkpoints.RFC-ACDP-0012 (0.3.0)
Witness cosignaturesacdp-log-witnessIndependent parties (not registries) verified a checkpoint's signature and consistency and cosigned it with their own keys — split-view protection.RFC-ACDP-0015 (0.4.0, Draft)

All four ride the frozen v0.1.0 wire as additional envelope members; none changes a body, a signature, or a content_hash. Alongside them, acdp-registry-lifecycle (RFC-ACDP-0013, 0.3.0) adds signed retraction/republication events (mark-not-delete), and RFC-ACDP-0014 (0.3.0) defines the key-revocation context type with fail-closed verification against receipt-attested publish times.

3. The transport

ACDP v0.1.0 is JSON over HTTP, content type application/acdp+json. All endpoints accept and emit this type.

TransportUse
HTTPS / JSONNormative. All endpoints.

Binary transport bindings are out of scope for v0.1.0 and may be specified in a future version. Production deployments MUST use TLS (RFC-ACDP-0008 §4.6).

4. Where state lives

StateOwnerStorage
Producer signing keysEach producerHSM, key vault, OS keystore.
Producer DID documentEach producerPublic — did:web or other.
Registry DID documentEach registryPublic — did:web matching the authority.
BodiesOrigin registryPersistent. Immutable. Indefinite retention.
Registry-stateOrigin registryPersistent. Mutable via supersession-driven status recomputation; at 0.3.0 with acdp-registry-lifecycle, also via signed retraction/republication events appended to lifecycle_events.
Registry receipts (0.2.0)Origin registryPersistent, minted atomically with the body; immutable once signed.
Transparency log (0.3.0)Origin registryAppend-only Merkle tree; signed checkpoints. Never rewritten — a consistency-proof failure is evidence of misbehavior.
Witness cosignatures (0.4.0)Witnesses (and aggregating registries)Each witness persists its retained head and cosignature history.
Producer DID document cacheConsumersTTL cache.
Capabilities document cacheConsumersTTL cache (1 hour suggested).
Body cacheConsumersIndefinite — bodies are immutable. Key by ctx_id, validate by content_hash.

5. The big invariant

A consumer's verification decision MUST be derivable from a single context body, the producer's public key (resolved from the producer's DID document), the JCS canonicalization algorithm, and the SHA-256 hash function.

If a consumer needs anything else to verify a context, the system is doing too much. ACDP exists to keep that invariant true.

6. What ACDP does not do

  • It does not authenticate the transport. Run ACDP over TLS.
  • It does not define audit logging beyond "log signature failures with enough context".
  • It does not define a policy language for metadata shape — bind it via schema_uri.
  • It does not define how data referenced by data_refs.location is accessed — defer to the underlying data store.
  • It does not enforce visibility on the underlying data; visibility scopes the metadata, not the data.
  • It does not specify how a producer's first context publication is authorized — that is operations (DID provisioning, registry account onboarding).

7. Reading order for implementers

  1. RFC-ACDP-0001 Core — identifiers, JCS, hash, signature.
  2. RFC-ACDP-0002 Context Body — body fields and constraints.
  3. RFC-ACDP-0003 Publish — POST flow and supersession.
  4. RFC-ACDP-0004 Retrieval — GET, lineage, derived status.
  5. RFC-ACDP-0005 Discovery — keyword search.
  6. RFC-ACDP-0006 Cross-Registryacdp:// resolution.
  7. RFC-ACDP-0007 Capabilities — well-known doc + errors.
  8. RFC-ACDP-0008 Security — what you must enforce.

Then, for the optional profiles you plan to advertise (all additive on the frozen core):

  1. RFC-ACDP-0010 Registry Receipts (0.2.0) — the receipt construction every later trust layer reuses.
  2. RFC-ACDP-0011 Lineage-Head Receipts (0.3.0).
  3. RFC-ACDP-0012 Transparency Log (0.3.0).
  4. RFC-ACDP-0013 Lifecycle Events (0.3.0) — retraction/republication.
  5. RFC-ACDP-0014 Key Revocation (0.3.0).
  6. RFC-ACDP-0015 Witness Cosigning (0.4.0, Draft).