@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 13: the contract of the harness file. Policy stays outside the
|
|
3
|
+
* provider: the state machine, the mandate, the guardrails and `tools.json`
|
|
4
|
+
* are the core's. Changing the model changes nothing about what can be paid.
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolSpec {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
input_schema: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToolCall {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
input: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Reply {
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ConversationMessage =
|
|
23
|
+
| { role: "user"; content: string }
|
|
24
|
+
| { role: "assistant"; content: string; tool_calls?: ToolCall[] }
|
|
25
|
+
| { role: "tool"; tool_call_id: string; name: string; content: string; is_error?: boolean };
|
|
26
|
+
|
|
27
|
+
export interface Turn {
|
|
28
|
+
system: string;
|
|
29
|
+
messages: ConversationMessage[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AgentRuntime {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
step(input: Turn, tools: ToolSpec[]): Promise<ToolCall[] | Reply>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isReply(out: ToolCall[] | Reply): out is Reply {
|
|
38
|
+
return !Array.isArray(out);
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The offer a spend presents to the API (`SpendQuote`): seller, resource,
|
|
3
|
+
* price and payee. When a spend carries one, the API binds it into the
|
|
4
|
+
* receipt's chain between the mandate and the payment, so the sealed receipt
|
|
5
|
+
* names the payee; without one the chain carries no payee at all, and
|
|
6
|
+
* `GET /v1/consumers/receipts/{id}` answers `quote: null` (OPEN_QUESTIONS §18).
|
|
7
|
+
*
|
|
8
|
+
* The quote is built from the approval artifact's items, the list its
|
|
9
|
+
* `items_hash` covers, and from nothing the model says after approval. The
|
|
10
|
+
* API compares the quote to what settled in OBSERVE mode only: a divergence is
|
|
11
|
+
* recorded on the receipt and the money still moves. So the kit refuses a
|
|
12
|
+
* divergence itself, before the call.
|
|
13
|
+
*/
|
|
14
|
+
import type { ApprovalArtifact } from "./types.js";
|
|
15
|
+
|
|
16
|
+
export interface SpendQuote {
|
|
17
|
+
seller: string;
|
|
18
|
+
resource: string;
|
|
19
|
+
price_minor: number;
|
|
20
|
+
payee: string;
|
|
21
|
+
/**
|
|
22
|
+
* When the offer was approved: the artifact's `approved_at`. Absent on a
|
|
23
|
+
* batch line, whose quote must be the same on every run of the list: the
|
|
24
|
+
* API compares the quote's digest on a repeat of an `attempt_id`, so an
|
|
25
|
+
* approval time in it would make the same line a different payment on
|
|
26
|
+
* every machine (OPEN_QUESTIONS §39c). The time is still in the artifact.
|
|
27
|
+
*/
|
|
28
|
+
at?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The quote for line `index` of what was approved, or undefined when the
|
|
33
|
+
* artifact has no such line. `stable` leaves the approval time out, for a
|
|
34
|
+
* batch line (see `SpendQuote.at`).
|
|
35
|
+
*/
|
|
36
|
+
export function quoteFromApproval(artifact: ApprovalArtifact, index: number, purpose: string, options: { stable?: boolean } = {}): SpendQuote | undefined {
|
|
37
|
+
const item = artifact.items[index];
|
|
38
|
+
if (!item) return undefined;
|
|
39
|
+
const quote = { seller: item.beneficiary, resource: item.description ?? purpose, price_minor: item.amount, payee: item.payee };
|
|
40
|
+
return options.stable ? quote : { ...quote, at: artifact.approved_at };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The quote a spend presents, or why it may not go out: none, or one that does not name exactly the amount and the payee it pays. */
|
|
44
|
+
export function checkQuote(payment: { amount_minor: number; payee: string; quote?: SpendQuote | undefined }): { ok: true; quote: SpendQuote } | { ok: false; code: "quote_missing" | "quote_mismatch"; detail: string } {
|
|
45
|
+
const { quote } = payment;
|
|
46
|
+
if (!quote) return { ok: false, code: "quote_missing", detail: "a spend carries the quote of what was approved, or it does not go out" };
|
|
47
|
+
if (quote.price_minor !== payment.amount_minor) return { ok: false, code: "quote_mismatch", detail: `the approved price ${quote.price_minor} is not the amount ${payment.amount_minor} about to be spent` };
|
|
48
|
+
if (quote.payee !== payment.payee) return { ok: false, code: "quote_mismatch", detail: "the approved payee is not the payee about to be paid" };
|
|
49
|
+
return { ok: true, quote };
|
|
50
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The payment rail the core dispatches to once an execution is `executing`.
|
|
3
|
+
* Two implementations: the CodeSpar sandbox through the API (needs a
|
|
4
|
+
* `csk_test_` key) and a local stub persisted in state.db for CI and
|
|
5
|
+
* scenarios. Idempotence is OPT-IN on the API and the core always opts in:
|
|
6
|
+
* every payment carries an explicit `attempt_id`, and presenting the same
|
|
7
|
+
* attempt again answers from the rail's record of it instead of paying twice
|
|
8
|
+
* (ent#1671): the original outcome when it settled, "still running" while it
|
|
9
|
+
* is in flight, "unknown, reconcile" while it is pinned, and a refusal when
|
|
10
|
+
* the same id arrives with a different payment. A payment sent WITHOUT an id
|
|
11
|
+
* is a new payment every time; nothing here sends one.
|
|
12
|
+
*/
|
|
13
|
+
import type { ApiOperation, ApiRequestBody } from "@codespar/sdk";
|
|
14
|
+
import type { SpendQuote } from "./quote.js";
|
|
15
|
+
import type { Actor, ApprovalArtifact, ChargeInstrument } from "./types.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The `approval` a spend presents (`SpendApproval` in the API's document,
|
|
19
|
+
* typed since @codespar/sdk 0.16.10): `sha256:<64 lowercase hex>`, as
|
|
20
|
+
* `itemsHash` and `batchHash` write it. Taken from the SDK so a change on
|
|
21
|
+
* the API's side fails `tsc` here.
|
|
22
|
+
*/
|
|
23
|
+
export type SpendApproval = NonNullable<ApiRequestBody<ApiOperation<"/v1/consumer-payments/execute", "post">>["approval"]>;
|
|
24
|
+
|
|
25
|
+
/** What a receipt says it SEALED as the approval link: `batch_hash` is null when the spend sent none. */
|
|
26
|
+
export interface SealedSpendApproval {
|
|
27
|
+
items_hash: string;
|
|
28
|
+
batch_hash: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The hashes of what was approved, for a spend of any line of this artifact: the ones the artifact's own HMAC covers. */
|
|
32
|
+
export function spendApprovalOf(artifact: Pick<ApprovalArtifact, "items_hash" | "batch">): SpendApproval {
|
|
33
|
+
return { items_hash: artifact.items_hash, ...(artifact.batch ? { batch_hash: artifact.batch.batch_hash } : {}) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const APPROVAL_HASH = /^(sha256:)?[0-9a-f]{64}$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The approval a spend presents, or why it may not go out: none, or a hash
|
|
40
|
+
* the API would refuse (`invalid_approval_hash`: lowercase hex SHA-256, the
|
|
41
|
+
* `sha256:` prefix optional). The shape and never the content, like the API:
|
|
42
|
+
* WHICH list is the artifact's business, checked before `executing`.
|
|
43
|
+
*/
|
|
44
|
+
export function checkSpendApproval(payment: { approval?: SpendApproval | undefined }): { ok: true; approval: SpendApproval } | { ok: false; code: "approval_missing" | "approval_malformed"; detail: string } {
|
|
45
|
+
const { approval } = payment;
|
|
46
|
+
if (!approval) return { ok: false, code: "approval_missing", detail: "a spend carries the hashes of the approval artifact, or it does not go out" };
|
|
47
|
+
if (!APPROVAL_HASH.test(approval.items_hash)) return { ok: false, code: "approval_malformed", detail: "the approval's items_hash is not a lowercase hex SHA-256" };
|
|
48
|
+
if (approval.batch_hash !== undefined && !APPROVAL_HASH.test(approval.batch_hash)) return { ok: false, code: "approval_malformed", detail: "the approval's batch_hash is not a lowercase hex SHA-256" };
|
|
49
|
+
return { ok: true, approval };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RailPayment {
|
|
53
|
+
attempt_id: string;
|
|
54
|
+
mandate_id: string;
|
|
55
|
+
amount_minor: number;
|
|
56
|
+
currency: string;
|
|
57
|
+
payee: string;
|
|
58
|
+
purpose: string;
|
|
59
|
+
agent_id: string;
|
|
60
|
+
/** The principal the agent acts for (the mandate's `consumer_id`). A receivable settles into THIS identity's account; `POST /v1/charges` needs it on the wire. */
|
|
61
|
+
consumer_id?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
/** Display name of the counterparty (a receivable's debtor). The rail that issues a charge needs it; a payout rail ignores it. */
|
|
64
|
+
beneficiary?: string;
|
|
65
|
+
/** Due date of a receivable, `YYYY-MM-DD`. */
|
|
66
|
+
due_date?: string;
|
|
67
|
+
/** What was approved for this line, presented with a spend so the sealed receipt names the payee. A charge rail ignores it. */
|
|
68
|
+
quote?: SpendQuote;
|
|
69
|
+
/**
|
|
70
|
+
* The approval artifact's hashes, exactly as the artifact carries them: its
|
|
71
|
+
* `items_hash`, and for a batch line its `batch.batch_hash`. The API seals
|
|
72
|
+
* them into the receipt's chain as a link of their own (chain v4,
|
|
73
|
+
* ent#1670), so whoever holds the receipt read can show THIS payment was
|
|
74
|
+
* sealed against THAT list. It is the caller's claim, sealed: the API checks
|
|
75
|
+
* its shape and never its content. A charge rail ignores it.
|
|
76
|
+
*/
|
|
77
|
+
approval?: SpendApproval;
|
|
78
|
+
/** Section 4.5: carried on every call. The API has no wire field for it yet; see OPEN_QUESTIONS. */
|
|
79
|
+
actor: Actor;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type RailOutcome =
|
|
83
|
+
| {
|
|
84
|
+
/**
|
|
85
|
+
* The rail took the attempt and the outcome comes later: a receivable
|
|
86
|
+
* was issued and its payer has not acted yet. The execution stays
|
|
87
|
+
* `executing`; `lookup()` or a `commerce.charge.*` event closes it.
|
|
88
|
+
*/
|
|
89
|
+
status: "accepted";
|
|
90
|
+
transaction_id: string;
|
|
91
|
+
instrument: ChargeInstrument;
|
|
92
|
+
sandbox: boolean;
|
|
93
|
+
raw: unknown;
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
status: "settled";
|
|
97
|
+
transaction_id: string;
|
|
98
|
+
receipt_id: string | null;
|
|
99
|
+
money_moved: boolean;
|
|
100
|
+
sandbox: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The rail answered from its record of an attempt that had ALREADY
|
|
103
|
+
* settled (`idempotent_replay`, ent#1671): every other field is the
|
|
104
|
+
* original answer, and nothing moved, was held or was sealed by this
|
|
105
|
+
* call. Absent on the call that settled it.
|
|
106
|
+
*/
|
|
107
|
+
replayed?: true;
|
|
108
|
+
raw: unknown;
|
|
109
|
+
}
|
|
110
|
+
| {
|
|
111
|
+
/** The rail answered and refused. Nothing moved. */
|
|
112
|
+
status: "failed";
|
|
113
|
+
code: string;
|
|
114
|
+
message: string;
|
|
115
|
+
/**
|
|
116
|
+
* The rail holds this `attempt_id` as an attempt that failed and moved
|
|
117
|
+
* no money, and will refuse it for good (`psp_attempt_conflict`). Set
|
|
118
|
+
* only on that answer: the id is spent, and a batch line pays under the
|
|
119
|
+
* next generation of its id (`batchAttemptId`).
|
|
120
|
+
*/
|
|
121
|
+
spent?: true;
|
|
122
|
+
/**
|
|
123
|
+
* The rail holds this `attempt_id` for something else: a DIFFERENT
|
|
124
|
+
* payment (`attempt_id_conflict`, checked before whatever became of it)
|
|
125
|
+
* or another project of the organization (`attempt_id_unavailable`).
|
|
126
|
+
* Nothing was held or sent. Never `spent`: the id is not free and not
|
|
127
|
+
* burned, it is someone else's, and presenting it again answers the same.
|
|
128
|
+
*/
|
|
129
|
+
held?: "conflict" | "unavailable";
|
|
130
|
+
raw?: unknown;
|
|
131
|
+
}
|
|
132
|
+
| {
|
|
133
|
+
/** The outcome is unknown (timeout, 5xx, `psp_dispatch_uncertain`). Never retried blind: reconciled. */
|
|
134
|
+
status: "uncertain";
|
|
135
|
+
code: string;
|
|
136
|
+
message: string;
|
|
137
|
+
raw?: unknown;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export interface RailReceipt {
|
|
141
|
+
receipt_id: string;
|
|
142
|
+
/** `payment` (the API's sealed receipt) or `charge` (the paid receivable as the API reports it; no seal exists for it today). */
|
|
143
|
+
kind?: "payment" | "charge";
|
|
144
|
+
state: string;
|
|
145
|
+
/** `sig_sha256` is SHA-256 of the mandate's own signature, which a v4 chain seals in place of it; never the signature itself, which is a bearer proof. */
|
|
146
|
+
mandate: { id: string; sig_sha256?: string };
|
|
147
|
+
/**
|
|
148
|
+
* The chain format the receipt was sealed under (4 once it carries the
|
|
149
|
+
* approval link), and the approval it sealed, as the API read them back:
|
|
150
|
+
* `null` when it sealed none. Absent when the rail cannot say — a charge,
|
|
151
|
+
* which seals nothing.
|
|
152
|
+
*/
|
|
153
|
+
chain_version?: number;
|
|
154
|
+
approval?: SealedSpendApproval | null;
|
|
155
|
+
/** `payee` is the one the receipt SEALED — the quote's, on a payment — and null when it sealed none. Never copied from the execution. */
|
|
156
|
+
payment: { amount_minor: number; payee: string | null; attempt_id: string; money_moved: boolean; sandbox: boolean; at: string };
|
|
157
|
+
/** Null when the API seals nothing for this kind of record. */
|
|
158
|
+
chain: string | null;
|
|
159
|
+
receipt_sig: string | null;
|
|
160
|
+
/** The Ed25519 signature CodeSpar seals alongside the HMAC (base64url) and
|
|
161
|
+
* the published key that made it. Null on a receipt sealed before the API
|
|
162
|
+
* had the capability, on a record the API seals nothing for (a paid
|
|
163
|
+
* charge), and on the stub rail, which is not CodeSpar and must not look
|
|
164
|
+
* like it. Carried onto the bundle's copy so THAT copy is what a third
|
|
165
|
+
* party checks: `npm run verify runs/<run-id>/receipts/<id>.json`. */
|
|
166
|
+
receipt_sig_ed25519: string | null;
|
|
167
|
+
receipt_sig_kid: string | null;
|
|
168
|
+
/** Section 4.5: the kit stamps the actor onto the local copy of every receipt. */
|
|
169
|
+
actor: Actor;
|
|
170
|
+
raw: unknown;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** What a reconcile learns: a recorded outcome, "still running", or nothing. Never a new payment. */
|
|
174
|
+
export type RailLookup = RailOutcome | { status: "in_flight" } | undefined;
|
|
175
|
+
|
|
176
|
+
export interface PaymentRail {
|
|
177
|
+
readonly name: "stub" | "codespar" | "stub-charge" | "codespar-charge";
|
|
178
|
+
pay(payment: RailPayment): Promise<RailOutcome>;
|
|
179
|
+
/**
|
|
180
|
+
* Answers the outcome of an attempt already presented, `in_flight` while the rail is still on it, or `undefined` when the rail
|
|
181
|
+
* never saw it. `transactionId` is the rail's own id for the attempt when the core recorded one (an accepted receivable): a rail
|
|
182
|
+
* whose read is keyed on its id and not on the caller's key looks it up by that.
|
|
183
|
+
*/
|
|
184
|
+
lookup(attemptId: string, payment: RailPayment, transactionId?: string): Promise<RailLookup>;
|
|
185
|
+
receipt(receiptId: string, actor: Actor): Promise<RailReceipt | undefined>;
|
|
186
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `receipt-chain`: the receipt's `chain` recomputed from its body, the way a
|
|
3
|
+
* party outside CodeSpar does it.
|
|
4
|
+
*
|
|
5
|
+
* The Ed25519 signature covers `codespar-receipt:v1:<id>:<chain>`, so on its
|
|
6
|
+
* own it proves CodeSpar sealed SOME body with that digest. Recomputing the
|
|
7
|
+
* digest from the receipt read is what binds THIS body — the payee, the
|
|
8
|
+
* amount, and since chain v4 the approval hashes — to the signature. CodeSpar
|
|
9
|
+
* publishes how, as data: `chain_recipe` on the unauthenticated
|
|
10
|
+
* `/.well-known/codespar-receipt-keys.json` (ent#1670). This module reads that
|
|
11
|
+
* recipe and applies it. It does not carry its own copy of the link shapes: a
|
|
12
|
+
* recipe it does not understand is an answer ("cannot recompute"), never a
|
|
13
|
+
* guess, because a verifier that silently skipped a clause would compute a
|
|
14
|
+
* different digest and blame the receipt.
|
|
15
|
+
*
|
|
16
|
+
* chain = hex(SHA-256(UTF-8(JCS({ "v": chain_version, "links": [...] }))))
|
|
17
|
+
*
|
|
18
|
+
* JCS is RFC 8785, implemented below in full rather than imported: it is
|
|
19
|
+
* thirty lines, the enterprise's own canonicalizer is exactly the code a
|
|
20
|
+
* verifier must not depend on, and a dependency would be one more thing to
|
|
21
|
+
* audit for a third party who wants to reimplement this. Like
|
|
22
|
+
* `receipt-verification`, it imports `node:crypto` and nothing else.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
|
|
26
|
+
/* ── RFC 8785 ────────────────────────────────────────────────── */
|
|
27
|
+
|
|
28
|
+
/** A high surrogate not followed by a low one, or a low one not preceded by a high one. */
|
|
29
|
+
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The JSON Canonicalization Scheme (RFC 8785) of a JSON value.
|
|
33
|
+
*
|
|
34
|
+
* - Objects: members sorted by their names as arrays of UTF-16 code units,
|
|
35
|
+
* which is what the default `Array.prototype.sort` compares (section 3.2.3).
|
|
36
|
+
* - Numbers: the ECMAScript `Number.prototype.toString` serialization, which
|
|
37
|
+
* is what `JSON.stringify` emits for a finite number (section 3.2.2.3);
|
|
38
|
+
* NaN and the infinities are not JSON and are refused.
|
|
39
|
+
* - Strings: `JSON.stringify`'s escaping, which is the RFC's (section
|
|
40
|
+
* 3.2.2.2) — except that JCS works on I-JSON, where a lone surrogate is not
|
|
41
|
+
* a string at all, so one is refused instead of escaped.
|
|
42
|
+
* - No whitespace anywhere.
|
|
43
|
+
*
|
|
44
|
+
* `undefined`, functions and other non-JSON values are refused: canonicalizing
|
|
45
|
+
* something that is not JSON would hash a value nobody can reproduce.
|
|
46
|
+
*/
|
|
47
|
+
export function canonicalize(value: unknown): string {
|
|
48
|
+
if (value === null) return "null";
|
|
49
|
+
switch (typeof value) {
|
|
50
|
+
case "boolean":
|
|
51
|
+
return value ? "true" : "false";
|
|
52
|
+
case "number":
|
|
53
|
+
if (!Number.isFinite(value)) throw new TypeError(`RFC 8785: ${value} is not a JSON number`);
|
|
54
|
+
return JSON.stringify(value);
|
|
55
|
+
case "string":
|
|
56
|
+
if (LONE_SURROGATE.test(value)) throw new TypeError("RFC 8785: a string with a lone surrogate is not I-JSON");
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
case "object": {
|
|
59
|
+
if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
|
|
60
|
+
const members = Object.keys(value as Record<string, unknown>).sort();
|
|
61
|
+
return `{${members
|
|
62
|
+
.map((name) => {
|
|
63
|
+
if (LONE_SURROGATE.test(name)) throw new TypeError("RFC 8785: a member name with a lone surrogate is not I-JSON");
|
|
64
|
+
return `${JSON.stringify(name)}:${canonicalize((value as Record<string, unknown>)[name])}`;
|
|
65
|
+
})
|
|
66
|
+
.join(",")}}`;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
throw new TypeError(`RFC 8785: a ${typeof value} is not a JSON value`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── The published recipe ────────────────────────────────────── */
|
|
74
|
+
|
|
75
|
+
/** One link of the recipe, as `chain_recipe.links[]` publishes it. */
|
|
76
|
+
export interface ChainRecipeLink {
|
|
77
|
+
link: string;
|
|
78
|
+
/** The member of the receipt read the link's fields are taken from. */
|
|
79
|
+
from: string;
|
|
80
|
+
/** When the link is part of the chain: a clause from a small published vocabulary. */
|
|
81
|
+
when: string;
|
|
82
|
+
fields?: string[];
|
|
83
|
+
fields_by_version?: Record<string, string[]>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ChainRecipe {
|
|
87
|
+
canonicalization: string;
|
|
88
|
+
digest: string;
|
|
89
|
+
version: { field: string; rule?: string };
|
|
90
|
+
/** The suffix that marks a field included only when the read has it and it is not null. */
|
|
91
|
+
optional_marker: string;
|
|
92
|
+
links: ChainRecipeLink[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The canonicalization this module implements, spelled as the recipe spells it. */
|
|
96
|
+
export const RECIPE_CANONICALIZATION = "RFC 8785 (JCS)";
|
|
97
|
+
|
|
98
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
99
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isStringArray(value: unknown): value is string[] {
|
|
103
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type RecipeRead = { ok: true; recipe: ChainRecipe } | { ok: false; reason: "recipe_unpublished" | "recipe_unrecognized"; message: string };
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The `chain_recipe` of a key document, or why there is none this module can
|
|
110
|
+
* follow. A document without one comes from a deployment older than ent#1670:
|
|
111
|
+
* nothing is wrong with the receipt, there is just no published way to bind
|
|
112
|
+
* its body. A recipe whose canonicalization or digest is not the one
|
|
113
|
+
* implemented here is refused whole rather than followed halfway.
|
|
114
|
+
*/
|
|
115
|
+
export function readChainRecipe(keyDocument: unknown): RecipeRead {
|
|
116
|
+
// OUTSIDE THE SDK'S TYPES, and the one place it is read: the well-known
|
|
117
|
+
// document's schema does not declare `chain_recipe` yet (ent#1759), so no
|
|
118
|
+
// SDK type exists to read it through. It is validated field by field below
|
|
119
|
+
// instead. When ent#1759 lands and the SDK types it, this read moves onto
|
|
120
|
+
// that type and the validation stays: a verifier checks what it is served.
|
|
121
|
+
const raw = isRecord(keyDocument) ? keyDocument["chain_recipe"] : undefined;
|
|
122
|
+
if (raw === undefined || raw === null) {
|
|
123
|
+
return { ok: false, reason: "recipe_unpublished", message: "the key set publishes no `chain_recipe`, so the receipt's body cannot be bound to its chain (a deployment older than ent#1670)" };
|
|
124
|
+
}
|
|
125
|
+
const unrecognized = (why: string): RecipeRead => ({ ok: false, reason: "recipe_unrecognized", message: `the published \`chain_recipe\` ${why}, so this verifier does not follow it` });
|
|
126
|
+
if (!isRecord(raw)) return unrecognized("is not an object");
|
|
127
|
+
if (raw["canonicalization"] !== RECIPE_CANONICALIZATION) return unrecognized(`names the canonicalization ${JSON.stringify(raw["canonicalization"])}, not ${RECIPE_CANONICALIZATION}`);
|
|
128
|
+
const digest = raw["digest"];
|
|
129
|
+
if (typeof digest !== "string" || !/\bSHA-256\b/.test(digest) || !/\blowercase hex\b/.test(digest)) return unrecognized(`names the digest ${JSON.stringify(digest)}, not lowercase-hex SHA-256`);
|
|
130
|
+
const version = raw["version"];
|
|
131
|
+
if (!isRecord(version) || typeof version["field"] !== "string") return unrecognized("does not name the field that carries the chain version");
|
|
132
|
+
const marker = raw["optional_marker"];
|
|
133
|
+
if (typeof marker !== "string" || marker.length === 0) return unrecognized("does not name its optional-field marker");
|
|
134
|
+
const links = raw["links"];
|
|
135
|
+
if (!Array.isArray(links) || links.length === 0) return unrecognized("lists no links");
|
|
136
|
+
const parsed: ChainRecipeLink[] = [];
|
|
137
|
+
for (const entry of links) {
|
|
138
|
+
if (!isRecord(entry) || typeof entry["link"] !== "string" || typeof entry["from"] !== "string" || typeof entry["when"] !== "string") return unrecognized("carries a link without `link`, `from` and `when`");
|
|
139
|
+
const fields = entry["fields"];
|
|
140
|
+
const byVersion = entry["fields_by_version"];
|
|
141
|
+
if (fields !== undefined && !isStringArray(fields)) return unrecognized(`gives the link ${entry["link"]} fields that are not a list of names`);
|
|
142
|
+
if (byVersion !== undefined && !(isRecord(byVersion) && Object.values(byVersion).every(isStringArray))) return unrecognized(`gives the link ${entry["link"]} per-version fields that are not lists of names`);
|
|
143
|
+
if (fields === undefined && byVersion === undefined) return unrecognized(`gives the link ${entry["link"]} no fields`);
|
|
144
|
+
parsed.push({
|
|
145
|
+
link: entry["link"],
|
|
146
|
+
from: entry["from"],
|
|
147
|
+
when: entry["when"],
|
|
148
|
+
...(fields !== undefined ? { fields } : {}),
|
|
149
|
+
...(byVersion !== undefined ? { fields_by_version: byVersion as Record<string, string[]> } : {}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
recipe: {
|
|
155
|
+
canonicalization: RECIPE_CANONICALIZATION,
|
|
156
|
+
digest,
|
|
157
|
+
version: { field: version["field"], ...(typeof version["rule"] === "string" ? { rule: version["rule"] } : {}) },
|
|
158
|
+
optional_marker: marker,
|
|
159
|
+
links: parsed,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ── Recomputing ─────────────────────────────────────────────── */
|
|
165
|
+
|
|
166
|
+
export type ChainRecomputation =
|
|
167
|
+
| { ok: true; chain: string; version: number }
|
|
168
|
+
| {
|
|
169
|
+
ok: false;
|
|
170
|
+
/**
|
|
171
|
+
* `chain_version_missing` the read does not say which version sealed it.
|
|
172
|
+
* `recipe_unrecognized` a clause, or a version, the recipe does not
|
|
173
|
+
* define in a way this module can follow.
|
|
174
|
+
* `mandate_sig_required` a v1–v3 chain seals the mandate's raw
|
|
175
|
+
* signature, which the read given here does
|
|
176
|
+
* not carry.
|
|
177
|
+
* `payee_masked` the quote's payee is a masked copy; the chain
|
|
178
|
+
* sealed the key itself.
|
|
179
|
+
* `field_missing` any other field the recipe needs is absent.
|
|
180
|
+
*/
|
|
181
|
+
reason: "chain_version_missing" | "recipe_unrecognized" | "mandate_sig_required" | "payee_masked" | "field_missing";
|
|
182
|
+
message: string;
|
|
183
|
+
version: number | null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/** A payee the proof bundle masked (`es***@exemplo.com.br`). No Pix key contains `***`. */
|
|
187
|
+
export function isMaskedPayee(payee: unknown): boolean {
|
|
188
|
+
return typeof payee === "string" && payee.includes("***");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Whether a link applies. `undefined` for a clause the recipe did not publish before this module was written. */
|
|
192
|
+
function applies(when: string, from: string, read: Record<string, unknown>, version: number): boolean | undefined {
|
|
193
|
+
if (when === "always") return true;
|
|
194
|
+
if (when === `${from} is not null`) return read[from] !== null && read[from] !== undefined;
|
|
195
|
+
const byVersion = /^chain_version is (\d+)$/.exec(when);
|
|
196
|
+
if (byVersion) return version === Number(byVersion[1]);
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Recompute `chain` from a receipt read (the JSON of
|
|
202
|
+
* `GET /v1/consumers/receipts/{id}`), following the recipe exactly: links in
|
|
203
|
+
* the published order, a link whose clause does not hold left out rather than
|
|
204
|
+
* nulled, values copied as read (`null` included), a field marked optional
|
|
205
|
+
* included only when present and not null. Timestamps are taken as read: the
|
|
206
|
+
* API seals them in the form it reads them back (RFC 3339 UTC, milliseconds).
|
|
207
|
+
*
|
|
208
|
+
* This is the pure recomputation, for any version. Whether a verifier SHOULD
|
|
209
|
+
* recompute a given version is policy, and lives in `verifyReceiptRead`.
|
|
210
|
+
*/
|
|
211
|
+
export function recomputeChain(read: Record<string, unknown>, recipe: ChainRecipe): ChainRecomputation {
|
|
212
|
+
const versionRaw = read[recipe.version.field];
|
|
213
|
+
if (typeof versionRaw !== "number" || !Number.isSafeInteger(versionRaw) || versionRaw < 1) {
|
|
214
|
+
return { ok: false, reason: "chain_version_missing", version: null, message: `the read carries no \`${recipe.version.field}\`, so which links were sealed cannot be known` };
|
|
215
|
+
}
|
|
216
|
+
const version = versionRaw;
|
|
217
|
+
const links: Record<string, unknown>[] = [];
|
|
218
|
+
for (const spec of recipe.links) {
|
|
219
|
+
const include = applies(spec.when, spec.from, read, version);
|
|
220
|
+
if (include === undefined) return { ok: false, reason: "recipe_unrecognized", version, message: `the recipe includes the link ${spec.link} "when ${spec.when}", a clause this verifier does not know` };
|
|
221
|
+
if (!include) continue;
|
|
222
|
+
const fields = spec.fields ?? spec.fields_by_version?.[String(version)];
|
|
223
|
+
if (!fields) return { ok: false, reason: "recipe_unrecognized", version, message: `the recipe gives the link ${spec.link} no fields for chain v${version}` };
|
|
224
|
+
const source = read[spec.from];
|
|
225
|
+
if (!isRecord(source)) return { ok: false, reason: "field_missing", version, message: `the read carries no \`${spec.from}\` object, which the ${spec.link} link of a v${version} chain is taken from` };
|
|
226
|
+
const link: Record<string, unknown> = {};
|
|
227
|
+
for (const field of fields) {
|
|
228
|
+
const optional = field.endsWith(recipe.optional_marker);
|
|
229
|
+
const name = optional ? field.slice(0, -recipe.optional_marker.length) : field;
|
|
230
|
+
const value = source[name];
|
|
231
|
+
if (optional) {
|
|
232
|
+
if (value !== undefined && value !== null) link[name] = value;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (value === undefined) {
|
|
236
|
+
if (spec.from === "mandate" && name === "sig") {
|
|
237
|
+
return { ok: false, reason: "mandate_sig_required", version, message: `chain v${version} seals the mandate's own signature, and the read carries none: it is a bearer proof only the tenant holds` };
|
|
238
|
+
}
|
|
239
|
+
return { ok: false, reason: "field_missing", version, message: `the read carries no \`${spec.from}.${name}\`, which the ${spec.link} link of a v${version} chain seals` };
|
|
240
|
+
}
|
|
241
|
+
if (spec.from === "quote" && name === "payee" && isMaskedPayee(value)) {
|
|
242
|
+
return { ok: false, reason: "payee_masked", version, message: "the quote's payee is a masked copy; the chain sealed the key itself, so this body cannot be recomputed from it" };
|
|
243
|
+
}
|
|
244
|
+
link[name] = value;
|
|
245
|
+
}
|
|
246
|
+
links.push(link);
|
|
247
|
+
}
|
|
248
|
+
return { ok: true, version, chain: createHash("sha256").update(canonicalize({ v: version, links }), "utf8").digest("hex") };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ── The approval link (chain v4) ────────────────────────────── */
|
|
252
|
+
|
|
253
|
+
export interface SealedApproval {
|
|
254
|
+
items_hash: string;
|
|
255
|
+
batch_hash: string | null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The approval a read says was sealed, or `null` when it carries none (every chain before v4). */
|
|
259
|
+
export function readSealedApproval(read: Record<string, unknown>): SealedApproval | null {
|
|
260
|
+
const approval = read["approval"];
|
|
261
|
+
if (!isRecord(approval) || typeof approval["items_hash"] !== "string") return null;
|
|
262
|
+
const batch = approval["batch_hash"];
|
|
263
|
+
return { items_hash: approval["items_hash"], batch_hash: typeof batch === "string" ? batch : null };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Two spellings of one SHA-256: the API seals the value as sent, and the `sha256:` prefix is optional on the wire. */
|
|
267
|
+
export function sameApprovalHash(a: string | null | undefined, b: string | null | undefined): boolean {
|
|
268
|
+
const bare = (h: string | null | undefined) => (typeof h === "string" ? h.replace(/^sha256:/, "") : null);
|
|
269
|
+
return bare(a) === bare(b);
|
|
270
|
+
}
|