AGENTS.md · diff

git:20260817.a1b09c5 to git:20260817.3fd8893

44 added, 13 removed. Audit A to A.

# 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
- `principal_signed_envelope.json` (the only really-signed fixture).
+ - `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 the other
- three), `aph-ts` (wasm binding; outside default-members).
+ 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 the JS boundary is JSON text in both
- directions — never route envelopes through a JS object round-trip.
+ 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.**
- `examples/principal_signed_envelope.json` carries four real Ed25519
- signatures over its bytes. Regenerate it — the recipe is in
- `skills/spec/SKILL.md`.
- 7. **Test keys only.** Fixtures sign with RFC 8032 test vectors. Never place
- production-looking key material — and never any private key — in a
- committed or published artifact.
+ 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`.