@kungfu-tech/buildchain 3.0.9-alpha.9 → 4.0.0-alpha.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/AGENTS.md +39 -0
- package/README.md +17 -13
- package/actions/promote-buildchain-ref/README.md +0 -10
- package/actions/release-tail/README.md +6 -10
- package/architecture/internal-capabilities.json +502 -0
- package/architecture/maintainability-baseline.json +59 -0
- package/architecture/maintainability-policy.json +1384 -0
- package/architecture/release-tail-contract-inventory.json +587 -0
- package/architecture/v3-core-mechanism-inventory.json +1043 -0
- package/architecture/v3-core-mechanism-inventory.md +67 -0
- package/architecture/v4-adopter-delivery-parity.json +47 -0
- package/architecture/v4-architecture-constitution.md +210 -0
- package/architecture/v4-bootstrap-authority.json +56 -0
- package/architecture/v4-canonical-contract-fixtures.json +105 -0
- package/architecture/v4-capability-state-machine-manifest.json +480 -0
- package/architecture/v4-capability-state-machine-manifest.schema.json +202 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +830 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.md +223 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.schema.json +318 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +227 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.schema.json +67 -0
- package/architecture/v4-exception-ledger.json +5 -0
- package/architecture/v4-exception-ledger.schema.json +56 -0
- package/architecture/v4-partial-mutation-recovery-qualification.json +65 -0
- package/architecture/v4-platform-stage-checkpoints.json +97 -0
- package/architecture/v4-provider-operation-journal-contract.json +113 -0
- package/architecture/v4-release-activation-shadow-domain.json +61 -0
- package/architecture/v4-release-train-parity.json +68 -0
- package/architecture/v4-rust-libnode-bridge-evaluation.json +107 -0
- package/architecture/v4-rust-libnode-bridge-spike.md +85 -0
- package/architecture/v4-stable-publication-fence.json +66 -0
- package/architecture/v4-stage-capsule-contract.json +90 -0
- package/architecture/v4-stage-capsule-qualification.json +99 -0
- package/architecture/v4-stage-capsule-resume-planner.json +52 -0
- package/architecture/v4-stage-capsule-store-contract.json +72 -0
- package/bin/buildchain.mjs +15 -10
- package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
- package/contracts/buildchain-v2-residuals-v1.json +2 -2
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/golden.json +158 -0
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/property.json +380 -0
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/replay.json +163 -0
- package/contracts/fixtures/v4-partial-mutation-recovery-v1/shared.json +155 -0
- package/contracts/fixtures/v4-provider-operation-journal-v1/shared.json +457 -0
- package/contracts/fixtures/v4-provider-readback-idempotency-v1/shared.json +682 -0
- package/contracts/fixtures/v4-release-activation-shadow-v1/shared.json +53 -0
- package/contracts/fixtures/v4-stable-publication-fence-v1/shared.json +56 -0
- package/contracts/fixtures/v4-stage-capsule-resume-v1/late-platform-failure.json +176 -0
- package/contracts/fixtures/v4-stage-capsule-store-v1/shared.json +132 -0
- package/contracts/fixtures/v4-stage-capsule-v1/shared.json +111 -0
- package/contracts/v4-canonical-contracts-v1.schema.json +182 -0
- package/contracts/v4-delivery-warrant-semantic-diff-report-v1.schema.json +284 -0
- package/contracts/v4-delivery-warrant-trace-v1.schema.json +215 -0
- package/contracts/v4-host-contract-v1.schema.json +135 -0
- package/contracts/v4-partial-mutation-recovery-v1.schema.json +168 -0
- package/contracts/v4-provider-operation-journal-v1.schema.json +381 -0
- package/contracts/v4-release-activation-shadow-v1.schema.json +186 -0
- package/contracts/v4-stable-publication-fence-v1.schema.json +125 -0
- package/contracts/v4-stage-capsule-resume-v1.schema.json +225 -0
- package/contracts/v4-stage-capsule-store-v1.schema.json +154 -0
- package/contracts/v4-stage-capsule-v1.schema.json +178 -0
- package/crates/buildchain-v4-bridge/Cargo.lock +213 -0
- package/crates/buildchain-v4-bridge/Cargo.toml +12 -0
- package/crates/buildchain-v4-bridge/src/main.rs +495 -0
- package/dist/site/agent-index.json +0 -4
- package/dist/site/artifact-schemas.json +0 -8
- package/dist/site/buildchain-contract.json +103 -397
- package/dist/site/buildchain-site.json +302 -386
- package/dist/site/capability-registry.json +9 -11
- package/dist/site/cli-registry.json +8 -53
- package/dist/site/controller-registry.json +48 -8
- package/dist/site/kfd-claims.json +129 -167
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +18 -49
- package/dist/site/node-api-registry.json +4345 -7345
- package/dist/site/page-registry.json +278 -346
- package/dist/site/public-surface-audit.json +85 -404
- package/dist/site/publication-authority-registry.json +11 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -9
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
- package/dist/site/site-manifest.json +22 -38
- package/dist/site/workflow-registry.json +78 -35
- package/docs/MAP.md +8 -5
- package/docs/auditable-demo.md +2 -2
- package/docs/aws-us-elastic-runner-burst-plane.md +7 -59
- package/docs/cli-reference.md +15 -213
- package/docs/cli.md +14 -14
- package/docs/dev-alpha-candidate-patrol.md +3 -11
- package/docs/dev-delivery-warrant.md +29 -194
- package/docs/engineering-housekeeper.md +12 -4
- package/docs/getting-started.md +1 -1
- package/docs/install.md +5 -5
- package/docs/kfd-support.md +2 -7
- package/docs/lifecycle-protocol.md +2 -4
- package/docs/node-api-reference.md +414 -554
- package/docs/release-flow.md +31 -31
- package/docs/release-governance.md +16 -28
- package/docs/release-passport.md +2 -15
- package/docs/release-tail-contract.md +13 -6
- package/docs/release-tail-provider-plane.md +13 -19
- package/docs/release-train.md +4 -4
- package/docs/reusable-build-surface.md +14 -27
- package/docs/site-bundle-contract.md +1 -5
- package/docs/stable-candidate-patrol.md +7 -7
- package/docs/v4-canonical-contracts.md +92 -0
- package/docs/v4-delivery-warrant-read-candidate.md +73 -0
- package/docs/v4-delivery-warrant-semantic-diff.md +75 -0
- package/docs/v4-production-release.md +63 -0
- package/docs/v4-stage-capsule.md +193 -0
- package/docs/versioning.md +3 -5
- package/package.json +19 -19
- package/packages/core/adopter-delivery-gate.js +576 -0
- package/packages/core/adopter-delivery-json.js +66 -0
- package/packages/core/artifact-signing.js +0 -61
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/build-facts.js +12 -4
- package/packages/core/buildchain-agent-manuals.js +0 -2
- package/packages/core/buildchain-channel-identity.js +3 -2
- package/packages/core/buildchain-config.js +4 -69
- package/packages/core/buildchain-contract.js +232 -46
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/channel-candidate.js +0 -8
- package/packages/core/channel-promotion-baseline.js +0 -52
- package/packages/core/controller-evidence.js +3 -4
- package/packages/core/dev-alpha-candidate-selection.js +2 -10
- package/packages/core/dev-delivery-warrant-cancellation.js +0 -1
- package/packages/core/dev-delivery-warrant-settlement.js +20 -67
- package/packages/core/dev-delivery-warrant.js +50 -138
- package/packages/core/diagnostics.js +3 -8
- package/packages/core/github-governance-authority.js +3 -1
- package/packages/core/kfd-adopter-category-driver.js +167 -0
- package/packages/core/kfd-adopter-manifest.js +47 -46
- package/packages/core/kfd-gate.js +15 -45
- package/packages/core/paper-agent-entry.js +7 -16
- package/packages/core/paper-fleet.js +2 -2
- package/packages/core/paper-repository.js +2 -3
- package/packages/core/paper-scaffold-content.js +2 -23
- package/packages/core/paper.js +11 -43
- package/packages/core/publication-reproducibility.js +4 -4
- package/packages/core/release-line-bootstrap.js +21 -8
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +20 -131
- package/packages/core/self-dogfood-version.js +48 -35
- package/packages/core/spawn-command.js +232 -8
- package/packages/core/v4-adopter-delivery-parity.js +219 -0
- package/packages/core/v4-canonical-contracts.js +284 -0
- package/packages/core/v4-delivery-warrant-fixture-runner.js +371 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +279 -0
- package/packages/core/v4-delivery-warrant-semantic-diff-gate.js +352 -0
- package/packages/core/v4-delivery-warrant-shadow-adapter.js +447 -0
- package/packages/core/v4-partial-mutation-recovery-qualification.js +395 -0
- package/packages/core/v4-platform-stage-checkpoints.js +471 -0
- package/packages/core/v4-provider-operation-journal.js +531 -0
- package/packages/core/v4-provider-readback-idempotency.js +394 -0
- package/packages/core/v4-release-activation-shadow.js +519 -0
- package/packages/core/v4-stable-publication-fence.js +357 -0
- package/packages/core/v4-stage-capsule-local-store.js +426 -0
- package/packages/core/v4-stage-capsule-qualification-campaign.js +512 -0
- package/packages/core/v4-stage-capsule-qualification.js +456 -0
- package/packages/core/v4-stage-capsule-resume-planner.js +397 -0
- package/packages/core/v4-stage-capsule-store.js +401 -0
- package/packages/core/v4-stage-capsule.js +319 -0
- package/scripts/assemble-self-publication-admission.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +5 -5
- package/scripts/auditable-demo-bundle-verification.mjs +3 -2
- package/scripts/auditable-demo-platform.mjs +2 -2
- package/scripts/auditable-demo-renditions.mjs +1 -1
- package/scripts/auditable-demo.mjs +2 -2
- package/scripts/aws-macos-jit-controller-core.mjs +13 -346
- package/scripts/aws-macos-jit-controller-runtime.mjs +2 -210
- package/scripts/aws-macos-jit-controller.mjs +37 -275
- package/scripts/aws-macos-jit-core.mjs +3 -41
- package/scripts/aws-macos-jit.mjs +0 -12
- package/scripts/aws-windows-jit-campaign.mjs +2 -2
- package/scripts/aws-windows-jit-controller-core.mjs +1 -1
- package/scripts/aws-windows-jit-controller.mjs +3 -3
- package/scripts/build-contract-core.mjs +3 -8
- package/scripts/build-standalone-binary.mjs +3 -14
- package/scripts/buildchain-channel-router.mjs +4 -0
- package/scripts/buildchain-cli-help.mjs +7 -16
- package/scripts/buildchain-contract-lock.mjs +0 -6
- package/scripts/check-action-bundles.mjs +3 -4
- package/scripts/check-internal-architecture.mjs +7 -6
- package/scripts/check-inventory.mjs +30 -25
- package/scripts/check-v4-public-dogfood-contract.mjs +270 -0
- package/scripts/create-release-bundle.mjs +8 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +1 -22
- package/scripts/dev-delivery-proof.mjs +2 -64
- package/scripts/dev-delivery-warrant.mjs +61 -86
- package/scripts/dev-pr-auto-merge.mjs +4 -30
- package/scripts/dev-pr-delivery-warrant.mjs +0 -55
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -4
- package/scripts/generate-buildchain-kfd-witnesses.mjs +94 -88
- package/scripts/generate-channel-build-workflow.mjs +54 -24
- package/scripts/generate-channel-promotion-workflow.mjs +15 -6
- package/scripts/generate-site-bundle.mjs +8 -27
- package/scripts/git-fetch-process-tree.mjs +34 -1
- package/scripts/init-repo.mjs +64 -159
- package/scripts/inspect-artifact-signing-requests.mjs +0 -6
- package/scripts/locked-source-checkout.mjs +3 -4
- package/scripts/maintainability-metrics.mjs +8 -2
- package/scripts/npm-publish-dry-run.mjs +2 -2
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/publication-commit-evidence.mjs +23 -69
- package/scripts/release-candidate-resolver.mjs +11 -17
- package/scripts/release-tail.mjs +3 -49
- package/scripts/resume-from-candidate-run.mjs +9 -123
- package/scripts/run-lifecycle-core.mjs +12 -13
- package/scripts/seal-artifact-signing-requests.mjs +17 -21
- package/scripts/site-capability-metadata.mjs +3 -20
- package/scripts/stable-candidate-qualification.mjs +10 -10
- package/scripts/v4-architecture.mjs +12 -2
- package/scripts/v4-bridge-bootstrap.mjs +141 -0
- package/scripts/v4-bridge-evidence.mjs +141 -0
- package/scripts/v4-host-adapter.mjs +182 -0
- package/scripts/v4-platform-stage-checkpoint-rehearsal.mjs +207 -0
- package/scripts/v4-stage-capsule-qualification.mjs +528 -0
- package/scripts/v4-stage-capsule-resume-rehearsal.mjs +57 -0
- package/scripts/v4-warrant-shadow-plan.mjs +601 -0
- package/scripts/verify-golden-path.mjs +5 -5
- package/scripts/web-surface-cloudfront-rewrite.mjs +15 -15
- package/scripts/web-surface-core.mjs +2 -8
- package/scripts/workflow-call-contract.mjs +5 -184
- package/contracts/dev-delivery-authority-v2.schema.json +0 -253
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +0 -16
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +0 -57
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +0 -47
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +0 -47
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +0 -40
- package/contracts/next-development-request-v1.schema.json +0 -66
- package/contracts/next-development-transition-v1.schema.json +0 -292
- package/contracts/publication-rehearsal-capsule-v1.schema.json +0 -173
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +0 -442
- package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +0 -269
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +0 -353
- package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +0 -94
- package/docs/dev-delivery-qualification-landing-adr.md +0 -137
- package/docs/next-development-transition.md +0 -119
- package/docs/publication-rehearsal.md +0 -94
- package/packages/core/buildchain-compatibility-proof.js +0 -631
- package/packages/core/dev-delivery-authority.js +0 -996
- package/packages/core/dev-delivery-native-proof.js +0 -418
- package/packages/core/dev-delivery-warrant-shadow.js +0 -502
- package/packages/core/next-development-candidate-reservation.js +0 -186
- package/packages/core/next-development-controller.js +0 -726
- package/packages/core/next-development-projection.js +0 -288
- package/packages/core/next-development-transition.js +0 -738
- package/packages/core/publication-rehearsal-projection.js +0 -173
- package/packages/core/publication-rehearsal-runtime.js +0 -921
- package/scripts/aws-macos-jit-instance-rehydrate.mjs +0 -260
- package/scripts/aws-macos-jit-source-rebind.mjs +0 -724
- package/scripts/dev-delivery-authority.mjs +0 -368
- package/scripts/dev-delivery-native-run.mjs +0 -235
- package/scripts/dev-delivery-two-phase-resume.mjs +0 -90
- package/scripts/dev-delivery-two-phase.mjs +0 -577
- package/scripts/generate-next-development-guidance.mjs +0 -49
- package/scripts/materialize-self-release-candidate-version.mjs +0 -137
- package/scripts/next-development-self-dogfood-harness.mjs +0 -409
- package/scripts/next-development-self-dogfood.mjs +0 -532
- package/scripts/next-development-transition.mjs +0 -47
- package/scripts/release-candidate-tail-reseal.mjs +0 -426
- package/templates/native-dev-delivery.yml +0 -86
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const V4_CANONICAL_JSON_CONTRACT = "buildchain-canonical-json/v1";
|
|
4
|
+
export const V4_EVENT_ENVELOPE_CONTRACT = "buildchain-v4-event-envelope/v1";
|
|
5
|
+
export const V4_RECEIPT_ENVELOPE_CONTRACT = "buildchain-v4-receipt-envelope/v1";
|
|
6
|
+
export const V4_CONTRACT_FAULT_CONTRACT = "buildchain-v4-contract-fault/v1";
|
|
7
|
+
|
|
8
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
9
|
+
const CLOCK_PATTERN = /^(?!0000)\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
10
|
+
const ASCII_KEY_PATTERN = /^[\x20-\x7e]+$/u;
|
|
11
|
+
const ASCII_TOKEN_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
12
|
+
const ROOT_DOMAINS = new Set([
|
|
13
|
+
"queue-state",
|
|
14
|
+
"candidate-identity",
|
|
15
|
+
"fencing-token",
|
|
16
|
+
"transition-receipt",
|
|
17
|
+
"observation",
|
|
18
|
+
"semantic-diff",
|
|
19
|
+
"bootstrap-evidence",
|
|
20
|
+
"stage-capsule-identity",
|
|
21
|
+
"stage-capsule",
|
|
22
|
+
"stage-capsule-availability",
|
|
23
|
+
"stage-capsule-output-manifest",
|
|
24
|
+
"stage-capsule-retention-promise",
|
|
25
|
+
"stage-capsule-retention-state",
|
|
26
|
+
"stage-capsule-transport",
|
|
27
|
+
"stage-capsule-store-receipt",
|
|
28
|
+
"stage-capsule-quarantine",
|
|
29
|
+
"stage-capsule-resume-observation",
|
|
30
|
+
"stage-capsule-resume-plan",
|
|
31
|
+
"stage-capsule-artifact-manifest",
|
|
32
|
+
"stage-capsule-artifact-content",
|
|
33
|
+
"stage-capsule-fault-campaign",
|
|
34
|
+
"stage-capsule-seed-evidence",
|
|
35
|
+
"stage-capsule-resume-evidence",
|
|
36
|
+
"stage-capsule-platform-qualification",
|
|
37
|
+
"stage-capsule-qualification",
|
|
38
|
+
"stage-capsule-wave-reconciliation",
|
|
39
|
+
"provider-operation-identity",
|
|
40
|
+
"provider-operation-intent",
|
|
41
|
+
"provider-operation-attempt",
|
|
42
|
+
"provider-operation-observation",
|
|
43
|
+
"provider-operation-confirmation",
|
|
44
|
+
"provider-operation-reconciliation",
|
|
45
|
+
"provider-operation-journal",
|
|
46
|
+
"provider-operation-journal-state",
|
|
47
|
+
"provider-readback-sample",
|
|
48
|
+
"provider-readback-fold",
|
|
49
|
+
"release-activation-step",
|
|
50
|
+
"release-activation-plan",
|
|
51
|
+
"release-activation-state",
|
|
52
|
+
"stable-publication-candidate",
|
|
53
|
+
"stable-publication-qualification",
|
|
54
|
+
"stable-publication-target",
|
|
55
|
+
"stable-publication-plan",
|
|
56
|
+
"stable-publication-fence",
|
|
57
|
+
"partial-mutation-recovery-checkpoint",
|
|
58
|
+
"partial-mutation-recovery-plan",
|
|
59
|
+
]);
|
|
60
|
+
const FAULT_CLASSES = new Set([
|
|
61
|
+
"validation",
|
|
62
|
+
"concurrency",
|
|
63
|
+
"authority",
|
|
64
|
+
"idempotence",
|
|
65
|
+
]);
|
|
66
|
+
const RETRY_DECISIONS = new Set(["stop", "reread", "redecide", "reselect"]);
|
|
67
|
+
const RECEIPT_OUTCOMES = new Set(["accepted", "rejected", "noop"]);
|
|
68
|
+
|
|
69
|
+
export class V4ContractFault extends Error {
|
|
70
|
+
constructor(code, path, message) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = "V4ContractFault";
|
|
73
|
+
this.code = code;
|
|
74
|
+
this.path = path;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fault(code, path, message) {
|
|
79
|
+
throw new V4ContractFault(code, path, message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function exactKeys(value, keys, path) {
|
|
83
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
84
|
+
fault("invalid-object", path, `${path} must be an object`);
|
|
85
|
+
const actual = Object.keys(value).sort();
|
|
86
|
+
const expected = [...keys].sort();
|
|
87
|
+
if (
|
|
88
|
+
actual.length !== expected.length ||
|
|
89
|
+
actual.some((key, index) => key !== expected[index])
|
|
90
|
+
)
|
|
91
|
+
fault("invalid-envelope-shape", path, `${path} keys are not canonical`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function canonicalize(value, path, active) {
|
|
95
|
+
if (value === null || typeof value === "boolean") return value;
|
|
96
|
+
if (typeof value === "string") {
|
|
97
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
98
|
+
const code = value.charCodeAt(index);
|
|
99
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
100
|
+
const next = value.charCodeAt(index + 1);
|
|
101
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
102
|
+
fault(
|
|
103
|
+
"unsupported-string",
|
|
104
|
+
path,
|
|
105
|
+
`${path} contains an unpaired surrogate`,
|
|
106
|
+
);
|
|
107
|
+
index += 1;
|
|
108
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
109
|
+
fault(
|
|
110
|
+
"unsupported-string",
|
|
111
|
+
path,
|
|
112
|
+
`${path} contains an unpaired surrogate`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
if (typeof value === "number") {
|
|
119
|
+
if (!Number.isSafeInteger(value) || Object.is(value, -0))
|
|
120
|
+
fault(
|
|
121
|
+
"unsupported-number",
|
|
122
|
+
path,
|
|
123
|
+
`${path} must be a safe base-10 integer and must not be negative zero`,
|
|
124
|
+
);
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
if (!value || typeof value !== "object")
|
|
128
|
+
fault("unsupported-json-value", path, `${path} is not a JSON value`);
|
|
129
|
+
if (active.has(value))
|
|
130
|
+
fault("cyclic-json-value", path, `${path} contains a cycle`);
|
|
131
|
+
active.add(value);
|
|
132
|
+
try {
|
|
133
|
+
if (Array.isArray(value))
|
|
134
|
+
return Array.from(value, (entry, index) =>
|
|
135
|
+
canonicalize(entry, `${path}/${index}`, active),
|
|
136
|
+
);
|
|
137
|
+
const prototype = Object.getPrototypeOf(value);
|
|
138
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
139
|
+
fault("unsupported-json-value", path, `${path} must be a plain object`);
|
|
140
|
+
const result = Object.create(null);
|
|
141
|
+
for (const key of Object.keys(value).sort((left, right) =>
|
|
142
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
143
|
+
)) {
|
|
144
|
+
if (!ASCII_KEY_PATTERN.test(key))
|
|
145
|
+
fault(
|
|
146
|
+
"unsupported-object-key",
|
|
147
|
+
`${path}/${key}`,
|
|
148
|
+
"object keys must be non-empty printable ASCII",
|
|
149
|
+
);
|
|
150
|
+
result[key] = canonicalize(value[key], `${path}/${key}`, active);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
} finally {
|
|
154
|
+
active.delete(value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function v4CanonicalBytes(value) {
|
|
159
|
+
return Buffer.from(
|
|
160
|
+
`${JSON.stringify(canonicalize(value, "$", new Set()))}\n`,
|
|
161
|
+
"utf8",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function v4ContentRoot(domain, value) {
|
|
166
|
+
if (!ROOT_DOMAINS.has(domain))
|
|
167
|
+
fault(
|
|
168
|
+
"unsupported-root-domain",
|
|
169
|
+
"$.domain",
|
|
170
|
+
`unsupported root domain: ${domain}`,
|
|
171
|
+
);
|
|
172
|
+
const hash = crypto.createHash("sha256");
|
|
173
|
+
hash.update(domain, "ascii");
|
|
174
|
+
hash.update(Buffer.from([0]));
|
|
175
|
+
hash.update(v4CanonicalBytes(value));
|
|
176
|
+
return `sha256:${hash.digest("hex")}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function validateV4Root(value, path = "$.root") {
|
|
180
|
+
if (typeof value !== "string" || !ROOT_PATTERN.test(value))
|
|
181
|
+
fault("invalid-root", path, `${path} must be a lowercase sha256 root`);
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function validateV4Clock(value, path = "$.clock") {
|
|
186
|
+
if (typeof value !== "string" || !CLOCK_PATTERN.test(value))
|
|
187
|
+
fault(
|
|
188
|
+
"invalid-clock",
|
|
189
|
+
path,
|
|
190
|
+
`${path} must be RFC3339 UTC with millisecond precision`,
|
|
191
|
+
);
|
|
192
|
+
const parsed = new Date(value);
|
|
193
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value)
|
|
194
|
+
fault("invalid-clock", path, `${path} is not a real UTC instant`);
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function validateV4ContractFault(value, path = "$.fault") {
|
|
199
|
+
exactKeys(
|
|
200
|
+
value,
|
|
201
|
+
["schema", "code", "class", "path", "message", "retry"],
|
|
202
|
+
path,
|
|
203
|
+
);
|
|
204
|
+
if (value.schema !== V4_CONTRACT_FAULT_CONTRACT)
|
|
205
|
+
fault("invalid-fault", `${path}/schema`, "unsupported fault schema");
|
|
206
|
+
if (!ASCII_TOKEN_PATTERN.test(value.code))
|
|
207
|
+
fault("invalid-fault", `${path}/code`, "fault code must be an ASCII token");
|
|
208
|
+
if (!FAULT_CLASSES.has(value.class))
|
|
209
|
+
fault("invalid-fault", `${path}/class`, "fault class is unsupported");
|
|
210
|
+
if (typeof value.path !== "string" || !value.path.startsWith("$"))
|
|
211
|
+
fault("invalid-fault", `${path}/path`, "fault path must start with $");
|
|
212
|
+
if (typeof value.message !== "string" || value.message.length === 0)
|
|
213
|
+
fault("invalid-fault", `${path}/message`, "fault message is required");
|
|
214
|
+
if (!RETRY_DECISIONS.has(value.retry))
|
|
215
|
+
fault(
|
|
216
|
+
"invalid-fault",
|
|
217
|
+
`${path}/retry`,
|
|
218
|
+
"fault retry decision is unsupported",
|
|
219
|
+
);
|
|
220
|
+
v4CanonicalBytes(value);
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function validateV4EventEnvelope(value) {
|
|
225
|
+
exactKeys(
|
|
226
|
+
value,
|
|
227
|
+
["schema", "eventId", "eventType", "occurredAt", "subjectRoot", "payload"],
|
|
228
|
+
"$",
|
|
229
|
+
);
|
|
230
|
+
if (value.schema !== V4_EVENT_ENVELOPE_CONTRACT)
|
|
231
|
+
fault("invalid-event", "$/schema", "unsupported event schema");
|
|
232
|
+
validateV4Root(value.eventId, "$/eventId");
|
|
233
|
+
validateV4Root(value.subjectRoot, "$/subjectRoot");
|
|
234
|
+
validateV4Clock(value.occurredAt, "$/occurredAt");
|
|
235
|
+
if (!ASCII_TOKEN_PATTERN.test(value.eventType))
|
|
236
|
+
fault("invalid-event", "$/eventType", "event type must be an ASCII token");
|
|
237
|
+
v4CanonicalBytes(value.payload);
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function validateV4ReceiptEnvelope(value) {
|
|
242
|
+
exactKeys(
|
|
243
|
+
value,
|
|
244
|
+
[
|
|
245
|
+
"schema",
|
|
246
|
+
"receiptType",
|
|
247
|
+
"recordedAt",
|
|
248
|
+
"eventRoot",
|
|
249
|
+
"priorStateRoot",
|
|
250
|
+
"nextStateRoot",
|
|
251
|
+
"outcome",
|
|
252
|
+
"fault",
|
|
253
|
+
],
|
|
254
|
+
"$",
|
|
255
|
+
);
|
|
256
|
+
if (value.schema !== V4_RECEIPT_ENVELOPE_CONTRACT)
|
|
257
|
+
fault("invalid-receipt", "$/schema", "unsupported receipt schema");
|
|
258
|
+
if (!ASCII_TOKEN_PATTERN.test(value.receiptType))
|
|
259
|
+
fault(
|
|
260
|
+
"invalid-receipt",
|
|
261
|
+
"$/receiptType",
|
|
262
|
+
"receipt type must be an ASCII token",
|
|
263
|
+
);
|
|
264
|
+
validateV4Clock(value.recordedAt, "$/recordedAt");
|
|
265
|
+
validateV4Root(value.eventRoot, "$/eventRoot");
|
|
266
|
+
if (value.priorStateRoot !== null)
|
|
267
|
+
validateV4Root(value.priorStateRoot, "$/priorStateRoot");
|
|
268
|
+
if (value.nextStateRoot !== null)
|
|
269
|
+
validateV4Root(value.nextStateRoot, "$/nextStateRoot");
|
|
270
|
+
if (!RECEIPT_OUTCOMES.has(value.outcome))
|
|
271
|
+
fault("invalid-receipt", "$/outcome", "receipt outcome is unsupported");
|
|
272
|
+
if (value.fault !== null) validateV4ContractFault(value.fault);
|
|
273
|
+
if ((value.outcome === "rejected") !== (value.fault !== null))
|
|
274
|
+
fault(
|
|
275
|
+
"invalid-receipt",
|
|
276
|
+
"$/fault",
|
|
277
|
+
"only rejected receipts carry a typed fault",
|
|
278
|
+
);
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function v4ContractDomains() {
|
|
283
|
+
return [...ROOT_DOMAINS];
|
|
284
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { TextDecoder } from "node:util";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
V4ContractFault,
|
|
5
|
+
v4CanonicalBytes,
|
|
6
|
+
v4ContentRoot,
|
|
7
|
+
validateV4ContractFault,
|
|
8
|
+
validateV4EventEnvelope,
|
|
9
|
+
validateV4ReceiptEnvelope,
|
|
10
|
+
validateV4Root,
|
|
11
|
+
} from "./v4-canonical-contracts.js";
|
|
12
|
+
|
|
13
|
+
export const V4_DELIVERY_WARRANT_TRACE_CONTRACT =
|
|
14
|
+
"buildchain-v4-delivery-warrant-trace-fixture/v1";
|
|
15
|
+
export const V4_DELIVERY_WARRANT_RUNNER_CONTRACT =
|
|
16
|
+
"buildchain-v4-delivery-warrant-fixture-runner/v1";
|
|
17
|
+
export const V4_DELIVERY_WARRANT_PROJECTION_CONTRACT =
|
|
18
|
+
"buildchain-v4-delivery-warrant-semantic-projection/v1";
|
|
19
|
+
|
|
20
|
+
const ASCII_TOKEN_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
21
|
+
const UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
22
|
+
|
|
23
|
+
function fail(code, path, message) {
|
|
24
|
+
throw new V4ContractFault(code, path, message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exactKeys(value, keys, path) {
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29
|
+
fail("invalid-object", path, `${path} must be an object`);
|
|
30
|
+
const actual = Object.keys(value).sort();
|
|
31
|
+
const expected = [...keys].sort();
|
|
32
|
+
if (
|
|
33
|
+
actual.length !== expected.length ||
|
|
34
|
+
actual.some((key, index) => key !== expected[index])
|
|
35
|
+
)
|
|
36
|
+
fail("invalid-trace-shape", path, `${path} keys are not canonical`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function token(value, path) {
|
|
40
|
+
if (typeof value !== "string" || !ASCII_TOKEN_PATTERN.test(value))
|
|
41
|
+
fail("invalid-trace-token", path, `${path} must be an ASCII token`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function nonNegativeInteger(value, path) {
|
|
46
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
47
|
+
fail(
|
|
48
|
+
"invalid-trace-integer",
|
|
49
|
+
path,
|
|
50
|
+
`${path} must be a non-negative safe integer`,
|
|
51
|
+
);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function equalCanonical(left, right) {
|
|
56
|
+
return v4CanonicalBytes(left).equals(v4CanonicalBytes(right));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function declaredRoot(value, expected, path, code) {
|
|
60
|
+
validateV4Root(value, path);
|
|
61
|
+
if (value !== expected)
|
|
62
|
+
fail(code, path, `${path} does not match the retained canonical bytes`);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readBytes(bytes) {
|
|
67
|
+
const input = Buffer.isBuffer(bytes)
|
|
68
|
+
? bytes
|
|
69
|
+
: bytes instanceof Uint8Array
|
|
70
|
+
? Buffer.from(bytes)
|
|
71
|
+
: typeof bytes === "string"
|
|
72
|
+
? Buffer.from(bytes, "utf8")
|
|
73
|
+
: fail(
|
|
74
|
+
"invalid-trace-bytes",
|
|
75
|
+
"$",
|
|
76
|
+
"trace input must be UTF-8 bytes or a string",
|
|
77
|
+
);
|
|
78
|
+
if (input.length === 0 || input.at(-1) !== 0x0a)
|
|
79
|
+
fail(
|
|
80
|
+
"invalid-trace-bytes",
|
|
81
|
+
"$",
|
|
82
|
+
"retained trace bytes must end with one LF",
|
|
83
|
+
);
|
|
84
|
+
if (
|
|
85
|
+
input.length >= 3 &&
|
|
86
|
+
input[0] === 0xef &&
|
|
87
|
+
input[1] === 0xbb &&
|
|
88
|
+
input[2] === 0xbf
|
|
89
|
+
)
|
|
90
|
+
fail("invalid-trace-bytes", "$", "retained trace bytes must not use BOM");
|
|
91
|
+
let text;
|
|
92
|
+
try {
|
|
93
|
+
text = UTF8.decode(input);
|
|
94
|
+
} catch {
|
|
95
|
+
fail("invalid-trace-bytes", "$", "retained trace bytes are not UTF-8");
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(text);
|
|
99
|
+
} catch {
|
|
100
|
+
fail("invalid-trace-json", "$", "retained trace bytes are not JSON");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function validateOrderedEntries(entries, path) {
|
|
105
|
+
if (!Array.isArray(entries))
|
|
106
|
+
fail("invalid-trace-shape", path, `${path} must be an array`);
|
|
107
|
+
return entries.map((entry, index) => {
|
|
108
|
+
exactKeys(entry, ["sequence", "type", "payload"], `${path}/${index}`);
|
|
109
|
+
if (entry.sequence !== index)
|
|
110
|
+
fail(
|
|
111
|
+
"reordered-trace",
|
|
112
|
+
`${path}/${index}/sequence`,
|
|
113
|
+
`${path} sequence must be contiguous and ordered`,
|
|
114
|
+
);
|
|
115
|
+
token(entry.type, `${path}/${index}/type`);
|
|
116
|
+
v4CanonicalBytes(entry.payload);
|
|
117
|
+
return entry;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateState(state, path) {
|
|
122
|
+
exactKeys(
|
|
123
|
+
state,
|
|
124
|
+
["schema", "generation", "fencingCounter", "activeWarrant", "candidates"],
|
|
125
|
+
path,
|
|
126
|
+
);
|
|
127
|
+
if (state.schema !== "buildchain-v4-delivery-warrant-state/v1")
|
|
128
|
+
fail(
|
|
129
|
+
"unsupported-trace-version",
|
|
130
|
+
`${path}/schema`,
|
|
131
|
+
"unsupported Delivery Warrant state schema",
|
|
132
|
+
);
|
|
133
|
+
const generation = nonNegativeInteger(state.generation, `${path}/generation`);
|
|
134
|
+
const fencingCounter = nonNegativeInteger(
|
|
135
|
+
state.fencingCounter,
|
|
136
|
+
`${path}/fencingCounter`,
|
|
137
|
+
);
|
|
138
|
+
if (!Array.isArray(state.candidates))
|
|
139
|
+
fail(
|
|
140
|
+
"invalid-trace-shape",
|
|
141
|
+
`${path}/candidates`,
|
|
142
|
+
"state candidates must be an array",
|
|
143
|
+
);
|
|
144
|
+
const canonicalUtf8 = v4CanonicalBytes(state).toString("utf8");
|
|
145
|
+
return {
|
|
146
|
+
generation,
|
|
147
|
+
fencingCounter,
|
|
148
|
+
canonicalUtf8,
|
|
149
|
+
root: v4ContentRoot("queue-state", state),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function validateDecision(decision, receipt, path) {
|
|
154
|
+
exactKeys(decision, ["action", "fault"], path);
|
|
155
|
+
const hasAction = decision.action !== null;
|
|
156
|
+
const hasFault = decision.fault !== null;
|
|
157
|
+
if (hasAction === hasFault)
|
|
158
|
+
fail(
|
|
159
|
+
"invalid-trace-decision",
|
|
160
|
+
path,
|
|
161
|
+
"decision must contain exactly one action or typed fault",
|
|
162
|
+
);
|
|
163
|
+
if (hasAction) {
|
|
164
|
+
token(decision.action, `${path}/action`);
|
|
165
|
+
if (receipt.outcome === "rejected" || receipt.fault !== null)
|
|
166
|
+
fail(
|
|
167
|
+
"invalid-trace-decision",
|
|
168
|
+
path,
|
|
169
|
+
"an action cannot bind a rejected receipt",
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
validateV4ContractFault(decision.fault, `${path}/fault`);
|
|
173
|
+
if (
|
|
174
|
+
receipt.outcome !== "rejected" ||
|
|
175
|
+
receipt.fault === null ||
|
|
176
|
+
!equalCanonical(decision.fault, receipt.fault)
|
|
177
|
+
)
|
|
178
|
+
fail(
|
|
179
|
+
"invalid-trace-decision",
|
|
180
|
+
path,
|
|
181
|
+
"typed fault and rejected receipt must match exactly",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return decision;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function projectStep(step, index, priorStateRoot) {
|
|
188
|
+
const path = `$/trace/steps/${index}`;
|
|
189
|
+
exactKeys(
|
|
190
|
+
step,
|
|
191
|
+
[
|
|
192
|
+
"sequence",
|
|
193
|
+
"id",
|
|
194
|
+
"operation",
|
|
195
|
+
"event",
|
|
196
|
+
"decision",
|
|
197
|
+
"priorStateRoot",
|
|
198
|
+
"successorState",
|
|
199
|
+
"successorRoot",
|
|
200
|
+
"effects",
|
|
201
|
+
"observations",
|
|
202
|
+
"receipt",
|
|
203
|
+
"receiptRoot",
|
|
204
|
+
],
|
|
205
|
+
path,
|
|
206
|
+
);
|
|
207
|
+
if (step.sequence !== index)
|
|
208
|
+
fail(
|
|
209
|
+
"reordered-trace",
|
|
210
|
+
`${path}/sequence`,
|
|
211
|
+
"trace step sequence must be contiguous and ordered",
|
|
212
|
+
);
|
|
213
|
+
token(step.id, `${path}/id`);
|
|
214
|
+
token(step.operation, `${path}/operation`);
|
|
215
|
+
declaredRoot(
|
|
216
|
+
step.priorStateRoot,
|
|
217
|
+
priorStateRoot,
|
|
218
|
+
`${path}/priorStateRoot`,
|
|
219
|
+
"stale-prior-root",
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
validateV4EventEnvelope(step.event);
|
|
223
|
+
if (step.event.subjectRoot !== priorStateRoot)
|
|
224
|
+
fail(
|
|
225
|
+
"stale-prior-root",
|
|
226
|
+
`${path}/event/subjectRoot`,
|
|
227
|
+
"event subjectRoot must bind the exact prior state",
|
|
228
|
+
);
|
|
229
|
+
const eventRoot = v4ContentRoot("observation", step.event);
|
|
230
|
+
|
|
231
|
+
const successor = validateState(
|
|
232
|
+
step.successorState,
|
|
233
|
+
`${path}/successorState`,
|
|
234
|
+
);
|
|
235
|
+
const successorRoot = declaredRoot(
|
|
236
|
+
step.successorRoot,
|
|
237
|
+
successor.root,
|
|
238
|
+
`${path}/successorRoot`,
|
|
239
|
+
"stale-successor-root",
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
validateV4ReceiptEnvelope(step.receipt);
|
|
243
|
+
if (
|
|
244
|
+
step.receipt.eventRoot !== eventRoot ||
|
|
245
|
+
step.receipt.priorStateRoot !== priorStateRoot ||
|
|
246
|
+
step.receipt.nextStateRoot !== successorRoot
|
|
247
|
+
)
|
|
248
|
+
fail(
|
|
249
|
+
"stale-receipt-root",
|
|
250
|
+
`${path}/receipt`,
|
|
251
|
+
"receipt roots must bind the event and exact state transition",
|
|
252
|
+
);
|
|
253
|
+
const decision = validateDecision(
|
|
254
|
+
step.decision,
|
|
255
|
+
step.receipt,
|
|
256
|
+
`${path}/decision`,
|
|
257
|
+
);
|
|
258
|
+
const effects = validateOrderedEntries(step.effects, `${path}/effects`);
|
|
259
|
+
if (decision.fault !== null && effects.length > 0)
|
|
260
|
+
fail(
|
|
261
|
+
"invalid-trace-effects",
|
|
262
|
+
`${path}/effects`,
|
|
263
|
+
"rejected decisions must not declare effects",
|
|
264
|
+
);
|
|
265
|
+
const observations = validateOrderedEntries(
|
|
266
|
+
step.observations,
|
|
267
|
+
`${path}/observations`,
|
|
268
|
+
);
|
|
269
|
+
const receiptCanonicalUtf8 = v4CanonicalBytes(step.receipt).toString("utf8");
|
|
270
|
+
const receiptRoot = declaredRoot(
|
|
271
|
+
step.receiptRoot,
|
|
272
|
+
v4ContentRoot("transition-receipt", step.receipt),
|
|
273
|
+
`${path}/receiptRoot`,
|
|
274
|
+
"stale-receipt-root",
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
projection: {
|
|
279
|
+
sequence: step.sequence,
|
|
280
|
+
id: step.id,
|
|
281
|
+
operation: step.operation,
|
|
282
|
+
decision,
|
|
283
|
+
eventRoot,
|
|
284
|
+
successorCanonicalUtf8: successor.canonicalUtf8,
|
|
285
|
+
successorRoot,
|
|
286
|
+
generation: successor.generation,
|
|
287
|
+
fencingCounter: successor.fencingCounter,
|
|
288
|
+
effects: effects.map((effect) => ({
|
|
289
|
+
sequence: effect.sequence,
|
|
290
|
+
type: effect.type,
|
|
291
|
+
canonicalUtf8: v4CanonicalBytes(effect.payload).toString("utf8"),
|
|
292
|
+
})),
|
|
293
|
+
observations: observations.map((observation) => ({
|
|
294
|
+
sequence: observation.sequence,
|
|
295
|
+
type: observation.type,
|
|
296
|
+
canonicalUtf8: v4CanonicalBytes(observation.payload).toString("utf8"),
|
|
297
|
+
root: v4ContentRoot("observation", observation.payload),
|
|
298
|
+
})),
|
|
299
|
+
receiptCanonicalUtf8,
|
|
300
|
+
receiptRoot,
|
|
301
|
+
},
|
|
302
|
+
successorRoot,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function runV4DeliveryWarrantTraceFixture(
|
|
307
|
+
bytes,
|
|
308
|
+
{ verifyExpectedRoot = true } = {},
|
|
309
|
+
) {
|
|
310
|
+
const fixture = readBytes(bytes);
|
|
311
|
+
exactKeys(
|
|
312
|
+
fixture,
|
|
313
|
+
["schemaVersion", "contract", "runner", "trace", "expectedProjectionRoot"],
|
|
314
|
+
"$",
|
|
315
|
+
);
|
|
316
|
+
if (
|
|
317
|
+
fixture.schemaVersion !== 1 ||
|
|
318
|
+
fixture.contract !== V4_DELIVERY_WARRANT_TRACE_CONTRACT ||
|
|
319
|
+
fixture.runner !== V4_DELIVERY_WARRANT_RUNNER_CONTRACT
|
|
320
|
+
)
|
|
321
|
+
fail(
|
|
322
|
+
"unsupported-trace-version",
|
|
323
|
+
"$/schemaVersion",
|
|
324
|
+
"unsupported trace fixture contract",
|
|
325
|
+
);
|
|
326
|
+
exactKeys(
|
|
327
|
+
fixture.trace,
|
|
328
|
+
["id", "kind", "initialState", "initialStateRoot", "steps"],
|
|
329
|
+
"$/trace",
|
|
330
|
+
);
|
|
331
|
+
token(fixture.trace.id, "$/trace/id");
|
|
332
|
+
if (!["golden", "property", "replay"].includes(fixture.trace.kind))
|
|
333
|
+
fail("invalid-trace-kind", "$/trace/kind", "unsupported trace kind");
|
|
334
|
+
if (!Array.isArray(fixture.trace.steps) || fixture.trace.steps.length === 0)
|
|
335
|
+
fail(
|
|
336
|
+
"incomplete-trace",
|
|
337
|
+
"$/trace/steps",
|
|
338
|
+
"trace must contain at least one step",
|
|
339
|
+
);
|
|
340
|
+
const initialState = validateState(
|
|
341
|
+
fixture.trace.initialState,
|
|
342
|
+
"$/trace/initialState",
|
|
343
|
+
);
|
|
344
|
+
const initialStateRoot = declaredRoot(
|
|
345
|
+
fixture.trace.initialStateRoot,
|
|
346
|
+
initialState.root,
|
|
347
|
+
"$/trace/initialStateRoot",
|
|
348
|
+
"stale-initial-root",
|
|
349
|
+
);
|
|
350
|
+
let priorStateRoot = initialStateRoot;
|
|
351
|
+
const steps = fixture.trace.steps.map((step, index) => {
|
|
352
|
+
const result = projectStep(step, index, priorStateRoot);
|
|
353
|
+
priorStateRoot = result.successorRoot;
|
|
354
|
+
return result.projection;
|
|
355
|
+
});
|
|
356
|
+
const projection = {
|
|
357
|
+
schema: V4_DELIVERY_WARRANT_PROJECTION_CONTRACT,
|
|
358
|
+
traceId: fixture.trace.id,
|
|
359
|
+
traceKind: fixture.trace.kind,
|
|
360
|
+
steps,
|
|
361
|
+
};
|
|
362
|
+
const projectionRoot = v4ContentRoot("semantic-diff", projection);
|
|
363
|
+
validateV4Root(fixture.expectedProjectionRoot, "$/expectedProjectionRoot");
|
|
364
|
+
if (verifyExpectedRoot && fixture.expectedProjectionRoot !== projectionRoot)
|
|
365
|
+
fail(
|
|
366
|
+
"stale-projection-root",
|
|
367
|
+
"$/expectedProjectionRoot",
|
|
368
|
+
"expected projection root does not match the semantic projection",
|
|
369
|
+
);
|
|
370
|
+
return { projection, projectionRoot };
|
|
371
|
+
}
|