@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,847 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Constructor-pinned adapters for the Gate Trust Program profile.
4
+ *
5
+ * These adapters do not create another policy engine. They provide the narrow
6
+ * joins needed to consume existing Quorum, AEC, Receipt Program, and Action
7
+ * Escrow verification results without allowing a presenter to choose verifier
8
+ * functions, trust roots, policies, mappings, or expected values at runtime.
9
+ */
10
+ import crypto from 'node:crypto';
11
+
12
+ // The compatibility entry point resolves to dist both from src during native
13
+ // TypeScript tests and from the compiled dist module.
14
+ // @ts-ignore -- declarations live behind the compatibility entry point.
15
+ import { canonicalize } from '../execution-binding.js';
16
+
17
+ type RecordLike = Record<string, any>;
18
+ type Verifier = (value: any, context: RecordLike) => any;
19
+ type TerminalOutcome = 'executed' | 'refused' | 'indeterminate';
20
+
21
+ const DIGEST = /^sha256:[0-9a-f]{64}$/;
22
+ const BASE64URL = /^[A-Za-z0-9_-]+$/;
23
+ const TRUST_BINDING_KEYS = new Set([
24
+ 'instance_id',
25
+ 'program_digest',
26
+ 'program_version',
27
+ 'root_caid',
28
+ 'action_digest',
29
+ 'stage_id',
30
+ 'requirement_id',
31
+ 'policy_digest',
32
+ 'predecessor_receipt_digests',
33
+ ]);
34
+ const ARTIFACT_KEYS = new Set(['evidence_id', 'binding', 'evidence']);
35
+ const QUORUM_EVIDENCE_KEYS = new Set(['members']);
36
+ const QUORUM_MEMBER_KEYS = new Set(['role', 'signoff']);
37
+ const RECEIPT_EVIDENCE_KEYS = new Set(['certificate']);
38
+ const ACTION_ESCROW_EVIDENCE_KEYS = new Set([
39
+ 'package', 'document_bytes', 'project_record_bytes',
40
+ ]);
41
+ const ACTION_ESCROW_COMPONENT_VERIFIERS = Object.freeze([
42
+ 'verifyBinding',
43
+ 'verifyProjectRecord',
44
+ 'verifyProfile',
45
+ 'verifyDocumentExecution',
46
+ 'verifyAgreementAcceptance',
47
+ 'verifyReleaseApproval',
48
+ 'verifyFunding',
49
+ 'verifyMilestone',
50
+ 'verifyRelease',
51
+ 'verifyAmendment',
52
+ 'verifyState',
53
+ ]);
54
+ const FORBIDDEN_RUNTIME_TRUST_KEYS = new Set([
55
+ 'approverKeys',
56
+ 'componentVerifiers',
57
+ 'expectedAction',
58
+ 'expectedActionDigest',
59
+ 'expectedAgreementId',
60
+ 'expectedContext',
61
+ 'expectedProgramDigest',
62
+ 'keys',
63
+ 'keysByType',
64
+ 'mappings',
65
+ 'policies',
66
+ 'policiesByType',
67
+ 'resolveCaid',
68
+ 'trustedCertificateKeys',
69
+ 'trustedConfiguration',
70
+ 'trustedKeys',
71
+ 'trustRoots',
72
+ 'verificationOptions',
73
+ 'verifierFunctions',
74
+ 'verifiers',
75
+ ]);
76
+ const VERIFY_PACKAGE = '@emilia-protocol/verify';
77
+ const AEC_PACKAGE = '@emilia-protocol/verify/evidence-chain';
78
+ const LOCAL_VERIFY_PACKAGE = '../../verify/index.js';
79
+ const LOCAL_AEC_PACKAGE = '../../verify/evidence-chain.js';
80
+ const RECEIPT_PROGRAM_MODULE = '../receipt-program.js';
81
+ const ACTION_ESCROW_EVIDENCE_MODULE = '../action-escrow-evidence.js';
82
+
83
+ async function verifyQuorumDefault(quorum: any, options: any) {
84
+ try {
85
+ const verifier = await import(VERIFY_PACKAGE);
86
+ return verifier.verifyQuorum(quorum, options);
87
+ } catch (error) {
88
+ if ((error as NodeJS.ErrnoException)?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
89
+ const verifier = await import(LOCAL_VERIFY_PACKAGE);
90
+ return verifier.verifyQuorum(quorum, options);
91
+ }
92
+ }
93
+
94
+ async function verifyAuthorizationChainDefault(chain: any, options: any) {
95
+ try {
96
+ const verifier = await import(AEC_PACKAGE);
97
+ return verifier.verifyAuthorizationChain(chain, options);
98
+ } catch (error) {
99
+ if ((error as NodeJS.ErrnoException)?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
100
+ const verifier = await import(LOCAL_AEC_PACKAGE);
101
+ return verifier.verifyAuthorizationChain(chain, options);
102
+ }
103
+ }
104
+
105
+ async function verifyReceiptProgramCertificateDefault(certificate: any, options: any) {
106
+ const verifier = await import(RECEIPT_PROGRAM_MODULE);
107
+ return verifier.verifyReceiptProgramCertificate(certificate, options);
108
+ }
109
+
110
+ async function verifyActionEscrowEvidencePackageDefault(pkg: any, options: any) {
111
+ const verifier = await import(ACTION_ESCROW_EVIDENCE_MODULE);
112
+ return verifier.verifyActionEscrowEvidencePackage(pkg, options);
113
+ }
114
+
115
+ function refusal(reason: string) {
116
+ return Object.freeze({ valid: false, reason });
117
+ }
118
+
119
+ function terminalRefusal(reason: string) {
120
+ return Object.freeze({
121
+ valid: false,
122
+ reason,
123
+ outcome: null,
124
+ evidence_digest: null,
125
+ });
126
+ }
127
+
128
+ function isRecord(value: unknown): value is RecordLike {
129
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
130
+ const prototype = Object.getPrototypeOf(value);
131
+ return prototype === Object.prototype || prototype === null;
132
+ }
133
+
134
+ /** Only enumerable data properties are accepted from a presenter. */
135
+ function isDataRecord(value: unknown): value is RecordLike {
136
+ if (!isRecord(value)) return false;
137
+ return Reflect.ownKeys(value).every((key) => {
138
+ if (typeof key !== 'string') return false;
139
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
140
+ return descriptor?.enumerable === true && Object.hasOwn(descriptor, 'value');
141
+ });
142
+ }
143
+
144
+ function exactKeys(value: unknown, allowed: Set<string>, required = allowed): value is RecordLike {
145
+ return isDataRecord(value)
146
+ && Object.keys(value).every((key) => allowed.has(key))
147
+ && [...required].every((key) => Object.hasOwn(value, key));
148
+ }
149
+
150
+ function canonicalCopy<T>(value: T): T {
151
+ return JSON.parse(canonicalize(value));
152
+ }
153
+
154
+ function canonicalDigest(value: unknown): string {
155
+ return `sha256:${crypto.createHash('sha256')
156
+ .update(Buffer.from(canonicalize(value), 'utf8')).digest('hex')}`;
157
+ }
158
+
159
+ function deepFreeze<T>(value: T): T {
160
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
161
+ Object.freeze(value);
162
+ for (const child of Object.values(value as RecordLike)) deepFreeze(child);
163
+ return value;
164
+ }
165
+
166
+ /**
167
+ * Snapshot constructor-owned configuration while preserving injected verifier
168
+ * function identity. Host objects such as KeyObject are immutable references;
169
+ * canonical data and byte arrays are copied.
170
+ */
171
+ function snapshotPinned<T>(value: T, seen = new WeakMap<object, any>()): T {
172
+ if (value === null || typeof value !== 'object') return value;
173
+ if (value instanceof Uint8Array) return Uint8Array.from(value) as T;
174
+ if (value instanceof ArrayBuffer) return value.slice(0) as T;
175
+ if (value instanceof Date) return new Date(value.getTime()) as T;
176
+ if (value instanceof crypto.KeyObject) return value;
177
+ if (seen.has(value)) return seen.get(value);
178
+ if (Array.isArray(value)) {
179
+ const result: any[] = [];
180
+ seen.set(value, result);
181
+ for (const entry of value) result.push(snapshotPinned(entry, seen));
182
+ return Object.freeze(result) as T;
183
+ }
184
+ if (!isDataRecord(value)) {
185
+ throw new TypeError('pinned adapter configuration must use data properties');
186
+ }
187
+ const result: RecordLike = {};
188
+ seen.set(value, result);
189
+ for (const [key, entry] of Object.entries(value)) result[key] = snapshotPinned(entry, seen);
190
+ return Object.freeze(result) as T;
191
+ }
192
+
193
+ function canonicalInstant(value: unknown): string | null {
194
+ if (typeof value !== 'string'
195
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/.test(value)) {
196
+ return null;
197
+ }
198
+ const parsed = Date.parse(value);
199
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
200
+ }
201
+
202
+ function normalizedStrings(value: unknown): string[] | null {
203
+ if (!Array.isArray(value) || value.length === 0 || value.length > 256) return null;
204
+ const normalized: string[] = [];
205
+ for (const entry of value) {
206
+ if (typeof entry !== 'string' || entry.length === 0 || entry.length > 512
207
+ || /[\u0000-\u001f\u007f]/.test(entry)) return null;
208
+ normalized.push(entry);
209
+ }
210
+ return [...new Set(normalized)].sort();
211
+ }
212
+
213
+ function normalizedFingerprints(value: unknown): string[] | null {
214
+ if (!Array.isArray(value) || value.length === 0 || value.length > 256
215
+ || value.some((entry) => typeof entry !== 'string' || !DIGEST.test(entry))) return null;
216
+ return [...new Set(value as string[])].sort();
217
+ }
218
+
219
+ function containsRuntimeTrustConfiguration(value: unknown): boolean {
220
+ const stack = [value];
221
+ const seen = new WeakSet<object>();
222
+ while (stack.length) {
223
+ const current = stack.pop();
224
+ if (!current || typeof current !== 'object') continue;
225
+ if (seen.has(current as object)) return true;
226
+ seen.add(current as object);
227
+ if (Array.isArray(current)) {
228
+ stack.push(...current);
229
+ continue;
230
+ }
231
+ if (!isDataRecord(current)) return true;
232
+ for (const [key, entry] of Object.entries(current)) {
233
+ if (FORBIDDEN_RUNTIME_TRUST_KEYS.has(key)) return true;
234
+ if (entry && typeof entry === 'object') stack.push(entry);
235
+ }
236
+ }
237
+ return false;
238
+ }
239
+
240
+ function normalizeVerifiedClaims(result: RecordLike) {
241
+ const subjects = normalizedStrings(result.subjects);
242
+ const keyFingerprints = normalizedFingerprints(result.key_fingerprints);
243
+ const issuedAt = canonicalInstant(result.issued_at);
244
+ const expiresAt = canonicalInstant(result.expires_at);
245
+ const revocationCheckedAt = result.revocation_checked_at === null
246
+ || result.revocation_checked_at === undefined
247
+ ? null : canonicalInstant(result.revocation_checked_at);
248
+ if (!subjects || !keyFingerprints) return refusal('evidence_principal_set_invalid');
249
+ if (!issuedAt || !expiresAt || Date.parse(expiresAt) <= Date.parse(issuedAt)) {
250
+ return refusal('evidence_time_invalid');
251
+ }
252
+ if (result.revocation_checked_at !== null && result.revocation_checked_at !== undefined
253
+ && revocationCheckedAt === null) return refusal('evidence_revocation_time_invalid');
254
+ return Object.freeze({
255
+ valid: true,
256
+ reason: null,
257
+ binding_digest: result.binding_digest,
258
+ policy_digest: result.policy_digest,
259
+ subjects,
260
+ key_fingerprints: keyFingerprints,
261
+ issued_at: issuedAt,
262
+ expires_at: expiresAt,
263
+ revocation_checked_at: revocationCheckedAt,
264
+ });
265
+ }
266
+
267
+ function exactTrustBinding(value: unknown, requirement: RecordLike, program: RecordLike) {
268
+ if (!exactKeys(value, TRUST_BINDING_KEYS)
269
+ || typeof value.instance_id !== 'string' || value.instance_id.length === 0
270
+ || typeof value.stage_id !== 'string' || value.stage_id.length === 0
271
+ || value.requirement_id !== requirement.requirement_id
272
+ || value.policy_digest !== requirement.policy_digest
273
+ || value.program_version !== program.version
274
+ || value.root_caid !== program.root_caid
275
+ || value.action_digest !== program.action_digest
276
+ || !DIGEST.test(value.program_digest)
277
+ || !Array.isArray(value.predecessor_receipt_digests)
278
+ || value.predecessor_receipt_digests.some((entry: unknown) =>
279
+ typeof entry !== 'string' || !DIGEST.test(entry))
280
+ || new Set(value.predecessor_receipt_digests).size
281
+ !== value.predecessor_receipt_digests.length
282
+ || value.predecessor_receipt_digests.some(
283
+ (entry: string, index: number) => index > 0
284
+ && entry < value.predecessor_receipt_digests[index - 1],
285
+ )) return null;
286
+ let digest: string;
287
+ try {
288
+ if (value.program_digest !== canonicalDigest(program)) return null;
289
+ digest = canonicalDigest(value);
290
+ } catch {
291
+ return null;
292
+ }
293
+ return { binding: deepFreeze(canonicalCopy(value)), digest };
294
+ }
295
+
296
+ function resolvePinnedInstant(value: unknown): unknown {
297
+ return typeof value === 'function' ? value() : value;
298
+ }
299
+
300
+ export interface PinnedEvidenceAdapterOptions {
301
+ policyDigest: string;
302
+ verify: Verifier;
303
+ trustedConfiguration?: RecordLike;
304
+ metadata?: (result: RecordLike, context: RecordLike) => any;
305
+ }
306
+
307
+ /**
308
+ * Wrap any evidence verifier in the Trust Program stage-verifier contract.
309
+ * Runtime artifacts have a closed three-field envelope, so trust configuration
310
+ * cannot ride beside the evidence and influence verification.
311
+ */
312
+ export function createPinnedEvidenceAdapter({
313
+ policyDigest,
314
+ verify,
315
+ trustedConfiguration = {},
316
+ metadata,
317
+ }: PinnedEvidenceAdapterOptions) {
318
+ if (!DIGEST.test(policyDigest) || typeof verify !== 'function'
319
+ || (metadata !== undefined && typeof metadata !== 'function')) {
320
+ throw new TypeError('pinned evidence adapter configuration invalid');
321
+ }
322
+ const pinnedConfiguration = snapshotPinned(trustedConfiguration);
323
+ return async function pinnedEvidenceAdapter({ artifact, requirement, program }: RecordLike) {
324
+ if (!exactKeys(artifact, ARTIFACT_KEYS)) return refusal('artifact_schema_invalid');
325
+ if (!isDataRecord(requirement) || !isDataRecord(program)) return refusal('verification_context_invalid');
326
+ if (requirement.policy_digest !== policyDigest) return refusal('requirement_policy_mismatch');
327
+ const expected = exactTrustBinding(artifact.binding, requirement, program);
328
+ if (!expected) return refusal('trust_binding_invalid');
329
+ let evidence: unknown;
330
+ try {
331
+ evidence = deepFreeze(canonicalCopy(artifact.evidence));
332
+ } catch {
333
+ return refusal('evidence_not_canonical');
334
+ }
335
+ if (containsRuntimeTrustConfiguration(evidence)) {
336
+ return refusal('artifact_trust_configuration_forbidden');
337
+ }
338
+ const context = Object.freeze({
339
+ trustedConfiguration: pinnedConfiguration,
340
+ expectedBinding: expected.binding,
341
+ expectedBindingDigest: expected.digest,
342
+ expectedPolicyDigest: policyDigest,
343
+ requirement: deepFreeze(canonicalCopy(requirement)),
344
+ program: deepFreeze(canonicalCopy(program)),
345
+ });
346
+ let verified: any;
347
+ try {
348
+ verified = await verify(evidence, context);
349
+ } catch {
350
+ return refusal('evidence_verification_failed');
351
+ }
352
+ if (!isDataRecord(verified) || verified.valid !== true) {
353
+ return refusal(typeof verified?.reason === 'string'
354
+ ? verified.reason : 'evidence_verification_failed');
355
+ }
356
+ let claims = verified;
357
+ if (metadata) {
358
+ try {
359
+ const resolved = await metadata(verified, Object.freeze({ ...context, evidence }));
360
+ if (!isDataRecord(resolved)) return refusal('evidence_metadata_invalid');
361
+ claims = { ...verified, ...resolved };
362
+ } catch {
363
+ return refusal('evidence_metadata_invalid');
364
+ }
365
+ }
366
+ if (claims.binding_digest !== expected.digest) return refusal('evidence_binding_mismatch');
367
+ if (claims.policy_digest !== policyDigest) return refusal('evidence_policy_mismatch');
368
+ return normalizeVerifiedClaims(claims);
369
+ };
370
+ }
371
+
372
+ /** Canonical SHA-256 fingerprint of an SPKI public key. */
373
+ export function canonicalKeyFingerprint(value: unknown): string | null {
374
+ try {
375
+ let key: crypto.KeyObject;
376
+ if (value instanceof crypto.KeyObject) {
377
+ key = value.type === 'public' ? value : crypto.createPublicKey(value as any);
378
+ } else {
379
+ if (typeof value !== 'string' || !BASE64URL.test(value)) return null;
380
+ const bytes = Buffer.from(value, 'base64url');
381
+ if (bytes.toString('base64url') !== value) return null;
382
+ key = crypto.createPublicKey({ key: bytes, type: 'spki', format: 'der' });
383
+ }
384
+ const spki = key.export({ type: 'spki', format: 'der' });
385
+ return `sha256:${crypto.createHash('sha256').update(spki).digest('hex')}`;
386
+ } catch {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ function publicKeyValue(value: unknown): string | null {
392
+ try {
393
+ if (typeof value === 'string') return canonicalKeyFingerprint(value) ? value : null;
394
+ if (!(value instanceof crypto.KeyObject)) return null;
395
+ const key = value.type === 'public' ? value : crypto.createPublicKey(value as any);
396
+ return key.export({ type: 'spki', format: 'der' }).toString('base64url');
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+
402
+ function validQuorumPolicy(policy: unknown): policy is RecordLike {
403
+ if (!isDataRecord(policy)
404
+ || !['threshold', 'ordered'].includes(policy.mode)
405
+ || !Number.isSafeInteger(policy.required) || policy.required < 1
406
+ || !Array.isArray(policy.approvers) || policy.approvers.length === 0
407
+ || policy.required > policy.approvers.length) return false;
408
+ const slots = new Set<string>();
409
+ for (const slot of policy.approvers) {
410
+ if (!isDataRecord(slot) || typeof slot.role !== 'string' || slot.role.length === 0
411
+ || typeof slot.approver !== 'string' || slot.approver.length === 0) return false;
412
+ const key = `${slot.role}\u0000${slot.approver}`;
413
+ if (slots.has(key)) return false;
414
+ slots.add(key);
415
+ }
416
+ return true;
417
+ }
418
+
419
+ export interface QuorumTrustProgramAdapterOptions {
420
+ policy: RecordLike;
421
+ policyDigest: string;
422
+ approverKeys: Record<string, unknown>;
423
+ verificationOptions?: RecordLike;
424
+ revocationCheckedAt: string | (() => string);
425
+ verifyQuorum?: (quorum: any, options?: any) => any;
426
+ }
427
+
428
+ /** Compose Trust Program with the repository's Quorum verifier. */
429
+ export function createQuorumTrustProgramAdapter(options: QuorumTrustProgramAdapterOptions) {
430
+ const policy = snapshotPinned(options.policy);
431
+ const approverKeys = snapshotPinned(options.approverKeys);
432
+ const verificationOptions = snapshotPinned(options.verificationOptions ?? {});
433
+ const verifyQuorum = options.verifyQuorum ?? verifyQuorumDefault;
434
+ const policyValid = validQuorumPolicy(policy);
435
+ const pinnedKeysValid = policyValid && policy.approvers.every(
436
+ (slot: RecordLike) => publicKeyValue(approverKeys[slot.approver]) !== null,
437
+ );
438
+ return createPinnedEvidenceAdapter({
439
+ policyDigest: options.policyDigest,
440
+ verify: async (evidence, context) => {
441
+ if (!policyValid || !pinnedKeysValid) return refusal('quorum_policy_invalid');
442
+ if (!exactKeys(evidence, QUORUM_EVIDENCE_KEYS)
443
+ || !Array.isArray(evidence.members) || evidence.members.length === 0
444
+ || evidence.members.length > policy.approvers.length) {
445
+ return refusal('quorum_evidence_schema_invalid');
446
+ }
447
+ const members: RecordLike[] = [];
448
+ for (const member of evidence.members) {
449
+ if (!exactKeys(member, QUORUM_MEMBER_KEYS)
450
+ || typeof member.role !== 'string' || !isDataRecord(member.signoff)
451
+ || !isDataRecord(member.signoff.context)
452
+ || typeof member.signoff.context.approver !== 'string') {
453
+ return refusal('quorum_evidence_schema_invalid');
454
+ }
455
+ const approver = member.signoff.context.approver;
456
+ const admitted = policy.approvers.some((slot: RecordLike) =>
457
+ slot.role === member.role && slot.approver === approver);
458
+ const publicKey = publicKeyValue(approverKeys[approver]);
459
+ if (!admitted || !publicKey) return refusal('quorum_member_not_pinned');
460
+ members.push({
461
+ role: member.role,
462
+ approver_public_key: publicKey,
463
+ signoff: member.signoff,
464
+ });
465
+ }
466
+ let result: any;
467
+ try {
468
+ result = await verifyQuorum({
469
+ '@type': 'ep.quorum',
470
+ action_hash: context.expectedBindingDigest,
471
+ policy,
472
+ members,
473
+ }, verificationOptions);
474
+ } catch {
475
+ return refusal('quorum_verification_failed');
476
+ }
477
+ if (!isDataRecord(result) || result.valid !== true) return refusal('quorum_verification_failed');
478
+ const issued = members.map((member) => canonicalInstant(member.signoff.context.issued_at));
479
+ const expires = members.map((member) => canonicalInstant(member.signoff.context.expires_at));
480
+ if (issued.some((entry) => entry === null) || expires.some((entry) => entry === null)) {
481
+ return refusal('evidence_time_invalid');
482
+ }
483
+ const subjects = members.map((member) => member.signoff.context.approver);
484
+ const fingerprints = members.map((member) => canonicalKeyFingerprint(member.approver_public_key));
485
+ if (fingerprints.some((entry) => entry === null)) return refusal('evidence_principal_set_invalid');
486
+ return {
487
+ valid: true,
488
+ binding_digest: context.expectedBindingDigest,
489
+ policy_digest: options.policyDigest,
490
+ subjects,
491
+ key_fingerprints: fingerprints,
492
+ issued_at: issued.sort()[0],
493
+ expires_at: expires.sort()[0],
494
+ revocation_checked_at: resolvePinnedInstant(options.revocationCheckedAt),
495
+ };
496
+ },
497
+ });
498
+ }
499
+
500
+ export interface AecTrustProgramAdapterOptions {
501
+ policyDigest: string;
502
+ requirement: string;
503
+ keysByType: RecordLike;
504
+ policiesByType: RecordLike;
505
+ verifiers?: Record<string, (...args: any[]) => any>;
506
+ verificationTime: string | (() => string);
507
+ verifyAuthorizationChain?: (chain: any, options?: any) => any;
508
+ metadata?: (result: RecordLike, context: RecordLike) => any;
509
+ }
510
+
511
+ /** Compose Trust Program with AEC under RP-owned policy, action, and trust roots. */
512
+ export function createAecTrustProgramAdapter(options: AecTrustProgramAdapterOptions) {
513
+ if (typeof options.requirement !== 'string' || !options.requirement.trim()) {
514
+ throw new TypeError('AEC relying-party requirement required');
515
+ }
516
+ const keysByType = snapshotPinned(options.keysByType);
517
+ const policiesByType = snapshotPinned(options.policiesByType);
518
+ const verifiers = snapshotPinned(options.verifiers ?? {});
519
+ const verifyAuthorizationChain = options.verifyAuthorizationChain
520
+ ?? verifyAuthorizationChainDefault;
521
+ return createPinnedEvidenceAdapter({
522
+ policyDigest: options.policyDigest,
523
+ metadata: options.metadata,
524
+ verify: async (chain, context) => {
525
+ let result: any;
526
+ try {
527
+ result = await verifyAuthorizationChain(chain, {
528
+ keysByType,
529
+ policiesByType,
530
+ verifiers,
531
+ requirement: options.requirement,
532
+ expectedAction: context.expectedBinding,
533
+ expectedActionDigest: context.expectedBindingDigest,
534
+ verificationTime: resolvePinnedInstant(options.verificationTime),
535
+ });
536
+ } catch {
537
+ return refusal('aec_verification_failed');
538
+ }
539
+ const actionDigest = typeof result?.action_digest === 'string'
540
+ ? result.action_digest.replace(/^sha256:/, '') : null;
541
+ if (!isDataRecord(result) || result.satisfied !== true
542
+ || result.expected_action_bound !== true
543
+ || actionDigest !== context.expectedBindingDigest.slice('sha256:'.length)) {
544
+ return refusal('aec_verification_failed');
545
+ }
546
+ return {
547
+ ...result,
548
+ valid: true,
549
+ binding_digest: context.expectedBindingDigest,
550
+ policy_digest: options.policyDigest,
551
+ };
552
+ },
553
+ });
554
+ }
555
+
556
+ function receiptProgramOwnership(binding: unknown): binding is RecordLike {
557
+ return isDataRecord(binding)
558
+ && binding.consequence_mode === 'receipt-program'
559
+ && typeof binding.capability_template_digest === 'string'
560
+ && DIGEST.test(binding.capability_template_digest)
561
+ && binding.escrow_profile_digest === null;
562
+ }
563
+
564
+ function actionEscrowOwnership(binding: unknown, profileDigest: string): binding is RecordLike {
565
+ return isDataRecord(binding)
566
+ && binding.consequence_mode === 'action-escrow'
567
+ && binding.capability_template_digest === null
568
+ && binding.escrow_profile_digest === profileDigest
569
+ && DIGEST.test(binding.escrow_profile_digest);
570
+ }
571
+
572
+ /** Explicitly detect and refuse Action Escrow nested under Receipt Program. */
573
+ export function containsActionEscrowConsequence(value: unknown): boolean {
574
+ const stack = [value];
575
+ const seen = new WeakSet<object>();
576
+ while (stack.length) {
577
+ const current = stack.pop();
578
+ if (!current || typeof current !== 'object') continue;
579
+ if (seen.has(current as object)) return true;
580
+ seen.add(current as object);
581
+ if (Array.isArray(current)) {
582
+ stack.push(...current);
583
+ continue;
584
+ }
585
+ if (!isDataRecord(current)) return true;
586
+ for (const [key, entry] of Object.entries(current)) {
587
+ if (key.startsWith('action_escrow')
588
+ || (key === 'consequence_mode' && entry === 'action-escrow')
589
+ || (typeof entry === 'string'
590
+ && (entry.startsWith('EP-ACTION-ESCROW-')
591
+ || entry.toLowerCase().includes('action-escrow')))) return true;
592
+ if (entry && typeof entry === 'object') stack.push(entry);
593
+ }
594
+ }
595
+ return false;
596
+ }
597
+
598
+ export interface ReceiptProgramTerminalOptions {
599
+ programId: string;
600
+ programDigest?: string;
601
+ trustedCertificateKeys: Record<string, string>;
602
+ expectedContext: RecordLike;
603
+ resolveCaid: (action: any) => any;
604
+ verifyReceiptProgramCertificate?: (certificate: any, options?: any) => any;
605
+ }
606
+
607
+ export type TerminalVerifier = ((input: RecordLike) => Promise<RecordLike>) & {
608
+ readonly options: RecordLike;
609
+ };
610
+
611
+ function withOptions<T extends (...args: any[]) => any>(fn: T, options: RecordLike): T & {
612
+ readonly options: RecordLike;
613
+ } {
614
+ Object.defineProperty(fn, 'options', {
615
+ configurable: false,
616
+ enumerable: false,
617
+ writable: false,
618
+ value: options,
619
+ });
620
+ return Object.freeze(fn) as T & { readonly options: RecordLike };
621
+ }
622
+
623
+ /** Verify and normalize one Receipt Program terminal certificate. */
624
+ export function createReceiptProgramTerminalOutcomeVerifier(
625
+ options: ReceiptProgramTerminalOptions,
626
+ ): TerminalVerifier {
627
+ if (typeof options.programId !== 'string' || options.programId.length === 0
628
+ || (options.programDigest !== undefined && !DIGEST.test(options.programDigest))
629
+ || typeof options.resolveCaid !== 'function') {
630
+ throw new TypeError('Receipt Program adapter configuration invalid');
631
+ }
632
+ const pinned = snapshotPinned({
633
+ programId: options.programId,
634
+ programDigest: options.programDigest,
635
+ trustedCertificateKeys: options.trustedCertificateKeys,
636
+ expectedContext: options.expectedContext,
637
+ resolveCaid: options.resolveCaid,
638
+ });
639
+ const verifyCertificate = options.verifyReceiptProgramCertificate
640
+ ?? verifyReceiptProgramCertificateDefault;
641
+ const verifier = async ({ evidence, authorizationBinding }: RecordLike) => {
642
+ if (!receiptProgramOwnership(authorizationBinding)) {
643
+ return terminalRefusal('consequence_ownership_invalid');
644
+ }
645
+ if (!exactKeys(evidence, RECEIPT_EVIDENCE_KEYS)) {
646
+ return terminalRefusal('receipt_program_evidence_schema_invalid');
647
+ }
648
+ let certificate: RecordLike;
649
+ try {
650
+ certificate = deepFreeze(canonicalCopy(evidence.certificate));
651
+ } catch {
652
+ return terminalRefusal('receipt_program_evidence_invalid');
653
+ }
654
+ if (containsActionEscrowConsequence(certificate?.program)) {
655
+ return terminalRefusal('action_escrow_receipt_program_nesting_refused');
656
+ }
657
+ let result: any;
658
+ try {
659
+ result = await verifyCertificate(certificate, {
660
+ trustedCertificateKeys: pinned.trustedCertificateKeys,
661
+ expectedContext: pinned.expectedContext,
662
+ resolveCaid: pinned.resolveCaid,
663
+ });
664
+ } catch {
665
+ return terminalRefusal('receipt_program_verification_failed');
666
+ }
667
+ const certificateProgram = certificate.program;
668
+ if (!isDataRecord(result) || result.ok !== true
669
+ || !isDataRecord(certificateProgram)
670
+ || !['executed', 'refused', 'indeterminate'].includes(result.outcome)
671
+ || certificate.outcome !== result.outcome
672
+ || certificateProgram.program_id !== pinned.programId
673
+ || certificateProgram.operation_id !== authorizationBinding.operation_id
674
+ || certificateProgram.action_digest !== authorizationBinding.action_digest
675
+ || certificateProgram.caid !== authorizationBinding.root_caid
676
+ || (pinned.programDigest !== undefined && result.program_digest !== pinned.programDigest)) {
677
+ return terminalRefusal('receipt_program_binding_mismatch');
678
+ }
679
+ return Object.freeze({
680
+ valid: true,
681
+ reason: null,
682
+ outcome: result.outcome as TerminalOutcome,
683
+ evidence_digest: canonicalDigest(certificate),
684
+ });
685
+ };
686
+ return withOptions(verifier, pinned);
687
+ }
688
+
689
+ /** Trust Program executionOutcomeVerifier wrapper for Receipt Program. */
690
+ export function createReceiptProgramExecutionOutcomeVerifier(
691
+ options: ReceiptProgramTerminalOptions,
692
+ ) {
693
+ const terminal = createReceiptProgramTerminalOutcomeVerifier(options);
694
+ return async function receiptProgramExecutionOutcomeVerifier(input: RecordLike) {
695
+ const result = await terminal(input);
696
+ return result.valid === true
697
+ && result.outcome === input.outcome
698
+ && result.evidence_digest === input.evidenceDigest;
699
+ };
700
+ }
701
+
702
+ export interface ActionEscrowTerminalOptions {
703
+ agreementId: string;
704
+ operationId: string;
705
+ releaseActionDigest: string;
706
+ profileDigest: string;
707
+ componentVerifiers: Record<string, (...args: any[]) => any>;
708
+ now?: Date | number | string | (() => Date | number | string);
709
+ maxDocumentBytes?: number;
710
+ maxProjectRecordBytes?: number;
711
+ verifyActionEscrowEvidencePackage?: (pkg: any, options?: any) => any;
712
+ }
713
+
714
+ function actionEscrowOutcome(stage: unknown): TerminalOutcome | null {
715
+ if (stage === 'released' || stage === 'completed') return 'executed';
716
+ if (stage === 'cancelled') return 'refused';
717
+ if (stage === 'release_indeterminate') return 'indeterminate';
718
+ return null;
719
+ }
720
+
721
+ /** Verify and normalize one Action Escrow authenticated terminal package. */
722
+ export function createActionEscrowTerminalOutcomeVerifier(
723
+ options: ActionEscrowTerminalOptions,
724
+ ): TerminalVerifier {
725
+ if (typeof options.agreementId !== 'string' || options.agreementId.length === 0
726
+ || typeof options.operationId !== 'string' || options.operationId.length === 0
727
+ || options.operationId.length > 512 || /[\u0000-\u001f\u007f]/.test(options.operationId)
728
+ || !DIGEST.test(options.releaseActionDigest) || !DIGEST.test(options.profileDigest)
729
+ || !isDataRecord(options.componentVerifiers)) {
730
+ throw new TypeError('Action Escrow adapter configuration invalid');
731
+ }
732
+ const componentVerifiers = snapshotPinned(options.componentVerifiers);
733
+ const pinned = snapshotPinned({
734
+ agreementId: options.agreementId,
735
+ operationId: options.operationId,
736
+ releaseActionDigest: options.releaseActionDigest,
737
+ profileDigest: options.profileDigest,
738
+ componentVerifiers,
739
+ now: options.now,
740
+ maxDocumentBytes: options.maxDocumentBytes,
741
+ maxProjectRecordBytes: options.maxProjectRecordBytes,
742
+ });
743
+ const verifyPackage = options.verifyActionEscrowEvidencePackage
744
+ ?? verifyActionEscrowEvidencePackageDefault;
745
+ const verifier = async ({ evidence, authorizationBinding }: RecordLike) => {
746
+ if (!actionEscrowOwnership(authorizationBinding, pinned.profileDigest)) {
747
+ return terminalRefusal('consequence_ownership_invalid');
748
+ }
749
+ if (authorizationBinding.operation_id !== pinned.operationId) {
750
+ return terminalRefusal('action_escrow_binding_mismatch');
751
+ }
752
+ if (!exactKeys(
753
+ evidence,
754
+ ACTION_ESCROW_EVIDENCE_KEYS,
755
+ new Set(['package', 'document_bytes']),
756
+ )) return terminalRefusal('action_escrow_evidence_schema_invalid');
757
+ let pkg: RecordLike;
758
+ try {
759
+ pkg = deepFreeze(canonicalCopy(evidence.package));
760
+ } catch {
761
+ return terminalRefusal('action_escrow_evidence_invalid');
762
+ }
763
+ const outcome = actionEscrowOutcome(pkg.stage);
764
+ if (!outcome) return terminalRefusal('action_escrow_state_not_terminal');
765
+ if (pkg.agreement_id !== pinned.agreementId || pkg.package_digest === undefined
766
+ || !DIGEST.test(pkg.package_digest)) {
767
+ return terminalRefusal('action_escrow_binding_mismatch');
768
+ }
769
+ if (outcome !== 'refused'
770
+ && (!isDataRecord(pkg.release)
771
+ || pkg.release.operation_idempotency_key !== pinned.operationId)) {
772
+ return terminalRefusal('action_escrow_binding_mismatch');
773
+ }
774
+ if (!(evidence.document_bytes instanceof Uint8Array)
775
+ || (evidence.project_record_bytes !== undefined
776
+ && !(evidence.project_record_bytes instanceof Uint8Array))) {
777
+ return terminalRefusal('action_escrow_evidence_invalid');
778
+ }
779
+ const verificationOptions: RecordLike = {
780
+ documentBytes: Uint8Array.from(evidence.document_bytes),
781
+ expectedAgreementId: pinned.agreementId,
782
+ };
783
+ if (evidence.project_record_bytes !== undefined) {
784
+ verificationOptions.projectRecordBytes = Uint8Array.from(evidence.project_record_bytes);
785
+ }
786
+ for (const name of ACTION_ESCROW_COMPONENT_VERIFIERS) {
787
+ const candidate = componentVerifiers[name];
788
+ if (candidate !== undefined) {
789
+ if (typeof candidate !== 'function') return terminalRefusal('action_escrow_verifier_invalid');
790
+ verificationOptions[name] = candidate;
791
+ }
792
+ }
793
+ if (pinned.now !== undefined) verificationOptions.now = resolvePinnedInstant(pinned.now);
794
+ if (pinned.maxDocumentBytes !== undefined) {
795
+ verificationOptions.maxDocumentBytes = pinned.maxDocumentBytes;
796
+ }
797
+ if (pinned.maxProjectRecordBytes !== undefined) {
798
+ verificationOptions.maxProjectRecordBytes = pinned.maxProjectRecordBytes;
799
+ }
800
+ let result: any;
801
+ try {
802
+ result = await verifyPackage(pkg, verificationOptions);
803
+ } catch {
804
+ return terminalRefusal('action_escrow_verification_failed');
805
+ }
806
+ if (!isDataRecord(result) || result.valid !== true
807
+ || result.package_digest !== pkg.package_digest
808
+ || result.agreement_id !== pinned.agreementId
809
+ || result.action_digest !== pinned.releaseActionDigest
810
+ || result.profile_digest !== pinned.profileDigest
811
+ || authorizationBinding.action_digest !== pinned.releaseActionDigest) {
812
+ return terminalRefusal('action_escrow_binding_mismatch');
813
+ }
814
+ return Object.freeze({
815
+ valid: true,
816
+ reason: null,
817
+ outcome,
818
+ evidence_digest: result.package_digest,
819
+ });
820
+ };
821
+ return withOptions(verifier, pinned);
822
+ }
823
+
824
+ /** Trust Program executionOutcomeVerifier wrapper for Action Escrow. */
825
+ export function createActionEscrowExecutionOutcomeVerifier(
826
+ options: ActionEscrowTerminalOptions,
827
+ ) {
828
+ const terminal = createActionEscrowTerminalOutcomeVerifier(options);
829
+ return async function actionEscrowExecutionOutcomeVerifier(input: RecordLike) {
830
+ const result = await terminal(input);
831
+ return result.valid === true
832
+ && result.outcome === input.outcome
833
+ && result.evidence_digest === input.evidenceDigest;
834
+ };
835
+ }
836
+
837
+ export default {
838
+ canonicalKeyFingerprint,
839
+ containsActionEscrowConsequence,
840
+ createPinnedEvidenceAdapter,
841
+ createQuorumTrustProgramAdapter,
842
+ createAecTrustProgramAdapter,
843
+ createReceiptProgramTerminalOutcomeVerifier,
844
+ createReceiptProgramExecutionOutcomeVerifier,
845
+ createActionEscrowTerminalOutcomeVerifier,
846
+ createActionEscrowExecutionOutcomeVerifier,
847
+ };