Immutable. This exact content is served forever at /api/v1/blob/3a2d8991cb319a38.
# AGENTS.md — working in the APH repository APH (Agent per Human) is a notarization protocol for outbound agent communications: a human-authorized Notary Service issues a W3C Verifiable Credential 2.0 envelope binding a specific payload, channel, agent, and policy decision to a human principal's authority. This repository holds the normative spec, a reference Rust implementation, a wasm/TypeScript binding, conformance fixtures, and an N Lang spec bundle. **Deep protocol knowledge lives in `skills/spec/SKILL.md`** (open Agent Skills format — load it before answering protocol questions or touching envelope JSON). Task recipes live in `commands/validate.md` and `commands/conformance.md`. This file carries only orientation, commands, and the invariants you must not break; it deliberately does not duplicate the skill. ## Repo map - `spec/aph-0.1.md` — the normative spec (wire shape §7, signing §8, key discovery §8.4, flows §9, errors §11). `spec/a2a-extension.md`, `spec/security-considerations.md` beside it. - `examples/` — golden envelope fixtures, one per channel kind, plus the FOUR really-signed ones: `principal_signed_envelope.json`, `es256_signed_envelope.json`, `detached_jws_envelope.json`, and `ts_minted_envelope.json` (minted by the TypeScript implementation, verified by the Rust). Enumerate the directory before stating a count; it has moved more than once. - `interpreters/rust/` — cargo workspace: `aph-core` (types + validation), `aph-conformance` (suite + fixture generator; its `tests/` also carry the multi-party exchange e2e — two-party admit-plus-refusals, three-party relay, cross-notary revocation — the only tests where two parties exchange anything, and the assembled §8.3 recipient algorithm lives in their shared harness), `aph-cli` (binary `aph`: `validate | inspect | golden`), `aph-resolver` (optional §8.4.5 DNS TXT + §8.4.4 `did:web` adapters over `aph-core`'s ports; the ONLY crate that may carry HTTP/DNS/runtime deps — never add an edge to it from any other member), `aph-ts` (wasm binding) and `aph-py` (pyo3 binding, Python module `aph`) — both outside default-members, both at deliberate export parity with each other, and neither a second implementation of anything. - `interpreters/typescript/` — the SECOND implementation, and the one that IS. Full mint + verify written from the spec and the published examples, sharing no code with the workspace above: its own RFC 8785 canonicalizer, strict parser, §7.2.1 bases, base58btc and `did:key` codecs; every signature through WebCrypto. Node >= 20, zero runtime dependencies. ⛔ Not wasm and not `aph-ts` — do not conflate them, and never make one import the other. It cross-verifies with the Rust in both directions through COMMITTED BYTES; no node runs in cargo and no cargo runs in node. - `snapp/` — the exported N Lang bundle (generated by `nlang export`; its integrity digest is content-derived — never hand-edit it). - `skills/`, `commands/`, `.claude-plugin/` — the agent pack this file is the Codex/agents.md entry point for. ## Build and test (exactly what CI runs) From `interpreters/rust/`: ``` cargo test # workspace default members cargo test -p aph-ts # the wasm binding's native tests (NOT in the default set) wasm-pack test --node aph-ts # wasm32 smoke over the signed golden cargo test -p aph-py # the Python binding (NOT in the default set; needs a shared libpython) cargo run -q -p aph-cli -- validate <envelope.json> ``` From `interpreters/typescript/` (the second implementation; a separate CI job): ``` npm install # the TypeScript compiler, and nothing else npm run build # tsc -> dist/ npm test # node --test dist/test/ (Node 20 and 22 in CI) npm run mint # regenerates examples/ts_minted_envelope.json ``` ## Invariants — break these and CI (or a verifier somewhere) breaks you 1. **Strict parsing everywhere.** Every envelope struct is `deny_unknown_fields`; an unknown key is a hard error, never dropped. 2. **The `proof` union is position-sensitive.** It is untagged: a single object (`NotaryAttested`) or a two-proof chain (`PrincipalSigned`, principal first). This is why EVERY binding boundary is JSON text in both directions — never route envelopes through a host-language object round trip, in JS or in Python. Which arm deserializes is decided by the bytes; an object route lets a second deserializer decide it instead. 3. **Absent `attestationMode` means `NotaryAttested`.** A `PrincipalSigned` label is never trusted alone: structure is checked (`APH_E013` on a forged label), and mode downgrades are refused (`APH_E012`). 4. **The error taxonomy is a closed set of fifteen** (`APH_E001`–`APH_E015`). `APH_E014` is TERMINAL ABSENCE of a discovery surface and nothing else — absence advances the §8.4.6 order (`did:key` → DNS TXT → `did:web`). A surface that was offered and then FAILED never falls through: it refuses on the spot under the failure's OWN code (`APH_E008` unreachable, `APH_E003` validity window, `APH_E010` algorithm, …), never a blanket one. `APH_E015` is a delegation mandate REVOKED per the §6.3.3 status list — a withdrawn authorization over still-valid signatures, held distinct from `APH_E003` (authority that ran out on schedule). `APH_E008` now covers any protocol-mandated fetch from a notary-hosted surface, the status list included, not only a notarization request that timed out. 5. **A status verdict must name the key that authenticated it.** `aph_core::credential_status::check_envelope_status` takes the issuer's verifying key BY VALUE, plus a caller-supplied gzip expander (this crate carries no compression dependency — it links into wasm and into a kernel). Neither is an oversight: an unverified status list is an unauthenticated assertion about whether someone's authority still holds, so an unauthenticated verdict is made unrepresentable rather than merely discouraged. Do not add a convenience wrapper that resolves the key internally or defaults it — the whole point is that a caller cannot skip it. Integration recipe in `skills/spec/SKILL.md`. 6. **Signed fixtures are never text-edited.** Four published examples carry real signatures over their own bytes. Three have Rust generators under `interpreters/rust/aph-conformance/tests/`; the fourth, `examples/ts_minted_envelope.json`, is minted by `interpreters/typescript` and regenerated with `npm run mint`. Both recipes are in `skills/spec/SKILL.md`. 7. **Test keys only.** Fixtures sign with published test vectors — RFC 8032 §7.1 for Ed25519, RFC 6979 A.2.5 and RFC 7515 A.3.1 for P-256, since no repeated-byte fake names a valid P-256 scalar. Cite the document a scalar comes from in the code. Never place production-looking key material — and never any private key that is not a published test vector — in a committed or published artifact. 8. **Domains are `squillo.com`.** The former domain is retired; do not reintroduce it in examples or docs. 9. **Name-hygiene CI.** Every push runs brand/name hygiene checks over the whole tree; keep tool-vendor names and internal-repo path references out of committed files, and write interpreter paths as `interpreters/rust/<crate>`. ## The live reference deployment `did:web:aph-notary.squillo.com` is a live §8.4.4 publication surface you can resolve against. Its degrade shape and what resolving it exercises are described in `skills/spec/SKILL.md`.