@emilia-protocol/gate 0.11.0 → 0.13.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 (419) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +252 -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 +1002 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +1736 -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/protocol-state.d.ts +141 -0
  203. package/dist/protocol-state.d.ts.map +1 -0
  204. package/dist/protocol-state.js +106 -0
  205. package/dist/protocol-state.js.map +1 -0
  206. package/dist/receipt-program.d.ts +59 -0
  207. package/dist/receipt-program.d.ts.map +1 -0
  208. package/dist/receipt-program.js +1053 -0
  209. package/dist/receipt-program.js.map +1 -0
  210. package/dist/reliance-kernel.d.ts +66 -0
  211. package/dist/reliance-kernel.d.ts.map +1 -0
  212. package/dist/reliance-kernel.js +103 -0
  213. package/dist/reliance-kernel.js.map +1 -0
  214. package/dist/reliance-packet.d.ts +67 -0
  215. package/dist/reliance-packet.d.ts.map +1 -0
  216. package/dist/reliance-packet.js +176 -0
  217. package/dist/reliance-packet.js.map +1 -0
  218. package/dist/remedy-program-postgres.d.ts +37 -0
  219. package/dist/remedy-program-postgres.d.ts.map +1 -0
  220. package/dist/remedy-program-postgres.js +291 -0
  221. package/dist/remedy-program-postgres.js.map +1 -0
  222. package/dist/remedy-program-receipt.d.ts +97 -0
  223. package/dist/remedy-program-receipt.d.ts.map +1 -0
  224. package/dist/remedy-program-receipt.js +817 -0
  225. package/dist/remedy-program-receipt.js.map +1 -0
  226. package/dist/remedy-program.d.ts +76 -0
  227. package/dist/remedy-program.d.ts.map +1 -0
  228. package/dist/remedy-program.js +1346 -0
  229. package/dist/remedy-program.js.map +1 -0
  230. package/dist/reports/art14.d.ts +101 -0
  231. package/dist/reports/art14.d.ts.map +1 -0
  232. package/dist/reports/art14.js +340 -0
  233. package/dist/reports/art14.js.map +1 -0
  234. package/dist/reports/assurance-package.d.ts +197 -0
  235. package/dist/reports/assurance-package.d.ts.map +1 -0
  236. package/dist/reports/assurance-package.js +313 -0
  237. package/dist/reports/assurance-package.js.map +1 -0
  238. package/dist/reports/auditor-workpaper.d.ts +194 -0
  239. package/dist/reports/auditor-workpaper.d.ts.map +1 -0
  240. package/dist/reports/auditor-workpaper.js +545 -0
  241. package/dist/reports/auditor-workpaper.js.map +1 -0
  242. package/dist/reports/external-verification.d.ts +76 -0
  243. package/dist/reports/external-verification.d.ts.map +1 -0
  244. package/dist/reports/external-verification.js +209 -0
  245. package/dist/reports/external-verification.js.map +1 -0
  246. package/dist/reports/reperform.d.ts +122 -0
  247. package/dist/reports/reperform.d.ts.map +1 -0
  248. package/dist/reports/reperform.js +432 -0
  249. package/dist/reports/reperform.js.map +1 -0
  250. package/dist/reports/underwriter.d.ts +127 -0
  251. package/dist/reports/underwriter.d.ts.map +1 -0
  252. package/dist/reports/underwriter.js +348 -0
  253. package/dist/reports/underwriter.js.map +1 -0
  254. package/dist/retention.d.ts +88 -0
  255. package/dist/retention.d.ts.map +1 -0
  256. package/dist/retention.js +90 -0
  257. package/dist/retention.js.map +1 -0
  258. package/dist/roster.d.ts +113 -0
  259. package/dist/roster.d.ts.map +1 -0
  260. package/dist/roster.js +261 -0
  261. package/dist/roster.js.map +1 -0
  262. package/dist/runtime-monitor.d.ts +132 -0
  263. package/dist/runtime-monitor.d.ts.map +1 -0
  264. package/dist/runtime-monitor.js +197 -0
  265. package/dist/runtime-monitor.js.map +1 -0
  266. package/dist/settlement.d.ts +68 -0
  267. package/dist/settlement.d.ts.map +1 -0
  268. package/dist/settlement.js +306 -0
  269. package/dist/settlement.js.map +1 -0
  270. package/dist/siem.d.ts +261 -0
  271. package/dist/siem.d.ts.map +1 -0
  272. package/dist/siem.js +247 -0
  273. package/dist/siem.js.map +1 -0
  274. package/dist/store-postgres.d.ts +107 -0
  275. package/dist/store-postgres.d.ts.map +1 -0
  276. package/dist/store-postgres.js +152 -0
  277. package/dist/store-postgres.js.map +1 -0
  278. package/dist/store.d.ts +95 -0
  279. package/dist/store.d.ts.map +1 -0
  280. package/dist/store.js +202 -0
  281. package/dist/store.js.map +1 -0
  282. package/dist/strict-json.d.ts +14 -0
  283. package/dist/strict-json.d.ts.map +1 -0
  284. package/dist/strict-json.js +132 -0
  285. package/dist/strict-json.js.map +1 -0
  286. package/dist/trust-program-adapters.d.ts +122 -0
  287. package/dist/trust-program-adapters.d.ts.map +1 -0
  288. package/dist/trust-program-adapters.js +802 -0
  289. package/dist/trust-program-adapters.js.map +1 -0
  290. package/dist/trust-program-postgres.d.ts +41 -0
  291. package/dist/trust-program-postgres.d.ts.map +1 -0
  292. package/dist/trust-program-postgres.js +346 -0
  293. package/dist/trust-program-postgres.js.map +1 -0
  294. package/dist/trust-program-revocation.d.ts +206 -0
  295. package/dist/trust-program-revocation.d.ts.map +1 -0
  296. package/dist/trust-program-revocation.js +600 -0
  297. package/dist/trust-program-revocation.js.map +1 -0
  298. package/dist/trust-program.d.ts +197 -0
  299. package/dist/trust-program.d.ts.map +1 -0
  300. package/dist/trust-program.js +1351 -0
  301. package/dist/trust-program.js.map +1 -0
  302. package/dist/witness-postgres.d.ts +43 -0
  303. package/dist/witness-postgres.d.ts.map +1 -0
  304. package/dist/witness-postgres.js +92 -0
  305. package/dist/witness-postgres.js.map +1 -0
  306. package/dist/zk-range-proof.d.ts +78 -0
  307. package/dist/zk-range-proof.d.ts.map +1 -0
  308. package/dist/zk-range-proof.js +228 -0
  309. package/dist/zk-range-proof.js.map +1 -0
  310. package/eg1-conformance.js +3 -404
  311. package/enterprise.js +3 -180
  312. package/ep-assure.mjs +1 -0
  313. package/evidence-postgres.js +3 -356
  314. package/evidence.js +4 -360
  315. package/execution-binding.js +3 -212
  316. package/formal-runtime-map.js +4 -0
  317. package/index.js +4 -1249
  318. package/key-registry.js +3 -134
  319. package/mcp.js +3 -109
  320. package/metering.js +3 -226
  321. package/metrics.js +3 -231
  322. package/network-witness.js +3 -499
  323. package/package.json +31 -2
  324. package/receipt-program.js +4 -0
  325. package/reliance-kernel.js +3 -112
  326. package/reliance-packet.js +3 -175
  327. package/remedy-program-postgres.js +4 -0
  328. package/remedy-program-receipt.js +4 -0
  329. package/remedy-program.js +4 -0
  330. package/reports/art14.js +0 -0
  331. package/reports/assurance-package.js +2 -312
  332. package/reports/auditor-workpaper.js +3 -537
  333. package/reports/external-verification.js +3 -222
  334. package/reports/reperform.js +3 -424
  335. package/reports/underwriter.js +3 -339
  336. package/retention.js +3 -84
  337. package/roster.js +3 -264
  338. package/runtime-monitor.js +4 -0
  339. package/settlement.js +3 -299
  340. package/siem.js +3 -235
  341. package/src/action-control-manifest.ts +424 -0
  342. package/src/action-escrow-custodian.ts +427 -0
  343. package/src/action-escrow-evidence.ts +1178 -0
  344. package/src/action-escrow-package.ts +1821 -0
  345. package/src/action-escrow-postgres.ts +366 -0
  346. package/src/action-escrow-state.ts +498 -0
  347. package/src/action-escrow-verifiers.ts +484 -0
  348. package/src/action-escrow.ts +3039 -0
  349. package/src/action-packs.ts +170 -0
  350. package/src/adapters/_kit.ts +117 -0
  351. package/src/adapters/aws.ts +142 -0
  352. package/src/adapters/cloudflare.ts +78 -0
  353. package/src/adapters/gcp.ts +71 -0
  354. package/src/adapters/github.ts +110 -0
  355. package/src/adapters/jira.ts +56 -0
  356. package/src/adapters/k8s.ts +71 -0
  357. package/src/adapters/linear.ts +56 -0
  358. package/src/adapters/salesforce.ts +56 -0
  359. package/src/adapters/stripe.ts +82 -0
  360. package/src/adapters/supabase.ts +122 -0
  361. package/src/adapters/terraform.ts +60 -0
  362. package/src/adapters/vercel.ts +85 -0
  363. package/src/aec-execution.ts +354 -0
  364. package/src/breakglass.ts +647 -0
  365. package/src/capability-receipt.ts +1258 -0
  366. package/src/cf1-conformance.ts +120 -0
  367. package/src/challenge-store.ts +70 -0
  368. package/src/control-plane.ts +374 -0
  369. package/src/coverage.ts +832 -0
  370. package/src/deployment-attestation.ts +259 -0
  371. package/src/eg1-conformance.ts +471 -0
  372. package/src/enterprise.ts +195 -0
  373. package/src/evidence-postgres.ts +374 -0
  374. package/src/evidence.ts +367 -0
  375. package/src/execution-binding.ts +236 -0
  376. package/src/formal-runtime-map.ts +24 -0
  377. package/src/index.ts +2122 -0
  378. package/src/key-registry.ts +150 -0
  379. package/src/mcp.ts +120 -0
  380. package/src/metering.ts +237 -0
  381. package/src/metrics.ts +232 -0
  382. package/src/network-witness.ts +585 -0
  383. package/src/protocol-state.ts +143 -0
  384. package/src/protocol-state.typecheck.ts +94 -0
  385. package/src/receipt-program.ts +1105 -0
  386. package/src/reliance-kernel.ts +119 -0
  387. package/src/reliance-packet.ts +206 -0
  388. package/src/remedy-program-postgres.ts +367 -0
  389. package/src/remedy-program-receipt.ts +891 -0
  390. package/src/remedy-program.ts +1388 -0
  391. package/src/reports/art14.ts +361 -0
  392. package/src/reports/assurance-package.ts +330 -0
  393. package/src/reports/auditor-workpaper.ts +558 -0
  394. package/src/reports/external-verification.ts +232 -0
  395. package/src/reports/reperform.ts +441 -0
  396. package/src/reports/underwriter.ts +356 -0
  397. package/src/retention.ts +100 -0
  398. package/src/roster.ts +273 -0
  399. package/src/runtime-monitor.ts +217 -0
  400. package/src/settlement.ts +313 -0
  401. package/src/siem.ts +247 -0
  402. package/src/store-postgres.ts +167 -0
  403. package/src/store.ts +214 -0
  404. package/src/strict-json.ts +115 -0
  405. package/src/trust-program-adapters.ts +847 -0
  406. package/src/trust-program-postgres.ts +429 -0
  407. package/src/trust-program-revocation.ts +751 -0
  408. package/src/trust-program.ts +1460 -0
  409. package/src/witness-postgres.ts +106 -0
  410. package/src/zk-range-proof.ts +250 -0
  411. package/store-postgres.js +3 -161
  412. package/store.js +4 -198
  413. package/strict-json.js +3 -102
  414. package/trust-program-adapters.js +4 -0
  415. package/trust-program-postgres.js +4 -0
  416. package/trust-program-revocation.js +4 -0
  417. package/trust-program.js +4 -0
  418. package/witness-postgres.js +3 -96
  419. package/zk-range-proof.js +4 -0
@@ -0,0 +1,1821 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Strict adapter from one exact Action Escrow kernel snapshot to the portable
4
+ * Action Escrow evidence-package profile.
5
+ *
6
+ * This module does not accept package-field overrides. Every package field is
7
+ * either derived from the durable record or occupies one of the four external
8
+ * artifact slots that the kernel intentionally does not own.
9
+ */
10
+ import crypto from 'node:crypto';
11
+
12
+ import {
13
+ ACTION_ESCROW_PROFILE_VERSION,
14
+ ACTION_ESCROW_STATE_VERSION,
15
+ ACTION_ESCROW_STATES,
16
+ ACTION_ESCROW_TRANSITIONS,
17
+ computeActionEscrowReleaseBindingMomentDigest,
18
+ computeActionEscrowResolutionNonce,
19
+ } from './action-escrow.js';
20
+ import { buildActionEscrowEvidencePackage } from './action-escrow-evidence.js';
21
+ import {
22
+ ACTION_ESCROW_STATE_STATEMENT_DOMAIN,
23
+ ACTION_ESCROW_STATE_STATEMENT_VERSION,
24
+ } from './action-escrow-state.js';
25
+ import {
26
+ ACTION_ESCROW_CONTRACTOR_TEMPLATE_VERSION,
27
+ computeActionEscrowAgreementDigest,
28
+ validateActionEscrowReleaseTemplate,
29
+ } from './action-escrow-verifiers.js';
30
+ import { canonicalize, hashCanonical } from './execution-binding.js';
31
+ import {
32
+ computeDocumentActionBindingDigest,
33
+ computeDocumentSha256,
34
+ computeReleaseActionDigest,
35
+ } from '@emilia-protocol/verify/document-action-binding';
36
+
37
+ const HASH = /^sha256:[0-9a-f]{64}$/;
38
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:/#@+-]{0,255}$/;
39
+ const BASE64URL = /^[A-Za-z0-9_-]+$/;
40
+ const MAX_RECORD_NODES = 50_000;
41
+ const MAX_RECORD_DEPTH = 64;
42
+ const MAX_STRING_BYTES = 4 * 1024 * 1024;
43
+ const DEFAULT_MAX_DOCUMENT_BYTES = 50 * 1024 * 1024;
44
+
45
+ const INPUT_KEYS = new Set([
46
+ 'kernelRecord',
47
+ 'finalPdfBytes',
48
+ 'documentFileName',
49
+ 'documentExecution',
50
+ 'operatorStateStatement',
51
+ 'verificationProfile',
52
+ ]);
53
+ const CONTRACTOR_INPUT_KEYS = new Set([
54
+ ...INPUT_KEYS,
55
+ 'projectRecordBytes',
56
+ 'projectRecordFileName',
57
+ 'projectRecordProvider',
58
+ 'projectRecordSnapshotDigest',
59
+ ]);
60
+ const OPTION_KEYS = new Set(['now', 'maxDocumentBytes']);
61
+ const CONTRACTOR_OPTION_KEYS = new Set([
62
+ ...OPTION_KEYS,
63
+ 'maxProjectRecordBytes',
64
+ ]);
65
+ const RECORD_KEYS = new Set([
66
+ '@version',
67
+ 'escrow_key',
68
+ 'revision',
69
+ 'state',
70
+ 'agreement_digest',
71
+ 'document_action_binding_digest',
72
+ 'milestone_id',
73
+ 'release_action_digest',
74
+ 'parties',
75
+ 'parties_digest',
76
+ 'profile',
77
+ 'profile_digest',
78
+ 'document_action_binding',
79
+ 'agreement_acceptances',
80
+ 'funding',
81
+ 'milestone_evidence',
82
+ 'release_approvals',
83
+ 'release',
84
+ 'dispute',
85
+ 'cancellation',
86
+ 'completion',
87
+ 'pending_amendment',
88
+ 'superseded_bindings',
89
+ 'operations',
90
+ 'history',
91
+ 'created_at',
92
+ 'updated_at',
93
+ ]);
94
+ const PROFILE_KEYS = new Set([
95
+ '@version',
96
+ 'profile_id',
97
+ 'provider_id',
98
+ 'required_acceptance_party_ids',
99
+ 'required_release_approver_party_ids',
100
+ 'prohibit_self_approval',
101
+ ]);
102
+ const PARTY_KEYS = new Set(['party_id', 'role']);
103
+ const CONTAINER_KEYS = new Set(['artifact', 'verification']);
104
+ const ACCEPTANCE_KEYS = new Set(['party_id', 'artifact', 'verification']);
105
+ const RELEASE_APPROVAL_KEYS = new Set(['party_id', 'resolution', 'verification']);
106
+ const CORE_VERIFICATION_KEYS = [
107
+ 'valid',
108
+ 'agreement_digest',
109
+ 'document_action_binding_digest',
110
+ 'milestone_id',
111
+ 'release_action_digest',
112
+ 'parties_digest',
113
+ 'profile_digest',
114
+ ];
115
+ const BINDING_VERIFICATION_KEYS = new Set([
116
+ ...CORE_VERIFICATION_KEYS,
117
+ 'verification_digest',
118
+ 'document_digest',
119
+ 'agreement_id',
120
+ 'binding_id',
121
+ 'release_action_template',
122
+ 'supersedes_document_action_binding_digest',
123
+ ]);
124
+ const ACCEPTANCE_VERIFICATION_KEYS = new Set([
125
+ ...CORE_VERIFICATION_KEYS,
126
+ 'party_id',
127
+ 'principal_key_id',
128
+ 'acceptance_digest',
129
+ ]);
130
+ const FUNDING_VERIFICATION_KEYS = new Set([
131
+ ...CORE_VERIFICATION_KEYS,
132
+ 'authenticated',
133
+ 'provider_id',
134
+ 'statement_type',
135
+ 'status',
136
+ 'provider_transaction_id',
137
+ 'provider_milestone_id',
138
+ 'amount',
139
+ 'currency',
140
+ 'destination_id',
141
+ 'statement_digest',
142
+ ]);
143
+ const MILESTONE_VERIFICATION_KEYS = new Set([
144
+ ...CORE_VERIFICATION_KEYS,
145
+ 'evidence_digest',
146
+ 'submitter_party_id',
147
+ 'observed_at',
148
+ ]);
149
+ const RELEASE_APPROVAL_VERIFICATION_KEYS = new Set([
150
+ ...CORE_VERIFICATION_KEYS,
151
+ 'authorizes_action',
152
+ 'outcome',
153
+ 'party_role',
154
+ 'principal_key_id',
155
+ 'nonce',
156
+ 'issued_at',
157
+ 'expires_at',
158
+ 'resolution_digest',
159
+ 'evidence_digest',
160
+ ]);
161
+ const PROVIDER_VERIFICATION_KEYS = new Set([
162
+ ...CORE_VERIFICATION_KEYS,
163
+ 'authenticated',
164
+ 'provider_id',
165
+ 'provider_idempotency_key',
166
+ 'provider_request_digest',
167
+ 'provider_transaction_id',
168
+ 'provider_milestone_id',
169
+ 'amount',
170
+ 'currency',
171
+ 'destination_id',
172
+ 'statement_type',
173
+ 'status',
174
+ 'statement_digest',
175
+ ]);
176
+ const COMMAND_VERIFICATION_KEYS = new Set([
177
+ ...CORE_VERIFICATION_KEYS,
178
+ 'authorizes_command',
179
+ 'command',
180
+ 'party_id',
181
+ 'details_digest',
182
+ 'command_digest',
183
+ ]);
184
+ const OPERATION_KEYS = new Set([
185
+ 'idempotency_key',
186
+ 'operation',
187
+ 'request_digest',
188
+ 'code',
189
+ 'ok',
190
+ 'outcome',
191
+ 'state',
192
+ 'at',
193
+ ]);
194
+ const HISTORY_KEYS = new Set([
195
+ 'from',
196
+ 'to',
197
+ 'operation',
198
+ 'idempotency_key',
199
+ 'at',
200
+ ]);
201
+ const SUPERSESSION_KEYS = new Set([
202
+ 'document_action_binding_digest',
203
+ 'release_action_digest',
204
+ 'superseded_by_binding_digest',
205
+ 'superseded_at',
206
+ ]);
207
+ const RELEASE_KEYS = new Set([
208
+ 'release_key',
209
+ 'provider_idempotency_key',
210
+ 'operation_idempotency_key',
211
+ 'status',
212
+ 'reserved_at',
213
+ 'reconciled_at',
214
+ 'provider_request',
215
+ 'provider_statement',
216
+ 'provider_verification',
217
+ ]);
218
+ const PROVIDER_REQUEST_KEYS = new Set([
219
+ 'method',
220
+ 'provider_id',
221
+ 'agreement_digest',
222
+ 'document_action_binding_digest',
223
+ 'milestone_id',
224
+ 'release_action_digest',
225
+ 'parties',
226
+ 'parties_digest',
227
+ 'profile',
228
+ 'profile_digest',
229
+ 'agreement_id',
230
+ 'binding_id',
231
+ 'document_digest',
232
+ 'release_action_template',
233
+ 'release_key',
234
+ 'idempotency_key',
235
+ 'request_digest',
236
+ ]);
237
+ const PENDING_AMENDMENT_KEYS = new Set([
238
+ 'from_state',
239
+ 'document_action_binding_digest',
240
+ 'release_action_digest',
241
+ 'document_action_binding',
242
+ 'agreement_acceptances',
243
+ 'proposer_party_id',
244
+ 'proposal_authorization',
245
+ 'proposed_at',
246
+ ]);
247
+ const COMMAND_CONTAINER_KEYS = new Set(['artifact', 'verification']);
248
+ const DISPUTE_KEYS = new Set(['party_id', 'reason', 'authorization', 'opened_at']);
249
+ const CANCELLATION_KEYS = new Set([
250
+ 'party_id',
251
+ 'reason',
252
+ 'authorization',
253
+ 'cancelled_at',
254
+ ]);
255
+ const COMPLETION_KEYS = new Set([
256
+ 'party_id',
257
+ 'meaning',
258
+ 'authorization',
259
+ 'completed_at',
260
+ ]);
261
+ const STATE_STATEMENT_KEYS = new Set([
262
+ 'version',
263
+ 'issuer',
264
+ 'payload',
265
+ 'statement_digest',
266
+ 'signature',
267
+ ]);
268
+ const STATE_ISSUER_KEYS = new Set(['operator_id', 'key_id']);
269
+ const STATE_PAYLOAD_KEYS = new Set([
270
+ 'statement_id',
271
+ 'agreement_id',
272
+ 'binding_digest',
273
+ 'action_digest',
274
+ 'profile_digest',
275
+ 'state',
276
+ 'revision',
277
+ 'amendment_digests',
278
+ 'state_record_digest',
279
+ 'previous_statement_digest',
280
+ 'occurred_at',
281
+ ]);
282
+ const STATE_SIGNATURE_KEYS = new Set(['algorithm', 'signature_b64u']);
283
+ const PROFILE_REFERENCE_KEYS = new Set(['id', 'digest']);
284
+
285
+ const EXECUTED_AGREEMENT_STATES = new Set([
286
+ 'effective',
287
+ 'awaiting_funding',
288
+ 'funded',
289
+ 'milestone_submitted',
290
+ 'release_reserved',
291
+ 'released',
292
+ 'disputed',
293
+ 'amendment_pending',
294
+ 'completed',
295
+ 'release_indeterminate',
296
+ ]);
297
+ const FUNDING_STATES = new Set([
298
+ 'funded',
299
+ 'milestone_submitted',
300
+ 'release_reserved',
301
+ 'released',
302
+ 'disputed',
303
+ 'completed',
304
+ 'release_indeterminate',
305
+ ]);
306
+ const MILESTONE_STATES = new Set([
307
+ 'milestone_submitted',
308
+ 'release_reserved',
309
+ 'released',
310
+ 'disputed',
311
+ 'completed',
312
+ 'release_indeterminate',
313
+ ]);
314
+ const RELEASE_APPROVAL_STATES = new Set([
315
+ 'milestone_submitted',
316
+ 'release_reserved',
317
+ 'released',
318
+ 'disputed',
319
+ 'completed',
320
+ 'release_indeterminate',
321
+ ]);
322
+ const COMPLETE_RELEASE_APPROVAL_STATES = new Set([
323
+ 'release_reserved',
324
+ 'released',
325
+ 'completed',
326
+ 'release_indeterminate',
327
+ ]);
328
+ const REQUIRED_RELEASE_STATES = new Set([
329
+ 'release_reserved',
330
+ 'released',
331
+ 'completed',
332
+ 'release_indeterminate',
333
+ ]);
334
+ const OPTIONAL_RELEASE_STATES = new Set([
335
+ 'milestone_submitted',
336
+ 'disputed',
337
+ ]);
338
+ const KERNEL_OPERATIONS = new Set([
339
+ 'create',
340
+ 'begin_acceptance',
341
+ 'accept_agreement',
342
+ 'request_funding',
343
+ 'record_funding',
344
+ 'submit_milestone',
345
+ 'approve_release',
346
+ 'release',
347
+ 'reconcile_release',
348
+ 'open_dispute',
349
+ 'propose_amendment',
350
+ 'accept_amendment',
351
+ 'cancel',
352
+ 'complete',
353
+ ]);
354
+
355
+ const SECRET_FIELD_NAMES = new Set([
356
+ 'api_key',
357
+ 'api_token',
358
+ 'api_secret',
359
+ 'access_key',
360
+ 'access_key_id',
361
+ 'access_token',
362
+ 'refresh_token',
363
+ 'session_token',
364
+ 'oauth_token',
365
+ 'bearer_token',
366
+ 'client_secret',
367
+ 'client_password',
368
+ 'password',
369
+ 'passwd',
370
+ 'private_key',
371
+ 'private_key_pem',
372
+ 'secret',
373
+ 'secret_key',
374
+ 'secret_access_key',
375
+ 'signing_secret',
376
+ 'webhook_secret',
377
+ 'credential',
378
+ 'credentials',
379
+ 'provider_credentials',
380
+ 'authorization_header',
381
+ 'cookie',
382
+ 'set_cookie',
383
+ 'request_headers',
384
+ 'http_headers',
385
+ 'headers',
386
+ 'connection_string',
387
+ 'database_url',
388
+ ]);
389
+
390
+ function fail(reason): never {
391
+ throw new TypeError(`action-escrow package: ${reason}`);
392
+ }
393
+
394
+ function isRecord(value) {
395
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
396
+ const prototype = Object.getPrototypeOf(value);
397
+ return prototype === Object.prototype || prototype === null;
398
+ }
399
+
400
+ function exactKeys(value, allowed, required = allowed) {
401
+ if (!isRecord(value)) return false;
402
+ const keys = Object.keys(value);
403
+ return keys.every((key) => allowed.has(key))
404
+ && [...required].every((key) => Object.hasOwn(value, key));
405
+ }
406
+
407
+ function validString(value, max = 512) {
408
+ return typeof value === 'string'
409
+ && value.length > 0
410
+ && value.length <= max
411
+ && !/[\u0000-\u001f\u007f]/.test(value);
412
+ }
413
+
414
+ function validDigest(value) {
415
+ return typeof value === 'string' && HASH.test(value);
416
+ }
417
+
418
+ function validInstant(value) {
419
+ if (typeof value !== 'string') return false;
420
+ const parsed = Date.parse(value);
421
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
422
+ }
423
+
424
+ function canonicalCopy(value, label) {
425
+ let nodes = 0;
426
+ let stringBytes = 0;
427
+ const seen = new WeakSet();
428
+
429
+ function copy(current, depth) {
430
+ nodes += 1;
431
+ if (nodes > MAX_RECORD_NODES || depth > MAX_RECORD_DEPTH) {
432
+ fail(`${label} exceeds resource limits`);
433
+ }
434
+ if (current === null || typeof current === 'boolean') return current;
435
+ if (typeof current === 'string') {
436
+ stringBytes += Buffer.byteLength(current, 'utf8');
437
+ if (stringBytes > MAX_STRING_BYTES) fail(`${label} exceeds string limits`);
438
+ return current;
439
+ }
440
+ if (typeof current === 'number') {
441
+ if (!Number.isSafeInteger(current) || Object.is(current, -0)) {
442
+ fail(`${label} contains a non-canonical number`);
443
+ }
444
+ return current;
445
+ }
446
+ if (!isRecord(current) && !Array.isArray(current)) {
447
+ fail(`${label} is not canonical JSON`);
448
+ }
449
+ if (seen.has(current)) fail(`${label} contains an alias or cycle`);
450
+ seen.add(current);
451
+ if (Array.isArray(current)) {
452
+ return current.map((entry) => copy(entry, depth + 1));
453
+ }
454
+ return Object.fromEntries(
455
+ Object.entries(current).map(([key, entry]) => [key, copy(entry, depth + 1)]),
456
+ );
457
+ }
458
+
459
+ return copy(value, 0);
460
+ }
461
+
462
+ function canonicalDigest(value) {
463
+ return `sha256:${hashCanonical(value)}`;
464
+ }
465
+
466
+ function sameCanonical(left, right) {
467
+ try {
468
+ return canonicalize(left) === canonicalize(right);
469
+ } catch {
470
+ return false;
471
+ }
472
+ }
473
+
474
+ function normalizeFieldName(value) {
475
+ return value
476
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
477
+ .replace(/[^A-Za-z0-9]+/g, '_')
478
+ .replace(/^_+|_+$/g, '')
479
+ .toLowerCase();
480
+ }
481
+
482
+ function assertNoSecrets(value, label) {
483
+ const stack = [{ value, path: label }];
484
+ while (stack.length > 0) {
485
+ const current = stack.pop();
486
+ // stack.length > 0 guarantees pop() returns an entry; TS can't see that.
487
+ if (!current) break;
488
+ if (typeof current.value === 'string') {
489
+ if (/-----BEGIN (?:ENCRYPTED )?PRIVATE KEY-----/.test(current.value)
490
+ || /^\s*Bearer\s+\S+/i.test(current.value)
491
+ || /^[a-z][a-z0-9+.-]*:\/\/[^/\s]*@/i.test(current.value)) {
492
+ fail(`${current.path} contains provider credentials or secrets`);
493
+ }
494
+ continue;
495
+ }
496
+ if (current.value === null || typeof current.value !== 'object') continue;
497
+ for (const [key, child] of Object.entries(current.value)) {
498
+ const normalized = normalizeFieldName(key);
499
+ if (SECRET_FIELD_NAMES.has(normalized)
500
+ || normalized.endsWith('_client_secret')
501
+ || normalized.endsWith('_private_key')
502
+ || normalized.endsWith('_access_token')
503
+ || normalized.endsWith('_refresh_token')
504
+ || normalized.endsWith('_password')) {
505
+ fail(`${current.path}.${key} contains provider credentials or secrets`);
506
+ }
507
+ stack.push({ value: child, path: `${current.path}.${key}` });
508
+ }
509
+ }
510
+ }
511
+
512
+ function expectedCore(
513
+ record,
514
+ overrides: { document_action_binding_digest?: any; release_action_digest?: any } = {},
515
+ ) {
516
+ return {
517
+ agreement_digest: record.agreement_digest,
518
+ document_action_binding_digest:
519
+ overrides.document_action_binding_digest ?? record.document_action_binding_digest,
520
+ milestone_id: record.milestone_id,
521
+ release_action_digest: overrides.release_action_digest ?? record.release_action_digest,
522
+ parties_digest: record.parties_digest,
523
+ profile_digest: record.profile_digest,
524
+ };
525
+ }
526
+
527
+ function verificationMatchesCore(value, expected) {
528
+ return isRecord(value)
529
+ && value.valid === true
530
+ && Object.entries(expected).every(([key, entry]) => value[key] === entry);
531
+ }
532
+
533
+ function validateStringArray(value, allowed, label) {
534
+ if (!Array.isArray(value)
535
+ || value.length === 0
536
+ || value.some((entry) => !validString(entry, 256))
537
+ || new Set(value).size !== value.length
538
+ || value.some((entry) => !allowed.has(entry))) {
539
+ fail(`${label} is invalid`);
540
+ }
541
+ }
542
+
543
+ function partyIdentity(party) {
544
+ return `${party.role}\u0000${party.party_id}`;
545
+ }
546
+
547
+ function sameParties(left, right) {
548
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
549
+ const expected = new Set(left.map(partyIdentity));
550
+ return expected.size === left.length && right.every((party) => (
551
+ exactKeys(party, PARTY_KEYS) && expected.has(partyIdentity(party))
552
+ ));
553
+ }
554
+
555
+ function validatePartiesAndProfile(record) {
556
+ if (!Array.isArray(record.parties)
557
+ || record.parties.length < 2
558
+ || record.parties.length > 16) {
559
+ fail('kernel record parties are invalid');
560
+ }
561
+ const ids = new Set();
562
+ for (const party of record.parties) {
563
+ if (!exactKeys(party, PARTY_KEYS)
564
+ || !validString(party.party_id, 256)
565
+ || !validString(party.role, 128)
566
+ || ids.has(party.party_id)) {
567
+ fail('kernel record party is malformed or duplicated');
568
+ }
569
+ ids.add(party.party_id);
570
+ }
571
+ if (record.parties_digest !== canonicalDigest(record.parties)) {
572
+ fail('kernel record parties digest mismatch');
573
+ }
574
+ if (!exactKeys(record.profile, PROFILE_KEYS)
575
+ || record.profile['@version'] !== ACTION_ESCROW_PROFILE_VERSION
576
+ || !validString(record.profile.profile_id, 256)
577
+ || !validString(record.profile.provider_id, 256)
578
+ || typeof record.profile.prohibit_self_approval !== 'boolean') {
579
+ fail('kernel record profile is malformed');
580
+ }
581
+ validateStringArray(
582
+ record.profile.required_acceptance_party_ids,
583
+ ids,
584
+ 'required acceptance party roster',
585
+ );
586
+ validateStringArray(
587
+ record.profile.required_release_approver_party_ids,
588
+ ids,
589
+ 'required release party roster',
590
+ );
591
+ if (record.profile.required_acceptance_party_ids.length !== record.parties.length) {
592
+ fail('required acceptance party roster does not cover the exact parties');
593
+ }
594
+ if (record.profile_digest !== canonicalDigest(record.profile)) {
595
+ fail('kernel record profile digest mismatch');
596
+ }
597
+ }
598
+
599
+ function validateHistory(record) {
600
+ if (!Array.isArray(record.operations)
601
+ || record.operations.length === 0
602
+ || !Array.isArray(record.history)
603
+ || record.history.length === 0) {
604
+ fail('kernel operation or state history is malformed');
605
+ }
606
+ const operationIds = new Set();
607
+ const operationsById = new Map();
608
+ let previousOperationTime = Number.NEGATIVE_INFINITY;
609
+ for (const operation of record.operations) {
610
+ if (!exactKeys(operation, OPERATION_KEYS)
611
+ || !validString(operation.idempotency_key, 512)
612
+ || operationIds.has(operation.idempotency_key)
613
+ || !validString(operation.operation, 128)
614
+ || !KERNEL_OPERATIONS.has(operation.operation)
615
+ || !validDigest(operation.request_digest)
616
+ || !validString(operation.code, 256)
617
+ || typeof operation.ok !== 'boolean'
618
+ || !validString(operation.outcome, 128)
619
+ || !ACTION_ESCROW_STATES.includes(operation.state)
620
+ || !validInstant(operation.at)) {
621
+ fail('kernel operation history is malformed');
622
+ }
623
+ const operationTime = Date.parse(operation.at);
624
+ if (operationTime < previousOperationTime) {
625
+ fail('kernel operation history is not time-monotonic');
626
+ }
627
+ operationIds.add(operation.idempotency_key);
628
+ operationsById.set(operation.idempotency_key, operation);
629
+ previousOperationTime = operationTime;
630
+ }
631
+ const firstOperation = record.operations[0];
632
+ if (firstOperation.operation !== 'create'
633
+ || firstOperation.state !== 'draft'
634
+ || firstOperation.code !== 'escrow_created'
635
+ || firstOperation.ok !== true) {
636
+ fail('kernel operation history has no valid creation root');
637
+ }
638
+
639
+ let previous = null;
640
+ let previousHistoryTime = Number.NEGATIVE_INFINITY;
641
+ const releaseHistoryCounts = new Map();
642
+ for (const [index, entry] of record.history.entries()) {
643
+ if (!exactKeys(entry, HISTORY_KEYS)
644
+ || entry.from !== previous
645
+ || !ACTION_ESCROW_STATES.includes(entry.to)
646
+ || !validString(entry.operation, 128)
647
+ || !validString(entry.idempotency_key, 512)
648
+ || !validInstant(entry.at)) {
649
+ fail('kernel state history is malformed');
650
+ }
651
+ const linkedOperation = operationsById.get(entry.idempotency_key);
652
+ const historyTime = Date.parse(entry.at);
653
+ if (!linkedOperation
654
+ || linkedOperation.operation !== entry.operation
655
+ || historyTime < previousHistoryTime) {
656
+ fail('kernel state history is not bound to its operation log');
657
+ }
658
+ if (entry.operation === 'release') {
659
+ if (historyTime < Date.parse(linkedOperation.at)) {
660
+ fail('kernel release history predates its reservation');
661
+ }
662
+ releaseHistoryCounts.set(
663
+ entry.idempotency_key,
664
+ (releaseHistoryCounts.get(entry.idempotency_key) ?? 0) + 1,
665
+ );
666
+ } else if (entry.at !== linkedOperation.at || entry.to !== linkedOperation.state) {
667
+ fail('kernel state history is inconsistent with its operation');
668
+ }
669
+ if (index === 0) {
670
+ if (entry.from !== null || entry.to !== 'draft' || entry.operation !== 'create') {
671
+ fail('kernel state history has no valid creation root');
672
+ }
673
+ } else if (!ACTION_ESCROW_TRANSITIONS[entry.from]?.includes(entry.to)) {
674
+ fail('kernel state history contains an invalid transition');
675
+ }
676
+ previous = entry.to;
677
+ previousHistoryTime = historyTime;
678
+ }
679
+ if (previous !== record.state) fail('kernel state does not match its history');
680
+ const internalReleaseTransitions = [...releaseHistoryCounts.values()]
681
+ .reduce((total, count) => total + Math.max(0, count - 1), 0);
682
+ if (record.revision !== record.operations.length - 1 + internalReleaseTransitions) {
683
+ fail('kernel revision does not match its complete operation history');
684
+ }
685
+
686
+ const eventTimes = [
687
+ ...record.operations.map((entry) => entry.at),
688
+ ...record.history.map((entry) => entry.at),
689
+ ];
690
+ if (record.created_at !== record.operations[0].at
691
+ || Date.parse(record.created_at) > Date.parse(record.updated_at)
692
+ || eventTimes.some((instant) => Date.parse(instant) > Date.parse(record.updated_at))
693
+ || Math.max(...eventTimes.map((instant) => Date.parse(instant)))
694
+ !== Date.parse(record.updated_at)) {
695
+ fail('kernel record timestamps do not match its history');
696
+ }
697
+ }
698
+
699
+ /**
700
+ * @param {any} container
701
+ * @param {any} record
702
+ * @param {{
703
+ * bindingDigest?: any,
704
+ * actionDigest?: any,
705
+ * supersedesDigest?: string|null,
706
+ * documentDigest?: string|null,
707
+ * documentLength?: number|null,
708
+ * }} [options]
709
+ */
710
+ function validateBindingContainer(container, record, {
711
+ bindingDigest = record.document_action_binding_digest,
712
+ actionDigest = record.release_action_digest,
713
+ supersedesDigest = null,
714
+ documentDigest = null,
715
+ documentLength = null,
716
+ }: {
717
+ bindingDigest?: string;
718
+ actionDigest?: string;
719
+ supersedesDigest?: string | null;
720
+ documentDigest?: string | null;
721
+ documentLength?: number | null;
722
+ } = {}) {
723
+ if (!exactKeys(container, CONTAINER_KEYS)
724
+ || !isRecord(container.artifact)
725
+ || !isRecord(container.verification)) {
726
+ fail('document-action binding container is malformed');
727
+ }
728
+ const binding = container.artifact;
729
+ const verification = container.verification;
730
+ const computedBindingDigest = computeDocumentActionBindingDigest(binding);
731
+ const computedActionDigest = computeReleaseActionDigest(binding.release_action?.template);
732
+ const bindingDocumentDigest = binding.document?.digest;
733
+ const currentContractorProfile =
734
+ binding.release_action?.template?.action_escrow_template_profile
735
+ === ACTION_ESCROW_CONTRACTOR_TEMPLATE_VERSION;
736
+ const projectRecordBound = validDigest(
737
+ binding.release_action?.template?.project_record_snapshot_digest,
738
+ );
739
+ // `validateActionEscrowReleaseTemplate` (action-escrow-verifiers.ts, outside
740
+ // this file's scope) still infers its options parameter type from only the
741
+ // one destructured property that carries a default value, so a fresh object
742
+ // literal here trips the excess-property check. Routing through a locally
743
+ // typed variable avoids that without changing what gets passed at runtime.
744
+ const releaseTemplateOptions: {
745
+ profileDigest?: any;
746
+ agreementId?: any;
747
+ agreementDigest?: any;
748
+ milestoneId?: any;
749
+ documentDigest?: any;
750
+ materialTerms?: any;
751
+ contractorProjectSource?: boolean;
752
+ } = {
753
+ profileDigest: record.profile_digest,
754
+ agreementId: binding.agreement_id,
755
+ agreementDigest: record.agreement_digest,
756
+ milestoneId: record.milestone_id,
757
+ documentDigest: bindingDocumentDigest,
758
+ materialTerms: binding.material_terms,
759
+ contractorProjectSource: currentContractorProfile,
760
+ };
761
+ const releaseActionTemplate = validateActionEscrowReleaseTemplate(
762
+ binding.release_action?.template,
763
+ releaseTemplateOptions,
764
+ );
765
+ const requiredVerification = new Set(BINDING_VERIFICATION_KEYS);
766
+ if (supersedesDigest === null) {
767
+ requiredVerification.delete('supersedes_document_action_binding_digest');
768
+ }
769
+ if (!exactKeys(verification, BINDING_VERIFICATION_KEYS, requiredVerification)
770
+ || !verificationMatchesCore(verification, expectedCore(record, {
771
+ document_action_binding_digest: bindingDigest,
772
+ release_action_digest: actionDigest,
773
+ }))
774
+ || computedBindingDigest !== bindingDigest
775
+ || binding.binding_digest !== bindingDigest
776
+ || verification.verification_digest !== bindingDigest
777
+ || computedActionDigest !== actionDigest
778
+ || binding.release_action?.digest !== actionDigest
779
+ || !validDigest(bindingDocumentDigest)
780
+ || verification.document_digest !== bindingDocumentDigest
781
+ || verification.agreement_id !== binding.agreement_id
782
+ || verification.binding_id !== binding.binding_id
783
+ || computeActionEscrowAgreementDigest(binding.agreement_id) !== record.agreement_digest
784
+ || releaseActionTemplate === null
785
+ || !sameCanonical(
786
+ verification.release_action_template,
787
+ releaseActionTemplate,
788
+ )
789
+ || !sameParties(record.parties, binding.parties)
790
+ || !sameParties(record.parties, binding.required_parties)
791
+ || (binding.supersedes_digest ?? null) !== supersedesDigest
792
+ || (verification.supersedes_document_action_binding_digest ?? null)
793
+ !== supersedesDigest) {
794
+ fail('document-action binding is inconsistent with the kernel record');
795
+ }
796
+ if (documentDigest !== null
797
+ && (bindingDocumentDigest !== documentDigest
798
+ || releaseActionTemplate.document_sha256 !== documentDigest)) {
799
+ fail('final PDF does not match the kernel document binding');
800
+ }
801
+ if (documentLength !== null && binding.document?.byte_length !== documentLength) {
802
+ fail('final PDF byte length does not match the kernel document binding');
803
+ }
804
+ return {
805
+ agreementId: binding.agreement_id,
806
+ binding,
807
+ contractorProjectSource: projectRecordBound,
808
+ documentDigest: bindingDocumentDigest,
809
+ projectRecordSnapshotDigest:
810
+ releaseActionTemplate.project_record_snapshot_digest ?? null,
811
+ };
812
+ }
813
+
814
+ function validateAcceptances(
815
+ entries,
816
+ record,
817
+ core,
818
+ {
819
+ requireComplete,
820
+ requireIncomplete = false,
821
+ }: { requireComplete?: boolean; requireIncomplete?: boolean } = {},
822
+ ) {
823
+ if (!Array.isArray(entries)) fail('agreement acceptances are malformed');
824
+ const parties = new Map<string, { party_id: any; role: any }>(
825
+ record.parties.map((party) => [party.party_id, party]),
826
+ );
827
+ const required = new Set(record.profile.required_acceptance_party_ids);
828
+ const seenParties = new Set();
829
+ const seenKeys = new Set();
830
+ const result: { party_id: any; role: any; evidence: any }[] = [];
831
+ for (const entry of entries) {
832
+ const verification = entry?.verification;
833
+ const party = parties.get(entry?.party_id);
834
+ if (!exactKeys(entry, ACCEPTANCE_KEYS)
835
+ || !party
836
+ || !required.has(entry.party_id)
837
+ || seenParties.has(entry.party_id)
838
+ || !isRecord(entry.artifact)
839
+ || !exactKeys(verification, ACCEPTANCE_VERIFICATION_KEYS)
840
+ || !verificationMatchesCore(verification, core)
841
+ || verification.party_id !== entry.party_id
842
+ || !validString(verification.principal_key_id, 512)
843
+ || seenKeys.has(verification.principal_key_id)
844
+ || verification.acceptance_digest !== canonicalDigest(entry.artifact)) {
845
+ fail('agreement acceptance is malformed or inconsistent');
846
+ }
847
+ seenParties.add(entry.party_id);
848
+ seenKeys.add(verification.principal_key_id);
849
+ result.push({
850
+ party_id: entry.party_id,
851
+ role: party.role,
852
+ evidence: entry.artifact,
853
+ });
854
+ }
855
+ if (requireComplete && seenParties.size !== required.size) {
856
+ fail('required agreement acceptance is missing');
857
+ }
858
+ if (requireIncomplete && seenParties.size >= required.size) {
859
+ fail('agreement acceptance count is inconsistent with the stage');
860
+ }
861
+ return result;
862
+ }
863
+
864
+ function validateFunding(record) {
865
+ const template = record.document_action_binding.verification.release_action_template;
866
+ if (!exactKeys(record.funding, new Set(['statement', 'verification']))
867
+ || !isRecord(record.funding.statement)
868
+ || !exactKeys(record.funding.verification, FUNDING_VERIFICATION_KEYS)
869
+ || !verificationMatchesCore(record.funding.verification, expectedCore(record))
870
+ || record.funding.verification.authenticated !== true
871
+ || record.funding.verification.provider_id !== record.profile.provider_id
872
+ || record.funding.verification.statement_type !== 'funding'
873
+ || record.funding.verification.status !== 'funded'
874
+ || record.funding.verification.provider_transaction_id
875
+ !== template.custodian_transaction_id
876
+ || record.funding.verification.provider_milestone_id
877
+ !== template.custodian_milestone_id
878
+ || record.funding.verification.amount !== template.amount
879
+ || record.funding.verification.currency !== template.currency
880
+ || record.funding.verification.destination_id !== template.destination_id
881
+ || record.funding.verification.statement_digest
882
+ !== canonicalDigest(record.funding.statement)) {
883
+ fail('funding statement is malformed or inconsistent');
884
+ }
885
+ return record.funding.statement;
886
+ }
887
+
888
+ function validateMilestone(record) {
889
+ if (!exactKeys(record.milestone_evidence, CONTAINER_KEYS)
890
+ || !isRecord(record.milestone_evidence.artifact)
891
+ || !exactKeys(
892
+ record.milestone_evidence.verification,
893
+ MILESTONE_VERIFICATION_KEYS,
894
+ )
895
+ || !verificationMatchesCore(
896
+ record.milestone_evidence.verification,
897
+ expectedCore(record),
898
+ )
899
+ || !validDigest(record.milestone_evidence.verification.evidence_digest)
900
+ || !record.parties.some(
901
+ (party) => party.party_id
902
+ === record.milestone_evidence.verification.submitter_party_id,
903
+ )
904
+ || !validInstant(record.milestone_evidence.verification.observed_at)) {
905
+ fail('milestone evidence is malformed or inconsistent');
906
+ }
907
+ const artifact = record.milestone_evidence.artifact;
908
+ const claimedDigest = artifact.evidence_digest
909
+ ?? artifact.payload?.claim?.evidence_manifest_digest;
910
+ if (claimedDigest !== undefined
911
+ && claimedDigest !== record.milestone_evidence.verification.evidence_digest) {
912
+ fail('milestone artifact digest does not match its kernel verification');
913
+ }
914
+ const submission = record.operations.find(
915
+ (operation) => operation.operation === 'submit_milestone',
916
+ );
917
+ if (!submission
918
+ || Date.parse(record.milestone_evidence.verification.observed_at)
919
+ > Date.parse(submission.at)) {
920
+ fail('milestone evidence was not valid at its submission operation');
921
+ }
922
+ return {
923
+ milestone_id: record.milestone_id,
924
+ evidence: artifact,
925
+ resolution: null,
926
+ };
927
+ }
928
+
929
+ function validateReleaseApprovals(record, milestoneVerification) {
930
+ if (!Array.isArray(record.release_approvals)) fail('release approvals are malformed');
931
+ const parties = new Map<string, { party_id: any; role: any }>(
932
+ record.parties.map((party) => [party.party_id, party]),
933
+ );
934
+ const required = new Set(record.profile.required_release_approver_party_ids);
935
+ const bindingInput = {
936
+ agreement_digest: record.agreement_digest,
937
+ document_action_binding_digest: record.document_action_binding_digest,
938
+ milestone_id: record.milestone_id,
939
+ release_action_digest: record.release_action_digest,
940
+ profile_digest: record.profile_digest,
941
+ evidence_digest: milestoneVerification.evidence_digest,
942
+ release_action_template:
943
+ record.document_action_binding.verification.release_action_template,
944
+ };
945
+ const bindingMomentDigest = computeActionEscrowReleaseBindingMomentDigest(bindingInput);
946
+ const expectedInitiator = milestoneVerification.submitter_party_id;
947
+ if (!validDigest(bindingMomentDigest) || !validString(expectedInitiator, 256)) {
948
+ fail('release approval binding context is malformed');
949
+ }
950
+ const seenParties = new Set();
951
+ const seenKeys = new Set();
952
+ const result: { party_id: any; role: any; evidence: any }[] = [];
953
+ const approvalOperations = record.operations.filter(
954
+ (operation) => operation.operation === 'approve_release',
955
+ );
956
+ for (const [index, entry] of record.release_approvals.entries()) {
957
+ const party = parties.get(entry?.party_id);
958
+ const verification = entry?.verification;
959
+ const context = entry?.resolution?.signoff?.context;
960
+ if (!exactKeys(entry, RELEASE_APPROVAL_KEYS)
961
+ || !party
962
+ || !required.has(entry.party_id)
963
+ || seenParties.has(entry.party_id)
964
+ || !isRecord(entry.resolution)
965
+ || entry.resolution.profile !== 'EP-RESOLUTION-v1'
966
+ || !isRecord(context)
967
+ || !exactKeys(verification, RELEASE_APPROVAL_VERIFICATION_KEYS)
968
+ || !verificationMatchesCore(verification, expectedCore(record))
969
+ || verification.authorizes_action !== true
970
+ || verification.outcome !== 'approved'
971
+ || verification.party_role !== party.role
972
+ || !validString(verification.principal_key_id, 512)
973
+ || seenKeys.has(verification.principal_key_id)
974
+ || !validString(verification.nonce, 512)
975
+ || !validInstant(verification.issued_at)
976
+ || !validInstant(verification.expires_at)
977
+ || Date.parse(verification.issued_at) < Date.parse(milestoneVerification.observed_at)
978
+ || Date.parse(verification.expires_at) <= Date.parse(verification.issued_at)
979
+ || verification.resolution_digest !== canonicalDigest(entry.resolution)
980
+ || verification.evidence_digest !== milestoneVerification.evidence_digest
981
+ || context.principal !== entry.party_id
982
+ || context.principal_key_id !== verification.principal_key_id
983
+ || context.envelope_hash !== bindingMomentDigest
984
+ || context.action_hash !== record.release_action_digest
985
+ || context.initiator !== expectedInitiator
986
+ || context.nonce !== computeActionEscrowResolutionNonce(bindingInput, entry.party_id)
987
+ || context.nonce !== verification.nonce
988
+ || context.issued_at !== verification.issued_at
989
+ || context.expires_at !== verification.expires_at
990
+ || context.resolution?.outcome !== 'approved'
991
+ || context.resolution?.selected_option !== 0) {
992
+ fail('release approval is malformed or inconsistent');
993
+ }
994
+ const approvalOperation = approvalOperations[index];
995
+ if (!approvalOperation
996
+ || Date.parse(verification.issued_at) > Date.parse(approvalOperation.at)
997
+ || Date.parse(verification.expires_at) <= Date.parse(approvalOperation.at)
998
+ || (
999
+ record.release !== null
1000
+ && (
1001
+ Date.parse(verification.issued_at) > Date.parse(record.release.reserved_at)
1002
+ || Date.parse(verification.expires_at) <= Date.parse(record.release.reserved_at)
1003
+ )
1004
+ )) {
1005
+ fail('release approval was not valid at its operation');
1006
+ }
1007
+ seenParties.add(entry.party_id);
1008
+ seenKeys.add(verification.principal_key_id);
1009
+ result.push({
1010
+ party_id: entry.party_id,
1011
+ role: party.role,
1012
+ evidence: entry.resolution,
1013
+ });
1014
+ }
1015
+ return { approvals: result, approvedPartyIds: seenParties };
1016
+ }
1017
+
1018
+ function validateProviderVerification(value, record, release) {
1019
+ const template = record.document_action_binding.verification.release_action_template;
1020
+ if (!exactKeys(value, PROVIDER_VERIFICATION_KEYS)
1021
+ || !verificationMatchesCore(value, expectedCore(record))
1022
+ || value.authenticated !== true
1023
+ || value.provider_id !== record.profile.provider_id
1024
+ || value.provider_idempotency_key !== release.provider_idempotency_key
1025
+ || value.provider_request_digest !== release.provider_request.request_digest
1026
+ || value.provider_transaction_id !== template.custodian_transaction_id
1027
+ || value.provider_milestone_id !== template.custodian_milestone_id
1028
+ || value.amount !== template.amount
1029
+ || value.currency !== template.currency
1030
+ || value.destination_id !== template.destination_id
1031
+ || value.statement_type !== 'release'
1032
+ || !['released', 'not_released', 'pending'].includes(value.status)
1033
+ || !validDigest(value.statement_digest)
1034
+ || value.statement_digest !== canonicalDigest(release.provider_statement)) {
1035
+ fail('provider release statement is malformed or inconsistent');
1036
+ }
1037
+ }
1038
+
1039
+ function validateRelease(record) {
1040
+ const release = record.release;
1041
+ if (!exactKeys(release, RELEASE_KEYS)
1042
+ || !validString(release.release_key, 256)
1043
+ || !validString(release.provider_idempotency_key, 256)
1044
+ || !validString(release.operation_idempotency_key, 512)
1045
+ || !['reserved', 'released', 'not_released', 'indeterminate'].includes(release.status)
1046
+ || !validInstant(release.reserved_at)
1047
+ || (release.reconciled_at !== null && !validInstant(release.reconciled_at))
1048
+ || !exactKeys(release.provider_request, PROVIDER_REQUEST_KEYS)) {
1049
+ fail('release record is malformed');
1050
+ }
1051
+ const expectedReleaseKey = `ep-ae-reservation:${hashCanonical({
1052
+ '@version': 'EP-ACTION-ESCROW-RELEASE-KEY-v1',
1053
+ agreement_digest: record.agreement_digest,
1054
+ document_action_binding_digest: record.document_action_binding_digest,
1055
+ milestone_id: record.milestone_id,
1056
+ release_action_digest: record.release_action_digest,
1057
+ profile_digest: record.profile_digest,
1058
+ })}`;
1059
+ const expectedProviderKey = `ep-ae-release:${hashCanonical({
1060
+ '@version': 'EP-ACTION-ESCROW-PROVIDER-IDEMPOTENCY-v1',
1061
+ agreement_digest: record.agreement_digest,
1062
+ document_action_binding_digest: record.document_action_binding_digest,
1063
+ milestone_id: record.milestone_id,
1064
+ release_action_digest: record.release_action_digest,
1065
+ profile_digest: record.profile_digest,
1066
+ })}`;
1067
+ if (release.release_key !== expectedReleaseKey
1068
+ || release.provider_idempotency_key !== expectedProviderKey) {
1069
+ fail('release reservation key mismatch');
1070
+ }
1071
+
1072
+ const request = release.provider_request;
1073
+ const { request_digest: requestDigest, ...requestScope } = request;
1074
+ if (request.method !== 'POST'
1075
+ || request.provider_id !== record.profile.provider_id
1076
+ || request.agreement_digest !== record.agreement_digest
1077
+ || request.document_action_binding_digest
1078
+ !== record.document_action_binding_digest
1079
+ || request.milestone_id !== record.milestone_id
1080
+ || request.release_action_digest !== record.release_action_digest
1081
+ || !sameCanonical(request.parties, record.parties)
1082
+ || request.parties_digest !== record.parties_digest
1083
+ || !sameCanonical(request.profile, record.profile)
1084
+ || request.profile_digest !== record.profile_digest
1085
+ || request.agreement_id
1086
+ !== record.document_action_binding.verification.agreement_id
1087
+ || request.binding_id !== record.document_action_binding.verification.binding_id
1088
+ || request.document_digest
1089
+ !== record.document_action_binding.verification.document_digest
1090
+ || !sameCanonical(
1091
+ request.release_action_template,
1092
+ record.document_action_binding.verification.release_action_template,
1093
+ )
1094
+ || request.release_key !== release.release_key
1095
+ || request.idempotency_key !== release.provider_idempotency_key
1096
+ || requestDigest !== canonicalDigest({
1097
+ '@version': 'EP-ACTION-ESCROW-PROVIDER-REQUEST-v1',
1098
+ ...requestScope,
1099
+ })) {
1100
+ fail('provider release request is inconsistent with the kernel record');
1101
+ }
1102
+
1103
+ const execution = record.operations.find((entry) => (
1104
+ entry.operation === 'release'
1105
+ && entry.idempotency_key === release.operation_idempotency_key
1106
+ ));
1107
+ if (!execution) fail('release execution record is missing');
1108
+ if (release.reserved_at !== execution.at
1109
+ || (
1110
+ release.reconciled_at !== null
1111
+ && Date.parse(release.reconciled_at) < Date.parse(release.reserved_at)
1112
+ )) {
1113
+ fail('release timestamps are inconsistent with the execution operation');
1114
+ }
1115
+ const expectedExecution = {
1116
+ reserved: {
1117
+ states: new Set(['release_reserved']),
1118
+ operationStates: new Set(['release_reserved']),
1119
+ codes: new Set(['release_reserved']),
1120
+ ok: false,
1121
+ outcomes: new Set(['reserved']),
1122
+ },
1123
+ released: {
1124
+ states: new Set(['released', 'completed']),
1125
+ operationStates: new Set(['released']),
1126
+ codes: new Set(['release_committed', 'release_reconciled_released']),
1127
+ ok: true,
1128
+ outcomes: new Set(['applied']),
1129
+ },
1130
+ not_released: {
1131
+ states: new Set(['milestone_submitted', 'disputed', 'amendment_pending']),
1132
+ operationStates: new Set(['milestone_submitted', 'amendment_pending']),
1133
+ codes: new Set([
1134
+ 'provider_release_not_released',
1135
+ 'release_reconciled_not_released',
1136
+ ]),
1137
+ ok: false,
1138
+ outcomes: new Set(['refused']),
1139
+ },
1140
+ indeterminate: {
1141
+ states: new Set(['release_indeterminate']),
1142
+ operationStates: new Set(['release_indeterminate']),
1143
+ codes: new Set([
1144
+ 'release_effect_indeterminate',
1145
+ 'release_commit_indeterminate',
1146
+ 'release_still_indeterminate',
1147
+ ]),
1148
+ ok: false,
1149
+ outcomes: new Set(['indeterminate']),
1150
+ },
1151
+ }[release.status];
1152
+ if (!expectedExecution.states.has(record.state)
1153
+ || !expectedExecution.operationStates.has(execution.state)
1154
+ || !expectedExecution.codes.has(execution.code)
1155
+ || execution.ok !== expectedExecution.ok
1156
+ || !expectedExecution.outcomes.has(execution.outcome)) {
1157
+ fail('release execution record is inconsistent with the release state');
1158
+ }
1159
+
1160
+ if (release.status === 'reserved') {
1161
+ if (release.reconciled_at !== null
1162
+ || release.provider_statement !== null
1163
+ || release.provider_verification !== null) {
1164
+ fail('reserved release carries premature provider evidence');
1165
+ }
1166
+ } else {
1167
+ if (release.reconciled_at === null) fail('reconciled release is missing its instant');
1168
+ const hasStatement = release.provider_statement !== null;
1169
+ const hasVerification = release.provider_verification !== null;
1170
+ if (hasStatement !== hasVerification) {
1171
+ fail('provider release statement and verification must be paired');
1172
+ }
1173
+ if (release.status !== 'indeterminate' && !hasStatement) {
1174
+ fail('authoritative provider release statement is missing');
1175
+ }
1176
+ if (hasStatement) {
1177
+ if (!isRecord(release.provider_statement)) {
1178
+ fail('provider release statement is malformed');
1179
+ }
1180
+ validateProviderVerification(release.provider_verification, record, release);
1181
+ if (release.status !== 'indeterminate'
1182
+ && release.provider_verification.status !== release.status) {
1183
+ fail('provider release status mismatch');
1184
+ }
1185
+ }
1186
+ }
1187
+
1188
+ return {
1189
+ reservation: {
1190
+ release_key: release.release_key,
1191
+ provider_idempotency_key: release.provider_idempotency_key,
1192
+ reserved_at: release.reserved_at,
1193
+ },
1194
+ provider_request: request,
1195
+ provider_statement: release.provider_statement,
1196
+ execution_record: execution,
1197
+ };
1198
+ }
1199
+
1200
+ function validateCommandAuthorization(container, record, command, partyId, details) {
1201
+ if (!exactKeys(container, COMMAND_CONTAINER_KEYS)
1202
+ || !isRecord(container.artifact)
1203
+ || !exactKeys(container.verification, COMMAND_VERIFICATION_KEYS)
1204
+ || !verificationMatchesCore(container.verification, expectedCore(record))) {
1205
+ fail(`${command} authorization is malformed`);
1206
+ }
1207
+ const detailsDigest = canonicalDigest(details);
1208
+ const commandDigest = canonicalDigest({
1209
+ '@version': 'EP-ACTION-ESCROW-COMMAND-v1',
1210
+ ...expectedCore(record),
1211
+ command,
1212
+ party_id: partyId,
1213
+ details_digest: detailsDigest,
1214
+ });
1215
+ if (container.verification.authorizes_command !== true
1216
+ || container.verification.command !== command
1217
+ || container.verification.party_id !== partyId
1218
+ || container.verification.details_digest !== detailsDigest
1219
+ || container.verification.command_digest !== commandDigest) {
1220
+ fail(`${command} authorization does not match the state change`);
1221
+ }
1222
+ }
1223
+
1224
+ function validateAncillaryState(record) {
1225
+ if (record.state === 'disputed') {
1226
+ if (!exactKeys(record.dispute, DISPUTE_KEYS)
1227
+ || !record.parties.some((party) => party.party_id === record.dispute.party_id)
1228
+ || !validString(record.dispute.reason, 2048)
1229
+ || !validInstant(record.dispute.opened_at)) {
1230
+ fail('dispute state is malformed');
1231
+ }
1232
+ validateCommandAuthorization(
1233
+ record.dispute.authorization,
1234
+ record,
1235
+ 'open_dispute',
1236
+ record.dispute.party_id,
1237
+ { reason: record.dispute.reason },
1238
+ );
1239
+ } else if (record.dispute !== null) {
1240
+ fail('non-disputed stage carries a dispute');
1241
+ }
1242
+
1243
+ if (record.state === 'cancelled') {
1244
+ if (!exactKeys(record.cancellation, CANCELLATION_KEYS)
1245
+ || !record.parties.some(
1246
+ (party) => party.party_id === record.cancellation.party_id,
1247
+ )
1248
+ || (record.cancellation.reason !== null
1249
+ && !validString(record.cancellation.reason, 2048))
1250
+ || !validInstant(record.cancellation.cancelled_at)) {
1251
+ fail('cancellation state is malformed');
1252
+ }
1253
+ validateCommandAuthorization(
1254
+ record.cancellation.authorization,
1255
+ record,
1256
+ 'cancel',
1257
+ record.cancellation.party_id,
1258
+ { reason: record.cancellation.reason },
1259
+ );
1260
+ } else if (record.cancellation !== null) {
1261
+ fail('non-cancelled stage carries a cancellation');
1262
+ }
1263
+
1264
+ if (record.state === 'completed') {
1265
+ if (!exactKeys(record.completion, COMPLETION_KEYS)
1266
+ || !record.parties.some((party) => party.party_id === record.completion.party_id)
1267
+ || record.completion.meaning !== 'administrative_archive_only'
1268
+ || !validInstant(record.completion.completed_at)) {
1269
+ fail('completion state is malformed');
1270
+ }
1271
+ validateCommandAuthorization(
1272
+ record.completion.authorization,
1273
+ record,
1274
+ 'complete',
1275
+ record.completion.party_id,
1276
+ { meaning: 'administrative_archive_only' },
1277
+ );
1278
+ } else if (record.completion !== null) {
1279
+ fail('non-completed stage carries a completion record');
1280
+ }
1281
+ }
1282
+
1283
+ /** @param {any} record */
1284
+ function validateSupersessions(record) {
1285
+ if (!Array.isArray(record.superseded_bindings)) {
1286
+ fail('superseded binding history is malformed');
1287
+ }
1288
+ const amendments: any[] = [];
1289
+ const amendmentDigests: string[] = [];
1290
+ let previousNext = null;
1291
+ const seen = new Set();
1292
+ for (const entry of record.superseded_bindings) {
1293
+ if (!exactKeys(entry, SUPERSESSION_KEYS)
1294
+ || !validDigest(entry.document_action_binding_digest)
1295
+ || !validDigest(entry.release_action_digest)
1296
+ || !validDigest(entry.superseded_by_binding_digest)
1297
+ || !validInstant(entry.superseded_at)
1298
+ || entry.document_action_binding_digest === entry.superseded_by_binding_digest
1299
+ || seen.has(entry.document_action_binding_digest)
1300
+ || (previousNext !== null
1301
+ && entry.document_action_binding_digest !== previousNext)) {
1302
+ fail('superseded binding history is inconsistent');
1303
+ }
1304
+ seen.add(entry.document_action_binding_digest);
1305
+ previousNext = entry.superseded_by_binding_digest;
1306
+ amendments.push(entry);
1307
+ amendmentDigests.push(canonicalDigest(entry));
1308
+ }
1309
+ if (previousNext !== null && previousNext !== record.document_action_binding_digest) {
1310
+ fail('superseded binding history does not terminate at the current binding');
1311
+ }
1312
+ return {
1313
+ amendments,
1314
+ amendmentDigests,
1315
+ currentSupersedes: amendments.at(-1)?.document_action_binding_digest ?? null,
1316
+ };
1317
+ }
1318
+
1319
+ /** @param {any} record */
1320
+ function validatePendingAmendment(record) {
1321
+ const pendingAllowed = record.state === 'amendment_pending'
1322
+ || record.state === 'release_indeterminate'
1323
+ || record.state === 'cancelled';
1324
+ if (record.pending_amendment === null) {
1325
+ if (record.state === 'amendment_pending') fail('pending amendment is missing');
1326
+ return;
1327
+ }
1328
+ if (!pendingAllowed
1329
+ || !exactKeys(record.pending_amendment, PENDING_AMENDMENT_KEYS)
1330
+ || ![
1331
+ 'effective',
1332
+ 'awaiting_funding',
1333
+ 'funded',
1334
+ 'milestone_submitted',
1335
+ 'disputed',
1336
+ 'release_reserved',
1337
+ 'release_indeterminate',
1338
+ ].includes(record.pending_amendment.from_state)
1339
+ || !validDigest(record.pending_amendment.document_action_binding_digest)
1340
+ || !validDigest(record.pending_amendment.release_action_digest)
1341
+ || record.pending_amendment.document_action_binding_digest
1342
+ === record.document_action_binding_digest
1343
+ || !record.parties.some(
1344
+ (/** @type {{party_id: string}} */ party) => party.party_id
1345
+ === record.pending_amendment.proposer_party_id,
1346
+ )
1347
+ || !validInstant(record.pending_amendment.proposed_at)) {
1348
+ fail('pending amendment is malformed');
1349
+ }
1350
+ if (record.state === 'amendment_pending'
1351
+ && ['release_reserved', 'release_indeterminate'].includes(
1352
+ record.pending_amendment.from_state,
1353
+ )) {
1354
+ fail('pending amendment stage is inconsistent with release reconciliation');
1355
+ }
1356
+ if (record.state === 'release_indeterminate'
1357
+ && !['release_reserved', 'release_indeterminate'].includes(
1358
+ record.pending_amendment.from_state,
1359
+ )) {
1360
+ fail('indeterminate amendment does not originate from an uncertain release');
1361
+ }
1362
+
1363
+ validateBindingContainer(
1364
+ record.pending_amendment.document_action_binding,
1365
+ record,
1366
+ {
1367
+ bindingDigest: record.pending_amendment.document_action_binding_digest,
1368
+ actionDigest: record.pending_amendment.release_action_digest,
1369
+ supersedesDigest: record.document_action_binding_digest,
1370
+ },
1371
+ );
1372
+ const pendingCore = expectedCore(record, {
1373
+ document_action_binding_digest:
1374
+ record.pending_amendment.document_action_binding_digest,
1375
+ release_action_digest: record.pending_amendment.release_action_digest,
1376
+ });
1377
+ validateAcceptances(
1378
+ record.pending_amendment.agreement_acceptances,
1379
+ record,
1380
+ pendingCore,
1381
+ { requireComplete: false, requireIncomplete: true },
1382
+ );
1383
+ validateCommandAuthorization(
1384
+ record.pending_amendment.proposal_authorization,
1385
+ record,
1386
+ 'propose_amendment',
1387
+ record.pending_amendment.proposer_party_id,
1388
+ {
1389
+ next_document_action_binding_digest:
1390
+ record.pending_amendment.document_action_binding_digest,
1391
+ next_release_action_digest: record.pending_amendment.release_action_digest,
1392
+ },
1393
+ );
1394
+ }
1395
+
1396
+ /** @param {any} record */
1397
+ function validateLifecycle(record) {
1398
+ const requiresExecutedAgreement = EXECUTED_AGREEMENT_STATES.has(record.state);
1399
+ const agreementAcceptances = validateAcceptances(
1400
+ record.agreement_acceptances,
1401
+ record,
1402
+ expectedCore(record),
1403
+ {
1404
+ requireComplete: requiresExecutedAgreement,
1405
+ requireIncomplete: record.state === 'awaiting_acceptance',
1406
+ },
1407
+ );
1408
+ if (record.state === 'draft' && agreementAcceptances.length !== 0) {
1409
+ fail('draft stage carries agreement acceptances');
1410
+ }
1411
+
1412
+ const requiresFunding = FUNDING_STATES.has(record.state);
1413
+ if (requiresFunding !== (record.funding !== null)) {
1414
+ fail(requiresFunding
1415
+ ? 'required funding statement is missing'
1416
+ : 'stage carries an unexpected funding statement');
1417
+ }
1418
+ const fundingStatement = record.funding === null ? null : validateFunding(record);
1419
+
1420
+ const requiresMilestone = MILESTONE_STATES.has(record.state);
1421
+ if (requiresMilestone !== (record.milestone_evidence !== null)) {
1422
+ fail(requiresMilestone
1423
+ ? 'required milestone evidence is missing'
1424
+ : 'stage carries unexpected milestone evidence');
1425
+ }
1426
+ const milestone = record.milestone_evidence === null ? null : validateMilestone(record);
1427
+
1428
+ validateLifecycleOperationCoverage(record);
1429
+ if (!RELEASE_APPROVAL_STATES.has(record.state)
1430
+ && record.release_approvals.length !== 0) {
1431
+ fail('stage carries unexpected release approvals');
1432
+ }
1433
+ if (record.release_approvals.length > 0 && milestone === null) {
1434
+ fail('release approval has no milestone evidence');
1435
+ }
1436
+ const releaseApprovalResult = milestone === null
1437
+ ? { approvals: [], approvedPartyIds: new Set() }
1438
+ : validateReleaseApprovals(record, record.milestone_evidence.verification);
1439
+ if (COMPLETE_RELEASE_APPROVAL_STATES.has(record.state)
1440
+ && releaseApprovalResult.approvedPartyIds.size
1441
+ !== record.profile.required_release_approver_party_ids.length) {
1442
+ fail('required release approval is missing');
1443
+ }
1444
+
1445
+ const hasRelease = record.release !== null;
1446
+ if (REQUIRED_RELEASE_STATES.has(record.state) && !hasRelease) {
1447
+ fail('required release artifacts are missing');
1448
+ }
1449
+ if (!REQUIRED_RELEASE_STATES.has(record.state)
1450
+ && !OPTIONAL_RELEASE_STATES.has(record.state)
1451
+ && hasRelease) {
1452
+ fail('stage carries unexpected release artifacts');
1453
+ }
1454
+ const release = hasRelease ? validateRelease(record) : null;
1455
+
1456
+ validateAncillaryState(record);
1457
+ validatePendingAmendment(record);
1458
+
1459
+ return {
1460
+ requiresExecutedAgreement,
1461
+ agreementAcceptances,
1462
+ fundingStatement,
1463
+ milestones: milestone === null ? [] : [milestone],
1464
+ releaseApprovals: releaseApprovalResult.approvals,
1465
+ release,
1466
+ };
1467
+ }
1468
+
1469
+ /** @param {any} record */
1470
+ function validateLifecycleOperationCoverage(record) {
1471
+ /** @param {string} name */
1472
+ const count = (name) => record.operations
1473
+ .filter((/** @type {{operation: string}} */ operation) => operation.operation === name)
1474
+ .length;
1475
+ /**
1476
+ * @param {string} name
1477
+ * @param {number} expected
1478
+ */
1479
+ const requireExact = (name, expected) => {
1480
+ if (count(name) !== expected) {
1481
+ fail(`kernel ${name} operation coverage does not match its artifacts`);
1482
+ }
1483
+ };
1484
+ const acceptanceCount = record.profile.required_acceptance_party_ids.length;
1485
+ const expectedInitialAcceptances = record.superseded_bindings.length > 0
1486
+ ? acceptanceCount
1487
+ : record.agreement_acceptances.length;
1488
+ const expectedAmendmentAcceptances =
1489
+ record.superseded_bindings.length * acceptanceCount
1490
+ + (Array.isArray(record.pending_amendment?.agreement_acceptances)
1491
+ ? record.pending_amendment.agreement_acceptances.length
1492
+ : 0);
1493
+
1494
+ requireExact('create', 1);
1495
+ requireExact(
1496
+ 'begin_acceptance',
1497
+ record.history.some((/** @type {{to: string}} */ entry) => entry.to === 'awaiting_acceptance')
1498
+ ? 1 : 0,
1499
+ );
1500
+ requireExact('accept_agreement', expectedInitialAcceptances);
1501
+ requireExact(
1502
+ 'request_funding',
1503
+ record.history.some((/** @type {{to: string}} */ entry) => entry.to === 'awaiting_funding')
1504
+ ? 1 : 0,
1505
+ );
1506
+ requireExact('record_funding', record.funding === null ? 0 : 1);
1507
+ requireExact('submit_milestone', record.milestone_evidence === null ? 0 : 1);
1508
+ requireExact('approve_release', record.release_approvals.length);
1509
+ requireExact(
1510
+ 'propose_amendment',
1511
+ record.superseded_bindings.length + (record.pending_amendment === null ? 0 : 1),
1512
+ );
1513
+ requireExact('accept_amendment', expectedAmendmentAcceptances);
1514
+ requireExact('open_dispute', record.dispute === null ? 0 : 1);
1515
+ requireExact('cancel', record.cancellation === null ? 0 : 1);
1516
+ requireExact('complete', record.completion === null ? 0 : 1);
1517
+ if ((record.release === null) !== (count('release') === 0)) {
1518
+ fail('kernel release operation coverage does not match its artifacts');
1519
+ }
1520
+ if (record.release === null && count('reconcile_release') !== 0) {
1521
+ fail('kernel reconciliation operation has no release artifact');
1522
+ }
1523
+ }
1524
+
1525
+ /** @param {any} record */
1526
+ function validateKernelRecord(record) {
1527
+ if (!exactKeys(record, RECORD_KEYS)
1528
+ || record['@version'] !== ACTION_ESCROW_STATE_VERSION
1529
+ || !Number.isSafeInteger(record.revision)
1530
+ || record.revision < 0
1531
+ || !ACTION_ESCROW_STATES.includes(record.state)
1532
+ || !validString(record.escrow_key, 256)
1533
+ || !validDigest(record.agreement_digest)
1534
+ || !validDigest(record.document_action_binding_digest)
1535
+ || !validString(record.milestone_id, 256)
1536
+ || !validDigest(record.release_action_digest)
1537
+ || !validDigest(record.parties_digest)
1538
+ || !validDigest(record.profile_digest)
1539
+ || !validInstant(record.created_at)
1540
+ || !validInstant(record.updated_at)) {
1541
+ fail('kernel record is malformed');
1542
+ }
1543
+ const expectedEscrowKey = `ep-action-escrow:${hashCanonical({
1544
+ '@version': 'EP-ACTION-ESCROW-KEY-v1',
1545
+ agreement_digest: record.agreement_digest,
1546
+ milestone_id: record.milestone_id,
1547
+ })}`;
1548
+ if (record.escrow_key !== expectedEscrowKey) fail('kernel escrow key mismatch');
1549
+ validatePartiesAndProfile(record);
1550
+ validateHistory(record);
1551
+ }
1552
+
1553
+ /**
1554
+ * @param {any} value
1555
+ * @param {{agreementId: string, bindingDigest: string | null, documentDigest: string | null}} expected
1556
+ * @param {boolean} required
1557
+ */
1558
+ function validateDocumentExecution(value, expected, required) {
1559
+ if (value === null) {
1560
+ if (required) fail('required document-execution artifact is missing');
1561
+ return null;
1562
+ }
1563
+ if (!isRecord(value)
1564
+ || value.agreement_id !== expected.agreementId
1565
+ || value.binding_digest !== expected.bindingDigest
1566
+ || value.document_digest !== expected.documentDigest
1567
+ || value.authorizes_action !== false
1568
+ || (value.state !== undefined && value.state !== 'executed')) {
1569
+ fail('document-execution artifact does not match the kernel record');
1570
+ }
1571
+ return value;
1572
+ }
1573
+
1574
+ /**
1575
+ * @param {any} value
1576
+ * @param {any} record
1577
+ */
1578
+ function validateProfileReference(value, record) {
1579
+ if (!exactKeys(value, PROFILE_REFERENCE_KEYS)
1580
+ || value.id !== record.profile.profile_id
1581
+ || value.digest !== record.profile_digest) {
1582
+ fail('verification-profile reference mismatch');
1583
+ }
1584
+ }
1585
+
1586
+ /** @param {any} statement */
1587
+ function stateStatementDigest(statement) {
1588
+ const body = {
1589
+ version: statement.version,
1590
+ issuer: statement.issuer,
1591
+ payload: statement.payload,
1592
+ };
1593
+ const bytes = Buffer.from(
1594
+ ACTION_ESCROW_STATE_STATEMENT_DOMAIN + canonicalize(body),
1595
+ 'utf8',
1596
+ );
1597
+ return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
1598
+ }
1599
+
1600
+ /**
1601
+ * @param {any} statement
1602
+ * @param {any} record
1603
+ * @param {string} agreementId
1604
+ * @param {string[]} amendmentDigests
1605
+ * @param {number} assembledAt
1606
+ */
1607
+ function validateStateStatement(
1608
+ statement,
1609
+ record,
1610
+ agreementId,
1611
+ amendmentDigests,
1612
+ assembledAt,
1613
+ ) {
1614
+ if (!exactKeys(statement, STATE_STATEMENT_KEYS)
1615
+ || statement.version !== ACTION_ESCROW_STATE_STATEMENT_VERSION
1616
+ || !exactKeys(statement.issuer, STATE_ISSUER_KEYS)
1617
+ || !validString(statement.issuer.operator_id, 256)
1618
+ || !validString(statement.issuer.key_id, 256)
1619
+ || !exactKeys(statement.payload, STATE_PAYLOAD_KEYS)
1620
+ || !validString(statement.payload.statement_id, 256)
1621
+ || statement.payload.agreement_id !== agreementId
1622
+ || statement.payload.binding_digest !== record.document_action_binding_digest
1623
+ || statement.payload.action_digest !== record.release_action_digest
1624
+ || statement.payload.profile_digest !== record.profile_digest
1625
+ || statement.payload.state !== record.state
1626
+ || statement.payload.revision !== record.revision
1627
+ || !Array.isArray(statement.payload.amendment_digests)
1628
+ || statement.payload.amendment_digests.length !== amendmentDigests.length
1629
+ || statement.payload.amendment_digests.some(
1630
+ (/** @type {string} */ digest, /** @type {number} */ index) => digest
1631
+ !== amendmentDigests[index],
1632
+ )
1633
+ || statement.payload.state_record_digest !== canonicalDigest(record)
1634
+ || (statement.payload.previous_statement_digest !== null
1635
+ && !validDigest(statement.payload.previous_statement_digest))
1636
+ || !validInstant(statement.payload.occurred_at)
1637
+ || Date.parse(statement.payload.occurred_at) > assembledAt
1638
+ || !exactKeys(statement.signature, STATE_SIGNATURE_KEYS)
1639
+ || statement.signature.algorithm !== 'Ed25519'
1640
+ || typeof statement.signature.signature_b64u !== 'string'
1641
+ || !BASE64URL.test(statement.signature.signature_b64u)
1642
+ || statement.signature.signature_b64u.length % 4 === 1
1643
+ || !validDigest(statement.statement_digest)
1644
+ || statement.statement_digest !== stateStatementDigest(statement)) {
1645
+ fail('operator state statement mismatch');
1646
+ }
1647
+ }
1648
+
1649
+ /**
1650
+ * @param {Buffer|Uint8Array} value
1651
+ * @param {number} maxDocumentBytes
1652
+ */
1653
+ function finalPdfBytes(value, maxDocumentBytes) {
1654
+ if (!(Buffer.isBuffer(value) || value instanceof Uint8Array)) {
1655
+ fail('finalPdfBytes must be bytes');
1656
+ }
1657
+ const bytes = Buffer.from(value);
1658
+ if (bytes.length === 0 || bytes.length > maxDocumentBytes) {
1659
+ fail(`finalPdfBytes must be between 1 and ${maxDocumentBytes} bytes`);
1660
+ }
1661
+ if (!bytes.subarray(0, 5).equals(Buffer.from('%PDF-', 'ascii'))
1662
+ || !bytes.subarray(Math.max(0, bytes.length - 1024)).includes(Buffer.from('%%EOF'))) {
1663
+ fail('finalPdfBytes is not a final PDF');
1664
+ }
1665
+ return bytes;
1666
+ }
1667
+
1668
+ /** @param {number|string|Date|(() => (number|string|Date))} value */
1669
+ function evaluationInstant(value) {
1670
+ const candidate = typeof value === 'function' ? value() : value;
1671
+ const parsed = candidate instanceof Date
1672
+ ? candidate.getTime()
1673
+ : typeof candidate === 'number'
1674
+ ? candidate
1675
+ : Date.parse(candidate);
1676
+ if (!Number.isFinite(parsed)) fail('now must be a valid instant');
1677
+ return parsed;
1678
+ }
1679
+
1680
+ /**
1681
+ * Assemble an EP-ACTION-ESCROW-EVIDENCE-PACKAGE-v1 from one exact durable
1682
+ * kernel record and the external artifacts the kernel deliberately does not
1683
+ * retain. Invalid local assembly input throws before a package is produced.
1684
+ */
1685
+ function assembleEvidencePackage(
1686
+ input,
1687
+ options,
1688
+ { contractorProjectSource },
1689
+ ) {
1690
+ const inputKeys = contractorProjectSource ? CONTRACTOR_INPUT_KEYS : INPUT_KEYS;
1691
+ const optionKeys = contractorProjectSource
1692
+ ? CONTRACTOR_OPTION_KEYS
1693
+ : OPTION_KEYS;
1694
+ if (!exactKeys(input, inputKeys)) {
1695
+ fail('caller overrides are not accepted; use the closed assembler input');
1696
+ }
1697
+ if (!exactKeys(options, optionKeys, new Set())) {
1698
+ fail('caller overrides are not accepted in assembler options');
1699
+ }
1700
+ const maxDocumentBytes = options.maxDocumentBytes ?? DEFAULT_MAX_DOCUMENT_BYTES;
1701
+ if (!Number.isSafeInteger(maxDocumentBytes) || maxDocumentBytes <= 0) {
1702
+ fail('maxDocumentBytes must be a positive safe integer');
1703
+ }
1704
+ const assembledAt = evaluationInstant(options.now ?? Date.now);
1705
+ const bytes = finalPdfBytes(input.finalPdfBytes, maxDocumentBytes);
1706
+ const record = canonicalCopy(input.kernelRecord, 'kernelRecord');
1707
+ const documentExecution = input.documentExecution === null
1708
+ ? null
1709
+ : canonicalCopy(input.documentExecution, 'documentExecution');
1710
+ const stateStatement = canonicalCopy(
1711
+ input.operatorStateStatement,
1712
+ 'operatorStateStatement',
1713
+ );
1714
+ const verificationProfile = canonicalCopy(
1715
+ input.verificationProfile,
1716
+ 'verificationProfile',
1717
+ );
1718
+
1719
+ assertNoSecrets(record, 'kernelRecord');
1720
+ assertNoSecrets(documentExecution, 'documentExecution');
1721
+ assertNoSecrets(stateStatement, 'operatorStateStatement');
1722
+ assertNoSecrets(verificationProfile, 'verificationProfile');
1723
+
1724
+ validateKernelRecord(record);
1725
+ if (Date.parse(record.updated_at) > assembledAt) {
1726
+ fail('kernel record is from the future');
1727
+ }
1728
+ const supersessions = validateSupersessions(record);
1729
+ const documentDigest = computeDocumentSha256(bytes);
1730
+ const binding = validateBindingContainer(record.document_action_binding, record, {
1731
+ supersedesDigest: supersessions.currentSupersedes,
1732
+ documentDigest,
1733
+ documentLength: bytes.length,
1734
+ });
1735
+ if (binding.contractorProjectSource !== contractorProjectSource) {
1736
+ fail(contractorProjectSource
1737
+ ? 'contractor evidence package requires the contractor binding profile'
1738
+ : 'contractor binding requires the contractor evidence-package assembler');
1739
+ }
1740
+ if (contractorProjectSource
1741
+ && input.projectRecordSnapshotDigest
1742
+ !== binding.projectRecordSnapshotDigest) {
1743
+ fail('project-record snapshot digest does not match the document binding');
1744
+ }
1745
+ const lifecycle = validateLifecycle(record);
1746
+ const execution = validateDocumentExecution(
1747
+ documentExecution,
1748
+ {
1749
+ agreementId: binding.agreementId,
1750
+ bindingDigest: record.document_action_binding_digest,
1751
+ documentDigest,
1752
+ },
1753
+ lifecycle.requiresExecutedAgreement,
1754
+ );
1755
+ validateProfileReference(verificationProfile, record);
1756
+ validateStateStatement(
1757
+ stateStatement,
1758
+ record,
1759
+ binding.agreementId,
1760
+ supersessions.amendmentDigests,
1761
+ assembledAt,
1762
+ );
1763
+
1764
+ return buildActionEscrowEvidencePackage(/** @type {any} */ ({
1765
+ agreementId: binding.agreementId,
1766
+ stage: record.state,
1767
+ binding: binding.binding,
1768
+ documentBytes: bytes,
1769
+ documentFileName: input.documentFileName,
1770
+ documentExecution: execution,
1771
+ agreementAcceptances: lifecycle.agreementAcceptances,
1772
+ releaseApprovals: lifecycle.releaseApprovals,
1773
+ fundingStatement: lifecycle.fundingStatement,
1774
+ milestones: lifecycle.milestones,
1775
+ release: lifecycle.release,
1776
+ stateRecord: {
1777
+ snapshot: record,
1778
+ statement: stateStatement,
1779
+ },
1780
+ amendments: supersessions.amendments,
1781
+ verificationProfile,
1782
+ ...(contractorProjectSource
1783
+ ? {
1784
+ projectRecordBytes: input.projectRecordBytes,
1785
+ projectRecordFileName: input.projectRecordFileName,
1786
+ projectRecordProvider: input.projectRecordProvider,
1787
+ projectRecordSnapshotDigest: input.projectRecordSnapshotDigest,
1788
+ }
1789
+ : {}),
1790
+ }), {
1791
+ now: assembledAt,
1792
+ maxDocumentBytes,
1793
+ ...(contractorProjectSource
1794
+ ? { maxProjectRecordBytes: options.maxProjectRecordBytes }
1795
+ : {}),
1796
+ });
1797
+ }
1798
+
1799
+ export function assembleActionEscrowEvidencePackage(input = {}, options = {}) {
1800
+ return assembleEvidencePackage(input, options, {
1801
+ contractorProjectSource: false,
1802
+ });
1803
+ }
1804
+
1805
+ export function assembleActionEscrowContractorEvidencePackage(
1806
+ input = {},
1807
+ options = {},
1808
+ ) {
1809
+ return assembleEvidencePackage(input, options, {
1810
+ contractorProjectSource: true,
1811
+ });
1812
+ }
1813
+
1814
+ export const buildActionEscrowEvidencePackageFromKernel =
1815
+ assembleActionEscrowEvidencePackage;
1816
+
1817
+ export default {
1818
+ assembleActionEscrowContractorEvidencePackage,
1819
+ assembleActionEscrowEvidencePackage,
1820
+ buildActionEscrowEvidencePackageFromKernel,
1821
+ };