@emilia-protocol/gate 0.11.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +310 -0
  3. package/action-control-manifest.js +3 -268
  4. package/action-escrow-custodian.js +3 -394
  5. package/action-escrow-evidence.js +3 -858
  6. package/action-escrow-package.js +3 -1668
  7. package/action-escrow-postgres.js +3 -337
  8. package/action-escrow-state.js +3 -396
  9. package/action-escrow-verifiers.js +3 -331
  10. package/action-escrow.js +3 -2447
  11. package/action-packs.js +3 -140
  12. package/adapters/_kit.js +3 -104
  13. package/adapters/aws.js +3 -108
  14. package/adapters/cloudflare.js +3 -54
  15. package/adapters/gcp.js +3 -70
  16. package/adapters/github.js +3 -109
  17. package/adapters/jira.js +3 -55
  18. package/adapters/k8s.js +3 -70
  19. package/adapters/linear.js +3 -55
  20. package/adapters/salesforce.js +3 -55
  21. package/adapters/stripe.js +3 -81
  22. package/adapters/supabase.js +3 -121
  23. package/adapters/terraform.js +3 -59
  24. package/adapters/vercel.js +3 -84
  25. package/aec-execution.js +3 -312
  26. package/breakglass.js +3 -582
  27. package/capability-receipt.js +4 -0
  28. package/cf1-conformance.js +3 -106
  29. package/challenge-store.js +3 -69
  30. package/control-plane.js +3 -340
  31. package/coverage.js +3 -721
  32. package/demo.mjs +1 -1
  33. package/deployment-attestation.js +3 -247
  34. package/dist/action-control-manifest.d.ts +82 -0
  35. package/dist/action-control-manifest.d.ts.map +1 -0
  36. package/dist/action-control-manifest.js +422 -0
  37. package/dist/action-control-manifest.js.map +1 -0
  38. package/dist/action-escrow-custodian.d.ts +74 -0
  39. package/dist/action-escrow-custodian.d.ts.map +1 -0
  40. package/dist/action-escrow-custodian.js +363 -0
  41. package/dist/action-escrow-custodian.js.map +1 -0
  42. package/dist/action-escrow-evidence.d.ts +130 -0
  43. package/dist/action-escrow-evidence.d.ts.map +1 -0
  44. package/dist/action-escrow-evidence.js +1012 -0
  45. package/dist/action-escrow-evidence.js.map +1 -0
  46. package/dist/action-escrow-package.d.ts +10 -0
  47. package/dist/action-escrow-package.d.ts.map +1 -0
  48. package/dist/action-escrow-package.js +1583 -0
  49. package/dist/action-escrow-package.js.map +1 -0
  50. package/dist/action-escrow-postgres.d.ts +62 -0
  51. package/dist/action-escrow-postgres.d.ts.map +1 -0
  52. package/dist/action-escrow-postgres.js +339 -0
  53. package/dist/action-escrow-postgres.js.map +1 -0
  54. package/dist/action-escrow-state.d.ts +141 -0
  55. package/dist/action-escrow-state.d.ts.map +1 -0
  56. package/dist/action-escrow-state.js +413 -0
  57. package/dist/action-escrow-state.js.map +1 -0
  58. package/dist/action-escrow-verifiers.d.ts +65 -0
  59. package/dist/action-escrow-verifiers.d.ts.map +1 -0
  60. package/dist/action-escrow-verifiers.js +421 -0
  61. package/dist/action-escrow-verifiers.js.map +1 -0
  62. package/dist/action-escrow.d.ts +112 -0
  63. package/dist/action-escrow.d.ts.map +1 -0
  64. package/dist/action-escrow.js +2661 -0
  65. package/dist/action-escrow.js.map +1 -0
  66. package/dist/action-packs.d.ts +54 -0
  67. package/dist/action-packs.d.ts.map +1 -0
  68. package/dist/action-packs.js +143 -0
  69. package/dist/action-packs.js.map +1 -0
  70. package/dist/adapters/_kit.d.ts +50 -0
  71. package/dist/adapters/_kit.d.ts.map +1 -0
  72. package/dist/adapters/_kit.js +98 -0
  73. package/dist/adapters/_kit.js.map +1 -0
  74. package/dist/adapters/aws.d.ts +158 -0
  75. package/dist/adapters/aws.d.ts.map +1 -0
  76. package/dist/adapters/aws.js +136 -0
  77. package/dist/adapters/aws.js.map +1 -0
  78. package/dist/adapters/cloudflare.d.ts +116 -0
  79. package/dist/adapters/cloudflare.d.ts.map +1 -0
  80. package/dist/adapters/cloudflare.js +75 -0
  81. package/dist/adapters/cloudflare.js.map +1 -0
  82. package/dist/adapters/gcp.d.ts +139 -0
  83. package/dist/adapters/gcp.d.ts.map +1 -0
  84. package/dist/adapters/gcp.js +69 -0
  85. package/dist/adapters/gcp.js.map +1 -0
  86. package/dist/adapters/github.d.ts +123 -0
  87. package/dist/adapters/github.d.ts.map +1 -0
  88. package/dist/adapters/github.js +105 -0
  89. package/dist/adapters/github.js.map +1 -0
  90. package/dist/adapters/jira.d.ts +109 -0
  91. package/dist/adapters/jira.d.ts.map +1 -0
  92. package/dist/adapters/jira.js +54 -0
  93. package/dist/adapters/jira.js.map +1 -0
  94. package/dist/adapters/k8s.d.ts +139 -0
  95. package/dist/adapters/k8s.d.ts.map +1 -0
  96. package/dist/adapters/k8s.js +69 -0
  97. package/dist/adapters/k8s.js.map +1 -0
  98. package/dist/adapters/linear.d.ts +109 -0
  99. package/dist/adapters/linear.d.ts.map +1 -0
  100. package/dist/adapters/linear.js +54 -0
  101. package/dist/adapters/linear.js.map +1 -0
  102. package/dist/adapters/salesforce.d.ts +109 -0
  103. package/dist/adapters/salesforce.d.ts.map +1 -0
  104. package/dist/adapters/salesforce.js +54 -0
  105. package/dist/adapters/salesforce.js.map +1 -0
  106. package/dist/adapters/stripe.d.ts +116 -0
  107. package/dist/adapters/stripe.d.ts.map +1 -0
  108. package/dist/adapters/stripe.js +77 -0
  109. package/dist/adapters/stripe.js.map +1 -0
  110. package/dist/adapters/supabase.d.ts +127 -0
  111. package/dist/adapters/supabase.d.ts.map +1 -0
  112. package/dist/adapters/supabase.js +112 -0
  113. package/dist/adapters/supabase.js.map +1 -0
  114. package/dist/adapters/terraform.d.ts +109 -0
  115. package/dist/adapters/terraform.d.ts.map +1 -0
  116. package/dist/adapters/terraform.js +58 -0
  117. package/dist/adapters/terraform.js.map +1 -0
  118. package/dist/adapters/vercel.d.ts +114 -0
  119. package/dist/adapters/vercel.d.ts.map +1 -0
  120. package/dist/adapters/vercel.js +81 -0
  121. package/dist/adapters/vercel.js.map +1 -0
  122. package/dist/aec-execution.d.ts +107 -0
  123. package/dist/aec-execution.d.ts.map +1 -0
  124. package/dist/aec-execution.js +334 -0
  125. package/dist/aec-execution.js.map +1 -0
  126. package/dist/breakglass.d.ts +205 -0
  127. package/dist/breakglass.d.ts.map +1 -0
  128. package/dist/breakglass.js +627 -0
  129. package/dist/breakglass.js.map +1 -0
  130. package/dist/capability-receipt.d.ts +601 -0
  131. package/dist/capability-receipt.d.ts.map +1 -0
  132. package/dist/capability-receipt.js +1177 -0
  133. package/dist/capability-receipt.js.map +1 -0
  134. package/dist/cf1-conformance.d.ts +83 -0
  135. package/dist/cf1-conformance.d.ts.map +1 -0
  136. package/dist/cf1-conformance.js +94 -0
  137. package/dist/cf1-conformance.js.map +1 -0
  138. package/dist/challenge-store.d.ts +20 -0
  139. package/dist/challenge-store.d.ts.map +1 -0
  140. package/dist/challenge-store.js +62 -0
  141. package/dist/challenge-store.js.map +1 -0
  142. package/dist/control-plane.d.ts +136 -0
  143. package/dist/control-plane.d.ts.map +1 -0
  144. package/dist/control-plane.js +349 -0
  145. package/dist/control-plane.js.map +1 -0
  146. package/dist/coverage.d.ts +156 -0
  147. package/dist/coverage.d.ts.map +1 -0
  148. package/dist/coverage.js +768 -0
  149. package/dist/coverage.js.map +1 -0
  150. package/dist/deployment-attestation.d.ts +93 -0
  151. package/dist/deployment-attestation.d.ts.map +1 -0
  152. package/dist/deployment-attestation.js +262 -0
  153. package/dist/deployment-attestation.js.map +1 -0
  154. package/dist/eg1-conformance.d.ts +286 -0
  155. package/dist/eg1-conformance.d.ts.map +1 -0
  156. package/dist/eg1-conformance.js +379 -0
  157. package/dist/eg1-conformance.js.map +1 -0
  158. package/dist/enterprise.d.ts +88 -0
  159. package/dist/enterprise.d.ts.map +1 -0
  160. package/dist/enterprise.js +194 -0
  161. package/dist/enterprise.js.map +1 -0
  162. package/dist/evidence-postgres.d.ts +123 -0
  163. package/dist/evidence-postgres.d.ts.map +1 -0
  164. package/dist/evidence-postgres.js +343 -0
  165. package/dist/evidence-postgres.js.map +1 -0
  166. package/dist/evidence.d.ts +113 -0
  167. package/dist/evidence.d.ts.map +1 -0
  168. package/dist/evidence.js +369 -0
  169. package/dist/evidence.js.map +1 -0
  170. package/dist/execution-binding.d.ts +49 -0
  171. package/dist/execution-binding.d.ts.map +1 -0
  172. package/dist/execution-binding.js +243 -0
  173. package/dist/execution-binding.js.map +1 -0
  174. package/dist/formal-runtime-map.d.ts +42 -0
  175. package/dist/formal-runtime-map.d.ts.map +1 -0
  176. package/dist/formal-runtime-map.js +23 -0
  177. package/dist/formal-runtime-map.js.map +1 -0
  178. package/dist/index.d.ts +1003 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +1737 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/key-registry.d.ts +60 -0
  183. package/dist/key-registry.d.ts.map +1 -0
  184. package/dist/key-registry.js +144 -0
  185. package/dist/key-registry.js.map +1 -0
  186. package/dist/mcp.d.ts +53 -0
  187. package/dist/mcp.d.ts.map +1 -0
  188. package/dist/mcp.js +112 -0
  189. package/dist/mcp.js.map +1 -0
  190. package/dist/metering.d.ts +80 -0
  191. package/dist/metering.d.ts.map +1 -0
  192. package/dist/metering.js +227 -0
  193. package/dist/metering.js.map +1 -0
  194. package/dist/metrics.d.ts +68 -0
  195. package/dist/metrics.d.ts.map +1 -0
  196. package/dist/metrics.js +242 -0
  197. package/dist/metrics.js.map +1 -0
  198. package/dist/network-witness.d.ts +167 -0
  199. package/dist/network-witness.d.ts.map +1 -0
  200. package/dist/network-witness.js +520 -0
  201. package/dist/network-witness.js.map +1 -0
  202. package/dist/proposal-to-effect.d.ts +146 -0
  203. package/dist/proposal-to-effect.d.ts.map +1 -0
  204. package/dist/proposal-to-effect.js +427 -0
  205. package/dist/proposal-to-effect.js.map +1 -0
  206. package/dist/protocol-state.d.ts +141 -0
  207. package/dist/protocol-state.d.ts.map +1 -0
  208. package/dist/protocol-state.js +106 -0
  209. package/dist/protocol-state.js.map +1 -0
  210. package/dist/receipt-program.d.ts +59 -0
  211. package/dist/receipt-program.d.ts.map +1 -0
  212. package/dist/receipt-program.js +1053 -0
  213. package/dist/receipt-program.js.map +1 -0
  214. package/dist/reliance-kernel.d.ts +66 -0
  215. package/dist/reliance-kernel.d.ts.map +1 -0
  216. package/dist/reliance-kernel.js +103 -0
  217. package/dist/reliance-kernel.js.map +1 -0
  218. package/dist/reliance-packet.d.ts +67 -0
  219. package/dist/reliance-packet.d.ts.map +1 -0
  220. package/dist/reliance-packet.js +176 -0
  221. package/dist/reliance-packet.js.map +1 -0
  222. package/dist/remedy-program-postgres.d.ts +37 -0
  223. package/dist/remedy-program-postgres.d.ts.map +1 -0
  224. package/dist/remedy-program-postgres.js +291 -0
  225. package/dist/remedy-program-postgres.js.map +1 -0
  226. package/dist/remedy-program-receipt.d.ts +97 -0
  227. package/dist/remedy-program-receipt.d.ts.map +1 -0
  228. package/dist/remedy-program-receipt.js +817 -0
  229. package/dist/remedy-program-receipt.js.map +1 -0
  230. package/dist/remedy-program.d.ts +76 -0
  231. package/dist/remedy-program.d.ts.map +1 -0
  232. package/dist/remedy-program.js +1346 -0
  233. package/dist/remedy-program.js.map +1 -0
  234. package/dist/reports/art14.d.ts +101 -0
  235. package/dist/reports/art14.d.ts.map +1 -0
  236. package/dist/reports/art14.js +340 -0
  237. package/dist/reports/art14.js.map +1 -0
  238. package/dist/reports/assurance-package.d.ts +197 -0
  239. package/dist/reports/assurance-package.d.ts.map +1 -0
  240. package/dist/reports/assurance-package.js +313 -0
  241. package/dist/reports/assurance-package.js.map +1 -0
  242. package/dist/reports/auditor-workpaper.d.ts +194 -0
  243. package/dist/reports/auditor-workpaper.d.ts.map +1 -0
  244. package/dist/reports/auditor-workpaper.js +545 -0
  245. package/dist/reports/auditor-workpaper.js.map +1 -0
  246. package/dist/reports/external-verification.d.ts +76 -0
  247. package/dist/reports/external-verification.d.ts.map +1 -0
  248. package/dist/reports/external-verification.js +209 -0
  249. package/dist/reports/external-verification.js.map +1 -0
  250. package/dist/reports/reperform.d.ts +122 -0
  251. package/dist/reports/reperform.d.ts.map +1 -0
  252. package/dist/reports/reperform.js +432 -0
  253. package/dist/reports/reperform.js.map +1 -0
  254. package/dist/reports/underwriter.d.ts +127 -0
  255. package/dist/reports/underwriter.d.ts.map +1 -0
  256. package/dist/reports/underwriter.js +348 -0
  257. package/dist/reports/underwriter.js.map +1 -0
  258. package/dist/retention.d.ts +88 -0
  259. package/dist/retention.d.ts.map +1 -0
  260. package/dist/retention.js +90 -0
  261. package/dist/retention.js.map +1 -0
  262. package/dist/roster.d.ts +113 -0
  263. package/dist/roster.d.ts.map +1 -0
  264. package/dist/roster.js +261 -0
  265. package/dist/roster.js.map +1 -0
  266. package/dist/runtime-monitor.d.ts +132 -0
  267. package/dist/runtime-monitor.d.ts.map +1 -0
  268. package/dist/runtime-monitor.js +197 -0
  269. package/dist/runtime-monitor.js.map +1 -0
  270. package/dist/settlement.d.ts +68 -0
  271. package/dist/settlement.d.ts.map +1 -0
  272. package/dist/settlement.js +306 -0
  273. package/dist/settlement.js.map +1 -0
  274. package/dist/siem.d.ts +261 -0
  275. package/dist/siem.d.ts.map +1 -0
  276. package/dist/siem.js +247 -0
  277. package/dist/siem.js.map +1 -0
  278. package/dist/store-postgres.d.ts +107 -0
  279. package/dist/store-postgres.d.ts.map +1 -0
  280. package/dist/store-postgres.js +152 -0
  281. package/dist/store-postgres.js.map +1 -0
  282. package/dist/store.d.ts +95 -0
  283. package/dist/store.d.ts.map +1 -0
  284. package/dist/store.js +202 -0
  285. package/dist/store.js.map +1 -0
  286. package/dist/strict-json.d.ts +14 -0
  287. package/dist/strict-json.d.ts.map +1 -0
  288. package/dist/strict-json.js +132 -0
  289. package/dist/strict-json.js.map +1 -0
  290. package/dist/trust-program-adapters.d.ts +122 -0
  291. package/dist/trust-program-adapters.d.ts.map +1 -0
  292. package/dist/trust-program-adapters.js +802 -0
  293. package/dist/trust-program-adapters.js.map +1 -0
  294. package/dist/trust-program-postgres.d.ts +41 -0
  295. package/dist/trust-program-postgres.d.ts.map +1 -0
  296. package/dist/trust-program-postgres.js +346 -0
  297. package/dist/trust-program-postgres.js.map +1 -0
  298. package/dist/trust-program-revocation.d.ts +206 -0
  299. package/dist/trust-program-revocation.d.ts.map +1 -0
  300. package/dist/trust-program-revocation.js +600 -0
  301. package/dist/trust-program-revocation.js.map +1 -0
  302. package/dist/trust-program.d.ts +197 -0
  303. package/dist/trust-program.d.ts.map +1 -0
  304. package/dist/trust-program.js +1351 -0
  305. package/dist/trust-program.js.map +1 -0
  306. package/dist/witness-postgres.d.ts +43 -0
  307. package/dist/witness-postgres.d.ts.map +1 -0
  308. package/dist/witness-postgres.js +92 -0
  309. package/dist/witness-postgres.js.map +1 -0
  310. package/dist/zk-range-proof.d.ts +78 -0
  311. package/dist/zk-range-proof.d.ts.map +1 -0
  312. package/dist/zk-range-proof.js +228 -0
  313. package/dist/zk-range-proof.js.map +1 -0
  314. package/eg1-conformance.js +3 -404
  315. package/enterprise.js +3 -180
  316. package/ep-assure.mjs +1 -0
  317. package/evidence-postgres.js +3 -356
  318. package/evidence.js +4 -360
  319. package/execution-binding.js +3 -212
  320. package/formal-runtime-map.js +4 -0
  321. package/index.js +4 -1249
  322. package/key-registry.js +3 -134
  323. package/mcp.js +3 -109
  324. package/metering.js +3 -226
  325. package/metrics.js +3 -231
  326. package/network-witness.js +3 -499
  327. package/package.json +35 -4
  328. package/proposal-to-effect.js +4 -0
  329. package/receipt-program.js +4 -0
  330. package/reliance-kernel.js +3 -112
  331. package/reliance-packet.js +3 -175
  332. package/remedy-program-postgres.js +4 -0
  333. package/remedy-program-receipt.js +4 -0
  334. package/remedy-program.js +4 -0
  335. package/reports/art14.js +0 -0
  336. package/reports/assurance-package.js +2 -312
  337. package/reports/auditor-workpaper.js +3 -537
  338. package/reports/external-verification.js +3 -222
  339. package/reports/reperform.js +3 -424
  340. package/reports/underwriter.js +3 -339
  341. package/retention.js +3 -84
  342. package/roster.js +3 -264
  343. package/runtime-monitor.js +4 -0
  344. package/settlement.js +3 -299
  345. package/siem.js +3 -235
  346. package/src/action-control-manifest.ts +424 -0
  347. package/src/action-escrow-custodian.ts +427 -0
  348. package/src/action-escrow-evidence.ts +1178 -0
  349. package/src/action-escrow-package.ts +1821 -0
  350. package/src/action-escrow-postgres.ts +366 -0
  351. package/src/action-escrow-state.ts +498 -0
  352. package/src/action-escrow-verifiers.ts +484 -0
  353. package/src/action-escrow.ts +3039 -0
  354. package/src/action-packs.ts +170 -0
  355. package/src/adapters/_kit.ts +117 -0
  356. package/src/adapters/aws.ts +142 -0
  357. package/src/adapters/cloudflare.ts +78 -0
  358. package/src/adapters/gcp.ts +71 -0
  359. package/src/adapters/github.ts +110 -0
  360. package/src/adapters/jira.ts +56 -0
  361. package/src/adapters/k8s.ts +71 -0
  362. package/src/adapters/linear.ts +56 -0
  363. package/src/adapters/salesforce.ts +56 -0
  364. package/src/adapters/stripe.ts +82 -0
  365. package/src/adapters/supabase.ts +122 -0
  366. package/src/adapters/terraform.ts +60 -0
  367. package/src/adapters/vercel.ts +85 -0
  368. package/src/aec-execution.ts +354 -0
  369. package/src/breakglass.ts +647 -0
  370. package/src/capability-receipt.ts +1258 -0
  371. package/src/cf1-conformance.ts +120 -0
  372. package/src/challenge-store.ts +70 -0
  373. package/src/control-plane.ts +374 -0
  374. package/src/coverage.ts +832 -0
  375. package/src/deployment-attestation.ts +259 -0
  376. package/src/eg1-conformance.ts +471 -0
  377. package/src/enterprise.ts +195 -0
  378. package/src/evidence-postgres.ts +374 -0
  379. package/src/evidence.ts +367 -0
  380. package/src/execution-binding.ts +236 -0
  381. package/src/formal-runtime-map.ts +24 -0
  382. package/src/index.ts +2127 -0
  383. package/src/key-registry.ts +150 -0
  384. package/src/mcp.ts +120 -0
  385. package/src/metering.ts +237 -0
  386. package/src/metrics.ts +232 -0
  387. package/src/network-witness.ts +585 -0
  388. package/src/proposal-to-effect.ts +571 -0
  389. package/src/protocol-state.ts +143 -0
  390. package/src/protocol-state.typecheck.ts +94 -0
  391. package/src/receipt-program.ts +1105 -0
  392. package/src/reliance-kernel.ts +119 -0
  393. package/src/reliance-packet.ts +206 -0
  394. package/src/remedy-program-postgres.ts +367 -0
  395. package/src/remedy-program-receipt.ts +891 -0
  396. package/src/remedy-program.ts +1388 -0
  397. package/src/reports/art14.ts +361 -0
  398. package/src/reports/assurance-package.ts +330 -0
  399. package/src/reports/auditor-workpaper.ts +558 -0
  400. package/src/reports/external-verification.ts +232 -0
  401. package/src/reports/reperform.ts +441 -0
  402. package/src/reports/underwriter.ts +356 -0
  403. package/src/retention.ts +100 -0
  404. package/src/roster.ts +273 -0
  405. package/src/runtime-monitor.ts +217 -0
  406. package/src/settlement.ts +313 -0
  407. package/src/siem.ts +247 -0
  408. package/src/store-postgres.ts +167 -0
  409. package/src/store.ts +214 -0
  410. package/src/strict-json.ts +115 -0
  411. package/src/trust-program-adapters.ts +847 -0
  412. package/src/trust-program-postgres.ts +429 -0
  413. package/src/trust-program-revocation.ts +751 -0
  414. package/src/trust-program.ts +1460 -0
  415. package/src/witness-postgres.ts +106 -0
  416. package/src/zk-range-proof.ts +250 -0
  417. package/store-postgres.js +3 -161
  418. package/store.js +4 -198
  419. package/strict-json.js +3 -102
  420. package/trust-program-adapters.js +4 -0
  421. package/trust-program-postgres.js +4 -0
  422. package/trust-program-revocation.js +4 -0
  423. package/trust-program.js +4 -0
  424. package/witness-postgres.js +3 -96
  425. package/zk-range-proof.js +4 -0
package/coverage.js CHANGED
@@ -1,722 +1,4 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- /**
3
- * Declared-surface coverage for EMILIA Gate.
4
- *
5
- * `gated` is intentionally hard to earn: a fresh workload attestation proves
6
- * the expected Gate is running, and a separately pinned active probe proves the
7
- * named surface refuses a canary action without a receipt. A passive witness by
8
- * itself is reported as `witness_only`, never as enforcement.
9
- */
10
- import crypto from 'node:crypto';
11
- import { canonicalize, hashCanonical } from './execution-binding.js';
12
- import { verifyDeploymentAttestation, deploymentProfileDigest } from './deployment-attestation.js';
13
- import {
14
- NETWORK_WITNESS_EVENTS,
15
- acceptNetworkWitnessStatement,
16
- networkWitnessDigest,
17
- validateTrustedNetworkWitnessAcceptance,
18
- } from './network-witness.js';
19
- import { strictJsonGate } from './strict-json.js';
20
-
21
- export const COVERAGE_INVENTORY_VERSION = 'EP-GATE-COVERAGE-INVENTORY-v1';
22
- export const COVERAGE_REPORT_VERSION = 'EP-GATE-COVERAGE-REPORT-v1';
23
- export const ENFORCEMENT_PROBE_VERSION = 'EP-GATE-ENFORCEMENT-PROBE-v1';
24
- export const COVERAGE_STATES = Object.freeze(['gated', 'witness_only', 'ungated', 'stale', 'unknown']);
25
- export const PROBE_RESULTS = Object.freeze(['blocked_without_receipt', 'executed_without_receipt', 'indeterminate']);
26
-
27
- const PROBE_DOMAIN = `${ENFORCEMENT_PROBE_VERSION}\0`;
28
- const MAX_EVIDENCE_ITEMS = 50_000;
29
- const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
30
- const RFC3339 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?Z$/;
31
-
32
- function isPlainObject(value) {
33
- if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
34
- const prototype = Object.getPrototypeOf(value);
35
- return prototype === Object.prototype || prototype === null;
36
- }
37
-
38
- function string(value, max = 512) {
39
- return typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
40
- }
41
-
42
- function digest(value) {
43
- return typeof value === 'string' && DIGEST_RE.test(value);
44
- }
45
-
46
- function exactKeys(value, allowed) {
47
- return isPlainObject(value) && Object.keys(value).every((key) => allowed.has(key));
48
- }
49
-
50
- function strictInstantMs(value) {
51
- if (typeof value !== 'string') return NaN;
52
- const match = value.match(RFC3339);
53
- if (!match) return NaN;
54
- const [, y, m, d, h, min, s] = match;
55
- const calendar = new Date(0);
56
- calendar.setUTCFullYear(Number(y), Number(m) - 1, Number(d));
57
- calendar.setUTCHours(Number(h), Number(min), Number(s), 0);
58
- if (calendar.toISOString().slice(0, 19) !== `${y}-${m}-${d}T${h}:${min}:${s}`) return NaN;
59
- const parsed = Date.parse(value);
60
- return Number.isFinite(parsed) ? parsed : NaN;
61
- }
62
-
63
- function sha256(bytes) {
64
- return crypto.createHash('sha256').update(bytes).digest('hex');
65
- }
66
-
67
- function decodeBase64Url(value, maxBytes) {
68
- if (typeof value !== 'string' || value.length === 0 || !/^[A-Za-z0-9_-]+$/.test(value)) return null;
69
- try {
70
- const bytes = Buffer.from(value, 'base64url');
71
- if (bytes.length === 0 || bytes.length > maxBytes || bytes.toString('base64url') !== value) return null;
72
- return bytes;
73
- } catch { return null; }
74
- }
75
-
76
- function keyIdFor(key) {
77
- const der = crypto.createPublicKey(key).export({ type: 'spki', format: 'der' });
78
- return `ep:probe-key:sha256:${sha256(der).slice(0, 16)}`;
79
- }
80
-
81
- function probeBytes(body) {
82
- return Buffer.from(PROBE_DOMAIN + canonicalize(body), 'utf8');
83
- }
84
-
85
- function probeDigest(statement) {
86
- const { signature: _signature, ...body } = statement;
87
- return `sha256:${sha256(probeBytes(body))}`;
88
- }
89
-
90
- function validateSurface(surface) {
91
- if (!exactKeys(surface, new Set([
92
- 'surface_id', 'action_family', 'gate_id', 'environment_id', 'deployment_profile_hash',
93
- 'probe_action_digest', 'required', 'witness',
94
- ]))) return 'surface_shape_invalid';
95
- for (const field of ['surface_id', 'action_family', 'gate_id', 'environment_id']) {
96
- if (!string(surface[field])) return `surface_${field}_invalid`;
97
- }
98
- if (!digest(surface.deployment_profile_hash) || !digest(surface.probe_action_digest)) {
99
- return 'surface_digest_invalid';
100
- }
101
- if (surface.required !== true && surface.required !== false) return 'surface_required_invalid';
102
- if (surface.witness !== undefined) {
103
- if (!exactKeys(surface.witness, new Set(['witness_id', 'capture_point_id', 'event', 'required']))) {
104
- return 'surface_witness_shape_invalid';
105
- }
106
- if (!string(surface.witness.witness_id) || !string(surface.witness.capture_point_id)
107
- || !NETWORK_WITNESS_EVENTS.includes(surface.witness.event)
108
- || (surface.witness.required !== true && surface.witness.required !== false)) {
109
- return 'surface_witness_invalid';
110
- }
111
- }
112
- return null;
113
- }
114
-
115
- function validateInventory(inventory) {
116
- if (!exactKeys(inventory, new Set(['@version', 'inventory_id', 'surfaces']))) return 'inventory_shape_invalid';
117
- if (inventory['@version'] !== COVERAGE_INVENTORY_VERSION) return 'inventory_version_invalid';
118
- if (!string(inventory.inventory_id) || !Array.isArray(inventory.surfaces)
119
- || inventory.surfaces.length === 0 || inventory.surfaces.length > 10_000) return 'inventory_invalid';
120
- const ids = new Set();
121
- for (const surface of inventory.surfaces) {
122
- const invalid = validateSurface(surface);
123
- if (invalid) return invalid;
124
- if (ids.has(surface.surface_id)) return 'surface_id_duplicate';
125
- ids.add(surface.surface_id);
126
- }
127
- try { canonicalize(inventory); } catch { return 'inventory_canonicalization_invalid'; }
128
- return null;
129
- }
130
-
131
- export function coverageInventoryDigest(inventory) {
132
- const invalid = validateInventory(inventory);
133
- if (invalid) throw new TypeError(invalid);
134
- return `sha256:${hashCanonical(inventory)}`;
135
- }
136
-
137
- function validateProbeBody(body) {
138
- if (!exactKeys(body, new Set(['@version', 'probe', 'test']))) return 'probe_shape_invalid';
139
- if (body['@version'] !== ENFORCEMENT_PROBE_VERSION) return 'probe_version_invalid';
140
- if (!exactKeys(body.probe, new Set(['id', 'key_id']))) return 'probe_identity_shape_invalid';
141
- if (!string(body.probe.id) || !string(body.probe.key_id)) return 'probe_identity_invalid';
142
- if (!exactKeys(body.test, new Set([
143
- 'surface_id', 'gate_id', 'environment_id', 'action_family', 'action_digest',
144
- 'tested_at', 'nonce', 'result', 'response_status',
145
- ]))) return 'probe_test_shape_invalid';
146
- for (const field of ['surface_id', 'gate_id', 'environment_id', 'action_family', 'nonce']) {
147
- if (!string(body.test[field], 1024)) return `probe_${field}_invalid`;
148
- }
149
- if (!digest(body.test.action_digest) || !Number.isFinite(strictInstantMs(body.test.tested_at))) {
150
- return 'probe_binding_invalid';
151
- }
152
- if (!PROBE_RESULTS.includes(body.test.result)) return 'probe_result_invalid';
153
- if (!Number.isSafeInteger(body.test.response_status)
154
- || body.test.response_status < 100 || body.test.response_status > 599) return 'probe_status_invalid';
155
- if (body.test.result === 'blocked_without_receipt' && body.test.response_status !== 428) {
156
- return 'probe_block_status_invalid';
157
- }
158
- try { canonicalize(body); } catch { return 'probe_canonicalization_invalid'; }
159
- return null;
160
- }
161
-
162
- export function signEnforcementProbe(input, privateKey) {
163
- if (!privateKey) throw new TypeError('privateKey is required');
164
- const keyId = input?.key_id ?? keyIdFor(privateKey);
165
- const body = {
166
- '@version': ENFORCEMENT_PROBE_VERSION,
167
- probe: { id: input?.probe_id, key_id: keyId },
168
- test: {
169
- surface_id: input?.surface_id,
170
- gate_id: input?.gate_id,
171
- environment_id: input?.environment_id,
172
- action_family: input?.action_family,
173
- action_digest: input?.action_digest,
174
- tested_at: input?.tested_at,
175
- nonce: input?.nonce,
176
- result: input?.result,
177
- response_status: input?.response_status,
178
- },
179
- };
180
- const invalid = validateProbeBody(body);
181
- if (invalid) throw new TypeError(invalid);
182
- const statementDigest = probeDigest(body);
183
- return Object.freeze({
184
- ...body,
185
- signature: Object.freeze({
186
- algorithm: 'Ed25519',
187
- key_id: keyId,
188
- statement_digest: statementDigest,
189
- signature_b64u: crypto.sign(null, probeBytes(body), privateKey).toString('base64url'),
190
- }),
191
- });
192
- }
193
-
194
- /** Duplicate-key-safe parser for an untrusted serialized probe artifact. */
195
- export function parseEnforcementProbeStatement(raw, { maxBytes = 64 * 1024 } = {}) {
196
- if (typeof raw !== 'string' || !Number.isSafeInteger(maxBytes) || maxBytes < 1
197
- || Buffer.byteLength(raw, 'utf8') > maxBytes) return null;
198
- if (!strictJsonGate(raw).ok) return null;
199
- try {
200
- const parsed = JSON.parse(raw);
201
- return isPlainObject(parsed) ? parsed : null;
202
- } catch { return null; }
203
- }
204
-
205
- function findProbePin(pins, probeId, keyId) {
206
- if (!Array.isArray(pins)) return null;
207
- return pins.find((pin) => isPlainObject(pin) && pin.probe_id === probeId && pin.key_id === keyId) ?? null;
208
- }
209
-
210
- export function verifyEnforcementProbe(statement, options = {}) {
211
- const refuse = (reason) => ({ accepted: false, verified: false, reason });
212
- try {
213
- if (!exactKeys(statement, new Set(['@version', 'probe', 'test', 'signature']))) return refuse('probe_shape_invalid');
214
- const { signature, ...body } = statement;
215
- const invalid = validateProbeBody(body);
216
- if (invalid) return refuse(invalid);
217
- if (!exactKeys(signature, new Set(['algorithm', 'key_id', 'statement_digest', 'signature_b64u']))
218
- || signature.algorithm !== 'Ed25519' || signature.key_id !== body.probe.key_id
219
- || !digest(signature.statement_digest) || !string(signature.signature_b64u, 512)) {
220
- return refuse('probe_signature_envelope_invalid');
221
- }
222
- const pin = findProbePin(options.pinnedProbes, body.probe.id, body.probe.key_id);
223
- if (!pin || !string(pin.public_key, 4096)) return refuse('probe_key_unpinned');
224
- if (!Array.isArray(pin.surface_ids) || !pin.surface_ids.includes(body.test.surface_id)) {
225
- return refuse('probe_surface_unpinned');
226
- }
227
- const expected = options.expectedSurface;
228
- if (isPlainObject(expected)) {
229
- if (body.test.surface_id !== expected.surface_id || body.test.gate_id !== expected.gate_id
230
- || body.test.environment_id !== expected.environment_id
231
- || body.test.action_family !== expected.action_family
232
- || body.test.action_digest !== expected.probe_action_digest) return refuse('probe_context_mismatch');
233
- }
234
- const now = options.now === undefined ? Date.now() : Number(options.now);
235
- const maxAgeSec = options.maxAgeSec === undefined ? 300 : options.maxAgeSec;
236
- const maxFutureSkewSec = options.maxFutureSkewSec === undefined ? 30 : options.maxFutureSkewSec;
237
- if (!Number.isFinite(now) || !Number.isSafeInteger(maxAgeSec) || maxAgeSec < 0
238
- || !Number.isSafeInteger(maxFutureSkewSec) || maxFutureSkewSec < 0) return refuse('probe_profile_invalid');
239
- const testedAt = strictInstantMs(body.test.tested_at);
240
- if (testedAt > now + maxFutureSkewSec * 1000) return refuse('probe_from_future');
241
- if (now - testedAt > maxAgeSec * 1000) return refuse('probe_stale');
242
- const computed = probeDigest(body);
243
- if (computed !== signature.statement_digest) return refuse('probe_digest_mismatch');
244
- let key;
245
- try {
246
- const keyBytes = decodeBase64Url(pin.public_key, 4096);
247
- if (!keyBytes) throw new TypeError('invalid base64url key');
248
- key = crypto.createPublicKey({ key: keyBytes, type: 'spki', format: 'der' });
249
- if (key.asymmetricKeyType !== 'ed25519') throw new TypeError('probe key must be Ed25519');
250
- } catch { return refuse('probe_pinned_key_invalid'); }
251
- const signatureBytes = decodeBase64Url(signature.signature_b64u, 64);
252
- if (!signatureBytes || signatureBytes.length !== 64
253
- || !crypto.verify(null, probeBytes(body), key, signatureBytes)) {
254
- return refuse('probe_signature_invalid');
255
- }
256
- return {
257
- accepted: true,
258
- verified: true,
259
- reason: null,
260
- statement_digest: computed,
261
- tested_at: body.test.tested_at,
262
- result: body.test.result,
263
- response_status: body.test.response_status,
264
- nonce: body.test.nonce,
265
- surface_id: body.test.surface_id,
266
- action_digest: body.test.action_digest,
267
- gate_id: body.test.gate_id,
268
- environment_id: body.test.environment_id,
269
- action_family: body.test.action_family,
270
- probe_id: body.probe.id,
271
- };
272
- } catch {
273
- return refuse('hostile_probe_refused');
274
- }
275
- }
276
-
277
- function deploymentKey(gateId, environmentId, profileHash) {
278
- return `${gateId}\0${environmentId}\0${profileHash}`;
279
- }
280
-
281
- function probeMatchesSurface(probe, surface) {
282
- return probe?.surface_id === surface.surface_id
283
- && probe?.gate_id === surface.gate_id
284
- && probe?.environment_id === surface.environment_id
285
- && probe?.action_family === surface.action_family
286
- && probe?.action_digest === surface.probe_action_digest;
287
- }
288
-
289
- function reportHash(report) {
290
- return `sha256:${hashCanonical(report)}`;
291
- }
292
-
293
- function canonicalSnapshot(value) {
294
- return JSON.parse(canonicalize(value));
295
- }
296
-
297
- function immutableCanonicalSnapshot(value) {
298
- const snapshot = canonicalSnapshot(value);
299
- const stack = [snapshot];
300
- while (stack.length) {
301
- const current = stack.pop();
302
- if (current && typeof current === 'object') {
303
- for (const child of Object.values(current)) stack.push(child);
304
- Object.freeze(current);
305
- }
306
- }
307
- return snapshot;
308
- }
309
-
310
- function snapshotExpectedProbeNonces(value, inventory) {
311
- if (value === undefined || value === null) return null;
312
- if (value instanceof Map) {
313
- const selected = Object.create(null);
314
- for (const surface of inventory.surfaces) {
315
- if (value.has(surface.surface_id)) selected[surface.surface_id] = value.get(surface.surface_id);
316
- }
317
- return immutableCanonicalSnapshot(selected);
318
- }
319
- return immutableCanonicalSnapshot(value);
320
- }
321
-
322
- function snapshotEvidenceList(items) {
323
- const snapshots = [];
324
- for (const item of items) {
325
- try { snapshots.push(canonicalSnapshot(item)); } catch { /* invalid evidence is ignored */ }
326
- }
327
- return snapshots;
328
- }
329
-
330
- function witnessCoverageKey(result) {
331
- if (!string(result?.witness_id) || !string(result?.capture_point_id)
332
- || !NETWORK_WITNESS_EVENTS.includes(result?.event) || !digest(result?.action_digest)) return null;
333
- return `${result.witness_id}\0${result.capture_point_id}\0${result.event}\0${result.action_digest}`;
334
- }
335
-
336
- function witnessEvidenceKey(result) {
337
- if (!string(result?.witness_id) || !string(result?.capture_point_id)
338
- || !Number.isSafeInteger(result?.sequence) || result.sequence < 0) return null;
339
- return `${result.witness_id}\0${result.capture_point_id}\0${result.sequence}`;
340
- }
341
-
342
- function verifiedWitnessPositionKey(result) {
343
- if (result?.verified !== true || !digest(result?.statement_digest)
344
- || !string(result?.witness_id) || !string(result?.capture_point_id)
345
- || !Number.isSafeInteger(result?.sequence) || result.sequence < 0
346
- || result.stream_id !== `${result.witness_id}\0${result.capture_point_id}`) return null;
347
- return `${result.stream_id}\0${result.sequence}`;
348
- }
349
-
350
- /**
351
- * Evaluate coverage of a relying-party-declared inventory. Inventory
352
- * completeness remains an explicit external assumption and is never inferred.
353
- */
354
- export async function evaluateGateCoverage(input = {}, options = {}) {
355
- let inventory;
356
- let invalid;
357
- try {
358
- invalid = validateInventory(input.inventory);
359
- inventory = invalid ? input.inventory : immutableCanonicalSnapshot(input.inventory);
360
- } catch {
361
- invalid = 'inventory_hostile_input';
362
- inventory = null;
363
- }
364
- if (invalid) {
365
- return {
366
- '@version': COVERAGE_REPORT_VERSION,
367
- complete: false,
368
- reason: invalid,
369
- inventory_hash: null,
370
- surfaces: [],
371
- counts: Object.fromEntries(COVERAGE_STATES.map((state) => [state, 0])),
372
- };
373
- }
374
- let now;
375
- let attestationVerifiers;
376
- let pinnedProbes;
377
- let pinnedWitnesses;
378
- let expectedProbeNonces;
379
- let probeMaxAgeSec;
380
- let witnessMaxAgeSec;
381
- let maxFutureSkewSec;
382
- let witnessSequenceStore;
383
- let allowEphemeralWitnessStore;
384
- let trustedWitnessAcceptances;
385
- try {
386
- now = options.now === undefined ? Date.now() : Number(options.now);
387
- attestationVerifiers = options.attestationVerifiers;
388
- pinnedProbes = options.pinnedProbes === undefined
389
- ? undefined
390
- : immutableCanonicalSnapshot(options.pinnedProbes);
391
- pinnedWitnesses = options.pinnedWitnesses === undefined
392
- ? undefined
393
- : immutableCanonicalSnapshot(options.pinnedWitnesses);
394
- expectedProbeNonces = snapshotExpectedProbeNonces(options.expectedProbeNonces, inventory);
395
- probeMaxAgeSec = options.probeMaxAgeSec;
396
- witnessMaxAgeSec = options.witnessMaxAgeSec;
397
- maxFutureSkewSec = options.maxFutureSkewSec;
398
- witnessSequenceStore = options.witnessSequenceStore;
399
- allowEphemeralWitnessStore = options.allowEphemeralWitnessStore === true;
400
- const trustedWitnessInput = options.trustedWitnessAcceptances;
401
- if (trustedWitnessInput !== undefined
402
- && (!Array.isArray(trustedWitnessInput) || trustedWitnessInput.length > MAX_EVIDENCE_ITEMS)) {
403
- throw new TypeError('trusted witness acceptance collection is invalid');
404
- }
405
- trustedWitnessAcceptances = [];
406
- for (const acceptance of Array.isArray(trustedWitnessInput) ? trustedWitnessInput : []) {
407
- trustedWitnessAcceptances.push(immutableCanonicalSnapshot(acceptance));
408
- }
409
- Object.freeze(trustedWitnessAcceptances);
410
- } catch {
411
- return {
412
- '@version': COVERAGE_REPORT_VERSION,
413
- complete: false,
414
- reason: 'coverage_rp_configuration_invalid',
415
- inventory_hash: coverageInventoryDigest(inventory),
416
- surfaces: [],
417
- counts: Object.fromEntries(COVERAGE_STATES.map((state) => [state, 0])),
418
- };
419
- }
420
- if (!Number.isFinite(now)) throw new TypeError('coverage now must be finite');
421
-
422
- const lists = {};
423
- for (const field of ['deployments', 'probes', 'witnesses']) {
424
- let value;
425
- try { value = input[field]; } catch { value = null; }
426
- if (value !== undefined && (!Array.isArray(value) || value.length > MAX_EVIDENCE_ITEMS)) {
427
- return {
428
- '@version': COVERAGE_REPORT_VERSION,
429
- complete: false,
430
- reason: `coverage_${field}_limit_exceeded`,
431
- inventory_hash: coverageInventoryDigest(inventory),
432
- surfaces: [],
433
- counts: Object.fromEntries(COVERAGE_STATES.map((state) => [state, 0])),
434
- };
435
- }
436
- lists[field] = Array.isArray(value) ? value.slice() : [];
437
- }
438
- if (lists.witnesses.length + trustedWitnessAcceptances.length > MAX_EVIDENCE_ITEMS) {
439
- return {
440
- '@version': COVERAGE_REPORT_VERSION,
441
- complete: false,
442
- reason: 'coverage_witness_evidence_limit_exceeded',
443
- inventory_hash: coverageInventoryDigest(inventory),
444
- surfaces: [],
445
- counts: Object.fromEntries(COVERAGE_STATES.map((state) => [state, 0])),
446
- };
447
- }
448
-
449
- // Signed JSON artifacts are copied before the first async verifier call so
450
- // a shared caller cannot rewrite the evidence set mid-evaluation.
451
- const probes = snapshotEvidenceList(lists.probes);
452
- const witnesses = snapshotEvidenceList(lists.witnesses);
453
- const deployments = [];
454
- for (const candidate of lists.deployments) {
455
- try {
456
- if (!isPlainObject(candidate)) continue;
457
- deployments.push({ profile: canonicalSnapshot(candidate.profile), evidence: candidate.evidence });
458
- } catch { /* invalid candidate is ignored */ }
459
- }
460
-
461
- const deploymentResults = new Map();
462
- for (const candidate of deployments) {
463
- let candidateHash;
464
- try { candidateHash = deploymentProfileDigest(candidate.profile); } catch { continue; }
465
- if (!inventory.surfaces.some((surface) => surface.deployment_profile_hash === candidateHash)) continue;
466
- const result = await verifyDeploymentAttestation(candidate.evidence, {
467
- profile: candidate.profile,
468
- verifiers: attestationVerifiers,
469
- now,
470
- });
471
- const wrapped = {
472
- ...result,
473
- gate_id: result.gate_id,
474
- environment_id: result.environment_id,
475
- profile_hash: candidateHash,
476
- };
477
- const key = deploymentKey(wrapped.gate_id, wrapped.environment_id, wrapped.profile_hash);
478
- const previous = deploymentResults.get(key);
479
- if (!previous || wrapped.accepted === true
480
- || strictInstantMs(wrapped.issued_at) > strictInstantMs(previous.issued_at)) {
481
- deploymentResults.set(key, wrapped);
482
- }
483
- }
484
-
485
- const freshProbes = new Map();
486
- const historicalProbes = new Map();
487
- for (const statement of probes) {
488
- const fresh = verifyEnforcementProbe(statement, {
489
- pinnedProbes,
490
- maxAgeSec: probeMaxAgeSec,
491
- maxFutureSkewSec,
492
- now,
493
- });
494
- if (fresh.accepted) {
495
- if (!freshProbes.has(fresh.surface_id)) freshProbes.set(fresh.surface_id, []);
496
- freshProbes.get(fresh.surface_id).push(fresh);
497
- }
498
- const historical = verifyEnforcementProbe(statement, {
499
- pinnedProbes,
500
- maxAgeSec: Number.MAX_SAFE_INTEGER,
501
- maxFutureSkewSec,
502
- now,
503
- });
504
- if (historical.accepted) {
505
- if (!historicalProbes.has(historical.surface_id)) historicalProbes.set(historical.surface_id, []);
506
- historicalProbes.get(historical.surface_id).push(historical);
507
- }
508
- }
509
-
510
- const witnessResults = new Map();
511
- const witnessRefusals = new Map();
512
- const acceptedWitnessEvidence = new Map();
513
- const equivocatedWitnessEvidence = new Map();
514
- const verifiedWitnessEvidence = new Map();
515
- const trustedByDigest = new Map();
516
- const indexWitnessAcceptance = (coverageKey, evidenceKey, result) => {
517
- let bucket = witnessResults.get(coverageKey);
518
- if (!bucket) {
519
- bucket = new Map();
520
- witnessResults.set(coverageKey, bucket);
521
- }
522
- bucket.set(evidenceKey, result);
523
- };
524
- const recordWitnessRefusal = (coverageKey, result) => {
525
- const previous = witnessRefusals.get(coverageKey);
526
- if (!previous || result.reason === 'sequence_equivocation') {
527
- witnessRefusals.set(coverageKey, result);
528
- }
529
- };
530
- const removeWitnessAcceptance = (evidenceKey) => {
531
- const previous = acceptedWitnessEvidence.get(evidenceKey) ?? null;
532
- if (!previous) return null;
533
- acceptedWitnessEvidence.delete(evidenceKey);
534
- const coverageKey = witnessCoverageKey(previous);
535
- const bucket = witnessResults.get(coverageKey);
536
- bucket?.delete(evidenceKey);
537
- if (bucket?.size === 0) witnessResults.delete(coverageKey);
538
- return previous;
539
- };
540
- const recordWitnessEquivocation = (result, evidenceKey, coverageKey, conflictingResult = null) => {
541
- const previous = removeWitnessAcceptance(evidenceKey);
542
- equivocatedWitnessEvidence.set(evidenceKey, result);
543
- recordWitnessRefusal(coverageKey, result);
544
- for (const candidate of [previous, conflictingResult]) {
545
- const candidateCoverageKey = witnessCoverageKey(candidate);
546
- if (candidateCoverageKey) {
547
- recordWitnessRefusal(candidateCoverageKey, result);
548
- }
549
- }
550
- };
551
- const recordWitnessResult = (result) => {
552
- const coverageKey = witnessCoverageKey(result);
553
- const evidenceKey = witnessEvidenceKey(result);
554
- if (!coverageKey || !evidenceKey) return;
555
- const verifiedPositionKey = verifiedWitnessPositionKey(result);
556
- if (verifiedPositionKey) {
557
- const previousVerified = verifiedWitnessEvidence.get(verifiedPositionKey);
558
- if (previousVerified?.statement_digest !== undefined
559
- && previousVerified.statement_digest !== result.statement_digest) {
560
- recordWitnessEquivocation(
561
- { ...result, accepted: false, consumed: false, reason: 'sequence_equivocation' },
562
- evidenceKey,
563
- coverageKey,
564
- previousVerified,
565
- );
566
- return;
567
- }
568
- if (!previousVerified) verifiedWitnessEvidence.set(verifiedPositionKey, result);
569
- }
570
- if (result.reason === 'sequence_equivocation') {
571
- recordWitnessEquivocation(result, evidenceKey, coverageKey);
572
- return;
573
- }
574
- if (result.accepted === true) {
575
- const equivocation = equivocatedWitnessEvidence.get(evidenceKey);
576
- if (equivocation) {
577
- recordWitnessRefusal(coverageKey, equivocation);
578
- return;
579
- }
580
- const previous = acceptedWitnessEvidence.get(evidenceKey);
581
- if (previous && previous.statement_digest !== result.statement_digest) {
582
- recordWitnessEquivocation({ ...result, accepted: false, reason: 'sequence_equivocation' }, evidenceKey, coverageKey);
583
- return;
584
- }
585
- acceptedWitnessEvidence.set(evidenceKey, result);
586
- indexWitnessAcceptance(coverageKey, evidenceKey, result);
587
- } else if (string(result.reason)) {
588
- recordWitnessRefusal(coverageKey, result);
589
- }
590
- };
591
- for (const acceptance of trustedWitnessAcceptances) {
592
- const trusted = validateTrustedNetworkWitnessAcceptance(acceptance, {
593
- maxAgeSec: witnessMaxAgeSec,
594
- maxFutureSkewSec,
595
- now,
596
- allowEphemeralStore: allowEphemeralWitnessStore,
597
- });
598
- recordWitnessResult(trusted);
599
- if (trusted.accepted === true) trustedByDigest.set(trusted.statement_digest, trusted);
600
- }
601
- for (const statement of witnesses) {
602
- let previouslyAccepted = null;
603
- try { previouslyAccepted = trustedByDigest.get(networkWitnessDigest(statement)) ?? null; } catch { /* ingest below */ }
604
- if (previouslyAccepted) {
605
- recordWitnessResult(previouslyAccepted);
606
- continue;
607
- }
608
- const accepted = await acceptNetworkWitnessStatement(statement, {
609
- pinnedWitnesses,
610
- maxAgeSec: witnessMaxAgeSec,
611
- maxFutureSkewSec,
612
- now,
613
- sequenceStore: witnessSequenceStore,
614
- allowEphemeralStore: allowEphemeralWitnessStore,
615
- });
616
- recordWitnessResult(accepted);
617
- }
618
-
619
- const rows = [];
620
- for (const surface of inventory.surfaces) {
621
- const deployment = deploymentResults.get(deploymentKey(
622
- surface.gate_id, surface.environment_id, surface.deployment_profile_hash,
623
- )) ?? null;
624
- const matchingProbes = (freshProbes.get(surface.surface_id) ?? []).filter((probe) => probeMatchesSurface(probe, surface));
625
- const bypass = matchingProbes.find((probe) => probe.result === 'executed_without_receipt');
626
- const expectedProbeNonce = isPlainObject(expectedProbeNonces)
627
- && Object.hasOwn(expectedProbeNonces, surface.surface_id)
628
- ? expectedProbeNonces[surface.surface_id]
629
- : null;
630
- const block = string(expectedProbeNonce, 1024)
631
- ? matchingProbes.find((probe) => probe.result === 'blocked_without_receipt'
632
- && probe.nonce === expectedProbeNonce)
633
- : null;
634
-
635
- let witness = null;
636
- let witnessRefusal = null;
637
- if (surface.witness) {
638
- const key = `${surface.witness.witness_id}\0${surface.witness.capture_point_id}\0${surface.witness.event}\0${surface.probe_action_digest}`;
639
- const accepted = witnessResults.get(key);
640
- witness = accepted?.values().next().value ?? null;
641
- witnessRefusal = witnessRefusals.get(key) ?? null;
642
- }
643
-
644
- let state = 'unknown';
645
- let reason = 'active_enforcement_not_proven';
646
- if (bypass) {
647
- state = 'ungated';
648
- reason = 'probe_executed_without_receipt';
649
- } else if (deployment?.accepted === true && block) {
650
- state = 'gated';
651
- reason = 'attested_gate_and_refusal_probe';
652
- } else if (witness) {
653
- state = 'witness_only';
654
- reason = 'traffic_observed_without_active_enforcement_proof';
655
- } else {
656
- const staleInput = (historicalProbes.get(surface.surface_id) ?? [])
657
- .some((probe) => probeMatchesSurface(probe, surface));
658
- if (staleInput || (deployment && deployment.verdict === 'refuse_stale')) {
659
- state = 'stale';
660
- reason = 'coverage_evidence_stale';
661
- }
662
- }
663
- const witnessOk = !surface.witness?.required || Boolean(witness);
664
- rows.push({
665
- surface_id: surface.surface_id,
666
- action_family: surface.action_family,
667
- required: surface.required,
668
- state,
669
- reason,
670
- deployment_attested: deployment?.accepted === true,
671
- refusal_probe_verified: Boolean(block),
672
- bypass_probe_verified: Boolean(bypass),
673
- probe_nonce_verified: Boolean(block),
674
- witness_required: surface.witness?.required === true,
675
- witness_verified: Boolean(witness),
676
- witness_acceptance_reason: witness
677
- ? null
678
- : (witnessRefusal?.reason ?? (surface.witness?.required === true ? 'witness_not_accepted' : null)),
679
- complete: state === 'gated' && witnessOk,
680
- });
681
- }
682
-
683
- const counts = Object.fromEntries(COVERAGE_STATES.map((state) => [state, rows.filter((row) => row.state === state).length]));
684
- const requiredRows = rows.filter((row) => row.required);
685
- const completeRequired = requiredRows.filter((row) => row.complete).length;
686
- const body = {
687
- '@version': COVERAGE_REPORT_VERSION,
688
- generated_at: new Date(now).toISOString(),
689
- inventory_id: inventory.inventory_id,
690
- inventory_hash: coverageInventoryDigest(inventory),
691
- complete: requiredRows.length > 0 && completeRequired === requiredRows.length,
692
- declared_required_surfaces: requiredRows.length,
693
- complete_required_surfaces: completeRequired,
694
- // Canonical EP JSON permits safe integers only. Basis points keep the
695
- // report deterministic and signable without binary-float ambiguity.
696
- declared_coverage_bps: requiredRows.length === 0
697
- ? 0
698
- : Math.floor((completeRequired * 10_000) / requiredRows.length),
699
- counts,
700
- surfaces: rows,
701
- limitations: [
702
- 'Coverage is measured only over the relying-party-declared inventory; omitted routes remain an external inventory risk.',
703
- 'Attestation proves expected workload measurements and the active probe proves one tested refusal path; neither proves all physical bypasses are impossible.',
704
- 'A gated row requires the exact relying-party challenge nonce for that evaluation; a replayed signed block result cannot establish current coverage.',
705
- 'A network witness counts only after durable sequence acceptance, or from a relying-party-trusted acceptance result; it never establishes enforcement by itself.',
706
- ],
707
- };
708
- return Object.freeze({ ...body, report_hash: reportHash(body) });
709
- }
710
-
711
- export default {
712
- COVERAGE_INVENTORY_VERSION,
713
- COVERAGE_REPORT_VERSION,
714
- ENFORCEMENT_PROBE_VERSION,
715
- COVERAGE_STATES,
716
- PROBE_RESULTS,
717
- coverageInventoryDigest,
718
- parseEnforcementProbeStatement,
719
- signEnforcementProbe,
720
- verifyEnforcementProbe,
721
- evaluateGateCoverage,
722
- };
2
+ // Compatibility entry point for the TypeScript coverage module.
3
+ export * from './dist/coverage.js';
4
+ export { default } from './dist/coverage.js';