@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21
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/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +211 -8
- package/architecture/maintainability-policy.json +113 -28
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +32 -3
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/dev-delivery-authority-v2.schema.json +662 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1475 -27
- package/dist/site/buildchain-site.json +627 -31
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/kfd-claims.json +490 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +14736 -8851
- package/dist/site/page-registry.json +593 -21
- package/dist/site/public-surface-audit.json +837 -48
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +24 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +34 -10
- package/dist/site/workflow-registry.json +160 -10
- package/docs/MAP.md +8 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +351 -38
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +646 -293
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +34 -6
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +96 -143
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +5 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +70 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +36 -9
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +143 -20
- package/packages/core/v4-floating-consumer-release-passport.js +32 -5
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/buildchain-cli-help.mjs +41 -2
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +11 -0
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +69 -4
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +557 -30
- package/scripts/site-capability-metadata.mjs +41 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +5 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +143 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { parseWorkflowDocument } from "./workflow-yaml-contract.js";
|
|
3
|
+
|
|
4
|
+
export const CI_LANE_CHANGE_BUDGET_CONTRACT =
|
|
5
|
+
"buildchain.ci-lane-change-budget/v1";
|
|
6
|
+
|
|
7
|
+
const AUTHORITY_CLASSES = new Set([
|
|
8
|
+
"merge-critical-required",
|
|
9
|
+
"merge-critical-admission",
|
|
10
|
+
"post-merge-advisory",
|
|
11
|
+
"diagnostic",
|
|
12
|
+
"evidence",
|
|
13
|
+
"release",
|
|
14
|
+
]);
|
|
15
|
+
const TRIGGER_CLASSES = new Set([
|
|
16
|
+
"merge-group",
|
|
17
|
+
"pull-request",
|
|
18
|
+
"push",
|
|
19
|
+
"scheduled",
|
|
20
|
+
"manual",
|
|
21
|
+
"reusable",
|
|
22
|
+
"release",
|
|
23
|
+
"mixed",
|
|
24
|
+
]);
|
|
25
|
+
const CONCURRENCY_MODES = new Set([
|
|
26
|
+
"none",
|
|
27
|
+
"ref-scoped",
|
|
28
|
+
"workflow-scoped",
|
|
29
|
+
"serialized",
|
|
30
|
+
]);
|
|
31
|
+
const CANCELLATION_BEHAVIORS = new Set([
|
|
32
|
+
"cancel-stale",
|
|
33
|
+
"finish-started",
|
|
34
|
+
"provider-default",
|
|
35
|
+
"not-applicable",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function canonical(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
Object.keys(value)
|
|
43
|
+
.sort()
|
|
44
|
+
.map((key) => [key, canonical(value[key])]),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function root(value) {
|
|
51
|
+
return `sha256:${crypto
|
|
52
|
+
.createHash("sha256")
|
|
53
|
+
.update(JSON.stringify(canonical(value)))
|
|
54
|
+
.digest("hex")}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function triggerClass(triggers) {
|
|
58
|
+
const classes = new Set();
|
|
59
|
+
for (const trigger of triggers) {
|
|
60
|
+
if (trigger === "merge_group") classes.add("merge-group");
|
|
61
|
+
else if (trigger.startsWith("pull_request")) classes.add("pull-request");
|
|
62
|
+
else if (trigger === "push") classes.add("push");
|
|
63
|
+
else if (trigger === "schedule") classes.add("scheduled");
|
|
64
|
+
else if (trigger === "workflow_dispatch") classes.add("manual");
|
|
65
|
+
else if (trigger === "workflow_call") classes.add("reusable");
|
|
66
|
+
else if (trigger === "release") classes.add("release");
|
|
67
|
+
else classes.add("mixed");
|
|
68
|
+
}
|
|
69
|
+
return classes.size === 1 ? [...classes][0] : "mixed";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function diagnostic(code, laneId, message) {
|
|
73
|
+
return { code, laneId, message };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateDeclaration(declaration, observed) {
|
|
77
|
+
const laneId = String(declaration?.laneId || "");
|
|
78
|
+
const diagnostics = [];
|
|
79
|
+
if (!AUTHORITY_CLASSES.has(declaration?.authorityClass)) {
|
|
80
|
+
diagnostics.push(
|
|
81
|
+
diagnostic(
|
|
82
|
+
"invalid-authority-class",
|
|
83
|
+
laneId,
|
|
84
|
+
"authorityClass must identify the lane's merge or non-merge authority",
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!TRIGGER_CLASSES.has(declaration?.triggerClass)) {
|
|
89
|
+
diagnostics.push(
|
|
90
|
+
diagnostic(
|
|
91
|
+
"invalid-trigger-class",
|
|
92
|
+
laneId,
|
|
93
|
+
"triggerClass must identify the workflow trigger class",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
} else if (observed && declaration.triggerClass !== observed.triggerClass) {
|
|
97
|
+
diagnostics.push(
|
|
98
|
+
diagnostic(
|
|
99
|
+
"trigger-class-mismatch",
|
|
100
|
+
laneId,
|
|
101
|
+
`declared ${declaration.triggerClass}, observed ${observed.triggerClass}`,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const concurrency = declaration?.concurrencyPolicy;
|
|
106
|
+
if (
|
|
107
|
+
!concurrency ||
|
|
108
|
+
!CONCURRENCY_MODES.has(concurrency.mode) ||
|
|
109
|
+
typeof concurrency.cancelInProgress !== "boolean"
|
|
110
|
+
) {
|
|
111
|
+
diagnostics.push(
|
|
112
|
+
diagnostic(
|
|
113
|
+
"invalid-concurrency-policy",
|
|
114
|
+
laneId,
|
|
115
|
+
"concurrencyPolicy requires a known mode and boolean cancelInProgress",
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
!Number.isFinite(declaration?.expectedRunnerMinutes) ||
|
|
121
|
+
declaration.expectedRunnerMinutes <= 0
|
|
122
|
+
) {
|
|
123
|
+
diagnostics.push(
|
|
124
|
+
diagnostic(
|
|
125
|
+
"invalid-runner-budget",
|
|
126
|
+
laneId,
|
|
127
|
+
"expectedRunnerMinutes must be greater than zero",
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!CANCELLATION_BEHAVIORS.has(declaration?.cancellationBehavior)) {
|
|
132
|
+
diagnostics.push(
|
|
133
|
+
diagnostic(
|
|
134
|
+
"invalid-cancellation-behavior",
|
|
135
|
+
laneId,
|
|
136
|
+
"cancellationBehavior must describe how stale or started work settles",
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const impact = declaration?.sloImpact;
|
|
141
|
+
if (
|
|
142
|
+
!impact ||
|
|
143
|
+
typeof impact.mergeCritical !== "boolean" ||
|
|
144
|
+
!String(impact.metric || "").trim() ||
|
|
145
|
+
!Number.isFinite(impact.expectedContributionSeconds) ||
|
|
146
|
+
impact.expectedContributionSeconds < 0 ||
|
|
147
|
+
!String(impact.rationale || "").trim()
|
|
148
|
+
) {
|
|
149
|
+
diagnostics.push(
|
|
150
|
+
diagnostic(
|
|
151
|
+
"invalid-slo-impact",
|
|
152
|
+
laneId,
|
|
153
|
+
"sloImpact requires mergeCritical, metric, expectedContributionSeconds, and rationale",
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return diagnostics;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectLanes(workflows) {
|
|
161
|
+
const lanes = new Map();
|
|
162
|
+
for (const workflow of workflows) {
|
|
163
|
+
const workflowPath = String(workflow?.path || "").trim();
|
|
164
|
+
if (!workflowPath) throw new Error("workflow path is required");
|
|
165
|
+
const document = parseWorkflowDocument(workflow.text);
|
|
166
|
+
for (const job of document.jobs) {
|
|
167
|
+
const laneId = `${workflowPath}#${job.id}`;
|
|
168
|
+
if (lanes.has(laneId)) throw new Error(`duplicate CI lane: ${laneId}`);
|
|
169
|
+
lanes.set(laneId, {
|
|
170
|
+
laneId,
|
|
171
|
+
workflowPath,
|
|
172
|
+
jobId: job.id,
|
|
173
|
+
triggerClass: triggerClass(document.triggers),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return lanes;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function evaluateCiLaneChangeBudget({
|
|
181
|
+
policy,
|
|
182
|
+
workflows,
|
|
183
|
+
baselineWorkflows = null,
|
|
184
|
+
} = {}) {
|
|
185
|
+
if (policy?.schema !== CI_LANE_CHANGE_BUDGET_CONTRACT) {
|
|
186
|
+
throw new Error(`expected ${CI_LANE_CHANGE_BUDGET_CONTRACT}`);
|
|
187
|
+
}
|
|
188
|
+
if (!Array.isArray(workflows)) throw new Error("workflows must be an array");
|
|
189
|
+
const observed = collectLanes(workflows);
|
|
190
|
+
const baselineLaneIds = baselineWorkflows
|
|
191
|
+
? [...collectLanes(baselineWorkflows).keys()]
|
|
192
|
+
: policy.baseline?.lanes || [];
|
|
193
|
+
const baseline = new Set(baselineLaneIds);
|
|
194
|
+
if (baseline.size !== baselineLaneIds.length) {
|
|
195
|
+
throw new Error("baseline CI lanes must be unique");
|
|
196
|
+
}
|
|
197
|
+
const declarations = new Map();
|
|
198
|
+
const diagnostics = [];
|
|
199
|
+
for (const declaration of policy.declarations || []) {
|
|
200
|
+
const laneId = String(declaration?.laneId || "").trim();
|
|
201
|
+
if (!laneId) throw new Error("CI lane declaration laneId is required");
|
|
202
|
+
if (declarations.has(laneId))
|
|
203
|
+
throw new Error(`duplicate CI lane declaration: ${laneId}`);
|
|
204
|
+
declarations.set(laneId, declaration);
|
|
205
|
+
if (!observed.has(laneId)) {
|
|
206
|
+
diagnostics.push(
|
|
207
|
+
diagnostic(
|
|
208
|
+
"stale-declaration",
|
|
209
|
+
laneId,
|
|
210
|
+
"declared CI lane is absent from the current workflow cut",
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
diagnostics.push(
|
|
215
|
+
...validateDeclaration(declaration, observed.get(laneId) || null),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const newLanes = [...observed.keys()]
|
|
219
|
+
.filter((laneId) => !baseline.has(laneId))
|
|
220
|
+
.sort();
|
|
221
|
+
for (const laneId of newLanes) {
|
|
222
|
+
if (!declarations.has(laneId)) {
|
|
223
|
+
diagnostics.push(
|
|
224
|
+
diagnostic(
|
|
225
|
+
"undeclared-new-lane",
|
|
226
|
+
laneId,
|
|
227
|
+
"new CI lane must declare authority, trigger, concurrency, runner, cancellation, and SLO budgets",
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const result = {
|
|
233
|
+
schema: "buildchain.ci-lane-change-budget.evaluation/v1",
|
|
234
|
+
ok: diagnostics.length === 0,
|
|
235
|
+
observedLaneCount: observed.size,
|
|
236
|
+
baselineLaneCount: baseline.size,
|
|
237
|
+
newLaneCount: newLanes.length,
|
|
238
|
+
declaredLaneCount: declarations.size,
|
|
239
|
+
newLanes,
|
|
240
|
+
diagnostics: diagnostics.sort((left, right) =>
|
|
241
|
+
`${left.laneId}:${left.code}`.localeCompare(
|
|
242
|
+
`${right.laneId}:${right.code}`,
|
|
243
|
+
),
|
|
244
|
+
),
|
|
245
|
+
};
|
|
246
|
+
return { ...result, evaluationRoot: root(result) };
|
|
247
|
+
}
|
|
@@ -82,7 +82,9 @@ export function selectLatestQualifiedSource({
|
|
|
82
82
|
requiredWorkflowPaths,
|
|
83
83
|
now,
|
|
84
84
|
maxAgeSeconds,
|
|
85
|
+
ignoredSourceShas = [],
|
|
85
86
|
}) {
|
|
87
|
+
const ignored = new Set(ignoredSourceShas);
|
|
86
88
|
const latestByPath = new Map(
|
|
87
89
|
requiredWorkflowPaths.map((workflow) => [
|
|
88
90
|
workflow,
|
|
@@ -90,7 +92,11 @@ export function selectLatestQualifiedSource({
|
|
|
90
92
|
]),
|
|
91
93
|
);
|
|
92
94
|
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
93
|
-
const sourceSha =
|
|
95
|
+
const sourceSha =
|
|
96
|
+
typeof sourceHistory[index] === "string"
|
|
97
|
+
? sourceHistory[index]
|
|
98
|
+
: sourceHistory[index]?.sha;
|
|
99
|
+
if (ignored.has(sourceSha)) continue;
|
|
94
100
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
95
101
|
latestByPath.get(workflow).get(sourceSha),
|
|
96
102
|
);
|
|
@@ -115,7 +121,9 @@ export function selectLatestQualifiedSource({
|
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
const staleSuccessfulPair = sourceHistory.some((
|
|
124
|
+
const staleSuccessfulPair = sourceHistory.some((entry) => {
|
|
125
|
+
const sourceSha = typeof entry === "string" ? entry : entry?.sha;
|
|
126
|
+
if (ignored.has(sourceSha)) return false;
|
|
119
127
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
120
128
|
latestByPath.get(workflow).get(sourceSha),
|
|
121
129
|
);
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryExactRoot as exactRoot,
|
|
3
|
+
devDeliveryExactSha as exactSha,
|
|
4
|
+
devDeliveryText as text,
|
|
5
|
+
devDeliveryTimestamp as timestamp,
|
|
6
|
+
} from "./dev-delivery-common.js";
|
|
7
|
+
import { createDevDeliveryCandidateIdentity } from "./dev-delivery-candidate-identity.js";
|
|
8
|
+
import { normalizeNativeCommandContract } from "./dev-delivery-native-proof.js";
|
|
9
|
+
import { normalizeDevDeliveryTerminalProviderEvidence } from "./dev-delivery-provider-attempt.js";
|
|
10
|
+
import { normalizeProviderFailureAuthorityBinding } from "./dev-delivery-warrant-settlement.js";
|
|
11
|
+
|
|
12
|
+
export const TERMINAL_STATES = new Set([
|
|
13
|
+
"merged",
|
|
14
|
+
"terminal-failure",
|
|
15
|
+
"dequeued",
|
|
16
|
+
"cancelled",
|
|
17
|
+
]);
|
|
18
|
+
const DELIVERY_CLASSES = new Set([
|
|
19
|
+
"non-native-fast",
|
|
20
|
+
"native-proof-required",
|
|
21
|
+
"cross-platform",
|
|
22
|
+
"release",
|
|
23
|
+
]);
|
|
24
|
+
export const DEV_DELIVERY_COMPATIBILITY_QUALIFICATION_SCHEMA =
|
|
25
|
+
"kungfu.buildchain.dev-delivery-compatibility-qualification/v1";
|
|
26
|
+
export const DEV_DELIVERY_NATIVE_QUALIFICATION_SCHEMA =
|
|
27
|
+
"kungfu.buildchain.dev-delivery-native-qualification/v1";
|
|
28
|
+
|
|
29
|
+
function nonNegativeInteger(value, label, fallback = 0) {
|
|
30
|
+
const parsed = Number(value ?? fallback);
|
|
31
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
32
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizedDomains(input = []) {
|
|
38
|
+
if (!Array.isArray(input))
|
|
39
|
+
throw new Error(
|
|
40
|
+
"qualificationDomains must be an array of rooted safety domains",
|
|
41
|
+
);
|
|
42
|
+
return [
|
|
43
|
+
...new Set(input.map((value) => exactRoot(value, "qualification domain"))),
|
|
44
|
+
].sort();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function candidateIdentity(input, expected) {
|
|
48
|
+
return createDevDeliveryCandidateIdentity(input, expected, (value) => {
|
|
49
|
+
const normalized = text(value);
|
|
50
|
+
if (!DELIVERY_CLASSES.has(normalized))
|
|
51
|
+
throw new Error(`unsupported deliveryClass ${normalized}`);
|
|
52
|
+
return normalized;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function nullableRoot(value, label) {
|
|
57
|
+
return value === null ? null : exactRoot(value, label);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeQualification(input) {
|
|
61
|
+
if (!input) return null;
|
|
62
|
+
if (input.schema === DEV_DELIVERY_COMPATIBILITY_QUALIFICATION_SCHEMA) {
|
|
63
|
+
if (input.authority !== "legacy-compatibility-only")
|
|
64
|
+
throw new Error(
|
|
65
|
+
"compatibility qualification cannot claim native authority",
|
|
66
|
+
);
|
|
67
|
+
if (input.nativeProofAuthority !== false)
|
|
68
|
+
throw new Error(
|
|
69
|
+
"compatibility qualification must deny native proof authority",
|
|
70
|
+
);
|
|
71
|
+
if (!["phase-less", "qualified"].includes(input.legacyWarrantPhase))
|
|
72
|
+
throw new Error(
|
|
73
|
+
"compatibility qualification legacy phase is unsupported",
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
schema: input.schema,
|
|
77
|
+
authority: input.authority,
|
|
78
|
+
nativeProofAuthority: false,
|
|
79
|
+
legacyStateRoot: exactRoot(
|
|
80
|
+
input.legacyStateRoot,
|
|
81
|
+
"compatibility qualification legacyStateRoot",
|
|
82
|
+
),
|
|
83
|
+
legacyWarrantPhase: input.legacyWarrantPhase,
|
|
84
|
+
legacyFencingToken: exactRoot(
|
|
85
|
+
input.legacyFencingToken,
|
|
86
|
+
"compatibility qualification legacyFencingToken",
|
|
87
|
+
),
|
|
88
|
+
legacyGeneration: nonNegativeInteger(
|
|
89
|
+
input.legacyGeneration,
|
|
90
|
+
"compatibility qualification legacyGeneration",
|
|
91
|
+
),
|
|
92
|
+
qualificationReceiptRoot: nullableRoot(
|
|
93
|
+
input.qualificationReceiptRoot,
|
|
94
|
+
"compatibility qualification receiptRoot",
|
|
95
|
+
),
|
|
96
|
+
sourceProofRoot: exactRoot(
|
|
97
|
+
input.sourceProofRoot,
|
|
98
|
+
"compatibility qualification sourceProofRoot",
|
|
99
|
+
),
|
|
100
|
+
nativeProofRoot: nullableRoot(
|
|
101
|
+
input.nativeProofRoot,
|
|
102
|
+
"compatibility qualification nativeProofRoot",
|
|
103
|
+
),
|
|
104
|
+
nativeExecutionBindingRoot: nullableRoot(
|
|
105
|
+
input.nativeExecutionBindingRoot,
|
|
106
|
+
"compatibility qualification nativeExecutionBindingRoot",
|
|
107
|
+
),
|
|
108
|
+
nativeExecutionReceiptRoot: nullableRoot(
|
|
109
|
+
input.nativeExecutionReceiptRoot,
|
|
110
|
+
"compatibility qualification nativeExecutionReceiptRoot",
|
|
111
|
+
),
|
|
112
|
+
nativeCommandRoot: nullableRoot(
|
|
113
|
+
input.nativeCommandRoot,
|
|
114
|
+
"compatibility qualification nativeCommandRoot",
|
|
115
|
+
),
|
|
116
|
+
qualificationContractRoot: nullableRoot(
|
|
117
|
+
input.qualificationContractRoot,
|
|
118
|
+
"compatibility qualification contractRoot",
|
|
119
|
+
),
|
|
120
|
+
qualifiedAt:
|
|
121
|
+
input.qualifiedAt === null
|
|
122
|
+
? null
|
|
123
|
+
: timestamp(
|
|
124
|
+
input.qualifiedAt,
|
|
125
|
+
"compatibility qualification qualifiedAt",
|
|
126
|
+
),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (input.schema !== DEV_DELIVERY_NATIVE_QUALIFICATION_SCHEMA)
|
|
130
|
+
throw new Error("native qualification schema is unsupported");
|
|
131
|
+
if (
|
|
132
|
+
input.authority !== "verified-native-qualification" ||
|
|
133
|
+
input.nativeProofAuthority !== true
|
|
134
|
+
)
|
|
135
|
+
throw new Error("native qualification must carry verified proof authority");
|
|
136
|
+
return {
|
|
137
|
+
schema: input.schema,
|
|
138
|
+
authority: input.authority,
|
|
139
|
+
nativeProofAuthority: true,
|
|
140
|
+
...(input.evidenceRoot
|
|
141
|
+
? {
|
|
142
|
+
evidenceRoot: exactRoot(
|
|
143
|
+
input.evidenceRoot,
|
|
144
|
+
"qualification evidenceRoot",
|
|
145
|
+
),
|
|
146
|
+
}
|
|
147
|
+
: {}),
|
|
148
|
+
qualificationReceiptRoot: exactRoot(
|
|
149
|
+
input.qualificationReceiptRoot,
|
|
150
|
+
"qualification receiptRoot",
|
|
151
|
+
),
|
|
152
|
+
sourceProofRoot: exactRoot(
|
|
153
|
+
input.sourceProofRoot,
|
|
154
|
+
"qualification sourceProofRoot",
|
|
155
|
+
),
|
|
156
|
+
nativeProofRoot: exactRoot(
|
|
157
|
+
input.nativeProofRoot,
|
|
158
|
+
"qualification nativeProofRoot",
|
|
159
|
+
),
|
|
160
|
+
nativeExecutionBindingRoot: exactRoot(
|
|
161
|
+
input.nativeExecutionBindingRoot,
|
|
162
|
+
"qualification nativeExecutionBindingRoot",
|
|
163
|
+
),
|
|
164
|
+
nativeExecutionReceiptRoot: exactRoot(
|
|
165
|
+
input.nativeExecutionReceiptRoot,
|
|
166
|
+
"qualification nativeExecutionReceiptRoot",
|
|
167
|
+
),
|
|
168
|
+
nativeCommandRoot: exactRoot(
|
|
169
|
+
input.nativeCommandRoot,
|
|
170
|
+
"qualification nativeCommandRoot",
|
|
171
|
+
),
|
|
172
|
+
qualificationContractRoot: exactRoot(
|
|
173
|
+
input.qualificationContractRoot,
|
|
174
|
+
"qualification contractRoot",
|
|
175
|
+
),
|
|
176
|
+
qualifiedAt: timestamp(input.qualifiedAt, "qualification qualifiedAt"),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function normalizeDevDeliveryAuthorityCandidate(input, expected) {
|
|
181
|
+
const identity = candidateIdentity(input, expected);
|
|
182
|
+
if (input.candidateId && input.candidateId !== identity.candidateId)
|
|
183
|
+
throw new Error(
|
|
184
|
+
`candidateId mismatch for PR #${identity.pullRequestNumber}`,
|
|
185
|
+
);
|
|
186
|
+
const status = text(input.status || "queued");
|
|
187
|
+
if (
|
|
188
|
+
![
|
|
189
|
+
"queued",
|
|
190
|
+
"qualifying",
|
|
191
|
+
"qualified",
|
|
192
|
+
"landing",
|
|
193
|
+
...TERMINAL_STATES,
|
|
194
|
+
].includes(status)
|
|
195
|
+
)
|
|
196
|
+
throw new Error(`unsupported candidate status ${status || "<empty>"}`);
|
|
197
|
+
const candidate = {
|
|
198
|
+
...identity,
|
|
199
|
+
sourceHead: exactSha(input.sourceHead, "sourceHead"),
|
|
200
|
+
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
201
|
+
sourceProofRoot: exactRoot(input.sourceProofRoot, "sourceProofRoot"),
|
|
202
|
+
planRoot: exactRoot(input.planRoot, "planRoot"),
|
|
203
|
+
closureRoot: exactRoot(input.closureRoot, "closureRoot"),
|
|
204
|
+
dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
|
|
205
|
+
toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
|
|
206
|
+
enqueuedAt: timestamp(input.enqueuedAt, "candidate enqueuedAt"),
|
|
207
|
+
updatedAt: timestamp(
|
|
208
|
+
input.updatedAt || input.enqueuedAt,
|
|
209
|
+
"candidate updatedAt",
|
|
210
|
+
),
|
|
211
|
+
status,
|
|
212
|
+
qualification: normalizeQualification(input.qualification),
|
|
213
|
+
terminal: input.terminal
|
|
214
|
+
? {
|
|
215
|
+
outcome: text(input.terminal.outcome),
|
|
216
|
+
evidenceRoot: exactRoot(
|
|
217
|
+
input.terminal.evidenceRoot,
|
|
218
|
+
"terminal evidenceRoot",
|
|
219
|
+
),
|
|
220
|
+
reason: text(input.terminal.reason),
|
|
221
|
+
settledAt: timestamp(input.terminal.settledAt, "terminal settledAt"),
|
|
222
|
+
...(normalizeProviderFailureAuthorityBinding(input.terminal) || {}),
|
|
223
|
+
...normalizeDevDeliveryTerminalProviderEvidence(input.terminal),
|
|
224
|
+
}
|
|
225
|
+
: null,
|
|
226
|
+
};
|
|
227
|
+
if (Object.hasOwn(input, "environmentRoot"))
|
|
228
|
+
candidate.environmentRoot = exactRoot(
|
|
229
|
+
input.environmentRoot,
|
|
230
|
+
"environmentRoot",
|
|
231
|
+
);
|
|
232
|
+
if (Object.hasOwn(input, "nativeCommandContract")) {
|
|
233
|
+
candidate.nativeCommandContract = normalizeNativeCommandContract(
|
|
234
|
+
input.nativeCommandContract,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (Object.hasOwn(input, "affectedPaths")) {
|
|
238
|
+
if (!Array.isArray(input.affectedPaths))
|
|
239
|
+
throw new Error("candidate affectedPaths must be an array");
|
|
240
|
+
candidate.affectedPaths = [
|
|
241
|
+
...new Set(input.affectedPaths.map(text).filter(Boolean)),
|
|
242
|
+
].sort();
|
|
243
|
+
}
|
|
244
|
+
if (Object.hasOwn(input, "shardEvidenceRoots")) {
|
|
245
|
+
if (!Array.isArray(input.shardEvidenceRoots))
|
|
246
|
+
throw new Error("candidate shardEvidenceRoots must be an array");
|
|
247
|
+
candidate.shardEvidenceRoots = [
|
|
248
|
+
...new Set(
|
|
249
|
+
input.shardEvidenceRoots.map((value) =>
|
|
250
|
+
exactRoot(value, "candidate shardEvidenceRoot"),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
253
|
+
].sort();
|
|
254
|
+
}
|
|
255
|
+
if (Object.hasOwn(input, "qualificationDomains"))
|
|
256
|
+
candidate.qualificationDomains = normalizedDomains(
|
|
257
|
+
input.qualificationDomains,
|
|
258
|
+
);
|
|
259
|
+
if (Object.hasOwn(input, "qualificationAttempts"))
|
|
260
|
+
candidate.qualificationAttempts = nonNegativeInteger(
|
|
261
|
+
input.qualificationAttempts,
|
|
262
|
+
"candidate qualificationAttempts",
|
|
263
|
+
);
|
|
264
|
+
if (Object.hasOwn(input, "landingOvertakes"))
|
|
265
|
+
candidate.landingOvertakes = nonNegativeInteger(
|
|
266
|
+
input.landingOvertakes,
|
|
267
|
+
"candidate landingOvertakes",
|
|
268
|
+
);
|
|
269
|
+
return candidate;
|
|
270
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryClone as clone,
|
|
3
|
+
devDeliveryContentRoot,
|
|
4
|
+
} from "./dev-delivery-common.js";
|
|
5
|
+
import { verifySourceQualificationProof } from "./dev-delivery-proof.js";
|
|
6
|
+
import { verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
|
|
7
|
+
|
|
8
|
+
export const DEV_DELIVERY_BOUNDED_QUALIFICATION_CONTRACT_SCHEMA =
|
|
9
|
+
"kungfu.buildchain.dev-delivery-bounded-qualification-contract/v1";
|
|
10
|
+
export const DEV_DELIVERY_BOUNDED_QUALIFICATION_RECEIPT_SCHEMA =
|
|
11
|
+
"kungfu.buildchain.dev-delivery-bounded-qualification-receipt/v1";
|
|
12
|
+
|
|
13
|
+
function contractBody({ state, candidate, lease, sourceProof, nativeProof }) {
|
|
14
|
+
return {
|
|
15
|
+
schema: DEV_DELIVERY_BOUNDED_QUALIFICATION_CONTRACT_SCHEMA,
|
|
16
|
+
authority: "qualification-only",
|
|
17
|
+
mergeGroupAdmission: false,
|
|
18
|
+
repository: state.repository,
|
|
19
|
+
protectedBase: state.protectedBase,
|
|
20
|
+
candidateId: candidate.candidateId,
|
|
21
|
+
qualificationLeaseToken: lease.token,
|
|
22
|
+
qualificationLeaseGeneration: lease.generation,
|
|
23
|
+
sourceHead: candidate.sourceHead,
|
|
24
|
+
sourceProofRoot: sourceProof.proofRoot,
|
|
25
|
+
nativeProofRoot: nativeProof.proofRoot,
|
|
26
|
+
nativeExecutionBindingRoot: nativeProof.nativeExecutionBindingRoot,
|
|
27
|
+
nativeExecutionReceiptRoot: nativeProof.nativeExecutionReceiptRoot,
|
|
28
|
+
nativeCommandRoot: candidate.nativeCommandContract.commandRoot,
|
|
29
|
+
planRoot: candidate.planRoot,
|
|
30
|
+
closureRoot: candidate.closureRoot,
|
|
31
|
+
dependencyRoot: candidate.dependencyRoot,
|
|
32
|
+
toolchainRoot: candidate.toolchainRoot,
|
|
33
|
+
environmentRoot: candidate.environmentRoot,
|
|
34
|
+
qualificationDomains: candidate.qualificationDomains || [],
|
|
35
|
+
shardEvidenceRoots: candidate.shardEvidenceRoots || [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createDevDeliveryQualificationContract(input = {}) {
|
|
40
|
+
const body = contractBody(input);
|
|
41
|
+
return { ...body, contractRoot: devDeliveryContentRoot(body) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function verifyDevDeliveryQualificationEvidence({
|
|
45
|
+
state,
|
|
46
|
+
candidate,
|
|
47
|
+
lease,
|
|
48
|
+
sourceProof,
|
|
49
|
+
nativeProof,
|
|
50
|
+
qualificationContract,
|
|
51
|
+
}) {
|
|
52
|
+
const source = verifySourceQualificationProof(sourceProof, {
|
|
53
|
+
repository: state.repository,
|
|
54
|
+
protectedBase: state.protectedBase,
|
|
55
|
+
sourceHead: candidate.sourceHead,
|
|
56
|
+
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
57
|
+
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
58
|
+
planRoot: candidate.planRoot,
|
|
59
|
+
closureRoot: candidate.closureRoot,
|
|
60
|
+
dependencyRoot: candidate.dependencyRoot,
|
|
61
|
+
toolchainRoot: candidate.toolchainRoot,
|
|
62
|
+
});
|
|
63
|
+
if (!source.ok) {
|
|
64
|
+
throw new Error(`source qualification proof rejected: ${source.reason}`);
|
|
65
|
+
}
|
|
66
|
+
if (source.proofRoot !== candidate.sourceProofRoot) {
|
|
67
|
+
throw new Error("source qualification proof root does not match candidate");
|
|
68
|
+
}
|
|
69
|
+
const native = verifyNativeQualificationProof(nativeProof, {
|
|
70
|
+
repository: state.repository,
|
|
71
|
+
protectedBase: state.protectedBase,
|
|
72
|
+
sourceHead: candidate.sourceHead,
|
|
73
|
+
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
74
|
+
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
75
|
+
planRoot: candidate.planRoot,
|
|
76
|
+
closureRoot: candidate.closureRoot,
|
|
77
|
+
dependencyRoot: candidate.dependencyRoot,
|
|
78
|
+
toolchainRoot: candidate.toolchainRoot,
|
|
79
|
+
environmentRoot: candidate.environmentRoot,
|
|
80
|
+
nativeCommandRoot: candidate.nativeCommandContract?.commandRoot,
|
|
81
|
+
});
|
|
82
|
+
if (!native.ok) {
|
|
83
|
+
throw new Error(`native qualification proof rejected: ${native.reason}`);
|
|
84
|
+
}
|
|
85
|
+
const expectedShards = [
|
|
86
|
+
...(candidate.shardEvidenceRoots || []),
|
|
87
|
+
nativeProof.nativeExecutionReceiptRoot,
|
|
88
|
+
].sort();
|
|
89
|
+
if (
|
|
90
|
+
JSON.stringify(nativeProof.shardEvidenceRoots) !==
|
|
91
|
+
JSON.stringify([...new Set(expectedShards)])
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"native qualification shard evidence does not match candidate",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const expected = createDevDeliveryQualificationContract({
|
|
98
|
+
state,
|
|
99
|
+
candidate,
|
|
100
|
+
lease,
|
|
101
|
+
sourceProof,
|
|
102
|
+
nativeProof,
|
|
103
|
+
});
|
|
104
|
+
const observed = clone(qualificationContract || {});
|
|
105
|
+
if (
|
|
106
|
+
JSON.stringify(observed) !== JSON.stringify(expected) ||
|
|
107
|
+
observed.contractRoot !== expected.contractRoot
|
|
108
|
+
) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"bounded qualification contract does not match exact evidence",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
sourceProofRoot: source.proofRoot,
|
|
115
|
+
nativeProofRoot: native.proofRoot,
|
|
116
|
+
nativeExecutionBindingRoot: nativeProof.nativeExecutionBindingRoot,
|
|
117
|
+
nativeExecutionReceiptRoot: nativeProof.nativeExecutionReceiptRoot,
|
|
118
|
+
nativeCommandRoot: candidate.nativeCommandContract.commandRoot,
|
|
119
|
+
qualificationContractRoot: expected.contractRoot,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function createDevDeliveryBoundedQualificationReceipt({
|
|
124
|
+
state,
|
|
125
|
+
candidate,
|
|
126
|
+
lease,
|
|
127
|
+
evidence,
|
|
128
|
+
qualifiedAt,
|
|
129
|
+
}) {
|
|
130
|
+
const receipt = {
|
|
131
|
+
schema: DEV_DELIVERY_BOUNDED_QUALIFICATION_RECEIPT_SCHEMA,
|
|
132
|
+
action: "qualification-completed-lease-released",
|
|
133
|
+
authority: "qualification-only",
|
|
134
|
+
mergeGroupAdmission: false,
|
|
135
|
+
repository: state.repository,
|
|
136
|
+
protectedBase: state.protectedBase,
|
|
137
|
+
candidateId: candidate.candidateId,
|
|
138
|
+
qualificationLeaseToken: lease.token,
|
|
139
|
+
qualificationLeaseGeneration: lease.generation,
|
|
140
|
+
...evidence,
|
|
141
|
+
qualifiedAt,
|
|
142
|
+
expectedOldStateRoot: state.stateRoot,
|
|
143
|
+
nextAction: "Wait for the single exclusive Landing Warrant.",
|
|
144
|
+
};
|
|
145
|
+
return { receipt, receiptRoot: devDeliveryContentRoot(receipt) };
|
|
146
|
+
}
|