@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.
- package/CHANGELOG.md +58 -0
- package/README.md +252 -0
- package/action-control-manifest.js +3 -268
- package/action-escrow-custodian.js +3 -394
- package/action-escrow-evidence.js +3 -858
- package/action-escrow-package.js +3 -1668
- package/action-escrow-postgres.js +3 -337
- package/action-escrow-state.js +3 -396
- package/action-escrow-verifiers.js +3 -331
- package/action-escrow.js +3 -2447
- package/action-packs.js +3 -140
- package/adapters/_kit.js +3 -104
- package/adapters/aws.js +3 -108
- package/adapters/cloudflare.js +3 -54
- package/adapters/gcp.js +3 -70
- package/adapters/github.js +3 -109
- package/adapters/jira.js +3 -55
- package/adapters/k8s.js +3 -70
- package/adapters/linear.js +3 -55
- package/adapters/salesforce.js +3 -55
- package/adapters/stripe.js +3 -81
- package/adapters/supabase.js +3 -121
- package/adapters/terraform.js +3 -59
- package/adapters/vercel.js +3 -84
- package/aec-execution.js +3 -312
- package/breakglass.js +3 -582
- package/capability-receipt.js +4 -0
- package/cf1-conformance.js +3 -106
- package/challenge-store.js +3 -69
- package/control-plane.js +3 -340
- package/coverage.js +3 -721
- package/demo.mjs +1 -1
- package/deployment-attestation.js +3 -247
- package/dist/action-control-manifest.d.ts +82 -0
- package/dist/action-control-manifest.d.ts.map +1 -0
- package/dist/action-control-manifest.js +422 -0
- package/dist/action-control-manifest.js.map +1 -0
- package/dist/action-escrow-custodian.d.ts +74 -0
- package/dist/action-escrow-custodian.d.ts.map +1 -0
- package/dist/action-escrow-custodian.js +363 -0
- package/dist/action-escrow-custodian.js.map +1 -0
- package/dist/action-escrow-evidence.d.ts +130 -0
- package/dist/action-escrow-evidence.d.ts.map +1 -0
- package/dist/action-escrow-evidence.js +1012 -0
- package/dist/action-escrow-evidence.js.map +1 -0
- package/dist/action-escrow-package.d.ts +10 -0
- package/dist/action-escrow-package.d.ts.map +1 -0
- package/dist/action-escrow-package.js +1583 -0
- package/dist/action-escrow-package.js.map +1 -0
- package/dist/action-escrow-postgres.d.ts +62 -0
- package/dist/action-escrow-postgres.d.ts.map +1 -0
- package/dist/action-escrow-postgres.js +339 -0
- package/dist/action-escrow-postgres.js.map +1 -0
- package/dist/action-escrow-state.d.ts +141 -0
- package/dist/action-escrow-state.d.ts.map +1 -0
- package/dist/action-escrow-state.js +413 -0
- package/dist/action-escrow-state.js.map +1 -0
- package/dist/action-escrow-verifiers.d.ts +65 -0
- package/dist/action-escrow-verifiers.d.ts.map +1 -0
- package/dist/action-escrow-verifiers.js +421 -0
- package/dist/action-escrow-verifiers.js.map +1 -0
- package/dist/action-escrow.d.ts +112 -0
- package/dist/action-escrow.d.ts.map +1 -0
- package/dist/action-escrow.js +2661 -0
- package/dist/action-escrow.js.map +1 -0
- package/dist/action-packs.d.ts +54 -0
- package/dist/action-packs.d.ts.map +1 -0
- package/dist/action-packs.js +143 -0
- package/dist/action-packs.js.map +1 -0
- package/dist/adapters/_kit.d.ts +50 -0
- package/dist/adapters/_kit.d.ts.map +1 -0
- package/dist/adapters/_kit.js +98 -0
- package/dist/adapters/_kit.js.map +1 -0
- package/dist/adapters/aws.d.ts +158 -0
- package/dist/adapters/aws.d.ts.map +1 -0
- package/dist/adapters/aws.js +136 -0
- package/dist/adapters/aws.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +116 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +75 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/gcp.d.ts +139 -0
- package/dist/adapters/gcp.d.ts.map +1 -0
- package/dist/adapters/gcp.js +69 -0
- package/dist/adapters/gcp.js.map +1 -0
- package/dist/adapters/github.d.ts +123 -0
- package/dist/adapters/github.d.ts.map +1 -0
- package/dist/adapters/github.js +105 -0
- package/dist/adapters/github.js.map +1 -0
- package/dist/adapters/jira.d.ts +109 -0
- package/dist/adapters/jira.d.ts.map +1 -0
- package/dist/adapters/jira.js +54 -0
- package/dist/adapters/jira.js.map +1 -0
- package/dist/adapters/k8s.d.ts +139 -0
- package/dist/adapters/k8s.d.ts.map +1 -0
- package/dist/adapters/k8s.js +69 -0
- package/dist/adapters/k8s.js.map +1 -0
- package/dist/adapters/linear.d.ts +109 -0
- package/dist/adapters/linear.d.ts.map +1 -0
- package/dist/adapters/linear.js +54 -0
- package/dist/adapters/linear.js.map +1 -0
- package/dist/adapters/salesforce.d.ts +109 -0
- package/dist/adapters/salesforce.d.ts.map +1 -0
- package/dist/adapters/salesforce.js +54 -0
- package/dist/adapters/salesforce.js.map +1 -0
- package/dist/adapters/stripe.d.ts +116 -0
- package/dist/adapters/stripe.d.ts.map +1 -0
- package/dist/adapters/stripe.js +77 -0
- package/dist/adapters/stripe.js.map +1 -0
- package/dist/adapters/supabase.d.ts +127 -0
- package/dist/adapters/supabase.d.ts.map +1 -0
- package/dist/adapters/supabase.js +112 -0
- package/dist/adapters/supabase.js.map +1 -0
- package/dist/adapters/terraform.d.ts +109 -0
- package/dist/adapters/terraform.d.ts.map +1 -0
- package/dist/adapters/terraform.js +58 -0
- package/dist/adapters/terraform.js.map +1 -0
- package/dist/adapters/vercel.d.ts +114 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +81 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/aec-execution.d.ts +107 -0
- package/dist/aec-execution.d.ts.map +1 -0
- package/dist/aec-execution.js +334 -0
- package/dist/aec-execution.js.map +1 -0
- package/dist/breakglass.d.ts +205 -0
- package/dist/breakglass.d.ts.map +1 -0
- package/dist/breakglass.js +627 -0
- package/dist/breakglass.js.map +1 -0
- package/dist/capability-receipt.d.ts +601 -0
- package/dist/capability-receipt.d.ts.map +1 -0
- package/dist/capability-receipt.js +1177 -0
- package/dist/capability-receipt.js.map +1 -0
- package/dist/cf1-conformance.d.ts +83 -0
- package/dist/cf1-conformance.d.ts.map +1 -0
- package/dist/cf1-conformance.js +94 -0
- package/dist/cf1-conformance.js.map +1 -0
- package/dist/challenge-store.d.ts +20 -0
- package/dist/challenge-store.d.ts.map +1 -0
- package/dist/challenge-store.js +62 -0
- package/dist/challenge-store.js.map +1 -0
- package/dist/control-plane.d.ts +136 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/control-plane.js +349 -0
- package/dist/control-plane.js.map +1 -0
- package/dist/coverage.d.ts +156 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +768 -0
- package/dist/coverage.js.map +1 -0
- package/dist/deployment-attestation.d.ts +93 -0
- package/dist/deployment-attestation.d.ts.map +1 -0
- package/dist/deployment-attestation.js +262 -0
- package/dist/deployment-attestation.js.map +1 -0
- package/dist/eg1-conformance.d.ts +286 -0
- package/dist/eg1-conformance.d.ts.map +1 -0
- package/dist/eg1-conformance.js +379 -0
- package/dist/eg1-conformance.js.map +1 -0
- package/dist/enterprise.d.ts +88 -0
- package/dist/enterprise.d.ts.map +1 -0
- package/dist/enterprise.js +194 -0
- package/dist/enterprise.js.map +1 -0
- package/dist/evidence-postgres.d.ts +123 -0
- package/dist/evidence-postgres.d.ts.map +1 -0
- package/dist/evidence-postgres.js +343 -0
- package/dist/evidence-postgres.js.map +1 -0
- package/dist/evidence.d.ts +113 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +369 -0
- package/dist/evidence.js.map +1 -0
- package/dist/execution-binding.d.ts +49 -0
- package/dist/execution-binding.d.ts.map +1 -0
- package/dist/execution-binding.js +243 -0
- package/dist/execution-binding.js.map +1 -0
- package/dist/formal-runtime-map.d.ts +42 -0
- package/dist/formal-runtime-map.d.ts.map +1 -0
- package/dist/formal-runtime-map.js +23 -0
- package/dist/formal-runtime-map.js.map +1 -0
- package/dist/index.d.ts +1002 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1736 -0
- package/dist/index.js.map +1 -0
- package/dist/key-registry.d.ts +60 -0
- package/dist/key-registry.d.ts.map +1 -0
- package/dist/key-registry.js +144 -0
- package/dist/key-registry.js.map +1 -0
- package/dist/mcp.d.ts +53 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +112 -0
- package/dist/mcp.js.map +1 -0
- package/dist/metering.d.ts +80 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/metering.js +227 -0
- package/dist/metering.js.map +1 -0
- package/dist/metrics.d.ts +68 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +242 -0
- package/dist/metrics.js.map +1 -0
- package/dist/network-witness.d.ts +167 -0
- package/dist/network-witness.d.ts.map +1 -0
- package/dist/network-witness.js +520 -0
- package/dist/network-witness.js.map +1 -0
- package/dist/protocol-state.d.ts +141 -0
- package/dist/protocol-state.d.ts.map +1 -0
- package/dist/protocol-state.js +106 -0
- package/dist/protocol-state.js.map +1 -0
- package/dist/receipt-program.d.ts +59 -0
- package/dist/receipt-program.d.ts.map +1 -0
- package/dist/receipt-program.js +1053 -0
- package/dist/receipt-program.js.map +1 -0
- package/dist/reliance-kernel.d.ts +66 -0
- package/dist/reliance-kernel.d.ts.map +1 -0
- package/dist/reliance-kernel.js +103 -0
- package/dist/reliance-kernel.js.map +1 -0
- package/dist/reliance-packet.d.ts +67 -0
- package/dist/reliance-packet.d.ts.map +1 -0
- package/dist/reliance-packet.js +176 -0
- package/dist/reliance-packet.js.map +1 -0
- package/dist/remedy-program-postgres.d.ts +37 -0
- package/dist/remedy-program-postgres.d.ts.map +1 -0
- package/dist/remedy-program-postgres.js +291 -0
- package/dist/remedy-program-postgres.js.map +1 -0
- package/dist/remedy-program-receipt.d.ts +97 -0
- package/dist/remedy-program-receipt.d.ts.map +1 -0
- package/dist/remedy-program-receipt.js +817 -0
- package/dist/remedy-program-receipt.js.map +1 -0
- package/dist/remedy-program.d.ts +76 -0
- package/dist/remedy-program.d.ts.map +1 -0
- package/dist/remedy-program.js +1346 -0
- package/dist/remedy-program.js.map +1 -0
- package/dist/reports/art14.d.ts +101 -0
- package/dist/reports/art14.d.ts.map +1 -0
- package/dist/reports/art14.js +340 -0
- package/dist/reports/art14.js.map +1 -0
- package/dist/reports/assurance-package.d.ts +197 -0
- package/dist/reports/assurance-package.d.ts.map +1 -0
- package/dist/reports/assurance-package.js +313 -0
- package/dist/reports/assurance-package.js.map +1 -0
- package/dist/reports/auditor-workpaper.d.ts +194 -0
- package/dist/reports/auditor-workpaper.d.ts.map +1 -0
- package/dist/reports/auditor-workpaper.js +545 -0
- package/dist/reports/auditor-workpaper.js.map +1 -0
- package/dist/reports/external-verification.d.ts +76 -0
- package/dist/reports/external-verification.d.ts.map +1 -0
- package/dist/reports/external-verification.js +209 -0
- package/dist/reports/external-verification.js.map +1 -0
- package/dist/reports/reperform.d.ts +122 -0
- package/dist/reports/reperform.d.ts.map +1 -0
- package/dist/reports/reperform.js +432 -0
- package/dist/reports/reperform.js.map +1 -0
- package/dist/reports/underwriter.d.ts +127 -0
- package/dist/reports/underwriter.d.ts.map +1 -0
- package/dist/reports/underwriter.js +348 -0
- package/dist/reports/underwriter.js.map +1 -0
- package/dist/retention.d.ts +88 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +90 -0
- package/dist/retention.js.map +1 -0
- package/dist/roster.d.ts +113 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +261 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime-monitor.d.ts +132 -0
- package/dist/runtime-monitor.d.ts.map +1 -0
- package/dist/runtime-monitor.js +197 -0
- package/dist/runtime-monitor.js.map +1 -0
- package/dist/settlement.d.ts +68 -0
- package/dist/settlement.d.ts.map +1 -0
- package/dist/settlement.js +306 -0
- package/dist/settlement.js.map +1 -0
- package/dist/siem.d.ts +261 -0
- package/dist/siem.d.ts.map +1 -0
- package/dist/siem.js +247 -0
- package/dist/siem.js.map +1 -0
- package/dist/store-postgres.d.ts +107 -0
- package/dist/store-postgres.d.ts.map +1 -0
- package/dist/store-postgres.js +152 -0
- package/dist/store-postgres.js.map +1 -0
- package/dist/store.d.ts +95 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +202 -0
- package/dist/store.js.map +1 -0
- package/dist/strict-json.d.ts +14 -0
- package/dist/strict-json.d.ts.map +1 -0
- package/dist/strict-json.js +132 -0
- package/dist/strict-json.js.map +1 -0
- package/dist/trust-program-adapters.d.ts +122 -0
- package/dist/trust-program-adapters.d.ts.map +1 -0
- package/dist/trust-program-adapters.js +802 -0
- package/dist/trust-program-adapters.js.map +1 -0
- package/dist/trust-program-postgres.d.ts +41 -0
- package/dist/trust-program-postgres.d.ts.map +1 -0
- package/dist/trust-program-postgres.js +346 -0
- package/dist/trust-program-postgres.js.map +1 -0
- package/dist/trust-program-revocation.d.ts +206 -0
- package/dist/trust-program-revocation.d.ts.map +1 -0
- package/dist/trust-program-revocation.js +600 -0
- package/dist/trust-program-revocation.js.map +1 -0
- package/dist/trust-program.d.ts +197 -0
- package/dist/trust-program.d.ts.map +1 -0
- package/dist/trust-program.js +1351 -0
- package/dist/trust-program.js.map +1 -0
- package/dist/witness-postgres.d.ts +43 -0
- package/dist/witness-postgres.d.ts.map +1 -0
- package/dist/witness-postgres.js +92 -0
- package/dist/witness-postgres.js.map +1 -0
- package/dist/zk-range-proof.d.ts +78 -0
- package/dist/zk-range-proof.d.ts.map +1 -0
- package/dist/zk-range-proof.js +228 -0
- package/dist/zk-range-proof.js.map +1 -0
- package/eg1-conformance.js +3 -404
- package/enterprise.js +3 -180
- package/ep-assure.mjs +1 -0
- package/evidence-postgres.js +3 -356
- package/evidence.js +4 -360
- package/execution-binding.js +3 -212
- package/formal-runtime-map.js +4 -0
- package/index.js +4 -1249
- package/key-registry.js +3 -134
- package/mcp.js +3 -109
- package/metering.js +3 -226
- package/metrics.js +3 -231
- package/network-witness.js +3 -499
- package/package.json +31 -2
- package/receipt-program.js +4 -0
- package/reliance-kernel.js +3 -112
- package/reliance-packet.js +3 -175
- package/remedy-program-postgres.js +4 -0
- package/remedy-program-receipt.js +4 -0
- package/remedy-program.js +4 -0
- package/reports/art14.js +0 -0
- package/reports/assurance-package.js +2 -312
- package/reports/auditor-workpaper.js +3 -537
- package/reports/external-verification.js +3 -222
- package/reports/reperform.js +3 -424
- package/reports/underwriter.js +3 -339
- package/retention.js +3 -84
- package/roster.js +3 -264
- package/runtime-monitor.js +4 -0
- package/settlement.js +3 -299
- package/siem.js +3 -235
- package/src/action-control-manifest.ts +424 -0
- package/src/action-escrow-custodian.ts +427 -0
- package/src/action-escrow-evidence.ts +1178 -0
- package/src/action-escrow-package.ts +1821 -0
- package/src/action-escrow-postgres.ts +366 -0
- package/src/action-escrow-state.ts +498 -0
- package/src/action-escrow-verifiers.ts +484 -0
- package/src/action-escrow.ts +3039 -0
- package/src/action-packs.ts +170 -0
- package/src/adapters/_kit.ts +117 -0
- package/src/adapters/aws.ts +142 -0
- package/src/adapters/cloudflare.ts +78 -0
- package/src/adapters/gcp.ts +71 -0
- package/src/adapters/github.ts +110 -0
- package/src/adapters/jira.ts +56 -0
- package/src/adapters/k8s.ts +71 -0
- package/src/adapters/linear.ts +56 -0
- package/src/adapters/salesforce.ts +56 -0
- package/src/adapters/stripe.ts +82 -0
- package/src/adapters/supabase.ts +122 -0
- package/src/adapters/terraform.ts +60 -0
- package/src/adapters/vercel.ts +85 -0
- package/src/aec-execution.ts +354 -0
- package/src/breakglass.ts +647 -0
- package/src/capability-receipt.ts +1258 -0
- package/src/cf1-conformance.ts +120 -0
- package/src/challenge-store.ts +70 -0
- package/src/control-plane.ts +374 -0
- package/src/coverage.ts +832 -0
- package/src/deployment-attestation.ts +259 -0
- package/src/eg1-conformance.ts +471 -0
- package/src/enterprise.ts +195 -0
- package/src/evidence-postgres.ts +374 -0
- package/src/evidence.ts +367 -0
- package/src/execution-binding.ts +236 -0
- package/src/formal-runtime-map.ts +24 -0
- package/src/index.ts +2122 -0
- package/src/key-registry.ts +150 -0
- package/src/mcp.ts +120 -0
- package/src/metering.ts +237 -0
- package/src/metrics.ts +232 -0
- package/src/network-witness.ts +585 -0
- package/src/protocol-state.ts +143 -0
- package/src/protocol-state.typecheck.ts +94 -0
- package/src/receipt-program.ts +1105 -0
- package/src/reliance-kernel.ts +119 -0
- package/src/reliance-packet.ts +206 -0
- package/src/remedy-program-postgres.ts +367 -0
- package/src/remedy-program-receipt.ts +891 -0
- package/src/remedy-program.ts +1388 -0
- package/src/reports/art14.ts +361 -0
- package/src/reports/assurance-package.ts +330 -0
- package/src/reports/auditor-workpaper.ts +558 -0
- package/src/reports/external-verification.ts +232 -0
- package/src/reports/reperform.ts +441 -0
- package/src/reports/underwriter.ts +356 -0
- package/src/retention.ts +100 -0
- package/src/roster.ts +273 -0
- package/src/runtime-monitor.ts +217 -0
- package/src/settlement.ts +313 -0
- package/src/siem.ts +247 -0
- package/src/store-postgres.ts +167 -0
- package/src/store.ts +214 -0
- package/src/strict-json.ts +115 -0
- package/src/trust-program-adapters.ts +847 -0
- package/src/trust-program-postgres.ts +429 -0
- package/src/trust-program-revocation.ts +751 -0
- package/src/trust-program.ts +1460 -0
- package/src/witness-postgres.ts +106 -0
- package/src/zk-range-proof.ts +250 -0
- package/store-postgres.js +3 -161
- package/store.js +4 -198
- package/strict-json.js +3 -102
- package/trust-program-adapters.js +4 -0
- package/trust-program-postgres.js +4 -0
- package/trust-program-revocation.js +4 -0
- package/trust-program.js +4 -0
- package/witness-postgres.js +3 -96
- package/zk-range-proof.js +4 -0
package/siem.js
CHANGED
|
@@ -1,236 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
|
|
3
|
-
*
|
|
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
|
-
if (format !== 'ocsf' && format !== 'cef') {
|
|
206
|
-
throw new Error(`EMILIA Gate SIEM: unknown format "${format}" (expected 'ocsf' or 'cef')`);
|
|
207
|
-
}
|
|
208
|
-
if (typeof sink !== 'function') {
|
|
209
|
-
throw new Error('EMILIA Gate SIEM: a sink function is required');
|
|
210
|
-
}
|
|
211
|
-
const counts = { forwarded: 0, dropped: 0, malformed: 0 };
|
|
212
|
-
|
|
213
|
-
async function forward(entry) {
|
|
214
|
-
let event = null;
|
|
215
|
-
try {
|
|
216
|
-
if (!classifyEntry(entry).valid) counts.malformed += 1;
|
|
217
|
-
event = format === 'cef' ? toCEF(entry) : toOCSF(entry);
|
|
218
|
-
await sink(event);
|
|
219
|
-
counts.forwarded += 1;
|
|
220
|
-
return { delivered: true, event };
|
|
221
|
-
} catch {
|
|
222
|
-
// Sink failure (or any unexpected mapper failure): counted, never
|
|
223
|
-
// propagated — enforcement must not depend on SIEM availability.
|
|
224
|
-
counts.dropped += 1;
|
|
225
|
-
return { delivered: false, event };
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function stats() {
|
|
230
|
-
return { format, forwarded: counts.forwarded, dropped: counts.dropped, malformed: counts.malformed };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return { forward, stats };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export default { SIEM_EXPORT_VERSION, SIEM_OCSF_CLASS_UID, toOCSF, toCEF, createSiemForwarder };
|
|
2
|
+
// Compatibility entry point for the TypeScript siem module.
|
|
3
|
+
export * from './dist/siem.js';
|
|
4
|
+
export { default } from './dist/siem.js';
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// EP Action Control Manifest: the missing control-plane waist between agent
|
|
4
|
+
// runtimes, receipt formats, transparency logs, and system-of-record adapters.
|
|
5
|
+
//
|
|
6
|
+
// A receipt proves an authorization event. This manifest tells an executor
|
|
7
|
+
// when a receipt is required, what assurance tier is required, which material
|
|
8
|
+
// fields must be observed from the system of record, and what evidence must be
|
|
9
|
+
// emitted after the effect boundary.
|
|
10
|
+
|
|
11
|
+
import { HIGH_RISK_ACTION_PACKS, DEFAULT_PASS_THROUGH_ACTIONS } from './action-packs.js';
|
|
12
|
+
|
|
13
|
+
type Obj = Record<string, any>;
|
|
14
|
+
|
|
15
|
+
export const ACTION_CONTROL_MANIFEST_VERSION = 'EP-ACTION-CONTROL-MANIFEST-v0.2';
|
|
16
|
+
export const ACTION_CONTROL_SCHEMA_URL = 'https://www.emiliaprotocol.ai/docs/schemas/agent-action-control-manifest-v0.2.schema.json';
|
|
17
|
+
export const ACTION_CONTROL_CONFORMANCE_LEVEL = 'EG-1';
|
|
18
|
+
export const ACTION_CONTROL_AUTHORIZATION = Object.freeze({
|
|
19
|
+
authorization_endpoint: 'https://www.emiliaprotocol.ai/api/v1/approvals',
|
|
20
|
+
flow: 'EP-APPROVAL-v1',
|
|
21
|
+
});
|
|
22
|
+
// Acquisition is advertised only when the reference server has a closed,
|
|
23
|
+
// independently verifiable ceremony for the action type. Receipt-required
|
|
24
|
+
// actions outside this registry remain enforceable, but challenge-only.
|
|
25
|
+
export const ACTION_CONTROL_ACQUISITION_ACTION_TYPES = Object.freeze([
|
|
26
|
+
'payment.release',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const RISK_LEVELS = new Set(['low', 'medium', 'high', 'critical']);
|
|
30
|
+
const ASSURANCE_CLASSES = new Set(['software', 'class_a', 'quorum']);
|
|
31
|
+
const ENFORCEMENT_POINTS = new Set(['pre_execution', 'pre_effect_commit']);
|
|
32
|
+
|
|
33
|
+
export const ACTION_CONTROL_DEFAULTS = Object.freeze({
|
|
34
|
+
decision_point: 'pre_effect_commit',
|
|
35
|
+
missing_receipt: 'refuse',
|
|
36
|
+
invalid_receipt: 'refuse',
|
|
37
|
+
stale_receipt: 'refuse',
|
|
38
|
+
replay: 'one_time_consumption',
|
|
39
|
+
evidence_log: 'strict',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const ACTION_CONTROL_EVIDENCE_PROFILES = Object.freeze({
|
|
43
|
+
authorization_receipt: 'EP-RECEIPT-v1',
|
|
44
|
+
execution_attestation: 'EP-EXECUTION-ATTESTATION-v1',
|
|
45
|
+
reliance_packet: 'EP-RELIANCE-PACKET-v1',
|
|
46
|
+
transparency: 'SCITT-compatible Signed Statement',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const ACTION_CONTROL_CONFORMANCE_CHECKS = Object.freeze([
|
|
50
|
+
'missing_receipt_refused',
|
|
51
|
+
'software_on_classA_refused',
|
|
52
|
+
'execution_drift_refused',
|
|
53
|
+
'valid_classA_runs',
|
|
54
|
+
'replay_refused',
|
|
55
|
+
'tampered_refused',
|
|
56
|
+
'execution_proof_binds',
|
|
57
|
+
'reliance_packet_rely',
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
function cloneJson(value: any): any {
|
|
61
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeRisk(risk: any): string {
|
|
65
|
+
return RISK_LEVELS.has(risk) ? risk : 'high';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeAssurance(value: any): string {
|
|
69
|
+
return ASSURANCE_CLASSES.has(value) ? value : 'software';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function supportsReferenceAcquisition(actionType: unknown): boolean {
|
|
73
|
+
return typeof actionType === 'string'
|
|
74
|
+
&& ACTION_CONTROL_ACQUISITION_ACTION_TYPES.includes(actionType);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function defaultControlForAction(action: Obj): Obj {
|
|
78
|
+
const requiredFields = action.execution_binding?.required_fields || [];
|
|
79
|
+
const caidSelector = action.execution_binding?.caid_selector;
|
|
80
|
+
if (!action.receipt_required) {
|
|
81
|
+
return {
|
|
82
|
+
enforcement_point: 'none',
|
|
83
|
+
authorization_receipt: { required: false },
|
|
84
|
+
evidence_output: { audit_event: true },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const control: Obj = {
|
|
88
|
+
enforcement_point: 'pre_effect_commit',
|
|
89
|
+
status: 428,
|
|
90
|
+
challenge_header: 'Receipt-Required',
|
|
91
|
+
proof_header: 'X-EMILIA-Receipt',
|
|
92
|
+
authorization_receipt: {
|
|
93
|
+
required: true,
|
|
94
|
+
profile: 'EP-RECEIPT-v1',
|
|
95
|
+
signature: 'Ed25519 over RFC 8785 canonical JSON',
|
|
96
|
+
verifier: 'offline',
|
|
97
|
+
},
|
|
98
|
+
replay: {
|
|
99
|
+
mode: 'one_time_consumption',
|
|
100
|
+
receipt_id_required: true,
|
|
101
|
+
},
|
|
102
|
+
execution_binding: {
|
|
103
|
+
required: true,
|
|
104
|
+
source: 'system_of_record',
|
|
105
|
+
required_fields: [...requiredFields],
|
|
106
|
+
...(caidSelector ? { caid_selector: cloneJson(caidSelector) } : {}),
|
|
107
|
+
},
|
|
108
|
+
transparency: {
|
|
109
|
+
mode: 'registerable',
|
|
110
|
+
profile: 'SCITT Signed Statement',
|
|
111
|
+
required: false,
|
|
112
|
+
},
|
|
113
|
+
evidence_output: {
|
|
114
|
+
audit_event: true,
|
|
115
|
+
execution_attestation: true,
|
|
116
|
+
reliance_packet: true,
|
|
117
|
+
blocked_attempts: true,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
if (supportsReferenceAcquisition(action.action_type)) {
|
|
121
|
+
control.authorization = { ...ACTION_CONTROL_AUTHORIZATION };
|
|
122
|
+
}
|
|
123
|
+
return control;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function toActionControl(action: Obj): Obj {
|
|
127
|
+
const out: Obj = {
|
|
128
|
+
id: action.id,
|
|
129
|
+
label: action.label || action.description || action.id,
|
|
130
|
+
action_type: action.action_type,
|
|
131
|
+
risk: normalizeRisk(action.risk || (action.receipt_required ? 'high' : 'low')),
|
|
132
|
+
receipt_required: !!action.receipt_required,
|
|
133
|
+
assurance_class: normalizeAssurance(action.assurance_class),
|
|
134
|
+
max_age_sec: action.max_age_sec || 900,
|
|
135
|
+
match: cloneJson(action.match || {}),
|
|
136
|
+
why: action.why || action.description || null,
|
|
137
|
+
control: cloneJson(action.control || defaultControlForAction(action)),
|
|
138
|
+
conformance: {
|
|
139
|
+
level: ACTION_CONTROL_CONFORMANCE_LEVEL,
|
|
140
|
+
checks: [...ACTION_CONTROL_CONFORMANCE_CHECKS],
|
|
141
|
+
...(action.conformance || {}),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
if (action.quorum) out.quorum = cloneJson(action.quorum);
|
|
145
|
+
if (action.business_authorization || action.businessAuthorization) {
|
|
146
|
+
out.business_authorization = cloneJson(action.business_authorization || action.businessAuthorization);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {object} [o]
|
|
153
|
+
* @param {{ name?: string, issuer?: string, manifest_url?: string }} [o.service]
|
|
154
|
+
* @param {boolean} [o.includePassThrough]
|
|
155
|
+
* @param {Array<object>} [o.extraActions]
|
|
156
|
+
*/
|
|
157
|
+
export function createDefaultActionControlManifest({
|
|
158
|
+
service = {},
|
|
159
|
+
includePassThrough = true,
|
|
160
|
+
extraActions = [],
|
|
161
|
+
} : { service?: Obj; includePassThrough?: boolean; extraActions?: Obj[] } = {}): Obj {
|
|
162
|
+
const actions = [
|
|
163
|
+
...HIGH_RISK_ACTION_PACKS.map(toActionControl),
|
|
164
|
+
...(includePassThrough ? DEFAULT_PASS_THROUGH_ACTIONS.map(toActionControl) : []),
|
|
165
|
+
];
|
|
166
|
+
// `extraActions` is also the supported customization mechanism. An exact id
|
|
167
|
+
// or exact transport-selector match replaces the built-in entry; leaving
|
|
168
|
+
// both would create an ambiguous manifest that the validator correctly
|
|
169
|
+
// rejects. Broader/partial overlaps are not silently normalized and remain
|
|
170
|
+
// validation errors.
|
|
171
|
+
for (const candidate of extraActions.map(toActionControl)) {
|
|
172
|
+
const index = actions.findIndex((current) => current.id === candidate.id
|
|
173
|
+
|| selectorsEqual(current, candidate));
|
|
174
|
+
if (index >= 0) actions.splice(index, 1, candidate);
|
|
175
|
+
else actions.push(candidate);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
'@version': ACTION_CONTROL_MANIFEST_VERSION,
|
|
179
|
+
'$schema': ACTION_CONTROL_SCHEMA_URL,
|
|
180
|
+
profile: 'agent-action-control',
|
|
181
|
+
service: {
|
|
182
|
+
name: service.name || 'EMILIA Gate default action controls',
|
|
183
|
+
issuer: service.issuer || 'https://www.emiliaprotocol.ai',
|
|
184
|
+
manifest_url: service.manifest_url || 'https://www.emiliaprotocol.ai/.well-known/agent-action-control.json',
|
|
185
|
+
...service,
|
|
186
|
+
},
|
|
187
|
+
defaults: { ...ACTION_CONTROL_DEFAULTS },
|
|
188
|
+
evidence_profiles: { ...ACTION_CONTROL_EVIDENCE_PROFILES },
|
|
189
|
+
actions,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function selectorMatches(action: Obj, selector: Obj = {}): boolean {
|
|
194
|
+
if (!action || typeof action !== 'object' || Array.isArray(action)
|
|
195
|
+
|| !selector || typeof selector !== 'object' || Array.isArray(selector)) return false;
|
|
196
|
+
const match = action.match;
|
|
197
|
+
if (!match || typeof match !== 'object' || Array.isArray(match)) return false;
|
|
198
|
+
const matchEntries = Object.entries(match);
|
|
199
|
+
if (matchEntries.length === 0) return false;
|
|
200
|
+
// action_type is an additional constraint; it may never bypass a conflicting
|
|
201
|
+
// transport selector. Other selector metadata (for example manifestUrl) is
|
|
202
|
+
// not part of the action's transport identity.
|
|
203
|
+
if (Object.prototype.hasOwnProperty.call(selector, 'action_type')
|
|
204
|
+
&& selector.action_type !== action.action_type) return false;
|
|
205
|
+
return matchEntries.every(([key, value]) => (
|
|
206
|
+
Object.prototype.hasOwnProperty.call(selector, key) && selector[key] === value
|
|
207
|
+
));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function findActionControl(manifest: Obj, selector: Obj = {}): Obj | null {
|
|
211
|
+
const resolved = resolveActionControl(manifest, selector);
|
|
212
|
+
return resolved.status === 'one' ? resolved.action : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function resolveActionControl(manifest: Obj, selector: Obj = {}):
|
|
216
|
+
| { status: 'none'; action: null }
|
|
217
|
+
| { status: 'one'; action: Obj }
|
|
218
|
+
| { status: 'ambiguous'; action: null; action_ids: string[] } {
|
|
219
|
+
if (!manifest || !Array.isArray(manifest.actions)) return { status: 'none', action: null };
|
|
220
|
+
const matches = manifest.actions.filter((action) => selectorMatches(action, selector));
|
|
221
|
+
if (matches.length === 0) return { status: 'none', action: null };
|
|
222
|
+
if (matches.length === 1) return { status: 'one', action: matches[0] };
|
|
223
|
+
return {
|
|
224
|
+
status: 'ambiguous',
|
|
225
|
+
action: null,
|
|
226
|
+
action_ids: matches.map((action) => String(action.id || '')).sort(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function selectorsOverlap(left: Obj, right: Obj): boolean {
|
|
231
|
+
const a = left?.match;
|
|
232
|
+
const b = right?.match;
|
|
233
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object'
|
|
234
|
+
|| Array.isArray(a) || Array.isArray(b)) return false;
|
|
235
|
+
const shared = Object.keys(a).filter((key) => Object.prototype.hasOwnProperty.call(b, key));
|
|
236
|
+
return shared.every((key) => a[key] === b[key]);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function selectorsEqual(left: Obj, right: Obj): boolean {
|
|
240
|
+
const a = left?.match;
|
|
241
|
+
const b = right?.match;
|
|
242
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object'
|
|
243
|
+
|| Array.isArray(a) || Array.isArray(b)) return false;
|
|
244
|
+
const aKeys = Object.keys(a).sort();
|
|
245
|
+
const bKeys = Object.keys(b).sort();
|
|
246
|
+
return aKeys.length > 0
|
|
247
|
+
&& aKeys.length === bKeys.length
|
|
248
|
+
&& aKeys.every((key, index) => key === bKeys[index] && a[key] === b[key]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function validateAuthorizationDescriptor(authorization: any, prefix: string, errors: string[]): void {
|
|
252
|
+
if (!authorization || typeof authorization !== 'object' || Array.isArray(authorization)) {
|
|
253
|
+
errors.push(`${prefix}.control.authorization must be an object`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const prototype = Object.getPrototypeOf(authorization);
|
|
258
|
+
const keys = Object.keys(authorization).sort();
|
|
259
|
+
if ((prototype !== Object.prototype && prototype !== null)
|
|
260
|
+
|| keys.length !== 2
|
|
261
|
+
|| keys[0] !== 'authorization_endpoint'
|
|
262
|
+
|| keys[1] !== 'flow') {
|
|
263
|
+
errors.push(`${prefix}.control.authorization must contain only authorization_endpoint and flow`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (authorization.flow !== 'EP-APPROVAL-v1') {
|
|
267
|
+
errors.push(`${prefix}.control.authorization.flow must be EP-APPROVAL-v1`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let endpoint: URL | null = null;
|
|
271
|
+
try {
|
|
272
|
+
endpoint = new URL(authorization.authorization_endpoint);
|
|
273
|
+
} catch {
|
|
274
|
+
// The common error below intentionally covers both malformed and relative URLs.
|
|
275
|
+
}
|
|
276
|
+
if (!endpoint
|
|
277
|
+
|| endpoint.protocol !== 'https:'
|
|
278
|
+
|| !endpoint.hostname
|
|
279
|
+
|| endpoint.username
|
|
280
|
+
|| endpoint.password
|
|
281
|
+
|| endpoint.search
|
|
282
|
+
|| endpoint.hash) {
|
|
283
|
+
errors.push(`${prefix}.control.authorization.authorization_endpoint must be an absolute HTTPS URL without credentials, query, or fragment`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function validateActionControlManifest(
|
|
288
|
+
manifest: Obj,
|
|
289
|
+
{ requireAcquisition = false }: { requireAcquisition?: boolean } = {},
|
|
290
|
+
): Obj {
|
|
291
|
+
const errors: string[] = [];
|
|
292
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
293
|
+
return { ok: false, errors: ['manifest must be an object'] };
|
|
294
|
+
}
|
|
295
|
+
if (manifest['@version'] !== ACTION_CONTROL_MANIFEST_VERSION) {
|
|
296
|
+
errors.push(`@version must be ${ACTION_CONTROL_MANIFEST_VERSION}`);
|
|
297
|
+
}
|
|
298
|
+
if (manifest.profile !== 'agent-action-control' && manifest.profile !== 'emilia.action-control') {
|
|
299
|
+
errors.push('profile must be agent-action-control (or the deployed alias emilia.action-control)');
|
|
300
|
+
}
|
|
301
|
+
if (!manifest.service || typeof manifest.service !== 'object') {
|
|
302
|
+
errors.push('service object is required');
|
|
303
|
+
}
|
|
304
|
+
if (!manifest.defaults || typeof manifest.defaults !== 'object') {
|
|
305
|
+
errors.push('defaults object is required');
|
|
306
|
+
}
|
|
307
|
+
if (!Array.isArray(manifest.actions) || manifest.actions.length === 0) {
|
|
308
|
+
errors.push('actions must be a non-empty array');
|
|
309
|
+
}
|
|
310
|
+
const ids = new Map<string, number>();
|
|
311
|
+
for (const [idx, action] of (manifest.actions || []).entries()) {
|
|
312
|
+
const prefix = `actions[${idx}]`;
|
|
313
|
+
if (!action || typeof action !== 'object') {
|
|
314
|
+
errors.push(`${prefix} must be an object`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (!action.id || typeof action.id !== 'string') errors.push(`${prefix}.id is required`);
|
|
318
|
+
else if (ids.has(action.id)) errors.push(`${prefix}.id duplicates actions[${ids.get(action.id)}].id`);
|
|
319
|
+
else ids.set(action.id, idx);
|
|
320
|
+
if (!action.action_type || typeof action.action_type !== 'string') errors.push(`${prefix}.action_type is required`);
|
|
321
|
+
if (!action.match || typeof action.match !== 'object') errors.push(`${prefix}.match is required`);
|
|
322
|
+
if (typeof action.receipt_required !== 'boolean') errors.push(`${prefix}.receipt_required must be boolean`);
|
|
323
|
+
if (!RISK_LEVELS.has(action.risk)) errors.push(`${prefix}.risk must be low|medium|high|critical`);
|
|
324
|
+
if (!ASSURANCE_CLASSES.has(action.assurance_class)) errors.push(`${prefix}.assurance_class must be software|class_a|quorum`);
|
|
325
|
+
// Key-class floor: a critical action must be bound to a human key. normalizeAssurance()
|
|
326
|
+
// defaults a missing or unrecognized tier to the weakest 'software', so this also fails
|
|
327
|
+
// closed on a critical action whose tier was omitted rather than silently downgrading it.
|
|
328
|
+
if (action.receipt_required && action.risk === 'critical' && action.assurance_class === 'software') {
|
|
329
|
+
errors.push(`${prefix}.assurance_class must be class_a or quorum when risk is critical`);
|
|
330
|
+
}
|
|
331
|
+
const authorization = action.control?.authorization;
|
|
332
|
+
if (authorization !== undefined) {
|
|
333
|
+
validateAuthorizationDescriptor(authorization, prefix, errors);
|
|
334
|
+
if (!supportsReferenceAcquisition(action.action_type)) {
|
|
335
|
+
errors.push(`${prefix}.control.authorization is advertised for an unsupported acquisition action_type`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (action.receipt_required) {
|
|
339
|
+
if (!Number.isFinite(action.max_age_sec) || action.max_age_sec <= 0) errors.push(`${prefix}.max_age_sec must be positive`);
|
|
340
|
+
const control = action.control;
|
|
341
|
+
if (!control || typeof control !== 'object') {
|
|
342
|
+
errors.push(`${prefix}.control is required when receipt_required=true`);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (requireAcquisition
|
|
346
|
+
&& supportsReferenceAcquisition(action.action_type)
|
|
347
|
+
&& authorization === undefined) {
|
|
348
|
+
errors.push(`${prefix}.control.authorization is required for acquisition conformance`);
|
|
349
|
+
}
|
|
350
|
+
if (!ENFORCEMENT_POINTS.has(control.enforcement_point)) {
|
|
351
|
+
errors.push(`${prefix}.control.enforcement_point must be pre_execution or pre_effect_commit`);
|
|
352
|
+
}
|
|
353
|
+
if (control.status !== 428) errors.push(`${prefix}.control.status must be 428`);
|
|
354
|
+
if (control.authorization_receipt?.required !== true) errors.push(`${prefix}.control.authorization_receipt.required must be true`);
|
|
355
|
+
if (control.authorization_receipt?.profile !== 'EP-RECEIPT-v1') errors.push(`${prefix}.control.authorization_receipt.profile must be EP-RECEIPT-v1`);
|
|
356
|
+
if (control.authorization_receipt?.verifier !== 'offline') errors.push(`${prefix}.control.authorization_receipt.verifier must be offline`);
|
|
357
|
+
if (control.replay?.mode !== 'one_time_consumption') errors.push(`${prefix}.control.replay.mode must be one_time_consumption`);
|
|
358
|
+
if (control.replay?.receipt_id_required !== true) errors.push(`${prefix}.control.replay.receipt_id_required must be true`);
|
|
359
|
+
const fields = control.execution_binding?.required_fields;
|
|
360
|
+
if (control.execution_binding?.required !== true) errors.push(`${prefix}.control.execution_binding.required must be true`);
|
|
361
|
+
if (control.execution_binding?.source !== 'system_of_record') errors.push(`${prefix}.control.execution_binding.source must be system_of_record`);
|
|
362
|
+
if (!Array.isArray(fields) || fields.length === 0 || fields.some((f: any) => typeof f !== 'string' || !f)) {
|
|
363
|
+
errors.push(`${prefix}.control.execution_binding.required_fields must be a non-empty string array`);
|
|
364
|
+
}
|
|
365
|
+
const caidSelector = control.execution_binding?.caid_selector;
|
|
366
|
+
if (caidSelector !== undefined
|
|
367
|
+
&& (!caidSelector || typeof caidSelector !== 'object' || Array.isArray(caidSelector)
|
|
368
|
+
|| Object.keys(caidSelector).length !== 1
|
|
369
|
+
|| typeof caidSelector.field !== 'string'
|
|
370
|
+
|| !/^[A-Za-z][A-Za-z0-9_]{0,127}$/.test(caidSelector.field))) {
|
|
371
|
+
errors.push(`${prefix}.control.execution_binding.caid_selector must select one safe action field`);
|
|
372
|
+
}
|
|
373
|
+
if (control.evidence_output?.execution_attestation !== true) errors.push(`${prefix}.control.evidence_output.execution_attestation must be true`);
|
|
374
|
+
if (control.evidence_output?.reliance_packet !== true) errors.push(`${prefix}.control.evidence_output.reliance_packet must be true`);
|
|
375
|
+
if (action.conformance?.level !== ACTION_CONTROL_CONFORMANCE_LEVEL) errors.push(`${prefix}.conformance.level must be ${ACTION_CONTROL_CONFORMANCE_LEVEL}`);
|
|
376
|
+
if (action.business_authorization !== undefined) {
|
|
377
|
+
const business = action.business_authorization;
|
|
378
|
+
const policy = business?.policy;
|
|
379
|
+
const approvers = business?.allowed_approvers;
|
|
380
|
+
if (!business || typeof business !== 'object' || Array.isArray(business)) {
|
|
381
|
+
errors.push(`${prefix}.business_authorization must be an object`);
|
|
382
|
+
} else {
|
|
383
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)
|
|
384
|
+
|| typeof policy.id !== 'string' || !policy.id
|
|
385
|
+
|| typeof policy.hash !== 'string' || !policy.hash) {
|
|
386
|
+
errors.push(`${prefix}.business_authorization.policy must pin non-empty id and hash`);
|
|
387
|
+
}
|
|
388
|
+
if (typeof business.tenant_id !== 'string' || !business.tenant_id) {
|
|
389
|
+
errors.push(`${prefix}.business_authorization.tenant_id must be a non-empty string`);
|
|
390
|
+
}
|
|
391
|
+
if (!Array.isArray(approvers) || approvers.length === 0
|
|
392
|
+
|| approvers.some((entry: any) => !entry || typeof entry !== 'object'
|
|
393
|
+
|| typeof entry.subject !== 'string' || !entry.subject
|
|
394
|
+
|| typeof entry.role !== 'string' || !entry.role)) {
|
|
395
|
+
errors.push(`${prefix}.business_authorization.allowed_approvers must name subject and role`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (let left = 0; left < (manifest.actions || []).length; left += 1) {
|
|
402
|
+
for (let right = left + 1; right < manifest.actions.length; right += 1) {
|
|
403
|
+
if (selectorsOverlap(manifest.actions[left], manifest.actions[right])) {
|
|
404
|
+
errors.push(`actions[${left}].match overlaps actions[${right}].match`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { ok: errors.length === 0, errors };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export default {
|
|
412
|
+
ACTION_CONTROL_MANIFEST_VERSION,
|
|
413
|
+
ACTION_CONTROL_SCHEMA_URL,
|
|
414
|
+
ACTION_CONTROL_CONFORMANCE_LEVEL,
|
|
415
|
+
ACTION_CONTROL_AUTHORIZATION,
|
|
416
|
+
ACTION_CONTROL_DEFAULTS,
|
|
417
|
+
ACTION_CONTROL_EVIDENCE_PROFILES,
|
|
418
|
+
ACTION_CONTROL_CONFORMANCE_CHECKS,
|
|
419
|
+
toActionControl,
|
|
420
|
+
createDefaultActionControlManifest,
|
|
421
|
+
findActionControl,
|
|
422
|
+
resolveActionControl,
|
|
423
|
+
validateActionControlManifest,
|
|
424
|
+
};
|