@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,1178 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * EP-ACTION-ESCROW-EVIDENCE-PACKAGE-v1
4
+ *
5
+ * A content-addressed manifest for the artifacts used by Action Escrow. This
6
+ * module does not decide whether a contract is enforceable and does not move
7
+ * money. Verification replays caller-supplied, relying-party-owned component
8
+ * verifiers so a package cannot make an invalid nested artifact trustworthy by
9
+ * merely hashing it.
10
+ */
11
+ import crypto from 'node:crypto';
12
+ import { hashCanonical } from './execution-binding.js';
13
+ import { strictJsonGate } from './strict-json.js';
14
+
15
+ export const ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION = 'EP-ACTION-ESCROW-EVIDENCE-PACKAGE-v1';
16
+ export const ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION =
17
+ 'EP-ACTION-ESCROW-CONTRACTOR-EVIDENCE-PACKAGE-v1';
18
+ export const ACTION_ESCROW_EVIDENCE_STAGES = Object.freeze([
19
+ 'draft',
20
+ 'awaiting_acceptance',
21
+ 'effective',
22
+ 'awaiting_funding',
23
+ 'funded',
24
+ 'milestone_submitted',
25
+ 'release_reserved',
26
+ 'released',
27
+ 'disputed',
28
+ 'amendment_pending',
29
+ 'cancelled',
30
+ 'completed',
31
+ 'release_indeterminate',
32
+ ]);
33
+
34
+ const HASH = /^sha256:[0-9a-f]{64}$/;
35
+ const TOP_KEYS = new Set([
36
+ 'version',
37
+ 'agreement_id',
38
+ 'stage',
39
+ 'binding',
40
+ 'document',
41
+ 'document_execution',
42
+ 'agreement_acceptances',
43
+ 'release_approvals',
44
+ 'funding_statement',
45
+ 'milestones',
46
+ 'release',
47
+ 'state_record',
48
+ 'amendments',
49
+ 'verification_profile',
50
+ 'assembled_at',
51
+ 'limitations',
52
+ 'package_digest',
53
+ ]);
54
+ const CONTRACTOR_TOP_KEYS = new Set([
55
+ ...TOP_KEYS,
56
+ 'project_record',
57
+ ]);
58
+ const DOCUMENT_KEYS = new Set(['media_type', 'digest', 'byte_length', 'file_name']);
59
+ const PROJECT_RECORD_KEYS = new Set([
60
+ 'media_type',
61
+ 'digest',
62
+ 'byte_length',
63
+ 'file_name',
64
+ 'provider',
65
+ 'snapshot_digest',
66
+ ]);
67
+ const PARTY_EVIDENCE_KEYS = new Set(['party_id', 'role', 'evidence']);
68
+ const MILESTONE_KEYS = new Set(['milestone_id', 'evidence', 'resolution']);
69
+ const RELEASE_KEYS = new Set([
70
+ 'reservation',
71
+ 'provider_request',
72
+ 'provider_statement',
73
+ 'execution_record',
74
+ ]);
75
+ const STATE_RECORD_KEYS = new Set(['snapshot', 'statement']);
76
+ const LIMITATIONS = Object.freeze([
77
+ 'The package does not establish contract enforceability, comprehension, voluntariness, workmanship, physical truth, or legal compliance.',
78
+ 'The package does not establish custodian licensing, solvency, or that no payment path existed outside the integrated release boundary.',
79
+ 'A content digest does not upgrade an invalid component; each component must verify under relying-party-pinned trust roots.',
80
+ 'An indeterminate provider effect is not proof of failure and must not be retried before authoritative reconciliation.',
81
+ ]);
82
+ const DEFAULT_MAX_DOCUMENT_BYTES = 50 * 1024 * 1024;
83
+ const DEFAULT_MAX_PROJECT_RECORD_BYTES = 4 * 1024 * 1024;
84
+ const DEFAULT_MAX_PACKAGE_BYTES = 4 * 1024 * 1024;
85
+
86
+ /**
87
+ * @param {unknown} value
88
+ * @returns {value is Record<string, any>}
89
+ */
90
+ function isRecord(value) {
91
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
92
+ }
93
+
94
+ /**
95
+ * @param {*} value
96
+ * @param {Set<string>} allowed
97
+ * @param {Set<string>} [required]
98
+ */
99
+ function exactKeys(value, allowed, required = allowed) {
100
+ if (!isRecord(value)) return false;
101
+ const keys = Object.keys(value);
102
+ return keys.every((key) => allowed.has(key))
103
+ && [...required].every((key) => Object.hasOwn(value, key));
104
+ }
105
+
106
+ /**
107
+ * @param {number|string|Date|(() => (number|string|Date))} value
108
+ */
109
+ function toIso(value) {
110
+ const candidate = typeof value === 'function' ? value() : value;
111
+ const date = candidate instanceof Date ? candidate : new Date(candidate ?? 0);
112
+ if (!Number.isFinite(date.getTime())) {
113
+ throw new TypeError('action-escrow evidence: assembled_at must be a valid instant');
114
+ }
115
+ return date.toISOString();
116
+ }
117
+
118
+ /**
119
+ * @param {*} value
120
+ */
121
+ function portableJsonCopy(value) {
122
+ let nodes = 0;
123
+ let stringBytes = 0;
124
+ const active = new Set();
125
+
126
+ /**
127
+ * @param {*} current
128
+ * @param {number} depth
129
+ * @returns {*}
130
+ */
131
+ function copy(current, depth) {
132
+ nodes += 1;
133
+ if (nodes > 50_000 || depth > 64) {
134
+ throw new TypeError('action-escrow evidence: value exceeds resource limits');
135
+ }
136
+ if (current === null || typeof current === 'boolean') return current;
137
+ if (typeof current === 'string') {
138
+ stringBytes += Buffer.byteLength(current, 'utf8');
139
+ if (stringBytes > 4 * 1024 * 1024) {
140
+ throw new TypeError('action-escrow evidence: strings exceed resource limits');
141
+ }
142
+ return current;
143
+ }
144
+ if (typeof current === 'number') {
145
+ if (!Number.isSafeInteger(current) || Object.is(current, -0)) {
146
+ throw new TypeError('action-escrow evidence: numbers must be safe integers');
147
+ }
148
+ return current;
149
+ }
150
+ if (!isRecord(current) && !Array.isArray(current)) {
151
+ throw new TypeError('action-escrow evidence: value is not canonical JSON');
152
+ }
153
+ if (active.has(current)) {
154
+ throw new TypeError('action-escrow evidence: cyclic or aliased value');
155
+ }
156
+ active.add(current);
157
+ try {
158
+ if (Array.isArray(current)) return current.map((entry) => copy(entry, depth + 1));
159
+ const prototype = Object.getPrototypeOf(current);
160
+ if (prototype !== Object.prototype && prototype !== null) {
161
+ throw new TypeError('action-escrow evidence: objects must use a plain prototype');
162
+ }
163
+ return Object.fromEntries(
164
+ Object.entries(current).map(([key, entry]) => [key, copy(entry, depth + 1)]),
165
+ );
166
+ } finally {
167
+ active.delete(current);
168
+ }
169
+ }
170
+
171
+ return copy(value, 0);
172
+ }
173
+
174
+ /**
175
+ * @param {*} value
176
+ */
177
+ function deepFreeze(value) {
178
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
179
+ Object.freeze(value);
180
+ for (const child of Object.values(value)) deepFreeze(child);
181
+ return value;
182
+ }
183
+
184
+ /**
185
+ * @param {*} value
186
+ * @param {number} maxBytes
187
+ */
188
+ function documentBytes(value, maxBytes) {
189
+ if (!(Buffer.isBuffer(value) || value instanceof Uint8Array)) {
190
+ throw new TypeError('action-escrow evidence: documentBytes must be bytes');
191
+ }
192
+ const bytes = Buffer.from(value);
193
+ if (bytes.length === 0 || bytes.length > maxBytes) {
194
+ throw new TypeError(`action-escrow evidence: documentBytes must be between 1 and ${maxBytes} bytes`);
195
+ }
196
+ return bytes;
197
+ }
198
+
199
+ /**
200
+ * @param {*} value
201
+ * @param {number} maxBytes
202
+ */
203
+ function projectRecordBytes(value, maxBytes) {
204
+ if (!(Buffer.isBuffer(value) || value instanceof Uint8Array)) {
205
+ throw new TypeError('action-escrow evidence: projectRecordBytes must be bytes');
206
+ }
207
+ const bytes = Buffer.from(value);
208
+ if (bytes.length === 0 || bytes.length > maxBytes) {
209
+ throw new TypeError(
210
+ `action-escrow evidence: projectRecordBytes must be between 1 and ${maxBytes} bytes`,
211
+ );
212
+ }
213
+ return bytes;
214
+ }
215
+
216
+ /**
217
+ * @param {*} value
218
+ * @param {string} fieldName
219
+ */
220
+ function validFileName(value, fieldName) {
221
+ if (value === null) return true;
222
+ if (typeof value !== 'string'
223
+ || value.length === 0
224
+ || value.length > 255
225
+ || /[/\\\u0000]/.test(value)) {
226
+ throw new TypeError(`action-escrow evidence: ${fieldName} is invalid`);
227
+ }
228
+ return true;
229
+ }
230
+
231
+ /**
232
+ * @param {Buffer} bytes
233
+ */
234
+ function sha256(bytes) {
235
+ return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
236
+ }
237
+
238
+ /**
239
+ * @param {*} value
240
+ */
241
+ function validInstant(value) {
242
+ if (typeof value !== 'string') return false;
243
+ const parsed = Date.parse(value);
244
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
245
+ }
246
+
247
+ /**
248
+ * @param {*} value
249
+ */
250
+ function canonicalSha256(value) {
251
+ return `sha256:${hashCanonical(value)}`;
252
+ }
253
+
254
+ /**
255
+ * @param {*} pkg
256
+ */
257
+ function digestScope(pkg) {
258
+ const { package_digest: _digest, ...scope } = pkg;
259
+ return scope;
260
+ }
261
+
262
+ /**
263
+ * Strict raw parser for security-bearing package transport.
264
+ * @param {*} raw
265
+ * @param {{ maxBytes?: number }} [options]
266
+ */
267
+ export function parseActionEscrowEvidencePackage(raw, {
268
+ maxBytes = DEFAULT_MAX_PACKAGE_BYTES,
269
+ } = {}) {
270
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
271
+ return { ok: false, reason: 'invalid_package_limit', value: null };
272
+ }
273
+ if (typeof raw !== 'string') {
274
+ return { ok: false, reason: 'package_must_be_json_text', value: null };
275
+ }
276
+ if (Buffer.byteLength(raw, 'utf8') > maxBytes) {
277
+ return { ok: false, reason: 'package_exceeds_size_limit', value: null };
278
+ }
279
+ const gated = strictJsonGate(raw);
280
+ if (!gated.ok) return { ok: false, reason: gated.reason, value: null };
281
+ try {
282
+ return { ok: true, reason: 'parsed', value: JSON.parse(raw) };
283
+ } catch {
284
+ return { ok: false, reason: 'invalid_json_syntax', value: null };
285
+ }
286
+ }
287
+
288
+ /**
289
+ * @param {*} value
290
+ */
291
+ function normalizedRequiredParties(value) {
292
+ if (!Array.isArray(value) || value.length === 0) return null;
293
+ const result: Array<{ party_id: string; role: string | null }> = [];
294
+ for (const entry of value) {
295
+ if (typeof entry === 'string' && entry.length > 0) {
296
+ result.push({ party_id: entry, role: null });
297
+ continue;
298
+ }
299
+ if (!isRecord(entry)
300
+ || typeof entry.party_id !== 'string' || entry.party_id.length === 0
301
+ || typeof entry.role !== 'string' || entry.role.length === 0) {
302
+ return null;
303
+ }
304
+ result.push({ party_id: entry.party_id, role: entry.role });
305
+ }
306
+ const identities = result.map((entry) => `${entry.role ?? ''}\u0000${entry.party_id}`);
307
+ return new Set(identities).size === identities.length ? result : null;
308
+ }
309
+
310
+ /**
311
+ * @param {string} stage
312
+ */
313
+ function stageRequiresExecutedAgreement(stage) {
314
+ return [
315
+ 'effective',
316
+ 'awaiting_funding',
317
+ 'funded',
318
+ 'milestone_submitted',
319
+ 'release_reserved',
320
+ 'released',
321
+ 'disputed',
322
+ 'completed',
323
+ 'release_indeterminate',
324
+ ].includes(stage);
325
+ }
326
+
327
+ /**
328
+ * @param {string} stage
329
+ */
330
+ function stageRequiresReleaseApprovals(stage) {
331
+ return ['release_reserved', 'released', 'completed', 'release_indeterminate'].includes(stage);
332
+ }
333
+
334
+ /**
335
+ * @param {string} stage
336
+ */
337
+ function stageRequiresFunding(stage) {
338
+ return [
339
+ 'funded',
340
+ 'milestone_submitted',
341
+ 'release_reserved',
342
+ 'released',
343
+ 'disputed',
344
+ 'completed',
345
+ 'release_indeterminate',
346
+ ].includes(stage);
347
+ }
348
+
349
+ /**
350
+ * @param {string} stage
351
+ */
352
+ function stageAllowsFunding(stage) {
353
+ return [
354
+ 'funded',
355
+ 'milestone_submitted',
356
+ 'release_reserved',
357
+ 'released',
358
+ 'disputed',
359
+ 'completed',
360
+ 'release_indeterminate',
361
+ ].includes(stage);
362
+ }
363
+
364
+ /**
365
+ * @param {string} stage
366
+ */
367
+ function stageRequiresMilestone(stage) {
368
+ return [
369
+ 'milestone_submitted',
370
+ 'release_reserved',
371
+ 'released',
372
+ 'disputed',
373
+ 'completed',
374
+ 'release_indeterminate',
375
+ ].includes(stage);
376
+ }
377
+
378
+ /**
379
+ * @param {string} stage
380
+ */
381
+ function stageAllowsMilestone(stage) {
382
+ return [
383
+ 'milestone_submitted',
384
+ 'release_reserved',
385
+ 'released',
386
+ 'disputed',
387
+ 'completed',
388
+ 'release_indeterminate',
389
+ ].includes(stage);
390
+ }
391
+
392
+ /**
393
+ * @param {string} stage
394
+ */
395
+ function stageAllowsReleaseApprovals(stage) {
396
+ return [
397
+ 'milestone_submitted',
398
+ 'release_reserved',
399
+ 'released',
400
+ 'disputed',
401
+ 'completed',
402
+ 'release_indeterminate',
403
+ ].includes(stage);
404
+ }
405
+
406
+ /**
407
+ * @param {string} stage
408
+ */
409
+ function stageRequiresRelease(stage) {
410
+ return ['release_reserved', 'released', 'completed', 'release_indeterminate'].includes(stage);
411
+ }
412
+
413
+ /**
414
+ * @param {string} stage
415
+ */
416
+ function stageAllowsRelease(stage) {
417
+ return [
418
+ 'milestone_submitted',
419
+ 'release_reserved',
420
+ 'released',
421
+ 'disputed',
422
+ 'amendment_pending',
423
+ 'completed',
424
+ 'release_indeterminate',
425
+ ].includes(stage);
426
+ }
427
+
428
+ /**
429
+ * Build a portable evidence manifest. The final document bytes are hashed but
430
+ * not embedded; transport them beside the JSON manifest.
431
+ *
432
+ * @param {{ now?: number, maxDocumentBytes?: number, maxProjectRecordBytes?: number }} [limits]
433
+ */
434
+ export function buildActionEscrowEvidencePackage({
435
+ agreementId,
436
+ stage,
437
+ binding,
438
+ documentBytes: rawDocumentBytes,
439
+ documentFileName = null,
440
+ documentExecution = null,
441
+ agreementAcceptances = [],
442
+ releaseApprovals = [],
443
+ fundingStatement = null,
444
+ milestones = [],
445
+ release = null,
446
+ stateRecord,
447
+ amendments = [],
448
+ verificationProfile,
449
+ projectRecordBytes: rawProjectRecordBytes = null,
450
+ projectRecordFileName = null,
451
+ projectRecordProvider = null,
452
+ projectRecordSnapshotDigest = null,
453
+ }: {
454
+ agreementId?: string;
455
+ stage?: string;
456
+ binding?: unknown;
457
+ documentBytes?: unknown;
458
+ documentFileName?: string | null;
459
+ documentExecution?: unknown;
460
+ agreementAcceptances?: any[];
461
+ releaseApprovals?: any[];
462
+ fundingStatement?: unknown;
463
+ milestones?: any[];
464
+ release?: unknown;
465
+ stateRecord?: unknown;
466
+ amendments?: any[];
467
+ verificationProfile?: unknown;
468
+ projectRecordBytes?: unknown;
469
+ projectRecordFileName?: string | null;
470
+ projectRecordProvider?: string | null;
471
+ projectRecordSnapshotDigest?: string | null;
472
+ } = {}, {
473
+ now = 0,
474
+ maxDocumentBytes = DEFAULT_MAX_DOCUMENT_BYTES,
475
+ maxProjectRecordBytes = DEFAULT_MAX_PROJECT_RECORD_BYTES,
476
+ }: {
477
+ now?: number;
478
+ maxDocumentBytes?: number;
479
+ maxProjectRecordBytes?: number;
480
+ } = {}) {
481
+ if (typeof agreementId !== 'string' || agreementId.length === 0) {
482
+ throw new TypeError('action-escrow evidence: agreementId is required');
483
+ }
484
+ if (!ACTION_ESCROW_EVIDENCE_STAGES.includes(stage as string)) {
485
+ throw new TypeError('action-escrow evidence: stage is not in the closed set');
486
+ }
487
+ if (!Number.isSafeInteger(maxDocumentBytes) || maxDocumentBytes <= 0) {
488
+ throw new TypeError('action-escrow evidence: maxDocumentBytes must be a positive safe integer');
489
+ }
490
+ const bytes = documentBytes(rawDocumentBytes, maxDocumentBytes);
491
+ validFileName(documentFileName, 'documentFileName');
492
+ const hasProjectRecord = rawProjectRecordBytes !== null
493
+ || projectRecordProvider !== null
494
+ || projectRecordSnapshotDigest !== null
495
+ || projectRecordFileName !== null;
496
+ let projectBytes: Buffer | null = null;
497
+ if (hasProjectRecord) {
498
+ if (!Number.isSafeInteger(maxProjectRecordBytes)
499
+ || maxProjectRecordBytes <= 0) {
500
+ throw new TypeError(
501
+ 'action-escrow evidence: maxProjectRecordBytes must be a positive safe integer',
502
+ );
503
+ }
504
+ if (typeof projectRecordProvider !== 'string'
505
+ || projectRecordProvider.length === 0
506
+ || projectRecordProvider.length > 128
507
+ // HASH.test() ToString-coerces its argument at runtime regardless;
508
+ // String() here makes that coercion explicit for the type checker.
509
+ || !HASH.test(String(projectRecordSnapshotDigest))) {
510
+ throw new TypeError(
511
+ 'action-escrow evidence: project-record provider and snapshot digest are required',
512
+ );
513
+ }
514
+ validFileName(projectRecordFileName, 'projectRecordFileName');
515
+ projectBytes = projectRecordBytes(
516
+ rawProjectRecordBytes,
517
+ maxProjectRecordBytes,
518
+ );
519
+ }
520
+
521
+ const body = portableJsonCopy({
522
+ version: hasProjectRecord
523
+ ? ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION
524
+ : ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION,
525
+ agreement_id: agreementId,
526
+ stage,
527
+ binding,
528
+ document: {
529
+ media_type: 'application/pdf',
530
+ digest: sha256(bytes),
531
+ byte_length: bytes.length,
532
+ ...(documentFileName === null ? {} : { file_name: documentFileName }),
533
+ },
534
+ ...(hasProjectRecord
535
+ ? {
536
+ project_record: {
537
+ media_type: 'application/json',
538
+ // projectBytes is non-null here: hasProjectRecord true means the
539
+ // `if (hasProjectRecord)` block above already assigned it.
540
+ digest: sha256(projectBytes!),
541
+ byte_length: projectBytes!.length,
542
+ ...(projectRecordFileName === null
543
+ ? {}
544
+ : { file_name: projectRecordFileName }),
545
+ provider: projectRecordProvider,
546
+ snapshot_digest: projectRecordSnapshotDigest,
547
+ },
548
+ }
549
+ : {}),
550
+ document_execution: documentExecution,
551
+ agreement_acceptances: agreementAcceptances,
552
+ release_approvals: releaseApprovals,
553
+ funding_statement: fundingStatement,
554
+ milestones,
555
+ release: release ?? {
556
+ reservation: null,
557
+ provider_request: null,
558
+ provider_statement: null,
559
+ execution_record: null,
560
+ },
561
+ state_record: stateRecord,
562
+ amendments,
563
+ verification_profile: verificationProfile,
564
+ assembled_at: toIso(now),
565
+ limitations: [...LIMITATIONS],
566
+ });
567
+
568
+ return deepFreeze({
569
+ ...body,
570
+ package_digest: canonicalSha256(body),
571
+ });
572
+ }
573
+
574
+ function resultFailure(reason, checks, details = {}) {
575
+ return {
576
+ valid: false,
577
+ reason,
578
+ checks,
579
+ ...details,
580
+ };
581
+ }
582
+
583
+ async function callVerifier(verifier, value, context) {
584
+ if (typeof verifier !== 'function') return { valid: false, reason: 'verifier_required' };
585
+ try {
586
+ const result = await verifier(value, context);
587
+ return isRecord(result) ? result : { valid: false, reason: 'malformed_verifier_result' };
588
+ } catch {
589
+ return { valid: false, reason: 'verifier_threw' };
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Shape of every pluggable relying-party-owned component verifier accepted
595
+ * below: caller-supplied, arbitrary in signature beyond (value, context).
596
+ */
597
+ type EvidenceVerifierFn = (value: any, context: any) => any;
598
+
599
+ /**
600
+ * Re-perform every package join using relying-party-owned component verifiers.
601
+ *
602
+ * Component verifiers are configuration, never read from the package. Their
603
+ * returned binding fields are checked again here so a valid artifact for one
604
+ * agreement, document, party, or action cannot be relabeled into another slot.
605
+ *
606
+ * @param {*} pkg
607
+ */
608
+ export async function verifyActionEscrowEvidencePackage(pkg, {
609
+ documentBytes: rawDocumentBytes,
610
+ projectRecordBytes: rawProjectRecordBytes,
611
+ verifyBinding,
612
+ verifyProjectRecord,
613
+ verifyProfile,
614
+ verifyDocumentExecution,
615
+ verifyAgreementAcceptance,
616
+ verifyReleaseApproval,
617
+ verifyFunding,
618
+ verifyMilestone,
619
+ verifyRelease,
620
+ verifyAmendment,
621
+ verifyState,
622
+ expectedAgreementId,
623
+ now,
624
+ maxDocumentBytes = DEFAULT_MAX_DOCUMENT_BYTES,
625
+ maxProjectRecordBytes = DEFAULT_MAX_PROJECT_RECORD_BYTES,
626
+ }: {
627
+ documentBytes?: unknown;
628
+ projectRecordBytes?: unknown;
629
+ verifyBinding?: EvidenceVerifierFn;
630
+ verifyProjectRecord?: EvidenceVerifierFn;
631
+ verifyProfile?: EvidenceVerifierFn;
632
+ verifyDocumentExecution?: EvidenceVerifierFn;
633
+ verifyAgreementAcceptance?: EvidenceVerifierFn;
634
+ verifyReleaseApproval?: EvidenceVerifierFn;
635
+ verifyFunding?: EvidenceVerifierFn;
636
+ verifyMilestone?: EvidenceVerifierFn;
637
+ verifyRelease?: EvidenceVerifierFn;
638
+ verifyAmendment?: EvidenceVerifierFn;
639
+ verifyState?: EvidenceVerifierFn;
640
+ expectedAgreementId?: string;
641
+ now?: Date | number | string;
642
+ maxDocumentBytes?: number;
643
+ maxProjectRecordBytes?: number;
644
+ } = {}) {
645
+ const checks = {
646
+ structure: false,
647
+ package_digest: false,
648
+ time: false,
649
+ document: false,
650
+ project_record: false,
651
+ binding: false,
652
+ profile: false,
653
+ document_execution: false,
654
+ agreement_acceptances: false,
655
+ amendments: false,
656
+ state: false,
657
+ release_approvals: false,
658
+ funding: false,
659
+ milestones: false,
660
+ release: false,
661
+ };
662
+
663
+ try {
664
+ const contractorPackage = pkg?.version
665
+ === ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION;
666
+ const allowedTop = contractorPackage ? CONTRACTOR_TOP_KEYS : TOP_KEYS;
667
+ const requiredTop = new Set(allowedTop);
668
+ checks.structure = exactKeys(pkg, allowedTop, requiredTop)
669
+ && (
670
+ pkg.version === ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION
671
+ || contractorPackage
672
+ )
673
+ && typeof pkg.agreement_id === 'string' && pkg.agreement_id.length > 0
674
+ && ACTION_ESCROW_EVIDENCE_STAGES.includes(pkg.stage)
675
+ && HASH.test(pkg.package_digest)
676
+ && exactKeys(pkg.document, DOCUMENT_KEYS, new Set(['media_type', 'digest', 'byte_length']))
677
+ && pkg.document.media_type === 'application/pdf'
678
+ && HASH.test(pkg.document.digest)
679
+ && Number.isSafeInteger(pkg.document.byte_length) && pkg.document.byte_length > 0
680
+ && (!contractorPackage
681
+ || (
682
+ exactKeys(
683
+ pkg.project_record,
684
+ PROJECT_RECORD_KEYS,
685
+ new Set([
686
+ 'media_type',
687
+ 'digest',
688
+ 'byte_length',
689
+ 'provider',
690
+ 'snapshot_digest',
691
+ ]),
692
+ )
693
+ && pkg.project_record.media_type === 'application/json'
694
+ && HASH.test(pkg.project_record.digest)
695
+ && Number.isSafeInteger(pkg.project_record.byte_length)
696
+ && pkg.project_record.byte_length > 0
697
+ && typeof pkg.project_record.provider === 'string'
698
+ && pkg.project_record.provider.length > 0
699
+ && pkg.project_record.provider.length <= 128
700
+ && HASH.test(pkg.project_record.snapshot_digest)
701
+ ))
702
+ && (pkg.document_execution === null || isRecord(pkg.document_execution))
703
+ && Array.isArray(pkg.agreement_acceptances)
704
+ && Array.isArray(pkg.release_approvals)
705
+ && Array.isArray(pkg.milestones)
706
+ && exactKeys(pkg.release, RELEASE_KEYS)
707
+ && exactKeys(pkg.state_record, STATE_RECORD_KEYS)
708
+ && Array.isArray(pkg.amendments)
709
+ && Array.isArray(pkg.limitations)
710
+ && pkg.limitations.length === LIMITATIONS.length
711
+ && pkg.limitations.every((entry, index) => entry === LIMITATIONS[index]);
712
+ if (!checks.structure) return resultFailure('malformed_evidence_package', checks);
713
+
714
+ if (expectedAgreementId !== undefined
715
+ && (typeof expectedAgreementId !== 'string' || pkg.agreement_id !== expectedAgreementId)) {
716
+ return resultFailure('agreement_id_mismatch', checks);
717
+ }
718
+
719
+ checks.package_digest = canonicalSha256(digestScope(pkg)) === pkg.package_digest;
720
+ if (!checks.package_digest) return resultFailure('package_digest_mismatch', checks);
721
+
722
+ const assembledAt = Date.parse(pkg.assembled_at);
723
+ const evaluation = now === undefined
724
+ ? null
725
+ : (now instanceof Date ? now.getTime() : typeof now === 'number' ? now : Date.parse(now));
726
+ checks.time = Number.isFinite(assembledAt)
727
+ && (evaluation === null || (Number.isFinite(evaluation) && assembledAt <= evaluation));
728
+ if (!checks.time) return resultFailure('invalid_or_future_assembled_at', checks);
729
+
730
+ if (!Number.isSafeInteger(maxDocumentBytes) || maxDocumentBytes <= 0) {
731
+ return resultFailure('invalid_document_limit', checks);
732
+ }
733
+ const bytes = documentBytes(rawDocumentBytes, maxDocumentBytes);
734
+ checks.document = bytes.length === pkg.document.byte_length && sha256(bytes) === pkg.document.digest;
735
+ if (!checks.document) return resultFailure('document_bytes_mismatch', checks);
736
+
737
+ const bindingResult = await callVerifier(verifyBinding, pkg.binding, {
738
+ expectedAgreementId: pkg.agreement_id,
739
+ expectedDocumentDigest: pkg.document.digest,
740
+ });
741
+ const contractorBinding = HASH.test(
742
+ pkg.binding?.release_action?.template?.project_record_snapshot_digest,
743
+ );
744
+ const requiredParties = normalizedRequiredParties(bindingResult.required_parties);
745
+ checks.binding = bindingResult.valid === true
746
+ && bindingResult.agreement_id === pkg.agreement_id
747
+ && bindingResult.document_digest === pkg.document.digest
748
+ && HASH.test(bindingResult.binding_digest)
749
+ && HASH.test(bindingResult.action_digest)
750
+ && (bindingResult.supersedes_digest === null || HASH.test(bindingResult.supersedes_digest))
751
+ && requiredParties !== null;
752
+ if (!checks.binding) {
753
+ return resultFailure('binding_verification_failed', checks, {
754
+ binding_reason: bindingResult.reason ?? null,
755
+ });
756
+ }
757
+ if (contractorBinding !== contractorPackage) {
758
+ return resultFailure('evidence_profile_mismatch', checks);
759
+ }
760
+
761
+ if (contractorPackage) {
762
+ if (!HASH.test(bindingResult.project_record_snapshot_digest)
763
+ || bindingResult.project_record_snapshot_digest
764
+ !== pkg.project_record.snapshot_digest
765
+ || !Number.isSafeInteger(maxProjectRecordBytes)
766
+ || maxProjectRecordBytes <= 0) {
767
+ return resultFailure('project_record_binding_failed', checks);
768
+ }
769
+ let sourceBytes;
770
+ try {
771
+ sourceBytes = projectRecordBytes(
772
+ rawProjectRecordBytes,
773
+ maxProjectRecordBytes,
774
+ );
775
+ } catch {
776
+ return resultFailure('project_record_bytes_mismatch', checks);
777
+ }
778
+ if (sourceBytes.length !== pkg.project_record.byte_length
779
+ || sha256(sourceBytes) !== pkg.project_record.digest) {
780
+ return resultFailure('project_record_bytes_mismatch', checks);
781
+ }
782
+ const gated = strictJsonGate(sourceBytes.toString('utf8'));
783
+ let projectRecord = null;
784
+ if (gated.ok) {
785
+ try {
786
+ projectRecord = JSON.parse(sourceBytes.toString('utf8'));
787
+ } catch {
788
+ projectRecord = null;
789
+ }
790
+ }
791
+ if (!isRecord(projectRecord)) {
792
+ return resultFailure('project_record_json_invalid', checks);
793
+ }
794
+ const projectResult = await callVerifier(
795
+ verifyProjectRecord,
796
+ projectRecord,
797
+ {
798
+ agreementId: pkg.agreement_id,
799
+ bindingDigest: bindingResult.binding_digest,
800
+ actionDigest: bindingResult.action_digest,
801
+ provider: pkg.project_record.provider,
802
+ snapshotDigest: pkg.project_record.snapshot_digest,
803
+ },
804
+ );
805
+ checks.project_record = projectResult.valid === true
806
+ && projectResult.authorizes_action === false
807
+ && projectResult.establishes_acceptance === false
808
+ && projectResult.provider === pkg.project_record.provider
809
+ && projectResult.snapshot_digest === pkg.project_record.snapshot_digest;
810
+ if (!checks.project_record) {
811
+ return resultFailure('project_record_verification_failed', checks, {
812
+ project_record_reason: projectResult.reason ?? null,
813
+ });
814
+ }
815
+ } else {
816
+ checks.project_record = true;
817
+ }
818
+
819
+ const profileResult = await callVerifier(verifyProfile, pkg.verification_profile, {
820
+ agreementId: pkg.agreement_id,
821
+ bindingDigest: bindingResult.binding_digest,
822
+ actionDigest: bindingResult.action_digest,
823
+ });
824
+ const requiredReleaseParties = normalizedRequiredParties(
825
+ profileResult.required_release_parties,
826
+ );
827
+ checks.profile = profileResult.valid === true
828
+ && profileResult.agreement_id === pkg.agreement_id
829
+ && profileResult.binding_digest === bindingResult.binding_digest
830
+ && profileResult.action_digest === bindingResult.action_digest
831
+ && HASH.test(profileResult.profile_digest)
832
+ && requiredReleaseParties !== null;
833
+ if (!checks.profile) {
834
+ return resultFailure('verification_profile_failed', checks, {
835
+ profile_reason: profileResult.reason ?? null,
836
+ });
837
+ }
838
+
839
+ if (pkg.document_execution !== null) {
840
+ const executionResult = await callVerifier(
841
+ verifyDocumentExecution,
842
+ pkg.document_execution,
843
+ {
844
+ agreementId: pkg.agreement_id,
845
+ bindingDigest: bindingResult.binding_digest,
846
+ documentDigest: pkg.document.digest,
847
+ },
848
+ );
849
+ checks.document_execution = executionResult.valid === true
850
+ && executionResult.authorizes_action === false
851
+ && executionResult.agreement_id === pkg.agreement_id
852
+ && executionResult.binding_digest === bindingResult.binding_digest
853
+ && executionResult.document_digest === pkg.document.digest
854
+ && executionResult.state === 'executed';
855
+ } else {
856
+ checks.document_execution = !stageRequiresExecutedAgreement(pkg.stage);
857
+ }
858
+ if (!checks.document_execution) {
859
+ return resultFailure('document_execution_verification_failed', checks);
860
+ }
861
+
862
+ // requiredParties is non-null here: checks.binding (verified above, with
863
+ // an early return when false) requires requiredParties !== null.
864
+ const requiredAgreementByIdentity = new Map(
865
+ requiredParties!.map((entry) => [`${entry.role ?? ''}\u0000${entry.party_id}`, entry]),
866
+ );
867
+ const seenAgreementParties = new Set();
868
+ const seenAgreementKeys = new Set();
869
+ let agreementAcceptancesValid = true;
870
+ for (const acceptance of pkg.agreement_acceptances) {
871
+ if (!exactKeys(acceptance, PARTY_EVIDENCE_KEYS)
872
+ || typeof acceptance.party_id !== 'string'
873
+ || typeof acceptance.role !== 'string') {
874
+ agreementAcceptancesValid = false;
875
+ break;
876
+ }
877
+ const identity = `${acceptance.role}\u0000${acceptance.party_id}`;
878
+ if (!requiredAgreementByIdentity.has(identity) || seenAgreementParties.has(identity)) {
879
+ agreementAcceptancesValid = false;
880
+ break;
881
+ }
882
+ const result = await callVerifier(
883
+ verifyAgreementAcceptance,
884
+ acceptance.evidence,
885
+ {
886
+ partyId: acceptance.party_id,
887
+ role: acceptance.role,
888
+ agreementId: pkg.agreement_id,
889
+ bindingDigest: bindingResult.binding_digest,
890
+ documentDigest: pkg.document.digest,
891
+ },
892
+ );
893
+ if (result.valid !== true
894
+ || result.accepts_agreement !== true
895
+ || result.authorizes_action !== false
896
+ || result.agreement_id !== pkg.agreement_id
897
+ || result.party_id !== acceptance.party_id
898
+ || result.role !== acceptance.role
899
+ || result.binding_digest !== bindingResult.binding_digest
900
+ || result.document_digest !== pkg.document.digest
901
+ || typeof result.principal_key_id !== 'string'
902
+ || result.principal_key_id.length === 0
903
+ || seenAgreementKeys.has(result.principal_key_id)) {
904
+ agreementAcceptancesValid = false;
905
+ break;
906
+ }
907
+ seenAgreementParties.add(identity);
908
+ seenAgreementKeys.add(result.principal_key_id);
909
+ }
910
+ if (stageRequiresExecutedAgreement(pkg.stage)) {
911
+ agreementAcceptancesValid = agreementAcceptancesValid
912
+ && seenAgreementParties.size === requiredAgreementByIdentity.size;
913
+ }
914
+ checks.agreement_acceptances = agreementAcceptancesValid;
915
+ if (!checks.agreement_acceptances) {
916
+ return resultFailure('agreement_acceptance_verification_failed', checks);
917
+ }
918
+
919
+ const amendmentResults: Array<Record<string, any>> = [];
920
+ let amendmentsValid = true;
921
+ let expectedNextBinding = bindingResult.binding_digest;
922
+ for (let index = pkg.amendments.length - 1; index >= 0; index -= 1) {
923
+ const result = await callVerifier(verifyAmendment, pkg.amendments[index], {
924
+ agreementId: pkg.agreement_id,
925
+ expectedNextBindingDigest: expectedNextBinding,
926
+ profileDigest: profileResult.profile_digest,
927
+ });
928
+ amendmentResults.unshift(result);
929
+ if (result.valid !== true
930
+ || !HASH.test(result.amendment_digest)
931
+ || !HASH.test(result.previous_binding_digest)
932
+ || result.next_binding_digest !== expectedNextBinding) {
933
+ amendmentsValid = false;
934
+ break;
935
+ }
936
+ expectedNextBinding = result.previous_binding_digest;
937
+ }
938
+ if (bindingResult.supersedes_digest === null) {
939
+ amendmentsValid = amendmentsValid && pkg.amendments.length === 0;
940
+ } else {
941
+ const finalAmendment = amendmentResults.at(-1);
942
+ amendmentsValid = amendmentsValid
943
+ && pkg.amendments.length > 0
944
+ && finalAmendment?.previous_binding_digest === bindingResult.supersedes_digest
945
+ && finalAmendment?.next_binding_digest === bindingResult.binding_digest;
946
+ }
947
+ checks.amendments = amendmentsValid;
948
+ if (!checks.amendments) {
949
+ return resultFailure('amendment_chain_verification_failed', checks);
950
+ }
951
+ const amendmentDigests = amendmentResults.map((result) => result.amendment_digest);
952
+
953
+ const stateResult = await callVerifier(verifyState, pkg.state_record, {
954
+ agreementId: pkg.agreement_id,
955
+ bindingDigest: bindingResult.binding_digest,
956
+ actionDigest: bindingResult.action_digest,
957
+ profileDigest: profileResult.profile_digest,
958
+ amendmentDigests,
959
+ stage: pkg.stage,
960
+ });
961
+ checks.state = stateResult.valid === true
962
+ && stateResult.agreement_id === pkg.agreement_id
963
+ && stateResult.binding_digest === bindingResult.binding_digest
964
+ && stateResult.action_digest === bindingResult.action_digest
965
+ && stateResult.profile_digest === profileResult.profile_digest
966
+ && stateResult.state === pkg.stage
967
+ && Number.isSafeInteger(stateResult.revision) && stateResult.revision >= 0
968
+ && Array.isArray(stateResult.amendment_digests)
969
+ && stateResult.amendment_digests.length === amendmentDigests.length
970
+ && stateResult.amendment_digests.every((digest, index) => digest === amendmentDigests[index]);
971
+ if (!checks.state) {
972
+ return resultFailure('state_record_verification_failed', checks, {
973
+ state_reason: stateResult.reason ?? null,
974
+ });
975
+ }
976
+
977
+ if (pkg.funding_statement !== null) {
978
+ if (!stageAllowsFunding(pkg.stage)) {
979
+ return resultFailure('unexpected_funding_statement', checks);
980
+ }
981
+ const fundingResult = await callVerifier(verifyFunding, pkg.funding_statement, {
982
+ agreementId: pkg.agreement_id,
983
+ bindingDigest: bindingResult.binding_digest,
984
+ actionDigest: bindingResult.action_digest,
985
+ });
986
+ checks.funding = fundingResult.valid === true
987
+ && fundingResult.agreement_id === pkg.agreement_id
988
+ && fundingResult.binding_digest === bindingResult.binding_digest
989
+ && fundingResult.action_digest === bindingResult.action_digest
990
+ && fundingResult.state === 'funded';
991
+ if (!checks.funding) {
992
+ return resultFailure('funding_statement_verification_failed', checks, {
993
+ funding_reason: fundingResult.reason ?? null,
994
+ });
995
+ }
996
+ } else {
997
+ checks.funding = !stageRequiresFunding(pkg.stage);
998
+ if (!checks.funding) return resultFailure('funding_statement_missing', checks);
999
+ }
1000
+
1001
+ let milestonesValid = true;
1002
+ const milestoneIds = new Set();
1003
+ const milestoneEvidenceDigests: string[] = [];
1004
+ if (pkg.milestones.length > 0 && !stageAllowsMilestone(pkg.stage)) {
1005
+ return resultFailure('unexpected_milestone_evidence', checks);
1006
+ }
1007
+ for (const milestone of pkg.milestones) {
1008
+ if (!exactKeys(milestone, MILESTONE_KEYS)
1009
+ || typeof milestone.milestone_id !== 'string' || milestone.milestone_id.length === 0
1010
+ || milestoneIds.has(milestone.milestone_id)) {
1011
+ milestonesValid = false;
1012
+ break;
1013
+ }
1014
+ milestoneIds.add(milestone.milestone_id);
1015
+ const result = await callVerifier(verifyMilestone, milestone, {
1016
+ agreementId: pkg.agreement_id,
1017
+ bindingDigest: bindingResult.binding_digest,
1018
+ actionDigest: bindingResult.action_digest,
1019
+ });
1020
+ if (result.valid !== true
1021
+ || result.agreement_id !== pkg.agreement_id
1022
+ || result.milestone_id !== milestone.milestone_id
1023
+ || result.binding_digest !== bindingResult.binding_digest
1024
+ || result.action_digest !== bindingResult.action_digest
1025
+ || !HASH.test(result.evidence_digest)) {
1026
+ milestonesValid = false;
1027
+ break;
1028
+ }
1029
+ milestoneEvidenceDigests.push(result.evidence_digest);
1030
+ }
1031
+ if (stageRequiresMilestone(pkg.stage)) milestonesValid = milestonesValid && milestoneIds.size > 0;
1032
+ checks.milestones = milestonesValid;
1033
+ if (!checks.milestones) return resultFailure('milestone_verification_failed', checks);
1034
+
1035
+ // requiredReleaseParties is non-null here: checks.profile (verified
1036
+ // above, with an early return when false) requires it !== null.
1037
+ const requiredReleaseByIdentity = new Map(
1038
+ requiredReleaseParties!.map(
1039
+ (entry) => [`${entry.role ?? ''}\u0000${entry.party_id}`, entry],
1040
+ ),
1041
+ );
1042
+ const seenReleaseParties = new Set();
1043
+ const seenReleaseKeys = new Set();
1044
+ let releaseApprovalsValid = true;
1045
+ if (pkg.release_approvals.length > 0 && !stageAllowsReleaseApprovals(pkg.stage)) {
1046
+ return resultFailure('unexpected_release_approval', checks);
1047
+ }
1048
+ const releaseExecutionAt = pkg.release.execution_record?.at ?? null;
1049
+ for (const [approvalIndex, approval] of pkg.release_approvals.entries()) {
1050
+ if (!exactKeys(approval, PARTY_EVIDENCE_KEYS)
1051
+ || typeof approval.party_id !== 'string'
1052
+ || typeof approval.role !== 'string') {
1053
+ releaseApprovalsValid = false;
1054
+ break;
1055
+ }
1056
+ const identity = `${approval.role}\u0000${approval.party_id}`;
1057
+ if (!requiredReleaseByIdentity.has(identity) || seenReleaseParties.has(identity)) {
1058
+ releaseApprovalsValid = false;
1059
+ break;
1060
+ }
1061
+ const result = await callVerifier(
1062
+ verifyReleaseApproval,
1063
+ approval.evidence,
1064
+ {
1065
+ partyId: approval.party_id,
1066
+ role: approval.role,
1067
+ agreementId: pkg.agreement_id,
1068
+ bindingDigest: bindingResult.binding_digest,
1069
+ actionDigest: bindingResult.action_digest,
1070
+ milestoneEvidenceDigests,
1071
+ approvalIndex,
1072
+ stateRecord: pkg.state_record,
1073
+ },
1074
+ );
1075
+ if (result.valid !== true
1076
+ || result.authorizes_action !== true
1077
+ || result.outcome !== 'approved'
1078
+ || result.agreement_id !== pkg.agreement_id
1079
+ || result.party_id !== approval.party_id
1080
+ || result.role !== approval.role
1081
+ || result.binding_digest !== bindingResult.binding_digest
1082
+ || result.action_digest !== bindingResult.action_digest
1083
+ || !Array.isArray(result.milestone_evidence_digests)
1084
+ || result.milestone_evidence_digests.length !== milestoneEvidenceDigests.length
1085
+ || result.milestone_evidence_digests.some(
1086
+ (digest, index) => digest !== milestoneEvidenceDigests[index],
1087
+ )
1088
+ || typeof result.principal_key_id !== 'string'
1089
+ || result.principal_key_id.length === 0
1090
+ || !validInstant(result.issued_at)
1091
+ || !validInstant(result.expires_at)
1092
+ || !validInstant(result.admitted_at)
1093
+ || Date.parse(result.issued_at) > Date.parse(result.admitted_at)
1094
+ || Date.parse(result.expires_at) <= Date.parse(result.admitted_at)
1095
+ || (
1096
+ releaseExecutionAt !== null
1097
+ && (
1098
+ !validInstant(releaseExecutionAt)
1099
+ || Date.parse(result.issued_at) > Date.parse(releaseExecutionAt)
1100
+ || Date.parse(result.expires_at) <= Date.parse(releaseExecutionAt)
1101
+ )
1102
+ )
1103
+ || seenReleaseKeys.has(result.principal_key_id)) {
1104
+ releaseApprovalsValid = false;
1105
+ break;
1106
+ }
1107
+ seenReleaseParties.add(identity);
1108
+ seenReleaseKeys.add(result.principal_key_id);
1109
+ }
1110
+ if (stageRequiresReleaseApprovals(pkg.stage)) {
1111
+ releaseApprovalsValid = releaseApprovalsValid
1112
+ && seenReleaseParties.size === requiredReleaseByIdentity.size;
1113
+ }
1114
+ checks.release_approvals = releaseApprovalsValid;
1115
+ if (!checks.release_approvals) {
1116
+ return resultFailure('release_approval_verification_failed', checks);
1117
+ }
1118
+
1119
+ const hasReleaseArtifacts = Object.values(pkg.release).some((entry) => entry !== null);
1120
+ if (hasReleaseArtifacts) {
1121
+ if (!stageAllowsRelease(pkg.stage)) {
1122
+ return resultFailure('release_artifacts_not_allowed_for_stage', checks);
1123
+ }
1124
+ const releaseResult = await callVerifier(verifyRelease, pkg.release, {
1125
+ agreementId: pkg.agreement_id,
1126
+ bindingDigest: bindingResult.binding_digest,
1127
+ actionDigest: bindingResult.action_digest,
1128
+ stage: pkg.stage,
1129
+ });
1130
+ const expectedReleaseState = pkg.stage === 'release_indeterminate'
1131
+ ? 'indeterminate'
1132
+ : pkg.stage === 'release_reserved'
1133
+ ? 'reserved'
1134
+ : ['released', 'completed'].includes(pkg.stage)
1135
+ ? 'released'
1136
+ : 'not_released';
1137
+ checks.release = releaseResult.valid === true
1138
+ && releaseResult.agreement_id === pkg.agreement_id
1139
+ && releaseResult.binding_digest === bindingResult.binding_digest
1140
+ && releaseResult.action_digest === bindingResult.action_digest
1141
+ && releaseResult.state === expectedReleaseState;
1142
+ if (!checks.release) {
1143
+ return resultFailure('release_verification_failed', checks, {
1144
+ release_reason: releaseResult.reason ?? null,
1145
+ });
1146
+ }
1147
+ } else {
1148
+ checks.release = !stageRequiresRelease(pkg.stage);
1149
+ if (!checks.release) return resultFailure('release_artifact_missing', checks);
1150
+ }
1151
+
1152
+ return {
1153
+ valid: true,
1154
+ reason: 'verified',
1155
+ checks,
1156
+ package_digest: pkg.package_digest,
1157
+ agreement_id: pkg.agreement_id,
1158
+ binding_digest: bindingResult.binding_digest,
1159
+ action_digest: bindingResult.action_digest,
1160
+ profile_digest: profileResult.profile_digest,
1161
+ project_record_snapshot_digest:
1162
+ bindingResult.project_record_snapshot_digest ?? null,
1163
+ required_parties: requiredParties,
1164
+ required_release_parties: requiredReleaseParties,
1165
+ };
1166
+ } catch {
1167
+ return resultFailure('malformed_evidence_package', checks);
1168
+ }
1169
+ }
1170
+
1171
+ export default {
1172
+ ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION,
1173
+ ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION,
1174
+ ACTION_ESCROW_EVIDENCE_STAGES,
1175
+ parseActionEscrowEvidencePackage,
1176
+ buildActionEscrowEvidencePackage,
1177
+ verifyActionEscrowEvidencePackage,
1178
+ };