Concept
Receipt
A signed record of one decision that anyone can check offline.
Definition
A receipt is a body and an Ed25519 signature. The signature covers the canonical JSON of the body: keys sorted by UTF-16 code unit, integers only, no whitespace, strings escaped as JavaScript's JSON.stringify escapes them. The public key is published at /.well-known/jwks.json under the kid the body names in agent_kid, and a receipt verifies only under that key. Each receipt names the one before it in the same tenant stream through prev.
Shape
interface SignedReceipt {
body: {
v: "ag47.receipt.v1";
ts_edge: number;
tenant: string;
agent_kid: string; // signing key id
delegation: string | null;
action_hash: string; // sha256 of method, path, authority, request id
policy_bundle_hash: string;
flag_state_hash: string;
outcome: Outcome;
rule_id: string;
prev: string; // hash of the previous receipt, or 64 zeros
bypass: boolean;
verification?: Verification;
};
sig: string; // base64url Ed25519 over canonical(body)
}
Invariants
| Invariant | Checked by |
|---|---|
| Receipts verify offline, reject any change to the body, and replay to the same bytes. | packages/engine/src/engine.test.ts: "receipts verify offline, reject tamper, and replay to the same bytes" |
| Canonical JSON sorts keys and rejects floats. | packages/engine/src/engine.test.ts: "canonical json sorts keys and rejects floats" |
| A receipt signed by any published key other than the one its agent_kid names does not verify. | apps/docs/test/replay.test.ts: "a receipt signed by another published key does not verify" |
| Receipts chain per tenant. | apps/api/src/api.test.ts: "decide receipts chain per tenant and story runs continue the stream (AG4-278)" |
| Every quickstart on this site verifies a real receipt in CI. | apps/docs/scripts/run-quickstarts.mjs |
Out of scope
- A global chain. The prev chain is per tenant stream inside one Worker isolate; a restarted isolate starts a new stream at 64 zeros. Nothing orders receipts across isolates yet: the published transparency head appends recent receipt hashes only to a per-data-center cache, best effort, up to 64 leaves.
- The request body. A receipt hashes the action; it does not store the content.
- Whether the sender was an AI agent, unless verification says verified.
Evidence grade
Evidence grade
Rung 2 · Operational
- Covers
- Which rule decided this request and that the record was not changed after signing. Not whether the sender was an AI agent.
- Confidence
- None stated. No calibrated confidence applies to this record.
- Calibration
- Uncalibrated
- Known error rates
- None measured. Silvector publishes no detection error rate yet.
- Basis
- Deterministic decide() rule allow.default
- Ed25519 signature over the canonical receipt body, checked against the published JWKS
- Bias risks
- The decision is only as good as the facts it was given. A request with no signature claim is not_identified, which is not a finding that a person or an agent sent it.
- Next step
- To ask who was behind a series of requests, keep the receipts and escalate to rung 3 with at least two independent signal families. How escalation works
- Assigned by
- These docs, from the rung definitions. The grade is not a signed field of the receipt.