@emilia-protocol/gate 0.10.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 +91 -0
- package/README.md +272 -0
- package/action-control-manifest.js +3 -268
- package/action-escrow-custodian.js +4 -0
- package/action-escrow-evidence.js +4 -0
- package/action-escrow-package.js +4 -0
- package/action-escrow-postgres.js +4 -0
- package/action-escrow-state.js +4 -0
- package/action-escrow-verifiers.js +4 -0
- package/action-escrow.js +4 -0
- 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 +47 -4
- 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 -85
- 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,1012 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* EP-ACTION-ESCROW-EVIDENCE-PACKAGE-v1
|
|
5
|
+
*
|
|
6
|
+
* A content-addressed manifest for the artifacts used by Action Escrow. This
|
|
7
|
+
* module does not decide whether a contract is enforceable and does not move
|
|
8
|
+
* money. Verification replays caller-supplied, relying-party-owned component
|
|
9
|
+
* verifiers so a package cannot make an invalid nested artifact trustworthy by
|
|
10
|
+
* merely hashing it.
|
|
11
|
+
*/
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
import { hashCanonical } from './execution-binding.js';
|
|
14
|
+
import { strictJsonGate } from './strict-json.js';
|
|
15
|
+
export const ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION = 'EP-ACTION-ESCROW-EVIDENCE-PACKAGE-v1';
|
|
16
|
+
export const ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION = 'EP-ACTION-ESCROW-CONTRACTOR-EVIDENCE-PACKAGE-v1';
|
|
17
|
+
export const ACTION_ESCROW_EVIDENCE_STAGES = Object.freeze([
|
|
18
|
+
'draft',
|
|
19
|
+
'awaiting_acceptance',
|
|
20
|
+
'effective',
|
|
21
|
+
'awaiting_funding',
|
|
22
|
+
'funded',
|
|
23
|
+
'milestone_submitted',
|
|
24
|
+
'release_reserved',
|
|
25
|
+
'released',
|
|
26
|
+
'disputed',
|
|
27
|
+
'amendment_pending',
|
|
28
|
+
'cancelled',
|
|
29
|
+
'completed',
|
|
30
|
+
'release_indeterminate',
|
|
31
|
+
]);
|
|
32
|
+
const HASH = /^sha256:[0-9a-f]{64}$/;
|
|
33
|
+
const TOP_KEYS = new Set([
|
|
34
|
+
'version',
|
|
35
|
+
'agreement_id',
|
|
36
|
+
'stage',
|
|
37
|
+
'binding',
|
|
38
|
+
'document',
|
|
39
|
+
'document_execution',
|
|
40
|
+
'agreement_acceptances',
|
|
41
|
+
'release_approvals',
|
|
42
|
+
'funding_statement',
|
|
43
|
+
'milestones',
|
|
44
|
+
'release',
|
|
45
|
+
'state_record',
|
|
46
|
+
'amendments',
|
|
47
|
+
'verification_profile',
|
|
48
|
+
'assembled_at',
|
|
49
|
+
'limitations',
|
|
50
|
+
'package_digest',
|
|
51
|
+
]);
|
|
52
|
+
const CONTRACTOR_TOP_KEYS = new Set([
|
|
53
|
+
...TOP_KEYS,
|
|
54
|
+
'project_record',
|
|
55
|
+
]);
|
|
56
|
+
const DOCUMENT_KEYS = new Set(['media_type', 'digest', 'byte_length', 'file_name']);
|
|
57
|
+
const PROJECT_RECORD_KEYS = new Set([
|
|
58
|
+
'media_type',
|
|
59
|
+
'digest',
|
|
60
|
+
'byte_length',
|
|
61
|
+
'file_name',
|
|
62
|
+
'provider',
|
|
63
|
+
'snapshot_digest',
|
|
64
|
+
]);
|
|
65
|
+
const PARTY_EVIDENCE_KEYS = new Set(['party_id', 'role', 'evidence']);
|
|
66
|
+
const MILESTONE_KEYS = new Set(['milestone_id', 'evidence', 'resolution']);
|
|
67
|
+
const RELEASE_KEYS = new Set([
|
|
68
|
+
'reservation',
|
|
69
|
+
'provider_request',
|
|
70
|
+
'provider_statement',
|
|
71
|
+
'execution_record',
|
|
72
|
+
]);
|
|
73
|
+
const STATE_RECORD_KEYS = new Set(['snapshot', 'statement']);
|
|
74
|
+
const LIMITATIONS = Object.freeze([
|
|
75
|
+
'The package does not establish contract enforceability, comprehension, voluntariness, workmanship, physical truth, or legal compliance.',
|
|
76
|
+
'The package does not establish custodian licensing, solvency, or that no payment path existed outside the integrated release boundary.',
|
|
77
|
+
'A content digest does not upgrade an invalid component; each component must verify under relying-party-pinned trust roots.',
|
|
78
|
+
'An indeterminate provider effect is not proof of failure and must not be retried before authoritative reconciliation.',
|
|
79
|
+
]);
|
|
80
|
+
const DEFAULT_MAX_DOCUMENT_BYTES = 50 * 1024 * 1024;
|
|
81
|
+
const DEFAULT_MAX_PROJECT_RECORD_BYTES = 4 * 1024 * 1024;
|
|
82
|
+
const DEFAULT_MAX_PACKAGE_BYTES = 4 * 1024 * 1024;
|
|
83
|
+
/**
|
|
84
|
+
* @param {unknown} value
|
|
85
|
+
* @returns {value is Record<string, any>}
|
|
86
|
+
*/
|
|
87
|
+
function isRecord(value) {
|
|
88
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* @param {*} value
|
|
92
|
+
* @param {Set<string>} allowed
|
|
93
|
+
* @param {Set<string>} [required]
|
|
94
|
+
*/
|
|
95
|
+
function exactKeys(value, allowed, required = allowed) {
|
|
96
|
+
if (!isRecord(value))
|
|
97
|
+
return false;
|
|
98
|
+
const keys = Object.keys(value);
|
|
99
|
+
return keys.every((key) => allowed.has(key))
|
|
100
|
+
&& [...required].every((key) => Object.hasOwn(value, key));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* @param {number|string|Date|(() => (number|string|Date))} value
|
|
104
|
+
*/
|
|
105
|
+
function toIso(value) {
|
|
106
|
+
const candidate = typeof value === 'function' ? value() : value;
|
|
107
|
+
const date = candidate instanceof Date ? candidate : new Date(candidate ?? 0);
|
|
108
|
+
if (!Number.isFinite(date.getTime())) {
|
|
109
|
+
throw new TypeError('action-escrow evidence: assembled_at must be a valid instant');
|
|
110
|
+
}
|
|
111
|
+
return date.toISOString();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @param {*} value
|
|
115
|
+
*/
|
|
116
|
+
function portableJsonCopy(value) {
|
|
117
|
+
let nodes = 0;
|
|
118
|
+
let stringBytes = 0;
|
|
119
|
+
const active = new Set();
|
|
120
|
+
/**
|
|
121
|
+
* @param {*} current
|
|
122
|
+
* @param {number} depth
|
|
123
|
+
* @returns {*}
|
|
124
|
+
*/
|
|
125
|
+
function copy(current, depth) {
|
|
126
|
+
nodes += 1;
|
|
127
|
+
if (nodes > 50_000 || depth > 64) {
|
|
128
|
+
throw new TypeError('action-escrow evidence: value exceeds resource limits');
|
|
129
|
+
}
|
|
130
|
+
if (current === null || typeof current === 'boolean')
|
|
131
|
+
return current;
|
|
132
|
+
if (typeof current === 'string') {
|
|
133
|
+
stringBytes += Buffer.byteLength(current, 'utf8');
|
|
134
|
+
if (stringBytes > 4 * 1024 * 1024) {
|
|
135
|
+
throw new TypeError('action-escrow evidence: strings exceed resource limits');
|
|
136
|
+
}
|
|
137
|
+
return current;
|
|
138
|
+
}
|
|
139
|
+
if (typeof current === 'number') {
|
|
140
|
+
if (!Number.isSafeInteger(current) || Object.is(current, -0)) {
|
|
141
|
+
throw new TypeError('action-escrow evidence: numbers must be safe integers');
|
|
142
|
+
}
|
|
143
|
+
return current;
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(current) && !Array.isArray(current)) {
|
|
146
|
+
throw new TypeError('action-escrow evidence: value is not canonical JSON');
|
|
147
|
+
}
|
|
148
|
+
if (active.has(current)) {
|
|
149
|
+
throw new TypeError('action-escrow evidence: cyclic or aliased value');
|
|
150
|
+
}
|
|
151
|
+
active.add(current);
|
|
152
|
+
try {
|
|
153
|
+
if (Array.isArray(current))
|
|
154
|
+
return current.map((entry) => copy(entry, depth + 1));
|
|
155
|
+
const prototype = Object.getPrototypeOf(current);
|
|
156
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
157
|
+
throw new TypeError('action-escrow evidence: objects must use a plain prototype');
|
|
158
|
+
}
|
|
159
|
+
return Object.fromEntries(Object.entries(current).map(([key, entry]) => [key, copy(entry, depth + 1)]));
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
active.delete(current);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return copy(value, 0);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* @param {*} value
|
|
169
|
+
*/
|
|
170
|
+
function deepFreeze(value) {
|
|
171
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value))
|
|
172
|
+
return value;
|
|
173
|
+
Object.freeze(value);
|
|
174
|
+
for (const child of Object.values(value))
|
|
175
|
+
deepFreeze(child);
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* @param {*} value
|
|
180
|
+
* @param {number} maxBytes
|
|
181
|
+
*/
|
|
182
|
+
function documentBytes(value, maxBytes) {
|
|
183
|
+
if (!(Buffer.isBuffer(value) || value instanceof Uint8Array)) {
|
|
184
|
+
throw new TypeError('action-escrow evidence: documentBytes must be bytes');
|
|
185
|
+
}
|
|
186
|
+
const bytes = Buffer.from(value);
|
|
187
|
+
if (bytes.length === 0 || bytes.length > maxBytes) {
|
|
188
|
+
throw new TypeError(`action-escrow evidence: documentBytes must be between 1 and ${maxBytes} bytes`);
|
|
189
|
+
}
|
|
190
|
+
return bytes;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @param {*} value
|
|
194
|
+
* @param {number} maxBytes
|
|
195
|
+
*/
|
|
196
|
+
function projectRecordBytes(value, maxBytes) {
|
|
197
|
+
if (!(Buffer.isBuffer(value) || value instanceof Uint8Array)) {
|
|
198
|
+
throw new TypeError('action-escrow evidence: projectRecordBytes must be bytes');
|
|
199
|
+
}
|
|
200
|
+
const bytes = Buffer.from(value);
|
|
201
|
+
if (bytes.length === 0 || bytes.length > maxBytes) {
|
|
202
|
+
throw new TypeError(`action-escrow evidence: projectRecordBytes must be between 1 and ${maxBytes} bytes`);
|
|
203
|
+
}
|
|
204
|
+
return bytes;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* @param {*} value
|
|
208
|
+
* @param {string} fieldName
|
|
209
|
+
*/
|
|
210
|
+
function validFileName(value, fieldName) {
|
|
211
|
+
if (value === null)
|
|
212
|
+
return true;
|
|
213
|
+
if (typeof value !== 'string'
|
|
214
|
+
|| value.length === 0
|
|
215
|
+
|| value.length > 255
|
|
216
|
+
|| /[/\\\u0000]/.test(value)) {
|
|
217
|
+
throw new TypeError(`action-escrow evidence: ${fieldName} is invalid`);
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* @param {Buffer} bytes
|
|
223
|
+
*/
|
|
224
|
+
function sha256(bytes) {
|
|
225
|
+
return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* @param {*} value
|
|
229
|
+
*/
|
|
230
|
+
function validInstant(value) {
|
|
231
|
+
if (typeof value !== 'string')
|
|
232
|
+
return false;
|
|
233
|
+
const parsed = Date.parse(value);
|
|
234
|
+
return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* @param {*} value
|
|
238
|
+
*/
|
|
239
|
+
function canonicalSha256(value) {
|
|
240
|
+
return `sha256:${hashCanonical(value)}`;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @param {*} pkg
|
|
244
|
+
*/
|
|
245
|
+
function digestScope(pkg) {
|
|
246
|
+
const { package_digest: _digest, ...scope } = pkg;
|
|
247
|
+
return scope;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Strict raw parser for security-bearing package transport.
|
|
251
|
+
* @param {*} raw
|
|
252
|
+
* @param {{ maxBytes?: number }} [options]
|
|
253
|
+
*/
|
|
254
|
+
export function parseActionEscrowEvidencePackage(raw, { maxBytes = DEFAULT_MAX_PACKAGE_BYTES, } = {}) {
|
|
255
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
256
|
+
return { ok: false, reason: 'invalid_package_limit', value: null };
|
|
257
|
+
}
|
|
258
|
+
if (typeof raw !== 'string') {
|
|
259
|
+
return { ok: false, reason: 'package_must_be_json_text', value: null };
|
|
260
|
+
}
|
|
261
|
+
if (Buffer.byteLength(raw, 'utf8') > maxBytes) {
|
|
262
|
+
return { ok: false, reason: 'package_exceeds_size_limit', value: null };
|
|
263
|
+
}
|
|
264
|
+
const gated = strictJsonGate(raw);
|
|
265
|
+
if (!gated.ok)
|
|
266
|
+
return { ok: false, reason: gated.reason, value: null };
|
|
267
|
+
try {
|
|
268
|
+
return { ok: true, reason: 'parsed', value: JSON.parse(raw) };
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return { ok: false, reason: 'invalid_json_syntax', value: null };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* @param {*} value
|
|
276
|
+
*/
|
|
277
|
+
function normalizedRequiredParties(value) {
|
|
278
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
279
|
+
return null;
|
|
280
|
+
const result = [];
|
|
281
|
+
for (const entry of value) {
|
|
282
|
+
if (typeof entry === 'string' && entry.length > 0) {
|
|
283
|
+
result.push({ party_id: entry, role: null });
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!isRecord(entry)
|
|
287
|
+
|| typeof entry.party_id !== 'string' || entry.party_id.length === 0
|
|
288
|
+
|| typeof entry.role !== 'string' || entry.role.length === 0) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
result.push({ party_id: entry.party_id, role: entry.role });
|
|
292
|
+
}
|
|
293
|
+
const identities = result.map((entry) => `${entry.role ?? ''}\u0000${entry.party_id}`);
|
|
294
|
+
return new Set(identities).size === identities.length ? result : null;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* @param {string} stage
|
|
298
|
+
*/
|
|
299
|
+
function stageRequiresExecutedAgreement(stage) {
|
|
300
|
+
return [
|
|
301
|
+
'effective',
|
|
302
|
+
'awaiting_funding',
|
|
303
|
+
'funded',
|
|
304
|
+
'milestone_submitted',
|
|
305
|
+
'release_reserved',
|
|
306
|
+
'released',
|
|
307
|
+
'disputed',
|
|
308
|
+
'completed',
|
|
309
|
+
'release_indeterminate',
|
|
310
|
+
].includes(stage);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* @param {string} stage
|
|
314
|
+
*/
|
|
315
|
+
function stageRequiresReleaseApprovals(stage) {
|
|
316
|
+
return ['release_reserved', 'released', 'completed', 'release_indeterminate'].includes(stage);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* @param {string} stage
|
|
320
|
+
*/
|
|
321
|
+
function stageRequiresFunding(stage) {
|
|
322
|
+
return [
|
|
323
|
+
'funded',
|
|
324
|
+
'milestone_submitted',
|
|
325
|
+
'release_reserved',
|
|
326
|
+
'released',
|
|
327
|
+
'disputed',
|
|
328
|
+
'completed',
|
|
329
|
+
'release_indeterminate',
|
|
330
|
+
].includes(stage);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* @param {string} stage
|
|
334
|
+
*/
|
|
335
|
+
function stageAllowsFunding(stage) {
|
|
336
|
+
return [
|
|
337
|
+
'funded',
|
|
338
|
+
'milestone_submitted',
|
|
339
|
+
'release_reserved',
|
|
340
|
+
'released',
|
|
341
|
+
'disputed',
|
|
342
|
+
'completed',
|
|
343
|
+
'release_indeterminate',
|
|
344
|
+
].includes(stage);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* @param {string} stage
|
|
348
|
+
*/
|
|
349
|
+
function stageRequiresMilestone(stage) {
|
|
350
|
+
return [
|
|
351
|
+
'milestone_submitted',
|
|
352
|
+
'release_reserved',
|
|
353
|
+
'released',
|
|
354
|
+
'disputed',
|
|
355
|
+
'completed',
|
|
356
|
+
'release_indeterminate',
|
|
357
|
+
].includes(stage);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* @param {string} stage
|
|
361
|
+
*/
|
|
362
|
+
function stageAllowsMilestone(stage) {
|
|
363
|
+
return [
|
|
364
|
+
'milestone_submitted',
|
|
365
|
+
'release_reserved',
|
|
366
|
+
'released',
|
|
367
|
+
'disputed',
|
|
368
|
+
'completed',
|
|
369
|
+
'release_indeterminate',
|
|
370
|
+
].includes(stage);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* @param {string} stage
|
|
374
|
+
*/
|
|
375
|
+
function stageAllowsReleaseApprovals(stage) {
|
|
376
|
+
return [
|
|
377
|
+
'milestone_submitted',
|
|
378
|
+
'release_reserved',
|
|
379
|
+
'released',
|
|
380
|
+
'disputed',
|
|
381
|
+
'completed',
|
|
382
|
+
'release_indeterminate',
|
|
383
|
+
].includes(stage);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* @param {string} stage
|
|
387
|
+
*/
|
|
388
|
+
function stageRequiresRelease(stage) {
|
|
389
|
+
return ['release_reserved', 'released', 'completed', 'release_indeterminate'].includes(stage);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* @param {string} stage
|
|
393
|
+
*/
|
|
394
|
+
function stageAllowsRelease(stage) {
|
|
395
|
+
return [
|
|
396
|
+
'milestone_submitted',
|
|
397
|
+
'release_reserved',
|
|
398
|
+
'released',
|
|
399
|
+
'disputed',
|
|
400
|
+
'amendment_pending',
|
|
401
|
+
'completed',
|
|
402
|
+
'release_indeterminate',
|
|
403
|
+
].includes(stage);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Build a portable evidence manifest. The final document bytes are hashed but
|
|
407
|
+
* not embedded; transport them beside the JSON manifest.
|
|
408
|
+
*
|
|
409
|
+
* @param {{ now?: number, maxDocumentBytes?: number, maxProjectRecordBytes?: number }} [limits]
|
|
410
|
+
*/
|
|
411
|
+
export function buildActionEscrowEvidencePackage({ agreementId, stage, binding, documentBytes: rawDocumentBytes, documentFileName = null, documentExecution = null, agreementAcceptances = [], releaseApprovals = [], fundingStatement = null, milestones = [], release = null, stateRecord, amendments = [], verificationProfile, projectRecordBytes: rawProjectRecordBytes = null, projectRecordFileName = null, projectRecordProvider = null, projectRecordSnapshotDigest = null, } = {}, { now = 0, maxDocumentBytes = DEFAULT_MAX_DOCUMENT_BYTES, maxProjectRecordBytes = DEFAULT_MAX_PROJECT_RECORD_BYTES, } = {}) {
|
|
412
|
+
if (typeof agreementId !== 'string' || agreementId.length === 0) {
|
|
413
|
+
throw new TypeError('action-escrow evidence: agreementId is required');
|
|
414
|
+
}
|
|
415
|
+
if (!ACTION_ESCROW_EVIDENCE_STAGES.includes(stage)) {
|
|
416
|
+
throw new TypeError('action-escrow evidence: stage is not in the closed set');
|
|
417
|
+
}
|
|
418
|
+
if (!Number.isSafeInteger(maxDocumentBytes) || maxDocumentBytes <= 0) {
|
|
419
|
+
throw new TypeError('action-escrow evidence: maxDocumentBytes must be a positive safe integer');
|
|
420
|
+
}
|
|
421
|
+
const bytes = documentBytes(rawDocumentBytes, maxDocumentBytes);
|
|
422
|
+
validFileName(documentFileName, 'documentFileName');
|
|
423
|
+
const hasProjectRecord = rawProjectRecordBytes !== null
|
|
424
|
+
|| projectRecordProvider !== null
|
|
425
|
+
|| projectRecordSnapshotDigest !== null
|
|
426
|
+
|| projectRecordFileName !== null;
|
|
427
|
+
let projectBytes = null;
|
|
428
|
+
if (hasProjectRecord) {
|
|
429
|
+
if (!Number.isSafeInteger(maxProjectRecordBytes)
|
|
430
|
+
|| maxProjectRecordBytes <= 0) {
|
|
431
|
+
throw new TypeError('action-escrow evidence: maxProjectRecordBytes must be a positive safe integer');
|
|
432
|
+
}
|
|
433
|
+
if (typeof projectRecordProvider !== 'string'
|
|
434
|
+
|| projectRecordProvider.length === 0
|
|
435
|
+
|| projectRecordProvider.length > 128
|
|
436
|
+
// HASH.test() ToString-coerces its argument at runtime regardless;
|
|
437
|
+
// String() here makes that coercion explicit for the type checker.
|
|
438
|
+
|| !HASH.test(String(projectRecordSnapshotDigest))) {
|
|
439
|
+
throw new TypeError('action-escrow evidence: project-record provider and snapshot digest are required');
|
|
440
|
+
}
|
|
441
|
+
validFileName(projectRecordFileName, 'projectRecordFileName');
|
|
442
|
+
projectBytes = projectRecordBytes(rawProjectRecordBytes, maxProjectRecordBytes);
|
|
443
|
+
}
|
|
444
|
+
const body = portableJsonCopy({
|
|
445
|
+
version: hasProjectRecord
|
|
446
|
+
? ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION
|
|
447
|
+
: ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION,
|
|
448
|
+
agreement_id: agreementId,
|
|
449
|
+
stage,
|
|
450
|
+
binding,
|
|
451
|
+
document: {
|
|
452
|
+
media_type: 'application/pdf',
|
|
453
|
+
digest: sha256(bytes),
|
|
454
|
+
byte_length: bytes.length,
|
|
455
|
+
...(documentFileName === null ? {} : { file_name: documentFileName }),
|
|
456
|
+
},
|
|
457
|
+
...(hasProjectRecord
|
|
458
|
+
? {
|
|
459
|
+
project_record: {
|
|
460
|
+
media_type: 'application/json',
|
|
461
|
+
// projectBytes is non-null here: hasProjectRecord true means the
|
|
462
|
+
// `if (hasProjectRecord)` block above already assigned it.
|
|
463
|
+
digest: sha256(projectBytes),
|
|
464
|
+
byte_length: projectBytes.length,
|
|
465
|
+
...(projectRecordFileName === null
|
|
466
|
+
? {}
|
|
467
|
+
: { file_name: projectRecordFileName }),
|
|
468
|
+
provider: projectRecordProvider,
|
|
469
|
+
snapshot_digest: projectRecordSnapshotDigest,
|
|
470
|
+
},
|
|
471
|
+
}
|
|
472
|
+
: {}),
|
|
473
|
+
document_execution: documentExecution,
|
|
474
|
+
agreement_acceptances: agreementAcceptances,
|
|
475
|
+
release_approvals: releaseApprovals,
|
|
476
|
+
funding_statement: fundingStatement,
|
|
477
|
+
milestones,
|
|
478
|
+
release: release ?? {
|
|
479
|
+
reservation: null,
|
|
480
|
+
provider_request: null,
|
|
481
|
+
provider_statement: null,
|
|
482
|
+
execution_record: null,
|
|
483
|
+
},
|
|
484
|
+
state_record: stateRecord,
|
|
485
|
+
amendments,
|
|
486
|
+
verification_profile: verificationProfile,
|
|
487
|
+
assembled_at: toIso(now),
|
|
488
|
+
limitations: [...LIMITATIONS],
|
|
489
|
+
});
|
|
490
|
+
return deepFreeze({
|
|
491
|
+
...body,
|
|
492
|
+
package_digest: canonicalSha256(body),
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function resultFailure(reason, checks, details = {}) {
|
|
496
|
+
return {
|
|
497
|
+
valid: false,
|
|
498
|
+
reason,
|
|
499
|
+
checks,
|
|
500
|
+
...details,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function callVerifier(verifier, value, context) {
|
|
504
|
+
if (typeof verifier !== 'function')
|
|
505
|
+
return { valid: false, reason: 'verifier_required' };
|
|
506
|
+
try {
|
|
507
|
+
const result = await verifier(value, context);
|
|
508
|
+
return isRecord(result) ? result : { valid: false, reason: 'malformed_verifier_result' };
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return { valid: false, reason: 'verifier_threw' };
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Re-perform every package join using relying-party-owned component verifiers.
|
|
516
|
+
*
|
|
517
|
+
* Component verifiers are configuration, never read from the package. Their
|
|
518
|
+
* returned binding fields are checked again here so a valid artifact for one
|
|
519
|
+
* agreement, document, party, or action cannot be relabeled into another slot.
|
|
520
|
+
*
|
|
521
|
+
* @param {*} pkg
|
|
522
|
+
*/
|
|
523
|
+
export async function verifyActionEscrowEvidencePackage(pkg, { documentBytes: rawDocumentBytes, projectRecordBytes: rawProjectRecordBytes, verifyBinding, verifyProjectRecord, verifyProfile, verifyDocumentExecution, verifyAgreementAcceptance, verifyReleaseApproval, verifyFunding, verifyMilestone, verifyRelease, verifyAmendment, verifyState, expectedAgreementId, now, maxDocumentBytes = DEFAULT_MAX_DOCUMENT_BYTES, maxProjectRecordBytes = DEFAULT_MAX_PROJECT_RECORD_BYTES, } = {}) {
|
|
524
|
+
const checks = {
|
|
525
|
+
structure: false,
|
|
526
|
+
package_digest: false,
|
|
527
|
+
time: false,
|
|
528
|
+
document: false,
|
|
529
|
+
project_record: false,
|
|
530
|
+
binding: false,
|
|
531
|
+
profile: false,
|
|
532
|
+
document_execution: false,
|
|
533
|
+
agreement_acceptances: false,
|
|
534
|
+
amendments: false,
|
|
535
|
+
state: false,
|
|
536
|
+
release_approvals: false,
|
|
537
|
+
funding: false,
|
|
538
|
+
milestones: false,
|
|
539
|
+
release: false,
|
|
540
|
+
};
|
|
541
|
+
try {
|
|
542
|
+
const contractorPackage = pkg?.version
|
|
543
|
+
=== ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION;
|
|
544
|
+
const allowedTop = contractorPackage ? CONTRACTOR_TOP_KEYS : TOP_KEYS;
|
|
545
|
+
const requiredTop = new Set(allowedTop);
|
|
546
|
+
checks.structure = exactKeys(pkg, allowedTop, requiredTop)
|
|
547
|
+
&& (pkg.version === ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION
|
|
548
|
+
|| contractorPackage)
|
|
549
|
+
&& typeof pkg.agreement_id === 'string' && pkg.agreement_id.length > 0
|
|
550
|
+
&& ACTION_ESCROW_EVIDENCE_STAGES.includes(pkg.stage)
|
|
551
|
+
&& HASH.test(pkg.package_digest)
|
|
552
|
+
&& exactKeys(pkg.document, DOCUMENT_KEYS, new Set(['media_type', 'digest', 'byte_length']))
|
|
553
|
+
&& pkg.document.media_type === 'application/pdf'
|
|
554
|
+
&& HASH.test(pkg.document.digest)
|
|
555
|
+
&& Number.isSafeInteger(pkg.document.byte_length) && pkg.document.byte_length > 0
|
|
556
|
+
&& (!contractorPackage
|
|
557
|
+
|| (exactKeys(pkg.project_record, PROJECT_RECORD_KEYS, new Set([
|
|
558
|
+
'media_type',
|
|
559
|
+
'digest',
|
|
560
|
+
'byte_length',
|
|
561
|
+
'provider',
|
|
562
|
+
'snapshot_digest',
|
|
563
|
+
]))
|
|
564
|
+
&& pkg.project_record.media_type === 'application/json'
|
|
565
|
+
&& HASH.test(pkg.project_record.digest)
|
|
566
|
+
&& Number.isSafeInteger(pkg.project_record.byte_length)
|
|
567
|
+
&& pkg.project_record.byte_length > 0
|
|
568
|
+
&& typeof pkg.project_record.provider === 'string'
|
|
569
|
+
&& pkg.project_record.provider.length > 0
|
|
570
|
+
&& pkg.project_record.provider.length <= 128
|
|
571
|
+
&& HASH.test(pkg.project_record.snapshot_digest)))
|
|
572
|
+
&& (pkg.document_execution === null || isRecord(pkg.document_execution))
|
|
573
|
+
&& Array.isArray(pkg.agreement_acceptances)
|
|
574
|
+
&& Array.isArray(pkg.release_approvals)
|
|
575
|
+
&& Array.isArray(pkg.milestones)
|
|
576
|
+
&& exactKeys(pkg.release, RELEASE_KEYS)
|
|
577
|
+
&& exactKeys(pkg.state_record, STATE_RECORD_KEYS)
|
|
578
|
+
&& Array.isArray(pkg.amendments)
|
|
579
|
+
&& Array.isArray(pkg.limitations)
|
|
580
|
+
&& pkg.limitations.length === LIMITATIONS.length
|
|
581
|
+
&& pkg.limitations.every((entry, index) => entry === LIMITATIONS[index]);
|
|
582
|
+
if (!checks.structure)
|
|
583
|
+
return resultFailure('malformed_evidence_package', checks);
|
|
584
|
+
if (expectedAgreementId !== undefined
|
|
585
|
+
&& (typeof expectedAgreementId !== 'string' || pkg.agreement_id !== expectedAgreementId)) {
|
|
586
|
+
return resultFailure('agreement_id_mismatch', checks);
|
|
587
|
+
}
|
|
588
|
+
checks.package_digest = canonicalSha256(digestScope(pkg)) === pkg.package_digest;
|
|
589
|
+
if (!checks.package_digest)
|
|
590
|
+
return resultFailure('package_digest_mismatch', checks);
|
|
591
|
+
const assembledAt = Date.parse(pkg.assembled_at);
|
|
592
|
+
const evaluation = now === undefined
|
|
593
|
+
? null
|
|
594
|
+
: (now instanceof Date ? now.getTime() : typeof now === 'number' ? now : Date.parse(now));
|
|
595
|
+
checks.time = Number.isFinite(assembledAt)
|
|
596
|
+
&& (evaluation === null || (Number.isFinite(evaluation) && assembledAt <= evaluation));
|
|
597
|
+
if (!checks.time)
|
|
598
|
+
return resultFailure('invalid_or_future_assembled_at', checks);
|
|
599
|
+
if (!Number.isSafeInteger(maxDocumentBytes) || maxDocumentBytes <= 0) {
|
|
600
|
+
return resultFailure('invalid_document_limit', checks);
|
|
601
|
+
}
|
|
602
|
+
const bytes = documentBytes(rawDocumentBytes, maxDocumentBytes);
|
|
603
|
+
checks.document = bytes.length === pkg.document.byte_length && sha256(bytes) === pkg.document.digest;
|
|
604
|
+
if (!checks.document)
|
|
605
|
+
return resultFailure('document_bytes_mismatch', checks);
|
|
606
|
+
const bindingResult = await callVerifier(verifyBinding, pkg.binding, {
|
|
607
|
+
expectedAgreementId: pkg.agreement_id,
|
|
608
|
+
expectedDocumentDigest: pkg.document.digest,
|
|
609
|
+
});
|
|
610
|
+
const contractorBinding = HASH.test(pkg.binding?.release_action?.template?.project_record_snapshot_digest);
|
|
611
|
+
const requiredParties = normalizedRequiredParties(bindingResult.required_parties);
|
|
612
|
+
checks.binding = bindingResult.valid === true
|
|
613
|
+
&& bindingResult.agreement_id === pkg.agreement_id
|
|
614
|
+
&& bindingResult.document_digest === pkg.document.digest
|
|
615
|
+
&& HASH.test(bindingResult.binding_digest)
|
|
616
|
+
&& HASH.test(bindingResult.action_digest)
|
|
617
|
+
&& (bindingResult.supersedes_digest === null || HASH.test(bindingResult.supersedes_digest))
|
|
618
|
+
&& requiredParties !== null;
|
|
619
|
+
if (!checks.binding) {
|
|
620
|
+
return resultFailure('binding_verification_failed', checks, {
|
|
621
|
+
binding_reason: bindingResult.reason ?? null,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (contractorBinding !== contractorPackage) {
|
|
625
|
+
return resultFailure('evidence_profile_mismatch', checks);
|
|
626
|
+
}
|
|
627
|
+
if (contractorPackage) {
|
|
628
|
+
if (!HASH.test(bindingResult.project_record_snapshot_digest)
|
|
629
|
+
|| bindingResult.project_record_snapshot_digest
|
|
630
|
+
!== pkg.project_record.snapshot_digest
|
|
631
|
+
|| !Number.isSafeInteger(maxProjectRecordBytes)
|
|
632
|
+
|| maxProjectRecordBytes <= 0) {
|
|
633
|
+
return resultFailure('project_record_binding_failed', checks);
|
|
634
|
+
}
|
|
635
|
+
let sourceBytes;
|
|
636
|
+
try {
|
|
637
|
+
sourceBytes = projectRecordBytes(rawProjectRecordBytes, maxProjectRecordBytes);
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
return resultFailure('project_record_bytes_mismatch', checks);
|
|
641
|
+
}
|
|
642
|
+
if (sourceBytes.length !== pkg.project_record.byte_length
|
|
643
|
+
|| sha256(sourceBytes) !== pkg.project_record.digest) {
|
|
644
|
+
return resultFailure('project_record_bytes_mismatch', checks);
|
|
645
|
+
}
|
|
646
|
+
const gated = strictJsonGate(sourceBytes.toString('utf8'));
|
|
647
|
+
let projectRecord = null;
|
|
648
|
+
if (gated.ok) {
|
|
649
|
+
try {
|
|
650
|
+
projectRecord = JSON.parse(sourceBytes.toString('utf8'));
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
projectRecord = null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!isRecord(projectRecord)) {
|
|
657
|
+
return resultFailure('project_record_json_invalid', checks);
|
|
658
|
+
}
|
|
659
|
+
const projectResult = await callVerifier(verifyProjectRecord, projectRecord, {
|
|
660
|
+
agreementId: pkg.agreement_id,
|
|
661
|
+
bindingDigest: bindingResult.binding_digest,
|
|
662
|
+
actionDigest: bindingResult.action_digest,
|
|
663
|
+
provider: pkg.project_record.provider,
|
|
664
|
+
snapshotDigest: pkg.project_record.snapshot_digest,
|
|
665
|
+
});
|
|
666
|
+
checks.project_record = projectResult.valid === true
|
|
667
|
+
&& projectResult.authorizes_action === false
|
|
668
|
+
&& projectResult.establishes_acceptance === false
|
|
669
|
+
&& projectResult.provider === pkg.project_record.provider
|
|
670
|
+
&& projectResult.snapshot_digest === pkg.project_record.snapshot_digest;
|
|
671
|
+
if (!checks.project_record) {
|
|
672
|
+
return resultFailure('project_record_verification_failed', checks, {
|
|
673
|
+
project_record_reason: projectResult.reason ?? null,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
checks.project_record = true;
|
|
679
|
+
}
|
|
680
|
+
const profileResult = await callVerifier(verifyProfile, pkg.verification_profile, {
|
|
681
|
+
agreementId: pkg.agreement_id,
|
|
682
|
+
bindingDigest: bindingResult.binding_digest,
|
|
683
|
+
actionDigest: bindingResult.action_digest,
|
|
684
|
+
});
|
|
685
|
+
const requiredReleaseParties = normalizedRequiredParties(profileResult.required_release_parties);
|
|
686
|
+
checks.profile = profileResult.valid === true
|
|
687
|
+
&& profileResult.agreement_id === pkg.agreement_id
|
|
688
|
+
&& profileResult.binding_digest === bindingResult.binding_digest
|
|
689
|
+
&& profileResult.action_digest === bindingResult.action_digest
|
|
690
|
+
&& HASH.test(profileResult.profile_digest)
|
|
691
|
+
&& requiredReleaseParties !== null;
|
|
692
|
+
if (!checks.profile) {
|
|
693
|
+
return resultFailure('verification_profile_failed', checks, {
|
|
694
|
+
profile_reason: profileResult.reason ?? null,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (pkg.document_execution !== null) {
|
|
698
|
+
const executionResult = await callVerifier(verifyDocumentExecution, pkg.document_execution, {
|
|
699
|
+
agreementId: pkg.agreement_id,
|
|
700
|
+
bindingDigest: bindingResult.binding_digest,
|
|
701
|
+
documentDigest: pkg.document.digest,
|
|
702
|
+
});
|
|
703
|
+
checks.document_execution = executionResult.valid === true
|
|
704
|
+
&& executionResult.authorizes_action === false
|
|
705
|
+
&& executionResult.agreement_id === pkg.agreement_id
|
|
706
|
+
&& executionResult.binding_digest === bindingResult.binding_digest
|
|
707
|
+
&& executionResult.document_digest === pkg.document.digest
|
|
708
|
+
&& executionResult.state === 'executed';
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
checks.document_execution = !stageRequiresExecutedAgreement(pkg.stage);
|
|
712
|
+
}
|
|
713
|
+
if (!checks.document_execution) {
|
|
714
|
+
return resultFailure('document_execution_verification_failed', checks);
|
|
715
|
+
}
|
|
716
|
+
// requiredParties is non-null here: checks.binding (verified above, with
|
|
717
|
+
// an early return when false) requires requiredParties !== null.
|
|
718
|
+
const requiredAgreementByIdentity = new Map(requiredParties.map((entry) => [`${entry.role ?? ''}\u0000${entry.party_id}`, entry]));
|
|
719
|
+
const seenAgreementParties = new Set();
|
|
720
|
+
const seenAgreementKeys = new Set();
|
|
721
|
+
let agreementAcceptancesValid = true;
|
|
722
|
+
for (const acceptance of pkg.agreement_acceptances) {
|
|
723
|
+
if (!exactKeys(acceptance, PARTY_EVIDENCE_KEYS)
|
|
724
|
+
|| typeof acceptance.party_id !== 'string'
|
|
725
|
+
|| typeof acceptance.role !== 'string') {
|
|
726
|
+
agreementAcceptancesValid = false;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
const identity = `${acceptance.role}\u0000${acceptance.party_id}`;
|
|
730
|
+
if (!requiredAgreementByIdentity.has(identity) || seenAgreementParties.has(identity)) {
|
|
731
|
+
agreementAcceptancesValid = false;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
const result = await callVerifier(verifyAgreementAcceptance, acceptance.evidence, {
|
|
735
|
+
partyId: acceptance.party_id,
|
|
736
|
+
role: acceptance.role,
|
|
737
|
+
agreementId: pkg.agreement_id,
|
|
738
|
+
bindingDigest: bindingResult.binding_digest,
|
|
739
|
+
documentDigest: pkg.document.digest,
|
|
740
|
+
});
|
|
741
|
+
if (result.valid !== true
|
|
742
|
+
|| result.accepts_agreement !== true
|
|
743
|
+
|| result.authorizes_action !== false
|
|
744
|
+
|| result.agreement_id !== pkg.agreement_id
|
|
745
|
+
|| result.party_id !== acceptance.party_id
|
|
746
|
+
|| result.role !== acceptance.role
|
|
747
|
+
|| result.binding_digest !== bindingResult.binding_digest
|
|
748
|
+
|| result.document_digest !== pkg.document.digest
|
|
749
|
+
|| typeof result.principal_key_id !== 'string'
|
|
750
|
+
|| result.principal_key_id.length === 0
|
|
751
|
+
|| seenAgreementKeys.has(result.principal_key_id)) {
|
|
752
|
+
agreementAcceptancesValid = false;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
seenAgreementParties.add(identity);
|
|
756
|
+
seenAgreementKeys.add(result.principal_key_id);
|
|
757
|
+
}
|
|
758
|
+
if (stageRequiresExecutedAgreement(pkg.stage)) {
|
|
759
|
+
agreementAcceptancesValid = agreementAcceptancesValid
|
|
760
|
+
&& seenAgreementParties.size === requiredAgreementByIdentity.size;
|
|
761
|
+
}
|
|
762
|
+
checks.agreement_acceptances = agreementAcceptancesValid;
|
|
763
|
+
if (!checks.agreement_acceptances) {
|
|
764
|
+
return resultFailure('agreement_acceptance_verification_failed', checks);
|
|
765
|
+
}
|
|
766
|
+
const amendmentResults = [];
|
|
767
|
+
let amendmentsValid = true;
|
|
768
|
+
let expectedNextBinding = bindingResult.binding_digest;
|
|
769
|
+
for (let index = pkg.amendments.length - 1; index >= 0; index -= 1) {
|
|
770
|
+
const result = await callVerifier(verifyAmendment, pkg.amendments[index], {
|
|
771
|
+
agreementId: pkg.agreement_id,
|
|
772
|
+
expectedNextBindingDigest: expectedNextBinding,
|
|
773
|
+
profileDigest: profileResult.profile_digest,
|
|
774
|
+
});
|
|
775
|
+
amendmentResults.unshift(result);
|
|
776
|
+
if (result.valid !== true
|
|
777
|
+
|| !HASH.test(result.amendment_digest)
|
|
778
|
+
|| !HASH.test(result.previous_binding_digest)
|
|
779
|
+
|| result.next_binding_digest !== expectedNextBinding) {
|
|
780
|
+
amendmentsValid = false;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
expectedNextBinding = result.previous_binding_digest;
|
|
784
|
+
}
|
|
785
|
+
if (bindingResult.supersedes_digest === null) {
|
|
786
|
+
amendmentsValid = amendmentsValid && pkg.amendments.length === 0;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
const finalAmendment = amendmentResults.at(-1);
|
|
790
|
+
amendmentsValid = amendmentsValid
|
|
791
|
+
&& pkg.amendments.length > 0
|
|
792
|
+
&& finalAmendment?.previous_binding_digest === bindingResult.supersedes_digest
|
|
793
|
+
&& finalAmendment?.next_binding_digest === bindingResult.binding_digest;
|
|
794
|
+
}
|
|
795
|
+
checks.amendments = amendmentsValid;
|
|
796
|
+
if (!checks.amendments) {
|
|
797
|
+
return resultFailure('amendment_chain_verification_failed', checks);
|
|
798
|
+
}
|
|
799
|
+
const amendmentDigests = amendmentResults.map((result) => result.amendment_digest);
|
|
800
|
+
const stateResult = await callVerifier(verifyState, pkg.state_record, {
|
|
801
|
+
agreementId: pkg.agreement_id,
|
|
802
|
+
bindingDigest: bindingResult.binding_digest,
|
|
803
|
+
actionDigest: bindingResult.action_digest,
|
|
804
|
+
profileDigest: profileResult.profile_digest,
|
|
805
|
+
amendmentDigests,
|
|
806
|
+
stage: pkg.stage,
|
|
807
|
+
});
|
|
808
|
+
checks.state = stateResult.valid === true
|
|
809
|
+
&& stateResult.agreement_id === pkg.agreement_id
|
|
810
|
+
&& stateResult.binding_digest === bindingResult.binding_digest
|
|
811
|
+
&& stateResult.action_digest === bindingResult.action_digest
|
|
812
|
+
&& stateResult.profile_digest === profileResult.profile_digest
|
|
813
|
+
&& stateResult.state === pkg.stage
|
|
814
|
+
&& Number.isSafeInteger(stateResult.revision) && stateResult.revision >= 0
|
|
815
|
+
&& Array.isArray(stateResult.amendment_digests)
|
|
816
|
+
&& stateResult.amendment_digests.length === amendmentDigests.length
|
|
817
|
+
&& stateResult.amendment_digests.every((digest, index) => digest === amendmentDigests[index]);
|
|
818
|
+
if (!checks.state) {
|
|
819
|
+
return resultFailure('state_record_verification_failed', checks, {
|
|
820
|
+
state_reason: stateResult.reason ?? null,
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
if (pkg.funding_statement !== null) {
|
|
824
|
+
if (!stageAllowsFunding(pkg.stage)) {
|
|
825
|
+
return resultFailure('unexpected_funding_statement', checks);
|
|
826
|
+
}
|
|
827
|
+
const fundingResult = await callVerifier(verifyFunding, pkg.funding_statement, {
|
|
828
|
+
agreementId: pkg.agreement_id,
|
|
829
|
+
bindingDigest: bindingResult.binding_digest,
|
|
830
|
+
actionDigest: bindingResult.action_digest,
|
|
831
|
+
});
|
|
832
|
+
checks.funding = fundingResult.valid === true
|
|
833
|
+
&& fundingResult.agreement_id === pkg.agreement_id
|
|
834
|
+
&& fundingResult.binding_digest === bindingResult.binding_digest
|
|
835
|
+
&& fundingResult.action_digest === bindingResult.action_digest
|
|
836
|
+
&& fundingResult.state === 'funded';
|
|
837
|
+
if (!checks.funding) {
|
|
838
|
+
return resultFailure('funding_statement_verification_failed', checks, {
|
|
839
|
+
funding_reason: fundingResult.reason ?? null,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
checks.funding = !stageRequiresFunding(pkg.stage);
|
|
845
|
+
if (!checks.funding)
|
|
846
|
+
return resultFailure('funding_statement_missing', checks);
|
|
847
|
+
}
|
|
848
|
+
let milestonesValid = true;
|
|
849
|
+
const milestoneIds = new Set();
|
|
850
|
+
const milestoneEvidenceDigests = [];
|
|
851
|
+
if (pkg.milestones.length > 0 && !stageAllowsMilestone(pkg.stage)) {
|
|
852
|
+
return resultFailure('unexpected_milestone_evidence', checks);
|
|
853
|
+
}
|
|
854
|
+
for (const milestone of pkg.milestones) {
|
|
855
|
+
if (!exactKeys(milestone, MILESTONE_KEYS)
|
|
856
|
+
|| typeof milestone.milestone_id !== 'string' || milestone.milestone_id.length === 0
|
|
857
|
+
|| milestoneIds.has(milestone.milestone_id)) {
|
|
858
|
+
milestonesValid = false;
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
milestoneIds.add(milestone.milestone_id);
|
|
862
|
+
const result = await callVerifier(verifyMilestone, milestone, {
|
|
863
|
+
agreementId: pkg.agreement_id,
|
|
864
|
+
bindingDigest: bindingResult.binding_digest,
|
|
865
|
+
actionDigest: bindingResult.action_digest,
|
|
866
|
+
});
|
|
867
|
+
if (result.valid !== true
|
|
868
|
+
|| result.agreement_id !== pkg.agreement_id
|
|
869
|
+
|| result.milestone_id !== milestone.milestone_id
|
|
870
|
+
|| result.binding_digest !== bindingResult.binding_digest
|
|
871
|
+
|| result.action_digest !== bindingResult.action_digest
|
|
872
|
+
|| !HASH.test(result.evidence_digest)) {
|
|
873
|
+
milestonesValid = false;
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
milestoneEvidenceDigests.push(result.evidence_digest);
|
|
877
|
+
}
|
|
878
|
+
if (stageRequiresMilestone(pkg.stage))
|
|
879
|
+
milestonesValid = milestonesValid && milestoneIds.size > 0;
|
|
880
|
+
checks.milestones = milestonesValid;
|
|
881
|
+
if (!checks.milestones)
|
|
882
|
+
return resultFailure('milestone_verification_failed', checks);
|
|
883
|
+
// requiredReleaseParties is non-null here: checks.profile (verified
|
|
884
|
+
// above, with an early return when false) requires it !== null.
|
|
885
|
+
const requiredReleaseByIdentity = new Map(requiredReleaseParties.map((entry) => [`${entry.role ?? ''}\u0000${entry.party_id}`, entry]));
|
|
886
|
+
const seenReleaseParties = new Set();
|
|
887
|
+
const seenReleaseKeys = new Set();
|
|
888
|
+
let releaseApprovalsValid = true;
|
|
889
|
+
if (pkg.release_approvals.length > 0 && !stageAllowsReleaseApprovals(pkg.stage)) {
|
|
890
|
+
return resultFailure('unexpected_release_approval', checks);
|
|
891
|
+
}
|
|
892
|
+
const releaseExecutionAt = pkg.release.execution_record?.at ?? null;
|
|
893
|
+
for (const [approvalIndex, approval] of pkg.release_approvals.entries()) {
|
|
894
|
+
if (!exactKeys(approval, PARTY_EVIDENCE_KEYS)
|
|
895
|
+
|| typeof approval.party_id !== 'string'
|
|
896
|
+
|| typeof approval.role !== 'string') {
|
|
897
|
+
releaseApprovalsValid = false;
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
const identity = `${approval.role}\u0000${approval.party_id}`;
|
|
901
|
+
if (!requiredReleaseByIdentity.has(identity) || seenReleaseParties.has(identity)) {
|
|
902
|
+
releaseApprovalsValid = false;
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
const result = await callVerifier(verifyReleaseApproval, approval.evidence, {
|
|
906
|
+
partyId: approval.party_id,
|
|
907
|
+
role: approval.role,
|
|
908
|
+
agreementId: pkg.agreement_id,
|
|
909
|
+
bindingDigest: bindingResult.binding_digest,
|
|
910
|
+
actionDigest: bindingResult.action_digest,
|
|
911
|
+
milestoneEvidenceDigests,
|
|
912
|
+
approvalIndex,
|
|
913
|
+
stateRecord: pkg.state_record,
|
|
914
|
+
});
|
|
915
|
+
if (result.valid !== true
|
|
916
|
+
|| result.authorizes_action !== true
|
|
917
|
+
|| result.outcome !== 'approved'
|
|
918
|
+
|| result.agreement_id !== pkg.agreement_id
|
|
919
|
+
|| result.party_id !== approval.party_id
|
|
920
|
+
|| result.role !== approval.role
|
|
921
|
+
|| result.binding_digest !== bindingResult.binding_digest
|
|
922
|
+
|| result.action_digest !== bindingResult.action_digest
|
|
923
|
+
|| !Array.isArray(result.milestone_evidence_digests)
|
|
924
|
+
|| result.milestone_evidence_digests.length !== milestoneEvidenceDigests.length
|
|
925
|
+
|| result.milestone_evidence_digests.some((digest, index) => digest !== milestoneEvidenceDigests[index])
|
|
926
|
+
|| typeof result.principal_key_id !== 'string'
|
|
927
|
+
|| result.principal_key_id.length === 0
|
|
928
|
+
|| !validInstant(result.issued_at)
|
|
929
|
+
|| !validInstant(result.expires_at)
|
|
930
|
+
|| !validInstant(result.admitted_at)
|
|
931
|
+
|| Date.parse(result.issued_at) > Date.parse(result.admitted_at)
|
|
932
|
+
|| Date.parse(result.expires_at) <= Date.parse(result.admitted_at)
|
|
933
|
+
|| (releaseExecutionAt !== null
|
|
934
|
+
&& (!validInstant(releaseExecutionAt)
|
|
935
|
+
|| Date.parse(result.issued_at) > Date.parse(releaseExecutionAt)
|
|
936
|
+
|| Date.parse(result.expires_at) <= Date.parse(releaseExecutionAt)))
|
|
937
|
+
|| seenReleaseKeys.has(result.principal_key_id)) {
|
|
938
|
+
releaseApprovalsValid = false;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
seenReleaseParties.add(identity);
|
|
942
|
+
seenReleaseKeys.add(result.principal_key_id);
|
|
943
|
+
}
|
|
944
|
+
if (stageRequiresReleaseApprovals(pkg.stage)) {
|
|
945
|
+
releaseApprovalsValid = releaseApprovalsValid
|
|
946
|
+
&& seenReleaseParties.size === requiredReleaseByIdentity.size;
|
|
947
|
+
}
|
|
948
|
+
checks.release_approvals = releaseApprovalsValid;
|
|
949
|
+
if (!checks.release_approvals) {
|
|
950
|
+
return resultFailure('release_approval_verification_failed', checks);
|
|
951
|
+
}
|
|
952
|
+
const hasReleaseArtifacts = Object.values(pkg.release).some((entry) => entry !== null);
|
|
953
|
+
if (hasReleaseArtifacts) {
|
|
954
|
+
if (!stageAllowsRelease(pkg.stage)) {
|
|
955
|
+
return resultFailure('release_artifacts_not_allowed_for_stage', checks);
|
|
956
|
+
}
|
|
957
|
+
const releaseResult = await callVerifier(verifyRelease, pkg.release, {
|
|
958
|
+
agreementId: pkg.agreement_id,
|
|
959
|
+
bindingDigest: bindingResult.binding_digest,
|
|
960
|
+
actionDigest: bindingResult.action_digest,
|
|
961
|
+
stage: pkg.stage,
|
|
962
|
+
});
|
|
963
|
+
const expectedReleaseState = pkg.stage === 'release_indeterminate'
|
|
964
|
+
? 'indeterminate'
|
|
965
|
+
: pkg.stage === 'release_reserved'
|
|
966
|
+
? 'reserved'
|
|
967
|
+
: ['released', 'completed'].includes(pkg.stage)
|
|
968
|
+
? 'released'
|
|
969
|
+
: 'not_released';
|
|
970
|
+
checks.release = releaseResult.valid === true
|
|
971
|
+
&& releaseResult.agreement_id === pkg.agreement_id
|
|
972
|
+
&& releaseResult.binding_digest === bindingResult.binding_digest
|
|
973
|
+
&& releaseResult.action_digest === bindingResult.action_digest
|
|
974
|
+
&& releaseResult.state === expectedReleaseState;
|
|
975
|
+
if (!checks.release) {
|
|
976
|
+
return resultFailure('release_verification_failed', checks, {
|
|
977
|
+
release_reason: releaseResult.reason ?? null,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
checks.release = !stageRequiresRelease(pkg.stage);
|
|
983
|
+
if (!checks.release)
|
|
984
|
+
return resultFailure('release_artifact_missing', checks);
|
|
985
|
+
}
|
|
986
|
+
return {
|
|
987
|
+
valid: true,
|
|
988
|
+
reason: 'verified',
|
|
989
|
+
checks,
|
|
990
|
+
package_digest: pkg.package_digest,
|
|
991
|
+
agreement_id: pkg.agreement_id,
|
|
992
|
+
binding_digest: bindingResult.binding_digest,
|
|
993
|
+
action_digest: bindingResult.action_digest,
|
|
994
|
+
profile_digest: profileResult.profile_digest,
|
|
995
|
+
project_record_snapshot_digest: bindingResult.project_record_snapshot_digest ?? null,
|
|
996
|
+
required_parties: requiredParties,
|
|
997
|
+
required_release_parties: requiredReleaseParties,
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
return resultFailure('malformed_evidence_package', checks);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
export default {
|
|
1005
|
+
ACTION_ESCROW_CONTRACTOR_EVIDENCE_PACKAGE_VERSION,
|
|
1006
|
+
ACTION_ESCROW_EVIDENCE_PACKAGE_VERSION,
|
|
1007
|
+
ACTION_ESCROW_EVIDENCE_STAGES,
|
|
1008
|
+
parseActionEscrowEvidencePackage,
|
|
1009
|
+
buildActionEscrowEvidencePackage,
|
|
1010
|
+
verifyActionEscrowEvidencePackage,
|
|
1011
|
+
};
|
|
1012
|
+
//# sourceMappingURL=action-escrow-evidence.js.map
|