@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,1351 @@
1
+ // @ts-nocheck
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * EMILIA Gate Trust Program Profile v1.
5
+ *
6
+ * A relying-party-controlled, fail-closed authorization DAG for consequential
7
+ * actions. This module composes evidence verifiers; it does not redefine the
8
+ * Handshake, Quorum, AEC, capability, or Action Escrow wire formats.
9
+ */
10
+ import crypto from 'node:crypto';
11
+ import { canonicalize, hashCanonical } from './execution-binding.js';
12
+ export const TRUST_PROGRAM_VERSION = 'EP-GATE-TRUST-PROGRAM-PROFILE-v1';
13
+ export const TRUST_STAGE_RECEIPT_VERSION = 'EP-GATE-TRUST-STAGE-RECEIPT-v1';
14
+ const STAGE_RECEIPT_DOMAIN = `${TRUST_STAGE_RECEIPT_VERSION}\0`;
15
+ const DIGEST = /^sha256:[0-9a-f]{64}$/;
16
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
17
+ const CAID = /^caid:1:[a-z][a-z0-9.-]*\.[1-9][0-9]*:jcs-sha256:[A-Za-z0-9_-]{43}$/;
18
+ const MAX_STAGES = 64;
19
+ const MAX_REQUIREMENTS_PER_STAGE = 64;
20
+ const MAX_TOTAL_REQUIREMENTS = 1024;
21
+ const PROGRAM_KEYS = new Set([
22
+ '@version', 'program_id', 'version', 'root_caid', 'action_digest',
23
+ 'valid_from', 'expires_at', 'stages', 'execution',
24
+ ]);
25
+ const STAGE_KEYS = new Set(['stage_id', 'depends_on', 'rule', 'requirements']);
26
+ const REQUIREMENT_KEYS = new Set([
27
+ 'requirement_id', 'evidence_type', 'verifier_profile', 'policy_digest',
28
+ 'max_age_sec', 'revocation_required',
29
+ ]);
30
+ const EXECUTION_KEYS = new Set([
31
+ 'depends_on', 'consequence_mode', 'capability_template_digest', 'escrow_profile_digest',
32
+ ]);
33
+ const STAGE_RECEIPT_KEYS = new Set([
34
+ 'version', 'issuer', 'payload', 'receipt_digest', 'signature',
35
+ ]);
36
+ const STAGE_RECEIPT_ISSUER_KEYS = new Set([
37
+ 'issuer', 'tenant', 'environment', 'audience', 'key_id',
38
+ ]);
39
+ const STAGE_RECEIPT_PAYLOAD_KEYS = new Set([
40
+ 'instance_id', 'program_id', 'program_version', 'program_digest', 'root_caid',
41
+ 'action_digest', 'stage_id', 'stage_policy_digest', 'predecessor_receipt_digests',
42
+ 'evidence_digests', 'subjects', 'key_fingerprints', 'satisfied_at',
43
+ ]);
44
+ const STAGE_RECEIPT_SIGNATURE_KEYS = new Set(['algorithm', 'value']);
45
+ const STATE_KEYS = new Set([
46
+ 'version', 'tenant_id', 'instance_id', 'program_id', 'program_version', 'program_digest',
47
+ 'root_caid', 'action_digest', 'status', 'revision', 'created_at', 'updated_at',
48
+ 'stages', 'used_evidence_ids', 'execution', 'invalidation_reason',
49
+ ]);
50
+ const STAGE_STATE_KEYS = new Set([
51
+ 'status', 'predecessor_receipt_digests', 'evidence', 'receipt',
52
+ ]);
53
+ const EVIDENCE_STATE_KEYS = new Set([
54
+ 'evidence_id', 'evidence_digest', 'policy_digest', 'binding_digest', 'subjects',
55
+ 'key_fingerprints', 'issued_at', 'expires_at', 'revocation_checked_at',
56
+ ]);
57
+ const EXECUTION_STATE_KEYS = new Set([
58
+ 'status', 'claim_token_digest', 'evidence_digest', 'outcome', 'operation_id',
59
+ 'claimed_at', 'authorization_binding', 'finalized_at', 'reconciled_at',
60
+ ]);
61
+ const AUTHORIZATION_BINDING_KEYS = new Set([
62
+ 'instance_id', 'operation_id', 'program_digest', 'root_caid', 'action_digest',
63
+ 'receipt_context_digest', 'terminal_stage_receipt_digests', 'consequence_mode',
64
+ 'capability_template_digest', 'escrow_profile_digest',
65
+ ]);
66
+ function fail(reason) {
67
+ return { ok: false, reason };
68
+ }
69
+ function isRecord(value) {
70
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
71
+ return false;
72
+ const prototype = Object.getPrototypeOf(value);
73
+ return prototype === Object.prototype || prototype === null;
74
+ }
75
+ function isDataRecord(value) {
76
+ return isRecord(value) && Reflect.ownKeys(value).every((key) => {
77
+ if (typeof key !== 'string')
78
+ return false;
79
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
80
+ return descriptor?.enumerable === true && Object.hasOwn(descriptor, 'value');
81
+ });
82
+ }
83
+ function exactKeys(value, allowed) {
84
+ return isDataRecord(value)
85
+ && Reflect.ownKeys(value).length === allowed.size
86
+ && Object.keys(value).every((key) => allowed.has(key));
87
+ }
88
+ function clone(value) {
89
+ return structuredClone(value);
90
+ }
91
+ function digest(value) {
92
+ return `sha256:${hashCanonical(value)}`;
93
+ }
94
+ function strictInstant(value) {
95
+ if (typeof value !== 'string'
96
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/.test(value))
97
+ return NaN;
98
+ const parsed = Date.parse(value);
99
+ return Number.isFinite(parsed) ? parsed : NaN;
100
+ }
101
+ function exactUniqueStrings(value, maximum) {
102
+ return Array.isArray(value)
103
+ && value.length <= maximum
104
+ && value.every((entry) => typeof entry === 'string' && ID.test(entry))
105
+ && new Set(value).size === value.length;
106
+ }
107
+ function boundedProjection(value, required) {
108
+ return Array.isArray(value)
109
+ && (!required || value.length > 0)
110
+ && value.length <= 256
111
+ && value.every((entry) => typeof entry === 'string'
112
+ && entry.length > 0 && entry.length <= 512
113
+ && !/[\u0000-\u001f\u007f]/.test(entry))
114
+ && new Set(value).size === value.length;
115
+ }
116
+ function sortedUniqueDigests(value, maximum) {
117
+ return Array.isArray(value)
118
+ && value.length <= maximum
119
+ && value.every((entry) => typeof entry === 'string' && DIGEST.test(entry))
120
+ && new Set(value).size === value.length
121
+ && value.every((entry, index) => index === 0 || value[index - 1] < entry);
122
+ }
123
+ function boundedContextString(value) {
124
+ return typeof value === 'string' && value.length > 0 && value.length <= 256
125
+ && !/[\u0000-\u001f\u007f]/.test(value);
126
+ }
127
+ function effectiveRequired(stage) {
128
+ if (stage.rule.mode === 'all')
129
+ return stage.requirements.length;
130
+ if (stage.rule.mode === 'any')
131
+ return 1;
132
+ return stage.rule.required;
133
+ }
134
+ function validationFailure(reason) {
135
+ return { valid: false, reason, digest: null };
136
+ }
137
+ /** Validate the closed, bounded DAG before any state is created. */
138
+ export function validateTrustProgram(program) {
139
+ try {
140
+ canonicalize(program);
141
+ }
142
+ catch {
143
+ return validationFailure('program_not_canonical');
144
+ }
145
+ if (!exactKeys(program, PROGRAM_KEYS) || program['@version'] !== TRUST_PROGRAM_VERSION) {
146
+ return validationFailure('program_version_unsupported');
147
+ }
148
+ if (typeof program.program_id !== 'string' || !ID.test(program.program_id)
149
+ || !Number.isSafeInteger(program.version) || program.version < 1
150
+ || typeof program.root_caid !== 'string' || !CAID.test(program.root_caid)
151
+ || typeof program.action_digest !== 'string' || !DIGEST.test(program.action_digest)) {
152
+ return validationFailure('program_binding_invalid');
153
+ }
154
+ const validFrom = strictInstant(program.valid_from);
155
+ const expiresAt = strictInstant(program.expires_at);
156
+ if (!Number.isFinite(validFrom) || !Number.isFinite(expiresAt) || expiresAt <= validFrom) {
157
+ return validationFailure('program_time_window_invalid');
158
+ }
159
+ if (!Array.isArray(program.stages) || program.stages.length === 0 || program.stages.length > MAX_STAGES) {
160
+ return validationFailure('program_stage_count_invalid');
161
+ }
162
+ if (!exactKeys(program.execution, EXECUTION_KEYS)
163
+ || !exactUniqueStrings(program.execution.depends_on, MAX_STAGES)
164
+ || program.execution.depends_on.length === 0
165
+ || !['receipt-program', 'action-escrow'].includes(program.execution.consequence_mode)
166
+ || (program.execution.consequence_mode === 'receipt-program'
167
+ && (!DIGEST.test(program.execution.capability_template_digest)
168
+ || program.execution.escrow_profile_digest !== null))
169
+ || (program.execution.consequence_mode === 'action-escrow'
170
+ && (!DIGEST.test(program.execution.escrow_profile_digest)
171
+ || program.execution.capability_template_digest !== null))) {
172
+ return validationFailure('program_execution_invalid');
173
+ }
174
+ const stageIds = new Set();
175
+ let totalRequirements = 0;
176
+ for (const stage of program.stages) {
177
+ if (!exactKeys(stage, STAGE_KEYS) || typeof stage.stage_id !== 'string' || !ID.test(stage.stage_id)
178
+ || stageIds.has(stage.stage_id))
179
+ return validationFailure('stage_id_invalid');
180
+ stageIds.add(stage.stage_id);
181
+ if (!exactUniqueStrings(stage.depends_on, MAX_STAGES) || stage.depends_on.includes(stage.stage_id)) {
182
+ return validationFailure('stage_dependency_invalid');
183
+ }
184
+ if (!Array.isArray(stage.requirements) || stage.requirements.length === 0
185
+ || stage.requirements.length > MAX_REQUIREMENTS_PER_STAGE) {
186
+ return validationFailure('stage_requirement_count_invalid');
187
+ }
188
+ totalRequirements += stage.requirements.length;
189
+ if (totalRequirements > MAX_TOTAL_REQUIREMENTS)
190
+ return validationFailure('program_requirement_limit');
191
+ const requirementIds = new Set();
192
+ for (const requirement of stage.requirements) {
193
+ if (!exactKeys(requirement, REQUIREMENT_KEYS)
194
+ || typeof requirement.requirement_id !== 'string' || !ID.test(requirement.requirement_id)
195
+ || requirementIds.has(requirement.requirement_id)
196
+ || typeof requirement.evidence_type !== 'string' || !ID.test(requirement.evidence_type)
197
+ || typeof requirement.verifier_profile !== 'string' || !ID.test(requirement.verifier_profile)
198
+ || typeof requirement.policy_digest !== 'string' || !DIGEST.test(requirement.policy_digest)
199
+ || !Number.isSafeInteger(requirement.max_age_sec) || requirement.max_age_sec < 1
200
+ || requirement.max_age_sec > 31_536_000
201
+ || typeof requirement.revocation_required !== 'boolean') {
202
+ return validationFailure('stage_requirement_invalid');
203
+ }
204
+ requirementIds.add(requirement.requirement_id);
205
+ }
206
+ const ruleKeys = stage.rule?.mode === 'threshold'
207
+ ? new Set(['mode', 'required', 'distinct_subjects', 'distinct_keys'])
208
+ : new Set(['mode', 'distinct_subjects', 'distinct_keys']);
209
+ if (!exactKeys(stage.rule, ruleKeys)
210
+ || !['all', 'any', 'threshold'].includes(stage.rule.mode)
211
+ || typeof stage.rule.distinct_subjects !== 'boolean'
212
+ || typeof stage.rule.distinct_keys !== 'boolean') {
213
+ return validationFailure('stage_rule_invalid');
214
+ }
215
+ const required = effectiveRequired(stage);
216
+ if (!Number.isSafeInteger(required) || required < 1 || required > stage.requirements.length) {
217
+ return validationFailure('stage_threshold_invalid');
218
+ }
219
+ }
220
+ for (const stage of program.stages) {
221
+ if (stage.depends_on.some((dependency) => !stageIds.has(dependency))) {
222
+ return validationFailure('stage_dependency_unknown');
223
+ }
224
+ }
225
+ if (program.execution.depends_on.some((dependency) => !stageIds.has(dependency))) {
226
+ return validationFailure('execution_dependency_unknown');
227
+ }
228
+ const byId = new Map(program.stages.map((stage) => [stage.stage_id, stage]));
229
+ const visiting = new Set();
230
+ const visited = new Set();
231
+ const visit = (stageId) => {
232
+ if (visiting.has(stageId))
233
+ return false;
234
+ if (visited.has(stageId))
235
+ return true;
236
+ visiting.add(stageId);
237
+ for (const dependency of byId.get(stageId).depends_on) {
238
+ if (!visit(dependency))
239
+ return false;
240
+ }
241
+ visiting.delete(stageId);
242
+ visited.add(stageId);
243
+ return true;
244
+ };
245
+ for (const stageId of stageIds) {
246
+ if (!visit(stageId))
247
+ return validationFailure('program_cycle');
248
+ }
249
+ const executionRelevant = new Set();
250
+ const markRelevant = (stageId) => {
251
+ if (executionRelevant.has(stageId))
252
+ return;
253
+ executionRelevant.add(stageId);
254
+ for (const dependency of byId.get(stageId).depends_on)
255
+ markRelevant(dependency);
256
+ };
257
+ for (const stageId of program.execution.depends_on)
258
+ markRelevant(stageId);
259
+ if ([...stageIds].some((stageId) => !executionRelevant.has(stageId))) {
260
+ return validationFailure('stage_not_execution_relevant');
261
+ }
262
+ return { valid: true, reason: null, digest: digest(program) };
263
+ }
264
+ export function trustProgramDigest(program) {
265
+ const result = validateTrustProgram(program);
266
+ if (!result.valid)
267
+ throw new TypeError(`invalid trust program: ${result.reason}`);
268
+ return result.digest;
269
+ }
270
+ function publicKey(value) {
271
+ try {
272
+ if (value instanceof crypto.KeyObject)
273
+ return value;
274
+ if (typeof value !== 'string' || !/^[A-Za-z0-9_-]+$/.test(value))
275
+ return null;
276
+ const bytes = Buffer.from(value, 'base64url');
277
+ if (bytes.toString('base64url') !== value)
278
+ return null;
279
+ const key = crypto.createPublicKey({ key: bytes, type: 'spki', format: 'der' });
280
+ return key.asymmetricKeyType === 'ed25519' ? key : null;
281
+ }
282
+ catch {
283
+ return null;
284
+ }
285
+ }
286
+ function stageReceiptBody(receipt) {
287
+ return { version: receipt.version, issuer: receipt.issuer, payload: receipt.payload };
288
+ }
289
+ function validStageReceiptShape(receipt) {
290
+ if (!exactKeys(receipt, STAGE_RECEIPT_KEYS)
291
+ || receipt.version !== TRUST_STAGE_RECEIPT_VERSION
292
+ || !exactKeys(receipt.issuer, STAGE_RECEIPT_ISSUER_KEYS)
293
+ || !Object.values(receipt.issuer).every(boundedContextString)
294
+ || !exactKeys(receipt.payload, STAGE_RECEIPT_PAYLOAD_KEYS)
295
+ || typeof receipt.payload.instance_id !== 'string' || !ID.test(receipt.payload.instance_id)
296
+ || typeof receipt.payload.program_id !== 'string' || !ID.test(receipt.payload.program_id)
297
+ || !Number.isSafeInteger(receipt.payload.program_version) || receipt.payload.program_version < 1
298
+ || typeof receipt.payload.program_digest !== 'string' || !DIGEST.test(receipt.payload.program_digest)
299
+ || typeof receipt.payload.root_caid !== 'string' || !CAID.test(receipt.payload.root_caid)
300
+ || typeof receipt.payload.action_digest !== 'string' || !DIGEST.test(receipt.payload.action_digest)
301
+ || typeof receipt.payload.stage_id !== 'string' || !ID.test(receipt.payload.stage_id)
302
+ || typeof receipt.payload.stage_policy_digest !== 'string' || !DIGEST.test(receipt.payload.stage_policy_digest)
303
+ || !sortedUniqueDigests(receipt.payload.predecessor_receipt_digests, MAX_STAGES)
304
+ || !sortedUniqueDigests(receipt.payload.evidence_digests, MAX_REQUIREMENTS_PER_STAGE)
305
+ || !boundedProjection(receipt.payload.subjects, false)
306
+ || !boundedProjection(receipt.payload.key_fingerprints, false)
307
+ || !receipt.payload.subjects.every((entry, index) => index === 0 || receipt.payload.subjects[index - 1] < entry)
308
+ || !receipt.payload.key_fingerprints.every((entry, index) => index === 0 || receipt.payload.key_fingerprints[index - 1] < entry)
309
+ || !Number.isFinite(strictInstant(receipt.payload.satisfied_at))
310
+ || typeof receipt.receipt_digest !== 'string' || !DIGEST.test(receipt.receipt_digest)
311
+ || !exactKeys(receipt.signature, STAGE_RECEIPT_SIGNATURE_KEYS)
312
+ || receipt.signature.algorithm !== 'Ed25519'
313
+ || typeof receipt.signature.value !== 'string'
314
+ || !/^[A-Za-z0-9_-]+$/.test(receipt.signature.value)
315
+ || Buffer.from(receipt.signature.value, 'base64url').length !== 64
316
+ || Buffer.from(receipt.signature.value, 'base64url').toString('base64url') !== receipt.signature.value) {
317
+ return false;
318
+ }
319
+ return true;
320
+ }
321
+ async function signStageReceipt(payload, context, privateKey, signer) {
322
+ const body = {
323
+ version: TRUST_STAGE_RECEIPT_VERSION,
324
+ issuer: {
325
+ issuer: context.issuer,
326
+ tenant: context.tenant,
327
+ environment: context.environment,
328
+ audience: context.audience,
329
+ key_id: context.key_id,
330
+ },
331
+ payload,
332
+ };
333
+ const receiptDigest = digest(body);
334
+ const signingBytes = Buffer.from(STAGE_RECEIPT_DOMAIN + canonicalize(body), 'utf8');
335
+ const signature = signer
336
+ ? await signer({ signingBytes, body: clone(body), receiptDigest })
337
+ : crypto.sign(null, signingBytes, privateKey).toString('base64url');
338
+ if (typeof signature !== 'string' || !/^[A-Za-z0-9_-]+$/.test(signature)
339
+ || Buffer.from(signature, 'base64url').length !== 64
340
+ || Buffer.from(signature, 'base64url').toString('base64url') !== signature) {
341
+ throw new TypeError('stage receipt signer returned a malformed Ed25519 signature');
342
+ }
343
+ return {
344
+ ...body,
345
+ receipt_digest: receiptDigest,
346
+ signature: { algorithm: 'Ed25519', value: signature },
347
+ };
348
+ }
349
+ /** Independently verify one stage receipt and optional relying-party bindings. */
350
+ export function verifyTrustStageReceipt(receipt, options = {}) {
351
+ const checks = {
352
+ structure: false, digest: false, key: false, signature: false, issuer: false, expected: false,
353
+ };
354
+ if (!validStageReceiptShape(receipt)) {
355
+ return { valid: false, reason: 'receipt_structure_invalid', checks };
356
+ }
357
+ checks.structure = true;
358
+ let bodyDigest;
359
+ try {
360
+ bodyDigest = digest(stageReceiptBody(receipt));
361
+ }
362
+ catch {
363
+ return { valid: false, reason: 'receipt_not_canonical', checks };
364
+ }
365
+ checks.digest = bodyDigest === receipt.receipt_digest;
366
+ if (!checks.digest)
367
+ return { valid: false, reason: 'receipt_digest_mismatch', checks };
368
+ const key = publicKey(options.trustedKeys?.[receipt.issuer.key_id]);
369
+ checks.key = key !== null;
370
+ if (!key)
371
+ return { valid: false, reason: 'receipt_key_untrusted', checks };
372
+ try {
373
+ checks.signature = crypto.verify(null, Buffer.from(STAGE_RECEIPT_DOMAIN + canonicalize(stageReceiptBody(receipt)), 'utf8'), key, Buffer.from(receipt.signature.value, 'base64url'));
374
+ }
375
+ catch {
376
+ checks.signature = false;
377
+ }
378
+ if (!checks.signature)
379
+ return { valid: false, reason: 'receipt_signature_invalid', checks };
380
+ try {
381
+ checks.issuer = options.expectedIssuer === undefined
382
+ || canonicalize(receipt.issuer) === canonicalize(options.expectedIssuer);
383
+ }
384
+ catch {
385
+ checks.issuer = false;
386
+ }
387
+ if (!checks.issuer)
388
+ return { valid: false, reason: 'receipt_expected_issuer_mismatch', checks };
389
+ const expected = options.expected ?? {};
390
+ checks.expected = Object.entries(expected).every(([field, value]) => {
391
+ try {
392
+ return canonicalize(receipt.payload[field]) === canonicalize(value);
393
+ }
394
+ catch {
395
+ return false;
396
+ }
397
+ });
398
+ if (!checks.expected)
399
+ return { valid: false, reason: 'receipt_expected_binding_mismatch', checks };
400
+ return { valid: true, reason: null, checks, receipt_digest: receipt.receipt_digest, payload: clone(receipt.payload) };
401
+ }
402
+ function initialState(program, programDigest, tenantId, instanceId, now) {
403
+ const stages = {};
404
+ for (const stage of program.stages) {
405
+ stages[stage.stage_id] = {
406
+ status: stage.depends_on.length === 0 ? 'collecting' : 'locked',
407
+ predecessor_receipt_digests: [],
408
+ evidence: {},
409
+ receipt: null,
410
+ };
411
+ }
412
+ return {
413
+ version: TRUST_PROGRAM_VERSION,
414
+ tenant_id: tenantId,
415
+ instance_id: instanceId,
416
+ program_id: program.program_id,
417
+ program_version: program.version,
418
+ program_digest: programDigest,
419
+ root_caid: program.root_caid,
420
+ action_digest: program.action_digest,
421
+ status: 'active',
422
+ revision: 0,
423
+ created_at: new Date(now).toISOString(),
424
+ updated_at: new Date(now).toISOString(),
425
+ stages,
426
+ used_evidence_ids: [],
427
+ execution: { status: 'locked', claim_token_digest: null, evidence_digest: null, outcome: null },
428
+ invalidation_reason: null,
429
+ };
430
+ }
431
+ function invalidateState(state, reason, at) {
432
+ const next = clone(state);
433
+ next.status = 'invalidated';
434
+ next.invalidation_reason = reason;
435
+ next.revision += 1;
436
+ next.updated_at = new Date(at).toISOString();
437
+ for (const stage of Object.values(next.stages))
438
+ stage.status = 'invalidated';
439
+ if (['locked', 'ready'].includes(next.execution.status))
440
+ next.execution.status = 'invalidated';
441
+ return next;
442
+ }
443
+ function storedStateValid(state, program, programDigest, tenantId, instanceId, receiptContext, receiptVerificationKey) {
444
+ if (!exactKeys(state, STATE_KEYS)
445
+ || state.version !== TRUST_PROGRAM_VERSION
446
+ || state.tenant_id !== tenantId
447
+ || state.instance_id !== instanceId
448
+ || state.program_id !== program.program_id
449
+ || state.program_version !== program.version
450
+ || state.program_digest !== programDigest
451
+ || state.root_caid !== program.root_caid
452
+ || state.action_digest !== program.action_digest
453
+ || !['active', 'invalidated'].includes(state.status)
454
+ || !Number.isSafeInteger(state.revision) || state.revision < 0
455
+ || !Number.isFinite(strictInstant(state.created_at))
456
+ || !Number.isFinite(strictInstant(state.updated_at))
457
+ || strictInstant(state.updated_at) < strictInstant(state.created_at)
458
+ || (state.status === 'active' && state.invalidation_reason !== null)
459
+ || (state.status === 'invalidated'
460
+ && (typeof state.invalidation_reason !== 'string'
461
+ || state.invalidation_reason.length < 1 || state.invalidation_reason.length > 256))
462
+ || !isRecord(state.stages)
463
+ || Object.keys(state.stages).length !== program.stages.length
464
+ || !Array.isArray(state.used_evidence_ids)
465
+ || state.used_evidence_ids.length > MAX_TOTAL_REQUIREMENTS
466
+ || !state.used_evidence_ids.every((entry) => typeof entry === 'string' && ID.test(entry))
467
+ || new Set(state.used_evidence_ids).size !== state.used_evidence_ids.length
468
+ || !state.used_evidence_ids.every((entry, index) => index === 0 || state.used_evidence_ids[index - 1] < entry)
469
+ || !isRecord(state.execution)
470
+ || !['status', 'claim_token_digest', 'evidence_digest', 'outcome'].every((key) => Object.hasOwn(state.execution, key))
471
+ || Object.keys(state.execution).some((key) => !EXECUTION_STATE_KEYS.has(key))) {
472
+ return false;
473
+ }
474
+ const usedEvidenceIds = [];
475
+ for (const definition of program.stages) {
476
+ const stage = state.stages[definition.stage_id];
477
+ if (!exactKeys(stage, STAGE_STATE_KEYS)
478
+ || !['locked', 'collecting', 'satisfied', 'invalidated'].includes(stage.status)
479
+ || !sortedUniqueDigests(stage.predecessor_receipt_digests, MAX_STAGES)
480
+ || !isRecord(stage.evidence)
481
+ || Object.keys(stage.evidence).length > definition.requirements.length)
482
+ return false;
483
+ const dependencyReceipts = definition.depends_on.map((dependency) => state.stages[dependency]?.receipt);
484
+ const expectedPredecessors = dependencyReceipts.every((entry) => entry !== null)
485
+ ? dependencyReceipts.map((entry) => entry.receipt_digest).sort()
486
+ : [];
487
+ if (canonicalize(stage.predecessor_receipt_digests) !== canonicalize(expectedPredecessors))
488
+ return false;
489
+ for (const [requirementId, accepted] of Object.entries(stage.evidence)) {
490
+ const requirement = definition.requirements.find((candidate) => candidate.requirement_id === requirementId);
491
+ if (!requirement || !exactKeys(accepted, EVIDENCE_STATE_KEYS)
492
+ || typeof accepted.evidence_id !== 'string' || !ID.test(accepted.evidence_id)
493
+ || typeof accepted.evidence_digest !== 'string' || !DIGEST.test(accepted.evidence_digest)
494
+ || accepted.policy_digest !== requirement.policy_digest
495
+ || typeof accepted.binding_digest !== 'string' || !DIGEST.test(accepted.binding_digest)
496
+ || !boundedProjection(accepted.subjects, definition.rule.distinct_subjects)
497
+ || !boundedProjection(accepted.key_fingerprints, definition.rule.distinct_keys)
498
+ || !Number.isFinite(strictInstant(accepted.issued_at))
499
+ || !Number.isFinite(strictInstant(accepted.expires_at))
500
+ || strictInstant(accepted.expires_at) <= strictInstant(accepted.issued_at)
501
+ || (accepted.revocation_checked_at !== null
502
+ && !Number.isFinite(strictInstant(accepted.revocation_checked_at)))
503
+ || (requirement.revocation_required && accepted.revocation_checked_at === null))
504
+ return false;
505
+ usedEvidenceIds.push(accepted.evidence_id);
506
+ }
507
+ const thresholdMet = stageIsSatisfied(definition, stage);
508
+ if (state.status === 'active') {
509
+ const dependenciesSatisfied = definition.depends_on.every((dependency) => state.stages[dependency].status === 'satisfied');
510
+ if ((!dependenciesSatisfied && stage.status !== 'locked')
511
+ || (dependenciesSatisfied && stage.status === 'locked')
512
+ || (stage.status === 'satisfied' && !thresholdMet)
513
+ || (stage.status === 'collecting' && thresholdMet)
514
+ || stage.status === 'invalidated')
515
+ return false;
516
+ }
517
+ else if (stage.status !== 'invalidated')
518
+ return false;
519
+ if (stage.receipt !== null) {
520
+ if (!validStageReceiptShape(stage.receipt)
521
+ || digest(stageReceiptBody(stage.receipt)) !== stage.receipt.receipt_digest)
522
+ return false;
523
+ const evidenceEntries = Object.values(stage.evidence);
524
+ const expectedPayload = {
525
+ instance_id: instanceId,
526
+ program_id: program.program_id,
527
+ program_version: program.version,
528
+ program_digest: programDigest,
529
+ root_caid: program.root_caid,
530
+ action_digest: program.action_digest,
531
+ stage_id: definition.stage_id,
532
+ stage_policy_digest: digest(definition),
533
+ predecessor_receipt_digests: [...stage.predecessor_receipt_digests],
534
+ evidence_digests: evidenceEntries.map((entry) => entry.evidence_digest).sort(),
535
+ subjects: [...new Set(evidenceEntries.flatMap((entry) => entry.subjects))].sort(),
536
+ key_fingerprints: [...new Set(evidenceEntries.flatMap((entry) => entry.key_fingerprints))].sort(),
537
+ };
538
+ if (!Object.entries(expectedPayload).every(([key, value]) => canonicalize(stage.receipt.payload[key]) === canonicalize(value)))
539
+ return false;
540
+ if (receiptVerificationKey) {
541
+ const verified = verifyTrustStageReceipt(stage.receipt, {
542
+ trustedKeys: { [receiptContext.key_id]: receiptVerificationKey },
543
+ expectedIssuer: receiptContext,
544
+ expected: expectedPayload,
545
+ });
546
+ if (!verified.valid)
547
+ return false;
548
+ }
549
+ }
550
+ if (stage.status === 'satisfied' && stage.receipt === null)
551
+ return false;
552
+ if (state.status === 'active' && stage.status !== 'satisfied' && stage.receipt !== null)
553
+ return false;
554
+ }
555
+ if (canonicalize([...usedEvidenceIds].sort()) !== canonicalize(state.used_evidence_ids))
556
+ return false;
557
+ const execution = state.execution;
558
+ if (!['locked', 'ready', 'claimed', 'executed', 'refused', 'indeterminate',
559
+ 'proved_no_effect', 'invalidated'].includes(execution.status))
560
+ return false;
561
+ const terminalReceipts = program.execution.depends_on.map((stageId) => state.stages[stageId].receipt?.receipt_digest);
562
+ const terminalSatisfied = terminalReceipts.every((entry) => typeof entry === 'string');
563
+ if (state.status === 'active'
564
+ && ((terminalSatisfied && execution.status === 'locked')
565
+ || (!terminalSatisfied && execution.status !== 'locked')
566
+ || execution.status === 'invalidated'))
567
+ return false;
568
+ if (state.status === 'invalidated'
569
+ && !['invalidated', 'claimed', 'executed', 'refused', 'indeterminate', 'proved_no_effect']
570
+ .includes(execution.status))
571
+ return false;
572
+ const claimedOrTerminal = ['claimed', 'executed', 'refused', 'indeterminate', 'proved_no_effect']
573
+ .includes(execution.status);
574
+ const createdAt = strictInstant(state.created_at);
575
+ const updatedAt = strictInstant(state.updated_at);
576
+ let claimedAt = NaN;
577
+ if (claimedOrTerminal) {
578
+ const binding = execution.authorization_binding;
579
+ claimedAt = strictInstant(execution.claimed_at);
580
+ if (!exactKeys(binding, AUTHORIZATION_BINDING_KEYS)
581
+ || binding.instance_id !== instanceId
582
+ || typeof binding.operation_id !== 'string' || !ID.test(binding.operation_id)
583
+ || binding.program_digest !== programDigest
584
+ || binding.root_caid !== program.root_caid
585
+ || binding.action_digest !== program.action_digest
586
+ || typeof binding.receipt_context_digest !== 'string'
587
+ || !DIGEST.test(binding.receipt_context_digest)
588
+ || binding.receipt_context_digest !== digest(receiptContext)
589
+ || canonicalize(binding.terminal_stage_receipt_digests)
590
+ !== canonicalize([...terminalReceipts].sort())
591
+ || binding.consequence_mode !== program.execution.consequence_mode
592
+ || binding.capability_template_digest !== program.execution.capability_template_digest
593
+ || binding.escrow_profile_digest !== program.execution.escrow_profile_digest
594
+ || !Number.isFinite(claimedAt) || claimedAt < createdAt || claimedAt > updatedAt)
595
+ return false;
596
+ }
597
+ else if (Object.hasOwn(execution, 'authorization_binding')
598
+ || Object.hasOwn(execution, 'operation_id') || Object.hasOwn(execution, 'claimed_at'))
599
+ return false;
600
+ if (execution.status === 'claimed') {
601
+ if (typeof execution.claim_token_digest !== 'string' || !DIGEST.test(execution.claim_token_digest)
602
+ || execution.evidence_digest !== null || execution.outcome !== null)
603
+ return false;
604
+ }
605
+ else if (execution.claim_token_digest !== null)
606
+ return false;
607
+ const outcomeStatus = ['executed', 'refused', 'indeterminate', 'proved_no_effect']
608
+ .includes(execution.status);
609
+ if (outcomeStatus) {
610
+ if (execution.outcome !== execution.status
611
+ || typeof execution.evidence_digest !== 'string' || !DIGEST.test(execution.evidence_digest))
612
+ return false;
613
+ }
614
+ else if (execution.evidence_digest !== null || execution.outcome !== null)
615
+ return false;
616
+ const hasFinalizedAt = Object.hasOwn(execution, 'finalized_at');
617
+ const hasReconciledAt = Object.hasOwn(execution, 'reconciled_at');
618
+ const finalizedAt = hasFinalizedAt ? strictInstant(execution.finalized_at) : NaN;
619
+ const reconciledAt = hasReconciledAt ? strictInstant(execution.reconciled_at) : NaN;
620
+ if (outcomeStatus) {
621
+ if (!hasFinalizedAt || !Number.isFinite(finalizedAt)
622
+ || finalizedAt < claimedAt || finalizedAt > updatedAt)
623
+ return false;
624
+ if (execution.status === 'proved_no_effect') {
625
+ if (!hasReconciledAt || !Number.isFinite(reconciledAt)
626
+ || reconciledAt < finalizedAt || reconciledAt > updatedAt)
627
+ return false;
628
+ }
629
+ else if (execution.status === 'executed' && hasReconciledAt) {
630
+ if (!Number.isFinite(reconciledAt)
631
+ || reconciledAt < finalizedAt || reconciledAt > updatedAt)
632
+ return false;
633
+ }
634
+ else if (execution.status !== 'executed' && hasReconciledAt)
635
+ return false;
636
+ }
637
+ else if (hasFinalizedAt || hasReconciledAt)
638
+ return false;
639
+ return true;
640
+ }
641
+ function assertStoreIdentity(tenantId, instanceId) {
642
+ if (typeof tenantId !== 'string'
643
+ || Buffer.byteLength(tenantId, 'utf8') < 1
644
+ || Buffer.byteLength(tenantId, 'utf8') > 512
645
+ || /[\u0000-\u001f\u007f]/.test(tenantId)) {
646
+ throw new TypeError('trust-program tenantId is invalid');
647
+ }
648
+ if (typeof instanceId !== 'string' || !ID.test(instanceId)) {
649
+ throw new TypeError('trust-program instanceId is invalid');
650
+ }
651
+ }
652
+ /**
653
+ * In-process compare-and-swap store. Deliberately rejected by the kernel unless
654
+ * allowEphemeralState is explicit; production must use a durable atomic store.
655
+ */
656
+ export function createMemoryTrustProgramStore() {
657
+ const records = new Map();
658
+ const storageKey = (tenantId, instanceId) => `${tenantId.length}:${tenantId}${instanceId}`;
659
+ return {
660
+ durable: false,
661
+ async create({ tenantId, state }) {
662
+ assertStoreIdentity(tenantId, state?.instance_id);
663
+ if (state?.tenant_id !== tenantId)
664
+ return fail('state_binding_invalid');
665
+ const key = storageKey(state.tenant_id, state.instance_id);
666
+ if (records.has(key))
667
+ return fail('instance_exists');
668
+ records.set(key, clone(state));
669
+ return { ok: true, state: clone(state) };
670
+ },
671
+ async get({ tenantId, instanceId }) {
672
+ assertStoreIdentity(tenantId, instanceId);
673
+ const state = records.get(storageKey(tenantId, instanceId));
674
+ return state ? { ok: true, state: clone(state) } : fail('instance_not_found');
675
+ },
676
+ async compareAndSwap({ tenantId, instanceId, expectedRevision, state }) {
677
+ assertStoreIdentity(tenantId, instanceId);
678
+ const key = storageKey(tenantId, instanceId);
679
+ const current = records.get(key);
680
+ if (!current)
681
+ return fail('instance_not_found');
682
+ if (state?.tenant_id !== tenantId || state?.instance_id !== instanceId) {
683
+ return fail('state_binding_invalid');
684
+ }
685
+ if (current.revision !== expectedRevision)
686
+ return fail('revision_conflict');
687
+ if (strictInstant(state?.updated_at) < strictInstant(current.updated_at)) {
688
+ return fail('clock_regression');
689
+ }
690
+ records.set(key, clone(state));
691
+ return { ok: true, state: clone(state) };
692
+ },
693
+ async invalidate({ tenantId, instanceId, expectedRevision, reason, at }) {
694
+ assertStoreIdentity(tenantId, instanceId);
695
+ const key = storageKey(tenantId, instanceId);
696
+ const current = records.get(key);
697
+ if (!current)
698
+ return fail('instance_not_found');
699
+ if (current.revision !== expectedRevision)
700
+ return fail('revision_conflict');
701
+ if (current.status === 'invalidated')
702
+ return fail('program_instance_invalidated');
703
+ if (!Number.isSafeInteger(at) || at < strictInstant(current.updated_at)) {
704
+ return fail('clock_regression');
705
+ }
706
+ const next = invalidateState(current, reason, at);
707
+ records.set(key, clone(next));
708
+ return { ok: true, state: clone(next) };
709
+ },
710
+ };
711
+ }
712
+ function findStage(program, stageId) {
713
+ return program.stages.find((stage) => stage.stage_id === stageId);
714
+ }
715
+ function findRequirement(stage, requirementId) {
716
+ return stage.requirements.find((requirement) => requirement.requirement_id === requirementId);
717
+ }
718
+ function updateUnlocks(state, program) {
719
+ for (const definition of program.stages) {
720
+ const stage = state.stages[definition.stage_id];
721
+ if (stage.status !== 'locked')
722
+ continue;
723
+ const predecessors = definition.depends_on.map((stageId) => state.stages[stageId]);
724
+ if (predecessors.every((entry) => entry.status === 'satisfied')) {
725
+ stage.status = 'collecting';
726
+ stage.predecessor_receipt_digests = predecessors
727
+ .map((entry) => entry.receipt.receipt_digest)
728
+ .sort();
729
+ }
730
+ }
731
+ if (program.execution.depends_on.every((stageId) => state.stages[stageId].status === 'satisfied')) {
732
+ state.execution.status = 'ready';
733
+ }
734
+ }
735
+ function stageIsSatisfied(definition, state) {
736
+ return Object.keys(state.evidence).length >= effectiveRequired(definition);
737
+ }
738
+ function tokenDigest(value) {
739
+ return digest({ token: value });
740
+ }
741
+ export function createTrustProgramKernel(options) {
742
+ const checked = validateTrustProgram(options?.program);
743
+ if (!checked.valid)
744
+ throw new TypeError(`invalid trust program: ${checked.reason}`);
745
+ if (!options.store || typeof options.store.create !== 'function'
746
+ || typeof options.store.get !== 'function'
747
+ || typeof options.store.compareAndSwap !== 'function'
748
+ || typeof options.store.invalidate !== 'function')
749
+ throw new TypeError('trust program store required');
750
+ if (options.store.durable !== true && options.allowEphemeralState !== true) {
751
+ throw new TypeError('durable trust program store required');
752
+ }
753
+ if (!isDataRecord(options.verifiers)
754
+ || Object.values(options.verifiers).some((verifier) => typeof verifier !== 'function')) {
755
+ throw new TypeError('trust program verifier registry required');
756
+ }
757
+ if ((!options.receiptPrivateKey && typeof options.receiptSigner !== 'function')
758
+ || !isDataRecord(options.receiptContext)
759
+ || !exactKeys(options.receiptContext, STAGE_RECEIPT_ISSUER_KEYS)
760
+ || !Object.values(options.receiptContext).every(boundedContextString)
761
+ || Buffer.byteLength(options.receiptContext.tenant, 'utf8') > 512) {
762
+ throw new TypeError('stage receipt signer required');
763
+ }
764
+ if (options.allowEphemeralState !== true
765
+ && (!options.receiptVerificationKey
766
+ || typeof options.actionBindingVerifier !== 'function'
767
+ || typeof options.executionBindingVerifier !== 'function'
768
+ || typeof options.executionEvidenceRevalidator !== 'function'
769
+ || typeof options.executionOutcomeVerifier !== 'function'
770
+ || typeof options.reconciliationVerifier !== 'function')) {
771
+ throw new TypeError('production trust verifiers required');
772
+ }
773
+ const program = clone(options.program);
774
+ const programDigest = checked.digest;
775
+ const receiptContext = Object.freeze(clone(options.receiptContext));
776
+ const tenantId = receiptContext.tenant;
777
+ const sourceStore = options.store;
778
+ const store = Object.freeze({
779
+ durable: sourceStore.durable === true,
780
+ create: sourceStore.create.bind(sourceStore),
781
+ get: sourceStore.get.bind(sourceStore),
782
+ compareAndSwap: sourceStore.compareAndSwap.bind(sourceStore),
783
+ invalidate: sourceStore.invalidate.bind(sourceStore),
784
+ });
785
+ const verifiers = Object.freeze({ ...options.verifiers });
786
+ const receiptPrivateKey = options.receiptPrivateKey;
787
+ const receiptVerificationKey = options.receiptVerificationKey;
788
+ const receiptSigner = options.receiptSigner;
789
+ const allowEphemeralState = options.allowEphemeralState === true;
790
+ const actionBindingVerifier = options.actionBindingVerifier;
791
+ const executionBindingVerifier = options.executionBindingVerifier;
792
+ const executionEvidenceRevalidator = options.executionEvidenceRevalidator;
793
+ const executionOutcomeVerifier = options.executionOutcomeVerifier;
794
+ const reconciliationVerifier = options.reconciliationVerifier;
795
+ const now = typeof options.now === 'function' ? options.now : Date.now;
796
+ const clockInstant = () => {
797
+ const current = now();
798
+ return Number.isSafeInteger(current) && current >= 0 && current <= 8_640_000_000_000_000
799
+ ? current : NaN;
800
+ };
801
+ const checkedStoreResult = (result, instanceId) => {
802
+ if (!isRecord(result) || typeof result.ok !== 'boolean')
803
+ return fail('store_response_invalid');
804
+ if (result.ok !== true)
805
+ return typeof result.reason === 'string'
806
+ ? result : fail('store_response_invalid');
807
+ if (!storedStateValid(result.state, program, programDigest, tenantId, instanceId, receiptContext, receiptVerificationKey))
808
+ return fail('store_state_invalid');
809
+ return result;
810
+ };
811
+ const load = async (instanceId) => {
812
+ try {
813
+ const result = await store.get({ tenantId, instanceId });
814
+ return checkedStoreResult(result, instanceId);
815
+ }
816
+ catch {
817
+ return fail('store_unavailable');
818
+ }
819
+ };
820
+ const compareAndSwap = async (input) => {
821
+ try {
822
+ const result = await store.compareAndSwap({ ...input, tenantId });
823
+ return checkedStoreResult(result, input.instanceId);
824
+ }
825
+ catch {
826
+ return fail('store_unavailable');
827
+ }
828
+ };
829
+ const active = (state) => state.status === 'invalidated'
830
+ ? fail('program_instance_invalidated') : null;
831
+ const inWindow = (current = clockInstant()) => {
832
+ if (!Number.isFinite(current))
833
+ return fail('clock_invalid');
834
+ if (current < strictInstant(program.valid_from))
835
+ return fail('program_not_yet_valid');
836
+ if (current >= strictInstant(program.expires_at))
837
+ return fail('program_expired');
838
+ return null;
839
+ };
840
+ const transitionInstant = (state) => {
841
+ const current = clockInstant();
842
+ if (!Number.isFinite(current))
843
+ return { ok: false, reason: 'clock_invalid' };
844
+ if (current < strictInstant(state.updated_at)) {
845
+ return { ok: false, reason: 'clock_regression' };
846
+ }
847
+ return { ok: true, at: current };
848
+ };
849
+ async function start({ instanceId, action }) {
850
+ if (typeof instanceId !== 'string' || !ID.test(instanceId))
851
+ return fail('instance_id_invalid');
852
+ const current = clockInstant();
853
+ const windowFailure = inWindow(current);
854
+ if (windowFailure)
855
+ return windowFailure;
856
+ if (typeof actionBindingVerifier === 'function') {
857
+ let actionBound = false;
858
+ try {
859
+ actionBound = await actionBindingVerifier({
860
+ action: clone(action),
861
+ expectedCaid: program.root_caid,
862
+ expectedActionDigest: program.action_digest,
863
+ program: clone(program),
864
+ }) === true;
865
+ }
866
+ catch {
867
+ actionBound = false;
868
+ }
869
+ if (!actionBound)
870
+ return fail('action_binding_invalid');
871
+ }
872
+ else if (!allowEphemeralState) {
873
+ return fail('action_binding_verifier_unavailable');
874
+ }
875
+ try {
876
+ const result = await store.create({
877
+ tenantId,
878
+ state: initialState(program, programDigest, tenantId, instanceId, current),
879
+ });
880
+ return checkedStoreResult(result, instanceId);
881
+ }
882
+ catch {
883
+ return fail('store_unavailable');
884
+ }
885
+ }
886
+ async function status(instanceId) {
887
+ return load(instanceId);
888
+ }
889
+ async function challenge({ instanceId, stageId, requirementId }) {
890
+ const loaded = await load(instanceId);
891
+ if (!loaded.ok)
892
+ return loaded;
893
+ const stateFailure = active(loaded.state);
894
+ if (stateFailure)
895
+ return stateFailure;
896
+ const windowFailure = inWindow();
897
+ if (windowFailure)
898
+ return windowFailure;
899
+ const definition = findStage(program, stageId);
900
+ if (!definition)
901
+ return fail('stage_unknown');
902
+ const requirement = findRequirement(definition, requirementId);
903
+ if (!requirement)
904
+ return fail('requirement_unknown');
905
+ const stage = loaded.state.stages[stageId];
906
+ if (stage.status === 'locked')
907
+ return fail('stage_locked');
908
+ if (stage.status !== 'collecting')
909
+ return fail('stage_not_collecting');
910
+ if (stage.evidence[requirementId])
911
+ return fail('requirement_already_satisfied');
912
+ const binding = {
913
+ instance_id: instanceId,
914
+ program_digest: programDigest,
915
+ program_version: program.version,
916
+ root_caid: program.root_caid,
917
+ action_digest: program.action_digest,
918
+ stage_id: stageId,
919
+ requirement_id: requirementId,
920
+ policy_digest: requirement.policy_digest,
921
+ predecessor_receipt_digests: [...stage.predecessor_receipt_digests],
922
+ };
923
+ return { ok: true, binding, binding_digest: digest(binding), verifier_profile: requirement.verifier_profile };
924
+ }
925
+ async function admit({ instanceId, stageId, requirementId, artifact }) {
926
+ const loaded = await load(instanceId);
927
+ if (!loaded.ok)
928
+ return loaded;
929
+ const stateFailure = active(loaded.state);
930
+ if (stateFailure)
931
+ return stateFailure;
932
+ const transition = transitionInstant(loaded.state);
933
+ if (!transition.ok)
934
+ return fail(transition.reason);
935
+ const current = transition.at;
936
+ const windowFailure = inWindow(current);
937
+ if (windowFailure)
938
+ return windowFailure;
939
+ const definition = findStage(program, stageId);
940
+ if (!definition)
941
+ return fail('stage_unknown');
942
+ const requirement = findRequirement(definition, requirementId);
943
+ if (!requirement)
944
+ return fail('requirement_unknown');
945
+ const stageState = loaded.state.stages[stageId];
946
+ if (stageState.status === 'locked')
947
+ return fail('stage_locked');
948
+ if (stageState.status !== 'collecting')
949
+ return fail('stage_not_collecting');
950
+ if (!isRecord(artifact) || typeof artifact.evidence_id !== 'string' || !ID.test(artifact.evidence_id)) {
951
+ return fail('evidence_id_invalid');
952
+ }
953
+ if (loaded.state.used_evidence_ids.includes(artifact.evidence_id))
954
+ return fail('evidence_replayed');
955
+ if (stageState.evidence[requirementId])
956
+ return fail('requirement_already_satisfied');
957
+ const verifier = verifiers[requirement.verifier_profile];
958
+ if (typeof verifier !== 'function')
959
+ return fail('verifier_unavailable');
960
+ let verified;
961
+ try {
962
+ verified = await verifier({ artifact: clone(artifact), requirement: clone(requirement), program: clone(program) });
963
+ }
964
+ catch {
965
+ return fail('evidence_verification_failed');
966
+ }
967
+ if (!isRecord(verified) || verified.valid !== true)
968
+ return fail(verified?.reason ?? 'evidence_verification_failed');
969
+ const expectedChallenge = await challenge({ instanceId, stageId, requirementId });
970
+ if (!expectedChallenge.ok)
971
+ return expectedChallenge;
972
+ if (verified.binding_digest !== expectedChallenge.binding_digest)
973
+ return fail('evidence_binding_mismatch');
974
+ if (verified.policy_digest !== requirement.policy_digest)
975
+ return fail('evidence_policy_mismatch');
976
+ const issuedAt = strictInstant(verified.issued_at);
977
+ const expiresAt = strictInstant(verified.expires_at);
978
+ if (!Number.isFinite(issuedAt) || issuedAt > current || current - issuedAt > requirement.max_age_sec * 1000) {
979
+ return fail('evidence_stale');
980
+ }
981
+ if (!Number.isFinite(expiresAt) || expiresAt <= current)
982
+ return fail('evidence_expired');
983
+ const revocationAt = strictInstant(verified.revocation_checked_at);
984
+ if (requirement.revocation_required
985
+ && (!Number.isFinite(revocationAt) || revocationAt > current
986
+ || current - revocationAt > requirement.max_age_sec * 1000)) {
987
+ return fail('revocation_check_required');
988
+ }
989
+ if (!boundedProjection(verified.subjects, definition.rule.distinct_subjects)
990
+ || !boundedProjection(verified.key_fingerprints, definition.rule.distinct_keys)) {
991
+ return fail('evidence_principal_set_invalid');
992
+ }
993
+ const prior = Object.values(stageState.evidence);
994
+ const priorSubjects = new Set(prior.flatMap((entry) => entry.subjects));
995
+ const priorKeys = new Set(prior.flatMap((entry) => entry.key_fingerprints));
996
+ if (definition.rule.distinct_subjects && verified.subjects.some((entry) => priorSubjects.has(entry))) {
997
+ return fail('stage_subject_not_distinct');
998
+ }
999
+ if (definition.rule.distinct_keys && verified.key_fingerprints.some((entry) => priorKeys.has(entry))) {
1000
+ return fail('stage_key_not_distinct');
1001
+ }
1002
+ const next = clone(loaded.state);
1003
+ const nextStage = next.stages[stageId];
1004
+ nextStage.evidence[requirementId] = {
1005
+ evidence_id: artifact.evidence_id,
1006
+ evidence_digest: digest(artifact),
1007
+ policy_digest: requirement.policy_digest,
1008
+ binding_digest: expectedChallenge.binding_digest,
1009
+ subjects: [...verified.subjects].sort(),
1010
+ key_fingerprints: [...verified.key_fingerprints].sort(),
1011
+ issued_at: verified.issued_at,
1012
+ expires_at: verified.expires_at,
1013
+ revocation_checked_at: verified.revocation_checked_at ?? null,
1014
+ };
1015
+ next.used_evidence_ids.push(artifact.evidence_id);
1016
+ next.used_evidence_ids.sort();
1017
+ next.revision += 1;
1018
+ next.updated_at = new Date(current).toISOString();
1019
+ let stageReceipt = null;
1020
+ if (stageIsSatisfied(definition, nextStage)) {
1021
+ nextStage.status = 'satisfied';
1022
+ const receiptPayload = {
1023
+ instance_id: instanceId,
1024
+ program_id: program.program_id,
1025
+ program_version: program.version,
1026
+ program_digest: programDigest,
1027
+ root_caid: program.root_caid,
1028
+ action_digest: program.action_digest,
1029
+ stage_id: stageId,
1030
+ stage_policy_digest: digest(definition),
1031
+ predecessor_receipt_digests: [...nextStage.predecessor_receipt_digests].sort(),
1032
+ evidence_digests: Object.values(nextStage.evidence)
1033
+ .map((entry) => entry.evidence_digest).sort(),
1034
+ subjects: [...new Set(Object.values(nextStage.evidence).flatMap((entry) => entry.subjects))].sort(),
1035
+ key_fingerprints: [...new Set(Object.values(nextStage.evidence).flatMap((entry) => entry.key_fingerprints))].sort(),
1036
+ satisfied_at: new Date(current).toISOString(),
1037
+ };
1038
+ try {
1039
+ stageReceipt = await signStageReceipt(receiptPayload, receiptContext, receiptPrivateKey, receiptSigner);
1040
+ }
1041
+ catch {
1042
+ return fail('stage_receipt_signing_failed');
1043
+ }
1044
+ if (receiptVerificationKey) {
1045
+ const selfCheck = verifyTrustStageReceipt(stageReceipt, {
1046
+ trustedKeys: { [receiptContext.key_id]: receiptVerificationKey },
1047
+ expectedIssuer: receiptContext,
1048
+ expected: receiptPayload,
1049
+ });
1050
+ if (!selfCheck.valid)
1051
+ return fail('stage_receipt_self_verification_failed');
1052
+ }
1053
+ nextStage.receipt = stageReceipt;
1054
+ updateUnlocks(next, program);
1055
+ }
1056
+ const committed = await compareAndSwap({
1057
+ instanceId,
1058
+ expectedRevision: loaded.state.revision,
1059
+ state: next,
1060
+ });
1061
+ if (!committed.ok)
1062
+ return committed;
1063
+ return {
1064
+ ok: true,
1065
+ stage_completed: stageReceipt !== null,
1066
+ stage_receipt: stageReceipt,
1067
+ state: committed.state,
1068
+ };
1069
+ }
1070
+ async function claimExecution({ instanceId, operationId, claimToken: suppliedClaimToken }) {
1071
+ const loaded = await load(instanceId);
1072
+ if (!loaded.ok)
1073
+ return loaded;
1074
+ const stateFailure = active(loaded.state);
1075
+ if (stateFailure)
1076
+ return stateFailure;
1077
+ const transition = transitionInstant(loaded.state);
1078
+ if (!transition.ok)
1079
+ return fail(transition.reason);
1080
+ const current = transition.at;
1081
+ const windowFailure = inWindow(current);
1082
+ if (windowFailure)
1083
+ return windowFailure;
1084
+ const statusValue = loaded.state.execution.status;
1085
+ if (statusValue === 'locked')
1086
+ return fail('execution_locked');
1087
+ if (statusValue === 'claimed') {
1088
+ if (typeof operationId === 'string' && operationId === loaded.state.execution.operation_id
1089
+ && typeof suppliedClaimToken === 'string'
1090
+ && tokenDigest(suppliedClaimToken) === loaded.state.execution.claim_token_digest) {
1091
+ return {
1092
+ ok: true,
1093
+ idempotent: true,
1094
+ claim_token: suppliedClaimToken,
1095
+ authorization_binding: clone(loaded.state.execution.authorization_binding),
1096
+ state: loaded.state,
1097
+ };
1098
+ }
1099
+ return fail('execution_already_claimed');
1100
+ }
1101
+ if (statusValue === 'indeterminate')
1102
+ return fail('execution_indeterminate');
1103
+ if (['executed', 'proved_no_effect', 'refused'].includes(statusValue))
1104
+ return fail('execution_already_terminal');
1105
+ if (statusValue !== 'ready')
1106
+ return fail('execution_unavailable');
1107
+ for (const definition of program.stages) {
1108
+ const accepted = Object.entries(loaded.state.stages[definition.stage_id].evidence);
1109
+ for (const [requirementId, entry] of accepted) {
1110
+ const requirement = findRequirement(definition, requirementId);
1111
+ const issuedAt = strictInstant(entry.issued_at);
1112
+ const expiresAt = strictInstant(entry.expires_at);
1113
+ const revocationAt = strictInstant(entry.revocation_checked_at);
1114
+ if (!requirement || !Number.isFinite(issuedAt) || !Number.isFinite(expiresAt)
1115
+ || issuedAt > current || current - issuedAt > requirement.max_age_sec * 1000
1116
+ || expiresAt <= current
1117
+ || (requirement.revocation_required
1118
+ && (!Number.isFinite(revocationAt)
1119
+ || revocationAt > current
1120
+ || current - revocationAt > requirement.max_age_sec * 1000))) {
1121
+ return fail('execution_evidence_stale');
1122
+ }
1123
+ }
1124
+ }
1125
+ if (typeof executionEvidenceRevalidator === 'function') {
1126
+ let evidenceStillValid = false;
1127
+ try {
1128
+ evidenceStillValid = await executionEvidenceRevalidator({
1129
+ state: clone(loaded.state),
1130
+ program: clone(program),
1131
+ checkedAt: new Date(current).toISOString(),
1132
+ }) === true;
1133
+ }
1134
+ catch {
1135
+ evidenceStillValid = false;
1136
+ }
1137
+ if (!evidenceStillValid)
1138
+ return fail('execution_evidence_revalidation_failed');
1139
+ }
1140
+ else if (!allowEphemeralState) {
1141
+ return fail('execution_evidence_revalidator_unavailable');
1142
+ }
1143
+ if (store.durable === true
1144
+ && (typeof operationId !== 'string' || !ID.test(operationId)
1145
+ || typeof suppliedClaimToken !== 'string' || suppliedClaimToken.length < 32)) {
1146
+ return fail('durable_execution_identity_required');
1147
+ }
1148
+ const claimToken = suppliedClaimToken ?? crypto.randomBytes(32).toString('base64url');
1149
+ if (typeof claimToken !== 'string' || claimToken.length < 32 || claimToken.length > 256) {
1150
+ return fail('execution_claim_token_invalid');
1151
+ }
1152
+ const stableOperationId = operationId ?? `tpo_${crypto.randomUUID()}`;
1153
+ const proposedBinding = {
1154
+ instance_id: instanceId,
1155
+ operation_id: stableOperationId,
1156
+ program_digest: programDigest,
1157
+ root_caid: program.root_caid,
1158
+ action_digest: program.action_digest,
1159
+ receipt_context_digest: digest(receiptContext),
1160
+ terminal_stage_receipt_digests: program.execution.depends_on
1161
+ .map((stageId) => loaded.state.stages[stageId].receipt.receipt_digest).sort(),
1162
+ consequence_mode: program.execution.consequence_mode,
1163
+ capability_template_digest: program.execution.capability_template_digest,
1164
+ escrow_profile_digest: program.execution.escrow_profile_digest,
1165
+ };
1166
+ if (typeof executionBindingVerifier === 'function') {
1167
+ let bindingValid = false;
1168
+ try {
1169
+ bindingValid = await executionBindingVerifier(clone(proposedBinding)) === true;
1170
+ }
1171
+ catch {
1172
+ bindingValid = false;
1173
+ }
1174
+ if (!bindingValid)
1175
+ return fail('execution_binding_invalid');
1176
+ }
1177
+ else if (!allowEphemeralState) {
1178
+ return fail('execution_binding_verifier_unavailable');
1179
+ }
1180
+ const next = clone(loaded.state);
1181
+ next.execution = {
1182
+ ...next.execution,
1183
+ status: 'claimed',
1184
+ operation_id: stableOperationId,
1185
+ claim_token_digest: tokenDigest(claimToken),
1186
+ claimed_at: new Date(current).toISOString(),
1187
+ authorization_binding: proposedBinding,
1188
+ };
1189
+ next.revision += 1;
1190
+ next.updated_at = new Date(current).toISOString();
1191
+ const committed = await compareAndSwap({
1192
+ instanceId, expectedRevision: loaded.state.revision, state: next,
1193
+ });
1194
+ if (!committed.ok)
1195
+ return committed;
1196
+ return {
1197
+ ok: true,
1198
+ claim_token: claimToken,
1199
+ authorization_binding: clone(next.execution.authorization_binding),
1200
+ state: committed.state,
1201
+ };
1202
+ }
1203
+ async function finalizeExecution({ instanceId, claimToken, outcome, evidenceDigest, evidence }) {
1204
+ const loaded = await load(instanceId);
1205
+ if (!loaded.ok)
1206
+ return loaded;
1207
+ const transition = transitionInstant(loaded.state);
1208
+ if (!transition.ok)
1209
+ return fail(transition.reason);
1210
+ const current = transition.at;
1211
+ if (loaded.state.execution.status !== 'claimed') {
1212
+ if (loaded.state.execution.status === 'indeterminate')
1213
+ return fail('execution_indeterminate');
1214
+ if (['executed', 'proved_no_effect', 'refused'].includes(loaded.state.execution.status)) {
1215
+ return fail('execution_already_terminal');
1216
+ }
1217
+ return fail('execution_not_claimed');
1218
+ }
1219
+ if (typeof claimToken !== 'string'
1220
+ || tokenDigest(claimToken) !== loaded.state.execution.claim_token_digest) {
1221
+ return fail('execution_claim_mismatch');
1222
+ }
1223
+ if (!['executed', 'refused', 'indeterminate'].includes(outcome) || !DIGEST.test(evidenceDigest)) {
1224
+ return fail('execution_outcome_invalid');
1225
+ }
1226
+ if (typeof executionOutcomeVerifier === 'function') {
1227
+ let verified = false;
1228
+ try {
1229
+ verified = await executionOutcomeVerifier({
1230
+ outcome,
1231
+ evidenceDigest,
1232
+ evidence: clone(evidence),
1233
+ authorizationBinding: clone(loaded.state.execution.authorization_binding),
1234
+ state: clone(loaded.state),
1235
+ }) === true;
1236
+ }
1237
+ catch {
1238
+ verified = false;
1239
+ }
1240
+ if (!verified)
1241
+ return fail('execution_evidence_invalid');
1242
+ }
1243
+ else if (!allowEphemeralState) {
1244
+ return fail('execution_outcome_verifier_unavailable');
1245
+ }
1246
+ const next = clone(loaded.state);
1247
+ next.execution.status = outcome;
1248
+ next.execution.outcome = outcome;
1249
+ next.execution.evidence_digest = evidenceDigest;
1250
+ next.execution.claim_token_digest = null;
1251
+ next.execution.finalized_at = new Date(current).toISOString();
1252
+ next.revision += 1;
1253
+ next.updated_at = new Date(current).toISOString();
1254
+ const committed = await compareAndSwap({
1255
+ instanceId, expectedRevision: loaded.state.revision, state: next,
1256
+ });
1257
+ return committed.ok ? { ok: true, state: committed.state } : committed;
1258
+ }
1259
+ async function reconcileExecution(input) {
1260
+ const loaded = await load(input.instanceId);
1261
+ if (!loaded.ok)
1262
+ return loaded;
1263
+ const transition = transitionInstant(loaded.state);
1264
+ if (!transition.ok)
1265
+ return fail(transition.reason);
1266
+ const current = transition.at;
1267
+ if (loaded.state.execution.status !== 'indeterminate') {
1268
+ if (['executed', 'proved_no_effect', 'refused'].includes(loaded.state.execution.status)) {
1269
+ return fail('execution_already_terminal');
1270
+ }
1271
+ return fail('execution_not_indeterminate');
1272
+ }
1273
+ if (!['executed', 'proved_no_effect'].includes(input.outcome) || !DIGEST.test(input.evidenceDigest)) {
1274
+ return fail('reconciliation_outcome_invalid');
1275
+ }
1276
+ if (typeof reconciliationVerifier === 'function') {
1277
+ let verified = false;
1278
+ try {
1279
+ verified = await reconciliationVerifier({
1280
+ ...clone(input),
1281
+ state: clone(loaded.state),
1282
+ authorizationBinding: clone(loaded.state.execution.authorization_binding),
1283
+ }) === true;
1284
+ }
1285
+ catch {
1286
+ verified = false;
1287
+ }
1288
+ if (!verified)
1289
+ return fail('reconciliation_evidence_invalid');
1290
+ }
1291
+ else if (!allowEphemeralState) {
1292
+ return fail('reconciliation_verifier_unavailable');
1293
+ }
1294
+ const next = clone(loaded.state);
1295
+ next.execution.status = input.outcome;
1296
+ next.execution.outcome = input.outcome;
1297
+ next.execution.evidence_digest = input.evidenceDigest;
1298
+ next.execution.reconciled_at = new Date(current).toISOString();
1299
+ next.revision += 1;
1300
+ next.updated_at = new Date(current).toISOString();
1301
+ const committed = await compareAndSwap({
1302
+ instanceId: input.instanceId, expectedRevision: loaded.state.revision, state: next,
1303
+ });
1304
+ return committed.ok ? { ok: true, state: committed.state } : committed;
1305
+ }
1306
+ async function invalidate({ instanceId, expectedRevision, reason }) {
1307
+ if (typeof reason !== 'string' || reason.length < 1 || reason.length > 256)
1308
+ return fail('invalidation_reason_invalid');
1309
+ const loaded = await load(instanceId);
1310
+ if (!loaded.ok)
1311
+ return loaded;
1312
+ if (!Number.isSafeInteger(expectedRevision) || loaded.state.revision !== expectedRevision) {
1313
+ return fail('revision_conflict');
1314
+ }
1315
+ if (loaded.state.status === 'invalidated')
1316
+ return fail('program_instance_invalidated');
1317
+ const transition = transitionInstant(loaded.state);
1318
+ if (!transition.ok)
1319
+ return fail(transition.reason);
1320
+ try {
1321
+ const result = await store.invalidate({
1322
+ tenantId, instanceId, expectedRevision, reason, at: transition.at,
1323
+ });
1324
+ return checkedStoreResult(result, instanceId);
1325
+ }
1326
+ catch {
1327
+ return fail('store_unavailable');
1328
+ }
1329
+ }
1330
+ return Object.freeze({
1331
+ program_digest: programDigest,
1332
+ start,
1333
+ status,
1334
+ challenge,
1335
+ admit,
1336
+ claimExecution,
1337
+ finalizeExecution,
1338
+ reconcileExecution,
1339
+ invalidate,
1340
+ });
1341
+ }
1342
+ export default {
1343
+ TRUST_PROGRAM_VERSION,
1344
+ TRUST_STAGE_RECEIPT_VERSION,
1345
+ validateTrustProgram,
1346
+ trustProgramDigest,
1347
+ verifyTrustStageReceipt,
1348
+ createMemoryTrustProgramStore,
1349
+ createTrustProgramKernel,
1350
+ };
1351
+ //# sourceMappingURL=trust-program.js.map