@codespar/cli 0.17.0 → 0.18.1
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/surface.js +53 -1
- package/dist/surface.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
|
@@ -17,28 +17,34 @@
|
|
|
17
17
|
* reconciling a payout, or a court — parties that
|
|
18
18
|
* have no relationship with CodeSpar at all.
|
|
19
19
|
*
|
|
20
|
-
* This module checks the second. It imports `node:crypto` and
|
|
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
|
|
21
22
|
* CodeSpar SDK, no key material, no token, no network of its own (the key
|
|
22
23
|
* document is passed in, or fetched by a function the caller supplies). A
|
|
23
24
|
* third party can read the forty lines that matter and reimplement them.
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
-
* THIS id and THIS chain
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* the
|
|
33
|
-
*
|
|
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.
|
|
34
40
|
*
|
|
35
|
-
* WHICH KEY SET.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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.
|
|
42
48
|
*
|
|
43
49
|
* The signed string is the enterprise's `receiptSigningString`
|
|
44
50
|
* (`packages/api/src/receipt-signature.ts`, ent#1633), reproduced here rather
|
|
@@ -46,6 +52,9 @@
|
|
|
46
52
|
* proves nothing about the signer.
|
|
47
53
|
*/
|
|
48
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";
|
|
49
58
|
|
|
50
59
|
/** The domain tag. A receipt names the version it was sealed under through
|
|
51
60
|
* this literal, so a future `v2` does not invalidate a `v1` signature. */
|
|
@@ -86,8 +95,51 @@ export function receiptSigningString(receiptId: string, chain: string): string {
|
|
|
86
95
|
* arbitrary JSON file would state that it is a receipt from
|
|
87
96
|
* before the capability existed, which is a claim about a
|
|
88
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.
|
|
89
113
|
*/
|
|
90
|
-
export type ReceiptVerdict = "verified" | "tampered" | "unsigned" | "unknown_key" | "unreachable" | "malformed";
|
|
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
|
+
}
|
|
91
143
|
|
|
92
144
|
export interface ReceiptVerification {
|
|
93
145
|
verdict: ReceiptVerdict;
|
|
@@ -106,6 +158,9 @@ export interface ReceiptVerification {
|
|
|
106
158
|
reason: string;
|
|
107
159
|
/** One sentence for a person. Never a stack trace. */
|
|
108
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;
|
|
109
164
|
}
|
|
110
165
|
|
|
111
166
|
/** One published key, RFC 8037 shape, as the JWKS serves it. */
|
|
@@ -398,4 +453,125 @@ export const VERDICT_EXIT_CODES: Record<ReceiptVerdict, number> = {
|
|
|
398
453
|
unknown_key: 4,
|
|
399
454
|
unreachable: 5,
|
|
400
455
|
malformed: 6,
|
|
456
|
+
signature_only: 7,
|
|
457
|
+
chain_mismatch: 8,
|
|
458
|
+
approval_mismatch: 9,
|
|
401
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
|
+
}
|
|
@@ -15,7 +15,7 @@ export type MandateStatus = "active" | "paused" | "revoked" | "expired";
|
|
|
15
15
|
export interface MandateStatusReport {
|
|
16
16
|
mandate_id: string;
|
|
17
17
|
status: MandateStatus | "unknown";
|
|
18
|
-
/** True when the organization pressed the kill switch (
|
|
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
19
|
org_paused: boolean;
|
|
20
20
|
checked_at: string;
|
|
21
21
|
/** Which source answered, so the bundle says whether the check was real. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* does not compile; the runtime check below refuses the same pair when the
|
|
7
7
|
* types were bypassed (a cast, a row read back from SQLite).
|
|
8
8
|
*/
|
|
9
|
-
import type { Actor, ApprovalMode, EscalationTrigger, ExecutionBatch, ExecutionItem, ExecutionReason, ItemOutcome, MandateRef } from "./types.js";
|
|
9
|
+
import type { Actor, ApprovalMode, EscalationTrigger, ExecutionBatch, ExecutionComposition, ExecutionItem, ExecutionReason, ItemOutcome, MandateRef } from "./types.js";
|
|
10
10
|
|
|
11
11
|
export type ExecutionState =
|
|
12
12
|
| "drafted"
|
|
@@ -78,6 +78,15 @@ export interface Execution<S extends ExecutionState = ExecutionState> {
|
|
|
78
78
|
items_hash: string;
|
|
79
79
|
/** Present when this execution is one line of a batch: the set it was presented inside, and where in it. */
|
|
80
80
|
batch?: ExecutionBatch;
|
|
81
|
+
/** Present when this execution's amount is composed of lines (a cart): the lines' hash, not the lines. */
|
|
82
|
+
composition?: ExecutionComposition;
|
|
83
|
+
/**
|
|
84
|
+
* A batch line's attempt generation per item index, when it is not 0: how
|
|
85
|
+
* many of the line's derived attempt ids the rail answered as spent before
|
|
86
|
+
* the one this execution presented (`batchAttemptId`). Saved BEFORE the
|
|
87
|
+
* next id is presented, so a reconcile looks up the id that was sent.
|
|
88
|
+
*/
|
|
89
|
+
attempt_generations?: Record<number, number>;
|
|
81
90
|
mandate: MandateRef;
|
|
82
91
|
/** Written once, before the rail is called. Retrying reuses it. */
|
|
83
92
|
idempotency_key: string;
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* a run with a test key gets instead; this file is never consulted on that
|
|
9
9
|
* path.
|
|
10
10
|
*
|
|
11
|
-
* It also holds the organization kill switch (`pauseAll`),
|
|
12
|
-
*
|
|
11
|
+
* It also holds the organization kill switch (`pauseAll`), so `org_paused`
|
|
12
|
+
* can be exercised locally the way `GET /v1/mandates/{id}` reports it
|
|
13
|
+
* (ent#1648): a flag next to the mandate's own status, which it leaves alone.
|
|
13
14
|
*/
|
|
14
15
|
import type { MandateStatusSource, MandateStatus, MandateStatusReport } from "../revocation.js";
|
|
15
16
|
import type { StateStore } from "../state/store.js";
|
|
@@ -46,7 +47,7 @@ export class LocalMandateStatusStub implements MandateStatusSource {
|
|
|
46
47
|
this.store.stubSetMandateStatus(mandateId, "active", null, this.clock().toISOString());
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
/** The organization kill switch, locally
|
|
50
|
+
/** The organization kill switch, locally. Against the API it is pressed with `POST /v1/orgs/{orgId}/pause`, which the kit never calls. */
|
|
50
51
|
pauseAll(): void {
|
|
51
52
|
this.store.stubSetOrgPaused(STUB_ORG_ID, true, this.clock().toISOString());
|
|
52
53
|
}
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
* scenarios, the adversarial suite and the restart test run offline. Every
|
|
4
4
|
* attempt is persisted in state.db before the outcome is returned, which is
|
|
5
5
|
* what lets a process killed right after "the money left" be reconciled on
|
|
6
|
-
* `resume` instead of paying again.
|
|
6
|
+
* `resume` instead of paying again. A repeat of an attempt id is answered the
|
|
7
|
+
* way the deployed API answers it (ent#1671): replayed when it settled,
|
|
8
|
+
* in flight while the provider is on it, spent when it failed, and refused
|
|
9
|
+
* when the id arrives with a different payment. A stub that simply replayed
|
|
10
|
+
* whatever it held would let a suite pass that the API fails. Receipts here are HMAC-shaped only in
|
|
7
11
|
* form; nothing about them is verifiable by anyone.
|
|
8
12
|
*/
|
|
9
|
-
import { sha256Hex } from "../hash.js";
|
|
10
|
-
import
|
|
13
|
+
import { canonicalJson, sha256Hex } from "../hash.js";
|
|
14
|
+
import { checkQuote } from "../quote.js";
|
|
15
|
+
import { checkSpendApproval, type PaymentRail, type RailLookup, type RailOutcome, type RailPayment, type RailReceipt } from "../rail.js";
|
|
11
16
|
import type { StateStore } from "../state/store.js";
|
|
12
17
|
import type { Actor } from "../types.js";
|
|
13
18
|
|
|
@@ -29,6 +34,34 @@ export interface StubRailOptions {
|
|
|
29
34
|
afterDispatch?: (attemptId: string) => void;
|
|
30
35
|
/** Attempt ids whose first lookup answers `in_flight` and whose second lookup finds the provider finished (settled), with no pay() in between. */
|
|
31
36
|
inFlightThenSettled?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Where the "provider" keeps its books, when that is not the agent's own
|
|
39
|
+
* state file. The real rail is a server two machines share; a test that
|
|
40
|
+
* runs the same batch from two state files points both at one ledger.
|
|
41
|
+
*/
|
|
42
|
+
ledger?: StateStore;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What the stub's books hold for an attempt the "provider" took and has not finished. */
|
|
46
|
+
const IN_FLIGHT = { status: "in_flight" } as const;
|
|
47
|
+
|
|
48
|
+
type Recorded = RailOutcome | typeof IN_FLIGHT;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The fields a repeat of an attempt id is compared on, the stub's share of
|
|
52
|
+
* the API's attempt tuple (ent#1671): whose money, how much, to whom, the
|
|
53
|
+
* digest of the quote and the approval the spend carried (ent#1670). Rail
|
|
54
|
+
* and instrument are the API's and have no stub counterpart.
|
|
55
|
+
*/
|
|
56
|
+
function mismatchedFields(recorded: Omit<RailPayment, "actor">, asked: Omit<RailPayment, "actor">): string[] {
|
|
57
|
+
const out: string[] = [];
|
|
58
|
+
if (recorded.mandate_id !== asked.mandate_id) out.push("mandate");
|
|
59
|
+
if (recorded.amount_minor !== asked.amount_minor) out.push("amount_minor");
|
|
60
|
+
if (recorded.currency !== asked.currency) out.push("currency");
|
|
61
|
+
if (recorded.payee !== asked.payee) out.push("payee");
|
|
62
|
+
if (canonicalJson(recorded.quote ?? null) !== canonicalJson(asked.quote ?? null)) out.push("quote");
|
|
63
|
+
if (canonicalJson(recorded.approval ?? null) !== canonicalJson(asked.approval ?? null)) out.push("approval");
|
|
64
|
+
return out;
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
export class StubRail implements PaymentRail {
|
|
@@ -36,18 +69,19 @@ export class StubRail implements PaymentRail {
|
|
|
36
69
|
private readonly clock: () => Date;
|
|
37
70
|
private readonly uncertainPending: Set<string>;
|
|
38
71
|
|
|
72
|
+
private readonly store: StateStore;
|
|
73
|
+
|
|
39
74
|
constructor(
|
|
40
|
-
|
|
75
|
+
store: StateStore,
|
|
41
76
|
private readonly options: StubRailOptions = {},
|
|
42
77
|
) {
|
|
78
|
+
this.store = options.ledger ?? store;
|
|
43
79
|
this.clock = options.clock ?? (() => new Date());
|
|
44
80
|
this.uncertainPending = new Set(options.uncertainOnce ?? []);
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
private uncertainArmed = false;
|
|
48
84
|
private readonly inFlightSeen = new Set<string>();
|
|
49
|
-
/** Attempts that answered `uncertain` because the stub was armed: the "provider" did take them, and a later lookup finds them finished. */
|
|
50
|
-
private readonly lateAttempts = new Set<string>();
|
|
51
85
|
/** Attempts that already gave their one `uncertain`, so a re-presentation behaves normally. */
|
|
52
86
|
private readonly uncertainAnswered = new Set<string>();
|
|
53
87
|
/** How many times pay() reached the point of persisting a NEW attempt. */
|
|
@@ -59,12 +93,19 @@ export class StubRail implements PaymentRail {
|
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
async pay(payment: RailPayment): Promise<RailOutcome> {
|
|
96
|
+
// The same refusal the CodeSpar rail makes before its call, so a scenario exercises it.
|
|
97
|
+
const quoted = checkQuote(payment);
|
|
98
|
+
if (!quoted.ok) return { status: "failed", code: quoted.code, message: `${quoted.detail}; nothing was sent` };
|
|
99
|
+
const approval = checkSpendApproval(payment);
|
|
100
|
+
if (!approval.ok) return { status: "failed", code: approval.code, message: `${approval.detail}; nothing was sent` };
|
|
101
|
+
const { actor: _actor, ...request } = payment;
|
|
62
102
|
const existing = this.store.stubRailGet(payment.attempt_id);
|
|
63
|
-
if (existing) return existing.outcome as
|
|
103
|
+
if (existing) return this.repeat(payment.attempt_id, existing.request as Omit<RailPayment, "actor">, existing.outcome as Recorded, request);
|
|
64
104
|
|
|
65
105
|
const uncertainPayee = this.options.uncertainPayees?.includes(payment.payee) === true && !this.uncertainAnswered.has(payment.attempt_id);
|
|
66
106
|
if (this.uncertainArmed || this.uncertainPending.has(payment.attempt_id) || uncertainPayee) {
|
|
67
|
-
|
|
107
|
+
// Armed and payee-scripted attempts were TAKEN by the provider, so they are on its books as in flight; `uncertainOnce` never reached it.
|
|
108
|
+
if (this.uncertainArmed || uncertainPayee) this.store.stubRailPut(payment.attempt_id, request, IN_FLIGHT, this.clock().toISOString());
|
|
68
109
|
this.uncertainAnswered.add(payment.attempt_id);
|
|
69
110
|
this.uncertainArmed = false;
|
|
70
111
|
this.uncertainPending.delete(payment.attempt_id);
|
|
@@ -74,40 +115,52 @@ export class StubRail implements PaymentRail {
|
|
|
74
115
|
const at = this.clock().toISOString();
|
|
75
116
|
const outcome: RailOutcome = this.options.refusePayees?.includes(payment.payee)
|
|
76
117
|
? { status: "failed", code: "psp_dispatch_failed", message: `stub: provider refused payee ${payment.payee}` }
|
|
77
|
-
: {
|
|
78
|
-
status: "settled",
|
|
79
|
-
transaction_id: `stubtx_${sha256Hex(payment.attempt_id).slice(0, 16)}`,
|
|
80
|
-
receipt_id: `rcpt_stub_${sha256Hex(`receipt:${payment.attempt_id}`).slice(0, 16)}`,
|
|
81
|
-
money_moved: false,
|
|
82
|
-
sandbox: true,
|
|
83
|
-
raw: { stub: true, attempt_id: payment.attempt_id, at },
|
|
84
|
-
};
|
|
85
|
-
const { actor: _actor, ...request } = payment;
|
|
118
|
+
: settledOutcome(payment.attempt_id, { stub: true, attempt_id: payment.attempt_id, at });
|
|
86
119
|
this.payCount += 1;
|
|
87
120
|
this.store.stubRailPut(payment.attempt_id, request, outcome, at);
|
|
88
121
|
this.options.afterDispatch?.(payment.attempt_id);
|
|
89
122
|
return outcome;
|
|
90
123
|
}
|
|
91
124
|
|
|
125
|
+
/**
|
|
126
|
+
* A presentation of an attempt id the books already hold, answered the way
|
|
127
|
+
* the deployed API answers it (ent#1671, #1683), and never by paying:
|
|
128
|
+
* a different payment under the id is `attempt_id_conflict`, checked before
|
|
129
|
+
* the state; a settled one is its ORIGINAL outcome with `idempotent_replay`;
|
|
130
|
+
* one still in flight is `psp_attempt_in_flight`; a failed one is
|
|
131
|
+
* `psp_attempt_conflict`, spent for good.
|
|
132
|
+
*/
|
|
133
|
+
private repeat(attemptId: string, recorded: Omit<RailPayment, "actor">, outcome: Recorded, asked: Omit<RailPayment, "actor">): RailOutcome {
|
|
134
|
+
const mismatched = mismatchedFields(recorded, asked);
|
|
135
|
+
if (mismatched.length > 0) {
|
|
136
|
+
return { status: "failed", code: "attempt_id_conflict", message: `stub: attempt ${attemptId} was already used for a different payment (differs in: ${mismatched.join(", ")}); nothing was held or sent`, held: "conflict" };
|
|
137
|
+
}
|
|
138
|
+
switch (outcome.status) {
|
|
139
|
+
case "in_flight":
|
|
140
|
+
return { status: "uncertain", code: "psp_attempt_in_flight", message: `stub: attempt ${attemptId} is claimed and its outcome is not yet recorded` };
|
|
141
|
+
case "settled":
|
|
142
|
+
return { ...outcome, replayed: true, raw: { ...(outcome.raw as Record<string, unknown>), idempotent_replay: true } };
|
|
143
|
+
case "failed":
|
|
144
|
+
return { status: "failed", code: "psp_attempt_conflict", message: `stub: attempt ${attemptId} already failed and moved no money; use a fresh attempt_id`, spent: true };
|
|
145
|
+
default:
|
|
146
|
+
return outcome;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
92
150
|
async lookup(attemptId: string, payment: RailPayment): Promise<RailLookup> {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
151
|
+
const { actor: _actor, ...request } = payment;
|
|
152
|
+
const existing = this.store.stubRailGet(attemptId);
|
|
153
|
+
const recorded = existing?.outcome as Recorded | undefined;
|
|
154
|
+
// Looking up IS presenting again on the real rail, so a recorded attempt answers exactly what a repeat of it answers.
|
|
155
|
+
if (existing && recorded && recorded.status !== "in_flight") return this.repeat(attemptId, existing.request as Omit<RailPayment, "actor">, recorded, request);
|
|
156
|
+
if (this.options.inFlightThenSettled?.includes(attemptId) || recorded?.status === "in_flight") {
|
|
96
157
|
if (!this.inFlightSeen.has(attemptId)) {
|
|
97
158
|
this.inFlightSeen.add(attemptId);
|
|
98
159
|
return { status: "in_flight" };
|
|
99
160
|
}
|
|
100
161
|
// The provider finished on its own; the stub records the outcome as the rail would, without a new dispatch.
|
|
101
|
-
const {
|
|
102
|
-
|
|
103
|
-
status: "settled",
|
|
104
|
-
transaction_id: `stubtx_${sha256Hex(attemptId).slice(0, 16)}`,
|
|
105
|
-
receipt_id: `rcpt_stub_${sha256Hex(`receipt:${attemptId}`).slice(0, 16)}`,
|
|
106
|
-
money_moved: false,
|
|
107
|
-
sandbox: true,
|
|
108
|
-
raw: { stub: true, attempt_id: attemptId, finished_in_background: true },
|
|
109
|
-
};
|
|
110
|
-
this.store.stubRailPut(attemptId, request, outcome, this.clock().toISOString());
|
|
162
|
+
const outcome = settledOutcome(attemptId, { stub: true, attempt_id: attemptId, finished_in_background: true });
|
|
163
|
+
this.store.stubRailReplace(attemptId, existing?.request ?? request, outcome, this.clock().toISOString());
|
|
111
164
|
return outcome;
|
|
112
165
|
}
|
|
113
166
|
return undefined;
|
|
@@ -118,13 +171,17 @@ export class StubRail implements PaymentRail {
|
|
|
118
171
|
if (!sealed) return undefined;
|
|
119
172
|
const req = sealed.request as Omit<RailPayment, "actor">;
|
|
120
173
|
const out = sealed.outcome as Extract<RailOutcome, { status: "settled" }>;
|
|
174
|
+
// Like the API: the payee on the receipt is the one the quote sealed, and none when the spend carried no quote.
|
|
121
175
|
const body = {
|
|
122
176
|
receipt_id: receiptId,
|
|
123
177
|
state: "paid",
|
|
124
178
|
mandate: { id: req.mandate_id },
|
|
125
|
-
|
|
179
|
+
// Like the API since ent#1670: the approval the spend carried is sealed and read back, batch_hash null when none was sent.
|
|
180
|
+
chain_version: req.approval ? 4 : 1,
|
|
181
|
+
approval: req.approval ? { items_hash: req.approval.items_hash, batch_hash: req.approval.batch_hash ?? null } : null,
|
|
182
|
+
payment: { amount_minor: req.amount_minor, payee: req.quote?.payee ?? null, attempt_id: req.attempt_id, money_moved: false, sandbox: true, at: sealed.at },
|
|
126
183
|
};
|
|
127
|
-
const chain = `sha256:${sha256Hex(JSON.stringify(body))}`;
|
|
184
|
+
const chain = `sha256:${sha256Hex(JSON.stringify(req.quote ? { ...body, quote: req.quote } : body))}`;
|
|
128
185
|
return {
|
|
129
186
|
...body,
|
|
130
187
|
chain,
|
|
@@ -139,3 +196,14 @@ export class StubRail implements PaymentRail {
|
|
|
139
196
|
};
|
|
140
197
|
}
|
|
141
198
|
}
|
|
199
|
+
|
|
200
|
+
function settledOutcome(attemptId: string, raw: Record<string, unknown>): RailOutcome {
|
|
201
|
+
return {
|
|
202
|
+
status: "settled",
|
|
203
|
+
transaction_id: `stubtx_${sha256Hex(attemptId).slice(0, 16)}`,
|
|
204
|
+
receipt_id: `rcpt_stub_${sha256Hex(`receipt:${attemptId}`).slice(0, 16)}`,
|
|
205
|
+
money_moved: false,
|
|
206
|
+
sandbox: true,
|
|
207
|
+
raw,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* authority comes before mandate-level authority: a tool that is not in
|
|
4
4
|
* this file is refused before anything else looks at the call.
|
|
5
5
|
*
|
|
6
|
-
* Two kinds. `meta_tools`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Two kinds. `meta_tools` borrow a CodeSpar meta-tool's name for the job
|
|
7
|
+
* they do, but the input shape is the kit's own and the kit's handler runs it:
|
|
8
|
+
* a payment or charge becomes a `drafted` execution, and the core sends the
|
|
9
|
+
* real call over REST. It is not a copy of the MCP's schema and does not match
|
|
10
|
+
* it (OPEN_QUESTIONS section 8 has the comparison). `local_tools` are the
|
|
11
|
+
* agent's own helpers: reads, and state the agent keeps for itself.
|
|
10
12
|
*/
|
|
11
13
|
import { readFileSync } from "node:fs";
|
|
12
14
|
import { z } from "zod";
|
|
@@ -17,8 +19,15 @@ const ToolDefinitionSchema = z
|
|
|
17
19
|
name: z.string().regex(/^[a-z][a-z0-9_]*$/),
|
|
18
20
|
description: z.string().min(1),
|
|
19
21
|
input_schema: z.record(z.string(), z.unknown()),
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* `payment` and `charge` tools create a `drafted` execution and nothing
|
|
24
|
+
* else; `read` tools never touch money and change nothing. `charge` is the
|
|
25
|
+
* receivable side: the counterparty pays us. `state` changes durable local
|
|
26
|
+
* state (a cart in `state.db`) and moves no money — not a read, because
|
|
27
|
+
* calling it twice is not calling it once, and not a payment, because no
|
|
28
|
+
* execution comes out of it.
|
|
29
|
+
*/
|
|
30
|
+
effect: z.enum(["payment", "charge", "read", "state"]),
|
|
22
31
|
})
|
|
23
32
|
.strict();
|
|
24
33
|
|
|
@@ -36,6 +45,8 @@ export const ToolsFileSchema = z
|
|
|
36
45
|
}
|
|
37
46
|
for (const [i, tool] of t.meta_tools.entries()) {
|
|
38
47
|
if (!tool.name.startsWith("codespar_")) ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["meta_tools", i, "name"], message: "meta-tools are named codespar_*" });
|
|
48
|
+
// A meta-tool borrows a CodeSpar name for a job CodeSpar does; the state the agent keeps for itself is not one.
|
|
49
|
+
if (tool.effect === "state") ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["meta_tools", i, "effect"], message: "a state tool is local: meta-tools move money or read" });
|
|
39
50
|
}
|
|
40
51
|
});
|
|
41
52
|
|