@codespar/cli 0.16.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 (486) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/mandate-verify.js +98 -19
  3. package/dist/commands/mandate-verify.js.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/mandate-codec.js +31 -20
  7. package/dist/mandate-codec.js.map +1 -1
  8. package/package.json +1 -1
  9. package/templates/bills-agent/README.md +1 -1
  10. package/templates/bills-agent/agents/bills-agent/AGENTS.md +1 -1
  11. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +1 -1
  12. package/templates/bills-agent/agents/bills-agent/README.md +6 -5
  13. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +5 -4
  14. package/templates/bills-agent/packages/agent-core/README.md +14 -1
  15. package/templates/bills-agent/packages/agent-core/package.json +1 -1
  16. package/templates/bills-agent/packages/agent-core/src/agent.ts +39 -1
  17. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  18. package/templates/bills-agent/packages/agent-core/src/api/client.ts +28 -5
  19. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  20. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +62 -26
  21. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  22. package/templates/bills-agent/packages/agent-core/src/approval.ts +14 -2
  23. package/templates/bills-agent/packages/agent-core/src/bundle.ts +35 -3
  24. package/templates/bills-agent/packages/agent-core/src/channels.ts +49 -3
  25. package/templates/bills-agent/packages/agent-core/src/check.ts +25 -2
  26. package/templates/bills-agent/packages/agent-core/src/engine.ts +235 -46
  27. package/templates/bills-agent/packages/agent-core/src/hash.ts +57 -1
  28. package/templates/bills-agent/packages/agent-core/src/index.ts +2 -0
  29. package/templates/bills-agent/packages/agent-core/src/quote.ts +50 -0
  30. package/templates/bills-agent/packages/agent-core/src/rail.ts +89 -4
  31. package/templates/bills-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  32. package/templates/bills-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  33. package/templates/bills-agent/packages/agent-core/src/revocation.ts +1 -1
  34. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +10 -1
  35. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  36. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  37. package/templates/bills-agent/packages/agent-core/src/tools.ts +17 -6
  38. package/templates/bills-agent/packages/agent-core/src/types.ts +54 -1
  39. package/templates/bills-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  40. package/templates/bills-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  41. package/templates/bills-agent/packages/agent-core/test/composition.test.ts +204 -0
  42. package/templates/bills-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  43. package/templates/bills-agent/packages/agent-core/test/helpers.ts +4 -1
  44. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  45. package/templates/bills-agent/packages/agent-core/test/quote.test.ts +198 -0
  46. package/templates/bills-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  47. package/templates/bills-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  48. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +103 -3
  49. package/templates/bills-agent/packages/agent-runtime/README.md +12 -0
  50. package/templates/bills-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  51. package/templates/bills-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  52. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  53. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  54. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  55. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  56. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  57. package/templates/bills-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  58. package/templates/bills-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  59. package/templates/bills-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  60. package/templates/bills-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  61. package/templates/bills-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  62. package/templates/bills-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  63. package/templates/bills-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  64. package/templates/bills-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  65. package/templates/bills-agent/packages/agent-runtime/src/index.ts +1 -0
  66. package/templates/bills-agent/packages/agent-runtime/src/inspect.ts +5 -1
  67. package/templates/bills-agent/packages/agent-runtime/src/kit.ts +25 -2
  68. package/templates/bills-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  69. package/templates/bills-agent/packages/agent-runtime/src/setup.ts +13 -3
  70. package/templates/bills-agent/packages/agent-runtime/src/terminal.ts +127 -18
  71. package/templates/bills-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  72. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  73. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  74. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  75. package/templates/checkout-agent/LICENSE +21 -0
  76. package/templates/checkout-agent/README.md +28 -0
  77. package/templates/checkout-agent/_gitignore +10 -0
  78. package/templates/checkout-agent/agents/checkout-agent/.env.example +5 -0
  79. package/templates/checkout-agent/agents/checkout-agent/AGENTS.md +20 -0
  80. package/templates/checkout-agent/agents/checkout-agent/CLAUDE.md +20 -0
  81. package/templates/checkout-agent/agents/checkout-agent/README.md +94 -0
  82. package/templates/checkout-agent/agents/checkout-agent/SYSTEM_PROMPT.md +36 -0
  83. package/templates/checkout-agent/agents/checkout-agent/agent.yaml +30 -0
  84. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/README.md +28 -0
  85. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-beatriz.json +11 -0
  86. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina-retomada.json +10 -0
  87. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/pedido-marina.json +11 -0
  88. package/templates/checkout-agent/agents/checkout-agent/channels/whatsapp/templates.json +49 -0
  89. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap-human.json +12 -0
  90. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.json +11 -0
  91. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +5 -0
  92. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.json +13 -0
  93. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/coupon-unknown.transcript.jsonl +4 -0
  94. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.json +26 -0
  95. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  96. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.json +11 -0
  97. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/false-authority.transcript.jsonl +5 -0
  98. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.json +12 -0
  99. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/fractioning.transcript.jsonl +5 -0
  100. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.json +13 -0
  101. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/model-total.transcript.jsonl +5 -0
  102. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.json +15 -0
  103. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/payment-claimed.transcript.jsonl +7 -0
  104. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.json +12 -0
  105. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/price-injected.transcript.jsonl +5 -0
  106. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.json +13 -0
  107. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/prompt-injection.transcript.jsonl +4 -0
  108. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.json +16 -0
  109. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped-human.transcript.jsonl +9 -0
  110. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.json +15 -0
  111. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/quantity-swapped.transcript.jsonl +8 -0
  112. package/templates/checkout-agent/agents/checkout-agent/evals/adversarial/webhook-replay.json +11 -0
  113. package/templates/checkout-agent/agents/checkout-agent/evals/eval.yaml +11 -0
  114. package/templates/checkout-agent/agents/checkout-agent/guardrails.json +25 -0
  115. package/templates/checkout-agent/agents/checkout-agent/mandate.example.json +28 -0
  116. package/templates/checkout-agent/agents/checkout-agent/package.json +33 -0
  117. package/templates/checkout-agent/agents/checkout-agent/runbook.md +19 -0
  118. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.json +42 -0
  119. package/templates/checkout-agent/agents/checkout-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +4 -0
  120. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.json +32 -0
  121. package/templates/checkout-agent/agents/checkout-agent/scenarios/cap-exceeded.transcript.jsonl +4 -0
  122. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.json +61 -0
  123. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-recomposed.transcript.jsonl +9 -0
  124. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.json +61 -0
  125. package/templates/checkout-agent/agents/checkout-agent/scenarios/cart-replaced.transcript.jsonl +9 -0
  126. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.json +36 -0
  127. package/templates/checkout-agent/agents/checkout-agent/scenarios/charge-expired.transcript.jsonl +6 -0
  128. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.json +23 -0
  129. package/templates/checkout-agent/agents/checkout-agent/scenarios/coupon-unknown.transcript.jsonl +4 -0
  130. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.json +72 -0
  131. package/templates/checkout-agent/agents/checkout-agent/scenarios/escalated-above-threshold.transcript.jsonl +9 -0
  132. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.json +15 -0
  133. package/templates/checkout-agent/agents/checkout-agent/scenarios/happy-path.transcript.jsonl +6 -0
  134. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.json +23 -0
  135. package/templates/checkout-agent/agents/checkout-agent/scenarios/item-unavailable.transcript.jsonl +3 -0
  136. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.json +42 -0
  137. package/templates/checkout-agent/agents/checkout-agent/scenarios/mandate-revoked.transcript.jsonl +12 -0
  138. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.json +14 -0
  139. package/templates/checkout-agent/agents/checkout-agent/scenarios/nfse-failed.transcript.jsonl +9 -0
  140. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.json +34 -0
  141. package/templates/checkout-agent/agents/checkout-agent/scenarios/payment-claimed.transcript.jsonl +7 -0
  142. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.json +33 -0
  143. package/templates/checkout-agent/agents/checkout-agent/scenarios/price-injected.transcript.jsonl +4 -0
  144. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.json +32 -0
  145. package/templates/checkout-agent/agents/checkout-agent/scenarios/prompt-injection.transcript.jsonl +4 -0
  146. package/templates/checkout-agent/agents/checkout-agent/src/catalog.ts +92 -0
  147. package/templates/checkout-agent/agents/checkout-agent/src/kit.ts +338 -0
  148. package/templates/checkout-agent/agents/checkout-agent/src/modules/bolepix-receivables.ts +179 -0
  149. package/templates/checkout-agent/agents/checkout-agent/src/modules/nfse-invoice.ts +374 -0
  150. package/templates/checkout-agent/agents/checkout-agent/src/modules/storefront-cart.ts +220 -0
  151. package/templates/checkout-agent/agents/checkout-agent/src/pricing.ts +297 -0
  152. package/templates/checkout-agent/agents/checkout-agent/test/adversarial.test.ts +117 -0
  153. package/templates/checkout-agent/agents/checkout-agent/test/check.test.ts +67 -0
  154. package/templates/checkout-agent/agents/checkout-agent/test/cli.test.ts +169 -0
  155. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/order-marina.transcript.jsonl +5 -0
  156. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/ordered-marina-awaiting-payer.transcript.jsonl +6 -0
  157. package/templates/checkout-agent/agents/checkout-agent/test/fixtures/tap-falar-agora-marina.transcript.jsonl +2 -0
  158. package/templates/checkout-agent/agents/checkout-agent/test/nfse.test.ts +170 -0
  159. package/templates/checkout-agent/agents/checkout-agent/test/pricing.test.ts +138 -0
  160. package/templates/checkout-agent/agents/checkout-agent/test/scenarios.test.ts +113 -0
  161. package/templates/checkout-agent/agents/checkout-agent/test/whatsapp.test.ts +171 -0
  162. package/templates/checkout-agent/agents/checkout-agent/tools.json +68 -0
  163. package/templates/checkout-agent/agents/checkout-agent/tsconfig.json +10 -0
  164. package/templates/checkout-agent/package.json +38 -0
  165. package/templates/checkout-agent/packages/agent-core/README.md +34 -0
  166. package/templates/checkout-agent/packages/agent-core/package.json +24 -0
  167. package/templates/checkout-agent/packages/agent-core/src/agent.ts +163 -0
  168. package/templates/checkout-agent/packages/agent-core/src/api/charge-rail.ts +195 -0
  169. package/templates/checkout-agent/packages/agent-core/src/api/client.ts +76 -0
  170. package/templates/checkout-agent/packages/agent-core/src/api/mandate-status.ts +74 -0
  171. package/templates/checkout-agent/packages/agent-core/src/api/rail.ts +153 -0
  172. package/templates/checkout-agent/packages/agent-core/src/api/sandbox-payer.ts +43 -0
  173. package/templates/checkout-agent/packages/agent-core/src/approval.ts +135 -0
  174. package/templates/checkout-agent/packages/agent-core/src/bundle.ts +191 -0
  175. package/templates/checkout-agent/packages/agent-core/src/channels.ts +186 -0
  176. package/templates/checkout-agent/packages/agent-core/src/check.ts +276 -0
  177. package/templates/checkout-agent/packages/agent-core/src/clock.ts +42 -0
  178. package/templates/checkout-agent/packages/agent-core/src/engine.ts +1074 -0
  179. package/templates/checkout-agent/packages/agent-core/src/escalate.ts +85 -0
  180. package/templates/checkout-agent/packages/agent-core/src/events.ts +40 -0
  181. package/templates/checkout-agent/packages/agent-core/src/guardrails.ts +45 -0
  182. package/templates/checkout-agent/packages/agent-core/src/hash.ts +118 -0
  183. package/templates/checkout-agent/packages/agent-core/src/ids.ts +10 -0
  184. package/templates/checkout-agent/packages/agent-core/src/index.ts +34 -0
  185. package/templates/checkout-agent/packages/agent-core/src/mandate.ts +100 -0
  186. package/templates/checkout-agent/packages/agent-core/src/manifest.ts +76 -0
  187. package/templates/checkout-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  188. package/templates/checkout-agent/packages/agent-core/src/providers/replay.ts +63 -0
  189. package/templates/checkout-agent/packages/agent-core/src/providers/types.ts +39 -0
  190. package/templates/checkout-agent/packages/agent-core/src/quote.ts +50 -0
  191. package/templates/checkout-agent/packages/agent-core/src/rail.ts +186 -0
  192. package/templates/checkout-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  193. package/templates/checkout-agent/packages/agent-core/src/receipt-verification.ts +577 -0
  194. package/templates/checkout-agent/packages/agent-core/src/revocation.ts +30 -0
  195. package/templates/checkout-agent/packages/agent-core/src/secrets.ts +30 -0
  196. package/templates/checkout-agent/packages/agent-core/src/state/store.ts +307 -0
  197. package/templates/checkout-agent/packages/agent-core/src/state-machine.ts +157 -0
  198. package/templates/checkout-agent/packages/agent-core/src/stubs/charge-rail.ts +195 -0
  199. package/templates/checkout-agent/packages/agent-core/src/stubs/mandate-status.ts +58 -0
  200. package/templates/checkout-agent/packages/agent-core/src/stubs/rail.ts +209 -0
  201. package/templates/checkout-agent/packages/agent-core/src/tools.ts +66 -0
  202. package/templates/checkout-agent/packages/agent-core/src/types.ts +187 -0
  203. package/templates/checkout-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  204. package/templates/checkout-agent/packages/agent-core/test/approval.test.ts +117 -0
  205. package/templates/checkout-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  206. package/templates/checkout-agent/packages/agent-core/test/bundle.test.ts +85 -0
  207. package/templates/checkout-agent/packages/agent-core/test/clock.test.ts +25 -0
  208. package/templates/checkout-agent/packages/agent-core/test/composition.test.ts +204 -0
  209. package/templates/checkout-agent/packages/agent-core/test/engine.test.ts +596 -0
  210. package/templates/checkout-agent/packages/agent-core/test/escalate.test.ts +42 -0
  211. package/templates/checkout-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  212. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt-keys.json +18 -0
  213. package/templates/checkout-agent/packages/agent-core/test/fixtures/staging-receipt.json +24 -0
  214. package/templates/checkout-agent/packages/agent-core/test/helpers.ts +142 -0
  215. package/templates/checkout-agent/packages/agent-core/test/mandate-status.test.ts +229 -0
  216. package/templates/checkout-agent/packages/agent-core/test/manifest.test.ts +45 -0
  217. package/templates/checkout-agent/packages/agent-core/test/quote.test.ts +198 -0
  218. package/templates/checkout-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  219. package/templates/checkout-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  220. package/templates/checkout-agent/packages/agent-core/test/receipt-verification.test.ts +316 -0
  221. package/templates/checkout-agent/packages/agent-core/test/receivables.test.ts +416 -0
  222. package/templates/checkout-agent/packages/agent-core/test/secrets.test.ts +28 -0
  223. package/templates/checkout-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  224. package/templates/checkout-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  225. package/templates/checkout-agent/packages/agent-core/tsconfig.json +10 -0
  226. package/templates/checkout-agent/packages/agent-runtime/README.md +80 -0
  227. package/templates/checkout-agent/packages/agent-runtime/bin.mjs +32 -0
  228. package/templates/checkout-agent/packages/agent-runtime/package.json +23 -0
  229. package/templates/checkout-agent/packages/agent-runtime/src/adversarial.ts +199 -0
  230. package/templates/checkout-agent/packages/agent-runtime/src/agent.ts +68 -0
  231. package/templates/checkout-agent/packages/agent-runtime/src/channels/contact.ts +13 -0
  232. package/templates/checkout-agent/packages/agent-runtime/src/channels/index.ts +72 -0
  233. package/templates/checkout-agent/packages/agent-runtime/src/channels/rules.ts +120 -0
  234. package/templates/checkout-agent/packages/agent-runtime/src/channels/types.ts +173 -0
  235. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +459 -0
  236. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +216 -0
  237. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/evidence.ts +109 -0
  238. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +350 -0
  239. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +180 -0
  240. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/present.ts +47 -0
  241. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +120 -0
  242. package/templates/checkout-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +127 -0
  243. package/templates/checkout-agent/packages/agent-runtime/src/cli.ts +98 -0
  244. package/templates/checkout-agent/packages/agent-runtime/src/commands/check.ts +15 -0
  245. package/templates/checkout-agent/packages/agent-runtime/src/commands/decide.ts +63 -0
  246. package/templates/checkout-agent/packages/agent-runtime/src/commands/eval.ts +58 -0
  247. package/templates/checkout-agent/packages/agent-runtime/src/commands/inspect.ts +100 -0
  248. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +367 -0
  249. package/templates/checkout-agent/packages/agent-runtime/src/commands/poll.ts +145 -0
  250. package/templates/checkout-agent/packages/agent-runtime/src/commands/reconcile.ts +42 -0
  251. package/templates/checkout-agent/packages/agent-runtime/src/commands/rerun.ts +86 -0
  252. package/templates/checkout-agent/packages/agent-runtime/src/commands/resume.ts +49 -0
  253. package/templates/checkout-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +134 -0
  254. package/templates/checkout-agent/packages/agent-runtime/src/commands/start.ts +248 -0
  255. package/templates/checkout-agent/packages/agent-runtime/src/commands/verify.ts +330 -0
  256. package/templates/checkout-agent/packages/agent-runtime/src/commands/webhook.ts +40 -0
  257. package/templates/checkout-agent/packages/agent-runtime/src/default-kit.ts +83 -0
  258. package/templates/checkout-agent/packages/agent-runtime/src/index.ts +13 -0
  259. package/templates/checkout-agent/packages/agent-runtime/src/inspect.ts +801 -0
  260. package/templates/checkout-agent/packages/agent-runtime/src/kit.ts +229 -0
  261. package/templates/checkout-agent/packages/agent-runtime/src/poll.ts +105 -0
  262. package/templates/checkout-agent/packages/agent-runtime/src/scenarios.ts +274 -0
  263. package/templates/checkout-agent/packages/agent-runtime/src/setup.ts +239 -0
  264. package/templates/checkout-agent/packages/agent-runtime/src/terminal.ts +263 -0
  265. package/templates/checkout-agent/packages/agent-runtime/src/webhook.ts +131 -0
  266. package/templates/checkout-agent/packages/agent-runtime/test/inspect.test.ts +336 -0
  267. package/templates/checkout-agent/packages/agent-runtime/test/verify-cli.test.ts +329 -0
  268. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +277 -0
  269. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-evidence.test.ts +94 -0
  270. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-rules.test.ts +174 -0
  271. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-session.test.ts +167 -0
  272. package/templates/checkout-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  273. package/templates/checkout-agent/packages/agent-runtime/tsconfig.json +9 -0
  274. package/templates/checkout-agent/scripts/whatsapp-emulator.mjs +84 -0
  275. package/templates/checkout-agent/tsconfig.base.json +16 -0
  276. package/templates/checkout-agent/vitest.config.ts +12 -0
  277. package/templates/collections-agent/README.md +6 -1
  278. package/templates/collections-agent/agents/collections-agent/README.md +12 -8
  279. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/README.md +8 -0
  280. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/acordo-1042-retomada.json +10 -0
  281. package/templates/collections-agent/agents/collections-agent/channels/whatsapp/templates.json +27 -1
  282. package/templates/collections-agent/agents/collections-agent/src/kit.ts +2 -0
  283. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +12 -2
  284. package/templates/collections-agent/agents/collections-agent/test/ambiguous-charge.test.ts +55 -0
  285. package/templates/collections-agent/agents/collections-agent/test/fixtures/tap-emitir-nova-1042.transcript.jsonl +2 -0
  286. package/templates/collections-agent/agents/collections-agent/test/whatsapp-poll.test.ts +12 -6
  287. package/templates/collections-agent/agents/collections-agent/test/whatsapp-status-replies.test.ts +148 -0
  288. package/templates/collections-agent/agents/collections-agent/test/whatsapp.test.ts +5 -0
  289. package/templates/collections-agent/package.json +1 -0
  290. package/templates/collections-agent/packages/agent-core/README.md +14 -1
  291. package/templates/collections-agent/packages/agent-core/package.json +1 -1
  292. package/templates/collections-agent/packages/agent-core/src/agent.ts +39 -1
  293. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  294. package/templates/collections-agent/packages/agent-core/src/api/client.ts +28 -5
  295. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  296. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +62 -26
  297. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  298. package/templates/collections-agent/packages/agent-core/src/approval.ts +14 -2
  299. package/templates/collections-agent/packages/agent-core/src/bundle.ts +35 -3
  300. package/templates/collections-agent/packages/agent-core/src/channels.ts +49 -3
  301. package/templates/collections-agent/packages/agent-core/src/check.ts +25 -2
  302. package/templates/collections-agent/packages/agent-core/src/engine.ts +235 -46
  303. package/templates/collections-agent/packages/agent-core/src/hash.ts +57 -1
  304. package/templates/collections-agent/packages/agent-core/src/index.ts +2 -0
  305. package/templates/collections-agent/packages/agent-core/src/quote.ts +50 -0
  306. package/templates/collections-agent/packages/agent-core/src/rail.ts +89 -4
  307. package/templates/collections-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  308. package/templates/collections-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  309. package/templates/collections-agent/packages/agent-core/src/revocation.ts +1 -1
  310. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +10 -1
  311. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  312. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  313. package/templates/collections-agent/packages/agent-core/src/tools.ts +17 -6
  314. package/templates/collections-agent/packages/agent-core/src/types.ts +54 -1
  315. package/templates/collections-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  316. package/templates/collections-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  317. package/templates/collections-agent/packages/agent-core/test/composition.test.ts +204 -0
  318. package/templates/collections-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  319. package/templates/collections-agent/packages/agent-core/test/helpers.ts +4 -1
  320. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  321. package/templates/collections-agent/packages/agent-core/test/quote.test.ts +198 -0
  322. package/templates/collections-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  323. package/templates/collections-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  324. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +103 -3
  325. package/templates/collections-agent/packages/agent-runtime/README.md +12 -0
  326. package/templates/collections-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  327. package/templates/collections-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  328. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  329. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  330. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  331. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  332. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  333. package/templates/collections-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  334. package/templates/collections-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  335. package/templates/collections-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  336. package/templates/collections-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  337. package/templates/collections-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  338. package/templates/collections-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  339. package/templates/collections-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  340. package/templates/collections-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  341. package/templates/collections-agent/packages/agent-runtime/src/index.ts +1 -0
  342. package/templates/collections-agent/packages/agent-runtime/src/inspect.ts +5 -1
  343. package/templates/collections-agent/packages/agent-runtime/src/kit.ts +25 -2
  344. package/templates/collections-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  345. package/templates/collections-agent/packages/agent-runtime/src/setup.ts +13 -3
  346. package/templates/collections-agent/packages/agent-runtime/src/terminal.ts +127 -18
  347. package/templates/collections-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  348. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  349. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  350. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  351. package/templates/collections-agent/scripts/whatsapp-emulator.mjs +84 -0
  352. package/templates/hello-agent/README.md +1 -1
  353. package/templates/hello-agent/packages/agent-core/README.md +14 -1
  354. package/templates/hello-agent/packages/agent-core/package.json +1 -1
  355. package/templates/hello-agent/packages/agent-core/src/agent.ts +39 -1
  356. package/templates/hello-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  357. package/templates/hello-agent/packages/agent-core/src/api/client.ts +28 -5
  358. package/templates/hello-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  359. package/templates/hello-agent/packages/agent-core/src/api/rail.ts +62 -26
  360. package/templates/hello-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  361. package/templates/hello-agent/packages/agent-core/src/approval.ts +14 -2
  362. package/templates/hello-agent/packages/agent-core/src/bundle.ts +35 -3
  363. package/templates/hello-agent/packages/agent-core/src/channels.ts +49 -3
  364. package/templates/hello-agent/packages/agent-core/src/check.ts +25 -2
  365. package/templates/hello-agent/packages/agent-core/src/engine.ts +235 -46
  366. package/templates/hello-agent/packages/agent-core/src/hash.ts +57 -1
  367. package/templates/hello-agent/packages/agent-core/src/index.ts +2 -0
  368. package/templates/hello-agent/packages/agent-core/src/quote.ts +50 -0
  369. package/templates/hello-agent/packages/agent-core/src/rail.ts +89 -4
  370. package/templates/hello-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  371. package/templates/hello-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  372. package/templates/hello-agent/packages/agent-core/src/revocation.ts +1 -1
  373. package/templates/hello-agent/packages/agent-core/src/state-machine.ts +10 -1
  374. package/templates/hello-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  375. package/templates/hello-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  376. package/templates/hello-agent/packages/agent-core/src/tools.ts +17 -6
  377. package/templates/hello-agent/packages/agent-core/src/types.ts +54 -1
  378. package/templates/hello-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  379. package/templates/hello-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  380. package/templates/hello-agent/packages/agent-core/test/composition.test.ts +204 -0
  381. package/templates/hello-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  382. package/templates/hello-agent/packages/agent-core/test/helpers.ts +4 -1
  383. package/templates/hello-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  384. package/templates/hello-agent/packages/agent-core/test/quote.test.ts +198 -0
  385. package/templates/hello-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  386. package/templates/hello-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  387. package/templates/hello-agent/packages/agent-core/test/receivables.test.ts +103 -3
  388. package/templates/hello-agent/packages/agent-runtime/README.md +12 -0
  389. package/templates/hello-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  390. package/templates/hello-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  391. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  392. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  393. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  394. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  395. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  396. package/templates/hello-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  397. package/templates/hello-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  398. package/templates/hello-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  399. package/templates/hello-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  400. package/templates/hello-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  401. package/templates/hello-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  402. package/templates/hello-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  403. package/templates/hello-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  404. package/templates/hello-agent/packages/agent-runtime/src/index.ts +1 -0
  405. package/templates/hello-agent/packages/agent-runtime/src/inspect.ts +5 -1
  406. package/templates/hello-agent/packages/agent-runtime/src/kit.ts +25 -2
  407. package/templates/hello-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  408. package/templates/hello-agent/packages/agent-runtime/src/setup.ts +13 -3
  409. package/templates/hello-agent/packages/agent-runtime/src/terminal.ts +127 -18
  410. package/templates/hello-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  411. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  412. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  413. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  414. package/templates/kits.lock.json +29 -6
  415. package/templates/supplier-payments-agent/README.md +1 -1
  416. package/templates/supplier-payments-agent/agents/supplier-payments-agent/AGENTS.md +1 -1
  417. package/templates/supplier-payments-agent/agents/supplier-payments-agent/CLAUDE.md +1 -1
  418. package/templates/supplier-payments-agent/agents/supplier-payments-agent/README.md +11 -9
  419. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/kit.ts +1 -1
  420. package/templates/supplier-payments-agent/agents/supplier-payments-agent/src/modules/batch-payout.ts +67 -20
  421. package/templates/supplier-payments-agent/agents/supplier-payments-agent/test/batch.test.ts +504 -5
  422. package/templates/supplier-payments-agent/packages/agent-core/README.md +14 -1
  423. package/templates/supplier-payments-agent/packages/agent-core/package.json +1 -1
  424. package/templates/supplier-payments-agent/packages/agent-core/src/agent.ts +39 -1
  425. package/templates/supplier-payments-agent/packages/agent-core/src/api/charge-rail.ts +54 -34
  426. package/templates/supplier-payments-agent/packages/agent-core/src/api/client.ts +28 -5
  427. package/templates/supplier-payments-agent/packages/agent-core/src/api/mandate-status.ts +28 -12
  428. package/templates/supplier-payments-agent/packages/agent-core/src/api/rail.ts +62 -26
  429. package/templates/supplier-payments-agent/packages/agent-core/src/api/sandbox-payer.ts +3 -18
  430. package/templates/supplier-payments-agent/packages/agent-core/src/approval.ts +14 -2
  431. package/templates/supplier-payments-agent/packages/agent-core/src/bundle.ts +35 -3
  432. package/templates/supplier-payments-agent/packages/agent-core/src/channels.ts +49 -3
  433. package/templates/supplier-payments-agent/packages/agent-core/src/check.ts +25 -2
  434. package/templates/supplier-payments-agent/packages/agent-core/src/engine.ts +235 -46
  435. package/templates/supplier-payments-agent/packages/agent-core/src/hash.ts +57 -1
  436. package/templates/supplier-payments-agent/packages/agent-core/src/index.ts +2 -0
  437. package/templates/supplier-payments-agent/packages/agent-core/src/quote.ts +50 -0
  438. package/templates/supplier-payments-agent/packages/agent-core/src/rail.ts +89 -4
  439. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-chain.ts +270 -0
  440. package/templates/supplier-payments-agent/packages/agent-core/src/receipt-verification.ts +194 -18
  441. package/templates/supplier-payments-agent/packages/agent-core/src/revocation.ts +1 -1
  442. package/templates/supplier-payments-agent/packages/agent-core/src/state-machine.ts +10 -1
  443. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/mandate-status.ts +4 -3
  444. package/templates/supplier-payments-agent/packages/agent-core/src/stubs/rail.ts +100 -32
  445. package/templates/supplier-payments-agent/packages/agent-core/src/tools.ts +17 -6
  446. package/templates/supplier-payments-agent/packages/agent-core/src/types.ts +54 -1
  447. package/templates/supplier-payments-agent/packages/agent-core/test/approval-seal.test.ts +233 -0
  448. package/templates/supplier-payments-agent/packages/agent-core/test/attempt-replay.test.ts +265 -0
  449. package/templates/supplier-payments-agent/packages/agent-core/test/composition.test.ts +204 -0
  450. package/templates/supplier-payments-agent/packages/agent-core/test/fixtures/production-receipt-keys.json +1 -0
  451. package/templates/supplier-payments-agent/packages/agent-core/test/helpers.ts +4 -1
  452. package/templates/supplier-payments-agent/packages/agent-core/test/mandate-status.test.ts +85 -9
  453. package/templates/supplier-payments-agent/packages/agent-core/test/quote.test.ts +198 -0
  454. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-chain.test.ts +220 -0
  455. package/templates/supplier-payments-agent/packages/agent-core/test/receipt-read-verification.test.ts +197 -0
  456. package/templates/supplier-payments-agent/packages/agent-core/test/receivables.test.ts +103 -3
  457. package/templates/supplier-payments-agent/packages/agent-runtime/README.md +12 -0
  458. package/templates/supplier-payments-agent/packages/agent-runtime/src/adversarial.ts +32 -2
  459. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/types.ts +53 -4
  460. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/cloud-api.ts +123 -22
  461. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/emulator.ts +30 -20
  462. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/index.ts +146 -21
  463. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/open.ts +40 -3
  464. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/run.ts +8 -1
  465. package/templates/supplier-payments-agent/packages/agent-runtime/src/channels/whatsapp/session.ts +16 -2
  466. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll-whatsapp.ts +70 -20
  467. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/poll.ts +2 -1
  468. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/resume.ts +8 -2
  469. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start-whatsapp.ts +3 -0
  470. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/start.ts +6 -3
  471. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/verify.ts +239 -38
  472. package/templates/supplier-payments-agent/packages/agent-runtime/src/commands/webhook.ts +3 -2
  473. package/templates/supplier-payments-agent/packages/agent-runtime/src/index.ts +1 -0
  474. package/templates/supplier-payments-agent/packages/agent-runtime/src/inspect.ts +5 -1
  475. package/templates/supplier-payments-agent/packages/agent-runtime/src/kit.ts +25 -2
  476. package/templates/supplier-payments-agent/packages/agent-runtime/src/scenarios.ts +8 -2
  477. package/templates/supplier-payments-agent/packages/agent-runtime/src/setup.ts +13 -3
  478. package/templates/supplier-payments-agent/packages/agent-runtime/src/terminal.ts +127 -18
  479. package/templates/supplier-payments-agent/packages/agent-runtime/test/verify-cli.test.ts +211 -9
  480. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-cloud-api.test.ts +45 -0
  481. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-session.test.ts +32 -0
  482. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-status-replies.test.ts +182 -0
  483. package/templates/bills-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  484. package/templates/collections-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  485. package/templates/hello-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
  486. package/templates/supplier-payments-agent/packages/agent-runtime/test/whatsapp-emulator.integration.test.ts +0 -204
@@ -0,0 +1,18 @@
1
+ {
2
+ "issuer": "did:web:id.codespar.dev",
3
+ "algorithm": "Ed25519",
4
+ "signing_string": "codespar-receipt:v1:<receipt_id>:<chain>",
5
+ "keys": [
6
+ {
7
+ "kid": "did:web:id.codespar.dev#1",
8
+ "kty": "OKP",
9
+ "crv": "Ed25519",
10
+ "x": "u7hulmfhdU9wxv44pfychrNTF0RvUIwDn_QKi3j5Puk",
11
+ "alg": "EdDSA",
12
+ "use": "sig",
13
+ "status": "active",
14
+ "created_at": "2026-09-24T22:22:38.589Z",
15
+ "retired_at": null
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "receipt_id": "rcpt_BkYTo_9eJnlyzGsRjfFYHk",
3
+ "state": "paid",
4
+ "mandate": {
5
+ "id": "cm_8m6ew__uzvMrp2i3"
6
+ },
7
+ "payment": {
8
+ "amount_minor": 185000,
9
+ "payee": null,
10
+ "attempt_id": "att_50fa99e93ed1183520bfd185d350a7c4_0",
11
+ "money_moved": false,
12
+ "sandbox": true,
13
+ "at": "2026-09-24T22:42:41.242Z"
14
+ },
15
+ "chain": "cf5a00d44c4aea42bd7e4bcb48f37f077bfeae1ff5f6f4fff20438861a79e17d",
16
+ "receipt_sig": "9a63a12e9ab88e10c76f77d8ec0c3f4dec9ca1251579a608be1be260922b312c",
17
+ "receipt_sig_ed25519": "aQTf3A1UR8kdthPUwBDfAmnnpYyn8SUp1DYyKcvMylOYhLtznQS_h2SB-N9PxQtSS2wTSO-lp_YDrrVS5Nz1CA",
18
+ "receipt_sig_kid": "did:web:id.codespar.dev#1",
19
+ "actor": {
20
+ "type": "agent",
21
+ "agent": "bills-agent@0.1.0",
22
+ "on_behalf_of": "usr_demo_titular"
23
+ }
24
+ }
@@ -0,0 +1,142 @@
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { hmacSigner } from "../src/approval.js";
5
+ import { ProofBundle } from "../src/bundle.js";
6
+ import { ExecutionEngine, type EngineDeps } from "../src/engine.js";
7
+ import { GuardrailsSchema, type Guardrails } from "../src/guardrails.js";
8
+ import { MandateSchema, type Mandate } from "../src/mandate.js";
9
+ import { ManifestSchema, type Manifest } from "../src/manifest.js";
10
+ import { StateStore } from "../src/state/store.js";
11
+ import { LocalMandateStatusStub } from "../src/stubs/mandate-status.js";
12
+ import type { PaymentRail } from "../src/rail.js";
13
+ import type { MandateStatusSource } from "../src/revocation.js";
14
+ import { StubRail, type StubRailOptions } from "../src/stubs/rail.js";
15
+ import type { ApprovalMode } from "../src/types.js";
16
+
17
+ export const ESCOLA = "escola@exemplo.com.br";
18
+ export const MERCADO = "+5511999990001";
19
+ export const FUNCIONARIA = "123.456.789-09";
20
+
21
+ export function testManifest(over: Partial<Manifest> = {}): Manifest {
22
+ return ManifestSchema.parse({
23
+ schema: 1,
24
+ name: "bills-agent",
25
+ version: "0.1.0",
26
+ approval: ["human", "mandate"],
27
+ default_approval: "human",
28
+ escalate_above: { amount: 150000, new_beneficiary: true, outside_hours: "22:00-07:00" },
29
+ mcp: "@codespar/mcp@0.5.8",
30
+ cli: "@codespar/cli@0.14.0",
31
+ tools: "./tools.json",
32
+ guardrails: "./guardrails.json",
33
+ mandate_schema: "./mandate.example.json",
34
+ events: ["commerce.payment.succeeded", "commerce.payment.failed"],
35
+ channels: ["terminal"],
36
+ maturity: { "pix-out": "sandbox", "embedded-consent": "sandbox", "receipt-verification": "blocked" },
37
+ scenarios: "./scenarios/",
38
+ evals: "./evals/",
39
+ agents_md: "./AGENTS.md",
40
+ ...over,
41
+ });
42
+ }
43
+
44
+ export function testGuardrails(over: Partial<Guardrails> = {}): Guardrails {
45
+ return GuardrailsSchema.parse({
46
+ approval: "human",
47
+ escalate_above: { amount: 150000, new_beneficiary: true, outside_hours: "22:00-07:00" },
48
+ velocity: { window_hours: 24 },
49
+ ...over,
50
+ });
51
+ }
52
+
53
+ export function testMandate(over: Partial<Mandate> = {}): Mandate {
54
+ return MandateSchema.parse({
55
+ id: "mdt_test_0001",
56
+ version: 1,
57
+ consumer_id: "usr_demo",
58
+ agent_id: "bills-agent",
59
+ purpose: "contas do mes",
60
+ currency: "BRL",
61
+ cap_minor: 7200000,
62
+ per_tx_cap_minor: 250000,
63
+ periodic_cap: { window: "month", cap_minor: 600000 },
64
+ merchant_pin_kind: "pix-key",
65
+ merchant_allowlist: [ESCOLA, MERCADO, FUNCIONARIA],
66
+ beneficiaries: [
67
+ { alias: "escola", name: "Escola Aurora", payee: ESCOLA },
68
+ { alias: "mercado", name: "Mercado do Bairro", payee: MERCADO },
69
+ { alias: "funcionaria", name: "Maria (funcionaria)", payee: FUNCIONARIA },
70
+ ],
71
+ status: "active",
72
+ expires_at: "2027-09-23T00:00:00.000Z",
73
+ ...over,
74
+ });
75
+ }
76
+
77
+ export interface Harness {
78
+ dir: string;
79
+ store: StateStore;
80
+ gate: LocalMandateStatusStub;
81
+ rail: StubRail;
82
+ bundle: ProofBundle;
83
+ engine: ExecutionEngine;
84
+ now: Date;
85
+ setNow(date: Date): void;
86
+ }
87
+
88
+ export interface HarnessOptions {
89
+ mode?: ApprovalMode;
90
+ now?: Date;
91
+ guardrails?: Partial<Guardrails>;
92
+ mandate?: Partial<Mandate>;
93
+ manifest?: Partial<Manifest>;
94
+ rail?: StubRailOptions;
95
+ /** What the engine dispatches to instead of the stub itself, e.g. the stub with a different receipt. `rail` on the harness stays the stub. */
96
+ wrapRail?: (stub: StubRail) => PaymentRail;
97
+ dir?: string;
98
+ runId?: string;
99
+ /** The section 4.7 status source; the local stub unless a test wires the API one. */
100
+ status?: MandateStatusSource;
101
+ }
102
+
103
+ export function harness(options: HarnessOptions = {}): Harness {
104
+ const dir = options.dir ?? mkdtempSync(join(tmpdir(), "agent-core-"));
105
+ let now = options.now ?? new Date("2026-09-23T18:00:00Z"); // 15:00 in Sao Paulo
106
+ const clock = () => now;
107
+ const store = new StateStore(join(dir, "state.db"));
108
+ const gate = new LocalMandateStatusStub(store, clock);
109
+ const rail = new StubRail(store, { clock, ...(options.rail ?? {}) });
110
+ const runId = options.runId ?? "run_test";
111
+ const bundle = new ProofBundle(join(dir, "runs"), runId);
112
+ const mode = options.mode ?? "human";
113
+ const deps: EngineDeps = {
114
+ store,
115
+ rail: options.wrapRail ? options.wrapRail(rail) : rail,
116
+ status: options.status ?? gate,
117
+ signer: hmacSigner("test", Buffer.alloc(32, 1)),
118
+ manifest: testManifest(options.manifest),
119
+ guardrails: testGuardrails({ approval: mode, ...(options.guardrails ?? {}) }),
120
+ mandate: testMandate(options.mandate),
121
+ bundle,
122
+ mode,
123
+ runId,
124
+ onBehalfOf: "usr_demo",
125
+ clock,
126
+ };
127
+ const engine = new ExecutionEngine(deps);
128
+ return {
129
+ dir,
130
+ store,
131
+ gate,
132
+ rail,
133
+ bundle,
134
+ engine,
135
+ get now() {
136
+ return now;
137
+ },
138
+ setNow(date: Date) {
139
+ now = date;
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Section 4.7 against the real client: `ApiMandateStatusSource` reads
3
+ * `GET /v1/mandates/{id}` on a mocked HTTP server, and the engine is driven
4
+ * through it. Every answer but `active` must keep `rail.pay` from ever
5
+ * being called, and a read that does not answer must refuse too. The
6
+ * organization kill switch (ent#1648) rides on the same read as `org_paused`,
7
+ * and the spend route refuses 403 `org_paused` on its own when the switch is
8
+ * pressed after the read.
9
+ */
10
+ import { createServer, type Server } from "node:http";
11
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
12
+ import { createCodeSparClient } from "../src/api/client.js";
13
+ import { ApiMandateStatusSource } from "../src/api/mandate-status.js";
14
+ import { CodeSparRail } from "../src/api/rail.js";
15
+ import type { MandateStatusSource } from "../src/revocation.js";
16
+ import { harness, type Harness } from "./helpers.js";
17
+
18
+ const MANDATE_ID = "mdt_test_0001";
19
+ const NOW = new Date("2026-09-23T18:00:00Z");
20
+ const approver = { id: "usr_demo_titular", channel: "terminal" };
21
+
22
+ /** The 14-field projection `GET /v1/mandates/{id}` answers (measured on staging, 2026-09-23), plus the two kill-switch fields of ent#1648. */
23
+ function mandateBody(over: Record<string, unknown> = {}): Record<string, unknown> {
24
+ return {
25
+ id: MANDATE_ID,
26
+ consumer_id: "usr_demo",
27
+ agent_id: "bills-agent",
28
+ display_name: null,
29
+ purpose: "contas do mes",
30
+ merchant_allowlist: ["escola@exemplo.com.br"],
31
+ merchant_pin_kind: "pix-key",
32
+ intent_note: null,
33
+ cap_minor: "7200000",
34
+ per_tx_cap_minor: "250000",
35
+ currency: "BRL",
36
+ status: "active",
37
+ expires_at: "2027-09-23T00:00:00.000Z",
38
+ created_at: "2026-09-23T00:00:00.000Z",
39
+ org_paused: false,
40
+ org_paused_at: null,
41
+ ...over,
42
+ };
43
+ }
44
+
45
+ type Answer = { status: number; body?: unknown; hang?: boolean };
46
+
47
+ let server: Server;
48
+ let baseUrl: string;
49
+ let next: Answer = { status: 200, body: mandateBody() };
50
+ /** What a POST (the spend) answers, when a test sends one; the mandate read keeps answering `next`. */
51
+ let spendNext: Answer | undefined;
52
+ const requests: Array<{ method: string; url: string; bearerIsTestKey: boolean }> = [];
53
+
54
+ beforeAll(async () => {
55
+ server = createServer((req, res) => {
56
+ requests.push({ method: req.method ?? "", url: req.url ?? "", bearerIsTestKey: (req.headers.authorization ?? "").startsWith("Bearer csk_test_") });
57
+ const answer = req.method === "POST" && spendNext ? spendNext : next;
58
+ if (answer.hang) return; // never answers: the client's timeout is the only way out
59
+ res.writeHead(answer.status, { "content-type": "application/json" });
60
+ res.end(typeof answer.body === "string" ? answer.body : JSON.stringify(answer.body));
61
+ });
62
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
63
+ baseUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
64
+ });
65
+
66
+ afterAll(async () => {
67
+ server.closeAllConnections();
68
+ await new Promise<void>((resolve) => server.close(() => resolve()));
69
+ });
70
+
71
+ beforeEach(() => {
72
+ next = { status: 200, body: mandateBody() };
73
+ spendNext = undefined;
74
+ requests.length = 0;
75
+ });
76
+
77
+ function apiClient(timeoutMs = 5_000) {
78
+ // The placeholder passes the `csk_test_` guard and is the one test-key-shaped string the secret scan allows.
79
+ return createCodeSparClient({ apiKey: "csk_test_your_key_here", baseUrl, timeoutMs });
80
+ }
81
+
82
+ function apiSource(timeoutMs = 5_000): ApiMandateStatusSource {
83
+ return new ApiMandateStatusSource(apiClient(timeoutMs), () => NOW);
84
+ }
85
+
86
+ function apiHarness(status: MandateStatusSource = apiSource()): Harness {
87
+ return harness({ mode: "mandate", now: NOW, manifest: { escalate_above: {} }, guardrails: { escalate_above: {} }, status });
88
+ }
89
+
90
+ /** Drafts and approves under `active`, then flips the API's answer: the last gate is what is under test. */
91
+ async function approvedThen(h: Harness, answer: Answer): Promise<string> {
92
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }] });
93
+ if (!d.ok) throw new Error(`refused before draft: ${d.reason}`);
94
+ expect(d.execution.state).toBe("approved");
95
+ next = answer;
96
+ return d.execution.id;
97
+ }
98
+
99
+ describe("ApiMandateStatusSource reads GET /v1/mandates/{id} with the test key", () => {
100
+ it("answers the status the API gives, on the one registered spelling of the route", async () => {
101
+ const report = await apiSource().check(MANDATE_ID);
102
+ expect(report).toMatchObject({ mandate_id: MANDATE_ID, status: "active", org_paused: false, source: "api" });
103
+ expect(requests).toEqual([{ method: "GET", url: `/v1/mandates/${MANDATE_ID}`, bearerIsTestKey: true }]);
104
+ });
105
+
106
+ it("an active mandate past its expires_at is expired, whatever the status field says", async () => {
107
+ next = { status: 200, body: mandateBody({ expires_at: "2026-09-23T17:59:59.000Z" }) };
108
+ expect((await apiSource().check(MANDATE_ID)).status).toBe("expired");
109
+ });
110
+
111
+ it("never throws: 404, 500, a hung connection, a body without a readable status and a status outside the four are all `unknown`, with a reason", async () => {
112
+ next = { status: 404, body: { error: { code: "mandate_not_found", message: "no such mandate" }, request_id: null } };
113
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "unknown", detail: expect.stringContaining("no mandate") });
114
+ next = { status: 500, body: { error: { code: "internal", message: "boom" } } };
115
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "unknown", detail: expect.stringContaining("did not answer") });
116
+ next = { status: 200, body: "not json at all" };
117
+ expect((await apiSource().check(MANDATE_ID)).status).toBe("unknown");
118
+ next = { status: 200, body: mandateBody({ status: "frozen" }) };
119
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "unknown", detail: expect.stringContaining("frozen") });
120
+ next = { status: 200, hang: true };
121
+ expect(await apiSource(150).check(MANDATE_ID)).toMatchObject({ status: "unknown", detail: expect.stringContaining("timeout") });
122
+ });
123
+ });
124
+
125
+ describe("section 4.7 through the engine, on the API source: executing only on active", () => {
126
+ it("active: the approved execution reaches executing and settles on the rail", async () => {
127
+ const h = apiHarness();
128
+ const id = await approvedThen(h, { status: 200, body: mandateBody() });
129
+ const out = await h.engine.execute(id);
130
+ expect(out.state).toBe("settled");
131
+ expect(h.rail.payCount).toBe(1);
132
+ });
133
+
134
+ const refusals: Array<[string, Answer, string, string]> = [
135
+ ["paused", { status: 200, body: mandateBody({ status: "paused" }) }, "denied", "mandate_paused"],
136
+ ["revoked", { status: 200, body: mandateBody({ status: "revoked" }) }, "denied", "mandate_revoked"],
137
+ ["expired", { status: 200, body: mandateBody({ status: "expired" }) }, "expired", "mandate_expired"],
138
+ ["500", { status: 500, body: { error: { code: "internal", message: "boom" } } }, "denied", "mandate_status_unavailable"],
139
+ ["404", { status: 404, body: { error: { code: "mandate_not_found", message: "gone" } } }, "denied", "mandate_status_unavailable"],
140
+ ["timeout", { status: 200, hang: true }, "denied", "mandate_status_unavailable"],
141
+ ];
142
+ for (const [name, answer, state, reason] of refusals) {
143
+ it(`${name} after approval: ${state} (${reason}), no outbox row, rail.pay never called`, async () => {
144
+ const h = apiHarness(name === "timeout" ? apiSource(150) : apiSource());
145
+ const id = await approvedThen(h, answer);
146
+ const out = await h.engine.execute(id);
147
+ expect(out.state).toBe(state);
148
+ expect(out.reason).toBe(reason);
149
+ expect(out.detail).toContain(MANDATE_ID);
150
+ expect(h.store.listOutbox()).toHaveLength(0);
151
+ expect(h.rail.payCount).toBe(0);
152
+ // A new request under the same answer is refused before anything is drafted.
153
+ expect(await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }] })).toMatchObject({ ok: false, refused_before_draft: true, reason });
154
+ expect(h.store.listExecutions()).toHaveLength(1);
155
+ });
156
+ }
157
+
158
+ it("a source that throws is fail-closed too: mandate_status_unavailable, rail.pay never called", async () => {
159
+ const h = apiHarness({ check: async () => { throw new Error("socket hang up"); } });
160
+ expect(await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }] })).toMatchObject({ ok: false, reason: "mandate_status_unavailable" });
161
+ expect(h.rail.payCount).toBe(0);
162
+ });
163
+
164
+ });
165
+
166
+ describe("the organization kill switch (ent#1648): org_paused on the read, 403 org_paused on the spend", () => {
167
+ const PAUSED = { org_paused: true, org_paused_at: "2026-09-23T17:30:00.000Z" };
168
+
169
+ it("the source reports org_paused as the API says it, and keeps the mandate's own status next to it", async () => {
170
+ next = { status: 200, body: mandateBody(PAUSED) };
171
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "active", org_paused: true, source: "api" });
172
+ });
173
+
174
+ it("org_paused missing, or not a boolean, is unknown: never read as a running organization", async () => {
175
+ const { org_paused: _dropped, ...withoutFlag } = mandateBody();
176
+ next = { status: 200, body: withoutFlag };
177
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "unknown", org_paused: false, detail: expect.stringContaining("org_paused") });
178
+ next = { status: 200, body: mandateBody({ org_paused: "false" }) };
179
+ expect(await apiSource().check(MANDATE_ID)).toMatchObject({ status: "unknown", detail: expect.stringContaining("org_paused") });
180
+ next = { status: 200, body: mandateBody({ org_paused: null }) };
181
+ expect((await apiSource().check(MANDATE_ID)).status).toBe("unknown");
182
+ });
183
+
184
+ it("paused before execute: the gate denies (org_paused) whatever status says, no outbox row, rail.pay never called", async () => {
185
+ const h = apiHarness();
186
+ const id = await approvedThen(h, { status: 200, body: mandateBody(PAUSED) });
187
+ const out = await h.engine.execute(id);
188
+ expect(out).toMatchObject({ state: "denied", reason: "org_paused" });
189
+ expect(h.store.listOutbox()).toHaveLength(0);
190
+ expect(h.rail.payCount).toBe(0);
191
+ expect(await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }] })).toMatchObject({ ok: false, refused_before_draft: true, reason: "org_paused" });
192
+ });
193
+
194
+ it("a read without org_paused after approval: denied (mandate_status_unavailable), rail.pay never called", async () => {
195
+ const h = apiHarness();
196
+ const { org_paused: _dropped, ...withoutFlag } = mandateBody();
197
+ const id = await approvedThen(h, { status: 200, body: withoutFlag });
198
+ const out = await h.engine.execute(id);
199
+ expect(out).toMatchObject({ state: "denied", reason: "mandate_status_unavailable" });
200
+ expect(out.detail).toContain("org_paused");
201
+ expect(h.rail.payCount).toBe(0);
202
+ });
203
+
204
+ // Paused between the gate and the spend: the read said running, the API refuses the spend itself before any hold.
205
+ // The execution is already `executing`, and section 4.7 closes that state as `settled` or `failed` only: no new edge.
206
+ const spendRefusals: Array<[string, unknown]> = [
207
+ ["the documented envelope", { error: { code: "org_paused", message: "the organization paused all agent spend (kill switch)" }, request_id: null }],
208
+ ["the flat body the guardrail sends", { error: "org_paused", message: "the organization paused all agent spend (kill switch)" }],
209
+ ];
210
+ for (const [shape, body] of spendRefusals) {
211
+ it(`paused between the gate and the spend, 403 org_paused (${shape}): failed (org_paused), and nothing but the read and the one spend reached the API`, async () => {
212
+ const h = harness({ mode: "mandate", now: NOW, manifest: { escalate_above: {} }, guardrails: { escalate_above: {} }, status: apiSource(), wrapRail: () => new CodeSparRail(apiClient()) });
213
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 1000 }] });
214
+ if (!d.ok) throw new Error(`refused before draft: ${d.reason}`);
215
+ spendNext = { status: 403, body };
216
+ const out = await h.engine.execute(d.execution.id);
217
+ expect(out.state).toBe("failed");
218
+ expect(out.reason).toBe("org_paused");
219
+ expect(out.history.at(-1)).toMatchObject({ from: "executing", to: "failed", reason: "org_paused" });
220
+ expect(out.outcomes).toEqual([expect.objectContaining({ status: "failed", code: "org_paused" })]);
221
+ expect(out.outcomes.some((o) => "receipt_id" in o || "transaction_id" in o)).toBe(false);
222
+ expect(h.store.listOutbox({ execution_id: out.id })).toEqual([expect.objectContaining({ status: "failed" })]);
223
+ // No receipt read, no ledger or fund call, no second spend: the mandate read(s) and exactly one POST.
224
+ const spend = `/v1/consumers/mandates/${out.mandate.id}/spend`;
225
+ expect(requests.filter((r) => r.method === "POST").map((r) => r.url)).toEqual([spend]);
226
+ expect(requests.filter((r) => !(r.method === "GET" && r.url === `/v1/mandates/${out.mandate.id}`) && r.url !== spend)).toEqual([]);
227
+ });
228
+ }
229
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseManifest } from "../src/manifest.js";
3
+
4
+ const valid = `
5
+ schema: 1
6
+ name: bills-agent
7
+ version: 0.1.0
8
+ approval: [human, mandate]
9
+ default_approval: human
10
+ escalate_above:
11
+ amount: 150000
12
+ new_beneficiary: true
13
+ outside_hours: "22:00-07:00"
14
+ mcp: "@codespar/mcp@0.5.8"
15
+ cli: "@codespar/cli@0.14.0"
16
+ tools: ./tools.json
17
+ guardrails: ./guardrails.json
18
+ mandate_schema: ./mandate.example.json
19
+ events: [commerce.payment.succeeded, commerce.payment.failed]
20
+ channels: [terminal]
21
+ maturity:
22
+ pix-out: sandbox
23
+ embedded-consent: sandbox
24
+ receipt-verification: blocked
25
+ scenarios: ./scenarios/
26
+ evals: ./evals/
27
+ agents_md: ./AGENTS.md
28
+ `;
29
+
30
+ describe("section 4.3: agent.yaml schema 1", () => {
31
+ it("parses the example", () => {
32
+ const m = parseManifest(valid);
33
+ expect(m.name).toBe("bills-agent");
34
+ expect(m.escalate_above?.amount).toBe(150000);
35
+ });
36
+
37
+ it("refuses an unpinned mcp or cli, a wrong schema, and an unknown field", () => {
38
+ expect(() => parseManifest(valid.replace('"@codespar/mcp@0.5.8"', '"@codespar/mcp"'))).toThrow(/mcp/);
39
+ expect(() => parseManifest(valid.replace('"@codespar/cli@0.14.0"', '"@codespar/cli@latest"'))).toThrow(/cli/);
40
+ expect(() => parseManifest(valid.replace("schema: 1", "schema: 2"))).toThrow();
41
+ expect(() => parseManifest(valid + "telemetry: true\n")).toThrow(/unrecognized/i);
42
+ expect(() => parseManifest(valid.replace("default_approval: human", "default_approval: mandate").replace("approval: [human, mandate]", "approval: [human]"))).toThrow(/default_approval/);
43
+ expect(() => parseManifest(valid.replace('outside_hours: "22:00-07:00"', 'outside_hours: "10pm-7am"'))).toThrow(/HH:MM/);
44
+ });
45
+ });
@@ -0,0 +1,198 @@
1
+ /**
2
+ * OPEN_QUESTIONS §18: the sealed receipt names the payee only when the spend
3
+ * carries a `SpendQuote`. Every spend carries one, built from the approval
4
+ * artifact's line; a spend without one, or with one that disagrees with the
5
+ * money about to move, never reaches the API; and a receipt whose sealed
6
+ * payee is not the payee that was paid is an error, not a line in a log.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import type { ApiClient } from "@codespar/sdk";
11
+ import { describe, expect, it, vi } from "vitest";
12
+ import { hmacSigner } from "../src/approval.js";
13
+ import { CodeSparRail } from "../src/api/rail.js";
14
+ import { maskPayee } from "../src/bundle.js";
15
+ import { ReceiptSealMismatchError } from "../src/engine.js";
16
+ import { canonicalJson } from "../src/hash.js";
17
+ import { checkQuote } from "../src/quote.js";
18
+ import type { PaymentRail, RailPayment } from "../src/rail.js";
19
+ import { ESCOLA, harness, MERCADO } from "./helpers.js";
20
+
21
+ const approver = { id: "usr_demo", channel: "terminal" };
22
+ const actor = { type: "agent" as const, agent: "bills-agent@0.1.0", on_behalf_of: "usr_demo" };
23
+
24
+ function payment(over: Partial<RailPayment> = {}): RailPayment {
25
+ return {
26
+ attempt_id: "att_quote_0",
27
+ mandate_id: "mdt_test_0001",
28
+ amount_minor: 185000,
29
+ currency: "BRL",
30
+ payee: ESCOLA,
31
+ purpose: "contas do mes",
32
+ agent_id: "bills-agent",
33
+ quote: { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, at: "2026-09-23T18:00:00.000Z" },
34
+ approval: { items_hash: `sha256:${"1".repeat(64)}` },
35
+ actor,
36
+ ...over,
37
+ };
38
+ }
39
+
40
+ /** An ApiClient that records what it was asked and answers a settled spend. */
41
+ function fakeApi(receiptBody?: unknown) {
42
+ const post = vi.fn(async (_path: string, _init: { body: Record<string, unknown> }) => ({ payment: { transactionId: "tx_1", moneyMoved: false }, receipt: { id: "rcpt_1" } }));
43
+ const get = vi.fn(async () => receiptBody);
44
+ return { api: { post, get } as unknown as ApiClient, post, get };
45
+ }
46
+
47
+ /** The stub rail, but the receipt it hands back seals whatever payee the test says. */
48
+ function sealing(inner: PaymentRail, sealedPayee: string | null): PaymentRail {
49
+ return {
50
+ name: inner.name,
51
+ pay: (p) => inner.pay(p),
52
+ lookup: (id, p, tx) => inner.lookup(id, p, tx),
53
+ receipt: async (id, a) => {
54
+ const r = await inner.receipt(id, a);
55
+ return r && { ...r, payment: { ...r.payment, payee: sealedPayee } };
56
+ },
57
+ };
58
+ }
59
+
60
+ describe("§18: every spend presents the quote of what was approved", () => {
61
+ it("the quote is the approved line: seller, resource, price, payee and the moment of approval", async () => {
62
+ const h = harness({ mode: "human" });
63
+ const seen: RailPayment[] = [];
64
+ const inner = h.rail;
65
+ const pay = inner.pay.bind(inner);
66
+ inner.pay = async (p) => (seen.push(p), pay(p));
67
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000, description: "outubro" }, { payee: "mercado", amount: 32000 }] });
68
+ if (!d.ok) throw new Error("refused");
69
+ const approved = h.engine.approve(d.execution.id, approver);
70
+ const artifact = h.store.getApproval(approved.approval_id!)!;
71
+ expect((await h.engine.execute(approved.id)).state).toBe("settled");
72
+
73
+ expect(seen.map((p) => p.quote)).toEqual([
74
+ { seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, at: artifact.approved_at },
75
+ // No description on the line: the resource is the mandate's purpose, never something the model adds later.
76
+ { seller: "Mercado do Bairro", resource: "contas do mes", price_minor: 32000, payee: MERCADO, at: artifact.approved_at },
77
+ ]);
78
+ });
79
+
80
+ it("the local receipt copy carries the payee the receipt sealed, masked", async () => {
81
+ const h = harness({ mode: "human" });
82
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
83
+ if (!d.ok) throw new Error("refused");
84
+ const settled = await h.engine.execute(h.engine.approve(d.execution.id, approver).id);
85
+ const copy = JSON.parse(readFileSync(join(h.bundle.dir, "receipts", `${settled.outcomes[0]!.receipt_id}.json`), "utf8"));
86
+ expect(copy.payment.payee).toBe(maskPayee(ESCOLA));
87
+ expect(JSON.stringify(copy)).not.toContain(ESCOLA);
88
+ });
89
+
90
+ it("an artifact whose line disagrees with the execution is refused before the call, even when its hash and signature hold", async () => {
91
+ const h = harness({ mode: "human" });
92
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
93
+ if (!d.ok) throw new Error("refused");
94
+ const approved = h.engine.approve(d.execution.id, approver);
95
+ // A writer bug, not an attacker: the artifact's items no longer say what its items_hash says. Re-signed with the harness key.
96
+ const { signature: _old, ...unsigned } = h.store.getApproval(approved.approval_id!)!;
97
+ const drifted = { ...unsigned, items: unsigned.items.map((i) => ({ ...i, amount: 999 })) };
98
+ h.store.saveApproval({ ...drifted, signature: { alg: "HMAC-SHA256", key_id: "test", value: hmacSigner("test", Buffer.alloc(32, 1)).sign(canonicalJson(drifted)) } });
99
+
100
+ const denied = await h.engine.execute(approved.id);
101
+ expect(denied.state).toBe("denied");
102
+ expect(denied.reason).toBe("quote_mismatch");
103
+ expect(h.rail.payCount).toBe(0);
104
+ });
105
+ });
106
+
107
+ describe("§18: a spend that would not seal what was approved never goes out", () => {
108
+ it("checkQuote: missing, wrong price, wrong payee", () => {
109
+ expect(checkQuote(payment())).toMatchObject({ ok: true });
110
+ const { quote: _q, ...bare } = payment();
111
+ expect(checkQuote(bare)).toMatchObject({ ok: false, code: "quote_missing" });
112
+ expect(checkQuote(payment({ amount_minor: 185001 }))).toMatchObject({ ok: false, code: "quote_mismatch" });
113
+ expect(checkQuote(payment({ payee: MERCADO }))).toMatchObject({ ok: false, code: "quote_mismatch" });
114
+ });
115
+
116
+ it("the CodeSpar rail refuses a spend without a quote, and one that disagrees, without calling the API", async () => {
117
+ const { api, post } = fakeApi();
118
+ const rail = new CodeSparRail(api, { canonical: { any: "envelope" } as never, signature: "a".repeat(64) });
119
+ const { quote: _q, ...bare } = payment();
120
+ expect(await rail.pay(bare)).toMatchObject({ status: "failed", code: "quote_missing" });
121
+ expect(await rail.pay(payment({ amount_minor: 1 }))).toMatchObject({ status: "failed", code: "quote_mismatch" });
122
+ expect(await rail.pay(payment({ payee: MERCADO }))).toMatchObject({ status: "failed", code: "quote_mismatch" });
123
+ expect(post).not.toHaveBeenCalled();
124
+ });
125
+
126
+ it("the CodeSpar rail sends the quote on both spend routes", async () => {
127
+ const quote = payment().quote;
128
+ const byEnvelope = fakeApi();
129
+ await new CodeSparRail(byEnvelope.api, { canonical: { any: "envelope" } as never, signature: "a".repeat(64) }).pay(payment());
130
+ expect(byEnvelope.post.mock.calls[0]![0]).toBe("/v1/consumer-payments/execute");
131
+ expect(byEnvelope.post.mock.calls[0]![1].body["quote"]).toEqual(quote);
132
+ const byId = fakeApi();
133
+ await new CodeSparRail(byId.api).pay(payment());
134
+ expect(byId.post.mock.calls[0]![0]).toBe("/v1/consumers/mandates/{id}/spend");
135
+ expect(byId.post.mock.calls[0]![1].body["quote"]).toEqual(quote);
136
+ });
137
+
138
+ it("the stub rail makes the same refusal, so a scenario cannot pass without a quote", async () => {
139
+ const h = harness();
140
+ const { quote: _q, ...bare } = payment();
141
+ expect(await h.rail.pay(bare)).toMatchObject({ status: "failed", code: "quote_missing" });
142
+ expect(h.rail.payCount).toBe(0);
143
+ });
144
+ });
145
+
146
+ describe("§18: the receipt's payee is the sealed one, and a mismatch is an error", () => {
147
+ const sealed = (quote: unknown) => ({
148
+ receipt_id: "rcpt_1",
149
+ state: "paid",
150
+ mandate: { id: "cm_1", nonce: "n", scope: "s", currency: "BRL", sig: "x" },
151
+ quote,
152
+ payment: { rail: "pix-consent", provider: "pix", tx_id: "tx_1", amount_minor: 185000, amount_atomic: null, sandbox: true, attempt_id: "att_quote_0", money_moved: false, at: "2026-09-23T18:00:01.000Z" },
153
+ delivery: null,
154
+ chain: "c".repeat(64),
155
+ receipt_sig: "s",
156
+ exceptions: [],
157
+ });
158
+
159
+ it("the CodeSpar rail reads the payee from the sealed quote, and null when the receipt sealed none", async () => {
160
+ const withQuote = await new CodeSparRail(fakeApi(sealed({ seller: "Escola Aurora", resource: "outubro", price_minor: 185000, payee: ESCOLA, session_id: null, sig: "q", at: null })).api).receipt("rcpt_1", actor);
161
+ expect(withQuote?.payment.payee).toBe(ESCOLA);
162
+ const without = await new CodeSparRail(fakeApi(sealed(null)).api).receipt("rcpt_1", actor);
163
+ expect(without?.payment.payee).toBeNull();
164
+ });
165
+
166
+ it("a mixed-case key sealed as it was sent raises nothing; the same key sealed lower-cased is a mismatch", async () => {
167
+ const MIXED = "Financeiro@Escola-Aurora.example.com.br";
168
+ const mandate = { merchant_allowlist: [MIXED] as [string], beneficiaries: [{ alias: "escola", name: "Escola Aurora", payee: MIXED }] as [{ alias: string; name: string; payee: string }] };
169
+ const verbatim = harness({ mode: "human", mandate });
170
+ const d1 = await verbatim.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
171
+ if (!d1.ok) throw new Error("refused");
172
+ expect((await verbatim.engine.execute(verbatim.engine.approve(d1.execution.id, approver).id)).state).toBe("settled");
173
+ expect(verbatim.bundle.readEvents().some((e) => e["type"] === "receipt.seal_mismatch")).toBe(false);
174
+
175
+ // The API stores and returns quote.payee verbatim (OPEN_QUESTIONS §18); if that ever changes, this is what the kit says.
176
+ const lowered = harness({ mode: "human", mandate, wrapRail: (stub) => sealing(stub, MIXED.toLowerCase()) });
177
+ const d2 = await lowered.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
178
+ if (!d2.ok) throw new Error("refused");
179
+ await expect(lowered.engine.execute(lowered.engine.approve(d2.execution.id, approver).id)).rejects.toBeInstanceOf(ReceiptSealMismatchError);
180
+ });
181
+
182
+ for (const [label, sealedPayee] of [["a different payee", MERCADO], ["no payee at all (a spend the API sealed without a quote)", null]] as const) {
183
+ it(`a receipt sealing ${label} throws after the outcome is saved, and the bundle says why without the key in the clear`, async () => {
184
+ const h = harness({ mode: "human", wrapRail: (stub) => sealing(stub, sealedPayee) });
185
+ const d = await h.engine.draft({ items: [{ payee: "escola", amount: 185000 }] });
186
+ if (!d.ok) throw new Error("refused");
187
+ const approved = h.engine.approve(d.execution.id, approver);
188
+
189
+ await expect(h.engine.execute(approved.id)).rejects.toBeInstanceOf(ReceiptSealMismatchError);
190
+ // The money is recorded where the rail put it; the error is about the evidence.
191
+ expect(h.engine.get(approved.id)?.state).toBe("settled");
192
+ const mismatch = h.bundle.readEvents().find((e) => e["type"] === "receipt.seal_mismatch");
193
+ expect(mismatch?.["payload"]).toMatchObject({ sealed_payee: sealedPayee === null ? null : maskPayee(sealedPayee), paid_payee: maskPayee(ESCOLA) });
194
+ expect(JSON.stringify(mismatch)).not.toContain(ESCOLA);
195
+ if (sealedPayee) expect(JSON.stringify(mismatch)).not.toContain(sealedPayee);
196
+ });
197
+ }
198
+ });