Curbi / Proof · open draft 0.1
Proof of Action Protocol (PAP)
Check the signatures on an agent’s action records, their positions in a Merkle log, and consistency with earlier checkpoints you provide. Use a public key you independently trust. The open draft and examples are available now; production signing is off.
One format, four operation fixtures.
The published vectors exercise the local gate with synthetic refund, booking, dispatch and access fixtures. They demonstrate a shared record format; they do not connect to payment providers, calendars, dispatch systems or physical doors.
| Domain | Operation | Resource | Agent | Bound amount |
|---|---|---|---|---|
| Payments | billing.refund | charge:ch_1001 | agent:support | 5000 usd (minor units) |
| Scheduling | calendar.book | slot:2026-11-03T15:00Z/dr-lee | agent:reception | — |
| Field dispatch | dispatch.create | job:hvac-4411 | agent:ops | — |
| Physical access | access.unlock | space:hq/floor-3/door-2 | agent:concierge | — |
An UNKNOWN provider outcome produces no receipt and never enters the log; the action is held until reconciled.
Four objects, one envelope.
| Object | Signing domain | Role |
|---|---|---|
| Attestation | curbi-attest-v1. | Permission for one exact mutation, before it runs. |
| Receipt | curbi-receipt-v1. | Provider-confirmed completion of exactly that mutation, bound to the attestation and evidence digests. |
| Checkpoint | curbi-checkpoint-v1. | Signed head of the append-only log: size and Merkle root over every receipt. |
| Bundle | none (carries the above) | Receipts, inclusion proofs, the head and consistency proofs to older heads: the unit that travels. |
Separate signing domains mean a permission can never pass as a completion, and neither can pass as a log head. Log hashing is RFC 9162 (Certificate Transparency).
16 conformance vectors.
Generated from the real gate with a fixed clock and a public test key, so they regenerate byte for byte. A verifier that conforms gives each its documented result. Run all of them in your browser → · cases.json
| Vector | What it proves | Expected |
|---|---|---|
valid-four-domains | Refund, booking, dispatch and door unlock: attested, provider-confirmed, logged, with a consistency proof from the size-2 head. | verifies |
test-key-refused | The same valid bundle under default production settings: test keys are refused, so demo receipts never pass as real history. | no_trusted_keys |
tampered-amount | $50 refund re-labelled as $500 without re-signing. | bad_signature |
attestation-as-receipt | A permission (attestation) presented as a completion receipt. Domain separation makes the signature fail. | bad_signature |
wrong-position | Inclusion proofs swapped between two receipts. | not_in_log |
signed-but-not-logged | A correctly signed door-unlock receipt that was never appended to the log cannot borrow the logged one's proof. | not_in_log |
attestation-not-bound | The dispatch attestation attached to the refund receipt. | attestation_not_bound |
duplicate-item | The same refund receipt listed twice counts once. | duplicate |
conflicting-receipts | Two different DONE receipts for one tenant and idempotency key: issuer misbehaviour, both rejected, never counted as two refunds. | conflicting_receipt |
equivocation | A verifier that already saw the honest head is shown a different head of the same size: a fork, returned with both signed heads as evidence. | fork_detected |
history-rewritten | An older head whose first two receipts were reordered cannot be proven a prefix of the current head. | history_unproven |
history-truncated | A smaller head delivered with a larger known head is rejected as rollback; same-clock signatures alone do not prove issuer truncation. | history_unproven |
checkpoint-unknown-key | A head signed by a key the verifier does not trust. | checkpoint_unknown_key |
trust-anchor-is-the-caller | Keys inside a bundle are ignored unless the caller opts in: without caller keys the bundle cannot verify. | no_trusted_keys |
fresh-mode-expired | Archival verification keeps history valid forever; fresh mode (conversation use) refuses receipts past exp. | expired |
key-window | A key retired before the later receipts and the head were signed cannot vouch for them. | checkpoint_key_not_valid_at_issue |
What a valid bundle shows, and what it does not.
| Shows | Does not show |
|---|---|
| A key you trust signed these exact receipts. | That the provider’s own systems agree. The receipt binds a digest of the evidence; a signature authenticates the issuer’s statement, not an independently observed business outcome. |
| Each receipt follows a permission for the same mutation, when attached. | That the issuer logged everything it did. Catching omissions needs monitors and witness co-signing (0.2). |
| They sit at these positions under a signed root; supplied earlier checkpoints have valid prefix proofs. | Unseen checkpoints, completeness of the log, or the truth of the underlying provider evidence. |
Verify anywhere.
import { verifyBundle } from './src/proof.mjs'; // one file, zero dependencies
const result = await verifyBundle(bundle, { keys: trustedJwks });
if (!result.ok) console.log(result.reason, result.items.filter(i => !i.ok));node bin/curbi-verify.mjs bundle.json --keys keys.json # exit 0 verified, 1 rejected node bin/curbi-verify.mjs vectors/bundle.valid.json --keys vectors/keys.test.json --allow-test
Built on standards, not claimed as new.
Merkle-logged signed records are Certificate Transparency (RFC 6962/9162), Trillian and Sigstore Rekor. Amount-bound signed authorization is in Google’s AP2 mandates. Tool-execution receipts are published research. Curbi’s part is the binding across the whole action, from grant to attested mutation to provider-confirmed receipt, logged history, and the agent’s customer-facing claim checked against that same receipt, in one provider-agnostic contract with open vectors.
Status.
Run a check. Keep the result.
Check a reply Connect your agent →A pass means no configured rule fired. It grants no execution authority or provider-completion proof.
One real provider. One inspectable receipt.
Stripe test mode: one 50-cent refund, a lost readback response, SQLite restart, GET-only recovery, one receipt. The signed bundle includes the supporting observation; tampering is rejected offline.
Locally qualified against the real Stripe test API. Not a hosted execution service, Stripe-signed proof or production signing.
Inspect the run and verify the bundle → Read the ontology →What PAP establishes
PAP separates a configured rule check, an authorization grant, a signed statement and provider confirmation. An issuer signature authenticates its statement; it does not independently establish that a provider action occurred.
The wire format remains curbi.proof/0.1. Protocol naming does not activate an issuer or change verification rules.