@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.
Files changed (480) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -1
  3. package/templates/bills-agent/README.md +1 -1
  4. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  5. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  6. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  7. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  8. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  9. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  10. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  11. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  12. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  13. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  14. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  15. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  16. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  17. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  18. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  19. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  20. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  21. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  22. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  23. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  24. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  25. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  26. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  27. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  28. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  29. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  30. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  31. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  32. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  33. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  34. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  35. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  36. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  37. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  38. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  39. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  40. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  41. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  42. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  43. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  44. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  45. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  46. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  47. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  48. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  49. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  50. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  52. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  53. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  54. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  55. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  56. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  57. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  59. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  60. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  61. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  62. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  63. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  64. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  65. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  66. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  67. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  68. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  69. package/templates/checkout-agent/LICENSE +21 -0
  70. package/templates/checkout-agent/README.md +28 -0
  71. package/templates/checkout-agent/_gitignore +10 -0
  72. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  73. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  74. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  75. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  76. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  77. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  78. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  79. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  80. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  81. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  82. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  83. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  84. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  85. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  86. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  87. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  88. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  108. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  109. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  110. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  111. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  112. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  113. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  114. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  115. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  116. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  117. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  140. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  141. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  142. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  143. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  144. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  145. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  146. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  147. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  148. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  149. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  150. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  151. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  156. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  157. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  158. package/templates/checkout-agent/package.json +38 -0
  159. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  160. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  161. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  162. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  163. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  164. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  165. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  166. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  167. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  168. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  169. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  170. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  171. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  172. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  173. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  174. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  175. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  176. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  177. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  178. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  179. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  180. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  181. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  182. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  183. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  184. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  185. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  186. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  187. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  188. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  189. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  190. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  191. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  192. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  193. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  194. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  195. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  196. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  197. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  198. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  199. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  200. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  201. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  202. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  203. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  204. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  205. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  206. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  207. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  208. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  209. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  210. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  211. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  212. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  213. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  214. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  215. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  216. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  217. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  218. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  219. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  220. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  221. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  222. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  223. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  224. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  225. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  226. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  227. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  228. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  260. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  261. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  262. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  263. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  264. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  265. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  267. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  268. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  269. package/templates/checkout-agent/tsconfig.base.json +16 -0
  270. package/templates/checkout-agent/vitest.config.ts +12 -0
  271. package/templates/collections-agent/README.md +6 -1
  272. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  273. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  274. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  275. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  276. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  277. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  278. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  279. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  280. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  281. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  282. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  283. package/templates/collections-agent/package.json +1 -0
  284. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  285. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  286. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  287. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  288. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  289. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  290. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  291. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  292. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  293. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  294. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  295. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  296. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  297. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  298. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  299. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  300. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  301. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  302. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  303. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  304. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  305. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  306. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  307. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  308. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  309. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  310. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  311. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  312. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  313. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  314. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  315. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  316. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  317. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  318. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  319. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  320. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  321. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  322. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  323. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  324. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  325. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  326. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  328. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  329. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  330. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  331. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  332. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  333. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  335. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  336. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  337. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  338. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  339. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  340. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  341. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  342. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  343. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  344. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  345. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  346. package/templates/hello-agent/README.md +1 -1
  347. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  348. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  349. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  350. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  351. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  352. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  353. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  354. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  355. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  356. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  357. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  358. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  359. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  360. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  361. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  362. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  363. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  364. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  365. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  366. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  367. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  368. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  369. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  370. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  371. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  372. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  373. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  374. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  375. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  376. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  377. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  378. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  379. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  380. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  381. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  382. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  383. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  384. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  385. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  386. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  387. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  388. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  389. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  391. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  392. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  393. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  394. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  395. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  396. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  398. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  399. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  400. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  401. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  402. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  403. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  404. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  405. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  406. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  407. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  408. package/templates/kits.lock.json +29 -6
  409. package/templates/supplier-payments-agent/README.md +1 -1
  410. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  411. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  412. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  413. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  414. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  415. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  416. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  417. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  418. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  419. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  420. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  421. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  422. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  423. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  424. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  425. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  426. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  427. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  428. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  429. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  430. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  431. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  432. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  433. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  434. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  435. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  436. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  437. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  438. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  439. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  440. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  441. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  442. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  443. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  444. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  445. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  446. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  447. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  448. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  449. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  451. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  452. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  453. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  454. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  455. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  456. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  457. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  473. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  474. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  475. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  476. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  477. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  478. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  479. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  480. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -17,28 +17,34 @@
17
17
  * reconciling a payout, or a court — parties that
18
18
  * have no relationship with CodeSpar at all.
19
19
  *
20
- * This module checks the second. It imports `node:crypto` and nothing else: no
20
+ * This module checks the second. It imports `node:crypto` and two modules of
21
+ * this package that import nothing else (`receipt-chain`, `hash`): no
21
22
  * CodeSpar SDK, no key material, no token, no network of its own (the key
22
23
  * document is passed in, or fetched by a function the caller supplies). A
23
24
  * third party can read the forty lines that matter and reimplement them.
24
25
  *
25
- * WHAT A `verified` VERDICT PROVES, exactly: CodeSpar sealed a receipt with
26
- * THIS id and THIS chain. The chain is a SHA-256 over the four links of the
27
- * Control Record, so the seller, the amount and the payee are inside it — but
28
- * recomputing that digest from the receipt body needs the canonical link
29
- * shapes, which CodeSpar does not publish today. Binding the body to the chain
30
- * is therefore not something this module can promise, and it does not pretend
31
- * to: see `docs/OPEN_QUESTIONS.md` §47. It is also why the signature survives
32
- * the proof bundle's masking — the bundle masks the payee, and the signature
33
- * covers the id and the digest, not the copy in front of you.
26
+ * TWO LAYERS, and a verdict says which one it reached. The signature proves
27
+ * CodeSpar sealed a receipt with THIS id and THIS chain; `verifyReceiptWithKeys`
28
+ * checks that and nothing else, which is why it survives the proof bundle's
29
+ * masking — the bundle masks the payee, and the signature covers the id and
30
+ * the digest, not the copy in front of you. The chain is a SHA-256 over the
31
+ * links of the Control Record, and since ent#1670 CodeSpar publishes how to
32
+ * recompute it (`chain_recipe`, beside the keys). `verifyReceiptRead` does:
33
+ * from the API's receipt READ, under the recipe of the SAME document whose
34
+ * key verified the signature, it recomputes the chain and holds it against
35
+ * the signed one — and for a v4 chain, holds the sealed approval link against
36
+ * the local approval artifact. `verified` from it means all of that held;
37
+ * `signature_only` means the signature held and the body could not be bound
38
+ * (a v1–v3 chain, a masked payee, no recipe), which is not a failure and is
39
+ * not a verification of the body either. See `docs/OPEN_QUESTIONS.md` §47.
34
40
  *
35
- * WHICH KEY SET. `kid` is `<did>#<n>` and the DID is the platform's, so it is
36
- * the same string on every deployment while the KEY behind it is not: a
37
- * sandbox receipt checked against the production key set finds a key of that
38
- * name, fails to verify, and reads `tampered`. The verifier therefore points
39
- * at ONE deployment's `/.well-known/codespar-receipt-keys.json` — the default
40
- * is production, and `--url` names another. Nothing in the two documents tells
41
- * them apart today; `docs/OPEN_QUESTIONS.md` §47 says so and names the ask.
41
+ * WHICH KEY SET. The verifier points at ONE deployment's
42
+ * `/.well-known/codespar-receipt-keys.json` — the default is production, and
43
+ * `--url` names another. Since ent#1641 a deployment names its keys by
44
+ * namespace (`#production-2`, `#staging-2`), so a receipt checked against the
45
+ * wrong set finds no key of its name (`unknown_key`); the retired `#1` is
46
+ * still published by both with different keys, and a receipt sealed under it
47
+ * checked against the wrong set reads `tampered`. `docs/OPEN_QUESTIONS.md` §47.
42
48
  *
43
49
  * The signed string is the enterprise's `receiptSigningString`
44
50
  * (`packages/api/src/receipt-signature.ts`, ent#1633), reproduced here rather
@@ -46,6 +52,9 @@
46
52
  * proves nothing about the signer.
47
53
  */
48
54
  import { createPublicKey, verify as verifyDetached } from "node:crypto";
55
+ import { itemsHash } from "./hash.js";
56
+ import { readChainRecipe, readSealedApproval, recomputeChain, sameApprovalHash, type SealedApproval } from "./receipt-chain.js";
57
+ import type { ExecutionItem } from "./types.js";
49
58
 
50
59
  /** The domain tag. A receipt names the version it was sealed under through
51
60
  * this literal, so a future `v2` does not invalidate a `v1` signature. */
@@ -86,8 +95,51 @@ export function receiptSigningString(receiptId: string, chain: string): string {
86
95
  * arbitrary JSON file would state that it is a receipt from
87
96
  * before the capability existed, which is a claim about a
88
97
  * file nobody checked.
98
+ *
99
+ * And three that only the body check (`verifyReceiptRead`) reaches, each
100
+ * after the signature verified:
101
+ *
102
+ * `signature_only` the body could not be bound to the chain: a v1–v3
103
+ * chain, a masked payee, no read, no recipe. NOT a
104
+ * failure, and NOT `verified`: the signature is
105
+ * CodeSpar's, and nothing here says the body beside it
106
+ * is the body it sealed.
107
+ * `chain_mismatch` the body, recomputed under the published recipe, is
108
+ * NOT the signed chain. The signature is genuine and
109
+ * the body in front of you is not the one it covers.
110
+ * `approval_mismatch` the body is the signed one, and the approval link it
111
+ * seals is NOT the local artifact's: this payment was
112
+ * not sealed against the list that artifact approved.
89
113
  */
90
- export type ReceiptVerdict = "verified" | "tampered" | "unsigned" | "unknown_key" | "unreachable" | "malformed";
114
+ export type ReceiptVerdict = "verified" | "tampered" | "unsigned" | "unknown_key" | "unreachable" | "malformed" | "signature_only" | "chain_mismatch" | "approval_mismatch";
115
+
116
+ /** What recomputing the chain from the body found. */
117
+ export interface ChainCheck {
118
+ /** `recomputed`: equal to the signed chain. `mismatch`: not equal. `not_recomputed`: see `reason`. */
119
+ status: "recomputed" | "mismatch" | "not_recomputed";
120
+ version: number | null;
121
+ /** The digest recomputed from the body, when it could be. */
122
+ recomputed: string | null;
123
+ reason: string;
124
+ }
125
+
126
+ /** What the sealed approval link was held against. Present on a v4 chain that recomputed. */
127
+ export interface ApprovalLinkCheck {
128
+ /** `matched`: the artifact's hashes are the sealed ones. `mismatch`: they are not. `not_compared`: no artifact was given. */
129
+ status: "matched" | "mismatch" | "not_compared";
130
+ sealed: SealedApproval;
131
+ artifact: { approval_id: string; items_hash: string; batch_hash: string | null } | null;
132
+ reason: string;
133
+ }
134
+
135
+ /** The part of an approval artifact the approval link is held against: what was approved, never who. */
136
+ export interface ApprovalClaim {
137
+ approval_id: string;
138
+ items_hash: string;
139
+ /** When present, `items_hash` is recomputed from them first: a hash that does not describe its own list proves nothing about the list. */
140
+ items?: readonly ExecutionItem[];
141
+ batch?: { batch_hash: string };
142
+ }
91
143
 
92
144
  export interface ReceiptVerification {
93
145
  verdict: ReceiptVerdict;
@@ -106,6 +158,9 @@ export interface ReceiptVerification {
106
158
  reason: string;
107
159
  /** One sentence for a person. Never a stack trace. */
108
160
  message: string;
161
+ /** The body check, when one was attempted: only `verifyReceiptRead` attempts it, and only on a signature that verified. */
162
+ chain_check?: ChainCheck;
163
+ approval_check?: ApprovalLinkCheck;
109
164
  }
110
165
 
111
166
  /** One published key, RFC 8037 shape, as the JWKS serves it. */
@@ -398,4 +453,125 @@ export const VERDICT_EXIT_CODES: Record<ReceiptVerdict, number> = {
398
453
  unknown_key: 4,
399
454
  unreachable: 5,
400
455
  malformed: 6,
456
+ signature_only: 7,
457
+ chain_mismatch: 8,
458
+ approval_mismatch: 9,
401
459
  };
460
+
461
+ /* ── The body: the chain recomputed from the read ────────────── */
462
+
463
+ /** The first chain version that seals `sha256(mandate.sig)` instead of the signature, and the approval link. */
464
+ export const FIRST_THIRD_PARTY_CHAIN_VERSION = 4;
465
+
466
+ /** Whether a JSON is the API's receipt read, rather than the proof bundle's masked copy of one: only the read carries the links. */
467
+ export function isReceiptRead(input: unknown): input is Record<string, unknown> {
468
+ if (!isRecord(input)) return false;
469
+ const mandate = input["mandate"];
470
+ const payment = input["payment"];
471
+ return isRecord(mandate) && "nonce" in mandate && isRecord(payment) && "rail" in payment;
472
+ }
473
+
474
+ /** A verified signature whose body could not be bound: `signature_only`, with the reason. */
475
+ export function signatureOnly(signature: ReceiptVerification, reason: string, why: string, version: number | null = null): ReceiptVerification {
476
+ return {
477
+ ...signature,
478
+ verdict: "signature_only",
479
+ reason,
480
+ message: `receipt ${signature.receipt_id ?? "(no id)"} carries CodeSpar's signature over its id and chain, and its body is NOT bound to that chain: ${why}`,
481
+ chain_check: { status: "not_recomputed", version, recomputed: null, reason },
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Verify a receipt READ — the JSON of `GET /v1/consumers/receipts/{id}` —
487
+ * against a key set already in hand: the signature, then the body, then the
488
+ * approval. Synchronous and offline, like `verifyReceiptWithKeys`.
489
+ *
490
+ * The recipe is taken from the SAME document whose key verified the
491
+ * signature, never from another: each deployment names its keys by
492
+ * namespace (§47), so a document that verified the signature is the sealing
493
+ * deployment's, and its recipe is the one that sealed.
494
+ *
495
+ * A v1–v3 chain is never recomputed here, even from a tenant's read that
496
+ * carries `mandate.sig`: that signature is a bearer proof (it authorizes
497
+ * spends), a verifier has no business handling it, and the API is retiring
498
+ * it from the read. Those chains also predate the API's timestamp fix
499
+ * (ent#1670), so some of them no longer recompute from their read at all —
500
+ * and a mismatch there would accuse a genuine receipt. They answer
501
+ * `signature_only`, never `verified` and never `chain_mismatch`.
502
+ */
503
+ export function verifyReceiptRead(read: unknown, keyDocument: unknown, options: { label?: string; approval?: ApprovalClaim } = {}): ReceiptVerification {
504
+ const signature = verifyReceiptWithKeys(read, keyDocument, options.label ?? "file");
505
+ if (signature.verdict !== "verified" || !isRecord(read)) return signature;
506
+ if (!isReceiptRead(read)) return signatureOnly(signature, "read_required", "this is not the API's receipt read (the proof bundle's copy masks the payee and carries none of the links); recompute from `GET /v1/consumers/receipts/{id}`");
507
+
508
+ const recipe = readChainRecipe(keyDocument);
509
+ if (!recipe.ok) return signatureOnly(signature, recipe.reason, recipe.message);
510
+
511
+ const versionRaw = read[recipe.recipe.version.field];
512
+ if (typeof versionRaw === "number" && versionRaw < FIRST_THIRD_PARTY_CHAIN_VERSION) {
513
+ return signatureOnly(
514
+ signature,
515
+ "mandate_sig_required",
516
+ `the chain is not recomputable without the mandate signature. Chain v${versionRaw} seals the mandate's raw signature, a bearer proof this verifier does not take; and a receipt sealed before ent#1670 may not recompute from its read at all. It carries no approval link either: that starts at v${FIRST_THIRD_PARTY_CHAIN_VERSION}`,
517
+ versionRaw,
518
+ );
519
+ }
520
+
521
+ const recomputed = recomputeChain(read, recipe.recipe);
522
+ if (!recomputed.ok) return signatureOnly(signature, recomputed.reason, recomputed.message, recomputed.version);
523
+ const sealedChain = read["chain"];
524
+ if (recomputed.chain !== sealedChain) {
525
+ return {
526
+ ...signature,
527
+ verdict: "chain_mismatch",
528
+ reason: "body_does_not_recompute",
529
+ message: `receipt ${signature.receipt_id} carries a genuine CodeSpar signature over chain ${sealedChain}, and the body beside it recomputes to ${recomputed.chain} under the published recipe (v${recomputed.version}): this body is not the one that was sealed. A payee, an amount, a timestamp or the approval was changed after sealing`,
530
+ chain_check: { status: "mismatch", version: recomputed.version, recomputed: recomputed.chain, reason: "body_does_not_recompute" },
531
+ };
532
+ }
533
+ const chainCheck: ChainCheck = { status: "recomputed", version: recomputed.version, recomputed: recomputed.chain, reason: "body_recomputes" };
534
+ const bound = `the body recomputes to the signed chain under the published recipe (v${recomputed.version})`;
535
+
536
+ const sealed = readSealedApproval(read);
537
+ if (!sealed) {
538
+ // Unreachable through the published recipe (v4 seals the link, and a read without it fails the recomputation above), kept so a future version is not misread.
539
+ return { ...signature, reason: "body_recomputes", message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}; it seals no approval link`, chain_check: chainCheck };
540
+ }
541
+ const approval = options.approval;
542
+ if (!approval) {
543
+ return {
544
+ ...signature,
545
+ reason: "body_recomputes",
546
+ message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}. It seals the approval ${sealed.items_hash}${sealed.batch_hash ? ` (batch ${sealed.batch_hash})` : ""}; no approval artifact was given to hold it against`,
547
+ chain_check: chainCheck,
548
+ approval_check: { status: "not_compared", sealed, artifact: null, reason: "no_artifact" },
549
+ };
550
+ }
551
+
552
+ const artifact = { approval_id: approval.approval_id, items_hash: approval.items_hash, batch_hash: approval.batch?.batch_hash ?? null };
553
+ const mismatch = (reason: string, why: string): ReceiptVerification => ({
554
+ ...signature,
555
+ verdict: "approval_mismatch",
556
+ reason,
557
+ message: `receipt ${signature.receipt_id} was sealed by CodeSpar and ${bound}, and ${why}: this payment was not sealed against the list artifact ${approval.approval_id} approved`,
558
+ chain_check: chainCheck,
559
+ approval_check: { status: "mismatch", sealed, artifact, reason },
560
+ });
561
+ if (approval.items && itemsHash(approval.items) !== approval.items_hash) {
562
+ return mismatch("artifact_items_hash_inconsistent", `the artifact's items_hash ${approval.items_hash} is not the hash of the items it lists (${itemsHash(approval.items)})`);
563
+ }
564
+ if (!sameApprovalHash(sealed.items_hash, artifact.items_hash)) {
565
+ return mismatch("items_hash_differs", `it seals items_hash ${sealed.items_hash} where the artifact carries ${artifact.items_hash}`);
566
+ }
567
+ if (!sameApprovalHash(sealed.batch_hash, artifact.batch_hash)) {
568
+ return mismatch("batch_hash_differs", `it seals batch_hash ${sealed.batch_hash ?? "none"} where the artifact carries ${artifact.batch_hash ?? "none"}`);
569
+ }
570
+ return {
571
+ ...signature,
572
+ reason: "approval_matches",
573
+ message: `receipt ${signature.receipt_id} was sealed by CodeSpar, ${bound}, and it was paid against the list artifact ${approval.approval_id} approved (${sealed.items_hash}${sealed.batch_hash ? `, batch ${sealed.batch_hash}` : ""}). WHO approved it is in the artifact, under a local key, and nowhere in the seal`,
574
+ chain_check: chainCheck,
575
+ approval_check: { status: "matched", sealed, artifact, reason: "approval_matches" },
576
+ };
577
+ }
@@ -15,7 +15,7 @@ export type MandateStatus = "active" | "paused" | "revoked" | "expired";
15
15
  export interface MandateStatusReport {
16
16
  mandate_id: string;
17
17
  status: MandateStatus | "unknown";
18
- /** True when the organization pressed the kill switch (`org pauseAll`). The API has no such read yet; only the stub can say it. */
18
+ /** True when the organization pressed the kill switch (ent#1648). The API says it as `org_paused` on `GET /v1/mandates/{id}`; the stub says it from `pauseAll`. */
19
19
  org_paused: boolean;
20
20
  checked_at: string;
21
21
  /** Which source answered, so the bundle says whether the check was real. */
@@ -6,7 +6,7 @@
6
6
  * does not compile; the runtime check below refuses the same pair when the
7
7
  * types were bypassed (a cast, a row read back from SQLite).
8
8
  */
9
- import type { Actor, ApprovalMode, EscalationTrigger, ExecutionBatch, ExecutionItem, ExecutionReason, ItemOutcome, MandateRef } from "./types.js";
9
+ import type { Actor, ApprovalMode, EscalationTrigger, ExecutionBatch, ExecutionComposition, ExecutionItem, ExecutionReason, ItemOutcome, MandateRef } from "./types.js";
10
10
 
11
11
  export type ExecutionState =
12
12
  | "drafted"
@@ -78,6 +78,15 @@ export interface Execution<S extends ExecutionState = ExecutionState> {
78
78
  items_hash: string;
79
79
  /** Present when this execution is one line of a batch: the set it was presented inside, and where in it. */
80
80
  batch?: ExecutionBatch;
81
+ /** Present when this execution's amount is composed of lines (a cart): the lines' hash, not the lines. */
82
+ composition?: ExecutionComposition;
83
+ /**
84
+ * A batch line's attempt generation per item index, when it is not 0: how
85
+ * many of the line's derived attempt ids the rail answered as spent before
86
+ * the one this execution presented (`batchAttemptId`). Saved BEFORE the
87
+ * next id is presented, so a reconcile looks up the id that was sent.
88
+ */
89
+ attempt_generations?: Record<number, number>;
81
90
  mandate: MandateRef;
82
91
  /** Written once, before the rail is called. Retrying reuses it. */
83
92
  idempotency_key: string;
@@ -8,8 +8,9 @@
8
8
  * a run with a test key gets instead; this file is never consulted on that
9
9
  * path.
10
10
  *
11
- * It also holds the organization kill switch (`pauseAll`), which the API
12
- * does not expose yet, so `org_paused` can be exercised locally.
11
+ * It also holds the organization kill switch (`pauseAll`), so `org_paused`
12
+ * can be exercised locally the way `GET /v1/mandates/{id}` reports it
13
+ * (ent#1648): a flag next to the mandate's own status, which it leaves alone.
13
14
  */
14
15
  import type { MandateStatusSource, MandateStatus, MandateStatusReport } from "../revocation.js";
15
16
  import type { StateStore } from "../state/store.js";
@@ -46,7 +47,7 @@ export class LocalMandateStatusStub implements MandateStatusSource {
46
47
  this.store.stubSetMandateStatus(mandateId, "active", null, this.clock().toISOString());
47
48
  }
48
49
 
49
- /** The organization kill switch, locally: the API has no `org pauseAll` yet. */
50
+ /** The organization kill switch, locally. Against the API it is pressed with `POST /v1/orgs/{orgId}/pause`, which the kit never calls. */
50
51
  pauseAll(): void {
51
52
  this.store.stubSetOrgPaused(STUB_ORG_ID, true, this.clock().toISOString());
52
53
  }
@@ -3,11 +3,16 @@
3
3
  * scenarios, the adversarial suite and the restart test run offline. Every
4
4
  * attempt is persisted in state.db before the outcome is returned, which is
5
5
  * what lets a process killed right after "the money left" be reconciled on
6
- * `resume` instead of paying again. Receipts here are HMAC-shaped only in
6
+ * `resume` instead of paying again. A repeat of an attempt id is answered the
7
+ * way the deployed API answers it (ent#1671): replayed when it settled,
8
+ * in flight while the provider is on it, spent when it failed, and refused
9
+ * when the id arrives with a different payment. A stub that simply replayed
10
+ * whatever it held would let a suite pass that the API fails. Receipts here are HMAC-shaped only in
7
11
  * form; nothing about them is verifiable by anyone.
8
12
  */
9
- import { sha256Hex } from "../hash.js";
10
- import type { PaymentRail, RailLookup, RailOutcome, RailPayment, RailReceipt } from "../rail.js";
13
+ import { canonicalJson, sha256Hex } from "../hash.js";
14
+ import { checkQuote } from "../quote.js";
15
+ import { checkSpendApproval, type PaymentRail, type RailLookup, type RailOutcome, type RailPayment, type RailReceipt } from "../rail.js";
11
16
  import type { StateStore } from "../state/store.js";
12
17
  import type { Actor } from "../types.js";
13
18
 
@@ -29,6 +34,34 @@ export interface StubRailOptions {
29
34
  afterDispatch?: (attemptId: string) => void;
30
35
  /** Attempt ids whose first lookup answers `in_flight` and whose second lookup finds the provider finished (settled), with no pay() in between. */
31
36
  inFlightThenSettled?: string[];
37
+ /**
38
+ * Where the "provider" keeps its books, when that is not the agent's own
39
+ * state file. The real rail is a server two machines share; a test that
40
+ * runs the same batch from two state files points both at one ledger.
41
+ */
42
+ ledger?: StateStore;
43
+ }
44
+
45
+ /** What the stub's books hold for an attempt the "provider" took and has not finished. */
46
+ const IN_FLIGHT = { status: "in_flight" } as const;
47
+
48
+ type Recorded = RailOutcome | typeof IN_FLIGHT;
49
+
50
+ /**
51
+ * The fields a repeat of an attempt id is compared on, the stub's share of
52
+ * the API's attempt tuple (ent#1671): whose money, how much, to whom, the
53
+ * digest of the quote and the approval the spend carried (ent#1670). Rail
54
+ * and instrument are the API's and have no stub counterpart.
55
+ */
56
+ function mismatchedFields(recorded: Omit<RailPayment, "actor">, asked: Omit<RailPayment, "actor">): string[] {
57
+ const out: string[] = [];
58
+ if (recorded.mandate_id !== asked.mandate_id) out.push("mandate");
59
+ if (recorded.amount_minor !== asked.amount_minor) out.push("amount_minor");
60
+ if (recorded.currency !== asked.currency) out.push("currency");
61
+ if (recorded.payee !== asked.payee) out.push("payee");
62
+ if (canonicalJson(recorded.quote ?? null) !== canonicalJson(asked.quote ?? null)) out.push("quote");
63
+ if (canonicalJson(recorded.approval ?? null) !== canonicalJson(asked.approval ?? null)) out.push("approval");
64
+ return out;
32
65
  }
33
66
 
34
67
  export class StubRail implements PaymentRail {
@@ -36,18 +69,19 @@ export class StubRail implements PaymentRail {
36
69
  private readonly clock: () => Date;
37
70
  private readonly uncertainPending: Set<string>;
38
71
 
72
+ private readonly store: StateStore;
73
+
39
74
  constructor(
40
- private readonly store: StateStore,
75
+ store: StateStore,
41
76
  private readonly options: StubRailOptions = {},
42
77
  ) {
78
+ this.store = options.ledger ?? store;
43
79
  this.clock = options.clock ?? (() => new Date());
44
80
  this.uncertainPending = new Set(options.uncertainOnce ?? []);
45
81
  }
46
82
 
47
83
  private uncertainArmed = false;
48
84
  private readonly inFlightSeen = new Set<string>();
49
- /** Attempts that answered `uncertain` because the stub was armed: the "provider" did take them, and a later lookup finds them finished. */
50
- private readonly lateAttempts = new Set<string>();
51
85
  /** Attempts that already gave their one `uncertain`, so a re-presentation behaves normally. */
52
86
  private readonly uncertainAnswered = new Set<string>();
53
87
  /** How many times pay() reached the point of persisting a NEW attempt. */
@@ -59,12 +93,19 @@ export class StubRail implements PaymentRail {
59
93
  }
60
94
 
61
95
  async pay(payment: RailPayment): Promise<RailOutcome> {
96
+ // The same refusal the CodeSpar rail makes before its call, so a scenario exercises it.
97
+ const quoted = checkQuote(payment);
98
+ if (!quoted.ok) return { status: "failed", code: quoted.code, message: `${quoted.detail}; nothing was sent` };
99
+ const approval = checkSpendApproval(payment);
100
+ if (!approval.ok) return { status: "failed", code: approval.code, message: `${approval.detail}; nothing was sent` };
101
+ const { actor: _actor, ...request } = payment;
62
102
  const existing = this.store.stubRailGet(payment.attempt_id);
63
- if (existing) return existing.outcome as RailOutcome;
103
+ if (existing) return this.repeat(payment.attempt_id, existing.request as Omit<RailPayment, "actor">, existing.outcome as Recorded, request);
64
104
 
65
105
  const uncertainPayee = this.options.uncertainPayees?.includes(payment.payee) === true && !this.uncertainAnswered.has(payment.attempt_id);
66
106
  if (this.uncertainArmed || this.uncertainPending.has(payment.attempt_id) || uncertainPayee) {
67
- if (this.uncertainArmed || uncertainPayee) this.lateAttempts.add(payment.attempt_id);
107
+ // Armed and payee-scripted attempts were TAKEN by the provider, so they are on its books as in flight; `uncertainOnce` never reached it.
108
+ if (this.uncertainArmed || uncertainPayee) this.store.stubRailPut(payment.attempt_id, request, IN_FLIGHT, this.clock().toISOString());
68
109
  this.uncertainAnswered.add(payment.attempt_id);
69
110
  this.uncertainArmed = false;
70
111
  this.uncertainPending.delete(payment.attempt_id);
@@ -74,40 +115,52 @@ export class StubRail implements PaymentRail {
74
115
  const at = this.clock().toISOString();
75
116
  const outcome: RailOutcome = this.options.refusePayees?.includes(payment.payee)
76
117
  ? { status: "failed", code: "psp_dispatch_failed", message: `stub: provider refused payee ${payment.payee}` }
77
- : {
78
- status: "settled",
79
- transaction_id: `stubtx_${sha256Hex(payment.attempt_id).slice(0, 16)}`,
80
- receipt_id: `rcpt_stub_${sha256Hex(`receipt:${payment.attempt_id}`).slice(0, 16)}`,
81
- money_moved: false,
82
- sandbox: true,
83
- raw: { stub: true, attempt_id: payment.attempt_id, at },
84
- };
85
- const { actor: _actor, ...request } = payment;
118
+ : settledOutcome(payment.attempt_id, { stub: true, attempt_id: payment.attempt_id, at });
86
119
  this.payCount += 1;
87
120
  this.store.stubRailPut(payment.attempt_id, request, outcome, at);
88
121
  this.options.afterDispatch?.(payment.attempt_id);
89
122
  return outcome;
90
123
  }
91
124
 
125
+ /**
126
+ * A presentation of an attempt id the books already hold, answered the way
127
+ * the deployed API answers it (ent#1671, #1683), and never by paying:
128
+ * a different payment under the id is `attempt_id_conflict`, checked before
129
+ * the state; a settled one is its ORIGINAL outcome with `idempotent_replay`;
130
+ * one still in flight is `psp_attempt_in_flight`; a failed one is
131
+ * `psp_attempt_conflict`, spent for good.
132
+ */
133
+ private repeat(attemptId: string, recorded: Omit<RailPayment, "actor">, outcome: Recorded, asked: Omit<RailPayment, "actor">): RailOutcome {
134
+ const mismatched = mismatchedFields(recorded, asked);
135
+ if (mismatched.length > 0) {
136
+ return { status: "failed", code: "attempt_id_conflict", message: `stub: attempt ${attemptId} was already used for a different payment (differs in: ${mismatched.join(", ")}); nothing was held or sent`, held: "conflict" };
137
+ }
138
+ switch (outcome.status) {
139
+ case "in_flight":
140
+ return { status: "uncertain", code: "psp_attempt_in_flight", message: `stub: attempt ${attemptId} is claimed and its outcome is not yet recorded` };
141
+ case "settled":
142
+ return { ...outcome, replayed: true, raw: { ...(outcome.raw as Record<string, unknown>), idempotent_replay: true } };
143
+ case "failed":
144
+ return { status: "failed", code: "psp_attempt_conflict", message: `stub: attempt ${attemptId} already failed and moved no money; use a fresh attempt_id`, spent: true };
145
+ default:
146
+ return outcome;
147
+ }
148
+ }
149
+
92
150
  async lookup(attemptId: string, payment: RailPayment): Promise<RailLookup> {
93
- const recorded = this.store.stubRailGet(attemptId)?.outcome as RailOutcome | undefined;
94
- if (recorded) return recorded;
95
- if (this.options.inFlightThenSettled?.includes(attemptId) || this.lateAttempts.has(attemptId)) {
151
+ const { actor: _actor, ...request } = payment;
152
+ const existing = this.store.stubRailGet(attemptId);
153
+ const recorded = existing?.outcome as Recorded | undefined;
154
+ // Looking up IS presenting again on the real rail, so a recorded attempt answers exactly what a repeat of it answers.
155
+ if (existing && recorded && recorded.status !== "in_flight") return this.repeat(attemptId, existing.request as Omit<RailPayment, "actor">, recorded, request);
156
+ if (this.options.inFlightThenSettled?.includes(attemptId) || recorded?.status === "in_flight") {
96
157
  if (!this.inFlightSeen.has(attemptId)) {
97
158
  this.inFlightSeen.add(attemptId);
98
159
  return { status: "in_flight" };
99
160
  }
100
161
  // The provider finished on its own; the stub records the outcome as the rail would, without a new dispatch.
101
- const { actor: _actor, ...request } = payment;
102
- const outcome: RailOutcome = {
103
- status: "settled",
104
- transaction_id: `stubtx_${sha256Hex(attemptId).slice(0, 16)}`,
105
- receipt_id: `rcpt_stub_${sha256Hex(`receipt:${attemptId}`).slice(0, 16)}`,
106
- money_moved: false,
107
- sandbox: true,
108
- raw: { stub: true, attempt_id: attemptId, finished_in_background: true },
109
- };
110
- this.store.stubRailPut(attemptId, request, outcome, this.clock().toISOString());
162
+ const outcome = settledOutcome(attemptId, { stub: true, attempt_id: attemptId, finished_in_background: true });
163
+ this.store.stubRailReplace(attemptId, existing?.request ?? request, outcome, this.clock().toISOString());
111
164
  return outcome;
112
165
  }
113
166
  return undefined;
@@ -118,13 +171,17 @@ export class StubRail implements PaymentRail {
118
171
  if (!sealed) return undefined;
119
172
  const req = sealed.request as Omit<RailPayment, "actor">;
120
173
  const out = sealed.outcome as Extract<RailOutcome, { status: "settled" }>;
174
+ // Like the API: the payee on the receipt is the one the quote sealed, and none when the spend carried no quote.
121
175
  const body = {
122
176
  receipt_id: receiptId,
123
177
  state: "paid",
124
178
  mandate: { id: req.mandate_id },
125
- payment: { amount_minor: req.amount_minor, payee: req.payee, attempt_id: req.attempt_id, money_moved: false, sandbox: true, at: sealed.at },
179
+ // Like the API since ent#1670: the approval the spend carried is sealed and read back, batch_hash null when none was sent.
180
+ chain_version: req.approval ? 4 : 1,
181
+ approval: req.approval ? { items_hash: req.approval.items_hash, batch_hash: req.approval.batch_hash ?? null } : null,
182
+ payment: { amount_minor: req.amount_minor, payee: req.quote?.payee ?? null, attempt_id: req.attempt_id, money_moved: false, sandbox: true, at: sealed.at },
126
183
  };
127
- const chain = `sha256:${sha256Hex(JSON.stringify(body))}`;
184
+ const chain = `sha256:${sha256Hex(JSON.stringify(req.quote ? { ...body, quote: req.quote } : body))}`;
128
185
  return {
129
186
  ...body,
130
187
  chain,
@@ -139,3 +196,14 @@ export class StubRail implements PaymentRail {
139
196
  };
140
197
  }
141
198
  }
199
+
200
+ function settledOutcome(attemptId: string, raw: Record<string, unknown>): RailOutcome {
201
+ return {
202
+ status: "settled",
203
+ transaction_id: `stubtx_${sha256Hex(attemptId).slice(0, 16)}`,
204
+ receipt_id: `rcpt_stub_${sha256Hex(`receipt:${attemptId}`).slice(0, 16)}`,
205
+ money_moved: false,
206
+ sandbox: true,
207
+ raw,
208
+ };
209
+ }
@@ -3,10 +3,12 @@
3
3
  * authority comes before mandate-level authority: a tool that is not in
4
4
  * this file is refused before anything else looks at the call.
5
5
  *
6
- * Two kinds. `meta_tools` are CodeSpar meta-tool names, with the input the
7
- * kit accepts for them (a snapshot of the MCP shape, pinned by `mcp` in the
8
- * manifest; see OPEN_QUESTIONS on why the list is not fetched live).
9
- * `local_tools` are the agent's own read-only helpers.
6
+ * Two kinds. `meta_tools` borrow a CodeSpar meta-tool's name for the job
7
+ * they do, but the input shape is the kit's own and the kit's handler runs it:
8
+ * a payment or charge becomes a `drafted` execution, and the core sends the
9
+ * real call over REST. It is not a copy of the MCP's schema and does not match
10
+ * it (OPEN_QUESTIONS section 8 has the comparison). `local_tools` are the
11
+ * agent's own helpers: reads, and state the agent keeps for itself.
10
12
  */
11
13
  import { readFileSync } from "node:fs";
12
14
  import { z } from "zod";
@@ -17,8 +19,15 @@ const ToolDefinitionSchema = z
17
19
  name: z.string().regex(/^[a-z][a-z0-9_]*$/),
18
20
  description: z.string().min(1),
19
21
  input_schema: z.record(z.string(), z.unknown()),
20
- /** `payment` and `charge` tools create a `drafted` execution and nothing else; `read` tools never touch money. `charge` is the receivable side: the counterparty pays us. */
21
- effect: z.enum(["payment", "charge", "read"]),
22
+ /**
23
+ * `payment` and `charge` tools create a `drafted` execution and nothing
24
+ * else; `read` tools never touch money and change nothing. `charge` is the
25
+ * receivable side: the counterparty pays us. `state` changes durable local
26
+ * state (a cart in `state.db`) and moves no money — not a read, because
27
+ * calling it twice is not calling it once, and not a payment, because no
28
+ * execution comes out of it.
29
+ */
30
+ effect: z.enum(["payment", "charge", "read", "state"]),
22
31
  })
23
32
  .strict();
24
33
 
@@ -36,6 +45,8 @@ export const ToolsFileSchema = z
36
45
  }
37
46
  for (const [i, tool] of t.meta_tools.entries()) {
38
47
  if (!tool.name.startsWith("codespar_")) ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["meta_tools", i, "name"], message: "meta-tools are named codespar_*" });
48
+ // A meta-tool borrows a CodeSpar name for a job CodeSpar does; the state the agent keeps for itself is not one.
49
+ if (tool.effect === "state") ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["meta_tools", i, "effect"], message: "a state tool is local: meta-tools move money or read" });
39
50
  }
40
51
  });
41
52