@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,313 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Deterministic evidence-completeness decision for settlement workflows.
4
+ *
5
+ * This is not a payment rail and does not decide legal liability. It answers a
6
+ * narrower question: does a relying-party-pinned profile have every verified,
7
+ * digest-joined artifact it required before its own settlement system acts?
8
+ */
9
+ import { canonicalize, hashCanonical } from './execution-binding.js';
10
+ import {
11
+ NETWORK_WITNESS_EVENTS,
12
+ acceptNetworkWitnessStatement,
13
+ networkWitnessDigest,
14
+ validateTrustedNetworkWitnessAcceptance,
15
+ } from './network-witness.js';
16
+
17
+ export const SETTLEMENT_PROFILE_VERSION = 'EP-GATE-SETTLEMENT-PROFILE-v1';
18
+ export const SETTLEMENT_RESULT_VERSION = 'EP-GATE-SETTLEMENT-RESULT-v1';
19
+ export const SETTLEMENT_VERDICTS = Object.freeze([
20
+ 'eligible',
21
+ 'refuse_profile_invalid',
22
+ 'refuse_authorization',
23
+ 'refuse_execution',
24
+ 'refuse_witness',
25
+ 'refuse_outcome',
26
+ 'refuse_coverage',
27
+ 'refuse_binding',
28
+ ]);
29
+
30
+ const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
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 exactKeys(value, allowed) {
39
+ return isPlainObject(value) && Object.keys(value).every((key) => allowed.has(key));
40
+ }
41
+
42
+ function string(value, max = 512) {
43
+ return typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
44
+ }
45
+
46
+ function digest(value) {
47
+ return typeof value === 'string' && DIGEST_RE.test(value);
48
+ }
49
+
50
+ function canonicalSnapshot(value) {
51
+ return JSON.parse(canonicalize(value));
52
+ }
53
+
54
+ function validateProfile(profile) {
55
+ if (!exactKeys(profile, new Set([
56
+ '@version', 'profile_id', 'require_witness', 'require_outcome', 'require_coverage',
57
+ 'required_witness_event', 'required_witness_id', 'required_capture_point_id',
58
+ 'required_coverage_state', 'required_surface_id',
59
+ ]))) return 'profile_shape_invalid';
60
+ if (profile['@version'] !== SETTLEMENT_PROFILE_VERSION || !string(profile.profile_id)) return 'profile_identity_invalid';
61
+ for (const field of ['require_witness', 'require_outcome', 'require_coverage']) {
62
+ if (profile[field] !== true && profile[field] !== false) return `profile_${field}_invalid`;
63
+ }
64
+ if (profile.require_witness && (!string(profile.required_witness_event)
65
+ || !NETWORK_WITNESS_EVENTS.includes(profile.required_witness_event)
66
+ || !string(profile.required_witness_id) || !string(profile.required_capture_point_id))) {
67
+ return 'profile_witness_binding_invalid';
68
+ }
69
+ if (!profile.require_witness && (profile.required_witness_event !== undefined
70
+ || profile.required_witness_id !== undefined || profile.required_capture_point_id !== undefined)) {
71
+ return 'profile_witness_fields_forbidden';
72
+ }
73
+ if (profile.require_coverage && (profile.required_coverage_state !== 'gated'
74
+ || !string(profile.required_surface_id))) return 'profile_coverage_binding_invalid';
75
+ if (!profile.require_coverage && (profile.required_coverage_state !== undefined
76
+ || profile.required_surface_id !== undefined)) return 'profile_coverage_fields_forbidden';
77
+ try { canonicalize(profile); } catch { return 'profile_canonicalization_invalid'; }
78
+ return null;
79
+ }
80
+
81
+ export function settlementProfileDigest(profile) {
82
+ const invalid = validateProfile(profile);
83
+ if (invalid) throw new TypeError(invalid);
84
+ return `sha256:${hashCanonical(profile)}`;
85
+ }
86
+
87
+ function refused(verdict, reason, profileHash, checks, actionDigest = null) {
88
+ const body = {
89
+ '@version': SETTLEMENT_RESULT_VERSION,
90
+ verdict,
91
+ eligible: false,
92
+ reason,
93
+ profile_hash: profileHash,
94
+ action_digest: actionDigest,
95
+ checks,
96
+ limitations: [
97
+ 'This result is evidence-completeness input to a relying party; it is not a legal settlement instruction or warranty.',
98
+ 'A valid evidence bundle does not establish physical truth beyond the separately verified outcome source.',
99
+ ],
100
+ };
101
+ return Object.freeze({ ...body, result_hash: `sha256:${hashCanonical(body)}` });
102
+ }
103
+
104
+ async function invokeVerifier(verifier, artifact, context) {
105
+ if (typeof verifier !== 'function') return { accepted: false, reason: 'pinned_verifier_missing' };
106
+ try {
107
+ const result = await verifier(artifact, Object.freeze({ ...context }));
108
+ if (!isPlainObject(result)) return { accepted: false, reason: 'verifier_result_invalid' };
109
+ return canonicalSnapshot(result);
110
+ } catch {
111
+ return { accepted: false, reason: 'pinned_verifier_error' };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Evaluate a raw evidence bundle. Authorization, execution, outcome, and
117
+ * coverage are interpreted only by verifier functions pinned in code by the
118
+ * relying party; no artifact may select its own verifier.
119
+ */
120
+ export async function evaluateSettlementEligibility(bundle = {}, options: {
121
+ profile?: Record<string, any>;
122
+ verifyAuthorization?: (...args: any[]) => any;
123
+ verifyExecution?: (...args: any[]) => any;
124
+ verifyOutcome?: (...args: any[]) => any;
125
+ verifyCoverage?: (...args: any[]) => any;
126
+ pinnedWitnesses?: any[];
127
+ trustedWitnessAcceptance?: Record<string, any>;
128
+ witnessSequenceStore?: Record<string, any>;
129
+ allowEphemeralWitnessStore?: boolean;
130
+ now?: number | (() => number);
131
+ witnessMaxAgeSec?: number;
132
+ maxFutureSkewSec?: number;
133
+ } = {}) {
134
+ let profileInput;
135
+ let profileInputInvalid = false;
136
+ try { profileInput = canonicalSnapshot(options.profile); } catch { profileInputInvalid = true; }
137
+ let verifyAuthorization;
138
+ let verifyExecution;
139
+ let verifyOutcome;
140
+ let verifyCoverage;
141
+ let pinnedWitnesses;
142
+ let trustedWitnessAcceptance = null;
143
+ let hasTrustedWitnessAcceptance = false;
144
+ let witnessSequenceStore;
145
+ let allowEphemeralWitnessStore = false;
146
+ let witnessNow;
147
+ let witnessMaxAgeSec;
148
+ let maxFutureSkewSec;
149
+ try {
150
+ verifyAuthorization = options.verifyAuthorization;
151
+ verifyExecution = options.verifyExecution;
152
+ verifyOutcome = options.verifyOutcome;
153
+ verifyCoverage = options.verifyCoverage;
154
+ pinnedWitnesses = options.pinnedWitnesses === undefined
155
+ ? undefined
156
+ : canonicalSnapshot(options.pinnedWitnesses);
157
+ hasTrustedWitnessAcceptance = Object.hasOwn(options, 'trustedWitnessAcceptance');
158
+ if (hasTrustedWitnessAcceptance) {
159
+ trustedWitnessAcceptance = canonicalSnapshot(options.trustedWitnessAcceptance);
160
+ }
161
+ witnessSequenceStore = options.witnessSequenceStore;
162
+ allowEphemeralWitnessStore = options.allowEphemeralWitnessStore === true;
163
+ witnessNow = options.now;
164
+ witnessMaxAgeSec = options.witnessMaxAgeSec;
165
+ maxFutureSkewSec = options.maxFutureSkewSec;
166
+ } catch {
167
+ pinnedWitnesses = [];
168
+ hasTrustedWitnessAcceptance = true;
169
+ trustedWitnessAcceptance = null;
170
+ }
171
+ let profile;
172
+ let invalid;
173
+ if (profileInputInvalid) {
174
+ invalid = 'profile_hostile_input';
175
+ profile = null;
176
+ } else {
177
+ invalid = validateProfile(profileInput);
178
+ profile = profileInput;
179
+ }
180
+ const baseChecks = {
181
+ profile: !invalid,
182
+ authorization: false,
183
+ execution: false,
184
+ witness: profile?.require_witness === false,
185
+ outcome: profile?.require_outcome === false,
186
+ coverage: profile?.require_coverage === false,
187
+ digest_join: false,
188
+ };
189
+ if (invalid) return refused('refuse_profile_invalid', invalid, null, baseChecks);
190
+ const profileHash = settlementProfileDigest(profile);
191
+ let evidence;
192
+ try {
193
+ evidence = JSON.parse(canonicalize(bundle));
194
+ } catch {
195
+ return refused('refuse_binding', 'evidence_bundle_not_canonical_json', profileHash, baseChecks);
196
+ }
197
+ const actionDigest = evidence.action_digest;
198
+ if (!digest(actionDigest)) {
199
+ return refused('refuse_binding', 'action_digest_invalid', profileHash, baseChecks);
200
+ }
201
+ const context = { action_digest: actionDigest, profile_hash: profileHash };
202
+
203
+ const authorization = await invokeVerifier(verifyAuthorization, evidence.authorization, context);
204
+ if (authorization.accepted !== true || authorization.action_digest !== actionDigest
205
+ || !digest(authorization.decision_digest)) {
206
+ return refused('refuse_authorization', authorization.reason ?? 'authorization_not_verified', profileHash, baseChecks, actionDigest);
207
+ }
208
+ baseChecks.authorization = true;
209
+
210
+ const execution = await invokeVerifier(verifyExecution, evidence.execution, {
211
+ ...context,
212
+ authorization_digest: authorization.decision_digest,
213
+ });
214
+ if (execution.accepted !== true || execution.outcome !== 'executed'
215
+ || execution.action_digest !== actionDigest || !digest(execution.execution_digest)) {
216
+ return refused('refuse_execution', execution.reason ?? 'execution_not_verified', profileHash, baseChecks, actionDigest);
217
+ }
218
+ baseChecks.execution = true;
219
+ if (execution.authorization_digest !== authorization.decision_digest) {
220
+ return refused('refuse_binding', 'execution_authorization_digest_mismatch', profileHash, baseChecks, actionDigest);
221
+ }
222
+
223
+ let witness: Record<string, any> | null = null;
224
+ if (profile.require_witness) {
225
+ const witnessOptions = {
226
+ expectedActionDigest: actionDigest,
227
+ expectedEvent: profile.required_witness_event,
228
+ maxAgeSec: witnessMaxAgeSec,
229
+ maxFutureSkewSec,
230
+ now: witnessNow,
231
+ allowEphemeralStore: allowEphemeralWitnessStore,
232
+ };
233
+ if (hasTrustedWitnessAcceptance) {
234
+ let expectedStatementDigest;
235
+ try { expectedStatementDigest = networkWitnessDigest(evidence.witness); } catch {
236
+ return refused('refuse_witness', 'witness_statement_digest_invalid', profileHash, baseChecks, actionDigest);
237
+ }
238
+ witness = validateTrustedNetworkWitnessAcceptance(trustedWitnessAcceptance, {
239
+ ...witnessOptions,
240
+ expectedStatementDigest,
241
+ });
242
+ } else {
243
+ witness = await acceptNetworkWitnessStatement(evidence.witness, {
244
+ ...witnessOptions,
245
+ pinnedWitnesses,
246
+ sequenceStore: witnessSequenceStore,
247
+ });
248
+ }
249
+ if (!witness || !witness.accepted) {
250
+ return refused('refuse_witness', witness?.reason ?? 'witness_not_verified', profileHash, baseChecks, actionDigest);
251
+ }
252
+ if (witness.witness_id !== profile.required_witness_id
253
+ || witness.capture_point_id !== profile.required_capture_point_id) {
254
+ return refused('refuse_binding', 'witness_capture_point_mismatch', profileHash, baseChecks, actionDigest);
255
+ }
256
+ baseChecks.witness = true;
257
+ }
258
+
259
+ let outcome: Record<string, any> | null = null;
260
+ if (profile.require_outcome) {
261
+ outcome = await invokeVerifier(verifyOutcome, evidence.outcome, {
262
+ ...context,
263
+ execution_digest: execution.execution_digest,
264
+ });
265
+ if (!outcome || outcome.accepted !== true || outcome.within_tolerance !== true
266
+ || outcome.action_digest !== actionDigest || !digest(outcome.outcome_digest)
267
+ || outcome.execution_digest !== execution.execution_digest) {
268
+ return refused('refuse_outcome', outcome?.reason ?? 'outcome_not_verified', profileHash, baseChecks, actionDigest);
269
+ }
270
+ baseChecks.outcome = true;
271
+ }
272
+
273
+ let coverage: Record<string, any> | null = null;
274
+ if (profile.require_coverage) {
275
+ coverage = await invokeVerifier(verifyCoverage, evidence.coverage, context);
276
+ if (!coverage || coverage.accepted !== true || coverage.state !== profile.required_coverage_state
277
+ || coverage.surface_id !== profile.required_surface_id || !digest(coverage.report_hash)) {
278
+ return refused('refuse_coverage', coverage?.reason ?? 'coverage_not_verified', profileHash, baseChecks, actionDigest);
279
+ }
280
+ baseChecks.coverage = true;
281
+ }
282
+
283
+ baseChecks.digest_join = true;
284
+ const body = {
285
+ '@version': SETTLEMENT_RESULT_VERSION,
286
+ verdict: 'eligible',
287
+ eligible: true,
288
+ reason: null,
289
+ profile_hash: profileHash,
290
+ action_digest: actionDigest,
291
+ evidence: {
292
+ authorization_digest: authorization.decision_digest,
293
+ execution_digest: execution.execution_digest,
294
+ ...(witness ? { witness_digest: witness.statement_digest } : {}),
295
+ ...(outcome ? { outcome_digest: outcome.outcome_digest } : {}),
296
+ ...(coverage ? { coverage_report_hash: coverage.report_hash, surface_id: coverage.surface_id } : {}),
297
+ },
298
+ checks: baseChecks,
299
+ limitations: [
300
+ 'Eligible means the relying party\'s pinned evidence profile was satisfied; the relying party still owns pricing, legal effect, and payment execution.',
301
+ 'The witness proves observation, not authorization or physical outcome; those are separate verified rows.',
302
+ ],
303
+ };
304
+ return Object.freeze({ ...body, result_hash: `sha256:${hashCanonical(body)}` });
305
+ }
306
+
307
+ export default {
308
+ SETTLEMENT_PROFILE_VERSION,
309
+ SETTLEMENT_RESULT_VERSION,
310
+ SETTLEMENT_VERDICTS,
311
+ settlementProfileDigest,
312
+ evaluateSettlementEligibility,
313
+ };
package/src/siem.ts ADDED
@@ -0,0 +1,247 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * EMILIA Gate — SIEM export of the evidence log (EP-GATE-SIEM-EXPORT-v1).
4
+ *
5
+ * Gate decisions must land where the SOC already looks: Splunk, Sentinel,
6
+ * Datadog. Two static, offline mappings over evidence-log entries:
7
+ * - OCSF (JSON object) for OCSF-native pipelines (Amazon Security Lake,
8
+ * Splunk CIM-OCSF, Sentinel ASIM ingestion);
9
+ * - CEF (single line) as the lowest-common-denominator syslog fallback.
10
+ * Pure functions: entry in, event out — no network, no wall clock. Every
11
+ * timestamp in the output comes from the entry itself, so a fixed entry maps
12
+ * to a byte-identical event on every call and every host.
13
+ *
14
+ * OCSF class choice — class_uid 6003 (API Activity, category 6 Application
15
+ * Activity). The gate is a policy-enforcement point in front of a tool/API
16
+ * call: each evidence entry is one attempted API operation with an
17
+ * allow/deny (or executed/failed) disposition, which is exactly what 6003
18
+ * models via status_id. The IAM alternatives fit worse: 3003 Authorize
19
+ * Session models session-privilege grants (no deny activity), and 6004 Web
20
+ * Resource Access Activity is deprecated in current OCSF.
21
+ *
22
+ * Mapping table (evidence entry → OCSF 6003):
23
+ * entry.at (ISO) → time (epoch ms; 0 sentinel if unparseable)
24
+ * entry.kind → activity_name ('decision'|'execution'), activity_id 99 (Other)
25
+ * entry.action → api.operation
26
+ * entry.allow / outcome → status_id 1 Success / 2 Failure (+ status)
27
+ * entry.reason / outcome → status_detail
28
+ * entry.subject → actor.user.uid
29
+ * entry.receipt_id → metadata.correlation_uid
30
+ * entry.hash → metadata.uid (ties the event to the evidence chain)
31
+ * allow → severity_id 1 Informational; refuse/fail → 3 Medium
32
+ * required_tier, selector, seq, prev_hash → unmapped.* (no OCSF slot)
33
+ *
34
+ * A malformed entry NEVER throws out of the mappers: it becomes a structured
35
+ * error event (status Failure, status_detail 'malformed_evidence_entry') so
36
+ * the corruption itself is visible in the SIEM instead of silently dropped.
37
+ */
38
+
39
+ export const SIEM_EXPORT_VERSION = 'EP-GATE-SIEM-EXPORT-v1';
40
+ export const SIEM_OCSF_CLASS_UID = 6003; // API Activity
41
+ const OCSF_SCHEMA_VERSION = '1.1.0';
42
+ const CATEGORY_UID = 6; // Application Activity
43
+ const PRODUCT = { name: 'EMILIA Gate', vendor_name: 'Emilia Protocol' };
44
+ const PREVIEW_MAX = 256;
45
+
46
+ /**
47
+ * Shape-check an evidence entry and derive the disposition. An entry is
48
+ * mappable only if it carries a parseable timestamp AND a verdict (a decision's
49
+ * `allow` boolean or an execution's `outcome` string) — anything else is
50
+ * treated as malformed and surfaced as an error event, never a throw.
51
+ */
52
+ function classifyEntry(entry) {
53
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return { valid: false };
54
+ const timeMs = typeof entry.at === 'string' ? Date.parse(entry.at) : NaN;
55
+ const hasVerdict = typeof entry.allow === 'boolean' || typeof entry.outcome === 'string';
56
+ if (!Number.isFinite(timeMs) || !hasVerdict) return { valid: false };
57
+ const kind = entry.kind === 'execution' ? 'execution' : 'decision';
58
+ const success = typeof entry.allow === 'boolean' ? entry.allow === true : entry.outcome === 'executed';
59
+ return { valid: true, kind, success, timeMs };
60
+ }
61
+
62
+ /** Bounded, throw-proof rendering of an unmappable entry for the error event. */
63
+ function safePreview(entry) {
64
+ let s;
65
+ try {
66
+ s = typeof entry === 'string' ? entry : JSON.stringify(entry);
67
+ } catch {
68
+ s = '[unserializable]';
69
+ }
70
+ if (s == null) s = String(entry);
71
+ return s.replace(/[\r\n]+/g, ' ').slice(0, PREVIEW_MAX);
72
+ }
73
+
74
+ /**
75
+ * Map an evidence-log entry to an OCSF API Activity (6003) event object.
76
+ * Static and deterministic: same entry, same object, always. Malformed input
77
+ * yields a structured error event rather than throwing (see module doc).
78
+ * @param {object} entry one record from evidence.all()
79
+ * @returns {object} OCSF-shaped event
80
+ */
81
+ export function toOCSF(entry) {
82
+ const c = classifyEntry(entry);
83
+ if (!c.valid) {
84
+ return {
85
+ activity_id: 0,
86
+ activity_name: 'Unknown',
87
+ category_uid: CATEGORY_UID,
88
+ category_name: 'Application Activity',
89
+ class_uid: SIEM_OCSF_CLASS_UID,
90
+ class_name: 'API Activity',
91
+ type_uid: SIEM_OCSF_CLASS_UID * 100 + 0,
92
+ // No trustworthy timestamp inside the entry; 0 sentinel, never the wall
93
+ // clock — the mapping stays pure and the bad entry stays evident.
94
+ time: 0,
95
+ severity_id: 3,
96
+ severity: 'Medium',
97
+ status_id: 2,
98
+ status: 'Failure',
99
+ status_detail: 'malformed_evidence_entry',
100
+ metadata: { version: OCSF_SCHEMA_VERSION, log_name: SIEM_EXPORT_VERSION, product: PRODUCT, uid: null, correlation_uid: null },
101
+ actor: { user: { uid: null } },
102
+ api: { operation: null },
103
+ unmapped: { error: 'malformed_evidence_entry', entry_preview: safePreview(entry) },
104
+ };
105
+ }
106
+ return {
107
+ activity_id: 99,
108
+ activity_name: c.kind,
109
+ category_uid: CATEGORY_UID,
110
+ category_name: 'Application Activity',
111
+ class_uid: SIEM_OCSF_CLASS_UID,
112
+ class_name: 'API Activity',
113
+ type_uid: SIEM_OCSF_CLASS_UID * 100 + 99,
114
+ time: c.timeMs,
115
+ severity_id: c.success ? 1 : 3,
116
+ severity: c.success ? 'Informational' : 'Medium',
117
+ status_id: c.success ? 1 : 2,
118
+ status: c.success ? 'Success' : 'Failure',
119
+ status_detail: entry.reason ?? entry.outcome ?? null,
120
+ metadata: {
121
+ version: OCSF_SCHEMA_VERSION,
122
+ log_name: SIEM_EXPORT_VERSION,
123
+ product: PRODUCT,
124
+ uid: entry.hash ?? null,
125
+ correlation_uid: entry.receipt_id ?? null,
126
+ },
127
+ actor: { user: { uid: entry.subject ?? null } },
128
+ api: { operation: entry.action ?? null },
129
+ unmapped: {
130
+ kind: c.kind,
131
+ required_tier: entry.required_tier ?? null,
132
+ selector: entry.selector ?? null,
133
+ evidence_seq: Number.isFinite(entry.seq) ? entry.seq : null,
134
+ prev_hash: entry.prev_hash ?? null,
135
+ },
136
+ };
137
+ }
138
+
139
+ // CEF escaping per the ArcSight CEF spec: prefix fields escape backslash and
140
+ // pipe; extension values escape backslash and equals. Newlines are collapsed
141
+ // in both — a CEF record is one line, always.
142
+ function escPrefix(v) {
143
+ return String(v).replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]+/g, ' ');
144
+ }
145
+ function escExt(v) {
146
+ return String(v).replace(/\\/g, '\\\\').replace(/=/g, '\\=').replace(/[\r\n]+/g, ' ');
147
+ }
148
+
149
+ /**
150
+ * Map an evidence-log entry to a one-line CEF string (syslog fallback for
151
+ * SIEMs without OCSF ingestion). Same determinism and malformed-input
152
+ * contract as toOCSF.
153
+ * @param {object} entry one record from evidence.all()
154
+ * @returns {string} `CEF:0|...` single line
155
+ */
156
+ export function toCEF(entry) {
157
+ const c = classifyEntry(entry);
158
+ if (!c.valid) {
159
+ const ext = [
160
+ 'act=error',
161
+ 'outcome=malformed_evidence_entry',
162
+ `msg=${escExt(safePreview(entry))}`,
163
+ `cs4=${escExt(SIEM_EXPORT_VERSION)}`, 'cs4Label=export_version',
164
+ ].join(' ');
165
+ return `CEF:0|EmiliaProtocol|Gate|1|gate.malformed|malformed evidence entry|5|${ext}`;
166
+ }
167
+ const verdictWord = c.kind === 'execution'
168
+ ? (c.success ? 'executed' : 'failed')
169
+ : (c.success ? 'allowed' : 'refused');
170
+ const signatureId = `gate.${c.kind}.${c.success ? 'allow' : 'deny'}`;
171
+ const name = `${entry.action ?? 'unknown_action'} ${verdictWord}`;
172
+ const severity = c.success ? 3 : 7;
173
+ const ext = [`end=${c.timeMs}`, `act=${c.success ? 'allow' : 'deny'}`];
174
+ const reason = entry.reason ?? entry.outcome;
175
+ if (reason != null) ext.push(`outcome=${escExt(reason)}`);
176
+ if (entry.subject != null) ext.push(`suser=${escExt(entry.subject)}`);
177
+ if (entry.receipt_id != null) ext.push(`cs1=${escExt(entry.receipt_id)}`, 'cs1Label=receipt_id');
178
+ if (entry.hash != null) ext.push(`cs2=${escExt(entry.hash)}`, 'cs2Label=evidence_hash');
179
+ if (entry.required_tier != null) ext.push(`cs3=${escExt(entry.required_tier)}`, 'cs3Label=required_tier');
180
+ ext.push(`cs4=${escExt(SIEM_EXPORT_VERSION)}`, 'cs4Label=export_version');
181
+ if (Number.isFinite(entry.seq)) ext.push(`cn1=${entry.seq}`, 'cn1Label=evidence_seq');
182
+ return `CEF:0|EmiliaProtocol|Gate|1|${escPrefix(signatureId)}|${escPrefix(name)}|${severity}|${ext.join(' ')}`;
183
+ }
184
+
185
+ /**
186
+ * Create a forwarder that ships evidence entries to a SIEM sink.
187
+ *
188
+ * INVARIANT: SIEM export must NEVER block or crash enforcement. The gate path
189
+ * calls forward() fire-and-forget; a sink that throws, rejects, or is down is
190
+ * recorded on the internal `dropped` counter (exposed via stats()) and NOTHING
191
+ * propagates back to the caller — forward() always resolves, never rejects.
192
+ * This is the inverse of the evidence log's strict mode: the evidence log is
193
+ * the authoritative record and fails closed; the SIEM copy is telemetry and
194
+ * fails open, silently, with an auditable drop count.
195
+ *
196
+ * Configuration errors (unknown format, missing sink) DO throw — at
197
+ * construction time, before anything is on the gate path.
198
+ *
199
+ * @param {object} [o]
200
+ * @param {'ocsf'|'cef'} [o.format='ocsf']
201
+ * @param {function} [o.sink] receives the mapped event (object for ocsf, string for cef); may be async
202
+ * @returns {{ forward(entry): Promise<{delivered:boolean, event:object|string|null}>, stats(): object }}
203
+ */
204
+ export function createSiemForwarder({ format = 'ocsf', sink }: {
205
+ format?: 'ocsf' | 'cef';
206
+ sink?: (event: any) => any;
207
+ } = {}) {
208
+ if (format !== 'ocsf' && format !== 'cef') {
209
+ throw new Error(`EMILIA Gate SIEM: unknown format "${format}" (expected 'ocsf' or 'cef')`);
210
+ }
211
+ if (typeof sink !== 'function') {
212
+ throw new Error('EMILIA Gate SIEM: a sink function is required');
213
+ }
214
+ // Rebind to a locally-scoped const: `sink` the parameter is typed
215
+ // `function|undefined` (it's optional in the JSDoc signature above), and
216
+ // that widened type is what closures below would otherwise capture even
217
+ // though the throw guard just proved it's a function for the lifetime of
218
+ // this forwarder. No behavior change, just handing the closure the type
219
+ // the guard already guarantees.
220
+ /** @type {function} */
221
+ const sinkFn = sink;
222
+ const counts = { forwarded: 0, dropped: 0, malformed: 0 };
223
+
224
+ async function forward(entry) {
225
+ let event: string | ReturnType<typeof toOCSF> | null = null;
226
+ try {
227
+ if (!classifyEntry(entry).valid) counts.malformed += 1;
228
+ event = format === 'cef' ? toCEF(entry) : toOCSF(entry);
229
+ await sinkFn(event);
230
+ counts.forwarded += 1;
231
+ return { delivered: true, event };
232
+ } catch {
233
+ // Sink failure (or any unexpected mapper failure): counted, never
234
+ // propagated — enforcement must not depend on SIEM availability.
235
+ counts.dropped += 1;
236
+ return { delivered: false, event };
237
+ }
238
+ }
239
+
240
+ function stats() {
241
+ return { format, forwarded: counts.forwarded, dropped: counts.dropped, malformed: counts.malformed };
242
+ }
243
+
244
+ return { forward, stats };
245
+ }
246
+
247
+ export default { SIEM_EXPORT_VERSION, SIEM_OCSF_CLASS_UID, toOCSF, toCEF, createSiemForwarder };