@emilia-protocol/gate 0.11.0 → 0.14.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 (425) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +310 -0
  3. package/action-control-manifest.js +3 -268
  4. package/action-escrow-custodian.js +3 -394
  5. package/action-escrow-evidence.js +3 -858
  6. package/action-escrow-package.js +3 -1668
  7. package/action-escrow-postgres.js +3 -337
  8. package/action-escrow-state.js +3 -396
  9. package/action-escrow-verifiers.js +3 -331
  10. package/action-escrow.js +3 -2447
  11. package/action-packs.js +3 -140
  12. package/adapters/_kit.js +3 -104
  13. package/adapters/aws.js +3 -108
  14. package/adapters/cloudflare.js +3 -54
  15. package/adapters/gcp.js +3 -70
  16. package/adapters/github.js +3 -109
  17. package/adapters/jira.js +3 -55
  18. package/adapters/k8s.js +3 -70
  19. package/adapters/linear.js +3 -55
  20. package/adapters/salesforce.js +3 -55
  21. package/adapters/stripe.js +3 -81
  22. package/adapters/supabase.js +3 -121
  23. package/adapters/terraform.js +3 -59
  24. package/adapters/vercel.js +3 -84
  25. package/aec-execution.js +3 -312
  26. package/breakglass.js +3 -582
  27. package/capability-receipt.js +4 -0
  28. package/cf1-conformance.js +3 -106
  29. package/challenge-store.js +3 -69
  30. package/control-plane.js +3 -340
  31. package/coverage.js +3 -721
  32. package/demo.mjs +1 -1
  33. package/deployment-attestation.js +3 -247
  34. package/dist/action-control-manifest.d.ts +82 -0
  35. package/dist/action-control-manifest.d.ts.map +1 -0
  36. package/dist/action-control-manifest.js +422 -0
  37. package/dist/action-control-manifest.js.map +1 -0
  38. package/dist/action-escrow-custodian.d.ts +74 -0
  39. package/dist/action-escrow-custodian.d.ts.map +1 -0
  40. package/dist/action-escrow-custodian.js +363 -0
  41. package/dist/action-escrow-custodian.js.map +1 -0
  42. package/dist/action-escrow-evidence.d.ts +130 -0
  43. package/dist/action-escrow-evidence.d.ts.map +1 -0
  44. package/dist/action-escrow-evidence.js +1012 -0
  45. package/dist/action-escrow-evidence.js.map +1 -0
  46. package/dist/action-escrow-package.d.ts +10 -0
  47. package/dist/action-escrow-package.d.ts.map +1 -0
  48. package/dist/action-escrow-package.js +1583 -0
  49. package/dist/action-escrow-package.js.map +1 -0
  50. package/dist/action-escrow-postgres.d.ts +62 -0
  51. package/dist/action-escrow-postgres.d.ts.map +1 -0
  52. package/dist/action-escrow-postgres.js +339 -0
  53. package/dist/action-escrow-postgres.js.map +1 -0
  54. package/dist/action-escrow-state.d.ts +141 -0
  55. package/dist/action-escrow-state.d.ts.map +1 -0
  56. package/dist/action-escrow-state.js +413 -0
  57. package/dist/action-escrow-state.js.map +1 -0
  58. package/dist/action-escrow-verifiers.d.ts +65 -0
  59. package/dist/action-escrow-verifiers.d.ts.map +1 -0
  60. package/dist/action-escrow-verifiers.js +421 -0
  61. package/dist/action-escrow-verifiers.js.map +1 -0
  62. package/dist/action-escrow.d.ts +112 -0
  63. package/dist/action-escrow.d.ts.map +1 -0
  64. package/dist/action-escrow.js +2661 -0
  65. package/dist/action-escrow.js.map +1 -0
  66. package/dist/action-packs.d.ts +54 -0
  67. package/dist/action-packs.d.ts.map +1 -0
  68. package/dist/action-packs.js +143 -0
  69. package/dist/action-packs.js.map +1 -0
  70. package/dist/adapters/_kit.d.ts +50 -0
  71. package/dist/adapters/_kit.d.ts.map +1 -0
  72. package/dist/adapters/_kit.js +98 -0
  73. package/dist/adapters/_kit.js.map +1 -0
  74. package/dist/adapters/aws.d.ts +158 -0
  75. package/dist/adapters/aws.d.ts.map +1 -0
  76. package/dist/adapters/aws.js +136 -0
  77. package/dist/adapters/aws.js.map +1 -0
  78. package/dist/adapters/cloudflare.d.ts +116 -0
  79. package/dist/adapters/cloudflare.d.ts.map +1 -0
  80. package/dist/adapters/cloudflare.js +75 -0
  81. package/dist/adapters/cloudflare.js.map +1 -0
  82. package/dist/adapters/gcp.d.ts +139 -0
  83. package/dist/adapters/gcp.d.ts.map +1 -0
  84. package/dist/adapters/gcp.js +69 -0
  85. package/dist/adapters/gcp.js.map +1 -0
  86. package/dist/adapters/github.d.ts +123 -0
  87. package/dist/adapters/github.d.ts.map +1 -0
  88. package/dist/adapters/github.js +105 -0
  89. package/dist/adapters/github.js.map +1 -0
  90. package/dist/adapters/jira.d.ts +109 -0
  91. package/dist/adapters/jira.d.ts.map +1 -0
  92. package/dist/adapters/jira.js +54 -0
  93. package/dist/adapters/jira.js.map +1 -0
  94. package/dist/adapters/k8s.d.ts +139 -0
  95. package/dist/adapters/k8s.d.ts.map +1 -0
  96. package/dist/adapters/k8s.js +69 -0
  97. package/dist/adapters/k8s.js.map +1 -0
  98. package/dist/adapters/linear.d.ts +109 -0
  99. package/dist/adapters/linear.d.ts.map +1 -0
  100. package/dist/adapters/linear.js +54 -0
  101. package/dist/adapters/linear.js.map +1 -0
  102. package/dist/adapters/salesforce.d.ts +109 -0
  103. package/dist/adapters/salesforce.d.ts.map +1 -0
  104. package/dist/adapters/salesforce.js +54 -0
  105. package/dist/adapters/salesforce.js.map +1 -0
  106. package/dist/adapters/stripe.d.ts +116 -0
  107. package/dist/adapters/stripe.d.ts.map +1 -0
  108. package/dist/adapters/stripe.js +77 -0
  109. package/dist/adapters/stripe.js.map +1 -0
  110. package/dist/adapters/supabase.d.ts +127 -0
  111. package/dist/adapters/supabase.d.ts.map +1 -0
  112. package/dist/adapters/supabase.js +112 -0
  113. package/dist/adapters/supabase.js.map +1 -0
  114. package/dist/adapters/terraform.d.ts +109 -0
  115. package/dist/adapters/terraform.d.ts.map +1 -0
  116. package/dist/adapters/terraform.js +58 -0
  117. package/dist/adapters/terraform.js.map +1 -0
  118. package/dist/adapters/vercel.d.ts +114 -0
  119. package/dist/adapters/vercel.d.ts.map +1 -0
  120. package/dist/adapters/vercel.js +81 -0
  121. package/dist/adapters/vercel.js.map +1 -0
  122. package/dist/aec-execution.d.ts +107 -0
  123. package/dist/aec-execution.d.ts.map +1 -0
  124. package/dist/aec-execution.js +334 -0
  125. package/dist/aec-execution.js.map +1 -0
  126. package/dist/breakglass.d.ts +205 -0
  127. package/dist/breakglass.d.ts.map +1 -0
  128. package/dist/breakglass.js +627 -0
  129. package/dist/breakglass.js.map +1 -0
  130. package/dist/capability-receipt.d.ts +601 -0
  131. package/dist/capability-receipt.d.ts.map +1 -0
  132. package/dist/capability-receipt.js +1177 -0
  133. package/dist/capability-receipt.js.map +1 -0
  134. package/dist/cf1-conformance.d.ts +83 -0
  135. package/dist/cf1-conformance.d.ts.map +1 -0
  136. package/dist/cf1-conformance.js +94 -0
  137. package/dist/cf1-conformance.js.map +1 -0
  138. package/dist/challenge-store.d.ts +20 -0
  139. package/dist/challenge-store.d.ts.map +1 -0
  140. package/dist/challenge-store.js +62 -0
  141. package/dist/challenge-store.js.map +1 -0
  142. package/dist/control-plane.d.ts +136 -0
  143. package/dist/control-plane.d.ts.map +1 -0
  144. package/dist/control-plane.js +349 -0
  145. package/dist/control-plane.js.map +1 -0
  146. package/dist/coverage.d.ts +156 -0
  147. package/dist/coverage.d.ts.map +1 -0
  148. package/dist/coverage.js +768 -0
  149. package/dist/coverage.js.map +1 -0
  150. package/dist/deployment-attestation.d.ts +93 -0
  151. package/dist/deployment-attestation.d.ts.map +1 -0
  152. package/dist/deployment-attestation.js +262 -0
  153. package/dist/deployment-attestation.js.map +1 -0
  154. package/dist/eg1-conformance.d.ts +286 -0
  155. package/dist/eg1-conformance.d.ts.map +1 -0
  156. package/dist/eg1-conformance.js +379 -0
  157. package/dist/eg1-conformance.js.map +1 -0
  158. package/dist/enterprise.d.ts +88 -0
  159. package/dist/enterprise.d.ts.map +1 -0
  160. package/dist/enterprise.js +194 -0
  161. package/dist/enterprise.js.map +1 -0
  162. package/dist/evidence-postgres.d.ts +123 -0
  163. package/dist/evidence-postgres.d.ts.map +1 -0
  164. package/dist/evidence-postgres.js +343 -0
  165. package/dist/evidence-postgres.js.map +1 -0
  166. package/dist/evidence.d.ts +113 -0
  167. package/dist/evidence.d.ts.map +1 -0
  168. package/dist/evidence.js +369 -0
  169. package/dist/evidence.js.map +1 -0
  170. package/dist/execution-binding.d.ts +49 -0
  171. package/dist/execution-binding.d.ts.map +1 -0
  172. package/dist/execution-binding.js +243 -0
  173. package/dist/execution-binding.js.map +1 -0
  174. package/dist/formal-runtime-map.d.ts +42 -0
  175. package/dist/formal-runtime-map.d.ts.map +1 -0
  176. package/dist/formal-runtime-map.js +23 -0
  177. package/dist/formal-runtime-map.js.map +1 -0
  178. package/dist/index.d.ts +1003 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +1737 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/key-registry.d.ts +60 -0
  183. package/dist/key-registry.d.ts.map +1 -0
  184. package/dist/key-registry.js +144 -0
  185. package/dist/key-registry.js.map +1 -0
  186. package/dist/mcp.d.ts +53 -0
  187. package/dist/mcp.d.ts.map +1 -0
  188. package/dist/mcp.js +112 -0
  189. package/dist/mcp.js.map +1 -0
  190. package/dist/metering.d.ts +80 -0
  191. package/dist/metering.d.ts.map +1 -0
  192. package/dist/metering.js +227 -0
  193. package/dist/metering.js.map +1 -0
  194. package/dist/metrics.d.ts +68 -0
  195. package/dist/metrics.d.ts.map +1 -0
  196. package/dist/metrics.js +242 -0
  197. package/dist/metrics.js.map +1 -0
  198. package/dist/network-witness.d.ts +167 -0
  199. package/dist/network-witness.d.ts.map +1 -0
  200. package/dist/network-witness.js +520 -0
  201. package/dist/network-witness.js.map +1 -0
  202. package/dist/proposal-to-effect.d.ts +146 -0
  203. package/dist/proposal-to-effect.d.ts.map +1 -0
  204. package/dist/proposal-to-effect.js +427 -0
  205. package/dist/proposal-to-effect.js.map +1 -0
  206. package/dist/protocol-state.d.ts +141 -0
  207. package/dist/protocol-state.d.ts.map +1 -0
  208. package/dist/protocol-state.js +106 -0
  209. package/dist/protocol-state.js.map +1 -0
  210. package/dist/receipt-program.d.ts +59 -0
  211. package/dist/receipt-program.d.ts.map +1 -0
  212. package/dist/receipt-program.js +1053 -0
  213. package/dist/receipt-program.js.map +1 -0
  214. package/dist/reliance-kernel.d.ts +66 -0
  215. package/dist/reliance-kernel.d.ts.map +1 -0
  216. package/dist/reliance-kernel.js +103 -0
  217. package/dist/reliance-kernel.js.map +1 -0
  218. package/dist/reliance-packet.d.ts +67 -0
  219. package/dist/reliance-packet.d.ts.map +1 -0
  220. package/dist/reliance-packet.js +176 -0
  221. package/dist/reliance-packet.js.map +1 -0
  222. package/dist/remedy-program-postgres.d.ts +37 -0
  223. package/dist/remedy-program-postgres.d.ts.map +1 -0
  224. package/dist/remedy-program-postgres.js +291 -0
  225. package/dist/remedy-program-postgres.js.map +1 -0
  226. package/dist/remedy-program-receipt.d.ts +97 -0
  227. package/dist/remedy-program-receipt.d.ts.map +1 -0
  228. package/dist/remedy-program-receipt.js +817 -0
  229. package/dist/remedy-program-receipt.js.map +1 -0
  230. package/dist/remedy-program.d.ts +76 -0
  231. package/dist/remedy-program.d.ts.map +1 -0
  232. package/dist/remedy-program.js +1346 -0
  233. package/dist/remedy-program.js.map +1 -0
  234. package/dist/reports/art14.d.ts +101 -0
  235. package/dist/reports/art14.d.ts.map +1 -0
  236. package/dist/reports/art14.js +340 -0
  237. package/dist/reports/art14.js.map +1 -0
  238. package/dist/reports/assurance-package.d.ts +197 -0
  239. package/dist/reports/assurance-package.d.ts.map +1 -0
  240. package/dist/reports/assurance-package.js +313 -0
  241. package/dist/reports/assurance-package.js.map +1 -0
  242. package/dist/reports/auditor-workpaper.d.ts +194 -0
  243. package/dist/reports/auditor-workpaper.d.ts.map +1 -0
  244. package/dist/reports/auditor-workpaper.js +545 -0
  245. package/dist/reports/auditor-workpaper.js.map +1 -0
  246. package/dist/reports/external-verification.d.ts +76 -0
  247. package/dist/reports/external-verification.d.ts.map +1 -0
  248. package/dist/reports/external-verification.js +209 -0
  249. package/dist/reports/external-verification.js.map +1 -0
  250. package/dist/reports/reperform.d.ts +122 -0
  251. package/dist/reports/reperform.d.ts.map +1 -0
  252. package/dist/reports/reperform.js +432 -0
  253. package/dist/reports/reperform.js.map +1 -0
  254. package/dist/reports/underwriter.d.ts +127 -0
  255. package/dist/reports/underwriter.d.ts.map +1 -0
  256. package/dist/reports/underwriter.js +348 -0
  257. package/dist/reports/underwriter.js.map +1 -0
  258. package/dist/retention.d.ts +88 -0
  259. package/dist/retention.d.ts.map +1 -0
  260. package/dist/retention.js +90 -0
  261. package/dist/retention.js.map +1 -0
  262. package/dist/roster.d.ts +113 -0
  263. package/dist/roster.d.ts.map +1 -0
  264. package/dist/roster.js +261 -0
  265. package/dist/roster.js.map +1 -0
  266. package/dist/runtime-monitor.d.ts +132 -0
  267. package/dist/runtime-monitor.d.ts.map +1 -0
  268. package/dist/runtime-monitor.js +197 -0
  269. package/dist/runtime-monitor.js.map +1 -0
  270. package/dist/settlement.d.ts +68 -0
  271. package/dist/settlement.d.ts.map +1 -0
  272. package/dist/settlement.js +306 -0
  273. package/dist/settlement.js.map +1 -0
  274. package/dist/siem.d.ts +261 -0
  275. package/dist/siem.d.ts.map +1 -0
  276. package/dist/siem.js +247 -0
  277. package/dist/siem.js.map +1 -0
  278. package/dist/store-postgres.d.ts +107 -0
  279. package/dist/store-postgres.d.ts.map +1 -0
  280. package/dist/store-postgres.js +152 -0
  281. package/dist/store-postgres.js.map +1 -0
  282. package/dist/store.d.ts +95 -0
  283. package/dist/store.d.ts.map +1 -0
  284. package/dist/store.js +202 -0
  285. package/dist/store.js.map +1 -0
  286. package/dist/strict-json.d.ts +14 -0
  287. package/dist/strict-json.d.ts.map +1 -0
  288. package/dist/strict-json.js +132 -0
  289. package/dist/strict-json.js.map +1 -0
  290. package/dist/trust-program-adapters.d.ts +122 -0
  291. package/dist/trust-program-adapters.d.ts.map +1 -0
  292. package/dist/trust-program-adapters.js +802 -0
  293. package/dist/trust-program-adapters.js.map +1 -0
  294. package/dist/trust-program-postgres.d.ts +41 -0
  295. package/dist/trust-program-postgres.d.ts.map +1 -0
  296. package/dist/trust-program-postgres.js +346 -0
  297. package/dist/trust-program-postgres.js.map +1 -0
  298. package/dist/trust-program-revocation.d.ts +206 -0
  299. package/dist/trust-program-revocation.d.ts.map +1 -0
  300. package/dist/trust-program-revocation.js +600 -0
  301. package/dist/trust-program-revocation.js.map +1 -0
  302. package/dist/trust-program.d.ts +197 -0
  303. package/dist/trust-program.d.ts.map +1 -0
  304. package/dist/trust-program.js +1351 -0
  305. package/dist/trust-program.js.map +1 -0
  306. package/dist/witness-postgres.d.ts +43 -0
  307. package/dist/witness-postgres.d.ts.map +1 -0
  308. package/dist/witness-postgres.js +92 -0
  309. package/dist/witness-postgres.js.map +1 -0
  310. package/dist/zk-range-proof.d.ts +78 -0
  311. package/dist/zk-range-proof.d.ts.map +1 -0
  312. package/dist/zk-range-proof.js +228 -0
  313. package/dist/zk-range-proof.js.map +1 -0
  314. package/eg1-conformance.js +3 -404
  315. package/enterprise.js +3 -180
  316. package/ep-assure.mjs +1 -0
  317. package/evidence-postgres.js +3 -356
  318. package/evidence.js +4 -360
  319. package/execution-binding.js +3 -212
  320. package/formal-runtime-map.js +4 -0
  321. package/index.js +4 -1249
  322. package/key-registry.js +3 -134
  323. package/mcp.js +3 -109
  324. package/metering.js +3 -226
  325. package/metrics.js +3 -231
  326. package/network-witness.js +3 -499
  327. package/package.json +35 -4
  328. package/proposal-to-effect.js +4 -0
  329. package/receipt-program.js +4 -0
  330. package/reliance-kernel.js +3 -112
  331. package/reliance-packet.js +3 -175
  332. package/remedy-program-postgres.js +4 -0
  333. package/remedy-program-receipt.js +4 -0
  334. package/remedy-program.js +4 -0
  335. package/reports/art14.js +0 -0
  336. package/reports/assurance-package.js +2 -312
  337. package/reports/auditor-workpaper.js +3 -537
  338. package/reports/external-verification.js +3 -222
  339. package/reports/reperform.js +3 -424
  340. package/reports/underwriter.js +3 -339
  341. package/retention.js +3 -84
  342. package/roster.js +3 -264
  343. package/runtime-monitor.js +4 -0
  344. package/settlement.js +3 -299
  345. package/siem.js +3 -235
  346. package/src/action-control-manifest.ts +424 -0
  347. package/src/action-escrow-custodian.ts +427 -0
  348. package/src/action-escrow-evidence.ts +1178 -0
  349. package/src/action-escrow-package.ts +1821 -0
  350. package/src/action-escrow-postgres.ts +366 -0
  351. package/src/action-escrow-state.ts +498 -0
  352. package/src/action-escrow-verifiers.ts +484 -0
  353. package/src/action-escrow.ts +3039 -0
  354. package/src/action-packs.ts +170 -0
  355. package/src/adapters/_kit.ts +117 -0
  356. package/src/adapters/aws.ts +142 -0
  357. package/src/adapters/cloudflare.ts +78 -0
  358. package/src/adapters/gcp.ts +71 -0
  359. package/src/adapters/github.ts +110 -0
  360. package/src/adapters/jira.ts +56 -0
  361. package/src/adapters/k8s.ts +71 -0
  362. package/src/adapters/linear.ts +56 -0
  363. package/src/adapters/salesforce.ts +56 -0
  364. package/src/adapters/stripe.ts +82 -0
  365. package/src/adapters/supabase.ts +122 -0
  366. package/src/adapters/terraform.ts +60 -0
  367. package/src/adapters/vercel.ts +85 -0
  368. package/src/aec-execution.ts +354 -0
  369. package/src/breakglass.ts +647 -0
  370. package/src/capability-receipt.ts +1258 -0
  371. package/src/cf1-conformance.ts +120 -0
  372. package/src/challenge-store.ts +70 -0
  373. package/src/control-plane.ts +374 -0
  374. package/src/coverage.ts +832 -0
  375. package/src/deployment-attestation.ts +259 -0
  376. package/src/eg1-conformance.ts +471 -0
  377. package/src/enterprise.ts +195 -0
  378. package/src/evidence-postgres.ts +374 -0
  379. package/src/evidence.ts +367 -0
  380. package/src/execution-binding.ts +236 -0
  381. package/src/formal-runtime-map.ts +24 -0
  382. package/src/index.ts +2127 -0
  383. package/src/key-registry.ts +150 -0
  384. package/src/mcp.ts +120 -0
  385. package/src/metering.ts +237 -0
  386. package/src/metrics.ts +232 -0
  387. package/src/network-witness.ts +585 -0
  388. package/src/proposal-to-effect.ts +571 -0
  389. package/src/protocol-state.ts +143 -0
  390. package/src/protocol-state.typecheck.ts +94 -0
  391. package/src/receipt-program.ts +1105 -0
  392. package/src/reliance-kernel.ts +119 -0
  393. package/src/reliance-packet.ts +206 -0
  394. package/src/remedy-program-postgres.ts +367 -0
  395. package/src/remedy-program-receipt.ts +891 -0
  396. package/src/remedy-program.ts +1388 -0
  397. package/src/reports/art14.ts +361 -0
  398. package/src/reports/assurance-package.ts +330 -0
  399. package/src/reports/auditor-workpaper.ts +558 -0
  400. package/src/reports/external-verification.ts +232 -0
  401. package/src/reports/reperform.ts +441 -0
  402. package/src/reports/underwriter.ts +356 -0
  403. package/src/retention.ts +100 -0
  404. package/src/roster.ts +273 -0
  405. package/src/runtime-monitor.ts +217 -0
  406. package/src/settlement.ts +313 -0
  407. package/src/siem.ts +247 -0
  408. package/src/store-postgres.ts +167 -0
  409. package/src/store.ts +214 -0
  410. package/src/strict-json.ts +115 -0
  411. package/src/trust-program-adapters.ts +847 -0
  412. package/src/trust-program-postgres.ts +429 -0
  413. package/src/trust-program-revocation.ts +751 -0
  414. package/src/trust-program.ts +1460 -0
  415. package/src/witness-postgres.ts +106 -0
  416. package/src/zk-range-proof.ts +250 -0
  417. package/store-postgres.js +3 -161
  418. package/store.js +4 -198
  419. package/strict-json.js +3 -102
  420. package/trust-program-adapters.js +4 -0
  421. package/trust-program-postgres.js +4 -0
  422. package/trust-program-revocation.js +4 -0
  423. package/trust-program.js +4 -0
  424. package/witness-postgres.js +3 -96
  425. package/zk-range-proof.js +4 -0
@@ -0,0 +1,1346 @@
1
+ // @ts-nocheck
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * EMILIA Gate Remedy Program Profile v1.
5
+ *
6
+ * A fail-closed, post-effect compensation state machine. The kernel never
7
+ * rewrites an already-observed effect: it verifies the original effect,
8
+ * records disputes and late revocations, and authorizes separately bound
9
+ * compensating operations through atomic compare-and-swap transitions.
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { canonicalize } from '../execution-binding.js';
13
+ export const REMEDY_PROGRAM_VERSION = 'EP-GATE-REMEDY-PROGRAM-PROFILE-v1';
14
+ const DIGEST = /^sha256:[0-9a-f]{64}$/;
15
+ const CAID = /^caid:1:[a-z][a-z0-9.-]*\.[1-9][0-9]*:jcs-sha256:[A-Za-z0-9_-]{43}$/;
16
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
17
+ const MAX_CONTEXT_BYTES = 512;
18
+ const MAX_REMEDY_ATTEMPTS = 1024;
19
+ const DEFAULT_MAX_DISPUTE_AGE_MS = 30 * 24 * 60 * 60 * 1000;
20
+ const CONFIG_KEYS = new Set([
21
+ 'store', 'verifyOriginalEffect', 'verifyRevocation', 'verifyDispute',
22
+ 'verifyRemedyAuthorization', 'verifyRemedyOutcome', 'verifyOriginalReconciliation',
23
+ 'verifyResolution',
24
+ 'now', 'maxDisputeAgeMs', 'allowEphemeralState', 'production',
25
+ ]);
26
+ const CREATE_KEYS = new Set([
27
+ 'instanceId', 'tenantId', 'environment', 'audience', 'original',
28
+ 'remedyProfileDigest', 'destinationBindingDigest', 'maxRemedyUnits',
29
+ 'unit', 'evidence',
30
+ ]);
31
+ const ORIGINAL_KEYS = new Set([
32
+ 'caid', 'action_digest', 'operation_id', 'consequence_mode',
33
+ 'consequence_digest', 'terminal_evidence_digest', 'outcome', 'occurred_at',
34
+ ]);
35
+ const VERIFIED_ORIGINAL_KEYS = new Set(['ok', ...ORIGINAL_KEYS, 'evidence_digest']);
36
+ const ORIGINAL_RECONCILIATION_INPUT_KEYS = new Set([
37
+ 'tenantId', 'instanceId', 'outcome', 'evidence',
38
+ ]);
39
+ const ORIGINAL_RECONCILIATION_EVIDENCE_KEYS = new Set([
40
+ 'evidence_id', 'evidence_digest', 'observed_at',
41
+ ]);
42
+ const VERIFIED_ORIGINAL_RECONCILIATION_KEYS = new Set([
43
+ 'ok', ...ORIGINAL_RECONCILIATION_EVIDENCE_KEYS, 'original_operation_id',
44
+ 'original_action_digest', 'terminal_evidence_digest', 'outcome',
45
+ ]);
46
+ const REVOCATION_INPUT_KEYS = new Set(['tenantId', 'instanceId', 'evidence']);
47
+ const REVOCATION_EVIDENCE_KEYS = new Set(['id', 'digest']);
48
+ const VERIFIED_REVOCATION_KEYS = new Set([
49
+ 'ok', 'evidence_id', 'evidence_digest', 'target_operation_id',
50
+ 'action_digest', 'authority_id', 'revoked_at',
51
+ ]);
52
+ const DISPUTE_INPUT_KEYS = new Set(['tenantId', 'instanceId', 'dispute']);
53
+ const DISPUTE_KEYS = new Set([
54
+ 'dispute_id', 'evidence_id', 'evidence_digest', 'challenger_id',
55
+ 'requested_units', 'opened_at',
56
+ ]);
57
+ const VERIFIED_DISPUTE_KEYS = new Set([
58
+ 'ok', ...DISPUTE_KEYS, 'original_operation_id', 'original_action_digest',
59
+ ]);
60
+ const AUTHORIZATION_INPUT_KEYS = new Set(['tenantId', 'instanceId', 'authorization']);
61
+ const AUTHORIZATION_KEYS = new Set([
62
+ 'evidence_id', 'evidence_digest', 'remedy_operation_id', 'remedy_caid',
63
+ 'remedy_action_digest', 'consequence_mode', 'capability_template_digest',
64
+ 'escrow_profile_digest', 'units', 'authorized_at',
65
+ ]);
66
+ const VERIFIED_AUTHORIZATION_KEYS = new Set([
67
+ 'ok', ...AUTHORIZATION_KEYS, 'dispute_id', 'original_operation_id',
68
+ 'destination_binding_digest', 'unit',
69
+ ]);
70
+ const CLAIM_KEYS = new Set(['tenantId', 'instanceId', 'remedyOperationId', 'claimToken']);
71
+ const FINALIZE_KEYS = new Set([
72
+ 'tenantId', 'instanceId', 'remedyOperationId', 'claimToken', 'outcome', 'evidence',
73
+ ]);
74
+ const RECONCILE_KEYS = new Set(['tenantId', 'instanceId', 'remedyOperationId', 'outcome', 'evidence']);
75
+ const OUTCOME_EVIDENCE_KEYS = new Set(['evidence_id', 'evidence_digest', 'observed_at']);
76
+ const VERIFIED_OUTCOME_KEYS = new Set([
77
+ 'ok', ...OUTCOME_EVIDENCE_KEYS, 'remedy_operation_id',
78
+ 'remedy_action_digest', 'destination_binding_digest', 'units', 'unit',
79
+ 'outcome',
80
+ ]);
81
+ const RESOLUTION_INPUT_KEYS = new Set(['tenantId', 'instanceId', 'resolution']);
82
+ const RESOLUTION_KEYS = new Set([
83
+ 'evidence_id', 'evidence_digest', 'outcome', 'resolved_at',
84
+ ]);
85
+ const VERIFIED_RESOLUTION_KEYS = new Set(['ok', ...RESOLUTION_KEYS, 'dispute_id']);
86
+ const STATE_KEYS = new Set([
87
+ 'version', 'instance_id', 'tenant_id', 'environment', 'audience', 'status',
88
+ 'revision', 'created_at', 'updated_at', 'original', 'remedy_profile_digest',
89
+ 'destination_binding_digest', 'max_remedy_units', 'unit', 'remedied_units',
90
+ 'remaining_units', 'used_evidence_ids', 'used_evidence_digests',
91
+ 'original_reconciliation', 'revocation', 'dispute', 'active_remedy',
92
+ 'remedies', 'resolution',
93
+ 'create_request_digest',
94
+ ]);
95
+ const STORED_ORIGINAL_KEYS = new Set([...ORIGINAL_KEYS, 'evidence_digest']);
96
+ const STORED_ORIGINAL_RECONCILIATION_KEYS = new Set([
97
+ ...VERIFIED_ORIGINAL_RECONCILIATION_KEYS,
98
+ 'request_digest',
99
+ ]);
100
+ STORED_ORIGINAL_RECONCILIATION_KEYS.delete('ok');
101
+ const STORED_REVOCATION_KEYS = new Set([
102
+ 'evidence_id', 'evidence_digest', 'target_operation_id', 'action_digest',
103
+ 'authority_id', 'revoked_at', 'effect', 'request_digest',
104
+ ]);
105
+ const STORED_DISPUTE_KEYS = new Set([
106
+ ...DISPUTE_KEYS, 'original_operation_id', 'original_action_digest',
107
+ 'request_digest',
108
+ ]);
109
+ const ATTEMPT_KEYS = new Set([
110
+ 'evidence_id', 'evidence_digest', 'dispute_id', 'original_operation_id',
111
+ 'remedy_operation_id', 'remedy_caid', 'remedy_action_digest',
112
+ 'consequence_mode', 'capability_template_digest', 'escrow_profile_digest',
113
+ 'destination_binding_digest', 'units', 'unit', 'authorized_at',
114
+ 'request_digest', 'status', 'claim_token_digest', 'claimed_at',
115
+ 'claim_request_digest', 'outcome', 'outcome_evidence',
116
+ 'finalize_request_digest', 'reconciliation', 'reconcile_request_digest',
117
+ ]);
118
+ const STORED_OUTCOME_KEYS = new Set([
119
+ ...OUTCOME_EVIDENCE_KEYS, 'remedy_operation_id', 'remedy_action_digest',
120
+ 'destination_binding_digest', 'units', 'unit', 'outcome',
121
+ ]);
122
+ const STORED_RESOLUTION_KEYS = new Set([
123
+ ...RESOLUTION_KEYS, 'dispute_id', 'request_digest',
124
+ ]);
125
+ function deepFreeze(value) {
126
+ if (value === null || typeof value !== 'object')
127
+ return value;
128
+ const stack = [value];
129
+ const seen = new WeakSet();
130
+ while (stack.length > 0) {
131
+ const current = stack.pop();
132
+ if (seen.has(current))
133
+ continue;
134
+ seen.add(current);
135
+ for (const child of Object.values(current)) {
136
+ if (child !== null && typeof child === 'object')
137
+ stack.push(child);
138
+ }
139
+ Object.freeze(current);
140
+ }
141
+ return value;
142
+ }
143
+ function snapshot(value, label) {
144
+ try {
145
+ canonicalize(value);
146
+ return deepFreeze(structuredClone(value));
147
+ }
148
+ catch {
149
+ throw new TypeError(`${label} must be bounded canonical JSON`);
150
+ }
151
+ }
152
+ function clone(value) {
153
+ return structuredClone(value);
154
+ }
155
+ function isRecord(value) {
156
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
157
+ return false;
158
+ const prototype = Object.getPrototypeOf(value);
159
+ return prototype === Object.prototype || prototype === null;
160
+ }
161
+ function isDataRecord(value) {
162
+ return isRecord(value) && Reflect.ownKeys(value).every((key) => {
163
+ if (typeof key !== 'string')
164
+ return false;
165
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
166
+ return descriptor?.enumerable === true && Object.hasOwn(descriptor, 'value');
167
+ });
168
+ }
169
+ function exactKeys(value, keys) {
170
+ return isDataRecord(value)
171
+ && Reflect.ownKeys(value).length === keys.size
172
+ && Object.keys(value).every((key) => keys.has(key));
173
+ }
174
+ function validId(value) {
175
+ return typeof value === 'string' && ID.test(value);
176
+ }
177
+ function validContext(value) {
178
+ return typeof value === 'string' && value.length > 0
179
+ && Buffer.byteLength(value, 'utf8') <= MAX_CONTEXT_BYTES
180
+ && !/[\u0000-\u001f\u007f]/.test(value);
181
+ }
182
+ function instant(value) {
183
+ if (typeof value !== 'string'
184
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
185
+ return NaN;
186
+ const parsed = Date.parse(value);
187
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value ? parsed : NaN;
188
+ }
189
+ function digest(value) {
190
+ return `sha256:${createHash('sha256').update(canonicalize(value)).digest('hex')}`;
191
+ }
192
+ function same(left, right) {
193
+ try {
194
+ return canonicalize(left) === canonicalize(right);
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
200
+ function fail(reason) {
201
+ return Object.freeze({ ok: false, reason });
202
+ }
203
+ function isFailure(value) {
204
+ return value.ok === false && typeof value.reason === 'string';
205
+ }
206
+ function pass(fields) {
207
+ return deepFreeze(fields);
208
+ }
209
+ function withoutOk(projection) {
210
+ const result = {};
211
+ for (const [key, value] of Object.entries(projection)) {
212
+ if (key !== 'ok')
213
+ result[key] = clone(value);
214
+ }
215
+ return result;
216
+ }
217
+ function validOriginal(value, stored = false) {
218
+ const keys = stored ? STORED_ORIGINAL_KEYS : ORIGINAL_KEYS;
219
+ return exactKeys(value, keys)
220
+ && typeof value.caid === 'string' && CAID.test(value.caid)
221
+ && typeof value.action_digest === 'string' && DIGEST.test(value.action_digest)
222
+ && validId(value.operation_id)
223
+ && ['receipt-program', 'action-escrow'].includes(value.consequence_mode)
224
+ && typeof value.consequence_digest === 'string' && DIGEST.test(value.consequence_digest)
225
+ && typeof value.terminal_evidence_digest === 'string' && DIGEST.test(value.terminal_evidence_digest)
226
+ && ['executed', 'indeterminate'].includes(value.outcome)
227
+ && Number.isFinite(instant(value.occurred_at))
228
+ && (!stored || (typeof value.evidence_digest === 'string' && DIGEST.test(value.evidence_digest)));
229
+ }
230
+ function validOriginalReconciliation(value) {
231
+ return exactKeys(value, STORED_ORIGINAL_RECONCILIATION_KEYS)
232
+ && validEvidencePair(value.evidence_id, value.evidence_digest)
233
+ && validId(value.original_operation_id)
234
+ && typeof value.original_action_digest === 'string' && DIGEST.test(value.original_action_digest)
235
+ && typeof value.terminal_evidence_digest === 'string' && DIGEST.test(value.terminal_evidence_digest)
236
+ && ['executed', 'proved_no_effect'].includes(value.outcome)
237
+ && Number.isFinite(instant(value.observed_at))
238
+ && typeof value.request_digest === 'string' && DIGEST.test(value.request_digest);
239
+ }
240
+ function validDispute(value) {
241
+ return exactKeys(value, STORED_DISPUTE_KEYS)
242
+ && validId(value.dispute_id) && validId(value.evidence_id)
243
+ && typeof value.evidence_digest === 'string' && DIGEST.test(value.evidence_digest)
244
+ && validContext(value.challenger_id)
245
+ && validId(value.original_operation_id)
246
+ && typeof value.original_action_digest === 'string' && DIGEST.test(value.original_action_digest)
247
+ && Number.isSafeInteger(value.requested_units) && value.requested_units > 0
248
+ && Number.isFinite(instant(value.opened_at))
249
+ && typeof value.request_digest === 'string' && DIGEST.test(value.request_digest);
250
+ }
251
+ function validStoredOutcome(value, reconciliation) {
252
+ return exactKeys(value, STORED_OUTCOME_KEYS)
253
+ && validId(value.evidence_id)
254
+ && typeof value.evidence_digest === 'string' && DIGEST.test(value.evidence_digest)
255
+ && validId(value.remedy_operation_id)
256
+ && typeof value.remedy_action_digest === 'string' && DIGEST.test(value.remedy_action_digest)
257
+ && typeof value.destination_binding_digest === 'string' && DIGEST.test(value.destination_binding_digest)
258
+ && Number.isSafeInteger(value.units) && value.units > 0
259
+ && validContext(value.unit)
260
+ && (reconciliation
261
+ ? ['executed', 'proved_no_effect'].includes(value.outcome)
262
+ : ['executed', 'proved_no_effect', 'indeterminate'].includes(value.outcome))
263
+ && Number.isFinite(instant(value.observed_at));
264
+ }
265
+ function validRemedyOwner(value) {
266
+ return (value.consequence_mode === 'receipt-program'
267
+ && typeof value.capability_template_digest === 'string'
268
+ && DIGEST.test(value.capability_template_digest)
269
+ && value.escrow_profile_digest === null)
270
+ || (value.consequence_mode === 'action-escrow'
271
+ && value.capability_template_digest === null
272
+ && typeof value.escrow_profile_digest === 'string'
273
+ && DIGEST.test(value.escrow_profile_digest));
274
+ }
275
+ function validAttempt(value) {
276
+ if (!exactKeys(value, ATTEMPT_KEYS)
277
+ || !validId(value.evidence_id)
278
+ || typeof value.evidence_digest !== 'string' || !DIGEST.test(value.evidence_digest)
279
+ || !validId(value.dispute_id) || !validId(value.original_operation_id)
280
+ || !validId(value.remedy_operation_id)
281
+ || typeof value.remedy_caid !== 'string' || !CAID.test(value.remedy_caid)
282
+ || typeof value.remedy_action_digest !== 'string' || !DIGEST.test(value.remedy_action_digest)
283
+ || !validRemedyOwner(value)
284
+ || typeof value.destination_binding_digest !== 'string' || !DIGEST.test(value.destination_binding_digest)
285
+ || !Number.isSafeInteger(value.units) || value.units < 1
286
+ || !validContext(value.unit) || !Number.isFinite(instant(value.authorized_at))
287
+ || typeof value.request_digest !== 'string' || !DIGEST.test(value.request_digest)
288
+ || !['authorized', 'claimed', 'indeterminate', 'executed', 'proved_no_effect'].includes(value.status)) {
289
+ return false;
290
+ }
291
+ const claimed = value.status !== 'authorized';
292
+ if (claimed !== (typeof value.claim_token_digest === 'string' && DIGEST.test(value.claim_token_digest))
293
+ || claimed !== Number.isFinite(instant(value.claimed_at))
294
+ || claimed !== (typeof value.claim_request_digest === 'string' && DIGEST.test(value.claim_request_digest))) {
295
+ return false;
296
+ }
297
+ const finalized = ['indeterminate', 'executed', 'proved_no_effect'].includes(value.status);
298
+ if (finalized !== (typeof value.finalize_request_digest === 'string' && DIGEST.test(value.finalize_request_digest))
299
+ || finalized !== validStoredOutcome(value.outcome_evidence, false)
300
+ || finalized !== (typeof value.outcome === 'string'))
301
+ return false;
302
+ const reconciled = value.reconciliation !== null;
303
+ if (reconciled !== (typeof value.reconcile_request_digest === 'string' && DIGEST.test(value.reconcile_request_digest))) {
304
+ return false;
305
+ }
306
+ if (reconciled && !validStoredOutcome(value.reconciliation, true))
307
+ return false;
308
+ return true;
309
+ }
310
+ function validState(value, tenantId, instanceId) {
311
+ if (!exactKeys(value, STATE_KEYS)
312
+ || value.version !== REMEDY_PROGRAM_VERSION
313
+ || !validId(value.instance_id) || (instanceId !== undefined && value.instance_id !== instanceId)
314
+ || !validContext(value.tenant_id) || (tenantId !== undefined && value.tenant_id !== tenantId)
315
+ || !validContext(value.environment) || !validContext(value.audience)
316
+ || ![
317
+ 'effect_executed', 'effect_indeterminate', 'disputed', 'remedy_authorized',
318
+ 'remedy_claimed', 'remedy_indeterminate', 'partially_remedied',
319
+ 'remedied', 'resolved_no_remedy', 'original_proved_no_effect',
320
+ ].includes(value.status)
321
+ || !Number.isSafeInteger(value.revision) || value.revision < 0
322
+ || !Number.isFinite(instant(value.created_at)) || !Number.isFinite(instant(value.updated_at))
323
+ || instant(value.updated_at) < instant(value.created_at)
324
+ || !validOriginal(value.original, true)
325
+ || typeof value.remedy_profile_digest !== 'string' || !DIGEST.test(value.remedy_profile_digest)
326
+ || typeof value.destination_binding_digest !== 'string' || !DIGEST.test(value.destination_binding_digest)
327
+ || !Number.isSafeInteger(value.max_remedy_units) || value.max_remedy_units < 1
328
+ || !validContext(value.unit)
329
+ || !Number.isSafeInteger(value.remedied_units) || value.remedied_units < 0
330
+ || !Number.isSafeInteger(value.remaining_units) || value.remaining_units < 0
331
+ || value.remedied_units + value.remaining_units !== value.max_remedy_units
332
+ || !Array.isArray(value.used_evidence_ids) || !value.used_evidence_ids.every(validId)
333
+ || new Set(value.used_evidence_ids).size !== value.used_evidence_ids.length
334
+ || !Array.isArray(value.used_evidence_digests)
335
+ || !value.used_evidence_digests.every((entry) => typeof entry === 'string' && DIGEST.test(entry))
336
+ || new Set(value.used_evidence_digests).size !== value.used_evidence_digests.length
337
+ || !Array.isArray(value.remedies) || value.remedies.length > MAX_REMEDY_ATTEMPTS
338
+ || !value.remedies.every(validAttempt)
339
+ || (value.dispute !== null && !validDispute(value.dispute))
340
+ || (value.active_remedy !== null && !validAttempt(value.active_remedy))
341
+ || typeof value.create_request_digest !== 'string' || !DIGEST.test(value.create_request_digest)) {
342
+ return false;
343
+ }
344
+ if (value.revocation !== null && (!exactKeys(value.revocation, STORED_REVOCATION_KEYS)
345
+ || !validId(value.revocation.evidence_id)
346
+ || typeof value.revocation.evidence_digest !== 'string' || !DIGEST.test(value.revocation.evidence_digest)
347
+ || !validId(value.revocation.target_operation_id)
348
+ || typeof value.revocation.action_digest !== 'string' || !DIGEST.test(value.revocation.action_digest)
349
+ || !validContext(value.revocation.authority_id)
350
+ || !Number.isFinite(instant(value.revocation.revoked_at))
351
+ || value.revocation.effect !== 'future_authority_only'
352
+ || typeof value.revocation.request_digest !== 'string' || !DIGEST.test(value.revocation.request_digest))) {
353
+ return false;
354
+ }
355
+ if (value.original_reconciliation !== null
356
+ && !validOriginalReconciliation(value.original_reconciliation))
357
+ return false;
358
+ if (value.original_reconciliation !== null
359
+ && (value.original.outcome !== 'indeterminate'
360
+ || value.original_reconciliation.original_operation_id !== value.original.operation_id
361
+ || value.original_reconciliation.original_action_digest !== value.original.action_digest
362
+ || value.original_reconciliation.terminal_evidence_digest !== value.original.terminal_evidence_digest
363
+ || instant(value.original_reconciliation.observed_at) < instant(value.original.occurred_at))) {
364
+ return false;
365
+ }
366
+ if (value.resolution !== null && (!exactKeys(value.resolution, STORED_RESOLUTION_KEYS)
367
+ || !validId(value.resolution.evidence_id)
368
+ || typeof value.resolution.evidence_digest !== 'string' || !DIGEST.test(value.resolution.evidence_digest)
369
+ || value.resolution.outcome !== 'no_remedy'
370
+ || !Number.isFinite(instant(value.resolution.resolved_at))
371
+ || !validId(value.resolution.dispute_id)
372
+ || typeof value.resolution.request_digest !== 'string' || !DIGEST.test(value.resolution.request_digest))) {
373
+ return false;
374
+ }
375
+ const committed = value.remedies
376
+ .filter((attempt) => attempt.status === 'executed')
377
+ .reduce((total, attempt) => total + attempt.units, 0);
378
+ if (committed !== value.remedied_units || committed > value.max_remedy_units)
379
+ return false;
380
+ const effectiveOriginalOutcome = value.original.outcome === 'executed'
381
+ ? 'executed' : value.original_reconciliation?.outcome ?? 'indeterminate';
382
+ if (value.status === 'effect_indeterminate' && effectiveOriginalOutcome !== 'indeterminate')
383
+ return false;
384
+ if (value.status === 'original_proved_no_effect') {
385
+ if (effectiveOriginalOutcome !== 'proved_no_effect'
386
+ || value.active_remedy !== null || value.remedies.length !== 0
387
+ || value.remedied_units !== 0)
388
+ return false;
389
+ }
390
+ else if (effectiveOriginalOutcome === 'proved_no_effect')
391
+ return false;
392
+ if (!['effect_indeterminate', 'original_proved_no_effect', 'disputed', 'resolved_no_remedy'].includes(value.status)
393
+ && effectiveOriginalOutcome !== 'executed')
394
+ return false;
395
+ if (value.status === 'disputed' && effectiveOriginalOutcome === 'proved_no_effect')
396
+ return false;
397
+ if (['remedy_authorized', 'remedy_claimed', 'remedy_indeterminate'].includes(value.status)
398
+ !== (value.active_remedy !== null))
399
+ return false;
400
+ return true;
401
+ }
402
+ function tokenDigest(token) {
403
+ return digest({ claim_token: token });
404
+ }
405
+ function validEvidencePair(id, evidenceDigest) {
406
+ return validId(id) && typeof evidenceDigest === 'string' && DIGEST.test(evidenceDigest);
407
+ }
408
+ function evidenceUsed(state, id, evidenceDigest) {
409
+ return state.used_evidence_ids.includes(id) || state.used_evidence_digests.includes(evidenceDigest);
410
+ }
411
+ function consumeEvidence(state, id, evidenceDigest) {
412
+ state.used_evidence_ids.push(id);
413
+ state.used_evidence_digests.push(evidenceDigest);
414
+ }
415
+ function expectedContext(state) {
416
+ return snapshot({
417
+ instance_id: state.instance_id,
418
+ tenant_id: state.tenant_id,
419
+ environment: state.environment,
420
+ audience: state.audience,
421
+ original: clone(state.original),
422
+ original_reconciliation: state.original_reconciliation === null
423
+ ? null : clone(state.original_reconciliation),
424
+ remedy_profile_digest: state.remedy_profile_digest,
425
+ destination_binding_digest: state.destination_binding_digest,
426
+ max_remedy_units: state.max_remedy_units,
427
+ unit: state.unit,
428
+ dispute: state.dispute === null ? null : clone(state.dispute),
429
+ }, 'verifier expectation');
430
+ }
431
+ /**
432
+ * In-process atomic CAS store. It is intentionally marked non-durable; callers
433
+ * selecting production mode must provide a durable external CAS store.
434
+ */
435
+ export function createRemedyMemoryStore() {
436
+ const records = new Map();
437
+ const storageKey = (tenantId, instanceId) => `${tenantId.length}:${tenantId}${instanceId}`;
438
+ const evidenceIds = new Set();
439
+ const evidenceDigests = new Set();
440
+ const remedyOperations = new Set();
441
+ const remedyActions = new Set();
442
+ const remedyCaids = new Set();
443
+ const claimKey = (tenantId, value) => `${tenantId.length}:${tenantId}${value}`;
444
+ const attempts = (state) => [
445
+ ...(state.active_remedy === null ? [] : [state.active_remedy]),
446
+ ...state.remedies,
447
+ ];
448
+ const claimEvidence = (state) => {
449
+ for (const id of state.used_evidence_ids)
450
+ evidenceIds.add(claimKey(state.tenant_id, id));
451
+ for (const value of state.used_evidence_digests) {
452
+ evidenceDigests.add(claimKey(state.tenant_id, value));
453
+ }
454
+ };
455
+ const claimAttempts = (state) => {
456
+ for (const attempt of attempts(state)) {
457
+ remedyOperations.add(claimKey(state.tenant_id, attempt.remedy_operation_id));
458
+ remedyActions.add(claimKey(state.tenant_id, attempt.remedy_action_digest));
459
+ remedyCaids.add(claimKey(state.tenant_id, attempt.remedy_caid));
460
+ }
461
+ };
462
+ return Object.freeze({
463
+ durable: false,
464
+ async create(state) {
465
+ let candidate;
466
+ try {
467
+ candidate = snapshot(state, 'remedy state');
468
+ }
469
+ catch {
470
+ return fail('store_state_invalid');
471
+ }
472
+ if (!validState(candidate))
473
+ return fail('store_state_invalid');
474
+ const key = storageKey(candidate.tenant_id, candidate.instance_id);
475
+ if (records.has(key))
476
+ return fail('instance_exists');
477
+ if (candidate.used_evidence_ids.some((id) => evidenceIds.has(claimKey(candidate.tenant_id, id)))
478
+ || candidate.used_evidence_digests.some((value) => (evidenceDigests.has(claimKey(candidate.tenant_id, value)))))
479
+ return fail('evidence_replayed');
480
+ records.set(key, clone(candidate));
481
+ claimEvidence(candidate);
482
+ claimAttempts(candidate);
483
+ return { ok: true, state: clone(candidate) };
484
+ },
485
+ async get({ tenantId, instanceId }) {
486
+ const state = records.get(storageKey(tenantId, instanceId));
487
+ return state ? { ok: true, state: clone(state) } : fail('instance_not_found');
488
+ },
489
+ async compareAndSwap({ tenantId, instanceId, expectedRevision, state }) {
490
+ const key = storageKey(tenantId, instanceId);
491
+ const current = records.get(key);
492
+ if (!current)
493
+ return fail('instance_not_found');
494
+ if (current.revision !== expectedRevision)
495
+ return fail('revision_conflict');
496
+ let candidate;
497
+ try {
498
+ candidate = snapshot(state, 'remedy state');
499
+ }
500
+ catch {
501
+ return fail('store_state_invalid');
502
+ }
503
+ if (!validState(candidate, tenantId, instanceId)
504
+ || candidate.revision !== expectedRevision + 1
505
+ || instant(candidate.updated_at) < instant(current.updated_at)) {
506
+ return fail('store_state_invalid');
507
+ }
508
+ const newEvidenceIds = candidate.used_evidence_ids.filter((id) => (!current.used_evidence_ids.includes(id)));
509
+ const newEvidenceDigests = candidate.used_evidence_digests.filter((value) => (!current.used_evidence_digests.includes(value)));
510
+ if (newEvidenceIds.some((id) => evidenceIds.has(claimKey(tenantId, id)))
511
+ || newEvidenceDigests.some((value) => evidenceDigests.has(claimKey(tenantId, value)))) {
512
+ return fail('evidence_replayed');
513
+ }
514
+ const currentOperations = new Set(attempts(current).map((attempt) => attempt.remedy_operation_id));
515
+ const newAttempts = attempts(candidate).filter((attempt) => (!currentOperations.has(attempt.remedy_operation_id)));
516
+ if (newAttempts.some((attempt) => (remedyOperations.has(claimKey(tenantId, attempt.remedy_operation_id))
517
+ || remedyActions.has(claimKey(tenantId, attempt.remedy_action_digest))
518
+ || remedyCaids.has(claimKey(tenantId, attempt.remedy_caid)))))
519
+ return fail('remedy_operation_replayed');
520
+ records.set(key, clone(candidate));
521
+ claimEvidence(candidate);
522
+ claimAttempts(candidate);
523
+ return { ok: true, state: clone(candidate) };
524
+ },
525
+ });
526
+ }
527
+ function verifierInput(fields) {
528
+ return snapshot(fields, 'verifier input');
529
+ }
530
+ function validClock(value) {
531
+ return Number.isSafeInteger(value) && Number(value) >= 0 && Number(value) <= 8_640_000_000_000_000;
532
+ }
533
+ /** Build a remedy kernel with all trust callbacks and store methods pinned. */
534
+ export function createRemedyProgramKernel(options) {
535
+ if (!isDataRecord(options) || Object.keys(options).some((key) => !CONFIG_KEYS.has(key))) {
536
+ throw new TypeError('remedy program configuration invalid');
537
+ }
538
+ const sourceStore = options.store;
539
+ if (!sourceStore || typeof sourceStore.create !== 'function'
540
+ || typeof sourceStore.get !== 'function'
541
+ || typeof sourceStore.compareAndSwap !== 'function') {
542
+ throw new TypeError('remedy program CAS store required');
543
+ }
544
+ for (const key of [
545
+ 'verifyOriginalEffect', 'verifyRevocation', 'verifyDispute',
546
+ 'verifyRemedyAuthorization', 'verifyRemedyOutcome', 'verifyOriginalReconciliation',
547
+ ]) {
548
+ if (typeof options[key] !== 'function')
549
+ throw new TypeError(`${key} callback required`);
550
+ }
551
+ if (options.verifyResolution !== undefined && typeof options.verifyResolution !== 'function') {
552
+ throw new TypeError('verifyResolution callback invalid');
553
+ }
554
+ if (options.now !== undefined && typeof options.now !== 'function') {
555
+ throw new TypeError('remedy program clock invalid');
556
+ }
557
+ if (options.production === true && options.allowEphemeralState === true) {
558
+ throw new TypeError('production remedy program cannot allow ephemeral state');
559
+ }
560
+ if ((options.production === true || options.allowEphemeralState === false)
561
+ && sourceStore.durable !== true) {
562
+ throw new TypeError('durable remedy program CAS store required');
563
+ }
564
+ const maxDisputeAgeMs = options.maxDisputeAgeMs ?? DEFAULT_MAX_DISPUTE_AGE_MS;
565
+ if (!Number.isSafeInteger(maxDisputeAgeMs) || maxDisputeAgeMs < 1) {
566
+ throw new TypeError('maxDisputeAgeMs invalid');
567
+ }
568
+ const store = Object.freeze({
569
+ durable: sourceStore.durable === true,
570
+ create: sourceStore.create.bind(sourceStore),
571
+ get: sourceStore.get.bind(sourceStore),
572
+ compareAndSwap: sourceStore.compareAndSwap.bind(sourceStore),
573
+ });
574
+ const verifyOriginalEffect = options.verifyOriginalEffect;
575
+ const verifyRevocation = options.verifyRevocation;
576
+ const verifyDispute = options.verifyDispute;
577
+ const verifyRemedyAuthorization = options.verifyRemedyAuthorization;
578
+ const verifyRemedyOutcome = options.verifyRemedyOutcome;
579
+ const verifyOriginalReconciliation = options.verifyOriginalReconciliation;
580
+ const verifyResolution = options.verifyResolution;
581
+ const now = options.now ?? Date.now;
582
+ const clock = () => {
583
+ let value;
584
+ try {
585
+ value = now();
586
+ }
587
+ catch {
588
+ return NaN;
589
+ }
590
+ return validClock(value) ? Number(value) : NaN;
591
+ };
592
+ const checkedState = (value, tenantId, instanceId) => {
593
+ let state;
594
+ try {
595
+ state = snapshot(value, 'stored remedy state');
596
+ }
597
+ catch {
598
+ return fail('store_state_invalid');
599
+ }
600
+ return validState(state, tenantId, instanceId)
601
+ ? pass({ ok: true, state })
602
+ : fail('store_state_invalid');
603
+ };
604
+ const normalizeStoreResult = (value, tenantId, instanceId, proposedState) => {
605
+ if (!isDataRecord(value))
606
+ return fail('store_response_invalid');
607
+ if (value.ok === true)
608
+ return checkedState(value.state ?? proposedState, tenantId, instanceId);
609
+ if (value.applied === true && proposedState)
610
+ return checkedState(value.state ?? proposedState, tenantId, instanceId);
611
+ const reason = value.reason;
612
+ if ([
613
+ 'instance_not_found', 'instance_exists', 'revision_conflict', 'clock_regression',
614
+ 'evidence_replayed', 'remedy_operation_replayed',
615
+ ].includes(reason)) {
616
+ return fail(reason);
617
+ }
618
+ if (value.ok === false || value.applied === false)
619
+ return fail('store_operation_failed');
620
+ return fail('store_response_invalid');
621
+ };
622
+ const load = async (tenantId, instanceId, creationProbe = false) => {
623
+ if (!validContext(tenantId) || !validId(instanceId))
624
+ return fail('remedy_lookup_invalid');
625
+ try {
626
+ const result = normalizeStoreResult(await store.get({ tenantId, instanceId }), tenantId, instanceId);
627
+ return result.reason === 'instance_not_found' && !creationProbe
628
+ ? fail('remedy_case_not_found') : result;
629
+ }
630
+ catch {
631
+ return fail('store_unavailable');
632
+ }
633
+ };
634
+ const commit = async (state, next) => {
635
+ try {
636
+ const result = normalizeStoreResult(await store.compareAndSwap({
637
+ tenantId: state.tenant_id,
638
+ instanceId: state.instance_id,
639
+ expectedRevision: state.revision,
640
+ state: next,
641
+ }), state.tenant_id, state.instance_id, next);
642
+ return result.reason === 'revision_conflict' ? fail('state_transition_conflict') : result;
643
+ }
644
+ catch {
645
+ return fail('store_unavailable');
646
+ }
647
+ };
648
+ const operationInput = (input, keys, reason) => {
649
+ let value;
650
+ try {
651
+ value = snapshot(input, reason);
652
+ }
653
+ catch {
654
+ return fail(reason);
655
+ }
656
+ return exactKeys(value, keys) ? value : fail(reason);
657
+ };
658
+ const transitionTime = (state) => {
659
+ const current = clock();
660
+ if (!Number.isFinite(current))
661
+ return fail('clock_invalid');
662
+ if (current < instant(state.updated_at))
663
+ return fail('clock_regression');
664
+ return current;
665
+ };
666
+ const touch = (state, at) => {
667
+ const next = clone(state);
668
+ next.revision += 1;
669
+ next.updated_at = new Date(at).toISOString();
670
+ return next;
671
+ };
672
+ const invoke = async (callback, input) => {
673
+ try {
674
+ const result = await callback(verifierInput(input));
675
+ return snapshot(result, 'verifier projection');
676
+ }
677
+ catch {
678
+ return null;
679
+ }
680
+ };
681
+ async function create(input) {
682
+ const parsed = operationInput(input, CREATE_KEYS, 'create_input_invalid');
683
+ if (isFailure(parsed))
684
+ return parsed;
685
+ const value = parsed;
686
+ if (!validId(value.instanceId) || !validContext(value.tenantId)
687
+ || !validContext(value.environment) || !validContext(value.audience)
688
+ || !validOriginal(value.original)
689
+ || typeof value.remedyProfileDigest !== 'string' || !DIGEST.test(value.remedyProfileDigest)
690
+ || typeof value.destinationBindingDigest !== 'string' || !DIGEST.test(value.destinationBindingDigest)
691
+ || !Number.isSafeInteger(value.maxRemedyUnits) || value.maxRemedyUnits < 1
692
+ || !validContext(value.unit) || !isDataRecord(value.evidence)) {
693
+ return fail('create_input_invalid');
694
+ }
695
+ const requestDigest = digest(value);
696
+ const existing = await load(value.tenantId, value.instanceId, true);
697
+ if (existing.ok) {
698
+ return existing.state.create_request_digest === requestDigest
699
+ ? pass({ ok: true, idempotent: true, state: existing.state })
700
+ : fail('instance_exists');
701
+ }
702
+ if (existing.reason !== 'instance_not_found')
703
+ return existing;
704
+ const current = clock();
705
+ if (!Number.isFinite(current))
706
+ return fail('clock_invalid');
707
+ if (instant(value.original.occurred_at) > current)
708
+ return fail('original_effect_time_invalid');
709
+ const verified = await invoke(verifyOriginalEffect, {
710
+ original: clone(value.original),
711
+ evidence: clone(value.evidence),
712
+ expected: {
713
+ instance_id: value.instanceId,
714
+ tenant_id: value.tenantId,
715
+ environment: value.environment,
716
+ audience: value.audience,
717
+ remedy_profile_digest: value.remedyProfileDigest,
718
+ destination_binding_digest: value.destinationBindingDigest,
719
+ max_remedy_units: value.maxRemedyUnits,
720
+ unit: value.unit,
721
+ },
722
+ });
723
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_ORIGINAL_KEYS)) {
724
+ return fail('original_effect_invalid');
725
+ }
726
+ if (!ORIGINAL_KEYS.size || [...ORIGINAL_KEYS].some((key) => !same(verified[key], value.original[key]))
727
+ || verified.evidence_digest !== value.original.terminal_evidence_digest) {
728
+ return fail('original_effect_binding_mismatch');
729
+ }
730
+ const at = new Date(current).toISOString();
731
+ const state = {
732
+ version: REMEDY_PROGRAM_VERSION,
733
+ instance_id: value.instanceId,
734
+ tenant_id: value.tenantId,
735
+ environment: value.environment,
736
+ audience: value.audience,
737
+ status: value.original.outcome === 'executed' ? 'effect_executed' : 'effect_indeterminate',
738
+ revision: 0,
739
+ created_at: at,
740
+ updated_at: at,
741
+ original: withoutOk(verified),
742
+ remedy_profile_digest: value.remedyProfileDigest,
743
+ destination_binding_digest: value.destinationBindingDigest,
744
+ max_remedy_units: value.maxRemedyUnits,
745
+ unit: value.unit,
746
+ remedied_units: 0,
747
+ remaining_units: value.maxRemedyUnits,
748
+ used_evidence_ids: [],
749
+ used_evidence_digests: [value.original.terminal_evidence_digest],
750
+ original_reconciliation: null,
751
+ revocation: null,
752
+ dispute: null,
753
+ active_remedy: null,
754
+ remedies: [],
755
+ resolution: null,
756
+ create_request_digest: requestDigest,
757
+ };
758
+ if (!validState(state, value.tenantId, value.instanceId))
759
+ return fail('original_effect_invalid');
760
+ try {
761
+ const result = normalizeStoreResult(await store.create(snapshot(state, 'initial remedy state')), value.tenantId, value.instanceId, state);
762
+ if (result.reason !== 'instance_exists')
763
+ return result;
764
+ const raced = await load(value.tenantId, value.instanceId, true);
765
+ return raced.ok && raced.state.create_request_digest === requestDigest
766
+ ? pass({ ok: true, idempotent: true, state: raced.state })
767
+ : fail('instance_exists');
768
+ }
769
+ catch {
770
+ return fail('store_unavailable');
771
+ }
772
+ }
773
+ async function reconcileOriginalEffect(input) {
774
+ const parsed = operationInput(input, ORIGINAL_RECONCILIATION_INPUT_KEYS, 'original_reconciliation_input_invalid');
775
+ if (isFailure(parsed))
776
+ return parsed;
777
+ const value = parsed;
778
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
779
+ || !['executed', 'proved_no_effect'].includes(value.outcome)
780
+ || !exactKeys(value.evidence, ORIGINAL_RECONCILIATION_EVIDENCE_KEYS)
781
+ || !validEvidencePair(value.evidence.evidence_id, value.evidence.evidence_digest)
782
+ || !Number.isFinite(instant(value.evidence.observed_at))) {
783
+ return fail('original_reconciliation_input_invalid');
784
+ }
785
+ const loaded = await load(value.tenantId, value.instanceId);
786
+ if (!loaded.ok)
787
+ return loaded;
788
+ const state = loaded.state;
789
+ const requestDigest = digest(value);
790
+ if (state.original.outcome !== 'indeterminate')
791
+ return fail('original_effect_already_conclusive');
792
+ if (state.original_reconciliation !== null) {
793
+ return state.original_reconciliation.request_digest === requestDigest
794
+ ? pass({ ok: true, idempotent: true, state })
795
+ : fail('original_reconciliation_conflict');
796
+ }
797
+ if (!['effect_indeterminate', 'disputed'].includes(state.status)
798
+ || state.active_remedy !== null || state.remedies.length !== 0) {
799
+ return fail('original_reconciliation_unavailable');
800
+ }
801
+ if (evidenceUsed(state, value.evidence.evidence_id, value.evidence.evidence_digest)) {
802
+ return fail('evidence_replayed');
803
+ }
804
+ const at = transitionTime(state);
805
+ if (typeof at !== 'number')
806
+ return at;
807
+ const observedAt = instant(value.evidence.observed_at);
808
+ if (observedAt < instant(state.original.occurred_at) || observedAt > at) {
809
+ return fail('original_reconciliation_time_invalid');
810
+ }
811
+ const verified = await invoke(verifyOriginalReconciliation, {
812
+ evidence: clone(value.evidence), outcome: value.outcome,
813
+ expected: expectedContext(state),
814
+ });
815
+ if (!verified || verified.ok !== true
816
+ || !exactKeys(verified, VERIFIED_ORIGINAL_RECONCILIATION_KEYS)) {
817
+ return fail('original_reconciliation_invalid');
818
+ }
819
+ if (verified.evidence_id !== value.evidence.evidence_id
820
+ || verified.evidence_digest !== value.evidence.evidence_digest
821
+ || verified.original_operation_id !== state.original.operation_id
822
+ || verified.original_action_digest !== state.original.action_digest
823
+ || verified.terminal_evidence_digest !== state.original.terminal_evidence_digest
824
+ || verified.outcome !== value.outcome
825
+ || verified.observed_at !== value.evidence.observed_at) {
826
+ return fail('original_reconciliation_binding_mismatch');
827
+ }
828
+ const next = touch(state, at);
829
+ next.original_reconciliation = {
830
+ ...withoutOk(verified), request_digest: requestDigest,
831
+ };
832
+ next.status = value.outcome === 'executed'
833
+ ? (state.dispute === null ? 'effect_executed' : 'disputed')
834
+ : 'original_proved_no_effect';
835
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
836
+ const committed = await commit(state, next);
837
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
838
+ }
839
+ async function recordRevocation(input) {
840
+ const parsed = operationInput(input, REVOCATION_INPUT_KEYS, 'revocation_input_invalid');
841
+ if (isFailure(parsed))
842
+ return parsed;
843
+ const value = parsed;
844
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
845
+ || !exactKeys(value.evidence, REVOCATION_EVIDENCE_KEYS)
846
+ || !validEvidencePair(value.evidence.id, value.evidence.digest)) {
847
+ return fail('revocation_input_invalid');
848
+ }
849
+ const loaded = await load(value.tenantId, value.instanceId);
850
+ if (!loaded.ok)
851
+ return loaded;
852
+ const state = loaded.state;
853
+ const requestDigest = digest(value);
854
+ if (state.revocation !== null) {
855
+ return state.revocation.request_digest === requestDigest
856
+ ? pass({ ok: true, idempotent: true, code: 'late_revocation_recorded', state })
857
+ : fail('revocation_already_recorded');
858
+ }
859
+ if (evidenceUsed(state, value.evidence.id, value.evidence.digest))
860
+ return fail('evidence_replayed');
861
+ const at = transitionTime(state);
862
+ if (typeof at !== 'number')
863
+ return at;
864
+ const verified = await invoke(verifyRevocation, {
865
+ evidence: clone(value.evidence), expected: expectedContext(state),
866
+ });
867
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_REVOCATION_KEYS)) {
868
+ return fail('revocation_invalid');
869
+ }
870
+ if (verified.evidence_id !== value.evidence.id
871
+ || verified.evidence_digest !== value.evidence.digest
872
+ || verified.target_operation_id !== state.original.operation_id
873
+ || verified.action_digest !== state.original.action_digest) {
874
+ return fail('revocation_binding_mismatch');
875
+ }
876
+ const revokedAt = instant(verified.revoked_at);
877
+ if (!validContext(verified.authority_id) || !Number.isFinite(revokedAt)
878
+ || revokedAt < instant(state.original.occurred_at) || revokedAt > at) {
879
+ return fail('revocation_time_invalid');
880
+ }
881
+ const next = touch(state, at);
882
+ next.revocation = {
883
+ ...withoutOk(verified),
884
+ effect: 'future_authority_only',
885
+ request_digest: requestDigest,
886
+ };
887
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
888
+ const committed = await commit(state, next);
889
+ return committed.ok
890
+ ? pass({ ok: true, code: 'late_revocation_recorded', state: committed.state })
891
+ : committed;
892
+ }
893
+ async function openDispute(input) {
894
+ const parsed = operationInput(input, DISPUTE_INPUT_KEYS, 'dispute_input_invalid');
895
+ if (isFailure(parsed))
896
+ return parsed;
897
+ const value = parsed;
898
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
899
+ || !exactKeys(value.dispute, DISPUTE_KEYS)
900
+ || !validId(value.dispute.dispute_id)
901
+ || !validEvidencePair(value.dispute.evidence_id, value.dispute.evidence_digest)
902
+ || !validContext(value.dispute.challenger_id)
903
+ || !Number.isSafeInteger(value.dispute.requested_units) || value.dispute.requested_units < 1
904
+ || !Number.isFinite(instant(value.dispute.opened_at))) {
905
+ return fail('dispute_input_invalid');
906
+ }
907
+ const loaded = await load(value.tenantId, value.instanceId);
908
+ if (!loaded.ok)
909
+ return loaded;
910
+ const state = loaded.state;
911
+ const requestDigest = digest(value);
912
+ if (state.dispute !== null) {
913
+ return state.dispute.request_digest === requestDigest
914
+ ? pass({ ok: true, idempotent: true, state })
915
+ : fail('dispute_already_open');
916
+ }
917
+ if (!['effect_executed', 'effect_indeterminate'].includes(state.status)) {
918
+ return fail('remedy_case_unavailable');
919
+ }
920
+ if (evidenceUsed(state, value.dispute.evidence_id, value.dispute.evidence_digest)) {
921
+ return fail('evidence_replayed');
922
+ }
923
+ const at = transitionTime(state);
924
+ if (typeof at !== 'number')
925
+ return at;
926
+ const openedAt = instant(value.dispute.opened_at);
927
+ if (openedAt < instant(state.original.occurred_at) || openedAt > at || at - openedAt > maxDisputeAgeMs) {
928
+ return fail('dispute_not_fresh');
929
+ }
930
+ if (value.dispute.requested_units > state.remaining_units)
931
+ return fail('dispute_limit_exceeded');
932
+ const verified = await invoke(verifyDispute, {
933
+ dispute: clone(value.dispute), expected: expectedContext(state),
934
+ });
935
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_DISPUTE_KEYS)) {
936
+ return fail('dispute_invalid');
937
+ }
938
+ if ([...DISPUTE_KEYS].some((key) => !same(verified[key], value.dispute[key]))
939
+ || verified.original_operation_id !== state.original.operation_id
940
+ || verified.original_action_digest !== state.original.action_digest) {
941
+ return fail('dispute_binding_mismatch');
942
+ }
943
+ const next = touch(state, at);
944
+ next.status = 'disputed';
945
+ next.dispute = { ...withoutOk(verified), request_digest: requestDigest };
946
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
947
+ const committed = await commit(state, next);
948
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
949
+ }
950
+ const allAttempts = (state) => [
951
+ ...(state.active_remedy ? [state.active_remedy] : []), ...state.remedies,
952
+ ];
953
+ async function authorizeRemedy(input) {
954
+ const parsed = operationInput(input, AUTHORIZATION_INPUT_KEYS, 'remedy_authorization_input_invalid');
955
+ if (isFailure(parsed))
956
+ return parsed;
957
+ const value = parsed;
958
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
959
+ || !exactKeys(value.authorization, AUTHORIZATION_KEYS)
960
+ || !validEvidencePair(value.authorization.evidence_id, value.authorization.evidence_digest)
961
+ || !validId(value.authorization.remedy_operation_id)
962
+ || typeof value.authorization.remedy_caid !== 'string' || !CAID.test(value.authorization.remedy_caid)
963
+ || typeof value.authorization.remedy_action_digest !== 'string'
964
+ || !DIGEST.test(value.authorization.remedy_action_digest)
965
+ || !Number.isSafeInteger(value.authorization.units) || value.authorization.units < 1
966
+ || !Number.isFinite(instant(value.authorization.authorized_at))) {
967
+ return fail('remedy_authorization_input_invalid');
968
+ }
969
+ if (!validRemedyOwner(value.authorization))
970
+ return fail('remedy_owner_invalid');
971
+ const loaded = await load(value.tenantId, value.instanceId);
972
+ if (!loaded.ok)
973
+ return loaded;
974
+ const state = loaded.state;
975
+ const requestDigest = digest(value);
976
+ if (state.status === 'original_proved_no_effect')
977
+ return fail('remedy_case_terminal');
978
+ if (state.original.outcome === 'indeterminate'
979
+ && state.original_reconciliation?.outcome !== 'executed') {
980
+ return fail('original_effect_indeterminate');
981
+ }
982
+ const priorOperation = allAttempts(state).find((attempt) => (attempt.remedy_operation_id === value.authorization.remedy_operation_id));
983
+ if (priorOperation) {
984
+ return priorOperation.request_digest === requestDigest
985
+ ? pass({ ok: true, idempotent: true, state })
986
+ : fail('remedy_operation_replayed');
987
+ }
988
+ if (state.remaining_units === 0)
989
+ return fail('remedy_limit_exhausted');
990
+ if (['remedied', 'resolved_no_remedy'].includes(state.status))
991
+ return fail('remedy_case_terminal');
992
+ if (state.active_remedy !== null) {
993
+ return state.status === 'remedy_indeterminate'
994
+ ? fail('remedy_indeterminate') : fail('remedy_already_active');
995
+ }
996
+ if (!['disputed', 'partially_remedied'].includes(state.status) || state.dispute === null) {
997
+ return fail('remedy_case_unavailable');
998
+ }
999
+ if (evidenceUsed(state, value.authorization.evidence_id, value.authorization.evidence_digest)) {
1000
+ return fail('evidence_replayed');
1001
+ }
1002
+ const at = transitionTime(state);
1003
+ if (typeof at !== 'number')
1004
+ return at;
1005
+ if (instant(value.authorization.authorized_at) > at
1006
+ || instant(value.authorization.authorized_at) < instant(state.dispute.opened_at)) {
1007
+ return fail('remedy_authorization_time_invalid');
1008
+ }
1009
+ const disputeRemaining = state.dispute.requested_units - state.remedied_units;
1010
+ if (value.authorization.units > state.remaining_units || value.authorization.units > disputeRemaining) {
1011
+ return fail('remedy_limit_exceeded');
1012
+ }
1013
+ if (value.authorization.remedy_operation_id === state.original.operation_id
1014
+ || value.authorization.remedy_action_digest === state.original.action_digest) {
1015
+ return fail('remedy_must_be_compensating');
1016
+ }
1017
+ const verified = await invoke(verifyRemedyAuthorization, {
1018
+ authorization: clone(value.authorization), expected: expectedContext(state),
1019
+ });
1020
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_AUTHORIZATION_KEYS)) {
1021
+ return fail('remedy_authorization_invalid');
1022
+ }
1023
+ if (!validRemedyOwner(verified)) {
1024
+ return fail('remedy_owner_invalid');
1025
+ }
1026
+ if ([...AUTHORIZATION_KEYS].some((key) => !same(verified[key], value.authorization[key]))
1027
+ || verified.dispute_id !== state.dispute.dispute_id
1028
+ || verified.original_operation_id !== state.original.operation_id
1029
+ || verified.destination_binding_digest !== state.destination_binding_digest
1030
+ || verified.unit !== state.unit) {
1031
+ return fail('remedy_authorization_binding_mismatch');
1032
+ }
1033
+ const next = touch(state, at);
1034
+ next.status = 'remedy_authorized';
1035
+ next.active_remedy = {
1036
+ ...withoutOk(verified),
1037
+ request_digest: requestDigest,
1038
+ status: 'authorized',
1039
+ claim_token_digest: null,
1040
+ claimed_at: null,
1041
+ claim_request_digest: null,
1042
+ outcome: null,
1043
+ outcome_evidence: null,
1044
+ finalize_request_digest: null,
1045
+ reconciliation: null,
1046
+ reconcile_request_digest: null,
1047
+ };
1048
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
1049
+ const committed = await commit(state, next);
1050
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
1051
+ }
1052
+ async function claimRemedy(input) {
1053
+ const parsed = operationInput(input, CLAIM_KEYS, 'remedy_claim_input_invalid');
1054
+ if (isFailure(parsed))
1055
+ return parsed;
1056
+ const value = parsed;
1057
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
1058
+ || !validId(value.remedyOperationId)
1059
+ || !validContext(value.claimToken) || Buffer.byteLength(value.claimToken, 'utf8') > 256) {
1060
+ return fail('remedy_claim_input_invalid');
1061
+ }
1062
+ const loaded = await load(value.tenantId, value.instanceId);
1063
+ if (!loaded.ok)
1064
+ return loaded;
1065
+ const state = loaded.state;
1066
+ if (state.status === 'original_proved_no_effect')
1067
+ return fail('remedy_case_terminal');
1068
+ if (state.original.outcome === 'indeterminate'
1069
+ && state.original_reconciliation?.outcome !== 'executed') {
1070
+ return fail('original_effect_indeterminate');
1071
+ }
1072
+ const requestDigest = digest(value);
1073
+ const prior = allAttempts(state).find((attempt) => attempt.remedy_operation_id === value.remedyOperationId);
1074
+ if (prior?.claim_request_digest === requestDigest) {
1075
+ if (state.status === 'remedy_indeterminate' && state.active_remedy === prior) {
1076
+ return fail('remedy_indeterminate');
1077
+ }
1078
+ return pass({ ok: true, idempotent: true, state });
1079
+ }
1080
+ if (state.status === 'remedy_indeterminate')
1081
+ return fail('remedy_indeterminate');
1082
+ if (!state.active_remedy)
1083
+ return prior ? fail('remedy_already_finalized') : fail('remedy_not_authorized');
1084
+ if (state.active_remedy.remedy_operation_id !== value.remedyOperationId) {
1085
+ return fail('remedy_operation_mismatch');
1086
+ }
1087
+ if (state.active_remedy.status === 'claimed')
1088
+ return fail('remedy_claim_owned');
1089
+ if (state.active_remedy.status !== 'authorized' || state.status !== 'remedy_authorized') {
1090
+ return fail('remedy_not_authorized');
1091
+ }
1092
+ const at = transitionTime(state);
1093
+ if (typeof at !== 'number')
1094
+ return at;
1095
+ const next = touch(state, at);
1096
+ next.status = 'remedy_claimed';
1097
+ next.active_remedy.status = 'claimed';
1098
+ next.active_remedy.claim_token_digest = tokenDigest(value.claimToken);
1099
+ next.active_remedy.claimed_at = new Date(at).toISOString();
1100
+ next.active_remedy.claim_request_digest = requestDigest;
1101
+ const committed = await commit(state, next);
1102
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
1103
+ }
1104
+ const outcomeExpected = (state, attempt) => ({
1105
+ ...clone(expectedContext(state)),
1106
+ remedy_operation_id: attempt.remedy_operation_id,
1107
+ remedy_caid: attempt.remedy_caid,
1108
+ remedy_action_digest: attempt.remedy_action_digest,
1109
+ destination_binding_digest: attempt.destination_binding_digest,
1110
+ units: attempt.units,
1111
+ unit: attempt.unit,
1112
+ });
1113
+ const outcomeBound = (verified, value, attempt) => verified.evidence_id === value.evidence.evidence_id
1114
+ && verified.evidence_digest === value.evidence.evidence_digest
1115
+ && verified.remedy_operation_id === attempt.remedy_operation_id
1116
+ && verified.remedy_action_digest === attempt.remedy_action_digest
1117
+ && verified.destination_binding_digest === attempt.destination_binding_digest
1118
+ && verified.units === attempt.units && verified.unit === attempt.unit
1119
+ && verified.outcome === value.outcome
1120
+ && verified.observed_at === value.evidence.observed_at;
1121
+ async function finalizeRemedy(input) {
1122
+ const parsed = operationInput(input, FINALIZE_KEYS, 'remedy_outcome_input_invalid');
1123
+ if (isFailure(parsed))
1124
+ return parsed;
1125
+ const value = parsed;
1126
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
1127
+ || !validId(value.remedyOperationId)
1128
+ || !validContext(value.claimToken) || Buffer.byteLength(value.claimToken, 'utf8') > 256
1129
+ || !['executed', 'proved_no_effect', 'indeterminate'].includes(value.outcome)
1130
+ || !exactKeys(value.evidence, OUTCOME_EVIDENCE_KEYS)
1131
+ || !validEvidencePair(value.evidence.evidence_id, value.evidence.evidence_digest)
1132
+ || !Number.isFinite(instant(value.evidence.observed_at))) {
1133
+ return fail('remedy_outcome_input_invalid');
1134
+ }
1135
+ const loaded = await load(value.tenantId, value.instanceId);
1136
+ if (!loaded.ok)
1137
+ return loaded;
1138
+ const state = loaded.state;
1139
+ const requestDigest = digest(value);
1140
+ const prior = allAttempts(state).find((attempt) => attempt.remedy_operation_id === value.remedyOperationId);
1141
+ if (prior?.finalize_request_digest === requestDigest) {
1142
+ return pass({ ok: true, idempotent: true, state });
1143
+ }
1144
+ if (state.status === 'remedy_indeterminate')
1145
+ return fail('remedy_indeterminate');
1146
+ if (!state.active_remedy)
1147
+ return prior ? fail('remedy_already_finalized') : fail('remedy_not_authorized');
1148
+ const attempt = state.active_remedy;
1149
+ if (attempt.remedy_operation_id !== value.remedyOperationId)
1150
+ return fail('remedy_operation_mismatch');
1151
+ if (attempt.status !== 'claimed' || state.status !== 'remedy_claimed')
1152
+ return fail('remedy_not_claimed');
1153
+ if (attempt.claim_token_digest !== tokenDigest(value.claimToken))
1154
+ return fail('remedy_claim_owned');
1155
+ if (evidenceUsed(state, value.evidence.evidence_id, value.evidence.evidence_digest)) {
1156
+ return fail('evidence_replayed');
1157
+ }
1158
+ const at = transitionTime(state);
1159
+ if (typeof at !== 'number')
1160
+ return at;
1161
+ if (instant(value.evidence.observed_at) < instant(attempt.authorized_at)
1162
+ || instant(value.evidence.observed_at) > at)
1163
+ return fail('remedy_outcome_time_invalid');
1164
+ const verified = await invoke(verifyRemedyOutcome, {
1165
+ evidence: clone(value.evidence), outcome: value.outcome,
1166
+ expected: outcomeExpected(state, attempt), reconciliation: false,
1167
+ });
1168
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_OUTCOME_KEYS)) {
1169
+ return fail('remedy_outcome_invalid');
1170
+ }
1171
+ if (!outcomeBound(verified, value, attempt)) {
1172
+ return fail('remedy_outcome_binding_mismatch');
1173
+ }
1174
+ if (value.outcome === 'executed' && attempt.units > state.remaining_units) {
1175
+ return fail('remedy_limit_exceeded');
1176
+ }
1177
+ const next = touch(state, at);
1178
+ const completed = next.active_remedy;
1179
+ completed.status = value.outcome;
1180
+ completed.outcome = value.outcome;
1181
+ completed.outcome_evidence = withoutOk(verified);
1182
+ completed.finalize_request_digest = requestDigest;
1183
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
1184
+ if (value.outcome === 'indeterminate') {
1185
+ next.status = 'remedy_indeterminate';
1186
+ }
1187
+ else {
1188
+ next.active_remedy = null;
1189
+ next.remedies.push(completed);
1190
+ if (value.outcome === 'executed') {
1191
+ next.remedied_units += completed.units;
1192
+ next.remaining_units -= completed.units;
1193
+ next.status = next.remaining_units === 0 ? 'remedied' : 'partially_remedied';
1194
+ }
1195
+ else {
1196
+ next.status = 'disputed';
1197
+ }
1198
+ }
1199
+ const committed = await commit(state, next);
1200
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
1201
+ }
1202
+ async function reconcileRemedy(input) {
1203
+ const parsed = operationInput(input, RECONCILE_KEYS, 'remedy_reconciliation_input_invalid');
1204
+ if (isFailure(parsed))
1205
+ return parsed;
1206
+ const value = parsed;
1207
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
1208
+ || !validId(value.remedyOperationId)
1209
+ || !['executed', 'proved_no_effect'].includes(value.outcome)
1210
+ || !exactKeys(value.evidence, OUTCOME_EVIDENCE_KEYS)
1211
+ || !validEvidencePair(value.evidence.evidence_id, value.evidence.evidence_digest)
1212
+ || !Number.isFinite(instant(value.evidence.observed_at))) {
1213
+ return fail('remedy_reconciliation_input_invalid');
1214
+ }
1215
+ const loaded = await load(value.tenantId, value.instanceId);
1216
+ if (!loaded.ok)
1217
+ return loaded;
1218
+ const state = loaded.state;
1219
+ const requestDigest = digest(value);
1220
+ const prior = allAttempts(state).find((attempt) => attempt.remedy_operation_id === value.remedyOperationId);
1221
+ if (prior?.reconcile_request_digest === requestDigest) {
1222
+ return pass({ ok: true, idempotent: true, state });
1223
+ }
1224
+ if (state.status !== 'remedy_indeterminate' || !state.active_remedy
1225
+ || state.active_remedy.status !== 'indeterminate') {
1226
+ return prior ? fail('remedy_reconciliation_conflict') : fail('remedy_not_indeterminate');
1227
+ }
1228
+ const attempt = state.active_remedy;
1229
+ if (attempt.remedy_operation_id !== value.remedyOperationId)
1230
+ return fail('remedy_operation_mismatch');
1231
+ if (evidenceUsed(state, value.evidence.evidence_id, value.evidence.evidence_digest)) {
1232
+ return fail('evidence_replayed');
1233
+ }
1234
+ const at = transitionTime(state);
1235
+ if (typeof at !== 'number')
1236
+ return at;
1237
+ if (instant(value.evidence.observed_at) < instant(attempt.outcome_evidence.observed_at)
1238
+ || instant(value.evidence.observed_at) > at)
1239
+ return fail('remedy_reconciliation_time_invalid');
1240
+ const verified = await invoke(verifyRemedyOutcome, {
1241
+ evidence: clone(value.evidence), outcome: value.outcome,
1242
+ expected: outcomeExpected(state, attempt), reconciliation: true,
1243
+ });
1244
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_OUTCOME_KEYS)) {
1245
+ return fail('remedy_reconciliation_invalid');
1246
+ }
1247
+ if (!outcomeBound(verified, value, attempt)) {
1248
+ return fail('remedy_reconciliation_binding_mismatch');
1249
+ }
1250
+ if (value.outcome === 'executed' && attempt.units > state.remaining_units) {
1251
+ return fail('remedy_limit_exceeded');
1252
+ }
1253
+ const next = touch(state, at);
1254
+ const completed = next.active_remedy;
1255
+ completed.status = value.outcome;
1256
+ completed.outcome = value.outcome;
1257
+ completed.reconciliation = withoutOk(verified);
1258
+ completed.reconcile_request_digest = requestDigest;
1259
+ next.active_remedy = null;
1260
+ next.remedies.push(completed);
1261
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
1262
+ if (value.outcome === 'executed') {
1263
+ next.remedied_units += completed.units;
1264
+ next.remaining_units -= completed.units;
1265
+ next.status = next.remaining_units === 0 ? 'remedied' : 'partially_remedied';
1266
+ }
1267
+ else {
1268
+ next.status = 'disputed';
1269
+ }
1270
+ const committed = await commit(state, next);
1271
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
1272
+ }
1273
+ async function resolveDispute(input) {
1274
+ const parsed = operationInput(input, RESOLUTION_INPUT_KEYS, 'resolution_input_invalid');
1275
+ if (isFailure(parsed))
1276
+ return parsed;
1277
+ const value = parsed;
1278
+ if (!validContext(value.tenantId) || !validId(value.instanceId)
1279
+ || !exactKeys(value.resolution, RESOLUTION_KEYS)
1280
+ || !validEvidencePair(value.resolution.evidence_id, value.resolution.evidence_digest)
1281
+ || value.resolution.outcome !== 'no_remedy'
1282
+ || !Number.isFinite(instant(value.resolution.resolved_at))) {
1283
+ return fail('resolution_input_invalid');
1284
+ }
1285
+ const loaded = await load(value.tenantId, value.instanceId);
1286
+ if (!loaded.ok)
1287
+ return loaded;
1288
+ const state = loaded.state;
1289
+ const requestDigest = digest(value);
1290
+ if (state.resolution !== null) {
1291
+ return state.resolution.request_digest === requestDigest
1292
+ ? pass({ ok: true, idempotent: true, state })
1293
+ : fail('remedy_case_terminal');
1294
+ }
1295
+ if (['remedied', 'resolved_no_remedy'].includes(state.status))
1296
+ return fail('remedy_case_terminal');
1297
+ if (!['disputed', 'partially_remedied'].includes(state.status)
1298
+ || state.dispute === null || state.active_remedy !== null) {
1299
+ return fail('resolution_unavailable');
1300
+ }
1301
+ if (typeof verifyResolution !== 'function')
1302
+ return fail('resolution_verifier_unavailable');
1303
+ if (evidenceUsed(state, value.resolution.evidence_id, value.resolution.evidence_digest)) {
1304
+ return fail('evidence_replayed');
1305
+ }
1306
+ const at = transitionTime(state);
1307
+ if (typeof at !== 'number')
1308
+ return at;
1309
+ if (instant(value.resolution.resolved_at) < instant(state.dispute.opened_at)
1310
+ || instant(value.resolution.resolved_at) > at)
1311
+ return fail('resolution_time_invalid');
1312
+ const verified = await invoke(verifyResolution, {
1313
+ resolution: clone(value.resolution), expected: expectedContext(state),
1314
+ });
1315
+ if (!verified || verified.ok !== true || !exactKeys(verified, VERIFIED_RESOLUTION_KEYS)) {
1316
+ return fail('resolution_invalid');
1317
+ }
1318
+ if ([...RESOLUTION_KEYS].some((key) => !same(verified[key], value.resolution[key]))
1319
+ || verified.dispute_id !== state.dispute.dispute_id) {
1320
+ return fail('resolution_binding_mismatch');
1321
+ }
1322
+ const next = touch(state, at);
1323
+ next.status = 'resolved_no_remedy';
1324
+ next.resolution = { ...withoutOk(verified), request_digest: requestDigest };
1325
+ consumeEvidence(next, verified.evidence_id, verified.evidence_digest);
1326
+ const committed = await commit(state, next);
1327
+ return committed.ok ? pass({ ok: true, state: committed.state }) : committed;
1328
+ }
1329
+ async function status(input) {
1330
+ const parsed = operationInput(input, new Set(['tenantId', 'instanceId']), 'remedy_lookup_invalid');
1331
+ if (isFailure(parsed))
1332
+ return parsed;
1333
+ const value = parsed;
1334
+ return load(value.tenantId, value.instanceId);
1335
+ }
1336
+ return Object.freeze({
1337
+ create, status, recordRevocation, reconcileOriginalEffect, openDispute, authorizeRemedy,
1338
+ claimRemedy, finalizeRemedy, reconcileRemedy, resolveDispute,
1339
+ });
1340
+ }
1341
+ export default {
1342
+ REMEDY_PROGRAM_VERSION,
1343
+ createRemedyMemoryStore,
1344
+ createRemedyProgramKernel,
1345
+ };
1346
+ //# sourceMappingURL=remedy-program.js.map