@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,220 @@
1
+ /**
2
+ * The chain recomputed from a receipt read (ent#1670): the RFC 8785
3
+ * canonicalization, the published recipe as data, and the digest.
4
+ *
5
+ * Nothing here is checked against the module agreeing with itself. The
6
+ * canonicalization is held to the RFC's own examples; the recipe is the one
7
+ * production served on 2026-09-26 (`fixtures/production-receipt-keys.json`,
8
+ * fetched from `https://api.codespar.dev/.well-known/codespar-receipt-keys.json`;
9
+ * staging served the same recipe that day); and the chains are either a
10
+ * literal the enterprise computed with its own code before ent#1670 existed,
11
+ * or the digest of a link list written out by hand, field by field, the way
12
+ * the enterprise's own seal test writes it. The published recipe carries no
13
+ * worked example of its own, so those two are the external vectors.
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { describe, expect, it } from "vitest";
19
+ import { canonicalize, isMaskedPayee, readChainRecipe, readSealedApproval, recomputeChain, sameApprovalHash, type ChainRecipe } from "../src/receipt-chain.js";
20
+
21
+ const PUBLISHED = JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", "production-receipt-keys.json"), "utf8")) as Record<string, unknown>;
22
+
23
+ function recipe(document: unknown = PUBLISHED): ChainRecipe {
24
+ const read = readChainRecipe(document);
25
+ if (!read.ok) throw new Error(read.message);
26
+ return read.recipe;
27
+ }
28
+
29
+ const sha256 = (s: string) => createHash("sha256").update(s, "utf8").digest("hex");
30
+ const ITEMS = `sha256:${"1".repeat(64)}`;
31
+ const BATCH = `sha256:${"2".repeat(64)}`;
32
+ const MANDATE_SIG = "a".repeat(64);
33
+
34
+ /** A v4 read, shaped as `GET /v1/consumers/receipts/{id}` answers it: fields the recipe does not name are there too, and must be ignored. */
35
+ function v4Read(over: Record<string, unknown> = {}): Record<string, unknown> {
36
+ return {
37
+ receipt_id: "rcpt_V1StGXR8Z5jdHi6BmyT0sw",
38
+ state: "paid",
39
+ chain_version: 4,
40
+ mandate: { id: "cm_gWEZO68q", nonce: "nonce-123456789", scope: "food", currency: "BRL", sig: MANDATE_SIG, sig_sha256: sha256(MANDATE_SIG) },
41
+ quote: { seller: "Sushi Yassu", resource: "Combinado 20 pc", price_minor: 9980, payee: "yassu@pix.com.br", session_id: "chk_abc123", sig: "q".repeat(64), at: "2026-06-25T12:00:00.000Z" },
42
+ approval: { items_hash: ITEMS, batch_hash: BATCH },
43
+ payment: {
44
+ rail: "pix",
45
+ provider: "celcoin",
46
+ tx_id: "E1393589202606251200",
47
+ amount_minor: 9980,
48
+ amount_atomic: null,
49
+ amount_authorized: null,
50
+ amount_charged: null,
51
+ amount_refunded: null,
52
+ metering: null,
53
+ attempt_id: "cmexec_nonce-123456789",
54
+ money_moved: true,
55
+ at: "2026-06-25T12:00:05.000Z",
56
+ },
57
+ delivery: null,
58
+ chain: "0".repeat(64),
59
+ receipt_sig: "hmac-not-read-here",
60
+ exceptions: [],
61
+ ...over,
62
+ };
63
+ }
64
+
65
+ describe("RFC 8785", () => {
66
+ it("serializes the RFC's own example (section 3.2.4) byte for byte", () => {
67
+ // The input as JSON text, so the numbers go through a parser exactly as the RFC's do.
68
+ const input = JSON.parse(
69
+ '{"numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001], "string": "\\u20ac$\\u000F\\u000aA\'\\u0042\\u0022\\u005c\\\\\\"\\/", "literals": [null, true, false]}',
70
+ ) as unknown;
71
+ expect(canonicalize(input)).toBe('{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\\u000f\\nA\'B\\"\\\\\\\\\\"/"}');
72
+ });
73
+
74
+ it("sorts members by UTF-16 code units, the RFC's sorting example (section 3.2.3)", () => {
75
+ const input = JSON.parse('{"\\u20ac": "Euro Sign", "\\r": "Carriage Return", "\\ufb33": "Hebrew Letter Dalet With Dagesh", "1": "One", "\\ud83d\\ude00": "Emoji: Grinning Face", "\\u0080": "Control", "\\u00f6": "Latin Small Letter O With Diaeresis"}') as Record<string, string>;
76
+ const order = [...canonicalize(input).matchAll(/"([^"]+)":"([^"]+)"/g)].map((m) => m[2]);
77
+ expect(order).toEqual(["Carriage Return", "One", "Control", "Latin Small Letter O With Diaeresis", "Euro Sign", "Emoji: Grinning Face", "Hebrew Letter Dalet With Dagesh"]);
78
+ });
79
+
80
+ it("serializes numbers as the RFC's Appendix B lists them", () => {
81
+ const cases: Array<[string, string]> = [
82
+ ["0000000000000000", "0"],
83
+ ["8000000000000000", "0"],
84
+ ["0000000000000001", "5e-324"],
85
+ ["7fefffffffffffff", "1.7976931348623157e+308"],
86
+ ["4340000000000000", "9007199254740992"],
87
+ ["4430000000000000", "295147905179352830000"],
88
+ ["44b52d02c7e14af6", "1e+23"],
89
+ ["444b1ae4d6e2ef50", "1e+21"],
90
+ ["3eb0c6f7a0b5ed8d", "0.000001"],
91
+ ["41b3de4355555555", "333333333.3333333"],
92
+ ["becbf647612f3696", "-0.0000033333333333333333"],
93
+ ["43143ff3c1cb0959", "1424953923781206.2"],
94
+ ];
95
+ for (const [ieee, text] of cases) expect(canonicalize(Buffer.from(ieee, "hex").readDoubleBE(0))).toBe(text);
96
+ });
97
+
98
+ it("refuses what is not I-JSON rather than hashing something nobody can reproduce", () => {
99
+ expect(() => canonicalize(Number.NaN)).toThrow(/not a JSON number/);
100
+ expect(() => canonicalize(Number.POSITIVE_INFINITY)).toThrow(/not a JSON number/);
101
+ expect(() => canonicalize("\ud800")).toThrow(/lone surrogate/);
102
+ expect(() => canonicalize({ ["\udc00"]: 1 })).toThrow(/lone surrogate/);
103
+ expect(() => canonicalize(undefined)).toThrow(/not a JSON value/);
104
+ expect(() => canonicalize({ a: undefined })).toThrow(/not a JSON value/);
105
+ expect(canonicalize("😀")).toBe('"😀"');
106
+ });
107
+ });
108
+
109
+ describe("the published recipe is read as data", () => {
110
+ it("reads the document production serves", () => {
111
+ const r = recipe();
112
+ expect(r.canonicalization).toBe("RFC 8785 (JCS)");
113
+ expect(r.links.map((l) => l.link)).toEqual(["mandate", "quote", "approval", "payment", "delivery"]);
114
+ expect(r.links[0]!.fields_by_version?.["4"]).toContain("sig_sha256");
115
+ });
116
+
117
+ it("names a document with no recipe for what it is: a deployment older than ent#1670", () => {
118
+ const { chain_recipe: _r, ...older } = PUBLISHED;
119
+ expect(readChainRecipe(older)).toMatchObject({ ok: false, reason: "recipe_unpublished" });
120
+ });
121
+
122
+ it("refuses a recipe it would only half follow", () => {
123
+ const base = PUBLISHED["chain_recipe"] as Record<string, unknown>;
124
+ expect(readChainRecipe({ chain_recipe: { ...base, canonicalization: "sorted keys" } })).toMatchObject({ ok: false, reason: "recipe_unrecognized" });
125
+ expect(readChainRecipe({ chain_recipe: { ...base, digest: "SHA-512" } })).toMatchObject({ ok: false, reason: "recipe_unrecognized" });
126
+ expect(readChainRecipe({ chain_recipe: { ...base, links: [] } })).toMatchObject({ ok: false, reason: "recipe_unrecognized" });
127
+ });
128
+ });
129
+
130
+ describe("the chain, recomputed from the read", () => {
131
+ it("reproduces a chain the enterprise's own code computed before ent#1670 (v2, pinned in its receipt-approval-seal test)", () => {
132
+ const read = {
133
+ chain_version: 2,
134
+ mandate: { id: "cm_gWEZO68q", nonce: "nonce-123456789", scope: "food", currency: "BRL", sig: MANDATE_SIG },
135
+ quote: null,
136
+ payment: { rail: "usdc-onchain", provider: "cdp", tx_id: "0xabc", amount_minor: 1, amount_atomic: "10000", amount_authorized: null, amount_charged: null, amount_refunded: null, metering: null, sandbox: true, attempt_id: "cmexec_nonce-123456789", money_moved: true, at: "2026-06-25T12:00:05.000Z" },
137
+ delivery: null,
138
+ };
139
+ expect(recomputeChain(read, recipe())).toEqual({ ok: true, version: 2, chain: "3519728810c899d3b85de0edd0dad1f9991c1bf659ce98597d099b09400df26b" });
140
+ });
141
+
142
+ it("v4 is the digest of {v:4, links:[mandate, quote, approval, payment]} with sig_sha256 in place of the signature", () => {
143
+ const read = v4Read();
144
+ const byHand = sha256(
145
+ canonicalize({
146
+ v: 4,
147
+ links: [
148
+ { id: "cm_gWEZO68q", nonce: "nonce-123456789", scope: "food", currency: "BRL", sig_sha256: sha256(MANDATE_SIG) },
149
+ { seller: "Sushi Yassu", resource: "Combinado 20 pc", price_minor: 9980, payee: "yassu@pix.com.br", session_id: "chk_abc123", sig: "q".repeat(64), at: "2026-06-25T12:00:00.000Z" },
150
+ { items_hash: ITEMS, batch_hash: BATCH },
151
+ { rail: "pix", provider: "celcoin", tx_id: "E1393589202606251200", amount_minor: 9980, attempt_id: "cmexec_nonce-123456789", money_moved: true, at: "2026-06-25T12:00:05.000Z" },
152
+ ],
153
+ }),
154
+ );
155
+ expect(recomputeChain(read, recipe())).toEqual({ ok: true, version: 4, chain: byHand });
156
+ });
157
+
158
+ it("a v4 read recomputes without the mandate's signature, which a third party must never be handed", () => {
159
+ const read = v4Read();
160
+ const { sig: _sig, ...mandate } = read["mandate"] as Record<string, unknown>;
161
+ expect(recomputeChain({ ...read, mandate }, recipe())).toEqual(recomputeChain(read, recipe()));
162
+ });
163
+
164
+ it("every field it seals changes it: payee, amount, approval, batch, a timestamp", () => {
165
+ const original = recomputeChain(v4Read(), recipe());
166
+ const q = v4Read()["quote"] as Record<string, unknown>;
167
+ const p = v4Read()["payment"] as Record<string, unknown>;
168
+ for (const altered of [
169
+ v4Read({ quote: { ...q, payee: "outra@pix.com.br" } }),
170
+ v4Read({ payment: { ...p, amount_minor: 9981 } }),
171
+ v4Read({ approval: { items_hash: `sha256:${"3".repeat(64)}`, batch_hash: BATCH } }),
172
+ v4Read({ approval: { items_hash: ITEMS, batch_hash: null } }),
173
+ v4Read({ payment: { ...p, at: "2026-06-25T12:00:05Z" } }),
174
+ ]) {
175
+ const r = recomputeChain(altered, recipe());
176
+ expect(r.ok).toBe(true);
177
+ expect(r.ok && original.ok && r.chain !== original.chain).toBe(true);
178
+ }
179
+ });
180
+
181
+ it("follows the order the document publishes, not one of its own", () => {
182
+ const base = PUBLISHED["chain_recipe"] as { links: unknown[] };
183
+ const [mandate, quote, approval, payment, delivery] = base.links;
184
+ const reordered = recipe({ chain_recipe: { ...base, links: [mandate, approval, quote, payment, delivery] } });
185
+ const a = recomputeChain(v4Read(), recipe());
186
+ const b = recomputeChain(v4Read(), reordered);
187
+ expect(a.ok && b.ok && a.chain !== b.chain).toBe(true);
188
+ });
189
+
190
+ it("answers why it cannot, and never guesses", () => {
191
+ const { sig: _sig, ...v2Mandate } = v4Read()["mandate"] as Record<string, unknown>;
192
+ expect(recomputeChain(v4Read({ chain_version: 2, approval: undefined, mandate: v2Mandate }), recipe())).toMatchObject({ ok: false, reason: "mandate_sig_required", version: 2 });
193
+ expect(recomputeChain(v4Read({ quote: { ...(v4Read()["quote"] as object), payee: "yu***@pix.com.br" } }), recipe())).toMatchObject({ ok: false, reason: "payee_masked" });
194
+ expect(recomputeChain(v4Read({ chain_version: undefined }), recipe())).toMatchObject({ ok: false, reason: "chain_version_missing" });
195
+ expect(recomputeChain(v4Read({ approval: null }), recipe())).toMatchObject({ ok: false, reason: "field_missing" });
196
+ const base = PUBLISHED["chain_recipe"] as { links: Array<Record<string, unknown>> };
197
+ const strange = recipe({ chain_recipe: { ...base, links: base.links.map((l) => (l["link"] === "delivery" ? { ...l, when: "the moon is full" } : l)) } });
198
+ expect(recomputeChain(v4Read(), strange)).toMatchObject({ ok: false, reason: "recipe_unrecognized" });
199
+ });
200
+ });
201
+
202
+ describe("the approval link", () => {
203
+ it("is read off the receipt, batch_hash null when the spend sent none", () => {
204
+ expect(readSealedApproval(v4Read())).toEqual({ items_hash: ITEMS, batch_hash: BATCH });
205
+ expect(readSealedApproval(v4Read({ approval: { items_hash: ITEMS, batch_hash: null } }))).toEqual({ items_hash: ITEMS, batch_hash: null });
206
+ expect(readSealedApproval(v4Read({ approval: undefined }))).toBeNull();
207
+ });
208
+
209
+ it("compares the two spellings of one hash as one hash, and nothing else as equal", () => {
210
+ expect(sameApprovalHash(ITEMS, "1".repeat(64))).toBe(true);
211
+ expect(sameApprovalHash(null, null)).toBe(true);
212
+ expect(sameApprovalHash(ITEMS, BATCH)).toBe(false);
213
+ expect(sameApprovalHash(null, BATCH)).toBe(false);
214
+ });
215
+
216
+ it("knows a masked payee when it sees one", () => {
217
+ expect(isMaskedPayee("es***@exemplo.com.br")).toBe(true);
218
+ expect(isMaskedPayee("escola@exemplo.com.br")).toBe(false);
219
+ });
220
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * `verifyReceiptRead`: the signature, then the body, then the approval
3
+ * (OPEN_QUESTIONS §3, §47). Every case is one that must be able to fail, and
4
+ * the ones that matter most are the ones that must NOT say `verified`: a v1–v3
5
+ * chain, a masked payee, a copy without its read, a document without a recipe.
6
+ *
7
+ * The chain a case is signed over is computed here by hand — `canonicalize`
8
+ * over the link list written out field by field, the RFC 8785 half of which is
9
+ * held to the RFC's examples in `receipt-chain.test.ts` — and signed with
10
+ * stock `node:crypto` and a key generated in the test, never with the module
11
+ * under test. The recipe is the one production published on 2026-09-26.
12
+ */
13
+ import { createHash, generateKeyPairSync, sign as signDetached, type KeyObject } from "node:crypto";
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { describe, expect, it } from "vitest";
17
+ import { itemsHash } from "../src/hash.js";
18
+ import { canonicalize } from "../src/receipt-chain.js";
19
+ import { VERDICT_EXIT_CODES, isReceiptRead, verifyReceiptRead, type ApprovalClaim } from "../src/receipt-verification.js";
20
+ import type { ExecutionItem } from "../src/types.js";
21
+
22
+ const PUBLISHED = JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", "production-receipt-keys.json"), "utf8")) as Record<string, unknown>;
23
+ const KID = "did:web:id.codespar.dev#production-2";
24
+ const RECEIPT_ID = "rcpt_V1StGXR8Z5jdHi6BmyT0sw";
25
+ const MANDATE_SIG = "a".repeat(64);
26
+ const sha256 = (s: string) => createHash("sha256").update(s, "utf8").digest("hex");
27
+
28
+ const ITEMS: ExecutionItem[] = [{ alias: "escola", beneficiary: "Escola Aurora", payee: "escola@exemplo.com.br", amount: 185000, currency: "BRL", description: "outubro" }];
29
+ const ITEMS_HASH = itemsHash(ITEMS);
30
+ const BATCH_HASH = `sha256:${"2".repeat(64)}`;
31
+
32
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
33
+ const X = Buffer.from(publicKey.export({ format: "der", type: "spki" }).subarray(-32)).toString("base64url");
34
+
35
+ /** The production document, with this test's public key under the production kid in place of CodeSpar's. */
36
+ function keyDocument(over: Record<string, unknown> = {}): Record<string, unknown> {
37
+ return { ...PUBLISHED, keys: [{ kid: KID, kty: "OKP", crv: "Ed25519", x: X, alg: "EdDSA", use: "sig", status: "active" }], ...over };
38
+ }
39
+
40
+ function body(version: number, approval: { items_hash: string; batch_hash: string | null } | null, over: { payee?: string; amount?: number } = {}) {
41
+ const payee = over.payee ?? "escola@exemplo.com.br";
42
+ const amount = over.amount ?? 185000;
43
+ return {
44
+ mandate: { id: "cm_test_1", nonce: "nonce-1", scope: "contas do mes", currency: "BRL", sig: MANDATE_SIG, sig_sha256: sha256(MANDATE_SIG) },
45
+ quote: { seller: "Escola Aurora", resource: "outubro", price_minor: amount, payee, session_id: null, sig: "q".repeat(64), at: "2026-09-26T18:00:00.000Z" },
46
+ payment: { rail: "pix-consent", provider: "pix", tx_id: "tx_1", amount_minor: amount, amount_atomic: null, sandbox: true, attempt_id: "att_1_0", money_moved: false, at: "2026-09-26T18:00:01.000Z" },
47
+ ...(version === 4 ? { approval } : {}),
48
+ };
49
+ }
50
+
51
+ /** The chain written out link by link, the way the recipe says and not the way the module under test builds it. */
52
+ function chainOf(version: number, b: ReturnType<typeof body>): string {
53
+ const m = b.mandate;
54
+ const q = b.quote;
55
+ const p = b.payment;
56
+ const links: unknown[] = [
57
+ version === 4 ? { id: m.id, nonce: m.nonce, scope: m.scope, currency: m.currency, sig_sha256: m.sig_sha256 } : { id: m.id, nonce: m.nonce, scope: m.scope, currency: m.currency, sig: m.sig },
58
+ { seller: q.seller, resource: q.resource, price_minor: q.price_minor, payee: q.payee, session_id: q.session_id, sig: q.sig, at: q.at },
59
+ ...(version === 4 ? [b.approval] : []),
60
+ { rail: p.rail, provider: p.provider, tx_id: p.tx_id, amount_minor: p.amount_minor, sandbox: true, attempt_id: p.attempt_id, money_moved: p.money_moved, at: p.at },
61
+ ];
62
+ return sha256(canonicalize({ v: version, links }));
63
+ }
64
+
65
+ function sealed(version: number, approval: { items_hash: string; batch_hash: string | null } | null = { items_hash: ITEMS_HASH, batch_hash: null }, key: KeyObject = privateKey) {
66
+ const b = body(version, approval);
67
+ const chain = chainOf(version, b);
68
+ return {
69
+ receipt_id: RECEIPT_ID,
70
+ state: "paid",
71
+ chain_version: version,
72
+ ...b,
73
+ delivery: null,
74
+ chain,
75
+ receipt_sig: "hmac-not-read-here",
76
+ receipt_sig_ed25519: signDetached(null, Buffer.from(`codespar-receipt:v1:${RECEIPT_ID}:${chain}`, "utf8"), key).toString("base64url"),
77
+ receipt_sig_kid: KID,
78
+ exceptions: [],
79
+ } as Record<string, unknown>;
80
+ }
81
+
82
+ const artifact = (over: Partial<ApprovalClaim> = {}): ApprovalClaim => ({ approval_id: "apr_1", items_hash: ITEMS_HASH, items: ITEMS, ...over });
83
+
84
+ describe("a v4 receipt: signature, body and approval", () => {
85
+ it("is verified when all three hold, and says which list it was paid against", () => {
86
+ const report = verifyReceiptRead(sealed(4), keyDocument(), { approval: artifact() });
87
+ expect(report.verdict).toBe("verified");
88
+ expect(report.chain_check).toMatchObject({ status: "recomputed", version: 4 });
89
+ expect(report.approval_check).toMatchObject({ status: "matched", sealed: { items_hash: ITEMS_HASH, batch_hash: null }, artifact: { approval_id: "apr_1" } });
90
+ expect(report.message).toContain(ITEMS_HASH);
91
+ expect(report.message).toContain("WHO approved it is in the artifact");
92
+ });
93
+
94
+ it("verifies without the mandate's signature on the read: a third party is never handed it", () => {
95
+ const read = sealed(4);
96
+ const { sig: _sig, ...mandate } = read["mandate"] as Record<string, unknown>;
97
+ expect(verifyReceiptRead({ ...read, mandate }, keyDocument(), { approval: artifact() }).verdict).toBe("verified");
98
+ });
99
+
100
+ it("is approval_mismatch when the sealed items_hash is not the artifact's", () => {
101
+ const other = `sha256:${"9".repeat(64)}`;
102
+ const report = verifyReceiptRead(sealed(4, { items_hash: other, batch_hash: null }), keyDocument(), { approval: artifact() });
103
+ expect(report.verdict).toBe("approval_mismatch");
104
+ expect(report.reason).toBe("items_hash_differs");
105
+ expect(report.approval_check).toMatchObject({ status: "mismatch", sealed: { items_hash: other } });
106
+ expect(VERDICT_EXIT_CODES[report.verdict]).toBe(9);
107
+ });
108
+
109
+ it("is approval_mismatch when the batch differs, in either direction", () => {
110
+ expect(verifyReceiptRead(sealed(4, { items_hash: ITEMS_HASH, batch_hash: BATCH_HASH }), keyDocument(), { approval: artifact() })).toMatchObject({ verdict: "approval_mismatch", reason: "batch_hash_differs" });
111
+ expect(verifyReceiptRead(sealed(4), keyDocument(), { approval: artifact({ batch: { batch_hash: BATCH_HASH } }) })).toMatchObject({ verdict: "approval_mismatch", reason: "batch_hash_differs" });
112
+ expect(verifyReceiptRead(sealed(4, { items_hash: ITEMS_HASH, batch_hash: BATCH_HASH }), keyDocument(), { approval: artifact({ batch: { batch_hash: BATCH_HASH } }) }).verdict).toBe("verified");
113
+ });
114
+
115
+ it("is approval_mismatch when the artifact's items were edited under its hash: a hash that does not describe its list proves nothing", () => {
116
+ const edited = [{ ...ITEMS[0]!, payee: "outra@exemplo.com.br" }];
117
+ expect(verifyReceiptRead(sealed(4), keyDocument(), { approval: artifact({ items: edited }) })).toMatchObject({ verdict: "approval_mismatch", reason: "artifact_items_hash_inconsistent" });
118
+ });
119
+
120
+ it("is verified, approval not compared, when no artifact is given — and prints the sealed hash to compare by hand", () => {
121
+ const report = verifyReceiptRead(sealed(4), keyDocument());
122
+ expect(report.verdict).toBe("verified");
123
+ expect(report.approval_check).toMatchObject({ status: "not_compared", artifact: null });
124
+ expect(report.message).toContain(ITEMS_HASH);
125
+ });
126
+
127
+ it("is chain_mismatch when the body beside a genuine signature was changed: payee, amount, approval", () => {
128
+ const read = sealed(4);
129
+ const q = read["quote"] as Record<string, unknown>;
130
+ const p = read["payment"] as Record<string, unknown>;
131
+ for (const altered of [
132
+ { ...read, quote: { ...q, payee: "outra@exemplo.com.br" } },
133
+ { ...read, payment: { ...p, amount_minor: 1 } },
134
+ { ...read, approval: { items_hash: `sha256:${"9".repeat(64)}`, batch_hash: null } },
135
+ ]) {
136
+ const report = verifyReceiptRead(altered, keyDocument(), { approval: artifact() });
137
+ expect(report.verdict).toBe("chain_mismatch");
138
+ expect(report.chain_check).toMatchObject({ status: "mismatch", version: 4 });
139
+ expect(VERDICT_EXIT_CODES[report.verdict]).toBe(8);
140
+ }
141
+ });
142
+
143
+ it("recomputes under the recipe of the document it was given: a different published order is a different chain", () => {
144
+ const base = PUBLISHED["chain_recipe"] as { links: unknown[] };
145
+ const [mandate, quote, approval, payment, delivery] = base.links;
146
+ const reordered = keyDocument({ chain_recipe: { ...base, links: [mandate, approval, quote, payment, delivery] } });
147
+ expect(verifyReceiptRead(sealed(4), reordered).verdict).toBe("chain_mismatch");
148
+ });
149
+
150
+ it("stops at the signature: a body check never runs on a receipt whose signature failed", () => {
151
+ const other = generateKeyPairSync("ed25519").privateKey;
152
+ const report = verifyReceiptRead(sealed(4, undefined, other), keyDocument(), { approval: artifact() });
153
+ expect(report.verdict).toBe("tampered");
154
+ expect(report.chain_check).toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe("what is never `verified`", () => {
159
+ for (const version of [1, 2, 3]) {
160
+ it(`a v${version} receipt: the chain is not recomputable without the mandate signature, even from a read that carries it`, () => {
161
+ const report = verifyReceiptRead(sealed(version, null), keyDocument(), { approval: artifact() });
162
+ expect(report.verdict).toBe("signature_only");
163
+ expect(report.reason).toBe("mandate_sig_required");
164
+ expect(report.message).toContain("not recomputable without the mandate signature");
165
+ expect(report.chain_check).toMatchObject({ status: "not_recomputed", version });
166
+ expect(report.approval_check).toBeUndefined();
167
+ });
168
+ }
169
+
170
+ it("a v2 receipt whose body was edited is signature_only too, never chain_mismatch: a pre-ent#1670 read may not recompute, and a mismatch there would accuse a genuine receipt", () => {
171
+ const read = sealed(2, null);
172
+ expect(verifyReceiptRead({ ...read, payment: { ...(read["payment"] as object), amount_minor: 1 } }, keyDocument()).verdict).toBe("signature_only");
173
+ });
174
+
175
+ it("a read with a masked payee", () => {
176
+ const read = sealed(4);
177
+ const report = verifyReceiptRead({ ...read, quote: { ...(read["quote"] as object), payee: "es***@exemplo.com.br" } }, keyDocument(), { approval: artifact() });
178
+ expect(report).toMatchObject({ verdict: "signature_only", reason: "payee_masked" });
179
+ });
180
+
181
+ it("the proof bundle's copy, which carries none of the links", () => {
182
+ const read = sealed(4);
183
+ const copy = { receipt_id: RECEIPT_ID, state: "paid", mandate: { id: "cm_test_1" }, payment: { amount_minor: 185000, payee: "es***@exemplo.com.br", attempt_id: "att_1_0", money_moved: false, sandbox: true, at: "2026-09-26T18:00:01.000Z" }, chain: read["chain"], receipt_sig: "x", receipt_sig_ed25519: read["receipt_sig_ed25519"], receipt_sig_kid: KID };
184
+ expect(isReceiptRead(copy)).toBe(false);
185
+ expect(verifyReceiptRead(copy, keyDocument())).toMatchObject({ verdict: "signature_only", reason: "read_required" });
186
+ });
187
+
188
+ it("a key set from a deployment that publishes no recipe", () => {
189
+ const { chain_recipe: _r, ...older } = keyDocument();
190
+ expect(verifyReceiptRead(sealed(4), older, { approval: artifact() })).toMatchObject({ verdict: "signature_only", reason: "recipe_unpublished" });
191
+ });
192
+
193
+ it("keeps every verdict's exit code apart", () => {
194
+ expect(new Set(Object.values(VERDICT_EXIT_CODES)).size).toBe(Object.keys(VERDICT_EXIT_CODES).length);
195
+ expect(VERDICT_EXIT_CODES.signature_only).not.toBe(0);
196
+ });
197
+ });