@codespar/cli 0.17.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/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
|
@@ -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
|
|
|
@@ -52,6 +52,45 @@ export interface ExecutionBatch {
|
|
|
52
52
|
count: number;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* One line of what an execution's amount is MADE OF, as the kit resolved it:
|
|
57
|
+
* a cart line (`ref` a SKU, a quantity, the unit price the catalog gave it)
|
|
58
|
+
* or a discount line (a negative `amount`). Only `compositionHash` reads it;
|
|
59
|
+
* the core never stores the lines, only their hash and their count.
|
|
60
|
+
*/
|
|
61
|
+
export interface CompositionLine {
|
|
62
|
+
ref: string;
|
|
63
|
+
quantity: number;
|
|
64
|
+
unit_amount: number;
|
|
65
|
+
amount: number;
|
|
66
|
+
currency: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* What an execution's single amount was composed from, when it was composed
|
|
71
|
+
* from something: a sale is ONE charge, so ONE item whose amount is the
|
|
72
|
+
* order's total, and the lines of the cart behind that total are not items.
|
|
73
|
+
*
|
|
74
|
+
* That leaves the composition unattested by `items_hash`: two units at 100
|
|
75
|
+
* and one unit at 200 are the same item, the same amount, the same hash.
|
|
76
|
+
* `composition_hash` binds the lines — computed by the kit over the resolved
|
|
77
|
+
* lines in order, carried by the execution and by every artifact of it — so a
|
|
78
|
+
* cart recomposed at a constant total after approval is not the cart that was
|
|
79
|
+
* approved.
|
|
80
|
+
*
|
|
81
|
+
* Absent on an execution that is not composed from anything, and an artifact
|
|
82
|
+
* without it is the artifact of section 4.2 unchanged, byte for byte: the
|
|
83
|
+
* field is omitted rather than null, the same discipline as `batch`.
|
|
84
|
+
*/
|
|
85
|
+
export interface ExecutionComposition {
|
|
86
|
+
/** The kit's reference for what was composed (`cart_id`). */
|
|
87
|
+
ref: string;
|
|
88
|
+
/** `compositionHash` over the resolved lines, in order. */
|
|
89
|
+
composition_hash: string;
|
|
90
|
+
/** How many lines that hash covers. */
|
|
91
|
+
line_count: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
55
94
|
/** The trigger of section 4.4 that sent a `mandate` execution to a human. */
|
|
56
95
|
export type EscalationTrigger = "amount" | "new_beneficiary" | "outside_hours";
|
|
57
96
|
|
|
@@ -67,6 +106,8 @@ export type ExecutionReason =
|
|
|
67
106
|
| "window_cap_exceeded"
|
|
68
107
|
| "approval_expired"
|
|
69
108
|
| "items_hash_mismatch"
|
|
109
|
+
/** The quote a spend would present does not name the approved amount and payee; refused before the call. */
|
|
110
|
+
| "quote_mismatch"
|
|
70
111
|
| "mandate_changed"
|
|
71
112
|
| "denied_by_approver"
|
|
72
113
|
| "rail_failed"
|
|
@@ -80,7 +121,13 @@ export type ExecutionReason =
|
|
|
80
121
|
/** A receivable was issued and the rail is waiting for the payer; the execution stays `executing` until `commerce.charge.*` closes it. */
|
|
81
122
|
| "awaiting_settlement"
|
|
82
123
|
| "charge_expired"
|
|
83
|
-
| "charge_cancelled"
|
|
124
|
+
| "charge_cancelled"
|
|
125
|
+
/**
|
|
126
|
+
* An ISSUED receivable whose read now answers that its reference matches more than one charge. Terminal for the
|
|
127
|
+
* execution, but not "nothing moved": the charge exists and may still be paid. Reconcile it by the charge id; never
|
|
128
|
+
* issue another to the same payee until that is done, which is why the core refuses one (`policy`).
|
|
129
|
+
*/
|
|
130
|
+
| "charge_reference_ambiguous";
|
|
84
131
|
|
|
85
132
|
export interface MandateRef {
|
|
86
133
|
id: string;
|
|
@@ -102,6 +149,8 @@ export interface ApprovalArtifact {
|
|
|
102
149
|
items_hash: string;
|
|
103
150
|
/** Present when this execution is one line of a batch: what binds the SET this line was approved inside. */
|
|
104
151
|
batch?: ExecutionBatch;
|
|
152
|
+
/** Present when this execution's amount is composed of lines (a cart): what binds the composition that was approved. */
|
|
153
|
+
composition?: ExecutionComposition;
|
|
105
154
|
/** Present when a section 4.4 trigger sent the execution to a human first. */
|
|
106
155
|
escalation?: { trigger: EscalationTrigger; detail: string };
|
|
107
156
|
actor: Actor;
|
|
@@ -117,6 +166,10 @@ export interface ItemOutcome {
|
|
|
117
166
|
transaction_id?: string;
|
|
118
167
|
/** The rail's code on a failed outcome (`charge_expired`, `charge_cancelled`, a provider code). */
|
|
119
168
|
code?: string;
|
|
169
|
+
/** A failed outcome because the attempt id is held for another payment or another project (`RailOutcome.held`). */
|
|
170
|
+
held?: "conflict" | "unavailable";
|
|
171
|
+
/** A settled outcome the rail answered from its record of an earlier presentation (`RailOutcome.replayed`). */
|
|
172
|
+
replayed?: true;
|
|
120
173
|
error?: string;
|
|
121
174
|
/** What the payer is shown for an accepted receivable, as the rail handed it back. Presentation only; nothing here decides money. */
|
|
122
175
|
instrument?: ChargeInstrument;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPEN_QUESTIONS §3: every spend carries the approval artifact's hashes, and
|
|
3
|
+
* the API seals them into the receipt's chain (v4, ent#1670). What is sent is
|
|
4
|
+
* the artifact's own `items_hash`, and for a batch line its `batch_hash` —
|
|
5
|
+
* the values the artifact's HMAC covers — and nothing the execution says
|
|
6
|
+
* later. A spend without them, or with a shape the API would refuse, never
|
|
7
|
+
* goes out; the bundle's receipt copy names the artifact by id so `verify`
|
|
8
|
+
* finds it by identity.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { ApiClient } from "@codespar/sdk";
|
|
13
|
+
import { describe, expect, it, vi } from "vitest";
|
|
14
|
+
import { CodeSparRail } from "../src/api/rail.js";
|
|
15
|
+
import { copyDisagreesWithRead, maskPayee } from "../src/bundle.js";
|
|
16
|
+
import { ReceiptSealMismatchError } from "../src/engine.js";
|
|
17
|
+
import { checkSpendApproval, spendApprovalOf, type PaymentRail, type RailPayment, type SealedSpendApproval } from "../src/rail.js";
|
|
18
|
+
import { ESCOLA, harness, MERCADO } from "./helpers.js";
|
|
19
|
+
|
|
20
|
+
const approver = { id: "usr_demo", channel: "terminal" };
|
|
21
|
+
const actor = { type: "agent" as const, agent: "bills-agent@0.1.0", on_behalf_of: "usr_demo" };
|
|
22
|
+
const ITEMS_HASH = `sha256:${"1".repeat(64)}`;
|
|
23
|
+
|
|
24
|
+
function payment(over: Partial<RailPayment> = {}): RailPayment {
|
|
25
|
+
return {
|
|
26
|
+
attempt_id: "att_seal_0",
|
|
27
|
+
mandate_id: "mdt_test_0001",
|
|
28
|
+
amount_minor: 185000,
|
|
29
|
+
currency: "BRL",
|
|
30
|
+
payee: ESCOLA,
|
|
31
|
+
purpose: "contas do mes",
|
|
32
|
+
agent_id: "bills-agent",
|
|
33
|
+
quote: { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, at: "2026-09-23T18:00:00.000Z" },
|
|
34
|
+
approval: { items_hash: ITEMS_HASH },
|
|
35
|
+
actor,
|
|
36
|
+
...over,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fakeApi(receiptBody?: unknown) {
|
|
41
|
+
const post = vi.fn(async (_path: string, _init: { body: Record<string, unknown> }) => ({ payment: { transactionId: "tx_1", moneyMoved: false }, receipt: { id: "rcpt_1" } }));
|
|
42
|
+
return { api: { post, get: vi.fn(async () => receiptBody) } as unknown as ApiClient, post };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The stub rail, but the receipt it hands back seals whatever approval the test says. */
|
|
46
|
+
function sealingApproval(sealed: SealedSpendApproval | null) {
|
|
47
|
+
return (inner: PaymentRail): PaymentRail => ({
|
|
48
|
+
name: inner.name,
|
|
49
|
+
pay: (p) => inner.pay(p),
|
|
50
|
+
lookup: (id, p, tx) => inner.lookup(id, p, tx),
|
|
51
|
+
receipt: async (id, a) => {
|
|
52
|
+
const r = await inner.receipt(id, a);
|
|
53
|
+
return r && { ...r, approval: sealed };
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function recording() {
|
|
59
|
+
const h = harness({ mode: "human" });
|
|
60
|
+
const seen: RailPayment[] = [];
|
|
61
|
+
const pay = h.rail.pay.bind(h.rail);
|
|
62
|
+
h.rail.pay = async (p) => (seen.push(p), pay(p));
|
|
63
|
+
return { h, seen };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe("every spend presents the artifact's hashes", () => {
|
|
67
|
+
it("a single execution: the artifact's items_hash on every line, no batch_hash, and the quote keeps the moment of approval", async () => {
|
|
68
|
+
const { h, seen } = recording();
|
|
69
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }, { payee: "mercado", amount: 32000 }] });
|
|
70
|
+
if (!d.ok) throw new Error("refused");
|
|
71
|
+
const approved = h.engine.approve(d.execution.id, approver);
|
|
72
|
+
const artifact = h.store.getApproval(approved.approval_id!)!;
|
|
73
|
+
expect((await h.engine.execute(approved.id)).state).toBe("settled");
|
|
74
|
+
expect(seen.map((p) => p.approval)).toEqual([{ items_hash: artifact.items_hash }, { items_hash: artifact.items_hash }]);
|
|
75
|
+
expect(seen.map((p) => p.quote?.at)).toEqual([artifact.approved_at, artifact.approved_at]);
|
|
76
|
+
// The form the API seals a timestamp in (RFC 3339, UTC, milliseconds), so the read gives back what was sent (ent#1670).
|
|
77
|
+
expect(artifact.approved_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
|
78
|
+
// What the "provider" holds for the attempt is what was sent.
|
|
79
|
+
expect((h.store.stubRailGet(seen[0]!.attempt_id)!.request as RailPayment).approval).toEqual({ items_hash: artifact.items_hash });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("a batch line: the line's items_hash and the batch's batch_hash, and a quote with no time in it", async () => {
|
|
83
|
+
const { h, seen } = recording();
|
|
84
|
+
const batch = { ref: "folha-teste", batch_hash: `sha256:${"a".repeat(64)}`, index: 0, count: 2 };
|
|
85
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }], batch });
|
|
86
|
+
if (!d.ok) throw new Error("refused");
|
|
87
|
+
const approved = h.engine.approve(d.execution.id, approver);
|
|
88
|
+
const artifact = h.store.getApproval(approved.approval_id!)!;
|
|
89
|
+
await h.engine.execute(approved.id);
|
|
90
|
+
expect(seen[0]!.approval).toEqual({ items_hash: artifact.items_hash, batch_hash: batch.batch_hash });
|
|
91
|
+
expect(seen[0]!.quote).not.toHaveProperty("at");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("spendApprovalOf carries exactly the artifact's two hashes", () => {
|
|
95
|
+
expect(spendApprovalOf({ items_hash: ITEMS_HASH })).toEqual({ items_hash: ITEMS_HASH });
|
|
96
|
+
expect(spendApprovalOf({ items_hash: ITEMS_HASH, batch: { ref: "r", batch_hash: `sha256:${"b".repeat(64)}`, index: 1, count: 3 } })).toEqual({ items_hash: ITEMS_HASH, batch_hash: `sha256:${"b".repeat(64)}` });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("the CodeSpar rail", () => {
|
|
101
|
+
it("sends approval on both spend routes, as the artifact spells it", async () => {
|
|
102
|
+
const withBatch = { items_hash: ITEMS_HASH, batch_hash: `sha256:${"b".repeat(64)}` };
|
|
103
|
+
const byEnvelope = fakeApi();
|
|
104
|
+
await new CodeSparRail(byEnvelope.api, { canonical: { any: "envelope" } as never, signature: "a".repeat(64) }).pay(payment({ approval: withBatch }));
|
|
105
|
+
expect(byEnvelope.post.mock.calls[0]![0]).toBe("/v1/consumer-payments/execute");
|
|
106
|
+
expect(byEnvelope.post.mock.calls[0]![1].body["approval"]).toEqual(withBatch);
|
|
107
|
+
const byId = fakeApi();
|
|
108
|
+
await new CodeSparRail(byId.api).pay(payment());
|
|
109
|
+
expect(byId.post.mock.calls[0]![0]).toBe("/v1/consumers/mandates/{id}/spend");
|
|
110
|
+
expect(byId.post.mock.calls[0]![1].body["approval"]).toEqual({ items_hash: ITEMS_HASH });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("refuses a spend without it, or with a hash the API would refuse, without calling the API", async () => {
|
|
114
|
+
const { api, post } = fakeApi();
|
|
115
|
+
const rail = new CodeSparRail(api);
|
|
116
|
+
const { approval: _a, ...bare } = payment();
|
|
117
|
+
expect(await rail.pay(bare)).toMatchObject({ status: "failed", code: "approval_missing" });
|
|
118
|
+
expect(await rail.pay(payment({ approval: { items_hash: "sha256:ABC" } }))).toMatchObject({ status: "failed", code: "approval_malformed" });
|
|
119
|
+
expect(await rail.pay(payment({ approval: { items_hash: ITEMS_HASH, batch_hash: "f".repeat(63) } }))).toMatchObject({ status: "failed", code: "approval_malformed" });
|
|
120
|
+
expect(post).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("checkSpendApproval accepts the two spellings the API accepts", () => {
|
|
124
|
+
expect(checkSpendApproval({ approval: { items_hash: ITEMS_HASH } }).ok).toBe(true);
|
|
125
|
+
expect(checkSpendApproval({ approval: { items_hash: "1".repeat(64) } }).ok).toBe(true);
|
|
126
|
+
expect(checkSpendApproval({ approval: { items_hash: `sha256:${"1".repeat(64)}`.toUpperCase() } }).ok).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("the receipt seals the approval the spend sent, or the run stops", () => {
|
|
131
|
+
it("the CodeSpar rail reads chain_version, the sealed approval and sig_sha256 through the SDK's types", async () => {
|
|
132
|
+
const read = {
|
|
133
|
+
receipt_id: "rcpt_1",
|
|
134
|
+
state: "paid",
|
|
135
|
+
chain_version: 4,
|
|
136
|
+
mandate: { id: "cm_1", nonce: "n", scope: "s", currency: "BRL", sig: "x", sig_sha256: "d".repeat(64) },
|
|
137
|
+
quote: { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, session_id: null, sig: "q", at: "2026-09-23T18:00:00.000Z" },
|
|
138
|
+
approval: { items_hash: ITEMS_HASH, batch_hash: null },
|
|
139
|
+
payment: { rail: "pix-consent", provider: "pix", tx_id: "tx_1", amount_minor: 185000, amount_atomic: null, sandbox: true, attempt_id: "att_seal_0", money_moved: false, at: "2026-09-23T18:00:01.000Z" },
|
|
140
|
+
delivery: null,
|
|
141
|
+
chain: "c".repeat(64),
|
|
142
|
+
receipt_sig: "s",
|
|
143
|
+
exceptions: [],
|
|
144
|
+
};
|
|
145
|
+
const receipt = await new CodeSparRail(fakeApi(read).api).receipt("rcpt_1", actor);
|
|
146
|
+
expect(receipt).toMatchObject({ chain_version: 4, approval: { items_hash: ITEMS_HASH, batch_hash: null }, mandate: { id: "cm_1", sig_sha256: "d".repeat(64) } });
|
|
147
|
+
expect(JSON.stringify({ ...receipt, raw: undefined })).not.toContain('"sig":"x"');
|
|
148
|
+
const { approval: _a, ...older } = read;
|
|
149
|
+
expect((await new CodeSparRail(fakeApi({ ...older, chain_version: 1 }).api).receipt("rcpt_1", actor))?.approval).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for (const [name, sealed] of [
|
|
153
|
+
["another list", { items_hash: `sha256:${"9".repeat(64)}`, batch_hash: null }],
|
|
154
|
+
["no approval at all", null],
|
|
155
|
+
] as const) {
|
|
156
|
+
it(`a receipt that seals ${name} is ReceiptSealMismatchError, after the outcome is saved`, async () => {
|
|
157
|
+
const h = harness({ mode: "human", wrapRail: sealingApproval(sealed) });
|
|
158
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
|
|
159
|
+
if (!d.ok) throw new Error("refused");
|
|
160
|
+
const approved = h.engine.approve(d.execution.id, approver);
|
|
161
|
+
const artifact = h.store.getApproval(approved.approval_id!)!;
|
|
162
|
+
await expect(h.engine.execute(approved.id)).rejects.toBeInstanceOf(ReceiptSealMismatchError);
|
|
163
|
+
expect(h.engine.get(approved.id)?.state).toBe("settled");
|
|
164
|
+
const mismatch = h.bundle.readEvents().find((e) => e["type"] === "receipt.seal_mismatch");
|
|
165
|
+
expect(mismatch?.["payload"]).toMatchObject({ sealed_approval: sealed, sent_approval: { items_hash: artifact.items_hash } });
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
it("the same hash in the other spelling of the prefix is the same approval", async () => {
|
|
170
|
+
const h = harness({ mode: "human" });
|
|
171
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
|
|
172
|
+
if (!d.ok) throw new Error("refused");
|
|
173
|
+
const approved = h.engine.approve(d.execution.id, approver);
|
|
174
|
+
const bare = h.store.getApproval(approved.approval_id!)!.items_hash.replace(/^sha256:/, "");
|
|
175
|
+
const spelled = harness({ mode: "human", dir: h.dir, wrapRail: sealingApproval({ items_hash: bare, batch_hash: null }) });
|
|
176
|
+
expect((await spelled.engine.execute(approved.id)).state).toBe("settled");
|
|
177
|
+
expect(spelled.bundle.readEvents().some((e) => e["type"] === "receipt.seal_mismatch")).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("the stub rail", () => {
|
|
182
|
+
it("makes the same refusal, so a scenario cannot pass without the hashes", async () => {
|
|
183
|
+
const h = harness();
|
|
184
|
+
const { approval: _a, ...bare } = payment();
|
|
185
|
+
expect(await h.rail.pay(bare)).toMatchObject({ status: "failed", code: "approval_missing" });
|
|
186
|
+
expect(h.rail.payCount).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("answers a repeat under a different approval as attempt_id_conflict naming it, the way the API's fingerprint does (ent#1670)", async () => {
|
|
190
|
+
const h = harness();
|
|
191
|
+
expect(await h.rail.pay(payment())).toMatchObject({ status: "settled" });
|
|
192
|
+
const other = await h.rail.pay(payment({ approval: { items_hash: `sha256:${"9".repeat(64)}` } }));
|
|
193
|
+
expect(other).toMatchObject({ status: "failed", code: "attempt_id_conflict", held: "conflict" });
|
|
194
|
+
expect((other as { message: string }).message).toContain("approval");
|
|
195
|
+
expect(h.rail.payCount).toBe(1);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("the bundle's receipt copy", () => {
|
|
200
|
+
it("names the artifact the spend carried, by id, and stays masked", async () => {
|
|
201
|
+
const h = harness({ mode: "human" });
|
|
202
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
|
|
203
|
+
if (!d.ok) throw new Error("refused");
|
|
204
|
+
const approved = h.engine.approve(d.execution.id, approver);
|
|
205
|
+
const settled = await h.engine.execute(approved.id);
|
|
206
|
+
const copy = JSON.parse(readFileSync(join(h.bundle.dir, "receipts", `${settled.outcomes[0]!.receipt_id}.json`), "utf8")) as Record<string, unknown>;
|
|
207
|
+
expect(copy["approval_id"]).toBe(approved.approval_id);
|
|
208
|
+
expect(h.bundle.readApprovals().map((a) => a.approval_id)).toContain(copy["approval_id"]);
|
|
209
|
+
expect(JSON.stringify(copy)).not.toContain(ESCOLA);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("is held against the read field by field, the payee in its masked form", () => {
|
|
213
|
+
const read = {
|
|
214
|
+
receipt_id: "rcpt_1",
|
|
215
|
+
chain: "c".repeat(64),
|
|
216
|
+
mandate: { id: "cm_1", nonce: "n" },
|
|
217
|
+
quote: { payee: ESCOLA },
|
|
218
|
+
payment: { rail: "pix", amount_minor: 185000, attempt_id: "att_0", money_moved: false, at: "2026-09-26T18:00:01.000Z" },
|
|
219
|
+
};
|
|
220
|
+
const copy = { receipt_id: "rcpt_1", chain: "c".repeat(64), mandate: { id: "cm_1" }, payment: { amount_minor: 185000, payee: maskPayee(ESCOLA), attempt_id: "att_0", money_moved: false, sandbox: true, at: "2026-09-26T18:00:01.000Z" } };
|
|
221
|
+
expect(copyDisagreesWithRead(copy, read)).toEqual([]);
|
|
222
|
+
expect(copyDisagreesWithRead({ ...copy, payment: { ...copy.payment, amount_minor: 1 } }, read)).toEqual(["payment.amount_minor"]);
|
|
223
|
+
expect(copyDisagreesWithRead({ ...copy, payment: { ...copy.payment, payee: maskPayee(MERCADO) } }, read)).toEqual(["payment.payee"]);
|
|
224
|
+
expect(copyDisagreesWithRead({ ...copy, chain: "d".repeat(64) }, read)).toEqual(["chain"]);
|
|
225
|
+
// A copy written since 0.16.10 carries what was sealed, and is held to it; one from before carries neither and is not faulted.
|
|
226
|
+
const sealedRead = { ...read, chain_version: 4, approval: { items_hash: ITEMS_HASH, batch_hash: null } };
|
|
227
|
+
const sealedCopy = { ...copy, chain_version: 4, approval: { batch_hash: null, items_hash: ITEMS_HASH } };
|
|
228
|
+
expect(copyDisagreesWithRead(sealedCopy, sealedRead)).toEqual([]);
|
|
229
|
+
expect(copyDisagreesWithRead({ ...sealedCopy, approval: { items_hash: `sha256:${"9".repeat(64)}`, batch_hash: null } }, sealedRead)).toEqual(["approval"]);
|
|
230
|
+
expect(copyDisagreesWithRead({ ...sealedCopy, chain_version: 3 }, sealedRead)).toEqual(["chain_version"]);
|
|
231
|
+
expect(copyDisagreesWithRead(copy, sealedRead)).toEqual([]);
|
|
232
|
+
});
|
|
233
|
+
});
|