@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.
Files changed (482) hide show
  1. package/README.md +1 -0
  2. package/dist/surface.js +53 -1
  3. package/dist/surface.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/bills-agent/README.md +1 -1
  6. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  7. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  8. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  9. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  10. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  11. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  12. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  13. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  14. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  15. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  16. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  17. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  18. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  19. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  20. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  21. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  22. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  23. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  24. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  25. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  26. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  27. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  28. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  29. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  30. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  31. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  32. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  33. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  34. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  35. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  36. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  37. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  38. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  39. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  40. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  41. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  42. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  43. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  44. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  45. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  46. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  47. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  48. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  49. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  50. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  52. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  53. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  54. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  55. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  56. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  57. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  59. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  60. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  61. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  62. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  63. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  64. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  65. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  66. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  67. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  68. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  69. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  70. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  71. package/templates/checkout-agent/LICENSE +21 -0
  72. package/templates/checkout-agent/README.md +28 -0
  73. package/templates/checkout-agent/_gitignore +10 -0
  74. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  75. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  76. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  77. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  78. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  79. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  80. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  81. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  82. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  83. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  84. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  85. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  86. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  87. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  88. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  108. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  109. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  110. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  111. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  112. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  113. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  114. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  115. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  116. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  117. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  140. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  141. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  142. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  143. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  144. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  145. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  146. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  147. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  148. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  149. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  150. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  151. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  156. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  157. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  158. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  159. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  160. package/templates/checkout-agent/package.json +38 -0
  161. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  162. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  163. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  164. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  165. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  166. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  167. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  168. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  169. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  170. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  171. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  172. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  173. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  174. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  175. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  176. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  177. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  178. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  179. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  180. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  181. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  182. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  183. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  184. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  185. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  186. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  187. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  188. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  189. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  190. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  191. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  192. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  193. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  194. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  195. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  196. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  197. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  198. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  199. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  200. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  201. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  202. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  203. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  204. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  205. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  206. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  207. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  208. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  209. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  210. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  211. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  212. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  213. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  214. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  215. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  216. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  217. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  218. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  219. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  220. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  221. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  222. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  223. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  224. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  225. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  226. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  227. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  228. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  260. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  261. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  262. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  263. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  264. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  265. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  267. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  268. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  269. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  270. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  271. package/templates/checkout-agent/tsconfig.base.json +16 -0
  272. package/templates/checkout-agent/vitest.config.ts +12 -0
  273. package/templates/collections-agent/README.md +6 -1
  274. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  275. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  276. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  277. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  278. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  279. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  280. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  281. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  282. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  283. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  284. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  285. package/templates/collections-agent/package.json +1 -0
  286. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  287. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  288. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  289. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  290. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  291. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  292. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  293. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  294. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  295. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  296. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  297. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  298. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  299. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  300. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  301. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  302. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  303. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  304. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  305. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  306. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  307. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  308. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  309. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  310. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  311. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  312. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  313. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  314. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  315. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  316. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  317. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  318. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  319. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  320. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  321. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  322. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  323. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  324. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  325. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  326. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  328. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  329. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  330. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  331. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  332. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  333. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  335. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  336. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  337. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  338. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  339. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  340. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  341. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  342. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  343. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  344. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  345. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  346. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  347. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  348. package/templates/hello-agent/README.md +1 -1
  349. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  350. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  351. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  352. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  353. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  354. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  355. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  356. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  357. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  358. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  359. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  360. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  361. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  362. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  363. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  364. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  365. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  366. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  367. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  368. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  369. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  370. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  371. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  372. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  373. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  374. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  375. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  376. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  377. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  378. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  379. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  380. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  381. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  382. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  383. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  384. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  385. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  386. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  387. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  388. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  389. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  391. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  392. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  393. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  394. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  395. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  396. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  398. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  399. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  400. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  401. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  402. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  403. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  404. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  405. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  406. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  407. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  408. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  409. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  410. package/templates/kits.lock.json +29 -6
  411. package/templates/supplier-payments-agent/README.md +1 -1
  412. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  413. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  414. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  415. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  416. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  417. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  418. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  419. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  420. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  421. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  422. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  423. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  424. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  425. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  426. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  427. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  428. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  429. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  430. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  431. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  432. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  433. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  434. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  435. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  436. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  437. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  438. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  439. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  440. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  441. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  442. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  443. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  444. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  445. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  446. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  447. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  448. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  449. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  451. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  452. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  453. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  454. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  455. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  456. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  457. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  473. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  474. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  475. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  476. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  477. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  478. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  479. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  480. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  481. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  482. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -16,6 +16,42 @@ export interface ToolContext {
16
16
  engine: ExecutionEngine;
17
17
  /** Fires when a payment tool created an execution; the channel decides what to do with it (ask, or run). */
18
18
  onExecution: (execution: Execution) => Promise<Execution>;
19
+ /**
20
+ * Fires ONCE for a batch, before its first line is drafted, when the channel
21
+ * can take one gesture for the whole list. Absent on a channel that decides
22
+ * line by line (the one-shot, the scenario runner), and then nothing changes:
23
+ * each line reaches `onExecution` and is decided there. The gesture never
24
+ * approves anything itself; every line is still drafted, still reaches
25
+ * `onExecution`, and still mints its own artifact.
26
+ */
27
+ onBatch?: (batch: BatchPresentation) => Promise<BatchGesture | undefined>;
28
+ }
29
+
30
+ /** What a person sees when a whole batch is put in front of them: the list, its total, and the hash of the list. */
31
+ export interface BatchPresentation {
32
+ ref: string;
33
+ label: string;
34
+ /** `batchHash` of the ordered lines. The gesture is a decision about THIS hash and no other. */
35
+ batch_hash: string;
36
+ count: number;
37
+ total_minor: number;
38
+ /** The total as the kit writes money. */
39
+ total: string;
40
+ lines: Array<{
41
+ index: number;
42
+ beneficiary: string;
43
+ amount_minor: number;
44
+ amount: string;
45
+ /** A line a previous run already covers is shown, and does not run again whatever the gesture says. */
46
+ status: "open" | "already_settled" | "in_progress" | "attempt_id_conflict";
47
+ }>;
48
+ }
49
+
50
+ /** The decision a person took on a presented batch: which positions they approved and which they vetoed. */
51
+ export interface BatchGesture {
52
+ batch_hash: string;
53
+ approved: number[];
54
+ vetoed: number[];
19
55
  }
20
56
 
21
57
  export type ToolHandler = (input: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
@@ -28,6 +64,7 @@ export interface AgentLoopOptions {
28
64
  bundle: ProofBundle;
29
65
  engine: ExecutionEngine;
30
66
  onExecution: ToolContext["onExecution"];
67
+ onBatch?: ToolContext["onBatch"] | undefined;
31
68
  /** A hard stop on tool round-trips per turn, so a looping model cannot run forever. */
32
69
  maxSteps?: number;
33
70
  clock?: () => Date;
@@ -91,7 +128,7 @@ export class AgentLoop {
91
128
  }
92
129
 
93
130
  private async dispatch(call: ToolCall, executions: Execution[]): Promise<{ refused: boolean; content: unknown }> {
94
- const { bundle, handlers, engine, onExecution } = this.options;
131
+ const { bundle, handlers, engine, onExecution, onBatch } = this.options;
95
132
  bundle.transcript({ at: this.now(), kind: "tool_call", tool_call_id: call.id, name: call.name, input: call.input });
96
133
 
97
134
  if (!this.allowed.has(call.name)) {
@@ -110,6 +147,7 @@ export class AgentLoop {
110
147
  executions.push(settledOrNot);
111
148
  return settledOrNot;
112
149
  },
150
+ ...(onBatch ? { onBatch } : {}),
113
151
  });
114
152
  return { refused: false, content };
115
153
  } catch (err) {
@@ -19,30 +19,39 @@
19
19
  * There is no receipt object for a receivable; the paid charge as the API
20
20
  * reports it is what the bundle keeps, marked `kind: "charge"` and unsealed.
21
21
  */
22
- import type { ApiClient } from "@codespar/sdk";
22
+ import type { ApiClient, ApiOperation, ApiSuccess } from "@codespar/sdk";
23
23
  import type { PaymentRail, RailLookup, RailOutcome, RailPayment, RailReceipt } from "../rail.js";
24
24
  import type { Actor, ChargeInstrument } from "../types.js";
25
- import { describeApiError, isUncertain } from "./client.js";
25
+ import { describeApiError, isUncertain, type ApiErrorCode } from "./client.js";
26
26
 
27
- /** The `/v1/charges` answer, as the SDK types it. */
28
- export interface ChargeView {
29
- id: string | null;
30
- status: string;
31
- local_status: string;
32
- status_conflict: boolean;
33
- method: string;
34
- currency: string;
35
- amount: number;
36
- amount_minor: number;
37
- due_date: string | null;
38
- payable: boolean;
39
- boleto_bar_code: string | null;
40
- boleto_bank_line: string | null;
41
- pix_copy_paste: string | null;
42
- credit_correlation_armed: boolean;
43
- payment_in_flight: boolean;
44
- settlement: "confirmed" | "pending" | "unconfirmable" | null;
45
- issuance_unconfirmed: boolean;
27
+ /** The `/v1/charges/{chargeId}` answer, as the SDK's generated OpenAPI types it. The create answers the same shape, and the compiler holds them together. */
28
+ export type ChargeView = ApiSuccess<ApiOperation<"/v1/charges/{chargeId}", "get">>;
29
+
30
+ /**
31
+ * Documented as the read's 409. The create does not document it, and the
32
+ * create's error path below still treats it as uncertain, which is the safe
33
+ * reading of a code the route is not known to send (OPEN_QUESTIONS §8).
34
+ */
35
+ const ISSUANCE_UNCONFIRMED: ApiErrorCode<ApiOperation<"/v1/charges/{chargeId}", "get">> = "issuance_unconfirmed";
36
+
37
+ /** The read's other 409: the reference matched more than one charge. Terminal for that reference; the API answers by the charge id. */
38
+ const REFERENCE_AMBIGUOUS: ApiErrorCode<ApiOperation<"/v1/charges/{chargeId}", "get">> = "charge_reference_ambiguous";
39
+
40
+ type ChargeRead =
41
+ | { kind: "view"; view: ChargeView }
42
+ | { kind: "missing" }
43
+ | { kind: "in_flight" }
44
+ | { kind: "failed"; outcome: Extract<RailOutcome, { status: "failed" }> }
45
+ | { kind: "uncertain"; outcome: Extract<RailOutcome, { status: "uncertain" }> };
46
+
47
+ /**
48
+ * When the charge settled, if the answer says so. `settled_at` is a column of
49
+ * the charge row, but the SDK's type for the read does not carry it, so this
50
+ * is the one field here read outside the type (OPEN_QUESTIONS §8).
51
+ */
52
+ function settledAt(view: ChargeView): string | undefined {
53
+ const value: unknown = (view as Record<string, unknown>)["settled_at"];
54
+ return typeof value === "string" && value !== "" ? value : undefined;
46
55
  }
47
56
 
48
57
  export function instrumentOf(view: ChargeView): ChargeInstrument {
@@ -82,7 +91,7 @@ export class CodeSparChargeRail implements PaymentRail {
82
91
  if (!payment.consumer_id) return { status: "failed", code: "consumer_id_required", message: "a receivable settles into the principal's account; the policy names no consumer_id" };
83
92
  let view: ChargeView;
84
93
  try {
85
- view = (await this.api.post("/v1/charges", {
94
+ view = await this.api.post("/v1/charges", {
86
95
  body: {
87
96
  consumer_id: payment.consumer_id,
88
97
  amount: payment.amount_minor / 100,
@@ -93,13 +102,13 @@ export class CodeSparChargeRail implements PaymentRail {
93
102
  due_date: payment.due_date,
94
103
  idempotency_key: payment.attempt_id,
95
104
  },
96
- })) as ChargeView;
105
+ });
97
106
  } catch (err) {
98
107
  const failure = describeApiError(err);
99
- if (isUncertain(failure) || failure.code === "issuance_unconfirmed") return { status: "uncertain", code: failure.code, message: failure.message };
108
+ if (isUncertain(failure) || failure.code === ISSUANCE_UNCONFIRMED) return { status: "uncertain", code: failure.code, message: failure.message };
100
109
  return { status: "failed", code: failure.code, message: failure.message };
101
110
  }
102
- if (view.issuance_unconfirmed || !view.id) return { status: "uncertain", code: "issuance_unconfirmed", message: "the issuer's answer to the create was lost; the key is reserved and a later read resolves it" };
111
+ if (view.issuance_unconfirmed || !view.id) return { status: "uncertain", code: ISSUANCE_UNCONFIRMED, message: "the issuer's answer to the create was lost; the key is reserved and a later read resolves it" };
103
112
  if (view.amount_minor !== payment.amount_minor) {
104
113
  // The API understood another amount than the one approved. The receivable exists; withdraw it rather than leave a debt nobody approved.
105
114
  const withdrawn = await this.withdraw(view.id);
@@ -107,7 +116,7 @@ export class CodeSparChargeRail implements PaymentRail {
107
116
  }
108
117
  const outcome = outcomeOf(view);
109
118
  // `in_flight` is excluded above (`issuance_unconfirmed` / no id); the create answers a state, never "still running".
110
- return outcome.status === "in_flight" ? { status: "uncertain", code: "issuance_unconfirmed", message: "the create answered no readable state" } : outcome;
119
+ return outcome.status === "in_flight" ? { status: "uncertain", code: ISSUANCE_UNCONFIRMED, message: "the create answered no readable state" } : outcome;
111
120
  }
112
121
 
113
122
  /**
@@ -119,37 +128,48 @@ export class CodeSparChargeRail implements PaymentRail {
119
128
  const byId = transactionId ? await this.read(transactionId) : { kind: "missing" as const };
120
129
  if (byId.kind === "view") return outcomeOf(byId.view);
121
130
  if (byId.kind === "in_flight") return { status: "in_flight" };
122
- if (byId.kind === "uncertain") return byId.outcome;
131
+ if (byId.kind === "failed" || byId.kind === "uncertain") return byId.outcome;
123
132
  const byKey = await this.read(attemptId);
124
133
  if (byKey.kind === "view") return outcomeOf(byKey.view);
125
134
  if (byKey.kind === "in_flight") return { status: "in_flight" };
126
- if (byKey.kind === "uncertain") return byKey.outcome;
135
+ if (byKey.kind === "failed" || byKey.kind === "uncertain") return byKey.outcome;
127
136
  return undefined;
128
137
  }
129
138
 
130
- private async read(chargeRef: string): Promise<{ kind: "view"; view: ChargeView } | { kind: "missing" } | { kind: "in_flight" } | { kind: "uncertain"; outcome: Extract<RailOutcome, { status: "uncertain" }> }> {
139
+ /**
140
+ * A 409 is branched on its code, never on the status alone: only `issuance_unconfirmed` means "still issuing".
141
+ * `charge_reference_ambiguous` is terminal for the reference and fails explicitly instead of being polled forever,
142
+ * and a 409 whose code this kit does not read is a failure too.
143
+ */
144
+ private async read(chargeRef: string): Promise<ChargeRead> {
131
145
  try {
132
- const view = (await this.api.get("/v1/charges/{chargeId}", { path: { chargeId: chargeRef } })) as ChargeView;
146
+ const view = await this.api.get("/v1/charges/{chargeId}", { path: { chargeId: chargeRef } });
133
147
  return { kind: "view", view };
134
148
  } catch (err) {
135
149
  const failure = describeApiError(err);
136
150
  if (failure.status === 404) return { kind: "missing" };
137
- if (failure.status === 409) return { kind: "in_flight" };
151
+ if (failure.status === 409) {
152
+ if (failure.code === ISSUANCE_UNCONFIRMED) return { kind: "in_flight" };
153
+ const message =
154
+ failure.code === REFERENCE_AMBIGUOUS
155
+ ? `the reference ${chargeRef} matches more than one charge, and the API answers for the charge id only; which one is this attempt's is not assumed, so reconcile it by the charge id`
156
+ : `GET /v1/charges/${chargeRef} answered 409 with a code this kit does not read (${failure.code}); it is not read as a charge still issuing`;
157
+ return { kind: "failed", outcome: { status: "failed", code: failure.code, message } };
158
+ }
138
159
  return { kind: "uncertain", outcome: { status: "uncertain", code: failure.code, message: failure.message } };
139
160
  }
140
161
  }
141
162
 
142
163
  async receipt(chargeId: string, actor: Actor): Promise<RailReceipt | undefined> {
143
164
  try {
144
- const view = (await this.api.get("/v1/charges/{chargeId}", { path: { chargeId } })) as ChargeView;
145
- const raw = view as ChargeView & { settled_at?: string };
165
+ const view = await this.api.get("/v1/charges/{chargeId}", { path: { chargeId } });
146
166
  return {
147
167
  receipt_id: chargeId,
148
168
  kind: "charge",
149
169
  state: view.local_status === "settled" ? "paid" : view.local_status,
150
170
  mandate: { id: "n/a" },
151
171
  // The read names no buyer, so the payee is not on this record; the execution's item names the debtor.
152
- payment: { amount_minor: view.amount_minor, payee: null, attempt_id: chargeId, money_moved: false, sandbox: true, at: raw.settled_at ?? new Date().toISOString() },
172
+ payment: { amount_minor: view.amount_minor, payee: null, attempt_id: chargeId, money_moved: false, sandbox: true, at: settledAt(view) ?? new Date().toISOString() },
153
173
  chain: null,
154
174
  receipt_sig: null,
155
175
  receipt_sig_ed25519: null,
@@ -3,7 +3,7 @@
3
3
  * the `csk_test_` prefix before the client is built, so no live key ever
4
4
  * reaches the network from this repository.
5
5
  */
6
- import { ApiClient, CodesparApiError, TimeoutError } from "@codespar/sdk";
6
+ import { ApiClient, CodesparApiError, TimeoutError, type ApiOperation, type ApiResponse } from "@codespar/sdk";
7
7
  import { assertTestKey } from "../secrets.js";
8
8
 
9
9
  export const DEFAULT_BASE_URL = "https://api.codespar.dev";
@@ -32,15 +32,38 @@ export interface ApiFailure {
32
32
  body?: unknown;
33
33
  }
34
34
 
35
+ /**
36
+ * The `error.code` values an operation documents on its non-2xx answers, read
37
+ * from the SDK's generated types. A code the kit branches on is declared
38
+ * against this, so a code the API stops documenting fails `tsc`.
39
+ */
40
+ export type ApiErrorCode<Op> = Extract<ApiResponse<Op>, { ok: false }>["data"] extends infer D ? (D extends { error: { code: infer C } } ? C : never) : never;
41
+
42
+ /** The codes both spend routes document; the rail takes either. */
43
+ export type SpendErrorCode = ApiErrorCode<ApiOperation<"/v1/consumers/mandates/{id}/spend", "post">> & ApiErrorCode<ApiOperation<"/v1/consumer-payments/execute", "post">>;
44
+
45
+ const UNCERTAIN_CODES: readonly SpendErrorCode[] = ["psp_dispatch_uncertain", "psp_attempt_uncertain"];
46
+
47
+ /** The thrown error's body is `unknown` to the SDK, whatever the route documents; this reads `{ error: { code, message } }` off it without a cast. */
48
+ function errorEnvelope(body: unknown): { code?: string; message?: string } {
49
+ if (!body || typeof body !== "object" || !("error" in body)) return {};
50
+ const error = body.error;
51
+ if (!error || typeof error !== "object") return {};
52
+ return {
53
+ ...("code" in error && typeof error.code === "string" ? { code: error.code } : {}),
54
+ ...("message" in error && typeof error.message === "string" ? { message: error.message } : {}),
55
+ };
56
+ }
57
+
35
58
  /** Normalise the SDK's two error classes into one readable shape without leaking the body into logs. */
36
59
  export function describeApiError(err: unknown): ApiFailure {
37
60
  if (err instanceof TimeoutError) return { status: 0, code: "timeout", message: `request timed out after ${err.timeoutMs}ms` };
38
61
  if (err instanceof CodesparApiError) {
39
- const body = err.body as { error?: { code?: string; message?: string } } | undefined;
62
+ const envelope = errorEnvelope(err.body);
40
63
  return {
41
64
  status: err.status,
42
- code: err.code ?? body?.error?.code ?? (err.status === 0 ? "network" : `http_${err.status}`),
43
- message: body?.error?.message ?? err.message,
65
+ code: err.code ?? envelope.code ?? (err.status === 0 ? "network" : `http_${err.status}`),
66
+ message: envelope.message ?? err.message,
44
67
  body: err.body,
45
68
  };
46
69
  }
@@ -49,5 +72,5 @@ export function describeApiError(err: unknown): ApiFailure {
49
72
 
50
73
  /** 5xx, timeouts and `psp_dispatch_uncertain` mean "the money may have moved". */
51
74
  export function isUncertain(failure: ApiFailure): boolean {
52
- return failure.status === 0 || failure.status >= 500 || failure.code === "psp_dispatch_uncertain" || failure.code === "psp_attempt_uncertain";
75
+ return failure.status === 0 || failure.status >= 500 || UNCERTAIN_CODES.some((code) => code === failure.code);
53
76
  }
@@ -6,26 +6,36 @@
6
6
  * the same `csk_test_` client every other call of the kit uses.
7
7
  *
8
8
  * Fail-closed: this source never throws and never assumes. A transport
9
- * failure, a timeout, a 404, a 5xx, an unreadable body or a status outside
10
- * `active | paused | revoked | expired` is reported as `unknown`, and the
11
- * engine executes nothing on `unknown`.
9
+ * failure, a timeout, a 404, a 5xx, an unreadable body, a status outside
10
+ * `active | paused | revoked | expired` or an `org_paused` that is not a
11
+ * boolean is reported as `unknown`, and the engine executes nothing on
12
+ * `unknown`.
12
13
  *
13
- * The organization kill switch (`org pauseAll`) has no API surface, so
14
- * `org_paused` is `false` here: the API cannot say it, and this source does
15
- * not invent it.
14
+ * The organization kill switch (ent#1648) answers on the same read:
15
+ * `org_paused` next to `status`, which stays the mandate's own. A missing
16
+ * `org_paused` is `unknown`, never "not paused": a deployment that cannot
17
+ * say whether the organization stopped every spend has not said it did not.
16
18
  */
17
- import type { ApiClient } from "@codespar/sdk";
19
+ import type { ApiOperation, ApiSuccess, ApiClient } from "@codespar/sdk";
18
20
  import { z } from "zod";
19
21
  import type { MandateStatusReport, MandateStatusSource } from "../revocation.js";
20
22
  import { describeApiError } from "./client.js";
21
23
 
22
- /** The two fields of the 14-field projection the gate reads; the rest is passed through untouched. */
24
+ /** `GET /v1/mandates/{id}` as the SDK's generated OpenAPI types it. */
25
+ type MandateRead = ApiSuccess<ApiOperation<"/v1/mandates/{id}", "get">>;
26
+
27
+ /**
28
+ * The three fields the gate reads; the rest is passed through untouched.
29
+ * Bound to the SDK's type, so a field the API renames or retypes breaks the
30
+ * compile here instead of reading as absent at run time.
31
+ */
23
32
  const MandateReadSchema = z
24
33
  .object({
25
34
  status: z.enum(["active", "paused", "revoked", "expired"]),
26
35
  expires_at: z.string(),
36
+ org_paused: z.boolean(),
27
37
  })
28
- .passthrough();
38
+ .passthrough() satisfies z.ZodType<Pick<MandateRead, "status" | "expires_at" | "org_paused">>;
29
39
 
30
40
  export class ApiMandateStatusSource implements MandateStatusSource {
31
41
  constructor(
@@ -35,7 +45,7 @@ export class ApiMandateStatusSource implements MandateStatusSource {
35
45
 
36
46
  async check(mandateId: string): Promise<MandateStatusReport> {
37
47
  const base = { mandate_id: mandateId, org_paused: false, checked_at: this.clock().toISOString(), source: "api" as const };
38
- let body: unknown;
48
+ let body: MandateRead;
39
49
  try {
40
50
  body = await this.api.get("/v1/mandates/{id}", { path: { id: mandateId } });
41
51
  } catch (err) {
@@ -46,11 +56,17 @@ export class ApiMandateStatusSource implements MandateStatusSource {
46
56
  : `GET /v1/mandates/${mandateId} did not answer (${failure.code}): ${failure.message}`;
47
57
  return { ...base, status: "unknown", detail };
48
58
  }
59
+ // The type says what the API promises; the parse checks that this answer kept the promise.
49
60
  const parsed = MandateReadSchema.safeParse(body);
50
61
  if (!parsed.success) {
51
- const status = body && typeof body === "object" && "status" in body ? String((body as { status: unknown }).status) : "missing";
52
- return { ...base, status: "unknown", detail: `GET /v1/mandates/${mandateId} answered a status this kit does not read (${status})` };
62
+ const unread = parsed.error.issues.map((issue) => issue.path.join(".") || "body");
63
+ const status = body && typeof body === "object" && "status" in body ? String(body.status) : "missing";
64
+ const detail = unread.includes("org_paused")
65
+ ? `GET /v1/mandates/${mandateId} did not say whether the organization is paused (org_paused unreadable)`
66
+ : `GET /v1/mandates/${mandateId} answered a status this kit does not read (${status})`;
67
+ return { ...base, status: "unknown", detail };
53
68
  }
69
+ if (parsed.data.org_paused) return { ...base, org_paused: true, status: parsed.data.status };
54
70
  const expiresAt = new Date(parsed.data.expires_at).getTime();
55
71
  const expired = Number.isFinite(expiresAt) && expiresAt <= this.clock().getTime();
56
72
  return { ...base, status: expired && parsed.data.status === "active" ? "expired" : parsed.data.status };
@@ -2,8 +2,10 @@
2
2
  * The CodeSpar sandbox rail. With the signed envelope the consent returned
3
3
  * (`mandate.canonical` + `mandate.signature`) it spends through
4
4
  * `POST /v1/consumer-payments/execute`; without it, by mandate id through
5
- * `POST /v1/consumers/mandates/{id}/spend`. Both are idempotent on
6
- * `attempt_id`. The by-id route answered `bad_signature` on staging for a
5
+ * `POST /v1/consumers/mandates/{id}/spend`. Idempotence on both is OPT-IN
6
+ * and keyed on an explicit `attempt_id`, which every payment here carries: a
7
+ * spend without one is a fresh payment on every request (ent#1671, #1683).
8
+ * What a repeat of an attempt answers is in `lookup` below. The by-id route answered `bad_signature` on staging for a
7
9
  * mandate carrying `periodic_cap` (OPEN_QUESTIONS §14), which is why the
8
10
  * envelope is preferred when it exists. Receipts come back from
9
11
  * `GET /v1/consumers/receipts/{id}`.
@@ -14,19 +16,19 @@
14
16
  */
15
17
  import type { ApiClient } from "@codespar/sdk";
16
18
  import type { Mandate } from "../mandate.js";
17
- import type { PaymentRail, RailLookup, RailOutcome, RailPayment, RailReceipt } from "../rail.js";
19
+ import { checkQuote } from "../quote.js";
20
+ import { checkSpendApproval, type PaymentRail, type RailLookup, type RailOutcome, type RailPayment, type RailReceipt } from "../rail.js";
18
21
  import type { Actor } from "../types.js";
19
- import { describeApiError, isUncertain } from "./client.js";
22
+ import { describeApiError, isUncertain, type SpendErrorCode } from "./client.js";
20
23
 
21
- /** The asymmetric seal as the API serves it, or nulls when this deployment
22
- * does not serve one. Never throws on a shape it does not recognise: a
23
- * receipt is evidence of a payment that already happened, and refusing to
24
- * record it over an unexpected field would trade the record for the proof. */
25
- function readEd25519Seal(body: unknown): { receipt_sig_ed25519: string | null; receipt_sig_kid: string | null } {
26
- const r = body as { receipt_sig_ed25519?: unknown; receipt_sig_kid?: unknown };
27
- const str = (v: unknown): string | null => (typeof v === "string" && v.length > 0 ? v : null);
28
- return { receipt_sig_ed25519: str(r.receipt_sig_ed25519), receipt_sig_kid: str(r.receipt_sig_kid) };
29
- }
24
+ /** Another presentation of this attempt is claimed and has no recorded outcome yet: it may be moving money right now. */
25
+ const ATTEMPT_IN_FLIGHT: SpendErrorCode = "psp_attempt_in_flight";
26
+ /** This attempt failed, moved no money and was compensated; the API will never run this id again. */
27
+ const ATTEMPT_SPENT: SpendErrorCode = "psp_attempt_conflict";
28
+ /** This attempt id was already used for a DIFFERENT payment (checked before its state); nothing was held or sent. */
29
+ const ATTEMPT_ID_CONFLICT: SpendErrorCode = "attempt_id_conflict";
30
+ /** Another project of this organization holds this attempt id; opaque by design, nothing was held or sent. */
31
+ const ATTEMPT_ID_UNAVAILABLE: SpendErrorCode = "attempt_id_unavailable";
30
32
 
31
33
  export class CodeSparRail implements PaymentRail {
32
34
  readonly name = "codespar" as const;
@@ -37,6 +39,12 @@ export class CodeSparRail implements PaymentRail {
37
39
  ) {}
38
40
 
39
41
  async pay(payment: RailPayment): Promise<RailOutcome> {
42
+ // Without the quote the sealed receipt names no payee (OPEN_QUESTIONS §18), and the API pays a quote that disagrees. Neither goes out.
43
+ const quoted = checkQuote(payment);
44
+ if (!quoted.ok) return { status: "failed", code: quoted.code, message: `${quoted.detail}; nothing was sent` };
45
+ // Without it the receipt seals no link to what was approved (OPEN_QUESTIONS §3), and the API refuses a malformed one with 400 anyway.
46
+ const approval = checkSpendApproval(payment);
47
+ if (!approval.ok) return { status: "failed", code: approval.code, message: `${approval.detail}; nothing was sent` };
40
48
  try {
41
49
  const outcome =
42
50
  this.envelope?.canonical && this.envelope.signature
@@ -49,11 +57,13 @@ export class CodeSparRail implements PaymentRail {
49
57
  agent_id: payment.agent_id,
50
58
  payee: payment.payee,
51
59
  attempt_id: payment.attempt_id,
60
+ quote: quoted.quote,
61
+ approval: approval.approval,
52
62
  },
53
63
  })
54
64
  : await this.api.post("/v1/consumers/mandates/{id}/spend", {
55
65
  path: { id: payment.mandate_id },
56
- body: { amount_minor: payment.amount_minor, payee: payment.payee, agent_id: payment.agent_id, attempt_id: payment.attempt_id },
66
+ body: { amount_minor: payment.amount_minor, payee: payment.payee, agent_id: payment.agent_id, attempt_id: payment.attempt_id, quote: quoted.quote, approval: approval.approval },
57
67
  });
58
68
  return {
59
69
  status: "settled",
@@ -61,24 +71,50 @@ export class CodeSparRail implements PaymentRail {
61
71
  receipt_id: outcome.receipt?.id ?? null,
62
72
  money_moved: outcome.payment.moneyMoved,
63
73
  sandbox: !outcome.payment.moneyMoved,
74
+ ...(outcome.idempotent_replay ? { replayed: true as const } : {}),
64
75
  raw: outcome,
65
76
  };
66
77
  } catch (err) {
67
78
  const failure = describeApiError(err);
79
+ // Not a refusal: the other presentation may already have reached the provider, so "nothing moved" would be a claim nobody can make.
80
+ if (failure.code === ATTEMPT_IN_FLIGHT) return { status: "uncertain", code: failure.code, message: failure.message };
68
81
  if (isUncertain(failure)) return { status: "uncertain", code: failure.code, message: failure.message };
82
+ if (failure.code === ATTEMPT_SPENT) return { status: "failed", code: failure.code, message: failure.message, spent: true };
83
+ // Held, never spent: the id belongs to another payment or another project, so no generation may be derived past it.
84
+ if (failure.code === ATTEMPT_ID_CONFLICT) return { status: "failed", code: failure.code, message: failure.message, held: "conflict" };
85
+ if (failure.code === ATTEMPT_ID_UNAVAILABLE) return { status: "failed", code: failure.code, message: failure.message, held: "unavailable" };
69
86
  return { status: "failed", code: failure.code, message: failure.message };
70
87
  }
71
88
  }
72
89
 
73
90
  /**
74
- * There is no read route for an attempt. The documented way is to present
75
- * the same `attempt_id` again: the lifecycle is idempotent on it and
76
- * answers the state it reached. `psp_attempt_in_flight` means the first
77
- * presentation is still running.
91
+ * There is no read route for an attempt: the way to learn what became of
92
+ * one is to present the same `attempt_id`, with the same payment, again.
93
+ * Against the deployed API (ent#1671, #1683) that call never moves money
94
+ * for an attempt it already holds, and each answer maps to one reading:
95
+ *
96
+ * - settled: `200` with the ORIGINAL body verbatim (same `transactionId`,
97
+ * same stored `receipt.id`, which the receipt route resolves) plus
98
+ * `idempotent_replay: true`. Read as `settled`, exactly as the first
99
+ * answer, and marked `replayed`.
100
+ * - `psp_attempt_in_flight` (409): claimed, no outcome yet, on every rail.
101
+ * Read as `in_flight`; the next reconcile asks again with the SAME id.
102
+ * - `psp_attempt_uncertain` (409): pinned, dispatch outcome unknown. Read as
103
+ * `uncertain`, which leaves the execution open for a person.
104
+ * - `psp_attempt_conflict` (409): failed, compensated, no money. Read as
105
+ * `failed` and `spent`.
106
+ * - `attempt_id_conflict` / `attempt_id_unavailable` (409): the id is held for
107
+ * a different payment, or by another project. Read as `failed` and `held`,
108
+ * never `spent`: nothing was held or sent by this call. Neither can come from re-presenting the
109
+ * payment this execution sent, whose tuple is the one the id was claimed
110
+ * with, so seeing one here is a defect worth its readable failure.
111
+ * - an attempt the API never took: there is no record to answer from, so
112
+ * this call IS the payment. Reconcile runs only after the outbox row
113
+ * flipped to `sent`, so an attempt it looks up was presented once already.
78
114
  */
79
115
  async lookup(_attemptId: string, payment: RailPayment): Promise<RailLookup> {
80
116
  const outcome = await this.pay(payment);
81
- if (outcome.status === "failed" && outcome.code === "psp_attempt_in_flight") return { status: "in_flight" };
117
+ if (outcome.status === "uncertain" && outcome.code === ATTEMPT_IN_FLIGHT) return { status: "in_flight" };
82
118
  return outcome;
83
119
  }
84
120
 
@@ -88,7 +124,9 @@ export class CodeSparRail implements PaymentRail {
88
124
  return {
89
125
  receipt_id: r.receipt_id,
90
126
  state: r.state,
91
- mandate: { id: r.mandate.id },
127
+ mandate: { id: r.mandate.id, sig_sha256: r.mandate.sig_sha256 },
128
+ chain_version: r.chain_version,
129
+ approval: r.approval ?? null,
92
130
  payment: {
93
131
  amount_minor: r.payment.amount_minor,
94
132
  payee: r.quote?.payee ?? null,
@@ -99,12 +137,10 @@ export class CodeSparRail implements PaymentRail {
99
137
  },
100
138
  chain: r.chain,
101
139
  receipt_sig: r.receipt_sig,
102
- // ent#1633 landed after `@codespar/sdk@0.16.6` was generated, so the
103
- // typed response does not carry these two yet. Read off the body
104
- // defensively rather than pinning a new SDK for two nullable strings:
105
- // a deployment that predates the change answers null, which is the
106
- // same answer the columns hold and reads as `unsigned`.
107
- ...readEd25519Seal(r),
140
+ // `||`, not `??`: a deployment older than ent#1633 omits both fields,
141
+ // and an empty string is no signature either. Both read as `unsigned`.
142
+ receipt_sig_ed25519: r.receipt_sig_ed25519 || null,
143
+ receipt_sig_kid: r.receipt_sig_kid || null,
108
144
  actor,
109
145
  raw: r,
110
146
  };
@@ -12,27 +12,12 @@
12
12
  * `fetch`, see docs/OPEN_QUESTIONS.md section 31c. The key is checked for
13
13
  * the `csk_test_` prefix before a client exists, like every other call.
14
14
  */
15
- import { ApiClient } from "@codespar/sdk";
15
+ import { ApiClient, type ApiOperation, type ApiSuccess } from "@codespar/sdk";
16
16
  import type { ApiFailure } from "./client.js";
17
17
  import { createCodeSparClient, describeApiError } from "./client.js";
18
18
 
19
- export interface SandboxPaidState {
20
- charge_id: string;
21
- status: "paid";
22
- local_status: string;
23
- currency: string;
24
- quoted_minor: number;
25
- paid_minor: number;
26
- payment: "full" | "partial" | "over";
27
- paid_via: string;
28
- wallet_id: string;
29
- ledger_entry_id: string | null;
30
- event: { id: string; type: string } | null;
31
- simulated: boolean;
32
- settled_against: string | null;
33
- money_moved: false;
34
- idempotent_replay: boolean;
35
- }
19
+ /** What the payer route answers, as the SDK's generated OpenAPI types it. */
20
+ export type SandboxPaidState = ApiSuccess<ApiOperation<"/v1/test/charges/{chargeId}/pay", "post">>;
36
21
 
37
22
  export type SandboxPayResult = { ok: true; state: SandboxPaidState } | { ok: false; failure: ApiFailure };
38
23
 
@@ -13,7 +13,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
13
13
  import { dirname, join } from "node:path";
14
14
  import { canonicalJson, hexEqual, hmacSha256Hex, itemsHash } from "./hash.js";
15
15
  import type { Execution } from "./state-machine.js";
16
- import type { Actor, ApprovalArtifact, EscalationTrigger, ExecutionBatch } from "./types.js";
16
+ import type { Actor, ApprovalArtifact, EscalationTrigger, ExecutionBatch, ExecutionComposition } from "./types.js";
17
17
  import { newId } from "./ids.js";
18
18
 
19
19
  export const APPROVAL_KEY_ID = "local-dev-stub";
@@ -78,6 +78,8 @@ export function createApprovalArtifact(signer: ApprovalSigner, input: CreateAppr
78
78
  // null — on an execution that is not part of a batch, so the payload a
79
79
  // bills-agent artifact signs is unchanged by this field existing.
80
80
  ...(execution.batch ? { batch: execution.batch } : {}),
81
+ // What the single amount was composed of, when it was composed of lines. Same rule as `batch`: omitted, never null.
82
+ ...(execution.composition ? { composition: execution.composition } : {}),
81
83
  ...(input.escalation ? { escalation: input.escalation } : {}),
82
84
  actor: input.actor,
83
85
  };
@@ -89,7 +91,7 @@ export function createApprovalArtifact(signer: ApprovalSigner, input: CreateAppr
89
91
 
90
92
  export type ApprovalCheck =
91
93
  | { ok: true }
92
- | { ok: false; problem: "signature_invalid" | "items_hash_mismatch" | "expired" | "wrong_execution" | "wrong_mandate" | "batch_mismatch" };
94
+ | { ok: false; problem: "signature_invalid" | "items_hash_mismatch" | "expired" | "wrong_execution" | "wrong_mandate" | "batch_mismatch" | "composition_mismatch" };
93
95
 
94
96
  /** Two batch bindings are the same binding, or one is absent and so is the other. */
95
97
  function sameBatch(a: ExecutionBatch | undefined, b: ExecutionBatch | undefined): boolean {
@@ -97,6 +99,12 @@ function sameBatch(a: ExecutionBatch | undefined, b: ExecutionBatch | undefined)
97
99
  return a.ref === b.ref && a.batch_hash === b.batch_hash && a.index === b.index && a.count === b.count;
98
100
  }
99
101
 
102
+ /** Two compositions are the same binding, or one is absent and so is the other. */
103
+ function sameComposition(a: ExecutionComposition | undefined, b: ExecutionComposition | undefined): boolean {
104
+ if (a === undefined || b === undefined) return a === b;
105
+ return a.ref === b.ref && a.composition_hash === b.composition_hash && a.line_count === b.line_count;
106
+ }
107
+
100
108
  /**
101
109
  * The rule that makes the artifact proof: before `executing`, recompute the
102
110
  * hash of what is about to be executed and compare it with the artifact.
@@ -118,6 +126,10 @@ export function checkApprovalArtifact(
118
126
  // line was one OF. An execution that gained, lost or moved its place in a
119
127
  // batch after approval is not the one that was approved.
120
128
  if (!sameBatch(artifact.batch, execution.batch)) return { ok: false, problem: "batch_mismatch" };
129
+ // The item's amount is attested above; this is what that amount was made of.
130
+ // A cart recomposed at a constant total leaves `items_hash` unchanged and
131
+ // this hash changed, and that is the only thing that sees it.
132
+ if (!sameComposition(artifact.composition, execution.composition)) return { ok: false, problem: "composition_mismatch" };
121
133
  if (new Date(artifact.expires_at).getTime() <= now.getTime()) return { ok: false, problem: "expired" };
122
134
  return { ok: true };
123
135
  }