@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
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Section 10: local state in `.codespar/state.db` (SQLite, via `node:sqlite`,
3
+ * so there is no native build step). Executions and their states, the
4
+ * append-only event log, the event cursor, the outbox with its
5
+ * `idempotency_key`, the approval artifacts, and two stub tables the
6
+ * sandbox stubs persist into so a killed process finds them again.
7
+ */
8
+ import { mkdirSync } from "node:fs";
9
+ import { dirname } from "node:path";
10
+ import { DatabaseSync } from "node:sqlite";
11
+ import type { Execution, ExecutionState } from "../state-machine.js";
12
+ import type { ApprovalArtifact } from "../types.js";
13
+
14
+ export interface EventRow {
15
+ seq: number;
16
+ run_id: string;
17
+ execution_id: string | null;
18
+ type: string;
19
+ payload: unknown;
20
+ at: string;
21
+ }
22
+
23
+ export type OutboxStatus = "pending" | "sent" | "done" | "failed";
24
+
25
+ export interface OutboxRow {
26
+ idempotency_key: string;
27
+ execution_id: string;
28
+ kind: string;
29
+ payload: unknown;
30
+ status: OutboxStatus;
31
+ response: unknown;
32
+ created_at: string;
33
+ updated_at: string;
34
+ }
35
+
36
+ const SCHEMA = `
37
+ CREATE TABLE IF NOT EXISTS executions (
38
+ id TEXT PRIMARY KEY,
39
+ run_id TEXT NOT NULL,
40
+ state TEXT NOT NULL,
41
+ mandate_id TEXT NOT NULL,
42
+ json TEXT NOT NULL,
43
+ created_at TEXT NOT NULL,
44
+ updated_at TEXT NOT NULL
45
+ );
46
+ CREATE INDEX IF NOT EXISTS executions_run ON executions(run_id);
47
+ CREATE INDEX IF NOT EXISTS executions_state ON executions(state);
48
+
49
+ CREATE TABLE IF NOT EXISTS events (
50
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ run_id TEXT NOT NULL,
52
+ execution_id TEXT,
53
+ event_id TEXT UNIQUE,
54
+ type TEXT NOT NULL,
55
+ payload TEXT NOT NULL,
56
+ at TEXT NOT NULL
57
+ );
58
+
59
+ CREATE TABLE IF NOT EXISTS cursors (
60
+ name TEXT PRIMARY KEY,
61
+ value TEXT NOT NULL
62
+ );
63
+
64
+ CREATE TABLE IF NOT EXISTS outbox (
65
+ idempotency_key TEXT PRIMARY KEY,
66
+ execution_id TEXT NOT NULL,
67
+ kind TEXT NOT NULL,
68
+ payload TEXT NOT NULL,
69
+ status TEXT NOT NULL,
70
+ response TEXT,
71
+ created_at TEXT NOT NULL,
72
+ updated_at TEXT NOT NULL
73
+ );
74
+
75
+ CREATE TABLE IF NOT EXISTS approvals (
76
+ approval_id TEXT PRIMARY KEY,
77
+ execution_id TEXT NOT NULL,
78
+ json TEXT NOT NULL,
79
+ created_at TEXT NOT NULL
80
+ );
81
+
82
+ CREATE TABLE IF NOT EXISTS stub_rail_attempts (
83
+ attempt_id TEXT PRIMARY KEY,
84
+ request TEXT NOT NULL,
85
+ outcome TEXT NOT NULL,
86
+ at TEXT NOT NULL
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS stub_mandate_status (
90
+ mandate_id TEXT PRIMARY KEY,
91
+ status TEXT NOT NULL,
92
+ reason TEXT,
93
+ updated_at TEXT NOT NULL
94
+ );
95
+
96
+ CREATE TABLE IF NOT EXISTS stub_org (
97
+ org_id TEXT PRIMARY KEY,
98
+ paused INTEGER NOT NULL DEFAULT 0,
99
+ updated_at TEXT NOT NULL
100
+ );
101
+ `;
102
+
103
+ export class StateStore {
104
+ private readonly db: DatabaseSync;
105
+
106
+ constructor(readonly path: string) {
107
+ if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
108
+ this.db = new DatabaseSync(path);
109
+ this.db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA foreign_keys = ON;");
110
+ this.db.exec(SCHEMA);
111
+ }
112
+
113
+ close(): void {
114
+ this.db.close();
115
+ }
116
+
117
+ transaction<T>(fn: () => T): T {
118
+ this.db.exec("BEGIN IMMEDIATE");
119
+ try {
120
+ const out = fn();
121
+ this.db.exec("COMMIT");
122
+ return out;
123
+ } catch (err) {
124
+ this.db.exec("ROLLBACK");
125
+ throw err;
126
+ }
127
+ }
128
+
129
+ // executions
130
+
131
+ saveExecution(execution: Execution): void {
132
+ this.db
133
+ .prepare(
134
+ `INSERT INTO executions (id, run_id, state, mandate_id, json, created_at, updated_at)
135
+ VALUES (?, ?, ?, ?, ?, ?, ?)
136
+ ON CONFLICT(id) DO UPDATE SET state = excluded.state, json = excluded.json, updated_at = excluded.updated_at`,
137
+ )
138
+ .run(execution.id, execution.run_id, execution.state, execution.mandate.id, JSON.stringify(execution), execution.created_at, execution.updated_at);
139
+ }
140
+
141
+ getExecution(id: string): Execution | undefined {
142
+ const row = this.db.prepare("SELECT json FROM executions WHERE id = ?").get(id) as { json: string } | undefined;
143
+ return row ? (JSON.parse(row.json) as Execution) : undefined;
144
+ }
145
+
146
+ listExecutions(filter: { run_id?: string; state?: ExecutionState | ExecutionState[]; mandate_id?: string } = {}): Execution[] {
147
+ const where: string[] = [];
148
+ const args: string[] = [];
149
+ if (filter.run_id) {
150
+ where.push("run_id = ?");
151
+ args.push(filter.run_id);
152
+ }
153
+ if (filter.mandate_id) {
154
+ where.push("mandate_id = ?");
155
+ args.push(filter.mandate_id);
156
+ }
157
+ if (filter.state) {
158
+ const states = Array.isArray(filter.state) ? filter.state : [filter.state];
159
+ where.push(`state IN (${states.map(() => "?").join(",")})`);
160
+ args.push(...states);
161
+ }
162
+ const sql = `SELECT json FROM executions ${where.length ? "WHERE " + where.join(" AND ") : ""} ORDER BY created_at, id`;
163
+ return (this.db.prepare(sql).all(...args) as { json: string }[]).map((r) => JSON.parse(r.json) as Execution);
164
+ }
165
+
166
+ // events
167
+
168
+ appendEvent(event: { run_id: string; execution_id?: string | null; event_id?: string; type: string; payload: unknown; at: string }): EventRow | undefined {
169
+ if (event.event_id) {
170
+ const dup = this.db.prepare("SELECT seq FROM events WHERE event_id = ?").get(event.event_id);
171
+ if (dup) return undefined;
172
+ }
173
+ const result = this.db
174
+ .prepare("INSERT INTO events (run_id, execution_id, event_id, type, payload, at) VALUES (?, ?, ?, ?, ?, ?)")
175
+ .run(event.run_id, event.execution_id ?? null, event.event_id ?? null, event.type, JSON.stringify(event.payload ?? null), event.at);
176
+ return { seq: Number(result.lastInsertRowid), run_id: event.run_id, execution_id: event.execution_id ?? null, type: event.type, payload: event.payload, at: event.at };
177
+ }
178
+
179
+ listEvents(filter: { run_id?: string; execution_id?: string; after_seq?: number } = {}): EventRow[] {
180
+ const where: string[] = [];
181
+ const args: (string | number)[] = [];
182
+ if (filter.run_id) {
183
+ where.push("run_id = ?");
184
+ args.push(filter.run_id);
185
+ }
186
+ if (filter.execution_id) {
187
+ where.push("execution_id = ?");
188
+ args.push(filter.execution_id);
189
+ }
190
+ if (filter.after_seq !== undefined) {
191
+ where.push("seq > ?");
192
+ args.push(filter.after_seq);
193
+ }
194
+ const sql = `SELECT seq, run_id, execution_id, type, payload, at FROM events ${where.length ? "WHERE " + where.join(" AND ") : ""} ORDER BY seq`;
195
+ return (this.db.prepare(sql).all(...args) as Array<Omit<EventRow, "payload"> & { payload: string }>).map((r) => ({ ...r, payload: JSON.parse(r.payload) }));
196
+ }
197
+
198
+ getCursor(name: string): string | undefined {
199
+ const row = this.db.prepare("SELECT value FROM cursors WHERE name = ?").get(name) as { value: string } | undefined;
200
+ return row?.value;
201
+ }
202
+
203
+ setCursor(name: string, value: string): void {
204
+ this.db.prepare("INSERT INTO cursors (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = excluded.value").run(name, value);
205
+ }
206
+
207
+ // outbox
208
+
209
+ putOutbox(row: Omit<OutboxRow, "updated_at">): void {
210
+ this.db
211
+ .prepare(
212
+ `INSERT INTO outbox (idempotency_key, execution_id, kind, payload, status, response, created_at, updated_at)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
214
+ ON CONFLICT(idempotency_key) DO NOTHING`,
215
+ )
216
+ .run(row.idempotency_key, row.execution_id, row.kind, JSON.stringify(row.payload), row.status, row.response === undefined ? null : JSON.stringify(row.response), row.created_at, row.created_at);
217
+ }
218
+
219
+ updateOutbox(idempotencyKey: string, status: OutboxStatus, response: unknown, at: string): void {
220
+ this.db.prepare("UPDATE outbox SET status = ?, response = ?, updated_at = ? WHERE idempotency_key = ?").run(status, response === undefined ? null : JSON.stringify(response), at, idempotencyKey);
221
+ }
222
+
223
+ getOutbox(idempotencyKey: string): OutboxRow | undefined {
224
+ const row = this.db.prepare("SELECT * FROM outbox WHERE idempotency_key = ?").get(idempotencyKey) as (Omit<OutboxRow, "payload" | "response"> & { payload: string; response: string | null }) | undefined;
225
+ return row ? { ...row, payload: JSON.parse(row.payload), response: row.response ? JSON.parse(row.response) : undefined } : undefined;
226
+ }
227
+
228
+ listOutbox(filter: { execution_id?: string; status?: OutboxStatus[] } = {}): OutboxRow[] {
229
+ const where: string[] = [];
230
+ const args: string[] = [];
231
+ if (filter.execution_id) {
232
+ where.push("execution_id = ?");
233
+ args.push(filter.execution_id);
234
+ }
235
+ if (filter.status) {
236
+ where.push(`status IN (${filter.status.map(() => "?").join(",")})`);
237
+ args.push(...filter.status);
238
+ }
239
+ const sql = `SELECT * FROM outbox ${where.length ? "WHERE " + where.join(" AND ") : ""} ORDER BY created_at, idempotency_key`;
240
+ return (this.db.prepare(sql).all(...args) as Array<Omit<OutboxRow, "payload" | "response"> & { payload: string; response: string | null }>).map((r) => ({
241
+ ...r,
242
+ payload: JSON.parse(r.payload),
243
+ response: r.response ? JSON.parse(r.response) : undefined,
244
+ }));
245
+ }
246
+
247
+ // approvals
248
+
249
+ saveApproval(artifact: ApprovalArtifact): void {
250
+ this.db.prepare("INSERT OR REPLACE INTO approvals (approval_id, execution_id, json, created_at) VALUES (?, ?, ?, ?)").run(artifact.approval_id, artifact.execution_id, JSON.stringify(artifact), artifact.approved_at);
251
+ }
252
+
253
+ getApproval(approvalId: string): ApprovalArtifact | undefined {
254
+ const row = this.db.prepare("SELECT json FROM approvals WHERE approval_id = ?").get(approvalId) as { json: string } | undefined;
255
+ return row ? (JSON.parse(row.json) as ApprovalArtifact) : undefined;
256
+ }
257
+
258
+ // stub rail (sandbox stand-in, persisted so a restart still sees the attempt)
259
+
260
+ stubRailGet(attemptId: string): { request: unknown; outcome: unknown; at: string } | undefined {
261
+ const row = this.db.prepare("SELECT request, outcome, at FROM stub_rail_attempts WHERE attempt_id = ?").get(attemptId) as { request: string; outcome: string; at: string } | undefined;
262
+ return row ? { request: JSON.parse(row.request), outcome: JSON.parse(row.outcome), at: row.at } : undefined;
263
+ }
264
+
265
+ stubRailFindByReceipt(receiptId: string): { request: unknown; outcome: unknown; at: string } | undefined {
266
+ const row = this.db.prepare("SELECT request, outcome, at FROM stub_rail_attempts WHERE json_extract(outcome, '$.receipt_id') = ?").get(receiptId) as { request: string; outcome: string; at: string } | undefined;
267
+ return row ? { request: JSON.parse(row.request), outcome: JSON.parse(row.outcome), at: row.at } : undefined;
268
+ }
269
+
270
+ stubRailFindByTransaction(transactionId: string): { request: unknown; outcome: unknown; at: string } | undefined {
271
+ const row = this.db.prepare("SELECT request, outcome, at FROM stub_rail_attempts WHERE json_extract(outcome, '$.transaction_id') = ?").get(transactionId) as { request: string; outcome: string; at: string } | undefined;
272
+ return row ? { request: JSON.parse(row.request), outcome: JSON.parse(row.outcome), at: row.at } : undefined;
273
+ }
274
+
275
+ stubRailPut(attemptId: string, request: unknown, outcome: unknown, at: string): boolean {
276
+ const result = this.db.prepare("INSERT OR IGNORE INTO stub_rail_attempts (attempt_id, request, outcome, at) VALUES (?, ?, ?, ?)").run(attemptId, JSON.stringify(request), JSON.stringify(outcome), at);
277
+ return result.changes === 1;
278
+ }
279
+
280
+ /** The stub receivable rail: an issued charge changes state as its payer acts, under the same attempt id. */
281
+ stubRailReplace(attemptId: string, request: unknown, outcome: unknown, at: string): void {
282
+ this.db
283
+ .prepare("INSERT INTO stub_rail_attempts (attempt_id, request, outcome, at) VALUES (?, ?, ?, ?) ON CONFLICT(attempt_id) DO UPDATE SET outcome = excluded.outcome, at = excluded.at")
284
+ .run(attemptId, JSON.stringify(request), JSON.stringify(outcome), at);
285
+ }
286
+
287
+ // stub mandate status / org pause (the local stand-in of stubs/mandate-status.ts, for runs without a key)
288
+
289
+ stubMandateStatus(mandateId: string): { status: string; reason: string | null } | undefined {
290
+ return this.db.prepare("SELECT status, reason FROM stub_mandate_status WHERE mandate_id = ?").get(mandateId) as { status: string; reason: string | null } | undefined;
291
+ }
292
+
293
+ stubSetMandateStatus(mandateId: string, status: string, reason: string | null, at: string): void {
294
+ this.db
295
+ .prepare("INSERT INTO stub_mandate_status (mandate_id, status, reason, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(mandate_id) DO UPDATE SET status = excluded.status, reason = excluded.reason, updated_at = excluded.updated_at")
296
+ .run(mandateId, status, reason, at);
297
+ }
298
+
299
+ stubOrgPaused(orgId: string): boolean {
300
+ const row = this.db.prepare("SELECT paused FROM stub_org WHERE org_id = ?").get(orgId) as { paused: number } | undefined;
301
+ return row?.paused === 1;
302
+ }
303
+
304
+ stubSetOrgPaused(orgId: string, paused: boolean, at: string): void {
305
+ this.db.prepare("INSERT INTO stub_org (org_id, paused, updated_at) VALUES (?, ?, ?) ON CONFLICT(org_id) DO UPDATE SET paused = excluded.paused, updated_at = excluded.updated_at").run(orgId, paused ? 1 : 0, at);
306
+ }
307
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Section 4.1: the execution as a closed state machine.
3
+ *
4
+ * The transition table is a type. `transition()` only accepts a target that
5
+ * the table lists for the current state, so a transition outside the table
6
+ * does not compile; the runtime check below refuses the same pair when the
7
+ * types were bypassed (a cast, a row read back from SQLite).
8
+ */
9
+ import type { Actor, ApprovalMode, EscalationTrigger, ExecutionBatch, ExecutionComposition, ExecutionItem, ExecutionReason, ItemOutcome, MandateRef } from "./types.js";
10
+
11
+ export type ExecutionState =
12
+ | "drafted"
13
+ | "awaiting_approval"
14
+ | "approved"
15
+ | "executing"
16
+ | "settled"
17
+ | "failed"
18
+ | "denied"
19
+ | "expired";
20
+
21
+ /**
22
+ * The table of section 4.1, plus the two exits section 4.7 adds (revocation
23
+ * from `drafted` / `approved`) and the return section 4.2 adds (`approved`
24
+ * back to `awaiting_approval` when the list changed after approval).
25
+ */
26
+ export type TransitionTable = {
27
+ drafted: "awaiting_approval" | "approved" | "denied" | "expired";
28
+ awaiting_approval: "approved" | "denied" | "expired";
29
+ approved: "executing" | "awaiting_approval" | "denied" | "expired";
30
+ executing: "settled" | "failed";
31
+ settled: never;
32
+ failed: never;
33
+ denied: never;
34
+ expired: never;
35
+ };
36
+
37
+ export type NextState<S extends ExecutionState> = TransitionTable[S];
38
+
39
+ export const TRANSITIONS: { readonly [S in ExecutionState]: readonly TransitionTable[S][] } = {
40
+ drafted: ["awaiting_approval", "approved", "denied", "expired"],
41
+ awaiting_approval: ["approved", "denied", "expired"],
42
+ approved: ["executing", "awaiting_approval", "denied", "expired"],
43
+ executing: ["settled", "failed"],
44
+ settled: [],
45
+ failed: [],
46
+ denied: [],
47
+ expired: [],
48
+ };
49
+
50
+ export const TERMINAL_STATES: readonly ExecutionState[] = ["settled", "failed", "denied", "expired"];
51
+
52
+ export function isTerminal(state: ExecutionState): boolean {
53
+ return TERMINAL_STATES.includes(state);
54
+ }
55
+
56
+ export interface TransitionRecord {
57
+ from: ExecutionState;
58
+ to: ExecutionState;
59
+ at: string;
60
+ reason?: ExecutionReason;
61
+ detail?: string;
62
+ actor: Actor;
63
+ }
64
+
65
+ export interface Execution<S extends ExecutionState = ExecutionState> {
66
+ id: string;
67
+ run_id: string;
68
+ state: S;
69
+ mode: ApprovalMode;
70
+ /** Who created the execution: always the agent acting for someone. */
71
+ actor: Actor;
72
+ items: ExecutionItem[];
73
+ /** Computed by the core, never by the model. */
74
+ total: number;
75
+ currency: string;
76
+ /** What the model claimed the total was, when it claimed one. Recorded, never used. */
77
+ model_claimed_total?: number;
78
+ items_hash: string;
79
+ /** Present when this execution is one line of a batch: the set it was presented inside, and where in it. */
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>;
90
+ mandate: MandateRef;
91
+ /** Written once, before the rail is called. Retrying reuses it. */
92
+ idempotency_key: string;
93
+ approval_id?: string;
94
+ escalation?: { trigger: EscalationTrigger; detail: string };
95
+ /** Reasons the core will refuse to approve, even if a human says yes. */
96
+ blocking_reasons: ExecutionReason[];
97
+ reason?: ExecutionReason;
98
+ detail?: string;
99
+ outcomes: ItemOutcome[];
100
+ history: TransitionRecord[];
101
+ created_at: string;
102
+ updated_at: string;
103
+ }
104
+
105
+ export class IllegalTransitionError extends Error {
106
+ constructor(
107
+ readonly executionId: string,
108
+ readonly from: ExecutionState,
109
+ readonly to: ExecutionState,
110
+ ) {
111
+ super(`illegal transition ${from} -> ${to} on ${executionId}`);
112
+ this.name = "IllegalTransitionError";
113
+ }
114
+ }
115
+
116
+ export function canTransition(from: ExecutionState, to: ExecutionState): boolean {
117
+ return (TRANSITIONS[from] as readonly ExecutionState[]).includes(to);
118
+ }
119
+
120
+ export interface TransitionInput {
121
+ at: string;
122
+ actor: Actor;
123
+ reason?: ExecutionReason;
124
+ detail?: string;
125
+ }
126
+
127
+ /**
128
+ * Move an execution to a state the table allows. Returns a new object with
129
+ * the transition appended to `history`; the caller persists it.
130
+ */
131
+ export function transition<S extends ExecutionState, T extends NextState<S>>(
132
+ execution: Execution<S>,
133
+ to: T,
134
+ input: TransitionInput,
135
+ ): Execution<T> {
136
+ if (!canTransition(execution.state, to)) {
137
+ throw new IllegalTransitionError(execution.id, execution.state, to);
138
+ }
139
+ const record: TransitionRecord = {
140
+ from: execution.state,
141
+ to,
142
+ at: input.at,
143
+ actor: input.actor,
144
+ ...(input.reason ? { reason: input.reason } : {}),
145
+ ...(input.detail ? { detail: input.detail } : {}),
146
+ };
147
+ // `reason`/`detail` describe the LATEST transition; a step without one clears what the previous step left.
148
+ const { reason: _reason, detail: _detail, ...rest } = execution;
149
+ return {
150
+ ...rest,
151
+ state: to,
152
+ ...(input.reason ? { reason: input.reason } : {}),
153
+ ...(input.detail ? { detail: input.detail } : {}),
154
+ history: [...execution.history, record],
155
+ updated_at: input.at,
156
+ } as Execution<T>;
157
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * STUB receivable rail. The deterministic stand-in for `POST /v1/charges`
3
+ * plus the sandbox payer, so the collections scenarios, the adversarial
4
+ * suite and the restart test run offline. Every issuance is persisted in
5
+ * state.db before the outcome is returned, so a process killed right after
6
+ * "the charge was issued" finds it again on `resume`.
7
+ *
8
+ * The fixture payer lives in the lookups: the first look after issuance
9
+ * finds the instrument registered (a cobranca com vencimento answers
10
+ * PROCESSING at create and PENDING once the clearing house has it), the
11
+ * second look finds what the payer did: paid (default), let it expire, or
12
+ * nothing yet. With a real key the scenario runner calls the sandbox pay
13
+ * route instead; this file never talks to the network.
14
+ */
15
+ import { sha256Hex } from "../hash.js";
16
+ import type { PaymentRail, RailLookup, RailOutcome, RailPayment, RailReceipt } from "../rail.js";
17
+ import type { StateStore } from "../state/store.js";
18
+ import type { Actor, ChargeInstrument } from "../types.js";
19
+
20
+ export type StubPayerBehaviour = "pays" | "expires" | "never";
21
+
22
+ export interface StubChargeRailOptions {
23
+ clock?: () => Date;
24
+ /** What the fixture payer does with a receivable, once it is payable. */
25
+ payer?: StubPayerBehaviour;
26
+ /** Debtor documents the issuer refuses (`failed` issuance), to script a partial failure. */
27
+ refusePayees?: string[];
28
+ /** Attempt ids whose create answers `uncertain` once (the issuer's answer was lost); the reservation still exists and a later look finds it. */
29
+ uncertainOnce?: string[];
30
+ /** Test hook: called after the issuance is persisted and before the outcome is returned. */
31
+ afterDispatch?: (attemptId: string) => void;
32
+ }
33
+
34
+ interface PayerState {
35
+ looks: number;
36
+ fate: StubPayerBehaviour;
37
+ }
38
+
39
+ const CURSOR = (attemptId: string) => `stub-charge-payer:${attemptId}`;
40
+
41
+ export class StubChargeRail implements PaymentRail {
42
+ readonly name = "stub-charge" as const;
43
+ private readonly clock: () => Date;
44
+ private fate: StubPayerBehaviour;
45
+ private readonly uncertainPending: Set<string>;
46
+ /** How many NEW receivables pay() issued. */
47
+ issueCount = 0;
48
+
49
+ constructor(
50
+ private readonly store: StateStore,
51
+ private readonly options: StubChargeRailOptions = {},
52
+ ) {
53
+ this.clock = options.clock ?? (() => new Date());
54
+ this.fate = options.payer ?? "pays";
55
+ this.uncertainPending = new Set(options.uncertainOnce ?? []);
56
+ }
57
+
58
+ /** Changes what the fixture payer does with the receivables issued from now on. */
59
+ setPayer(behaviour: StubPayerBehaviour): void {
60
+ this.fate = behaviour;
61
+ }
62
+
63
+ /** Scripts the payer for ONE already-issued receivable (a scenario that pays or expires a specific parcel). */
64
+ decide(attemptId: string, behaviour: StubPayerBehaviour): void {
65
+ const state = this.payerState(attemptId);
66
+ this.savePayerState(attemptId, { looks: state?.looks ?? 0, fate: behaviour });
67
+ }
68
+
69
+ async pay(payment: RailPayment): Promise<RailOutcome> {
70
+ const existing = this.store.stubRailGet(payment.attempt_id);
71
+ if (existing) return existing.outcome as RailOutcome;
72
+
73
+ if (this.uncertainPending.has(payment.attempt_id)) {
74
+ this.uncertainPending.delete(payment.attempt_id);
75
+ // The issuer took the create and the answer was lost: the reservation exists, so a later lookup finds it.
76
+ this.persistIssued(payment, "PROCESSING", false);
77
+ return { status: "uncertain", code: "issuance_unconfirmed", message: "stub: the issuer's answer to the create was lost; the key is reserved" };
78
+ }
79
+
80
+ const at = this.clock().toISOString();
81
+ if (this.options.refusePayees?.includes(payment.payee)) {
82
+ const outcome: RailOutcome = { status: "failed", code: "issuer_refused", message: `stub: issuer refused a charge to ${payment.payee}` };
83
+ const { actor: _actor, ...request } = payment;
84
+ this.store.stubRailPut(payment.attempt_id, request, outcome, at);
85
+ this.options.afterDispatch?.(payment.attempt_id);
86
+ return outcome;
87
+ }
88
+ const outcome = this.persistIssued(payment, "PROCESSING", false);
89
+ this.options.afterDispatch?.(payment.attempt_id);
90
+ return outcome;
91
+ }
92
+
93
+ async lookup(attemptId: string, payment: RailPayment): Promise<RailLookup> {
94
+ const recorded = this.store.stubRailGet(attemptId);
95
+ if (!recorded) return undefined;
96
+ const outcome = recorded.outcome as RailOutcome;
97
+ if (outcome.status !== "accepted") return outcome;
98
+
99
+ const state = this.payerState(attemptId) ?? { looks: 0, fate: this.fate };
100
+ const looks = state.looks + 1;
101
+ this.savePayerState(attemptId, { ...state, looks });
102
+
103
+ // First look: the clearing house registered the instrument. It is payable from here.
104
+ if (looks === 1 || !outcome.instrument.payable) {
105
+ return this.update(attemptId, payment, "PENDING", true);
106
+ }
107
+ if (state.fate === "pays") {
108
+ const settled: RailOutcome = {
109
+ status: "settled",
110
+ transaction_id: outcome.transaction_id,
111
+ receipt_id: outcome.transaction_id,
112
+ money_moved: false,
113
+ sandbox: true,
114
+ raw: { stub: true, attempt_id: attemptId, simulated: true, settled_against: "sandbox_fixture", paid_at: this.clock().toISOString() },
115
+ };
116
+ this.replace(attemptId, payment, settled);
117
+ return settled;
118
+ }
119
+ if (state.fate === "expires") {
120
+ const failed: RailOutcome = { status: "failed", code: "charge_expired", message: `stub: the receivable ${outcome.transaction_id} reached its due date unpaid`, raw: { stub: true } };
121
+ this.replace(attemptId, payment, failed);
122
+ return failed;
123
+ }
124
+ return outcome;
125
+ }
126
+
127
+ async receipt(receiptId: string, actor: Actor): Promise<RailReceipt | undefined> {
128
+ // The paid charge IS the record: found by its receipt id once the payer acted here, or by its charge id when the settlement came by event.
129
+ const sealed = this.store.stubRailFindByReceipt(receiptId) ?? this.store.stubRailFindByTransaction(receiptId);
130
+ if (!sealed) return undefined;
131
+ const req = sealed.request as Omit<RailPayment, "actor">;
132
+ const out = sealed.outcome as Extract<RailOutcome, { status: "settled" | "accepted" }>;
133
+ const raw = out.raw as { paid_at?: string };
134
+ return {
135
+ receipt_id: receiptId,
136
+ kind: "charge",
137
+ state: "paid",
138
+ mandate: { id: req.mandate_id },
139
+ payment: { amount_minor: req.amount_minor, payee: req.payee, attempt_id: req.attempt_id, money_moved: false, sandbox: true, at: raw.paid_at ?? sealed.at },
140
+ chain: null,
141
+ receipt_sig: null,
142
+ receipt_sig_ed25519: null,
143
+ receipt_sig_kid: null,
144
+ actor,
145
+ raw: { stub: true, charge_id: out.transaction_id, simulated: true, settled_against: "sandbox_fixture" },
146
+ };
147
+ }
148
+
149
+ private instrument(payment: RailPayment, status: string, payable: boolean, chargeId: string): ChargeInstrument {
150
+ return {
151
+ payable,
152
+ status,
153
+ pix_copy_paste: payable ? `00020126580014br.gov.bcb.pix0136stub-${chargeId}5204000053039865802BR5909CODESPAR6009SAO PAULO62070503***6304STUB` : null,
154
+ boleto_bank_line: payable ? `${sha256Hex(chargeId).replace(/[a-f]/g, "").padEnd(47, "0").slice(0, 47)}` : null,
155
+ boleto_bar_code: payable ? `${sha256Hex(`bar:${chargeId}`).replace(/[a-f]/g, "").padEnd(44, "0").slice(0, 44)}` : null,
156
+ due_date: payment.due_date ?? null,
157
+ };
158
+ }
159
+
160
+ private persistIssued(payment: RailPayment, status: string, payable: boolean): Extract<RailOutcome, { status: "accepted" }> {
161
+ const chargeId = `chg_stub_${sha256Hex(payment.attempt_id).slice(0, 16)}`;
162
+ const outcome: Extract<RailOutcome, { status: "accepted" }> = {
163
+ status: "accepted",
164
+ transaction_id: chargeId,
165
+ instrument: this.instrument(payment, status, payable, chargeId),
166
+ sandbox: true,
167
+ raw: { stub: true, attempt_id: payment.attempt_id, issued_at: this.clock().toISOString() },
168
+ };
169
+ const { actor: _actor, ...request } = payment;
170
+ if (this.store.stubRailPut(payment.attempt_id, request, outcome, this.clock().toISOString())) this.issueCount += 1;
171
+ return outcome;
172
+ }
173
+
174
+ private update(attemptId: string, payment: RailPayment, status: string, payable: boolean): Extract<RailOutcome, { status: "accepted" }> {
175
+ const recorded = this.store.stubRailGet(attemptId)!;
176
+ const prior = recorded.outcome as Extract<RailOutcome, { status: "accepted" }>;
177
+ const outcome = { ...prior, instrument: this.instrument(payment, status, payable, prior.transaction_id) };
178
+ this.replace(attemptId, payment, outcome);
179
+ return outcome;
180
+ }
181
+
182
+ private replace(attemptId: string, payment: RailPayment, outcome: RailOutcome): void {
183
+ const { actor: _actor, ...request } = payment;
184
+ this.store.stubRailReplace(attemptId, request, outcome, this.clock().toISOString());
185
+ }
186
+
187
+ private payerState(attemptId: string): PayerState | undefined {
188
+ const raw = this.store.getCursor(CURSOR(attemptId));
189
+ return raw ? (JSON.parse(raw) as PayerState) : undefined;
190
+ }
191
+
192
+ private savePayerState(attemptId: string, state: PayerState): void {
193
+ this.store.setCursor(CURSOR(attemptId), JSON.stringify(state));
194
+ }
195
+ }