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