@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
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a repeated `attempt_id` answers on the deployed API (ent#1671,
|
|
3
|
+
* codespar-enterprise #1683), and what the kit reads each answer as. The
|
|
4
|
+
* CodeSpar rail is driven through the real SDK client against a mocked HTTP
|
|
5
|
+
* server, so the error envelope is parsed the way it is in production; the
|
|
6
|
+
* stub rail is held to the same table, because a stub that answered a repeat
|
|
7
|
+
* more kindly than the API would let a suite pass that the API fails.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdtempSync } from "node:fs";
|
|
10
|
+
import { createServer, type Server } from "node:http";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
+
import { createCodeSparClient } from "../src/api/client.js";
|
|
15
|
+
import { CodeSparRail } from "../src/api/rail.js";
|
|
16
|
+
import { batchAttemptId } from "../src/hash.js";
|
|
17
|
+
import { quoteFromApproval } from "../src/quote.js";
|
|
18
|
+
import type { PaymentRail, RailOutcome, RailPayment } from "../src/rail.js";
|
|
19
|
+
import { StubRail } from "../src/stubs/rail.js";
|
|
20
|
+
import { StateStore } from "../src/state/store.js";
|
|
21
|
+
import { ESCOLA, harness, MERCADO } from "./helpers.js";
|
|
22
|
+
|
|
23
|
+
const actor = { type: "agent" as const, agent: "bills-agent@0.1.0", on_behalf_of: "usr_demo" };
|
|
24
|
+
|
|
25
|
+
function payment(over: Partial<RailPayment> = {}): RailPayment {
|
|
26
|
+
return {
|
|
27
|
+
attempt_id: "ska_replay_0",
|
|
28
|
+
mandate_id: "mdt_test_0001",
|
|
29
|
+
amount_minor: 185000,
|
|
30
|
+
currency: "BRL",
|
|
31
|
+
payee: ESCOLA,
|
|
32
|
+
purpose: "contas do mes",
|
|
33
|
+
agent_id: "bills-agent",
|
|
34
|
+
quote: { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA },
|
|
35
|
+
approval: { items_hash: `sha256:${"1".repeat(64)}` },
|
|
36
|
+
actor,
|
|
37
|
+
...over,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Answer = { status: number; body: unknown };
|
|
42
|
+
|
|
43
|
+
let server: Server;
|
|
44
|
+
let baseUrl: string;
|
|
45
|
+
let answer: Answer;
|
|
46
|
+
let posts = 0;
|
|
47
|
+
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
server = createServer((req, res) => {
|
|
50
|
+
if (req.method === "POST") posts += 1;
|
|
51
|
+
res.writeHead(answer.status, { "content-type": "application/json" });
|
|
52
|
+
res.end(JSON.stringify(answer.body));
|
|
53
|
+
});
|
|
54
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
55
|
+
baseUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterAll(async () => {
|
|
59
|
+
server.closeAllConnections();
|
|
60
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
posts = 0;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function rail(): CodeSparRail {
|
|
68
|
+
// The placeholder passes the `csk_test_` guard and is the one test-key-shaped string the secret scan allows.
|
|
69
|
+
return new CodeSparRail(createCodeSparClient({ apiKey: "csk_test_your_key_here", baseUrl, timeoutMs: 5_000 }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function refusal(code: string, details?: Record<string, unknown>): Answer {
|
|
73
|
+
return { status: 409, body: { error: { code, message: `api: ${code}`, ...(details ? { details } : {}) }, request_id: null } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The ORIGINAL settled body, as the API stores it and answers it again with `idempotent_replay: true`. */
|
|
77
|
+
const settledBody = {
|
|
78
|
+
payment: { transactionId: "tx_original", moneyMoved: false },
|
|
79
|
+
receipt: { id: "rcpt_original" },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
describe("CodeSparRail reads every answer to a repeated attempt_id (ent#1671)", () => {
|
|
83
|
+
it("settled: the original body with idempotent_replay is the original outcome, same transaction and same receipt, marked replayed", async () => {
|
|
84
|
+
answer = { status: 200, body: { ...settledBody, idempotent_replay: true } };
|
|
85
|
+
for (const outcome of [await rail().pay(payment()), await rail().lookup("ska_replay_0", payment())]) {
|
|
86
|
+
expect(outcome).toMatchObject({ status: "settled", transaction_id: "tx_original", receipt_id: "rcpt_original", replayed: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("the call that settles it is not a replay", async () => {
|
|
91
|
+
answer = { status: 200, body: { ...settledBody, idempotent_replay: false } };
|
|
92
|
+
const outcome = await rail().pay(payment());
|
|
93
|
+
expect(outcome).toMatchObject({ status: "settled" });
|
|
94
|
+
expect("replayed" in outcome).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("psp_attempt_in_flight: never a refusal. pay() reads it as unknown, lookup() as still running", async () => {
|
|
98
|
+
answer = refusal("psp_attempt_in_flight");
|
|
99
|
+
expect(await rail().pay(payment())).toMatchObject({ status: "uncertain", code: "psp_attempt_in_flight" });
|
|
100
|
+
expect(await rail().lookup("ska_replay_0", payment())).toEqual({ status: "in_flight" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("psp_attempt_uncertain: pinned for reconciliation, on both paths", async () => {
|
|
104
|
+
answer = refusal("psp_attempt_uncertain");
|
|
105
|
+
expect(await rail().pay(payment())).toMatchObject({ status: "uncertain", code: "psp_attempt_uncertain" });
|
|
106
|
+
expect(await rail().lookup("ska_replay_0", payment())).toMatchObject({ status: "uncertain", code: "psp_attempt_uncertain" });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("psp_attempt_conflict: failed with no money, and the id is spent", async () => {
|
|
110
|
+
answer = refusal("psp_attempt_conflict");
|
|
111
|
+
expect(await rail().pay(payment())).toEqual({ status: "failed", code: "psp_attempt_conflict", message: "api: psp_attempt_conflict", spent: true });
|
|
112
|
+
expect(await rail().lookup("ska_replay_0", payment())).toMatchObject({ status: "failed", spent: true });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("attempt_id_conflict and attempt_id_unavailable: a refusal that sent nothing, held for another payment or project, and NEVER spent", async () => {
|
|
116
|
+
for (const [code, details, held] of [
|
|
117
|
+
["attempt_id_conflict", { mismatched_fields: ["quote"], attempt_id: "ska_replay_0" }, "conflict"],
|
|
118
|
+
["attempt_id_unavailable", { attempt_id: "ska_replay_0" }, "unavailable"],
|
|
119
|
+
] as const) {
|
|
120
|
+
answer = refusal(code, details);
|
|
121
|
+
for (const outcome of [await rail().pay(payment()), await rail().lookup("ska_replay_0", payment())]) {
|
|
122
|
+
expect(outcome).toEqual({ status: "failed", code, message: `api: ${code}`, held });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("every answer above is one POST: a lookup is the same presentation, never a second call of its own", async () => {
|
|
128
|
+
answer = refusal("psp_attempt_in_flight");
|
|
129
|
+
await rail().lookup("ska_replay_0", payment());
|
|
130
|
+
expect(posts).toBe(1);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("the stub rail answers a repeat the way the API does", () => {
|
|
135
|
+
function stub(options: ConstructorParameters<typeof StubRail>[1] = {}): StubRail {
|
|
136
|
+
return new StubRail(new StateStore(join(mkdtempSync(join(tmpdir(), "agent-core-replay-")), "state.db")), options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
it("settled: the original outcome, marked replayed, and nothing paid again", async () => {
|
|
140
|
+
const r = stub();
|
|
141
|
+
const first = await r.pay(payment());
|
|
142
|
+
const again = await r.pay(payment());
|
|
143
|
+
expect(r.payCount).toBe(1);
|
|
144
|
+
expect(again).toMatchObject({ status: "settled", replayed: true, transaction_id: (first as { transaction_id: string }).transaction_id, receipt_id: (first as { receipt_id: string }).receipt_id });
|
|
145
|
+
expect("replayed" in first).toBe(false);
|
|
146
|
+
expect((again as { raw: Record<string, unknown> }).raw["idempotent_replay"]).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("a different payment under the same id is attempt_id_conflict, whatever became of the first, and names what differs", async () => {
|
|
150
|
+
const r = stub();
|
|
151
|
+
await r.pay(payment());
|
|
152
|
+
const changed = await r.pay(payment({ quote: { ...payment().quote!, at: "2026-09-23T18:00:00.000Z" } }));
|
|
153
|
+
expect(changed).toMatchObject({ status: "failed", code: "attempt_id_conflict", held: "conflict" });
|
|
154
|
+
expect((changed as { message: string }).message).toContain("quote");
|
|
155
|
+
expect((changed as { spent?: true }).spent).toBeUndefined();
|
|
156
|
+
const otherPayee = await r.pay(payment({ payee: MERCADO, quote: { ...payment().quote!, payee: MERCADO } }));
|
|
157
|
+
expect((otherPayee as { message: string }).message).toContain("payee");
|
|
158
|
+
expect(r.payCount).toBe(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("failed: psp_attempt_conflict, spent", async () => {
|
|
162
|
+
const r = stub({ refusePayees: [ESCOLA] });
|
|
163
|
+
expect(await r.pay(payment())).toMatchObject({ status: "failed", code: "psp_dispatch_failed" });
|
|
164
|
+
expect(await r.pay(payment())).toMatchObject({ status: "failed", code: "psp_attempt_conflict", spent: true });
|
|
165
|
+
expect(r.payCount).toBe(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("in flight: psp_attempt_in_flight on another presentation, on the shared books another machine reads too", async () => {
|
|
169
|
+
const ledger = new StateStore(join(mkdtempSync(join(tmpdir(), "agent-core-ledger-")), "rail.db"));
|
|
170
|
+
const a = stub({ ledger, uncertainPayees: [ESCOLA] });
|
|
171
|
+
expect(await a.pay(payment())).toMatchObject({ status: "uncertain", code: "psp_dispatch_uncertain" });
|
|
172
|
+
const b = stub({ ledger });
|
|
173
|
+
expect(await b.pay(payment())).toMatchObject({ status: "uncertain", code: "psp_attempt_in_flight" });
|
|
174
|
+
expect(b.payCount).toBe(0);
|
|
175
|
+
expect(await b.lookup("ska_replay_0", payment())).toEqual({ status: "in_flight" });
|
|
176
|
+
expect(await b.lookup("ska_replay_0", payment())).toMatchObject({ status: "settled" });
|
|
177
|
+
expect(b.payCount).toBe(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("a batch line's quote is the same on every run of the list", () => {
|
|
182
|
+
it("stable leaves the approval time out; anything else keeps it", () => {
|
|
183
|
+
const artifact = { approved_at: "2026-09-23T18:00:00.000Z", items: [{ beneficiary: "Escola Aurora", description: "outubro", amount: 185000, payee: ESCOLA }] } as never;
|
|
184
|
+
expect(quoteFromApproval(artifact, 0, "contas do mes")).toEqual({ seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, at: "2026-09-23T18:00:00.000Z" });
|
|
185
|
+
expect(quoteFromApproval(artifact, 0, "contas do mes", { stable: true })).toEqual({ seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA });
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* When a batch line moves to the next generation of its id: on
|
|
191
|
+
* `psp_attempt_conflict` — the server stating the attempt failed with no
|
|
192
|
+
* money — and on NOTHING else. Advancing on an unknown outcome is how one line
|
|
193
|
+
* gets paid under two ids; advancing on `attempt_id_conflict` or
|
|
194
|
+
* `attempt_id_unavailable` is minting a new payment for an id somebody else's
|
|
195
|
+
* record holds.
|
|
196
|
+
*/
|
|
197
|
+
describe("a batch line's attempt id advances on a spent id, and on nothing else", () => {
|
|
198
|
+
const BATCH = { ref: "folha-teste", batch_hash: `sha256:${"a".repeat(64)}`, index: 0, count: 1 };
|
|
199
|
+
const approver = { id: "usr_demo", channel: "terminal" };
|
|
200
|
+
|
|
201
|
+
/** A rail that answers `script(n)` for its n-th call, and records every attempt id it was shown. */
|
|
202
|
+
function scripted(script: (call: number) => RailOutcome) {
|
|
203
|
+
const seen: string[] = [];
|
|
204
|
+
const wrap = (): PaymentRail => ({
|
|
205
|
+
name: "stub",
|
|
206
|
+
pay: async (p) => (seen.push(p.attempt_id), script(seen.length - 1)),
|
|
207
|
+
lookup: async () => undefined,
|
|
208
|
+
receipt: async () => undefined,
|
|
209
|
+
});
|
|
210
|
+
return { seen, wrap };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function runLine(rail: ReturnType<typeof scripted>) {
|
|
214
|
+
const h = harness({ wrapRail: rail.wrap });
|
|
215
|
+
const d = await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }], batch: BATCH });
|
|
216
|
+
if (!d.ok) throw new Error(`refused before draft: ${d.reason}`);
|
|
217
|
+
h.engine.approve(d.execution.id, approver);
|
|
218
|
+
return h.engine.execute(d.execution.id);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const held: Array<[string, RailOutcome]> = [
|
|
222
|
+
["attempt_id_conflict", { status: "failed", code: "attempt_id_conflict", message: "used for a different payment", held: "conflict" }],
|
|
223
|
+
["attempt_id_unavailable", { status: "failed", code: "attempt_id_unavailable", message: "not available", held: "unavailable" }],
|
|
224
|
+
["an unknown refusal", { status: "failed", code: "some_code_nobody_documented", message: "?" }],
|
|
225
|
+
["psp_attempt_in_flight", { status: "uncertain", code: "psp_attempt_in_flight", message: "claimed" }],
|
|
226
|
+
["psp_attempt_uncertain", { status: "uncertain", code: "psp_attempt_uncertain", message: "pinned" }],
|
|
227
|
+
["psp_dispatch_uncertain", { status: "uncertain", code: "psp_dispatch_uncertain", message: "timeout" }],
|
|
228
|
+
];
|
|
229
|
+
for (const [name, answer] of held) {
|
|
230
|
+
it(`${name}: one presentation, the generation-0 id, no generation recorded`, async () => {
|
|
231
|
+
const rail = scripted(() => answer);
|
|
232
|
+
const out = await runLine(rail);
|
|
233
|
+
const g0 = batchAttemptId(out.mandate.id, BATCH, 0);
|
|
234
|
+
expect(rail.seen).toEqual([g0]);
|
|
235
|
+
expect(out.attempt_generations).toBeUndefined();
|
|
236
|
+
expect(out.state).toBe(answer.status === "failed" ? "failed" : "executing");
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
it("psp_attempt_conflict: the next generation, derived the same way on any machine, and saved on the execution", async () => {
|
|
241
|
+
const settled: RailOutcome = { status: "settled", transaction_id: "tx_g2", receipt_id: null, money_moved: false, sandbox: true, raw: {} };
|
|
242
|
+
const spent: RailOutcome = { status: "failed", code: "psp_attempt_conflict", message: "failed, no money", spent: true };
|
|
243
|
+
const rail = scripted((call) => (call < 2 ? spent : settled));
|
|
244
|
+
const out = await runLine(rail);
|
|
245
|
+
expect(rail.seen).toEqual([0, 1, 2].map((g) => batchAttemptId(out.mandate.id, BATCH, 0, g)));
|
|
246
|
+
expect(out.state).toBe("settled");
|
|
247
|
+
expect(out.outcomes[0]!.attempt_id).toBe(rail.seen[2]);
|
|
248
|
+
expect(out.attempt_generations).toEqual({ 0: 2 });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("a psp_attempt_conflict the rail did not mark spent does not advance either", async () => {
|
|
252
|
+
const rail = scripted(() => ({ status: "failed", code: "psp_attempt_conflict", message: "no spent flag" }));
|
|
253
|
+
const out = await runLine(rail);
|
|
254
|
+
expect(rail.seen).toHaveLength(1);
|
|
255
|
+
expect(out.attempt_generations).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("stops after eight spent generations, failed, never looping", async () => {
|
|
259
|
+
const rail = scripted(() => ({ status: "failed", code: "psp_attempt_conflict", message: "failed, no money", spent: true }));
|
|
260
|
+
const out = await runLine(rail);
|
|
261
|
+
expect(rail.seen).toHaveLength(9);
|
|
262
|
+
expect(out.state).toBe("failed");
|
|
263
|
+
expect(out.attempt_generations).toEqual({ 0: 8 });
|
|
264
|
+
});
|
|
265
|
+
});
|