@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55
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/README.md +9 -6
- package/actions/release-tail/README.md +8 -0
- package/actions/v4-release-candidate-promote/README.md +3 -0
- package/architecture/agent-change-map.md +1015 -0
- package/architecture/ci-lane-change-budget.json +256 -0
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +220 -15
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +489 -132
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +4 -3
- package/architecture/v3-v4-capability-closure.md +59 -0
- package/architecture/v3-v4-live-capability-inventory.json +102404 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/buildchain.mjs +7 -1
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +1 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -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-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 +1 -1
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/agent-index.json +4 -0
- package/dist/site/artifact-schemas.json +8 -0
- package/dist/site/buildchain-contract.json +125 -34
- package/dist/site/buildchain-site.json +308 -33
- package/dist/site/capability-registry.json +13 -12
- package/dist/site/cli-registry.json +57 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +438 -24
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -7
- package/dist/site/node-api-registry.json +12843 -9314
- package/dist/site/page-registry.json +288 -21
- package/dist/site/public-surface-audit.json +281 -21
- package/dist/site/publication-authority-registry.json +66 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +20 -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-publication-qualification-v1.schema.json +51 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +19 -11
- package/dist/site/workflow-registry.json +195 -17
- package/docs/MAP.md +4 -2
- package/docs/cli-reference.md +70 -2
- package/docs/cli.md +8 -0
- package/docs/dev-delivery-warrant.md +34 -10
- package/docs/node-api-reference.md +524 -279
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +152 -0
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/package.json +24 -4
- package/packages/core/adopter-delivery-passport.js +295 -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 +1 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +31 -1
- 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 +2 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-delivery-native-proof.js +43 -27
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +46 -10
- package/packages/core/dev-delivery-warrant.js +29 -7
- package/packages/core/github-governance-authority.js +5 -4
- package/packages/core/index.js +6 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/paper-scaffold-content.js +401 -0
- package/packages/core/paper.js +12 -378
- package/packages/core/publication-control-plane-audit.js +135 -111
- package/packages/core/publication-rehearsal-projection.js +104 -0
- package/packages/core/publication-rehearsal-runtime.js +190 -0
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +71 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +95 -170
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -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 +139 -35
- package/packages/core/v4-floating-consumer-release-passport.js +154 -8
- package/packages/core/v4-publication-qualification.js +222 -0
- 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/workflow-yaml-contract.js +24 -2
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +24 -6
- package/scripts/check-maintainability.mjs +145 -4
- package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
- package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +30 -4
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -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-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant-store.mjs +10 -6
- package/scripts/dev-delivery-warrant.mjs +50 -6
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +45 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-release-candidate-passport.mjs +177 -0
- package/scripts/generate-site-bundle.mjs +36 -0
- package/scripts/maintainability-domain-metrics.mjs +237 -0
- package/scripts/maintainability-governance.mjs +473 -0
- package/scripts/maintainability-metrics.mjs +28 -6
- package/scripts/project-cut-merge-queue-admission.mjs +394 -0
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +68 -3
- package/scripts/resume-from-candidate-run.mjs +595 -65
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +21 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-consumer-policy.mjs +4 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- package/scripts/v4-declarative-promotion-admission.mjs +42 -0
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { adopterDeliveryGateDigest } from "./adopter-delivery-json.js";
|
|
2
|
+
|
|
3
|
+
export const V4_CROSS_PLATFORM_ADOPTER_REPORT_CONTRACT =
|
|
4
|
+
"kungfu-buildchain-v4-cross-platform-adopter-report/v1";
|
|
5
|
+
export const V4_CROSS_PLATFORM_ADOPTER_QUALIFICATION_CONTRACT =
|
|
6
|
+
"kungfu-buildchain-v4-cross-platform-adopter-qualification/v1";
|
|
7
|
+
export const V4_CROSS_PLATFORM_ADOPTER_PLATFORMS = Object.freeze([
|
|
8
|
+
"linux-x64",
|
|
9
|
+
"macos-arm64",
|
|
10
|
+
"windows-x64",
|
|
11
|
+
]);
|
|
12
|
+
export const V4_CROSS_PLATFORM_NEUTRAL_DRIVER = "ledger-specification-driver";
|
|
13
|
+
|
|
14
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
15
|
+
const ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
16
|
+
const CONSUMER = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
17
|
+
|
|
18
|
+
function fail(message) {
|
|
19
|
+
throw new Error(`v4 cross-platform adopter qualification: ${message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function exactKeys(value, keys, label) {
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
24
|
+
fail(`${label} must be an object`);
|
|
25
|
+
const actual = Object.keys(value).sort();
|
|
26
|
+
const expected = [...keys].sort();
|
|
27
|
+
if (
|
|
28
|
+
actual.length !== expected.length ||
|
|
29
|
+
actual.some((key, index) => key !== expected[index])
|
|
30
|
+
)
|
|
31
|
+
fail(`${label} has an unsupported field set`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function same(left, right) {
|
|
36
|
+
return adopterDeliveryGateDigest(left) === adopterDeliveryGateDigest(right);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function summarizeV3V4CapabilityInventory(inventory) {
|
|
40
|
+
exactKeys(
|
|
41
|
+
inventory,
|
|
42
|
+
[
|
|
43
|
+
"capabilities",
|
|
44
|
+
"contract",
|
|
45
|
+
"extraction",
|
|
46
|
+
"reverseHistory",
|
|
47
|
+
"schemaVersion",
|
|
48
|
+
"sourceCuts",
|
|
49
|
+
"summary",
|
|
50
|
+
],
|
|
51
|
+
"capability inventory",
|
|
52
|
+
);
|
|
53
|
+
if (
|
|
54
|
+
inventory.schemaVersion !== 1 ||
|
|
55
|
+
inventory.contract !==
|
|
56
|
+
"kungfu-buildchain-v3-v4-live-capability-inventory" ||
|
|
57
|
+
!Array.isArray(inventory.capabilities) ||
|
|
58
|
+
inventory.capabilities.length === 0
|
|
59
|
+
)
|
|
60
|
+
fail("capability inventory contract is unsupported");
|
|
61
|
+
|
|
62
|
+
const categoryCounts = {};
|
|
63
|
+
const dispositionCounts = {};
|
|
64
|
+
const ids = new Set();
|
|
65
|
+
for (const [index, capability] of inventory.capabilities.entries()) {
|
|
66
|
+
if (
|
|
67
|
+
!capability ||
|
|
68
|
+
typeof capability !== "object" ||
|
|
69
|
+
typeof capability.id !== "string" ||
|
|
70
|
+
typeof capability.category !== "string" ||
|
|
71
|
+
typeof capability.disposition !== "string" ||
|
|
72
|
+
!capability.sourceEvidence ||
|
|
73
|
+
!capability.v4Route ||
|
|
74
|
+
typeof capability.v4Route.capabilityId !== "string" ||
|
|
75
|
+
capability.v4Route.capabilityId.length === 0 ||
|
|
76
|
+
!capability.v4Route.evidence ||
|
|
77
|
+
capability.residual !== null
|
|
78
|
+
)
|
|
79
|
+
fail(
|
|
80
|
+
`capability ${index} does not bind an exact v3 source to a v4 route`,
|
|
81
|
+
);
|
|
82
|
+
if (ids.has(capability.id))
|
|
83
|
+
fail(`duplicate capability id ${capability.id}`);
|
|
84
|
+
ids.add(capability.id);
|
|
85
|
+
categoryCounts[capability.category] =
|
|
86
|
+
(categoryCounts[capability.category] || 0) + 1;
|
|
87
|
+
dispositionCounts[capability.disposition] =
|
|
88
|
+
(dispositionCounts[capability.disposition] || 0) + 1;
|
|
89
|
+
}
|
|
90
|
+
const summary = {
|
|
91
|
+
capabilityCount: inventory.capabilities.length,
|
|
92
|
+
categoryCounts: Object.fromEntries(
|
|
93
|
+
Object.entries(categoryCounts).sort(([left], [right]) =>
|
|
94
|
+
left.localeCompare(right, "en"),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
dispositionCounts: Object.fromEntries(
|
|
98
|
+
Object.entries(dispositionCounts).sort(([left], [right]) =>
|
|
99
|
+
left.localeCompare(right, "en"),
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
residualCount: 0,
|
|
103
|
+
unknownCount: 0,
|
|
104
|
+
unownedCount: 0,
|
|
105
|
+
};
|
|
106
|
+
if (!same(summary, inventory.summary))
|
|
107
|
+
fail("capability inventory summary does not match its raw rows");
|
|
108
|
+
return {
|
|
109
|
+
inventoryRoot: adopterDeliveryGateDigest(inventory),
|
|
110
|
+
sourceCuts: structuredClone(inventory.sourceCuts),
|
|
111
|
+
summary,
|
|
112
|
+
categories: Object.entries(summary.categoryCounts).map(
|
|
113
|
+
([category, count]) => ({
|
|
114
|
+
category,
|
|
115
|
+
count,
|
|
116
|
+
applicability: "exact-source-route",
|
|
117
|
+
status: "passed",
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validateSourceBinding(binding) {
|
|
124
|
+
exactKeys(
|
|
125
|
+
binding,
|
|
126
|
+
["runtimeSha", "consumerSha", "inventoryRoot", "sourceCuts"],
|
|
127
|
+
"source binding",
|
|
128
|
+
);
|
|
129
|
+
if (
|
|
130
|
+
!SHA.test(binding.runtimeSha || "") ||
|
|
131
|
+
!SHA.test(binding.consumerSha || "") ||
|
|
132
|
+
!ROOT.test(binding.inventoryRoot || "")
|
|
133
|
+
)
|
|
134
|
+
fail("report requires exact runtime, consumer and inventory roots");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function completeCategory(entry, counts) {
|
|
138
|
+
return (
|
|
139
|
+
entry &&
|
|
140
|
+
typeof entry.category === "string" &&
|
|
141
|
+
Number.isSafeInteger(entry.count) &&
|
|
142
|
+
entry.count > 0 &&
|
|
143
|
+
entry.applicability === "exact-source-route" &&
|
|
144
|
+
entry.status === "passed" &&
|
|
145
|
+
counts[entry.category] === entry.count
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sumCounts(counts) {
|
|
150
|
+
return Object.values(counts).reduce((total, count) => total + count, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function validateCapabilityMatrix(matrix) {
|
|
154
|
+
exactKeys(
|
|
155
|
+
matrix,
|
|
156
|
+
["capabilityCount", "categoryCounts", "dispositionCounts", "categories"],
|
|
157
|
+
"capability matrix",
|
|
158
|
+
);
|
|
159
|
+
const complete =
|
|
160
|
+
Number.isSafeInteger(matrix.capabilityCount) &&
|
|
161
|
+
matrix.capabilityCount > 0 &&
|
|
162
|
+
Array.isArray(matrix.categories) &&
|
|
163
|
+
matrix.categories.length > 0 &&
|
|
164
|
+
matrix.categories.every((entry) =>
|
|
165
|
+
completeCategory(entry, matrix.categoryCounts),
|
|
166
|
+
) &&
|
|
167
|
+
Object.keys(matrix.categoryCounts).length === matrix.categories.length &&
|
|
168
|
+
sumCounts(matrix.categoryCounts) === matrix.capabilityCount &&
|
|
169
|
+
sumCounts(matrix.dispositionCounts) === matrix.capabilityCount;
|
|
170
|
+
if (!complete)
|
|
171
|
+
fail("platform report does not carry the complete raw capability matrix");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function validateExecutionEvidence(execution) {
|
|
175
|
+
exactKeys(
|
|
176
|
+
execution,
|
|
177
|
+
[
|
|
178
|
+
"initialRun",
|
|
179
|
+
"tamperFailure",
|
|
180
|
+
"retryRun",
|
|
181
|
+
"terminalVerify",
|
|
182
|
+
"bootstrap",
|
|
183
|
+
"neutralDriver",
|
|
184
|
+
],
|
|
185
|
+
"execution evidence",
|
|
186
|
+
);
|
|
187
|
+
const complete =
|
|
188
|
+
execution.initialRun?.status === "passed" &&
|
|
189
|
+
ROOT.test(execution.initialRun?.readbackRoot || "") &&
|
|
190
|
+
execution.tamperFailure?.status === "failed-as-required" &&
|
|
191
|
+
Number.isSafeInteger(execution.tamperFailure?.exitCode) &&
|
|
192
|
+
execution.tamperFailure.exitCode > 0 &&
|
|
193
|
+
execution.retryRun?.status === "passed" &&
|
|
194
|
+
execution.retryRun?.readbackRoot === execution.initialRun.readbackRoot &&
|
|
195
|
+
execution.terminalVerify?.status === "passed" &&
|
|
196
|
+
execution.terminalVerify?.readbackRoot ===
|
|
197
|
+
execution.retryRun.readbackRoot &&
|
|
198
|
+
execution.bootstrap?.status === "passed" &&
|
|
199
|
+
ROOT.test(execution.bootstrap?.resultRoot || "") &&
|
|
200
|
+
execution.neutralDriver?.id === V4_CROSS_PLATFORM_NEUTRAL_DRIVER &&
|
|
201
|
+
execution.neutralDriver?.status === "passed" &&
|
|
202
|
+
execution.neutralDriver?.kfdDependencyPresent === false;
|
|
203
|
+
if (!complete)
|
|
204
|
+
fail("failure, retry, terminal or neutral-driver evidence is incomplete");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function validateAuthorityCeiling(authority) {
|
|
208
|
+
exactKeys(
|
|
209
|
+
authority,
|
|
210
|
+
[
|
|
211
|
+
"productionWrites",
|
|
212
|
+
"providerEffects",
|
|
213
|
+
"releaseEffects",
|
|
214
|
+
"stablePublication",
|
|
215
|
+
],
|
|
216
|
+
"authority ceiling",
|
|
217
|
+
);
|
|
218
|
+
if (Object.values(authority).some((value) => value !== false))
|
|
219
|
+
fail("qualification report must not grant production or release authority");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function validateV4CrossPlatformAdopterReport(report) {
|
|
223
|
+
exactKeys(
|
|
224
|
+
report,
|
|
225
|
+
[
|
|
226
|
+
"schemaVersion",
|
|
227
|
+
"contract",
|
|
228
|
+
"platform",
|
|
229
|
+
"consumer",
|
|
230
|
+
"sourceBinding",
|
|
231
|
+
"capabilityMatrix",
|
|
232
|
+
"execution",
|
|
233
|
+
"authority",
|
|
234
|
+
"reportRoot",
|
|
235
|
+
],
|
|
236
|
+
"platform report",
|
|
237
|
+
);
|
|
238
|
+
if (
|
|
239
|
+
report.schemaVersion !== 1 ||
|
|
240
|
+
report.contract !== V4_CROSS_PLATFORM_ADOPTER_REPORT_CONTRACT
|
|
241
|
+
)
|
|
242
|
+
fail("platform report contract is unsupported");
|
|
243
|
+
if (!V4_CROSS_PLATFORM_ADOPTER_PLATFORMS.includes(report.platform))
|
|
244
|
+
fail(`undeclared platform ${report.platform}`);
|
|
245
|
+
if (!CONSUMER.test(report.consumer || ""))
|
|
246
|
+
fail("consumer must be a stable lowercase identity");
|
|
247
|
+
validateSourceBinding(report.sourceBinding);
|
|
248
|
+
validateCapabilityMatrix(report.capabilityMatrix);
|
|
249
|
+
validateExecutionEvidence(report.execution);
|
|
250
|
+
validateAuthorityCeiling(report.authority);
|
|
251
|
+
if (!ROOT.test(report.reportRoot || "")) fail("report root is invalid");
|
|
252
|
+
const body = structuredClone(report);
|
|
253
|
+
delete body.reportRoot;
|
|
254
|
+
if (adopterDeliveryGateDigest(body) !== report.reportRoot)
|
|
255
|
+
fail("report root does not match exact report bytes");
|
|
256
|
+
return structuredClone(report);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function createV4CrossPlatformAdopterReport(input) {
|
|
260
|
+
const body = structuredClone(input);
|
|
261
|
+
const report = {
|
|
262
|
+
schemaVersion: 1,
|
|
263
|
+
contract: V4_CROSS_PLATFORM_ADOPTER_REPORT_CONTRACT,
|
|
264
|
+
...body,
|
|
265
|
+
};
|
|
266
|
+
report.reportRoot = adopterDeliveryGateDigest(report);
|
|
267
|
+
return validateV4CrossPlatformAdopterReport(report);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function qualifyV4CrossPlatformAdopters({ reports, consumers }) {
|
|
271
|
+
if (!Array.isArray(reports) || reports.length === 0)
|
|
272
|
+
fail("at least one raw platform report is required");
|
|
273
|
+
if (
|
|
274
|
+
!Array.isArray(consumers) ||
|
|
275
|
+
consumers.length === 0 ||
|
|
276
|
+
consumers.some((consumer) => !CONSUMER.test(consumer)) ||
|
|
277
|
+
new Set(consumers).size !== consumers.length
|
|
278
|
+
)
|
|
279
|
+
fail("required consumers must be unique stable lowercase identities");
|
|
280
|
+
const normalized = reports.map(validateV4CrossPlatformAdopterReport);
|
|
281
|
+
const expectedKeys = consumers.flatMap((consumer) =>
|
|
282
|
+
V4_CROSS_PLATFORM_ADOPTER_PLATFORMS.map(
|
|
283
|
+
(platform) => `${consumer}:${platform}`,
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
const reportByKey = new Map();
|
|
287
|
+
for (const report of normalized) {
|
|
288
|
+
const key = `${report.consumer}:${report.platform}`;
|
|
289
|
+
if (reportByKey.has(key)) fail(`duplicate report ${key}`);
|
|
290
|
+
reportByKey.set(key, report);
|
|
291
|
+
}
|
|
292
|
+
const missing = expectedKeys.filter((key) => !reportByKey.has(key));
|
|
293
|
+
const unexpected = [...reportByKey.keys()].filter(
|
|
294
|
+
(key) => !expectedKeys.includes(key),
|
|
295
|
+
);
|
|
296
|
+
if (missing.length || unexpected.length)
|
|
297
|
+
fail(
|
|
298
|
+
`platform matrix mismatch; missing=${missing.join(",") || "none"}; unexpected=${unexpected.join(",") || "none"}`,
|
|
299
|
+
);
|
|
300
|
+
const reference = normalized[0].sourceBinding;
|
|
301
|
+
for (const report of normalized.slice(1)) {
|
|
302
|
+
if (
|
|
303
|
+
report.sourceBinding.runtimeSha !== reference.runtimeSha ||
|
|
304
|
+
report.sourceBinding.inventoryRoot !== reference.inventoryRoot ||
|
|
305
|
+
!same(report.sourceBinding.sourceCuts, reference.sourceCuts) ||
|
|
306
|
+
!same(report.capabilityMatrix, normalized[0].capabilityMatrix)
|
|
307
|
+
)
|
|
308
|
+
fail("all platform reports must bind the same exact source and matrix");
|
|
309
|
+
}
|
|
310
|
+
const orderedReports = expectedKeys.map((key) => reportByKey.get(key));
|
|
311
|
+
const body = {
|
|
312
|
+
schemaVersion: 1,
|
|
313
|
+
contract: V4_CROSS_PLATFORM_ADOPTER_QUALIFICATION_CONTRACT,
|
|
314
|
+
sourceBinding: {
|
|
315
|
+
runtimeSha: reference.runtimeSha,
|
|
316
|
+
inventoryRoot: reference.inventoryRoot,
|
|
317
|
+
sourceCuts: structuredClone(reference.sourceCuts),
|
|
318
|
+
},
|
|
319
|
+
consumers: [...consumers],
|
|
320
|
+
platforms: [...V4_CROSS_PLATFORM_ADOPTER_PLATFORMS],
|
|
321
|
+
capabilityMatrix: structuredClone(normalized[0].capabilityMatrix),
|
|
322
|
+
reports: orderedReports.map(({ consumer, platform, reportRoot }) => ({
|
|
323
|
+
consumer,
|
|
324
|
+
platform,
|
|
325
|
+
reportRoot,
|
|
326
|
+
})),
|
|
327
|
+
neutralDriver: {
|
|
328
|
+
id: V4_CROSS_PLATFORM_NEUTRAL_DRIVER,
|
|
329
|
+
platformCoverage: [...V4_CROSS_PLATFORM_ADOPTER_PLATFORMS],
|
|
330
|
+
status: "passed",
|
|
331
|
+
},
|
|
332
|
+
authority: {
|
|
333
|
+
productionWrites: false,
|
|
334
|
+
providerEffects: false,
|
|
335
|
+
releaseEffects: false,
|
|
336
|
+
stablePublication: false,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
...body,
|
|
341
|
+
qualificationRoot: adopterDeliveryGateDigest(body),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -100,7 +100,9 @@ export function validateV4DeliveryWarrantReadQualification(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export function projectV3QueueToV4ReadState(queueInput) {
|
|
103
|
-
const queue = normalizeDevDeliveryQueue(queueInput
|
|
103
|
+
const queue = normalizeDevDeliveryQueue(queueInput, {
|
|
104
|
+
allowLegacyV3Readback: true,
|
|
105
|
+
});
|
|
104
106
|
return {
|
|
105
107
|
schema: STATE_CONTRACT,
|
|
106
108
|
generation: queue.generation,
|
|
@@ -219,7 +221,9 @@ export async function runV4DeliveryWarrantReadCandidate(
|
|
|
219
221
|
});
|
|
220
222
|
if (typeof retain !== "function")
|
|
221
223
|
fail("retention-required", "v4 read evidence retention is required");
|
|
222
|
-
const queue = normalizeDevDeliveryQueue(queueInput
|
|
224
|
+
const queue = normalizeDevDeliveryQueue(queueInput, {
|
|
225
|
+
allowLegacyV3Readback: true,
|
|
226
|
+
});
|
|
223
227
|
const state = projectV3QueueToV4ReadState(queue);
|
|
224
228
|
const request = createV4DeliveryWarrantReadRequest(state, {
|
|
225
229
|
requestId,
|
|
@@ -235,7 +239,10 @@ export async function runV4DeliveryWarrantReadCandidate(
|
|
|
235
239
|
);
|
|
236
240
|
}
|
|
237
241
|
const projection = validateProjection(response, request, state);
|
|
238
|
-
const observation = observeDevDeliveryQueue(queue, {
|
|
242
|
+
const observation = observeDevDeliveryQueue(queue, {
|
|
243
|
+
now: observedAt,
|
|
244
|
+
allowLegacyV3Readback: true,
|
|
245
|
+
});
|
|
239
246
|
const evidence = {
|
|
240
247
|
schema: V4_DELIVERY_WARRANT_READ_EVIDENCE_CONTRACT,
|
|
241
248
|
authority: "typescript-v3",
|
|
@@ -8,6 +8,12 @@ export const V4_FLOATING_CONSUMER_CERTIFICATION =
|
|
|
8
8
|
"kungfu-buildchain-v4-floating-consumer-certification/v1";
|
|
9
9
|
|
|
10
10
|
const SHA256_ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
11
|
+
const ALPHA_RECOVERY_BOOTSTRAP = Object.freeze({
|
|
12
|
+
repository: "kungfu-systems/buildchain",
|
|
13
|
+
sourcePath: ".github/workflows/buildchain-ref-promotion-recovery.yml",
|
|
14
|
+
workflow: ".github/workflows/.release-candidate-promote.yml",
|
|
15
|
+
selector: "alpha/v4/v4.0",
|
|
16
|
+
});
|
|
11
17
|
|
|
12
18
|
function stableJson(value) {
|
|
13
19
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -109,15 +115,23 @@ function verifyAuthorityFields(value, expected, check, label) {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
function verifyFloatingSelector(value, check, label) {
|
|
118
|
+
const bootstrap =
|
|
119
|
+
value?.caller?.repository === ALPHA_RECOVERY_BOOTSTRAP.repository &&
|
|
120
|
+
value?.invocation?.sourcePath === ALPHA_RECOVERY_BOOTSTRAP.sourcePath &&
|
|
121
|
+
value?.invocation?.workflow === ALPHA_RECOVERY_BOOTSTRAP.workflow &&
|
|
122
|
+
value?.invocation?.visibleSelector === ALPHA_RECOVERY_BOOTSTRAP.selector &&
|
|
123
|
+
value?.invocation?.selectorClass === "protected-bootstrap" &&
|
|
124
|
+
value?.invocation?.channel === "alpha";
|
|
112
125
|
check(
|
|
113
|
-
value?.invocation?.selectorClass === "floating",
|
|
126
|
+
value?.invocation?.selectorClass === "floating" || bootstrap,
|
|
114
127
|
"selector-class-invalid",
|
|
115
|
-
`${label} must bind a floating selector`,
|
|
128
|
+
`${label} must bind a floating selector or the bounded protected alpha recovery bootstrap`,
|
|
116
129
|
);
|
|
117
130
|
check(
|
|
118
|
-
["v4", "v4-alpha"].includes(value?.invocation?.visibleSelector)
|
|
131
|
+
["v4", "v4-alpha"].includes(value?.invocation?.visibleSelector) ||
|
|
132
|
+
bootstrap,
|
|
119
133
|
"selector-invalid",
|
|
120
|
-
`${label} selector must be v4
|
|
134
|
+
`${label} selector must be v4, v4-alpha, or the bounded protected alpha recovery bootstrap`,
|
|
121
135
|
);
|
|
122
136
|
}
|
|
123
137
|
|
|
@@ -26,6 +26,11 @@ const EXACT_SHA = /^[0-9a-f]{40}$/u;
|
|
|
26
26
|
const SHA256_ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
27
27
|
const BUILDCHAIN_REPOSITORY = "kungfu-systems/buildchain";
|
|
28
28
|
const CHANNELS = Object.freeze({ v4: "stable", "v4-alpha": "alpha" });
|
|
29
|
+
const ALPHA_RECOVERY_BOOTSTRAP = Object.freeze({
|
|
30
|
+
selector: "alpha/v4/v4.0",
|
|
31
|
+
sourcePath: ".github/workflows/buildchain-ref-promotion-recovery.yml",
|
|
32
|
+
workflow: ".github/workflows/.release-candidate-promote.yml",
|
|
33
|
+
});
|
|
29
34
|
const DEFAULT_STABLE_LOCK_PATH = ".buildchain/contract-lock.json";
|
|
30
35
|
const DEFAULT_ALPHA_LOCK_PATH = ".buildchain/alpha-contract-lock.json";
|
|
31
36
|
const TRANSIENT_ACTION =
|
|
@@ -277,7 +282,7 @@ function readContractLock(root, relative, expectedRef, label, failures) {
|
|
|
277
282
|
}
|
|
278
283
|
}
|
|
279
284
|
|
|
280
|
-
function classifyBuildchainUses(records, failures) {
|
|
285
|
+
function classifyBuildchainUses(records, failures, { repository } = {}) {
|
|
281
286
|
const uses = [];
|
|
282
287
|
for (const record of records) {
|
|
283
288
|
const parsed = coordinate(record.uses);
|
|
@@ -286,15 +291,28 @@ function classifyBuildchainUses(records, failures) {
|
|
|
286
291
|
parsed.selector === "v4" ||
|
|
287
292
|
parsed.selector === "v4-alpha" ||
|
|
288
293
|
/^v4(?:[./-]|$)/u.test(parsed.selector) ||
|
|
294
|
+
parsed.selector === ALPHA_RECOVERY_BOOTSTRAP.selector ||
|
|
289
295
|
EXACT_SHA.test(parsed.selector.toLowerCase()) ||
|
|
290
296
|
parsed.selector.includes("${{");
|
|
291
297
|
if (!v4Candidate) continue;
|
|
292
|
-
const
|
|
298
|
+
const bootstrapShape =
|
|
299
|
+
normalizeWorkflowPath(parsed.path) ===
|
|
300
|
+
ALPHA_RECOVERY_BOOTSTRAP.workflow &&
|
|
301
|
+
record.sourcePath === ALPHA_RECOVERY_BOOTSTRAP.sourcePath &&
|
|
302
|
+
parsed.selector === ALPHA_RECOVERY_BOOTSTRAP.selector;
|
|
303
|
+
const protectedBootstrap =
|
|
304
|
+
repository === BUILDCHAIN_REPOSITORY && bootstrapShape;
|
|
305
|
+
const channel =
|
|
306
|
+
CHANNELS[parsed.selector] || (protectedBootstrap ? "alpha" : "");
|
|
293
307
|
uses.push({
|
|
294
308
|
...record,
|
|
295
309
|
...parsed,
|
|
296
310
|
channel,
|
|
297
|
-
selectorClass:
|
|
311
|
+
selectorClass: protectedBootstrap
|
|
312
|
+
? "protected-bootstrap"
|
|
313
|
+
: channel
|
|
314
|
+
? "floating"
|
|
315
|
+
: "rejected",
|
|
298
316
|
});
|
|
299
317
|
if (!channel) failures.push(selectorFailure(record, parsed.selector));
|
|
300
318
|
}
|
|
@@ -320,8 +338,97 @@ function selectInvocation(uses, workflow, failures, source = "", channel = "") {
|
|
|
320
338
|
return { expectedWorkflow, selected: invocations[0] };
|
|
321
339
|
}
|
|
322
340
|
|
|
341
|
+
function scanInvocationSource({
|
|
342
|
+
sourceRoot,
|
|
343
|
+
invocationRoot,
|
|
344
|
+
sourceScan,
|
|
345
|
+
sourceUses,
|
|
346
|
+
failures,
|
|
347
|
+
repository,
|
|
348
|
+
}) {
|
|
349
|
+
if (invocationRoot === sourceRoot)
|
|
350
|
+
return { invocationScan: sourceScan, invocationUses: sourceUses };
|
|
351
|
+
try {
|
|
352
|
+
const invocationScan = enumerateUses(invocationRoot);
|
|
353
|
+
return {
|
|
354
|
+
invocationScan,
|
|
355
|
+
invocationUses: classifyBuildchainUses(invocationScan.records, failures, {
|
|
356
|
+
repository,
|
|
357
|
+
}),
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
failures.push({
|
|
361
|
+
code: "invocation-source-scan-failed",
|
|
362
|
+
message: error.message,
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
invocationScan: { files: [], records: [] },
|
|
366
|
+
invocationUses: [],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function sourceScanRoot({
|
|
372
|
+
sourceRoot,
|
|
373
|
+
invocationRoot,
|
|
374
|
+
sourceScan,
|
|
375
|
+
invocationScan,
|
|
376
|
+
invocationUses,
|
|
377
|
+
}) {
|
|
378
|
+
const material =
|
|
379
|
+
invocationRoot === sourceRoot
|
|
380
|
+
? sourceScan
|
|
381
|
+
: {
|
|
382
|
+
source: sourceScan,
|
|
383
|
+
invocation: {
|
|
384
|
+
files: invocationScan.files,
|
|
385
|
+
invocations: invocationUses,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
return sha256(stableJson(material));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function verifySelectedLock({
|
|
392
|
+
selected,
|
|
393
|
+
stable,
|
|
394
|
+
alpha,
|
|
395
|
+
workflowSha,
|
|
396
|
+
failures,
|
|
397
|
+
}) {
|
|
398
|
+
const selectedLock = selected?.channel === "alpha" ? alpha : stable;
|
|
399
|
+
if (!selected?.channel || !selectedLock) return;
|
|
400
|
+
const authorityRef =
|
|
401
|
+
selected.selectorClass === "protected-bootstrap"
|
|
402
|
+
? "v4-alpha"
|
|
403
|
+
: selected.selector;
|
|
404
|
+
try {
|
|
405
|
+
const compatibility = evaluateBuildchainContractLock({
|
|
406
|
+
lock: selectedLock,
|
|
407
|
+
current: createBuildchainContractWorld({ root: defaultRuntimeRoot() }),
|
|
408
|
+
runtimeRef: authorityRef,
|
|
409
|
+
runtimeSha: workflowSha,
|
|
410
|
+
runtimeClass: selected.channel,
|
|
411
|
+
workflowShellRef: authorityRef,
|
|
412
|
+
expectedChannel: selected.channel,
|
|
413
|
+
expectedMajor: "v4",
|
|
414
|
+
});
|
|
415
|
+
if (!compatibility.ok) {
|
|
416
|
+
failures.push({
|
|
417
|
+
code: "selected-lock-runtime-incompatible",
|
|
418
|
+
message: `${selected.channel} contract lock rejects resolved workflow shell ${workflowSha}: ${compatibility.reasons?.join("; ") || compatibility.status}`,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
failures.push({
|
|
423
|
+
code: "selected-lock-contract-world-invalid",
|
|
424
|
+
message: `cannot evaluate ${selected.channel} contract lock against the resolved workflow shell: ${error.message}`,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
323
429
|
export function scanV4FloatingConsumerPolicy({
|
|
324
430
|
root = process.cwd(),
|
|
431
|
+
invocationRoot = root,
|
|
325
432
|
repository = "",
|
|
326
433
|
sourceSha = "",
|
|
327
434
|
invokedWorkflow = "",
|
|
@@ -335,6 +442,7 @@ export function scanV4FloatingConsumerPolicy({
|
|
|
335
442
|
scannerRoot = "",
|
|
336
443
|
} = {}) {
|
|
337
444
|
const resolvedRoot = path.resolve(root);
|
|
445
|
+
const resolvedInvocationRoot = path.resolve(invocationRoot);
|
|
338
446
|
if (!policy || policy.contract !== V4_FLOATING_CONSUMER_POLICY) {
|
|
339
447
|
throw new Error(`policy must use ${V4_FLOATING_CONSUMER_POLICY}`);
|
|
340
448
|
}
|
|
@@ -374,43 +482,34 @@ export function scanV4FloatingConsumerPolicy({
|
|
|
374
482
|
} catch (error) {
|
|
375
483
|
failures.push({ code: "source-scan-failed", message: error.message });
|
|
376
484
|
}
|
|
377
|
-
const buildchainUses = classifyBuildchainUses(scanned.records, failures
|
|
485
|
+
const buildchainUses = classifyBuildchainUses(scanned.records, failures, {
|
|
486
|
+
repository,
|
|
487
|
+
});
|
|
488
|
+
const { invocationScan, invocationUses } = scanInvocationSource({
|
|
489
|
+
sourceRoot: resolvedRoot,
|
|
490
|
+
invocationRoot: resolvedInvocationRoot,
|
|
491
|
+
sourceScan: scanned,
|
|
492
|
+
sourceUses: buildchainUses,
|
|
493
|
+
failures,
|
|
494
|
+
repository,
|
|
495
|
+
});
|
|
378
496
|
const sourcePath = String(invocationSourcePath || "")
|
|
379
497
|
.split("@")[0]
|
|
380
498
|
.replace(`${repository}/`, "");
|
|
381
499
|
const { expectedWorkflow, selected } = selectInvocation(
|
|
382
|
-
|
|
500
|
+
invocationUses,
|
|
383
501
|
invokedWorkflow,
|
|
384
502
|
failures,
|
|
385
503
|
sourcePath,
|
|
386
504
|
expectedInvocationChannel,
|
|
387
505
|
);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
runtimeSha: normalizedWorkflowSha,
|
|
396
|
-
runtimeClass: selected.channel,
|
|
397
|
-
workflowShellRef: selected.selector,
|
|
398
|
-
expectedChannel: selected.channel,
|
|
399
|
-
expectedMajor: "v4",
|
|
400
|
-
});
|
|
401
|
-
if (!compatibility.ok) {
|
|
402
|
-
failures.push({
|
|
403
|
-
code: "selected-lock-runtime-incompatible",
|
|
404
|
-
message: `${selected.channel} contract lock rejects resolved workflow shell ${normalizedWorkflowSha}: ${compatibility.reasons?.join("; ") || compatibility.status}`,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
} catch (error) {
|
|
408
|
-
failures.push({
|
|
409
|
-
code: "selected-lock-contract-world-invalid",
|
|
410
|
-
message: `cannot evaluate ${selected.channel} contract lock against the resolved workflow shell: ${error.message}`,
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
}
|
|
506
|
+
verifySelectedLock({
|
|
507
|
+
selected,
|
|
508
|
+
stable,
|
|
509
|
+
alpha,
|
|
510
|
+
workflowSha: normalizedWorkflowSha,
|
|
511
|
+
failures,
|
|
512
|
+
});
|
|
414
513
|
|
|
415
514
|
const policyRoot = sha256(stableJson(policy));
|
|
416
515
|
const resolvedScannerRoot = scannerRoot || policyRoot;
|
|
@@ -424,9 +523,14 @@ export function scanV4FloatingConsumerPolicy({
|
|
|
424
523
|
stable: stable ? sha256(stableJson(stable)) : "",
|
|
425
524
|
alpha: alpha ? sha256(stableJson(alpha)) : "",
|
|
426
525
|
};
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
526
|
+
const sourceScan = { files: scanned.files, invocations: buildchainUses };
|
|
527
|
+
const scanRoot = sourceScanRoot({
|
|
528
|
+
sourceRoot: resolvedRoot,
|
|
529
|
+
invocationRoot: resolvedInvocationRoot,
|
|
530
|
+
sourceScan,
|
|
531
|
+
invocationScan,
|
|
532
|
+
invocationUses,
|
|
533
|
+
});
|
|
430
534
|
const receipt = {
|
|
431
535
|
schema: V4_FLOATING_CONSUMER_RECEIPT,
|
|
432
536
|
status: failures.length ? "failed" : "passed",
|
|
@@ -459,7 +563,7 @@ export function scanV4FloatingConsumerPolicy({
|
|
|
459
563
|
ok: failures.length === 0,
|
|
460
564
|
failures,
|
|
461
565
|
scannedFiles: scanned.files,
|
|
462
|
-
invocations:
|
|
566
|
+
invocations: invocationUses,
|
|
463
567
|
receipt,
|
|
464
568
|
receiptRoot,
|
|
465
569
|
};
|