# Appendix B — REP-0001: The Registry Core

**version** draft-1 (site-agent first pass — corpus version to be assigned on adoption)
**date** 20 August 2026
**from** The site agent
**to** Project lead, Engineering, Architecture, Security

**type** Appendix — normative specification, in PEP form

*The pack's design restated as a specification an implementer works from, in the form Python uses for its enhancement proposals. Everything normative in documents 01–03 and 11–13 is collected here with **MUST / SHOULD / MAY**, and every corrective the appendix records is applied — so this is the only document in the pack where the schemas are current rather than superseded-with-a-note. The format is borrowed for the sections it forces: **Security Implications**, **How to Teach This**, **Rejected Ideas** and **Open Issues** are required, not optional, and three of the four are where this design has the most to say. Limitation: this is a Draft with no reference implementation of the registry, no sponsor, and no accepting authority — it is a specification, not a decision, and its Status field says so.*

---

## About this format

**REP** — Registry Enhancement Proposal — is this pack's analogue of a PEP. The vocabulary is borrowed from PEP 1 and the section set from PEP 12, with one deliberate deviation noted under *Copyright*.

| Field | Values used here |
|---|---|
| **Type** | `Standards Track` (a normative spec), `Informational`, `Process` |
| **Status** | `Draft` → `Accepted` / `Rejected` / `Withdrawn` / `Deferred` → `Final`; also `Active`, `Provisional`, `Superseded` |

**The pack's decisions register is a Status field that has not been formalised yet.** Thirty-six decisions carry *Settled* or *Open* by hand. If REPs are adopted, each open decision becomes a REP with a real status and a champion, and the register becomes an index rather than a table.

---

## Preamble

```
REP:              1
Title:            The Registry Core
Author:           The pki.sgit.ai site agent
Sponsor:          (none — a REP needs a champion and this one has none)
Status:           Draft
Type:             Standards Track
Created:          20-Aug-2026
Requires:         (none)
Replaces:         registry-mvp documents 01, 02, 03 (normative content only)
Post-History:     20-Aug-2026
Resolution:       (pending — no accepting authority is defined; see Open Issues)
```

**The `Sponsor` field is empty and that is meaningful rather than incomplete.** PEP 1 requires a champion who takes responsibility for the proposal and shepherds discussion. This document has an author and no sponsor, which is the same standing problem the pack records elsewhere: it has exactly the authority of an agent saying so.

## Abstract

This REP specifies a public register of agent identities, mandates, grants and revocations, hosted as ordinary files in a public vault. Each subject owns one record: an append-only, hash-chained log of signed statements. A verifier holding nothing but a URL can establish, from published files and signature checks alone, what a given agent was authorised to do, by whom, until when — or receive a refusal naming where the chain stopped resolving. The register contains no private key material and no usable credential at any time. It records authority; it does not enforce it and does not observe behaviour.

## Motivation

Public key registries were solved and then unsolved. The keyserver network of the 2010s was flooded to uselessness in 2019 because anyone could append anything to anybody's record — a design goal, stated at the outset, not a bug. Its maintainer called it unsalvageable. Any proposal for a key register now must show it was designed with that history in hand.

At the same time, the layer this register would serve did not exist then. Agents are now delegated authority by people, and the shipped tooling underneath this proposal has **no revocation and no directory** — stated plainly in its own documentation. Those two absences are not underneath the surface; they sit on top of it, closest to the need, and they are what makes *may this agent do this?* unanswerable by anybody except the party being asked.

Three things follow, and they are the motivation rather than the design:

1. **The question worth answering is a third party's**, not the operator's. An operator asserting an agent's authority is the witness testifying about the suspect.
2. **What is delegated needs a shape.** A credential permits a set of things; a mandate states what was authorised. The difference — **excess authority** — is exposure that nobody accepted, and today it is not computable because one half is never written down.
3. **The absence of an answer defaults to trust.** Where nothing can be checked, systems render unchecked claims identically to checked ones, and volume reads as substance.

## Rationale

**Why files in a public vault rather than a service.** A service that answers queries must observe every query, which accumulates a map of who is evaluating whom — held in plaintext by whoever runs it. Publishing rather than answering removes both the surveillance capability and the metering capability, and those turn out to be one property. Files also mean mirroring is free and custody grants nothing, because the register holds no secrets.

**Why one record per subject, owned by that subject.** This is the 2019 failure inverted into a rule: *only the owner writes to their own record.* It is the reason a mandate lives in the **issuer's** record rather than the subject's, with the subject appending an acceptance — the alternative required an exception for issuers, and "anybody may append, except when they may not" is the design that already failed once.

**Why append-only with an effective date, rather than deletion.** A revocation that deletes destroys the historically checkable question. *Was this valid last Tuesday?* is the question an auditor asks and the one a deletion can never answer.

**Why signatures rather than keypairs on every object.** A signature's value is the scarcity of the private half. An artefact — a grant, a mandate, a claim — does not act, cannot hold an interest, and cannot keep a secret; giving it a keypair multiplies the private-key inventory by every document in the register and buys an address that does not exist yet. **A key belongs to whatever can keep a secret; everything else is signed by something that can.**

**Why the index has no authority.** A curated index makes the common query cheap. It is also unsigned, and unsigned conveniences become load-bearing by default. The specification therefore makes it regenerable by anyone and forbids the read path from depending on it.

## Specification

The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHOULD**, **SHOULD NOT**, **MAY** and **OPTIONAL** are to be interpreted as in RFC 2119.

### 1. Register layout

A register **MUST** be reachable over HTTPS at a stable prefix and **MUST** contain:

```
llms.txt          machine-readable front door        REQUIRED
roots.json        declared trust roots               REQUIRED
params.json       bounds + canonicalisation version  REQUIRED
index.json        curated convenience — NO authority  OPTIONAL
records/<id>/     one directory per subject          REQUIRED
```

`<id>` **MUST** be the subject's signing-key fingerprint in the form `sha256:<hex>`. Every file **MUST** be fetchable without credentials, and a register **MUST NOT** require an account, an API key or a session to read.

### 2. Records and statements

A record is a directory of zero-padded, five-digit JSON files, `00001.json` upward. Each is one statement:

```json
{
  "v": 1,
  "seq": 7,
  "prev": "sha256:<hash of the previous statement file, or null at seq 1>",
  "type": "identity | mandate | grant | acceptance | revocation | check",
  "at": "2026-08-20T09:41:07Z",
  "body": { },
  "sig": "<detached signature over the canonical bytes>"
}
```

- `seq` **MUST** begin at 1 and increase by exactly 1 with no gaps.
- `prev` **MUST** be `null` at `seq` 1 and otherwise the hash of the immediately preceding statement file.
- Statement 1 of every record **MUST** be of type `identity`, self-signed by the key it describes.
- A statement **MUST** be signed by the key that owns the record. A verifier **MUST** reject any statement whose signature does not verify against the record owner's signing key, **regardless of whether that signature is otherwise valid.** A valid signature by a non-owner is the 2019 failure and **MUST NOT** be treated as write authority.
- Statements **MUST NOT** be modified or removed once published. Correction is by appending.

### 3. Canonicalisation

Canonical bytes **MUST** be produced by removing the `sig` member and serialising the remainder as compact, key-sorted JSON (the recipe published in `params.json`). `params.json` **MUST** carry a canonicalisation version, and a verifier **MUST** refuse a statement whose canonicalisation version it does not implement rather than guessing.

### 4. Statement bodies

**`identity`** — self-signed; proves possession and **nothing about trust**.

```json
{ "bundle": { "v": 1, "encrypt": "<PEM>", "sign": "<PEM>",
              "label": "<free text, unverified>",
              "fingerprint": "sha256:…", "signing_fingerprint": "sha256:…" },
  "agent_type": "llm-session | service | human | operator",
  "operated_by": "sha256:<a claim, corroborated by nothing>",
  "private_key_published": false,
  "publication_intent": "none | deliberate | leaked",
  "claims": { } }
```

`private_key_published` is **REQUIRED**. Where it is `true` the entry is a **fixture**: it exists to exercise the plumbing, **MUST NOT** be reachable from the trust graph, and a verifier **MUST** read this flag **before** evaluating any signature on the record — because a fixture's signatures verify and prove nothing. `publication_intent` **SHOULD** be recorded at issue; a deliberate publication and a leak are indistinguishable afterwards.

**`mandate`** — the issuer's statement, appended to the **issuer's own record**.

```json
{ "mandate_subject": "sha256:…", "capability": "<vocabulary term>",
  "resource": "<identifier>", "constraints": { }, "environment": "<name>",
  "valid_from": "<RFC3339>", "valid_until": "<RFC3339>",
  "on_authority_of": "sha256:<optional chained authority>" }
```

- A mandate **MUST** carry `valid_from` and `valid_until`. **A mandate with no interval is a grant wearing a mandate's name and MUST be rejected.**
- `constraints` **MUST** be stored opaquely. The register **MUST NOT** interpret or enforce them; enforcement is an execution-time concern outside this specification.
- A mandate **MUST NOT** be expressed as a deny-list. A deny-list widens silently whenever a supplier ships a capability it could not have excluded. Implementations **MAY** render the allow-list's complement as prohibitions for human acceptance, and such a rendering **MUST** carry the date and capability-set version it was generated from.

**`acceptance`** — the subject's statement, appended to the **subject's own record**: `{ "accepts": { "record": "sha256:…", "seq": 7 }, "as": "mandate" }`. An unaccepted mandate **SHOULD** be treated as inert (see Open Issues).

**`grant`** — a record that a concrete capability was conferred. A grant **MUST** carry a descriptor that is a **hash** of what was issued. **The register MUST NOT at any time contain a usable credential**, and an implementation **SHOULD** enforce this with a build-time check that fails on credential-shaped strings anywhere in the tree.

A grant **MAY** be a node in a tree. Where it is, each node **SHOULD** carry:

```json
{ "parent": "<node id or null>", "reaches": ["<capability>"],
  "mechanism": "<named control, or null>",
  "enforced_by": "boundary | setting | expectation | none",
  "evidence": "tested | documented | derived | asserted",
  "checked": "<date, per node>" }
```

`enforced_by` **MUST** be determined by one test: **a control bounds a grant only when it is enforced by something the grant does not include.** A control enforced by a tool running inside the grant is `setting`, not `boundary`, however it is documented. `checked` **MUST** be per node; a tree dated as a whole is wrong in one place while looking current.

**`revocation`** — `{ "revokes": { "record": "sha256:…", "seq": 4 }, "reason": "<enum>", "effective_from": "<RFC3339>" }`. It **MUST** be signed by whoever could sign the original. Nothing is deleted. State before `effective_from` **MUST** remain derivable.

**`check`** — written by a **checker** into the **issuer's** lane, never a central log. It **MUST** carry what was checked, when, the question asked, the result and the anchor, and the checker's identity **or a stable pseudonym**. It **MUST NOT** carry what the checker was authorising, the checker's credentials, or anything the issuer could not already infer from holding the mandate.

### 5. The verification walk

A verifier **MUST**:

1. Fetch `roots.json` and `params.json`.
2. Fetch the subject record; verify `seq` contiguity and every `prev`.
3. Read statement 1 for the key bundle; **check `private_key_published` before evaluating any signature.**
4. Verify every signature against the record owner's signing key.
5. Follow acceptances to mandates in issuer records; verify those chains to a declared root.
6. Check revocations in **both** records, honouring `effective_from`.
7. Return the last applicable statement on both sides.

A verifier **MUST NOT** rely on `index.json` for any step. A verifier **MUST** be able to return a **partial** result naming where it stopped; a partial resolution is a legitimate output and **MUST NOT** be reported as failure. A result **MUST** distinguish *confirmed*, *denied*, *unknown*, *unreachable* and *not checked*; an implementation **MUST NOT** render *unreachable* as *denied*.

### 6. Writing

Writes **SHOULD** use the append lane: account-less, token in the body, blind acknowledgement. The acknowledgement **MUST NOT** disclose whether a submission was accepted, queued, read or declined, and those four outcomes **MUST** be indistinguishable by response, status and timing. The public read path is the only outcome channel.

A processor holding the register write key **MUST** apply the ownership rule, the interval rule, the size bounds in `params.json` and the credential-shape check before committing, and **SHOULD** publish its decisions (see Open Issues, which records the conflict this creates with the blind acknowledgement).

## Backwards Compatibility

There is nothing to be compatible with: no register exists and this REP has no users. Three forward-compatibility obligations apply instead.

`v` and the canonicalisation version in `params.json` exist so that a change in serialisation is detected rather than guessed. **A statement whose canonicalisation version a verifier does not implement MUST be refused**, never approximated.

The pack's own record model has a change queued and not applied: growth moves from the record to the commit graph, making an entry a file inside a commit graph rather than a log that accumulates. **This REP specifies the accumulating form**, which is what documents 01–03 describe, and a future REP superseding it is expected. It is stated here so an implementer knows the ground will move.

A register that has published statements **MUST NOT** renumber, reformat or re-canonicalise them. Corrections are appended. This is the same rule the pack applies to its own documents.

## Security Implications

The section PEP 1 requires, and the one with the most to say.

**The register is public by design and holds no secrets.** Its confidentiality exposure is not that content leaks but that **content is a signal**: which agents exist, who issued to whom, and how often a mandate is exercised. Rate of checking is itself a business signal, observable from lane growth without decrypting anything.

**The 2019 failure is the threat model.** The controlling mitigation is the ownership rule in §2: a valid signature by a non-owner **MUST NOT** be write authority. An implementation that checks signatures but not ownership has reproduced the flooding vulnerability exactly.

**A published private half cannot be recalled or promoted.** It cannot be revoked through the register's own revocation rule either, because anybody can sign the revocation *and* anybody can sign the append that reverses it. This is why fixtures are a marked, quarantined class and why the flag is read before any signature.

**A signature anybody can forge is more dangerous than a hash.** A hash makes no promise. A forgeable signature makes a promise it cannot keep, to a verifier that succeeds and concludes something false. §4's prohibition on per-object keypairs follows from this and not from tidiness.

**The register cannot detect equivocation on its own.** A hash chain proves a reader's own view is internally consistent. It cannot prove every reader saw the same view. Publishing a signed head on a stated cadence would make that falsifiable by anybody who recorded a previous head — proposed, not required, and the largest unmitigated risk in this specification.

**Check events are a surveillance hazard placed deliberately.** A central check log accumulates who is evaluating whom across parties that never consented. §4 requires the event to go to the issuer's own lane instead, which makes it an owner observing their own asset. **This forecloses the aggregate, which is a real commercial cost chosen rather than discovered.** Removing the `MUST NOT` list from the check body converts this design into the thing it was built to avoid.

**Enforcement is out of scope and the boundary must be stated in the product, not only here.** The register records authority. A mandate constrains what an agent may be *authorised* to do, not what it does. The realistic failure is not technical: it is a reader concluding the register prevents something.

**Blind acknowledgement is a security property, not a usability defect.** Any signal distinguishing pending from declined — including timing — is an oracle for probing enrolment policy.

## How to Teach This

**The one-sentence version.** *A register where only you can write to your own record, nothing is ever deleted, and a stranger can check what you claimed without asking you.*

**Teach it in this order**, because each step motivates the next: (1) the 2019 flood, and *anybody may append to anybody's record*; (2) therefore the ownership rule; (3) therefore a mandate lives in the issuer's record and the subject appends an acceptance; (4) therefore revocation is an append with a date, because deletion destroys the historical question; (5) therefore a verifier walks two chains and may legitimately stop halfway.

**The misconception to pre-empt, every time:** *identity is not authorisation, and authorisation is not enforcement.* A key proves possession. A mandate records what was authorised. Neither observes behaviour.

**Teachability is a definition of done here rather than documentation.** The pack's thesis is that the first client is a documented workflow followed by a fresh LLM session holding nothing. **If such a session cannot complete the walk from the published page alone, this specification is not finished** — whatever else works. That is an acceptance test, and it is the harshest one available, because a session reads literally and cannot ask what a page meant.

## Reference Implementation

**None for the registry.** No register exists; §§1–6 are unimplemented.

Two things do exist and bound the risk. The signing, verification and encryption primitives ship in `sgit pki` — ECDSA P-256 detached signatures and RSA-OAEP 4096 — as does the append lane, account-less with a blind acknowledgement. And the **grant tree of §4** has one implementation, at `pki.sgit.ai/assess`, which renders trees, control labels and the prohibition view against a public library. It is a **consumer** of this model rather than a piece of the register, and it stores nothing on anybody's behalf.

Per PEP 1's convention, this REP **MUST NOT** move to `Final` before a reference implementation of §§1–6 exists and a fresh session has completed the acceptance test in *How to Teach This*.

## Rejected Ideas

Required by PEP 1, and the section a reader should check before proposing an improvement.

| Idea | Why rejected |
|---|---|
| **Mandates in the subject's record** | Simpler lookup, and it requires an exception to the ownership rule. "Anybody may append, except issuers" is the design that already failed |
| **A copy in both records** | Rule-clean and produces two copies that can diverge — the worst property a register can have |
| **Keypairs for grants, mandates and evidence** | A signature's value is scarcity. Publishing the private half leaves a hash wearing a signature's clothes, and would make the fixture flag true on every row — a flag that is always true is a column, not evidence |
| **A central check log** | Accumulates who is evaluating whom, across parties that never consented, in plaintext |
| **Metered verification as a service** | Charging per check requires observing every check. The revenue model and the surveillance capability are one mechanism |
| **Deny-lists as the stored rule** | Widens silently when a supplier ships a capability. Permitted only as a dated, generated view |
| **A trust score** | Any single number averages *confirmed*, *unreachable* and *unverifiable* into one figure — the collapse the five result states exist to prevent |
| **Receipts in the register** | A receipt is the executor's statement, not the register's. Deliberately out of scope |
| **Deletion as revocation** | Destroys the historically checkable question |
| **Trusting `index.json`** | Unsigned convenience. Regenerable by anyone; authoritative to nobody |
| **Handing a session a secret to prove where it runs** | Proves possession, not location, and hands over a larger identity than the one being established |

## Open Issues

| Issue | Status |
|---|---|
| **The capability vocabulary** | Undefined. `capability` is a string drawn from a vocabulary nobody has written, which blocks the *shortfall* computation entirely and makes three documents' examples unbindable |
| **Acceptance semantics** | `SHOULD` above. Whether an unaccepted mandate is inert or live-on-issue is undecided; the spec takes the stricter reading provisionally |
| **Size bounds** | Proposed numbers only, and the record model is expected to change to a commit graph before they are tested |
| **A published signed head** | Proposed as a fifth rule. Without it the equivocation gap in *Security Implications* is unmitigated |
| **Processor transparency versus the blind acknowledgement** | Directly conflicting requirements in §6. A public decision log is the oracle the blind acknowledgement exists to withhold. Unresolved; today the blind acknowledgement wins by default, **which is a decision nobody made** |
| **Lane anchoring** | Whether a lane with no anchors accepts any token holder is absent from the platform's documentation and gates the coverage of check events |
| **The address derivation** | Proposed, not shipped. Enrolment tokens are agreed out of band today |
| **Who accepts a REP** | There is no Steering Council here. Until an accepting authority exists, `Status` can never move past `Draft`, which is the honest state |

## References

- The 2019 keyserver flood and the append-anybody design goal — `pki.sgit.ai/failure/`
- The four rules this REP encodes — `pki.sgit.ai/rules/`
- The shipped primitives and the stated absence of revocation and directory — `sgit.ai/docs/pki`
- The pack's corrections and decisions register — Appendix C of this pack
- PEP 1 (purpose and guidelines) and PEP 12 (template), whose section set this borrows

## Copyright

**A deviation, stated rather than made quietly.** PEPs are placed in the public domain or under CC0-1.0. This document is released under **CC BY 4.0**, like the rest of the pack — attribution required, which is a stronger condition than a PEP carries. The pack's own discipline is that a claim should be traceable to who made it, and CC0 would sever that. An implementer wanting the specification under CC0 should ask.

---

## Honest Tensions

| Tension | Note |
|---|---|
| **A Draft with no sponsor** | PEP 1 requires a champion, and the empty field is accurate rather than an omission. A specification nobody has accepted has the standing of an agent saying so |
| Normative language over an unbuilt design | RFC 2119 keywords read as authority. Every `MUST` here is a design intention that has never met an implementation, and `Reference Implementation` says so |
| The spec is current; the pack is superseded-with-notes | Useful for an implementer and a second source of truth for the pack. If they drift, this one is wrong — the sources plus the appendix are the record |
| Borrowing PEP's shape without its process | The sections are the valuable part; the process — a champion, public discussion, an accepting authority — is exactly what this lacks, and the format makes that visible instead of hiding it |
| One REP for the whole core | PEP practice is one idea per PEP. This is six, and it should split the moment any of them is separately contested |

## Open Questions

| Question | Notes |
|---|---|
| **Do REPs get adopted as the pack's decision format?** | If yes, each open decision becomes a REP with a status and a champion, and the decisions register becomes an index. That is a real process commitment, not a formatting choice |
| Who is the accepting authority? | Without one, every REP stays `Draft` forever. The project lead is the only candidate today, and a sole accepting authority is its own finding |
| Should this split into six REPs? | Layout, statements, bodies, verification, writing and observability are separately contestable |
| Does `Replaces:` mean documents 01–03 are superseded? | Only their **normative** content. Their reasoning, tensions and open questions are not reproduced here and are not replaced |

---

This document is released under the Creative Commons Attribution 4.0 International licence (CC BY 4.0).
