CurbiCurbi

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.

Verify a bundle in your browser →Download the verifier ↓Conformance vectors
01 · GRANTExact agent, operation, resource, limits.
02 · ATTESTSigned permission for one mutation.
03 · CONFIRMProvider evidence must match, or no receipt.
04 · LOGReceipt appended; signed head over all history.
05 · VERIFYAnywhere, offline, with a public key.

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.

DomainOperationResourceAgentBound amount
Paymentsbilling.refundcharge:ch_1001agent:support5000 usd (minor units)
Schedulingcalendar.bookslot:2026-11-03T15:00Z/dr-leeagent:reception—
Field dispatchdispatch.createjob:hvac-4411agent:ops—
Physical accessaccess.unlockspace:hq/floor-3/door-2agent:concierge—

An UNKNOWN provider outcome produces no receipt and never enters the log; the action is held until reconciled.

Four objects, one envelope.

ObjectSigning domainRole
Attestationcurbi-attest-v1.Permission for one exact mutation, before it runs.
Receiptcurbi-receipt-v1.Provider-confirmed completion of exactly that mutation, bound to the attestation and evidence digests.
Checkpointcurbi-checkpoint-v1.Signed head of the append-only log: size and Merkle root over every receipt.
Bundlenone (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

VectorWhat it provesExpected
valid-four-domainsRefund, booking, dispatch and door unlock: attested, provider-confirmed, logged, with a consistency proof from the size-2 head.verifies
test-key-refusedThe 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-receiptA permission (attestation) presented as a completion receipt. Domain separation makes the signature fail.bad_signature
wrong-positionInclusion proofs swapped between two receipts.not_in_log
signed-but-not-loggedA correctly signed door-unlock receipt that was never appended to the log cannot borrow the logged one's proof.not_in_log
attestation-not-boundThe dispatch attestation attached to the refund receipt.attestation_not_bound
duplicate-itemThe same refund receipt listed twice counts once.duplicate
conflicting-receiptsTwo different DONE receipts for one tenant and idempotency key: issuer misbehaviour, both rejected, never counted as two refunds.conflicting_receipt
equivocationA 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-rewrittenAn older head whose first two receipts were reordered cannot be proven a prefix of the current head.history_unproven
history-truncatedA 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-keyA head signed by a key the verifier does not trust.checkpoint_unknown_key
trust-anchor-is-the-callerKeys inside a bundle are ignored unless the caller opts in: without caller keys the bundle cannot verify.no_trusted_keys
fresh-mode-expiredArchival verification keeps history valid forever; fresh mode (conversation use) refuses receipts past exp.expired
key-windowA 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.

ShowsDoes 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.

Browser, Worker, Deno or Node 20+
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));
CI
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.

Open draft 0.1. Production signing is off and the current public key set lists available keys and their validity dates. Every example uses a key tagged test, which verifiers refuse by default. The reference log runs in memory. Live: the read-only MCP verify_bundle tool checks bundles against caller-supplied trusted keys, with no provider calls or storage. A verified bundle does not prove the provider action happened and grants no authority. Next: production key ceremony and the first production-signed effect, a durable single-writer log with published heads, and witness co-signing. The verifier is MIT-licensed.

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.