KeelsonDocs
    Judge Mode

    Specification

    What Keelson signs, logs and enforces, stated as requirements.

    Version 1.0.0, 2026-11-12.

    This specification states what Keelson signs, logs and enforces. It describes the deployed system and marks the one part still being built. Each section names the component that implements it; that component's README is the detailed reference. A mandate is what the user allows an agent to buy. A countersign is the TEE legs' signed decision on one cart. An Alibi is the verifiable record of that decision. A verdict is the TEE legs' signed attribution of a dispute.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals, as shown here.

    1. Primitives

    PrimitiveDefinitionImplemented inTest vectors
    Canonical JSONRFC 8785 (JCS)packages/core/src/jcs.ts, engine/corepackages/core/test/vectors/jcs-raw.json
    Domain-separated hashH(dst, v) = SHA-256(utf8(dst) ‖ 0x00 ‖ utf8(JCS(v))), with dst one of keelson/mandate/v1, keelson/mandate-challenge/v1, keelson/cart/v1, keelson/evidence-item/v1, keelson/eval/v1, keelson/alibi/v1, keelson/verdict/v1packages/core/src/hash.ts (hashObject)packages/core/test/vectors/hash.json, packages/core/test/vectors/cart-hash.json
    IdentifiersULID (26 Crockford base32 characters)packages/core/src/ulid.tsnone; covered by packages/core/test/ulid.test.ts
    MoneyInteger minor units and an ISO 4217 codepackages/core/src/money.tspackages/core/test/vectors/money.json
    ZK-friendly hashPoseidon2 over BN254packages/core/src/poseidon2.ts, the Noir circuitspackages/core/test/vectors/poseidon2.json, packages/core/test/vectors/field-from-ascii.json
    Typed signaturesEIP-712, domain {Keelson, 1, chainId, VerdictVerifier} for TEE messagespackages/core/src/eip712.tspackages/core/test/vectors/eip712.json, packages/core/test/vectors/countersign.json, packages/core/test/vectors/report-data.json

    The TypeScript and Rust implementations MUST produce the same output for every vector in packages/core/test/vectors.

    2. Mandate

    A mandate is a keelson.mandate.1 JSON payload, defined by the schema in packages/mandate/src/schema.ts. A verifier MUST reject a payload that has an unknown key or an unknown constraint type, a string that contains a Unicode control (Cc) or format (Cf) character, or a lifetime (exp − iat) longer than 30 days.

    MemberMeaning
    mid, iat, nbf, exp, nonceIdentity and validity window
    modeimmediate or autonomous
    userThe user's sub and the passkey credential (cred: id, RP ID, P-256 JWK)
    agentThe operator's ERC-8004 registry and agent_id, the agent's confirmation key (cnf), and the bond (vault, asset, minimum)
    paymentrail (paypal or card), currency, and constraints: payment.amount_range, payment.budget, payment.allowed_payees, payment.execution_date, payment.card_controls
    checkoutConstraints: checkout.allowed_merchants, checkout.line_items, keelson.category (Google Product Taxonomy ids), keelson.attributes, keelson.returns
    intentThe prompt's summary and hash, and the subjective wishes no machine can check
    policyEngine, version, policy_set_hash and the allowed TEE measurements
    consentTerms hash and the user's acknowledgements
    zk.commitmentC_m, the Poseidon2 commitment to the mandate's checkable fields
    embedPresent when signed inside a framed approval: the top-level origin the passkey assertion must name

    Selective disclosure. The payload is issued as SD-JWT (RFC 9901), so an Alibi can show an arbiter one field without the others.

    Signing. Let H_sd = H("keelson/mandate/v1", payload_SD). The passkey challenge MUST be M = SHA-256("keelson/mandate-challenge/v1" ‖ 0x00 ‖ H_sd ‖ C_m). The user signs M with a WebAuthn passkey on the RP ID keelson.finance, through Secure Payment Confirmation where the browser supports it. What the user signs MUST be the card rendered from the payload, never a model's summary of it. When embed is present, the assertion MUST name that top-level origin.

    Field colors. Each field of the card says who answers for it:

    ColorFieldsWho answers
    Greenagent, cart sanity, currency, total, budget, merchant, deadline, quantityKeelson checks it; the warranty pays when it was wrong
    Ambercategory, variant, return policyThe operator attests it; its bond pays when it was wrong
    Graysubjective wishes, fields the drafting models disagree onOnly the user can judge it

    Evidence MAY raise a field's color: a merchant-signed checkout, or a zkTLS proof of the field.

    Compilation. packages/compiler turns a natural-language request into a mandate. Two models draft it independently. The merge MUST NOT be wider than either draft, and a field the drafts disagree on MUST be shown gray. A third model checks the card against the request. Nothing a model returns is signed without the user's passkey.

    3. Policy

    Mandate rules are a Cedar policy set, in engine/policy. The evaluator MUST fail closed: an error is a DENY. Every decision records a keelson.eval/1 trace whose hash is evalHash. Extras a user adds MUST compile only to forbid, so they can narrow a mandate and never widen it.

    cedar-policy-symcc proves that the policy set states the rules in engine/policy/policy/spec.cedar. The proof bundle is committed, checked for freshness in CI and attested on main. The WebAssembly evaluator in packages/policy is pinned byte for byte to the Rust one by a parity job.

    4. Countersign

    The engine runs in two TEE legs built from one reproducible image, in engine/tee:

    • Leg A, Intel TDX on Phala Cloud, holds the KMS root, issues credentials, keeps the budget ledger, reads PayPal and produces the Observation of each countersign.
    • Leg B, an AWS Nitro Enclave with no network and no KMS root, verifies everything again and countersigns over leg A's Observation. Leg B MUST NOT trust leg A's decision; it re-evaluates the request itself.

    Each leg MUST sign every decision, ALLOW or DENY, with a secp256k1 key generated in RAM at boot. The signed message is the EIP-712 Countersign:

    Countersign(bytes32 requestId, bytes32 mandateHash, bytes32 mandateCommitment, bytes32 cartHash,
                uint256 agentId, bytes32 merchantId, uint64 amount, uint16 currency, uint8 category,
                bytes32 evidenceRoot, bytes32 evalHash, bytes32 policySetHash, uint8 decision,
                uint32 failedFieldsMask, uint40 issuedAt, uint40 expiresAt)

    failedFieldsMask is pinned by packages/core/test/vectors/failed-fields-mask.json.

    Idempotence. A request id is answered once per mandate. Retrying the same input MUST return the same countersign, and any other input under that id, including the same id on another mandate, MUST fail with replay (engine/tee/src/budget.rs).

    Budget. Leg A's available budget is max − captured − live reservations, with captured taken from its own PayPal read. Leg A MUST NOT ALLOW a cart whose total exceeds it.

    Two of two. A purchase is countersigned by both legs. When leg B is unreachable, leg A MAY sign alone; every artifact of that purchase MUST then say degraded. On chain, a degraded result waits a longer veto window (DEGRADED_WINDOW, 72 hours, in contracts/src/KeelsonBondVault.sol). Vectors: packages/core/test/vectors/countersign-2of2.json, packages/core/test/vectors/countersign-degraded.json.

    Attestation binding. Each leg's attestation MUST carry

    report_data = keccak256(abi.encode(keccak256("KEELSON_TEE_SIGNER_V1"), chainid, bondVault, teeKind,
                  signer, policySetHash, engineBuildId, bootEpoch, issuerJwkThumbprint)) ‖ blockHash

    with blockHash from a recent Arbitrum block (engine/tee/src/report_data.rs). VerdictVerifier MUST register a signer only from such an attestation, under an allowed measurement, and only for 72 hours (SIGNER_TTL in contracts/src/VerdictVerifier.sol).

    5. Evidence

    What the agent saw is sealed item by item, in packages/evidence: the product and cart data, what the model received, a DOM snapshot in keelson-dom-v1, the accessibility tree, a screenshot, MHTML, a normalized HAR, outside observations and the PayPal amount countersigned.

    Each item is a salted leaf (keelson/evidence-item/v1) of an RFC 6962 Merkle tree whose root is evidenceRoot, so one item can be shown without the others and an unrevealed item cannot be guessed from its hash. PII MUST be redacted from every public item; the originals are kept encrypted. A hidden-text detector records text a page hid from people but showed to the agent.

    Two kinds of outside testimony MAY be attached:

    • Channel3, a third party's live product data, compared field by field with what the agent saw. It is testimony, not proof.
    • TLSNotary: leg A notarizes a TLS session with the store's public product endpoint and signs a WebFact (hostHash, urlPathHash, tCapture, rangeCommitment, attId). TLSNotary is alpha software.

    6. Alibi

    An Alibi is the bundle that lets anyone check a purchase without trusting Keelson. It is built by packages/alibi and checked by apps/verify. It holds the log leaf, its inclusion proof, the on-chain anchor, both TEE attestations and signatures, the selectively disclosed mandate, the evidence manifest, the evaluation trace, optional ZK proofs, card records and PayPal references. It is served as JSON, as a .alibi file, and as a PDF with the file embedded and a QR link to verify.keelson.finance. The Alibi hash fields are pinned by packages/core/test/vectors/alibi.json.

    A verifier MUST check an Alibi against trust roots pinned in its code, never against roots the Alibi or a Keelson server supplies: the log keys and witness quorum, the TSA roots, Intel's and AWS's attestation roots, the allowed engine builds, and the contract addresses. The reference verifier runs in the browser and reads the chain once, in one Multicall. A verifier MUST NOT report an Alibi as valid when any check fails. Each check, with a command that runs it, is listed in Verify every claim.

    7. Transparency log

    Every countersign, ALLOW or DENY, MUST be a leaf of the public log in services/log:

    leaf      = "keelson.finance/alibi/v1\n" ‖ JCS(object)
    leaf_hash = SHA-256(0x00 ‖ leaf)

    The object MUST hold hashes and ids only: agent_id, alibi_id, cart_hash, countersign_digest, eval_hash, evidence_root, issued_at, mandate_commitment, mandate_hash, measurements, mode, paypal_ref_commit, policy_set_hash, optional prev_alibi_by_user, and tee_sigs. A user's kill switch is logged as a keelson.finance/stop/v1 leaf.

    PropertyHow
    Formatc2sp.org/tlog-tiles on Tessera
    Originslog.keelson.finance/alibi/2026-1 (Arbitrum Sepolia), log.keelson.finance/alibi-live/2026-1 (Arbitrum One, not yet anchored)
    Log signaturesEd25519 and ML-DSA-44 on every checkpoint. The Ed25519 key is derived inside leg A from the dstack KMS and never leaves it
    Witnesses2 of 3 Keelson-run witnesses on three providers cosign every published checkpoint
    External recordCheckpoints co-logged hourly in Sigsum's seasalp log, cosigned by at least two of the Glasklar, Mullvad and Tillitis witnesses
    AnchoringCheckpointAnchor accepts a witnessed checkpoint signed by both TEE legs, with an RFC 6962 consistency proof from the previous anchor; at least hourly or every 1,000 leaves
    EquivocationLogBond.proveEquivocation(noteA, noteB) slashes the log's PYUSD bond when two checkpoints signed by its key state different roots for one size, verified on chain with an Ed25519 verifier; 10% goes to the prover and the rest to KeelsonReserve
    MonitoringA public monitor (services/monitor) checks signatures, quorum, consistency, every leaf's format and inclusion, and the Sigsum co-logging. Anyone can run it

    8. PayPal

    PayPal is the rail. Keelson uses the Orders v2 and Payments v2 APIs with intent: AUTHORIZE, through packages/paypal and services/api:

    1. Hold. One AUTHORIZE order per mandate for its cap, approved with the JavaScript SDK v6, then authorized. A mandate on the PayPal rail that allows several merchants MUST instead hold each cart on its own (one order per cart), since one authorization pays one payee.
    2. Capture. Each countersigned ALLOW MUST be captured from the hold for exactly the countersigned total. final_capture MUST be true only for the capture that uses up the hold, or for a per-cart hold. invoice_id names the purchase, and PayPal-Request-Id MUST be derived from the countersign digest, so a retry cannot charge twice.
    3. Close. The remainder MUST be voided when the mandate ends. An authorization older than three days, the end of PayPal's honor period, is reauthorized.
    4. Webhooks. A webhook MUST be verified before it is used, and it only points at a resource: Keelson MUST act on an authenticated GET of that resource, never on the notification body.
    5. Disputes are read, evaluated and answered through the Disputes API (section 10).
    6. Refunds and Payouts MUST carry a verdict reference and a PayPal-Request-Id derived from it, so a retry is the same operation.

    The identifiers Keelson writes into PayPal objects (custom_id, invoice_id, PayPal-Request-Id and the references on refunds and payouts) are an open convention, specified in the PayPal Agent Mandate Extension.

    Card rail. A mandate with payment.rail: card holds the user's budget on PayPal, on Keelson's account, which pays the card network; merchants that take cards are paid through a single-use card. The card MUST be issued only after both legs ALLOW, MUST be capped at the countersigned total, and MUST be limited to the mandate's merchant category codes and country. Every authorization the card network sends MUST be checked again by both legs (POST /v1/card/authorize), and the settled charge is captured from the PayPal hold. See card mandates.

    9. On chain

    Contracts are deployed on Arbitrum Sepolia from contracts. They are unaudited.

    ContractRole
    VerdictVerifierRegisters TEE signers from attestations (Automata DCAP for TDX, a Nitro validator) and verifies 2-of-2 and degraded signatures
    KeelsonBondVaultHolds each operator's PYUSD bond by ERC-8004 agentId, locks a coverage per purchase, and slashes on a verdict
    KeelsonReserveERC-4626 vault of premiums, which pays warranties when Keelson is at fault
    CheckpointAnchorAnchors witnessed checkpoints, enforcing consistency
    LogBondPuts PYUSD behind the log's key
    MandateViolationCourt, AmberContradictionCourt, ViolationBondPoolConvictions from ZK proofs, without the TEEs
    ZK verifiersUltraHonk verifiers for C1, C2a, C2b, C3 and C1+C3, each with a reference build
    RegisteredUserSetRoots of the registered-user tree, for C3
    KeelsonReputationReporter, KeelsonValidationRegistryERC-8004 reputation and validation records

    Governance. A timelock (5 minutes on testnets, 48 hours on Arbitrum One) is the only admin of the access manager. A 2-of-3 guardian Safe can cancel timelock operations, veto slashes and revoke signers. A relayer pays gas, so operators, merchants and buyers never hold ETH.

    10. Disputes and attribution

    A PayPal dispute is attributed by an ordered decision list compiled to Cedar, with symcc proofs that it is total, exclusive and reachable (engine/policy). Attribution fails closed.

    Party at faultRulesConsequence
    OperatorR00 (a proof fails), R31 (double capture), R61 (amber field failed)Bond slashed
    KeelsonR30 (single-use mandate countersigned twice), R60 (green field failed)Reserve pays the warranty
    MerchantR11, R21, R35, R40, R50Evidence to PayPal
    UserR10, R20, R32, R62, R70Evidence to PayPal

    Both TEE legs sign the EIP-712 Verdict:

    Verdict(bytes32 disputeId, bytes32 coverageId, uint256 agentId, address payee, uint96 amount,
            uint8 ruleId, uint8 liable, uint8 fieldId, bytes32 evidenceHash, bytes32 inputsHash,
            bytes32 policySetHash, bytes32 checkpointRoot, uint40 issuedAt, uint40 deadline)

    disputeId = keccak256(paypalDisputeId ‖ coverageId) is a nullifier: a verdict MUST NOT be applied twice for one disputeId. A merchant is settled in PYUSD on chain or by PayPal Payout, as it chooses. PayPal adjudicates the dispute; the verdict decides the evidence, the slash and the warranty.

    11. Zero-knowledge circuits

    Noir circuits proved with Barretenberg UltraHonk, in circuits:

    CircuitStatementStatus
    C1 mandate_predicate (circuits/c1)The countersigned cart satisfies the mandate C_m commits to, without revealing itProved inside leg A for an ALLOW; verifier on chain
    C2a mandate_violation (circuits/c2a)A countersigned cart violates its mandate (amount, currency, category, expiry, merchant non-membership)MandateViolationCourt on chain
    C2b amber_contradiction (circuits/c2b)A notarized TLS response contradicts an amber field the operator attestedAmberContradictionCourt on chain
    C3 anonymous_mandate (circuits/c3)A registered user's passkey signed the mandate, without revealing which userProved by the API; verifier on chain
    C1+C3 anonymous_purchase (circuits/c1c3)C1 and C3 composed by recursion in one proofProved by the API; verifier on chain
    SP1 engine proof (engine/zkvm)The engine's evaluation of a disputed purchase produced the countersigned digestIn progress

    12. Agents

    Agents reach Keelson over REST or MCP (https://api.keelson.finance/mcp, Streamable HTTP), an OAuth 2.1 protected resource (services/mcp). An operator's agent uses client credentials and closes mandates with its own key. A host acting for a user uses the authorization code grant with PKCE and MUST buy only under mandates the user approved for it. The kill switch, per-host disconnects, and push and email alerts are described in how it works. The OpenAPI document is in openapi.

    13. Risk

    Every operator has a trust grade, a premium and a minimum bond, set by the red team's results and its own record (GET /v1/operators/{agentId}/grade). The risk model is described in services/api/README.md.

    14. Limits

    What Keelson does not protect against is stated in the threat model.