@codespar/cli 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/commands/mandate-verify.js +98 -19
- package/dist/commands/mandate-verify.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mandate-codec.js +31 -20
- package/dist/mandate-codec.js.map +1 -1
- package/package.json +1 -1
- package/templates/bills-agent/README.md +1 -1
- package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
- package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
- package/templates/bills-agent/agents/bills-agent/README.md +6 -5
- package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
- package/templates/bills-agent/packages/agent-core/README.md +14 -1
- package/templates/bills-agent/packages/agent-core/package.json +1 -1
- package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
- package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/checkout-agent/LICENSE +21 -0
- package/templates/checkout-agent/README.md +28 -0
- package/templates/checkout-agent/_gitignore +10 -0
- package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
- package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
- package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
- package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
- package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
- package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
- package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
- package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
- package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
- package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
- package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
- package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
- package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
- package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
- package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
- package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
- package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
- package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
- package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
- package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
- package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
- package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
- package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
- package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
- package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
- package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
- package/templates/checkout-agent/package.json +38 -0
- package/templates/checkout-agent/packages/agent-core/README.md +34 -0
- package/templates/checkout-agent/packages/agent-core/package.json +24 -0
- package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
- package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
- package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
- package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
- package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
- package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
- package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
- package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
- package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
- package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
- package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
- package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
- package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
- package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
- package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
- package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
- package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
- package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
- package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
- package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
- package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
- package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
- package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
- package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
- package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
- package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
- package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
- package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
- package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
- package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
- package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
- package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
- package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
- package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
- package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
- package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
- package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
- package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
- package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
- package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
- package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
- package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
- package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
- package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
- package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
- package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
- package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
- package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
- package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
- package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
- package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
- package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
- package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
- package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
- package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
- package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
- package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
- package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
- package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
- package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
- package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
- package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
- package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
- package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
- package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
- package/templates/checkout-agent/tsconfig.base.json +16 -0
- package/templates/checkout-agent/vitest.config.ts +12 -0
- package/templates/collections-agent/README.md +6 -1
- package/templates/collections-agent/agents/collections-agent/README.md +12 -8
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
- package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
- package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
- package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
- package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
- package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
- package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
- package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
- package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
- package/templates/collections-agent/package.json +1 -0
- package/templates/collections-agent/packages/agent-core/README.md +14 -1
- package/templates/collections-agent/packages/agent-core/package.json +1 -1
- package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
- package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
- package/templates/hello-agent/README.md +1 -1
- package/templates/hello-agent/packages/agent-core/README.md +14 -1
- package/templates/hello-agent/packages/agent-core/package.json +1 -1
- package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
- package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/kits.lock.json +29 -6
- package/templates/supplier-payments-agent/README.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
- package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
- package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
- package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
- package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
- package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
- package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
- package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
- package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
- package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
- package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
- package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
- package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
- package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
- package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
- package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
- package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
- package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
- package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
- package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
- package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
- package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
- package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
- package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
- package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
- package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
- package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
- package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
- package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `receipt-verification`: the half of a receipt a party OUTSIDE CodeSpar can
|
|
3
|
+
* check.
|
|
4
|
+
*
|
|
5
|
+
* A receipt carries two signatures and they prove different things to
|
|
6
|
+
* different people.
|
|
7
|
+
*
|
|
8
|
+
* `receipt_sig` HMAC over the chain, under the consumer's secret.
|
|
9
|
+
* Verifying it means holding the key that also MINTS
|
|
10
|
+
* it, so it proves the payment to whoever runs the
|
|
11
|
+
* agent and to nobody else.
|
|
12
|
+
* `receipt_sig_ed25519` Ed25519 over `codespar-receipt:v1:<id>:<chain>`,
|
|
13
|
+
* made with CodeSpar's platform issuer key, whose
|
|
14
|
+
* PUBLIC half is served with no credential at
|
|
15
|
+
* `/.well-known/codespar-receipt-keys.json`. That one
|
|
16
|
+
* proves the payment to an accountant, a marketplace
|
|
17
|
+
* reconciling a payout, or a court — parties that
|
|
18
|
+
* have no relationship with CodeSpar at all.
|
|
19
|
+
*
|
|
20
|
+
* This module checks the second. It imports `node:crypto` and two modules of
|
|
21
|
+
* this package that import nothing else (`receipt-chain`, `hash`): no
|
|
22
|
+
* CodeSpar SDK, no key material, no token, no network of its own (the key
|
|
23
|
+
* document is passed in, or fetched by a function the caller supplies). A
|
|
24
|
+
* third party can read the forty lines that matter and reimplement them.
|
|
25
|
+
*
|
|
26
|
+
* TWO LAYERS, and a verdict says which one it reached. The signature proves
|
|
27
|
+
* CodeSpar sealed a receipt with THIS id and THIS chain; `verifyReceiptWithKeys`
|
|
28
|
+
* checks that and nothing else, which is why it survives the proof bundle's
|
|
29
|
+
* masking — the bundle masks the payee, and the signature covers the id and
|
|
30
|
+
* the digest, not the copy in front of you. The chain is a SHA-256 over the
|
|
31
|
+
* links of the Control Record, and since ent#1670 CodeSpar publishes how to
|
|
32
|
+
* recompute it (`chain_recipe`, beside the keys). `verifyReceiptRead` does:
|
|
33
|
+
* from the API's receipt READ, under the recipe of the SAME document whose
|
|
34
|
+
* key verified the signature, it recomputes the chain and holds it against
|
|
35
|
+
* the signed one — and for a v4 chain, holds the sealed approval link against
|
|
36
|
+
* the local approval artifact. `verified` from it means all of that held;
|
|
37
|
+
* `signature_only` means the signature held and the body could not be bound
|
|
38
|
+
* (a v1–v3 chain, a masked payee, no recipe), which is not a failure and is
|
|
39
|
+
* not a verification of the body either. See `docs/OPEN_QUESTIONS.md` §47.
|
|
40
|
+
*
|
|
41
|
+
* WHICH KEY SET. The verifier points at ONE deployment's
|
|
42
|
+
* `/.well-known/codespar-receipt-keys.json` — the default is production, and
|
|
43
|
+
* `--url` names another. Since ent#1641 a deployment names its keys by
|
|
44
|
+
* namespace (`#production-2`, `#staging-2`), so a receipt checked against the
|
|
45
|
+
* wrong set finds no key of its name (`unknown_key`); the retired `#1` is
|
|
46
|
+
* still published by both with different keys, and a receipt sealed under it
|
|
47
|
+
* checked against the wrong set reads `tampered`. `docs/OPEN_QUESTIONS.md` §47.
|
|
48
|
+
*
|
|
49
|
+
* The signed string is the enterprise's `receiptSigningString`
|
|
50
|
+
* (`packages/api/src/receipt-signature.ts`, ent#1633), reproduced here rather
|
|
51
|
+
* than imported, because a verifier that has to install the signer's code
|
|
52
|
+
* proves nothing about the signer.
|
|
53
|
+
*/
|
|
54
|
+
import { createPublicKey, verify as verifyDetached } from "node:crypto";
|
|
55
|
+
import { itemsHash } from "./hash.js";
|
|
56
|
+
import { readChainRecipe, readSealedApproval, recomputeChain, sameApprovalHash, type SealedApproval } from "./receipt-chain.js";
|
|
57
|
+
import type { ExecutionItem } from "./types.js";
|
|
58
|
+
|
|
59
|
+
/** The domain tag. A receipt names the version it was sealed under through
|
|
60
|
+
* this literal, so a future `v2` does not invalidate a `v1` signature. */
|
|
61
|
+
export const RECEIPT_SIGNATURE_DOMAIN = "codespar-receipt:v1";
|
|
62
|
+
|
|
63
|
+
/** The unauthenticated key set of the production API. `--url` points the
|
|
64
|
+
* verifier at another deployment (staging), `--keys` at a file. */
|
|
65
|
+
export const DEFAULT_RECEIPT_KEYS_URL = "https://api.codespar.dev/.well-known/codespar-receipt-keys.json";
|
|
66
|
+
|
|
67
|
+
/** The key set of a deployment, from its base URL. */
|
|
68
|
+
export function receiptKeysUrl(baseUrl: string): string {
|
|
69
|
+
return new URL("/.well-known/codespar-receipt-keys.json", baseUrl).toString();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The exact bytes the Ed25519 signature covers. Every segment is fixed-shape:
|
|
73
|
+
* the domain is a literal, the id is `rcpt_` + 22 url-safe characters, and the
|
|
74
|
+
* chain is 64 hex. No free text reaches this string. */
|
|
75
|
+
export function receiptSigningString(receiptId: string, chain: string): string {
|
|
76
|
+
return `${RECEIPT_SIGNATURE_DOMAIN}:${receiptId}:${chain}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The six honest answers. Five of them are not "invalid", and keeping them
|
|
81
|
+
* apart is the point of the module:
|
|
82
|
+
*
|
|
83
|
+
* `verified` the signature is CodeSpar's, over this id and this chain.
|
|
84
|
+
* `tampered` a signature is there and it does not match. A refusal.
|
|
85
|
+
* `unsigned` no signature on this copy. NOT a failure: every receipt
|
|
86
|
+
* sealed before CodeSpar added Ed25519 has none and never
|
|
87
|
+
* will, because signing them today with today's key would
|
|
88
|
+
* attest to what the database says now, not to what happened.
|
|
89
|
+
* `unknown_key` the `kid` is not in the key set. Retired keys stay
|
|
90
|
+
* published precisely so old signatures keep verifying, so a
|
|
91
|
+
* missing kid means another deployment, or a revoked key.
|
|
92
|
+
* `unreachable` no usable key set. UNKNOWN — never "invalid".
|
|
93
|
+
* `malformed` the file is not a receipt this module can read. Distinct
|
|
94
|
+
* from `unsigned` on purpose: answering "unsigned" for an
|
|
95
|
+
* arbitrary JSON file would state that it is a receipt from
|
|
96
|
+
* before the capability existed, which is a claim about a
|
|
97
|
+
* file nobody checked.
|
|
98
|
+
*
|
|
99
|
+
* And three that only the body check (`verifyReceiptRead`) reaches, each
|
|
100
|
+
* after the signature verified:
|
|
101
|
+
*
|
|
102
|
+
* `signature_only` the body could not be bound to the chain: a v1–v3
|
|
103
|
+
* chain, a masked payee, no read, no recipe. NOT a
|
|
104
|
+
* failure, and NOT `verified`: the signature is
|
|
105
|
+
* CodeSpar's, and nothing here says the body beside it
|
|
106
|
+
* is the body it sealed.
|
|
107
|
+
* `chain_mismatch` the body, recomputed under the published recipe, is
|
|
108
|
+
* NOT the signed chain. The signature is genuine and
|
|
109
|
+
* the body in front of you is not the one it covers.
|
|
110
|
+
* `approval_mismatch` the body is the signed one, and the approval link it
|
|
111
|
+
* seals is NOT the local artifact's: this payment was
|
|
112
|
+
* not sealed against the list that artifact approved.
|
|
113
|
+
*/
|
|
114
|
+
export type ReceiptVerdict = "verified" | "tampered" | "unsigned" | "unknown_key" | "unreachable" | "malformed" | "signature_only" | "chain_mismatch" | "approval_mismatch";
|
|
115
|
+
|
|
116
|
+
/** What recomputing the chain from the body found. */
|
|
117
|
+
export interface ChainCheck {
|
|
118
|
+
/** `recomputed`: equal to the signed chain. `mismatch`: not equal. `not_recomputed`: see `reason`. */
|
|
119
|
+
status: "recomputed" | "mismatch" | "not_recomputed";
|
|
120
|
+
version: number | null;
|
|
121
|
+
/** The digest recomputed from the body, when it could be. */
|
|
122
|
+
recomputed: string | null;
|
|
123
|
+
reason: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** What the sealed approval link was held against. Present on a v4 chain that recomputed. */
|
|
127
|
+
export interface ApprovalLinkCheck {
|
|
128
|
+
/** `matched`: the artifact's hashes are the sealed ones. `mismatch`: they are not. `not_compared`: no artifact was given. */
|
|
129
|
+
status: "matched" | "mismatch" | "not_compared";
|
|
130
|
+
sealed: SealedApproval;
|
|
131
|
+
artifact: { approval_id: string; items_hash: string; batch_hash: string | null } | null;
|
|
132
|
+
reason: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The part of an approval artifact the approval link is held against: what was approved, never who. */
|
|
136
|
+
export interface ApprovalClaim {
|
|
137
|
+
approval_id: string;
|
|
138
|
+
items_hash: string;
|
|
139
|
+
/** When present, `items_hash` is recomputed from them first: a hash that does not describe its own list proves nothing about the list. */
|
|
140
|
+
items?: readonly ExecutionItem[];
|
|
141
|
+
batch?: { batch_hash: string };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ReceiptVerification {
|
|
145
|
+
verdict: ReceiptVerdict;
|
|
146
|
+
/** The receipt this is about, when the document names one. */
|
|
147
|
+
receipt_id: string | null;
|
|
148
|
+
/** The key the receipt names, when it names one. */
|
|
149
|
+
kid: string | null;
|
|
150
|
+
/** `active` or `retired`, as the key set publishes it. A retired key signs
|
|
151
|
+
* nothing new and verifies everything it already signed. */
|
|
152
|
+
key_status: string | null;
|
|
153
|
+
/** The bytes that were verified, so a reader can reproduce the check by hand. */
|
|
154
|
+
signing_string: string | null;
|
|
155
|
+
/** Where the keys came from, or `null` when no key was needed. */
|
|
156
|
+
keys_from: string | null;
|
|
157
|
+
/** A stable machine code under the verdict. */
|
|
158
|
+
reason: string;
|
|
159
|
+
/** One sentence for a person. Never a stack trace. */
|
|
160
|
+
message: string;
|
|
161
|
+
/** The body check, when one was attempted: only `verifyReceiptRead` attempts it, and only on a signature that verified. */
|
|
162
|
+
chain_check?: ChainCheck;
|
|
163
|
+
approval_check?: ApprovalLinkCheck;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** One published key, RFC 8037 shape, as the JWKS serves it. */
|
|
167
|
+
export interface PublishedReceiptKey {
|
|
168
|
+
kid: string;
|
|
169
|
+
kty: string;
|
|
170
|
+
crv: string;
|
|
171
|
+
/** base64url of the raw 32-byte public key. */
|
|
172
|
+
x: string;
|
|
173
|
+
alg?: string;
|
|
174
|
+
use?: string;
|
|
175
|
+
status?: string;
|
|
176
|
+
created_at?: string;
|
|
177
|
+
retired_at?: string | null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ReceiptKeyDocument {
|
|
181
|
+
issuer?: string;
|
|
182
|
+
algorithm?: string;
|
|
183
|
+
signing_string?: string;
|
|
184
|
+
keys: PublishedReceiptKey[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The receipt fields this module reads. Everything else in the JSON is none
|
|
188
|
+
* of its business — including the HMAC seal, which needs a secret it must
|
|
189
|
+
* never be given. */
|
|
190
|
+
export interface SignedReceiptFields {
|
|
191
|
+
receipt_id: string;
|
|
192
|
+
chain: string | null;
|
|
193
|
+
receipt_sig_ed25519: string | null;
|
|
194
|
+
receipt_sig_kid: string | null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The caller's fetcher. Injected so the module has no network of its own and
|
|
198
|
+
* a unit test needs none. */
|
|
199
|
+
export type KeyDocumentFetcher = (url: string) => Promise<unknown>;
|
|
200
|
+
|
|
201
|
+
export interface KeySource {
|
|
202
|
+
/** A key set already in hand — a file, a fixture, a document fetched once
|
|
203
|
+
* and reused across a folder of receipts. */
|
|
204
|
+
document?: unknown;
|
|
205
|
+
/** Where to fetch it from, with the function that does the fetching. */
|
|
206
|
+
url?: string;
|
|
207
|
+
fetch?: KeyDocumentFetcher;
|
|
208
|
+
/** What to call the origin in the report. Defaults to the url, or `file`. */
|
|
209
|
+
label?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── Reading the documents ───────────────────────────────────── */
|
|
213
|
+
|
|
214
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
215
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function stringOrNull(value: unknown): string | null {
|
|
219
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** base64url, strictly: Node's decoder accepts standard base64 and skips
|
|
223
|
+
* characters it does not know, so a round trip is what proves the string was
|
|
224
|
+
* the encoding it claims to be. */
|
|
225
|
+
function decodeBase64Url(value: string, expectedBytes: number): Buffer | undefined {
|
|
226
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) return undefined;
|
|
227
|
+
const buf = Buffer.from(value, "base64url");
|
|
228
|
+
if (buf.length !== expectedBytes || buf.toString("base64url") !== value) return undefined;
|
|
229
|
+
return buf;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export type ReceiptRead = { ok: true; fields: SignedReceiptFields } | { ok: false; reason: string; message: string; receipt_id: string | null };
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read the four fields out of a receipt JSON, or say why it cannot be read.
|
|
236
|
+
*
|
|
237
|
+
* A signature without its `kid`, or a `kid` without its signature, is a
|
|
238
|
+
* MALFORMED document and not an unsigned one. The API writes both columns or
|
|
239
|
+
* neither, so half a pair means the file was edited — and "unsigned" is the
|
|
240
|
+
* benign verdict, which is exactly the one an edit must not be able to reach
|
|
241
|
+
* by deletion.
|
|
242
|
+
*/
|
|
243
|
+
export function readSignedReceipt(input: unknown): ReceiptRead {
|
|
244
|
+
if (!isRecord(input)) {
|
|
245
|
+
return { ok: false, reason: "not_an_object", receipt_id: null, message: "this file is not a JSON object, so it is not a receipt" };
|
|
246
|
+
}
|
|
247
|
+
const receiptId = stringOrNull(input["receipt_id"]);
|
|
248
|
+
if (receiptId === null) {
|
|
249
|
+
return { ok: false, reason: "receipt_id_missing", receipt_id: null, message: "this JSON carries no `receipt_id`, so it is not a CodeSpar receipt" };
|
|
250
|
+
}
|
|
251
|
+
const sigRaw = input["receipt_sig_ed25519"];
|
|
252
|
+
const kidRaw = input["receipt_sig_kid"];
|
|
253
|
+
const sig = stringOrNull(sigRaw);
|
|
254
|
+
const kid = stringOrNull(kidRaw);
|
|
255
|
+
const sigAbsent = sigRaw === undefined || sigRaw === null;
|
|
256
|
+
const kidAbsent = kidRaw === undefined || kidRaw === null;
|
|
257
|
+
if (!sigAbsent && sig === null) {
|
|
258
|
+
return { ok: false, reason: "signature_not_a_string", receipt_id: receiptId, message: `receipt ${receiptId} carries a \`receipt_sig_ed25519\` that is not a string` };
|
|
259
|
+
}
|
|
260
|
+
if (!kidAbsent && kid === null) {
|
|
261
|
+
return { ok: false, reason: "kid_not_a_string", receipt_id: receiptId, message: `receipt ${receiptId} carries a \`receipt_sig_kid\` that is not a string` };
|
|
262
|
+
}
|
|
263
|
+
if (sig !== null && kid === null) {
|
|
264
|
+
return { ok: false, reason: "kid_missing", receipt_id: receiptId, message: `receipt ${receiptId} carries a signature and no \`receipt_sig_kid\`, so there is no way to know which key to check it against; a receipt sealed before Ed25519 carries neither` };
|
|
265
|
+
}
|
|
266
|
+
if (kid !== null && sig === null) {
|
|
267
|
+
return { ok: false, reason: "signature_missing", receipt_id: receiptId, message: `receipt ${receiptId} names the key \`${kid}\` and carries no \`receipt_sig_ed25519\` to check against it` };
|
|
268
|
+
}
|
|
269
|
+
const chain = stringOrNull(input["chain"]);
|
|
270
|
+
if (sig !== null && chain === null) {
|
|
271
|
+
return { ok: false, reason: "chain_missing", receipt_id: receiptId, message: `receipt ${receiptId} carries a signature and no \`chain\`, so the signed string cannot be rebuilt` };
|
|
272
|
+
}
|
|
273
|
+
return { ok: true, fields: { receipt_id: receiptId, chain, receipt_sig_ed25519: sig, receipt_sig_kid: kid } };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export type KeyDocumentRead = { ok: true; document: ReceiptKeyDocument } | { ok: false; reason: string; message: string };
|
|
277
|
+
|
|
278
|
+
/** Read a JWKS, or say why it is not one. Anything unusable is `unreachable`
|
|
279
|
+
* territory: a key set we cannot read leaves us knowing nothing about the
|
|
280
|
+
* signature, which is not the same as knowing it is bad. */
|
|
281
|
+
export function readKeyDocument(input: unknown): KeyDocumentRead {
|
|
282
|
+
if (!isRecord(input)) return { ok: false, reason: "key_document_unreadable", message: "the key set is not a JSON object" };
|
|
283
|
+
const keys = input["keys"];
|
|
284
|
+
if (!Array.isArray(keys)) return { ok: false, reason: "key_document_unreadable", message: "the key set carries no `keys` array" };
|
|
285
|
+
const parsed: PublishedReceiptKey[] = [];
|
|
286
|
+
for (const entry of keys) {
|
|
287
|
+
if (!isRecord(entry)) continue;
|
|
288
|
+
const kid = stringOrNull(entry["kid"]);
|
|
289
|
+
const x = stringOrNull(entry["x"]);
|
|
290
|
+
const kty = stringOrNull(entry["kty"]);
|
|
291
|
+
const crv = stringOrNull(entry["crv"]);
|
|
292
|
+
if (kid === null || x === null || kty === null || crv === null) continue;
|
|
293
|
+
parsed.push({
|
|
294
|
+
kid,
|
|
295
|
+
kty,
|
|
296
|
+
crv,
|
|
297
|
+
x,
|
|
298
|
+
...(stringOrNull(entry["alg"]) !== null ? { alg: entry["alg"] as string } : {}),
|
|
299
|
+
...(stringOrNull(entry["status"]) !== null ? { status: entry["status"] as string } : {}),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const document: ReceiptKeyDocument = {
|
|
303
|
+
keys: parsed,
|
|
304
|
+
...(stringOrNull(input["issuer"]) !== null ? { issuer: input["issuer"] as string } : {}),
|
|
305
|
+
...(stringOrNull(input["signing_string"]) !== null ? { signing_string: input["signing_string"] as string } : {}),
|
|
306
|
+
};
|
|
307
|
+
return { ok: true, document };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* ── The check ───────────────────────────────────────────────── */
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Verify a receipt against a key set already in hand. Synchronous, offline,
|
|
314
|
+
* and the whole of the cryptography: everything else in this file is reading
|
|
315
|
+
* JSON carefully.
|
|
316
|
+
*/
|
|
317
|
+
export function verifyReceiptWithKeys(receipt: unknown, keyDocument: unknown, label = "file"): ReceiptVerification {
|
|
318
|
+
const read = readSignedReceipt(receipt);
|
|
319
|
+
if (!read.ok) {
|
|
320
|
+
return { verdict: "malformed", receipt_id: read.receipt_id, kid: null, key_status: null, signing_string: null, keys_from: null, reason: read.reason, message: read.message };
|
|
321
|
+
}
|
|
322
|
+
const { receipt_id: receiptId, chain, receipt_sig_ed25519: signature, receipt_sig_kid: kid } = read.fields;
|
|
323
|
+
|
|
324
|
+
if (signature === null || kid === null || chain === null) {
|
|
325
|
+
return {
|
|
326
|
+
verdict: "unsigned",
|
|
327
|
+
receipt_id: receiptId,
|
|
328
|
+
kid: null,
|
|
329
|
+
key_status: null,
|
|
330
|
+
signing_string: null,
|
|
331
|
+
keys_from: null,
|
|
332
|
+
reason: "no_ed25519_signature",
|
|
333
|
+
message: `receipt ${receiptId} carries no Ed25519 signature. Receipts sealed before CodeSpar added one carry none and never will; its HMAC seal is unaffected and still proves the payment to whoever runs the agent`,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const signingString = receiptSigningString(receiptId, chain);
|
|
338
|
+
const doc = readKeyDocument(keyDocument);
|
|
339
|
+
if (!doc.ok) {
|
|
340
|
+
return { verdict: "unreachable", receipt_id: receiptId, kid, key_status: null, signing_string: signingString, keys_from: label, reason: doc.reason, message: `${doc.message}, so nothing is proved and nothing is disproved about receipt ${receiptId}` };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const key = doc.document.keys.find((k) => k.kid === kid);
|
|
344
|
+
if (!key) {
|
|
345
|
+
return {
|
|
346
|
+
verdict: "unknown_key",
|
|
347
|
+
receipt_id: receiptId,
|
|
348
|
+
kid,
|
|
349
|
+
key_status: null,
|
|
350
|
+
signing_string: signingString,
|
|
351
|
+
keys_from: label,
|
|
352
|
+
reason: "kid_not_published",
|
|
353
|
+
message: `the key \`${kid}\` is not among the ${doc.document.keys.length} published at ${label}. CodeSpar keeps retired keys published so old signatures go on verifying, so a missing kid is a key from another deployment, or one that was revoked`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const keyStatus = key.status ?? null;
|
|
357
|
+
|
|
358
|
+
if (key.kty !== "OKP" || key.crv !== "Ed25519") {
|
|
359
|
+
return { verdict: "unreachable", receipt_id: receiptId, kid, key_status: keyStatus, signing_string: signingString, keys_from: label, reason: "key_unusable", message: `the published key \`${kid}\` is ${key.kty}/${key.crv} and not OKP/Ed25519, so this module cannot check the signature with it` };
|
|
360
|
+
}
|
|
361
|
+
const publicKey = decodeBase64Url(key.x, 32);
|
|
362
|
+
if (!publicKey) {
|
|
363
|
+
return { verdict: "unreachable", receipt_id: receiptId, kid, key_status: keyStatus, signing_string: signingString, keys_from: label, reason: "key_unusable", message: `the published key \`${kid}\` does not carry a 32-byte base64url public key, so this module cannot check the signature with it` };
|
|
364
|
+
}
|
|
365
|
+
const signatureBytes = decodeBase64Url(signature, 64);
|
|
366
|
+
if (!signatureBytes) {
|
|
367
|
+
return { verdict: "tampered", receipt_id: receiptId, kid, key_status: keyStatus, signing_string: signingString, keys_from: label, reason: "signature_malformed", message: `receipt ${receiptId} carries a \`receipt_sig_ed25519\` that is not a 64-byte base64url Ed25519 signature, so it is not the one CodeSpar wrote` };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let matches: boolean;
|
|
371
|
+
try {
|
|
372
|
+
matches = verifyDetached(null, Buffer.from(signingString, "utf8"), createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: key.x }, format: "jwk" }), signatureBytes);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
return { verdict: "unreachable", receipt_id: receiptId, kid, key_status: keyStatus, signing_string: signingString, keys_from: label, reason: "key_unusable", message: `the published key \`${kid}\` could not be used: ${err instanceof Error ? err.message : String(err)}` };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!matches) {
|
|
378
|
+
return {
|
|
379
|
+
verdict: "tampered",
|
|
380
|
+
receipt_id: receiptId,
|
|
381
|
+
kid,
|
|
382
|
+
key_status: keyStatus,
|
|
383
|
+
signing_string: signingString,
|
|
384
|
+
keys_from: label,
|
|
385
|
+
reason: "signature_does_not_match",
|
|
386
|
+
message: `receipt ${receiptId} does not match its signature: the key \`${kid}\` did not sign this receipt id and this chain. The id or the chain was changed after the receipt was sealed, the signature was copied from another receipt, or this key set is not the one the deployment that sealed it publishes — a sandbox receipt checked against the production keys reads exactly like this, because every deployment publishes its own key under the same \`kid\``,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
verdict: "verified",
|
|
391
|
+
receipt_id: receiptId,
|
|
392
|
+
kid,
|
|
393
|
+
key_status: keyStatus,
|
|
394
|
+
signing_string: signingString,
|
|
395
|
+
keys_from: label,
|
|
396
|
+
reason: "signature_matches",
|
|
397
|
+
message: `receipt ${receiptId} was sealed by CodeSpar: the ${keyStatus ?? "published"} key \`${kid}\` signed this receipt id and this chain`,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Verify a receipt, fetching the key set when one is not in hand.
|
|
403
|
+
*
|
|
404
|
+
* The fetch is the caller's function, so this module opens no socket and a
|
|
405
|
+
* unit test needs no network. A fetch that fails is `unreachable` and never
|
|
406
|
+
* `tampered`: not knowing is not the same as knowing it is bad, and a
|
|
407
|
+
* verifier that conflated the two would call every receipt invalid the day
|
|
408
|
+
* its DNS broke.
|
|
409
|
+
*/
|
|
410
|
+
export async function verifyReceipt(receipt: unknown, source: KeySource = {}): Promise<ReceiptVerification> {
|
|
411
|
+
const read = readSignedReceipt(receipt);
|
|
412
|
+
if (!read.ok) {
|
|
413
|
+
return { verdict: "malformed", receipt_id: read.receipt_id, kid: null, key_status: null, signing_string: null, keys_from: null, reason: read.reason, message: read.message };
|
|
414
|
+
}
|
|
415
|
+
// An unsigned receipt needs no key, so it is answered before the network is
|
|
416
|
+
// touched: fetching a key set to check a signature that is not there would
|
|
417
|
+
// turn an offline answer into an outage.
|
|
418
|
+
if (read.fields.receipt_sig_ed25519 === null) return verifyReceiptWithKeys(receipt, { keys: [] }, source.label ?? "none");
|
|
419
|
+
|
|
420
|
+
if (source.document !== undefined) return verifyReceiptWithKeys(receipt, source.document, source.label ?? "file");
|
|
421
|
+
|
|
422
|
+
const url = source.url ?? DEFAULT_RECEIPT_KEYS_URL;
|
|
423
|
+
const label = source.label ?? url;
|
|
424
|
+
if (!source.fetch) {
|
|
425
|
+
return { verdict: "unreachable", receipt_id: read.fields.receipt_id, kid: read.fields.receipt_sig_kid, key_status: null, signing_string: null, keys_from: label, reason: "no_fetcher", message: "no key set was given and no way to fetch one: pass a document, or a fetch function" };
|
|
426
|
+
}
|
|
427
|
+
let fetched: unknown;
|
|
428
|
+
try {
|
|
429
|
+
fetched = await source.fetch(url);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
return {
|
|
432
|
+
verdict: "unreachable",
|
|
433
|
+
receipt_id: read.fields.receipt_id,
|
|
434
|
+
kid: read.fields.receipt_sig_kid,
|
|
435
|
+
key_status: null,
|
|
436
|
+
signing_string: null,
|
|
437
|
+
keys_from: label,
|
|
438
|
+
reason: "fetch_failed",
|
|
439
|
+
message: `the published key set could not be read from ${label}: ${err instanceof Error ? err.message : String(err)}. Nothing is proved and nothing is disproved; the keys can also be saved to a file and passed in`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return verifyReceiptWithKeys(receipt, fetched, label);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** The exit code of `codespar-agent verify`, one per verdict, so a script can
|
|
446
|
+
* branch on the answer instead of on "did it fail". `unsigned` is not 0: it is
|
|
447
|
+
* not a failure, and it is not a verification either. 2 is the usage error the
|
|
448
|
+
* other commands already use. */
|
|
449
|
+
export const VERDICT_EXIT_CODES: Record<ReceiptVerdict, number> = {
|
|
450
|
+
verified: 0,
|
|
451
|
+
tampered: 1,
|
|
452
|
+
unsigned: 3,
|
|
453
|
+
unknown_key: 4,
|
|
454
|
+
unreachable: 5,
|
|
455
|
+
malformed: 6,
|
|
456
|
+
signature_only: 7,
|
|
457
|
+
chain_mismatch: 8,
|
|
458
|
+
approval_mismatch: 9,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/* ── The body: the chain recomputed from the read ────────────── */
|
|
462
|
+
|
|
463
|
+
/** The first chain version that seals `sha256(mandate.sig)` instead of the signature, and the approval link. */
|
|
464
|
+
export const FIRST_THIRD_PARTY_CHAIN_VERSION = 4;
|
|
465
|
+
|
|
466
|
+
/** Whether a JSON is the API's receipt read, rather than the proof bundle's masked copy of one: only the read carries the links. */
|
|
467
|
+
export function isReceiptRead(input: unknown): input is Record<string, unknown> {
|
|
468
|
+
if (!isRecord(input)) return false;
|
|
469
|
+
const mandate = input["mandate"];
|
|
470
|
+
const payment = input["payment"];
|
|
471
|
+
return isRecord(mandate) && "nonce" in mandate && isRecord(payment) && "rail" in payment;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** A verified signature whose body could not be bound: `signature_only`, with the reason. */
|
|
475
|
+
export function signatureOnly(signature: ReceiptVerification, reason: string, why: string, version: number | null = null): ReceiptVerification {
|
|
476
|
+
return {
|
|
477
|
+
...signature,
|
|
478
|
+
verdict: "signature_only",
|
|
479
|
+
reason,
|
|
480
|
+
message: `receipt ${signature.receipt_id ?? "(no id)"} carries CodeSpar's signature over its id and chain, and its body is NOT bound to that chain: ${why}`,
|
|
481
|
+
chain_check: { status: "not_recomputed", version, recomputed: null, reason },
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Verify a receipt READ — the JSON of `GET /v1/consumers/receipts/{id}` —
|
|
487
|
+
* against a key set already in hand: the signature, then the body, then the
|
|
488
|
+
* approval. Synchronous and offline, like `verifyReceiptWithKeys`.
|
|
489
|
+
*
|
|
490
|
+
* The recipe is taken from the SAME document whose key verified the
|
|
491
|
+
* signature, never from another: each deployment names its keys by
|
|
492
|
+
* namespace (§47), so a document that verified the signature is the sealing
|
|
493
|
+
* deployment's, and its recipe is the one that sealed.
|
|
494
|
+
*
|
|
495
|
+
* A v1–v3 chain is never recomputed here, even from a tenant's read that
|
|
496
|
+
* carries `mandate.sig`: that signature is a bearer proof (it authorizes
|
|
497
|
+
* spends), a verifier has no business handling it, and the API is retiring
|
|
498
|
+
* it from the read. Those chains also predate the API's timestamp fix
|
|
499
|
+
* (ent#1670), so some of them no longer recompute from their read at all —
|
|
500
|
+
* and a mismatch there would accuse a genuine receipt. They answer
|
|
501
|
+
* `signature_only`, never `verified` and never `chain_mismatch`.
|
|
502
|
+
*/
|
|
503
|
+
export function verifyReceiptRead(read: unknown, keyDocument: unknown, options: { label?: string; approval?: ApprovalClaim } = {}): ReceiptVerification {
|
|
504
|
+
const signature = verifyReceiptWithKeys(read, keyDocument, options.label ?? "file");
|
|
505
|
+
if (signature.verdict !== "verified" || !isRecord(read)) return signature;
|
|
506
|
+
if (!isReceiptRead(read)) return signatureOnly(signature, "read_required", "this is not the API's receipt read (the proof bundle's copy masks the payee and carries none of the links); recompute from `GET /v1/consumers/receipts/{id}`");
|
|
507
|
+
|
|
508
|
+
const recipe = readChainRecipe(keyDocument);
|
|
509
|
+
if (!recipe.ok) return signatureOnly(signature, recipe.reason, recipe.message);
|
|
510
|
+
|
|
511
|
+
const versionRaw = read[recipe.recipe.version.field];
|
|
512
|
+
if (typeof versionRaw === "number" && versionRaw < FIRST_THIRD_PARTY_CHAIN_VERSION) {
|
|
513
|
+
return signatureOnly(
|
|
514
|
+
signature,
|
|
515
|
+
"mandate_sig_required",
|
|
516
|
+
`the chain is not recomputable without the mandate signature. Chain v${versionRaw} seals the mandate's raw signature, a bearer proof this verifier does not take; and a receipt sealed before ent#1670 may not recompute from its read at all. It carries no approval link either: that starts at v${FIRST_THIRD_PARTY_CHAIN_VERSION}`,
|
|
517
|
+
versionRaw,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const recomputed = recomputeChain(read, recipe.recipe);
|
|
522
|
+
if (!recomputed.ok) return signatureOnly(signature, recomputed.reason, recomputed.message, recomputed.version);
|
|
523
|
+
const sealedChain = read["chain"];
|
|
524
|
+
if (recomputed.chain !== sealedChain) {
|
|
525
|
+
return {
|
|
526
|
+
...signature,
|
|
527
|
+
verdict: "chain_mismatch",
|
|
528
|
+
reason: "body_does_not_recompute",
|
|
529
|
+
message: `receipt ${signature.receipt_id} carries a genuine CodeSpar signature over chain ${sealedChain}, and the body beside it recomputes to ${recomputed.chain} under the published recipe (v${recomputed.version}): this body is not the one that was sealed. A payee, an amount, a timestamp or the approval was changed after sealing`,
|
|
530
|
+
chain_check: { status: "mismatch", version: recomputed.version, recomputed: recomputed.chain, reason: "body_does_not_recompute" },
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const chainCheck: ChainCheck = { status: "recomputed", version: recomputed.version, recomputed: recomputed.chain, reason: "body_recomputes" };
|
|
534
|
+
const bound = `the body recomputes to the signed chain under the published recipe (v${recomputed.version})`;
|
|
535
|
+
|
|
536
|
+
const sealed = readSealedApproval(read);
|
|
537
|
+
if (!sealed) {
|
|
538
|
+
// Unreachable through the published recipe (v4 seals the link, and a read without it fails the recomputation above), kept so a future version is not misread.
|
|
539
|
+
return { ...signature, reason: "body_recomputes", message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}; it seals no approval link`, chain_check: chainCheck };
|
|
540
|
+
}
|
|
541
|
+
const approval = options.approval;
|
|
542
|
+
if (!approval) {
|
|
543
|
+
return {
|
|
544
|
+
...signature,
|
|
545
|
+
reason: "body_recomputes",
|
|
546
|
+
message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}. It seals the approval ${sealed.items_hash}${sealed.batch_hash ? ` (batch ${sealed.batch_hash})` : ""}; no approval artifact was given to hold it against`,
|
|
547
|
+
chain_check: chainCheck,
|
|
548
|
+
approval_check: { status: "not_compared", sealed, artifact: null, reason: "no_artifact" },
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const artifact = { approval_id: approval.approval_id, items_hash: approval.items_hash, batch_hash: approval.batch?.batch_hash ?? null };
|
|
553
|
+
const mismatch = (reason: string, why: string): ReceiptVerification => ({
|
|
554
|
+
...signature,
|
|
555
|
+
verdict: "approval_mismatch",
|
|
556
|
+
reason,
|
|
557
|
+
message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}, and ${why}: this payment was not sealed against the list artifact ${approval.approval_id} approved`,
|
|
558
|
+
chain_check: chainCheck,
|
|
559
|
+
approval_check: { status: "mismatch", sealed, artifact, reason },
|
|
560
|
+
});
|
|
561
|
+
if (approval.items && itemsHash(approval.items) !== approval.items_hash) {
|
|
562
|
+
return mismatch("artifact_items_hash_inconsistent", `the artifact's items_hash ${approval.items_hash} is not the hash of the items it lists (${itemsHash(approval.items)})`);
|
|
563
|
+
}
|
|
564
|
+
if (!sameApprovalHash(sealed.items_hash, artifact.items_hash)) {
|
|
565
|
+
return mismatch("items_hash_differs", `it seals items_hash ${sealed.items_hash} where the artifact carries ${artifact.items_hash}`);
|
|
566
|
+
}
|
|
567
|
+
if (!sameApprovalHash(sealed.batch_hash, artifact.batch_hash)) {
|
|
568
|
+
return mismatch("batch_hash_differs", `it seals batch_hash ${sealed.batch_hash ?? "none"} where the artifact carries ${artifact.batch_hash ?? "none"}`);
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
...signature,
|
|
572
|
+
reason: "approval_matches",
|
|
573
|
+
message: `receipt ${signature.receipt_id} was sealed by CodeSpar, ${bound}, and it was paid against the list artifact ${approval.approval_id} approved (${sealed.items_hash}${sealed.batch_hash ? `, batch ${sealed.batch_hash}` : ""}). WHO approved it is in the artifact, under a local key, and nowhere in the seal`,
|
|
574
|
+
chain_check: chainCheck,
|
|
575
|
+
approval_check: { status: "matched", sealed, artifact, reason: "approval_matches" },
|
|
576
|
+
};
|
|
577
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 4.7: the core asks a status source before `executing`. The
|
|
3
|
+
* approval artifact alone is not enough, because it may predate a
|
|
4
|
+
* revocation. The interface is the contract. Two implementations ship:
|
|
5
|
+
* `api/mandate-status.ts` reads `GET /v1/mandates/{id}` and is what a run
|
|
6
|
+
* with a test key gets; `stubs/mandate-status.ts` answers from the local
|
|
7
|
+
* state.db and is what a run without a key (the CI, the scenarios, `rerun`)
|
|
8
|
+
* gets, so revocation can be driven with no network.
|
|
9
|
+
*
|
|
10
|
+
* `unknown` is a status too: the source could not say. The engine treats it
|
|
11
|
+
* as "do not execute" (`mandate_status_unavailable`), never as `active`.
|
|
12
|
+
*/
|
|
13
|
+
export type MandateStatus = "active" | "paused" | "revoked" | "expired";
|
|
14
|
+
|
|
15
|
+
export interface MandateStatusReport {
|
|
16
|
+
mandate_id: string;
|
|
17
|
+
status: MandateStatus | "unknown";
|
|
18
|
+
/** True when the organization pressed the kill switch (ent#1648). The API says it as `org_paused` on `GET /v1/mandates/{id}`; the stub says it from `pauseAll`. */
|
|
19
|
+
org_paused: boolean;
|
|
20
|
+
checked_at: string;
|
|
21
|
+
/** Which source answered, so the bundle says whether the check was real. */
|
|
22
|
+
source: "stub" | "api";
|
|
23
|
+
/** Why the status is `unknown`, for the trail. Never carries a response body. */
|
|
24
|
+
detail?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MandateStatusSource {
|
|
28
|
+
/** Must not throw for a read that failed: answer `unknown` and say why. A source that throws is still fail-closed at the gate. */
|
|
29
|
+
check(mandateId: string): Promise<MandateStatusReport>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox by construction: a key outside the `csk_test_` pattern fails
|
|
3
|
+
* before any network call. Nothing in this repository ever holds a live key.
|
|
4
|
+
*/
|
|
5
|
+
export const TEST_KEY_PREFIX = "csk_test_";
|
|
6
|
+
|
|
7
|
+
export class NotATestKeyError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(
|
|
10
|
+
`CODESPAR_API_KEY must be a test key (prefix "${TEST_KEY_PREFIX}"). ` +
|
|
11
|
+
"This kit runs in the sandbox only; a live key is refused before any call is made.",
|
|
12
|
+
);
|
|
13
|
+
this.name = "NotATestKeyError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isTestKey(key: string | undefined): key is string {
|
|
18
|
+
return typeof key === "string" && key.startsWith(TEST_KEY_PREFIX) && key.length > TEST_KEY_PREFIX.length;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function assertTestKey(key: string | undefined): string {
|
|
22
|
+
if (!isTestKey(key)) throw new NotATestKeyError();
|
|
23
|
+
return key;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** For logs and bundles: the prefix and the last four characters, nothing else. */
|
|
27
|
+
export function redactKey(key: string): string {
|
|
28
|
+
if (key.length <= 12) return "csk_****";
|
|
29
|
+
return `${key.slice(0, 9)}…${key.slice(-4)}`;
|
|
30
|
+
}
|