@kungfu-tech/buildchain 4.0.2-alpha.0 → 4.0.2-alpha.4
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/architecture/agent-change-map.md +7 -4
- package/architecture/ci-lane-change-budget.json +76 -8
- package/architecture/internal-capabilities.json +2 -0
- package/architecture/maintainability-debt.json +52 -124
- package/architecture/maintainability-policy.json +15 -10
- package/architecture/release-tail-contract-inventory.json +4 -4
- package/architecture/v3-core-mechanism-inventory.json +1 -0
- package/architecture/v3-v4-live-capability-inventory.json +17 -17
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +5 -5
- package/architecture/v4-release-invocation-fixtures.json +139 -0
- package/architecture/v4-release-topology.json +243 -0
- package/architecture/v4-runtime-semantic-closure.json +4 -1
- package/contracts/buildchain-v2-residuals-v1.json +0 -9
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-release-invocation-v1.schema.json +134 -0
- package/dist/site/buildchain-contract.json +9 -9
- package/dist/site/buildchain-site.json +7 -7
- package/dist/site/kfd-claims.json +24 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +90 -15
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +34 -9
- package/dist/site/publication-authority-registry.json +2 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +36 -11
- package/docs/node-api-reference.md +16 -13
- package/package.json +2 -2
- package/packages/core/dev-delivery-candidate-identity.js +45 -17
- package/packages/core/dev-delivery-execution-transfer.js +6 -7
- package/packages/core/dev-delivery-provider-heartbeat.js +22 -6
- package/packages/core/dev-delivery-warrant-legacy-recovery.js +4 -2
- package/packages/core/dev-delivery-warrant-native-compatibility.js +33 -0
- package/packages/core/dev-delivery-warrant-state.js +54 -54
- package/packages/core/dev-delivery-warrant.js +8 -0
- package/packages/core/dev-delivery-writer-protocol-transition.js +72 -0
- package/packages/core/v4-canonical-contracts.js +10 -0
- package/packages/core/v4-floating-consumer-policy.js +4 -1
- package/packages/core/v4-protected-publication-source.js +93 -0
- package/packages/core/v4-publication-qualification.js +1 -0
- package/packages/core/v4-release-invocation.js +425 -0
- package/scripts/audit-publication-control-plane.mjs +0 -1
- package/scripts/check-inventory.mjs +27 -59
- package/scripts/check-maintainability.mjs +13 -5
- package/scripts/check-v3-v4-capability-inventory.mjs +3 -61
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -20
- package/scripts/check-v4-release-topology.mjs +383 -0
- package/scripts/dev-delivery-warrant.mjs +26 -5
- package/scripts/generate-channel-promotion-workflow.mjs +14 -55
- package/scripts/release-candidate-resolver.mjs +17 -17
- package/scripts/resume-from-candidate-run.mjs +15 -16
- package/scripts/v3-v4-capability-catalog.mjs +107 -0
- package/scripts/v4-declarative-promotion-admission.mjs +4 -1
- package/scripts/v4-release-candidate-adapter.mjs +41 -0
- package/scripts/capture-package-release-propagation.mjs +0 -263
- package/scripts/publication-commit-evidence.mjs +0 -444
- package/scripts/publish-github-artifact-attestation-evidence.mjs +0 -201
- package/scripts/stage-github-artifact-attestation-inputs.mjs +0 -65
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import {
|
|
2
|
+
V4ContractFault,
|
|
3
|
+
v4CanonicalBytes,
|
|
4
|
+
v4ContentRoot,
|
|
5
|
+
validateV4Root,
|
|
6
|
+
} from "./v4-canonical-contracts.js";
|
|
7
|
+
|
|
8
|
+
export const V4_RELEASE_INVOCATION_CONTRACT =
|
|
9
|
+
"kungfu-buildchain-v4-release-invocation/v1";
|
|
10
|
+
export const V4_RELEASE_INVOCATION_ADAPTER_CONTRACT =
|
|
11
|
+
"kungfu-buildchain-v4-release-invocation-adapter/v1";
|
|
12
|
+
export const V4_RELEASE_TRANSACTION_CONTRACT =
|
|
13
|
+
"kungfu-buildchain-v4-release-transaction/v1";
|
|
14
|
+
export const V4_RELEASE_RECEIPT_CONTRACT =
|
|
15
|
+
"kungfu-buildchain-v4-release-receipt/v1";
|
|
16
|
+
export const V4_RELEASE_PROVIDER_CONTRACT =
|
|
17
|
+
"kungfu-buildchain-release-tail-provider/v1";
|
|
18
|
+
|
|
19
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/u;
|
|
20
|
+
const TAG_PATTERN = /^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
21
|
+
const ROUTE_SURFACES = new Set([
|
|
22
|
+
"alpha",
|
|
23
|
+
"stable",
|
|
24
|
+
"public",
|
|
25
|
+
"private",
|
|
26
|
+
"declarative",
|
|
27
|
+
"legacy-compatible",
|
|
28
|
+
]);
|
|
29
|
+
const EXECUTION_MODES = new Set(["fresh", "resume"]);
|
|
30
|
+
const COMPARISON_STATES = new Set(["identical", "ahead", "behind", "diverged"]);
|
|
31
|
+
|
|
32
|
+
function fault(code, path, message) {
|
|
33
|
+
throw new V4ContractFault(code, path, message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function exactKeys(value, keys, path) {
|
|
37
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
38
|
+
fault("invalid-release-invocation", path, `${path} must be an object`);
|
|
39
|
+
const actual = Object.keys(value).sort();
|
|
40
|
+
const expected = [...keys].sort();
|
|
41
|
+
if (
|
|
42
|
+
actual.length !== expected.length ||
|
|
43
|
+
actual.some((key, index) => key !== expected[index])
|
|
44
|
+
)
|
|
45
|
+
fault(
|
|
46
|
+
"invalid-release-invocation-shape",
|
|
47
|
+
path,
|
|
48
|
+
`${path} keys are not canonical`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sha(value, path, nullable = false) {
|
|
53
|
+
if (nullable && value === null) return value;
|
|
54
|
+
if (typeof value !== "string" || !SHA_PATTERN.test(value))
|
|
55
|
+
fault("invalid-release-sha", path, `${path} must be an exact Git SHA`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function text(value, path) {
|
|
60
|
+
if (
|
|
61
|
+
typeof value !== "string" ||
|
|
62
|
+
value.length === 0 ||
|
|
63
|
+
/[^\x20-\x7e]/u.test(value)
|
|
64
|
+
)
|
|
65
|
+
fault("invalid-release-text", path, `${path} must be printable ASCII`);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validatePublisher(value) {
|
|
70
|
+
exactKeys(
|
|
71
|
+
value,
|
|
72
|
+
["repository", "workflow", "workflowSha", "job"],
|
|
73
|
+
"$/publisher",
|
|
74
|
+
);
|
|
75
|
+
if (
|
|
76
|
+
value.repository !== "kungfu-systems/buildchain" ||
|
|
77
|
+
value.workflow !== ".github/workflows/.release-candidate-promote.yml" ||
|
|
78
|
+
value.job !== "apply"
|
|
79
|
+
)
|
|
80
|
+
fault(
|
|
81
|
+
"invalid-publisher-identity",
|
|
82
|
+
"$/publisher",
|
|
83
|
+
"publisher identity is not the canonical v4 APPLY job",
|
|
84
|
+
);
|
|
85
|
+
sha(value.workflowSha, "$/publisher/workflowSha");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function validateRuntime(value) {
|
|
89
|
+
exactKeys(value, ["repository", "commit", "tree"], "$/runtime");
|
|
90
|
+
if (value.repository !== "kungfu-systems/buildchain")
|
|
91
|
+
fault(
|
|
92
|
+
"invalid-runtime-identity",
|
|
93
|
+
"$/runtime/repository",
|
|
94
|
+
"runtime repository is not canonical",
|
|
95
|
+
);
|
|
96
|
+
sha(value.commit, "$/runtime/commit");
|
|
97
|
+
sha(value.tree, "$/runtime/tree");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateCandidate(value) {
|
|
101
|
+
exactKeys(value, ["repository", "commit", "tree", "version"], "$/candidate");
|
|
102
|
+
text(value.repository, "$/candidate/repository");
|
|
103
|
+
sha(value.commit, "$/candidate/commit");
|
|
104
|
+
sha(value.tree, "$/candidate/tree");
|
|
105
|
+
text(value.version, "$/candidate/version");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateTarget(value) {
|
|
109
|
+
exactKeys(value, ["channel", "tag", "expectedOldSha"], "$/target");
|
|
110
|
+
if (
|
|
111
|
+
!["alpha", "stable"].includes(value.channel) ||
|
|
112
|
+
!TAG_PATTERN.test(value.tag)
|
|
113
|
+
)
|
|
114
|
+
fault(
|
|
115
|
+
"invalid-release-target",
|
|
116
|
+
"$/target",
|
|
117
|
+
"release channel and exact tag are not canonical",
|
|
118
|
+
);
|
|
119
|
+
sha(value.expectedOldSha, "$/target/expectedOldSha", true);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function validateAuthority(value) {
|
|
123
|
+
exactKeys(
|
|
124
|
+
value,
|
|
125
|
+
["policyRoot", "qualificationRoot", "warrantRoot"],
|
|
126
|
+
"$/authority",
|
|
127
|
+
);
|
|
128
|
+
for (const name of ["policyRoot", "qualificationRoot", "warrantRoot"])
|
|
129
|
+
validateV4Root(value[name], `$/authority/${name}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function validateProvider(value, candidate) {
|
|
133
|
+
exactKeys(value, ["adapter", "contract", "repository"], "$/provider");
|
|
134
|
+
if (
|
|
135
|
+
value.adapter !== "built-in-provider-plane" ||
|
|
136
|
+
value.contract !== V4_RELEASE_PROVIDER_CONTRACT ||
|
|
137
|
+
value.repository !== candidate.repository
|
|
138
|
+
)
|
|
139
|
+
fault(
|
|
140
|
+
"invalid-release-provider",
|
|
141
|
+
"$/provider",
|
|
142
|
+
"provider identity must bind the built-in Provider Plane and candidate repository",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function validateParent(value) {
|
|
147
|
+
exactKeys(
|
|
148
|
+
value,
|
|
149
|
+
["invocationRoot", "transactionRoot", "receiptRoot"],
|
|
150
|
+
"$/parent",
|
|
151
|
+
);
|
|
152
|
+
const roots = [
|
|
153
|
+
value.invocationRoot,
|
|
154
|
+
value.transactionRoot,
|
|
155
|
+
value.receiptRoot,
|
|
156
|
+
];
|
|
157
|
+
if (roots.every((root) => root === null)) return;
|
|
158
|
+
if (roots.some((root) => root === null))
|
|
159
|
+
fault(
|
|
160
|
+
"invalid-release-parent-lineage",
|
|
161
|
+
"$/parent",
|
|
162
|
+
"parent lineage roots must be either all null or all present",
|
|
163
|
+
);
|
|
164
|
+
roots.forEach((root, index) =>
|
|
165
|
+
validateV4Root(
|
|
166
|
+
root,
|
|
167
|
+
`$/parent/${["invocationRoot", "transactionRoot", "receiptRoot"][index]}`,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function createV4ReleaseInvocation(value) {
|
|
173
|
+
exactKeys(
|
|
174
|
+
value,
|
|
175
|
+
[
|
|
176
|
+
"schema",
|
|
177
|
+
"publisher",
|
|
178
|
+
"runtime",
|
|
179
|
+
"candidate",
|
|
180
|
+
"target",
|
|
181
|
+
"authority",
|
|
182
|
+
"provider",
|
|
183
|
+
"parent",
|
|
184
|
+
],
|
|
185
|
+
"$",
|
|
186
|
+
);
|
|
187
|
+
if (value.schema !== V4_RELEASE_INVOCATION_CONTRACT)
|
|
188
|
+
fault(
|
|
189
|
+
"invalid-release-invocation",
|
|
190
|
+
"$/schema",
|
|
191
|
+
"unsupported invocation schema",
|
|
192
|
+
);
|
|
193
|
+
validatePublisher(value.publisher);
|
|
194
|
+
validateRuntime(value.runtime);
|
|
195
|
+
validateCandidate(value.candidate);
|
|
196
|
+
validateTarget(value.target);
|
|
197
|
+
validateAuthority(value.authority);
|
|
198
|
+
validateProvider(value.provider, value.candidate);
|
|
199
|
+
validateParent(value.parent);
|
|
200
|
+
v4CanonicalBytes(value);
|
|
201
|
+
const roots = {
|
|
202
|
+
publisherRoot: v4ContentRoot(
|
|
203
|
+
"release-invocation-publisher",
|
|
204
|
+
value.publisher,
|
|
205
|
+
),
|
|
206
|
+
runtimeRoot: v4ContentRoot("release-invocation-runtime", value.runtime),
|
|
207
|
+
candidateRoot: v4ContentRoot(
|
|
208
|
+
"release-invocation-candidate",
|
|
209
|
+
value.candidate,
|
|
210
|
+
),
|
|
211
|
+
targetRoot: v4ContentRoot("release-invocation-target", value.target),
|
|
212
|
+
authorityRoot: v4ContentRoot(
|
|
213
|
+
"release-invocation-authority",
|
|
214
|
+
value.authority,
|
|
215
|
+
),
|
|
216
|
+
providerRoot: v4ContentRoot("release-invocation-provider", value.provider),
|
|
217
|
+
parentRoot: v4ContentRoot("release-invocation-parent", value.parent),
|
|
218
|
+
};
|
|
219
|
+
const invocationRoot = v4ContentRoot("release-invocation", {
|
|
220
|
+
schema: V4_RELEASE_INVOCATION_CONTRACT,
|
|
221
|
+
...roots,
|
|
222
|
+
});
|
|
223
|
+
return { invocation: value, roots: { ...roots, invocationRoot } };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function adaptV4ReleaseInvocation(value) {
|
|
227
|
+
exactKeys(value, ["schema", "route", "invocation"], "$adapter");
|
|
228
|
+
if (value.schema !== V4_RELEASE_INVOCATION_ADAPTER_CONTRACT)
|
|
229
|
+
fault(
|
|
230
|
+
"invalid-release-adapter",
|
|
231
|
+
"$adapter/schema",
|
|
232
|
+
"unsupported adapter schema",
|
|
233
|
+
);
|
|
234
|
+
exactKeys(value.route, ["surface", "execution"], "$adapter/route");
|
|
235
|
+
if (!ROUTE_SURFACES.has(value.route.surface))
|
|
236
|
+
fault(
|
|
237
|
+
"invalid-release-adapter",
|
|
238
|
+
"$adapter/route/surface",
|
|
239
|
+
"unsupported route surface",
|
|
240
|
+
);
|
|
241
|
+
if (!EXECUTION_MODES.has(value.route.execution))
|
|
242
|
+
fault(
|
|
243
|
+
"invalid-release-adapter",
|
|
244
|
+
"$adapter/route/execution",
|
|
245
|
+
"unsupported execution mode",
|
|
246
|
+
);
|
|
247
|
+
return createV4ReleaseInvocation(value.invocation);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function planV4ReleaseRoute({
|
|
251
|
+
requestedSha,
|
|
252
|
+
observedSha,
|
|
253
|
+
comparisonStatus,
|
|
254
|
+
requestedChannel = "",
|
|
255
|
+
targetRef,
|
|
256
|
+
dryRun = false,
|
|
257
|
+
resume = false,
|
|
258
|
+
}) {
|
|
259
|
+
sha(requestedSha, "$route/requestedSha");
|
|
260
|
+
sha(observedSha, "$route/observedSha");
|
|
261
|
+
if (!COMPARISON_STATES.has(comparisonStatus))
|
|
262
|
+
fault(
|
|
263
|
+
"invalid-release-route",
|
|
264
|
+
"$route/comparisonStatus",
|
|
265
|
+
"unsupported source comparison state",
|
|
266
|
+
);
|
|
267
|
+
text(targetRef, "$route/targetRef");
|
|
268
|
+
const alphaLane = /^alpha\/v[0-9]+\/v[0-9]+\.[0-9]+$/u.test(targetRef);
|
|
269
|
+
const stableLane =
|
|
270
|
+
/^release\/v[0-9]+\/v[0-9]+\.[0-9]+$/u.test(targetRef) ||
|
|
271
|
+
["publish-gate/major", "major-gate"].includes(targetRef);
|
|
272
|
+
if (!alphaLane && !stableLane)
|
|
273
|
+
fault(
|
|
274
|
+
"invalid-release-route",
|
|
275
|
+
"$route/targetRef",
|
|
276
|
+
"source lane is not a supported v4 release lane",
|
|
277
|
+
);
|
|
278
|
+
const derivedChannel = alphaLane ? "alpha" : "stable";
|
|
279
|
+
const normalizedRequested =
|
|
280
|
+
requestedChannel === "alpha"
|
|
281
|
+
? "alpha"
|
|
282
|
+
: ["release", "stable", "major"].includes(requestedChannel)
|
|
283
|
+
? "stable"
|
|
284
|
+
: requestedChannel === ""
|
|
285
|
+
? derivedChannel
|
|
286
|
+
: "";
|
|
287
|
+
if (!normalizedRequested || normalizedRequested !== derivedChannel)
|
|
288
|
+
fault(
|
|
289
|
+
"invalid-release-route",
|
|
290
|
+
"$route/channel",
|
|
291
|
+
"requested channel does not match the source lane",
|
|
292
|
+
);
|
|
293
|
+
let decision = resume ? "Resume" : "Fresh";
|
|
294
|
+
let reason = resume ? "resume" : "fresh";
|
|
295
|
+
if (comparisonStatus === "ahead" && !resume && !dryRun) {
|
|
296
|
+
decision = "NoOp";
|
|
297
|
+
reason = "source-advanced";
|
|
298
|
+
} else if (
|
|
299
|
+
requestedSha !== observedSha &&
|
|
300
|
+
!(resume && comparisonStatus === "ahead") &&
|
|
301
|
+
!(dryRun && comparisonStatus === "ahead")
|
|
302
|
+
) {
|
|
303
|
+
decision = "Blocked";
|
|
304
|
+
reason = `source-${comparisonStatus}`;
|
|
305
|
+
}
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
decision,
|
|
308
|
+
reason,
|
|
309
|
+
channel: normalizedRequested,
|
|
310
|
+
targetRef,
|
|
311
|
+
requestedSha,
|
|
312
|
+
observedSha,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function createV4ReleaseTransaction(value) {
|
|
317
|
+
exactKeys(
|
|
318
|
+
value,
|
|
319
|
+
[
|
|
320
|
+
"invocationRoot",
|
|
321
|
+
"publisherRoot",
|
|
322
|
+
"runtimeRoot",
|
|
323
|
+
"providerRoot",
|
|
324
|
+
"parentRoot",
|
|
325
|
+
],
|
|
326
|
+
"$transaction",
|
|
327
|
+
);
|
|
328
|
+
for (const name of [
|
|
329
|
+
"invocationRoot",
|
|
330
|
+
"publisherRoot",
|
|
331
|
+
"runtimeRoot",
|
|
332
|
+
"providerRoot",
|
|
333
|
+
"parentRoot",
|
|
334
|
+
])
|
|
335
|
+
validateV4Root(value[name], `$transaction/${name}`);
|
|
336
|
+
const transaction = {
|
|
337
|
+
schema: V4_RELEASE_TRANSACTION_CONTRACT,
|
|
338
|
+
invocationRoot: value.invocationRoot,
|
|
339
|
+
publisherRoot: value.publisherRoot,
|
|
340
|
+
runtimeRoot: value.runtimeRoot,
|
|
341
|
+
providerRoot: value.providerRoot,
|
|
342
|
+
parentRoot: value.parentRoot,
|
|
343
|
+
phases: ["QUALIFY", "APPLY", "SETTLE"],
|
|
344
|
+
writer: "canonical-v4-apply",
|
|
345
|
+
};
|
|
346
|
+
return {
|
|
347
|
+
transaction,
|
|
348
|
+
transactionRoot: v4ContentRoot("release-transaction", transaction),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function createV4ReleaseReceipt(value) {
|
|
353
|
+
exactKeys(
|
|
354
|
+
value,
|
|
355
|
+
[
|
|
356
|
+
"schema",
|
|
357
|
+
"transactionRoot",
|
|
358
|
+
"outcome",
|
|
359
|
+
"releasePassportRoot",
|
|
360
|
+
"providerTransactionRoot",
|
|
361
|
+
"providerStateRoot",
|
|
362
|
+
"providerReceiptRoots",
|
|
363
|
+
],
|
|
364
|
+
"$receipt",
|
|
365
|
+
);
|
|
366
|
+
if (value.schema !== V4_RELEASE_RECEIPT_CONTRACT)
|
|
367
|
+
fault(
|
|
368
|
+
"invalid-release-receipt",
|
|
369
|
+
"$receipt/schema",
|
|
370
|
+
"unsupported receipt schema",
|
|
371
|
+
);
|
|
372
|
+
validateV4Root(value.transactionRoot, "$receipt/transactionRoot");
|
|
373
|
+
if (!["complete", "blocked"].includes(value.outcome))
|
|
374
|
+
fault(
|
|
375
|
+
"invalid-release-receipt",
|
|
376
|
+
"$receipt/outcome",
|
|
377
|
+
"unsupported receipt outcome",
|
|
378
|
+
);
|
|
379
|
+
for (const name of [
|
|
380
|
+
"releasePassportRoot",
|
|
381
|
+
"providerTransactionRoot",
|
|
382
|
+
"providerStateRoot",
|
|
383
|
+
]) {
|
|
384
|
+
if (value[name] !== null) validateV4Root(value[name], `$receipt/${name}`);
|
|
385
|
+
}
|
|
386
|
+
if (!Array.isArray(value.providerReceiptRoots))
|
|
387
|
+
fault(
|
|
388
|
+
"invalid-release-receipt",
|
|
389
|
+
"$receipt/providerReceiptRoots",
|
|
390
|
+
"receipt roots must be an array",
|
|
391
|
+
);
|
|
392
|
+
value.providerReceiptRoots.forEach((root, index) =>
|
|
393
|
+
validateV4Root(root, `$receipt/providerReceiptRoots/${index}`),
|
|
394
|
+
);
|
|
395
|
+
const canonicalRoots = [...new Set(value.providerReceiptRoots)].sort();
|
|
396
|
+
if (
|
|
397
|
+
canonicalRoots.length !== value.providerReceiptRoots.length ||
|
|
398
|
+
canonicalRoots.some(
|
|
399
|
+
(root, index) => root !== value.providerReceiptRoots[index],
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
fault(
|
|
403
|
+
"invalid-release-receipt",
|
|
404
|
+
"$receipt/providerReceiptRoots",
|
|
405
|
+
"provider receipt roots must be sorted and unique",
|
|
406
|
+
);
|
|
407
|
+
if (
|
|
408
|
+
value.outcome === "complete" &&
|
|
409
|
+
[
|
|
410
|
+
value.releasePassportRoot,
|
|
411
|
+
value.providerTransactionRoot,
|
|
412
|
+
value.providerStateRoot,
|
|
413
|
+
].some((root) => root === null)
|
|
414
|
+
)
|
|
415
|
+
fault(
|
|
416
|
+
"invalid-release-receipt",
|
|
417
|
+
"$receipt/outcome",
|
|
418
|
+
"complete receipt requires every terminal root",
|
|
419
|
+
);
|
|
420
|
+
v4CanonicalBytes(value);
|
|
421
|
+
return {
|
|
422
|
+
receipt: value,
|
|
423
|
+
receiptRoot: v4ContentRoot("release-receipt", value),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
@@ -196,7 +196,6 @@ function normalizeNpmPublisher(value, { packageName, repository, workflowFilenam
|
|
|
196
196
|
configurationRead: true,
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
|
-
|
|
200
199
|
function main() {
|
|
201
200
|
const repository = flag("repository");
|
|
202
201
|
const workflowRepository = flag("workflow-repository", repository);
|
|
@@ -127,7 +127,6 @@ const requiredPaths = [
|
|
|
127
127
|
"scripts/npm-publish-transaction.mjs",
|
|
128
128
|
"scripts/paper.mjs",
|
|
129
129
|
"scripts/publication-package.mjs",
|
|
130
|
-
"scripts/publication-commit-evidence.mjs",
|
|
131
130
|
"scripts/publication-reproducibility.mjs",
|
|
132
131
|
"scripts/release-candidate-resolver.mjs",
|
|
133
132
|
"scripts/resume-from-candidate-run.mjs",
|
|
@@ -300,8 +299,8 @@ if (channelPromotionWorkflow !== generateChannelPromotionWorkflow(advancedPromot
|
|
|
300
299
|
throw new Error("generated channel promotion workflow is stale");
|
|
301
300
|
}
|
|
302
301
|
for (const requiredSnippet of [
|
|
303
|
-
"
|
|
304
|
-
"
|
|
302
|
+
"name: Adapt recovery inputs into the canonical v4 publisher",
|
|
303
|
+
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4-alpha",
|
|
305
304
|
"declarative-release-tail: true",
|
|
306
305
|
]) {
|
|
307
306
|
if (!boundedAlphaRecoveryWorkflow.includes(requiredSnippet)) {
|
|
@@ -316,8 +315,8 @@ for (const workflowSource of [boundedAlphaRecoveryWorkflow, channelPromotionWork
|
|
|
316
315
|
for (const requiredSnippet of [
|
|
317
316
|
"buildchain-channel:",
|
|
318
317
|
`/${promotionShellRouting.alpha.workflowPath}@${promotionShellRouting.alpha.callRef}`,
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
`SELECTED_SHELL_REF: v${selfDogfoodMajor}-alpha`,
|
|
319
|
+
"name: Invoke the single v4 publisher adapter",
|
|
321
320
|
"promotion-contract-lock-digest:",
|
|
322
321
|
"authorize-promotion-runtime-override.cjs",
|
|
323
322
|
"BUILDCHAIN_ROUTER_REPOSITORY: ${{ inputs.buildchain-repository }}",
|
|
@@ -329,6 +328,9 @@ for (const requiredSnippet of [
|
|
|
329
328
|
throw new Error(`channel promotion workflow missing routing contract: ${requiredSnippet}`);
|
|
330
329
|
}
|
|
331
330
|
}
|
|
331
|
+
if (channelPromotionWorkflow.includes("Promote with stable workflow shell")) {
|
|
332
|
+
throw new Error("channel promotion workflow retains a parallel stable publisher");
|
|
333
|
+
}
|
|
332
334
|
for (const forbiddenSnippet of ["job.workflow_repository", "job.workflow_sha"]) {
|
|
333
335
|
if (channelPromotionWorkflow.includes(forbiddenSnippet)) {
|
|
334
336
|
throw new Error(`channel promotion workflow uses unsupported GitHub context: ${forbiddenSnippet}`);
|
|
@@ -1046,7 +1048,6 @@ for (const requiredSnippet of [
|
|
|
1046
1048
|
"resume-candidate-run-id:",
|
|
1047
1049
|
"resume-expected-source-tree:",
|
|
1048
1050
|
"resume-buildchain-runtime-ref:",
|
|
1049
|
-
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4",
|
|
1050
1051
|
"release-passport-buildchain-self-kfd: true",
|
|
1051
1052
|
"declarative-release-tail: true",
|
|
1052
1053
|
"release-passport-impact-json: .buildchain/release-impact.json",
|
|
@@ -1057,61 +1058,28 @@ for (const requiredSnippet of [
|
|
|
1057
1058
|
}
|
|
1058
1059
|
const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/.release-candidate-promote.yml"), "utf8");
|
|
1059
1060
|
for (const requiredSnippet of [
|
|
1060
|
-
"release
|
|
1061
|
-
"
|
|
1062
|
-
"
|
|
1063
|
-
"
|
|
1064
|
-
"
|
|
1065
|
-
"
|
|
1066
|
-
"
|
|
1067
|
-
"release-
|
|
1068
|
-
"release-
|
|
1069
|
-
"release-
|
|
1070
|
-
"release-passport-kfd-support-matrix-json:", "release-passport-kfd-support-matrix-json: ${{ inputs.release-passport-kfd-support-matrix-json }}",
|
|
1071
|
-
"release-passport-kfd-product-gate-jsons:",
|
|
1072
|
-
"release-passport-kfd-product-gate-jsons: ${{ inputs.release-passport-kfd-product-gate-jsons }}",
|
|
1073
|
-
"release-passport-invariant-passport-jsons:",
|
|
1074
|
-
"release-passport-invariant-passport-jsons: ${{ inputs.release-passport-invariant-passport-jsons }}",
|
|
1075
|
-
"release-passport-invariant-passport-command:",
|
|
1076
|
-
"release-passport-invariant-passport-command: ${{ inputs.release-passport-invariant-passport-command }}",
|
|
1077
|
-
"release-passport-buildchain-self-kfd:",
|
|
1078
|
-
"release-passport-buildchain-self-kfd: ${{ inputs.release-passport-buildchain-self-kfd }}",
|
|
1079
|
-
"github-release:",
|
|
1080
|
-
"default: true",
|
|
1081
|
-
"github-release: ${{ inputs.github-release }}",
|
|
1082
|
-
"github-release-payload-patterns:",
|
|
1083
|
-
"github-release-artifact-paths: ${{ steps.rc.outputs.release-candidate-github-release-artifact-paths }}",
|
|
1084
|
-
"github-release-title: ${{ inputs.github-release-title }}",
|
|
1085
|
-
"github-release-notes: ${{ inputs.github-release-notes }}",
|
|
1086
|
-
"publication-commit-command:",
|
|
1087
|
-
"BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY:",
|
|
1088
|
-
"KUNGFU_GOVERNANCE_AUDITOR_APP_PRIVATE_KEY:",
|
|
1089
|
-
"Commit consumer publication authority last",
|
|
1090
|
-
"node .buildchain/runtime/scripts/publication-commit-evidence.mjs",
|
|
1091
|
-
"require-publish-source-lock: \"true\"",
|
|
1092
|
-
"publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
|
|
1093
|
-
"publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
|
|
1094
|
-
"publish-source-locked: ${{ steps.publish-gate.outputs.locked }}",
|
|
1095
|
-
"expected-publication-version: ${{ needs.publication-plan.outputs.version }}",
|
|
1096
|
-
"publication-version: ${{ needs.publication-plan.outputs.version }}",
|
|
1097
|
-
"name: Plan exact publication version",
|
|
1098
|
-
"name: Install exact publication planning dependencies",
|
|
1099
|
-
"DRY_RUN: ${{ inputs.dry-run }}",
|
|
1100
|
-
"if (dryRun) {",
|
|
1101
|
-
"Enforce Buildchain stable release canary gate",
|
|
1102
|
-
"BUILDCHAIN_STABLE_RELEASE_POLICY: .buildchain/stable-release-policy.json",
|
|
1103
|
-
"Consumer has no binary-distribution.yml; standalone binary dispatch is not applicable.",
|
|
1104
|
-
"gh workflow view binary-distribution.yml",
|
|
1105
|
-
"resume-candidate-run-id:",
|
|
1106
|
-
"node .buildchain/runtime/scripts/resume-from-candidate-run.mjs",
|
|
1107
|
-
"publish-sealed-bundle-root: ${{ steps.rc.outputs.publish-sealed-bundle-root }}",
|
|
1108
|
-
"BUILDCHAIN_EXPECTED_TRANSACTION_ID: ${{ inputs.resume-transaction-id }}",
|
|
1109
|
-
"if: ${{ inputs.resume-candidate-run-id == '' }}",
|
|
1110
|
-
"Bridge Buildchain self-runtime dependencies",
|
|
1111
|
-
"ln -s .buildchain/runtime/node_modules node_modules",
|
|
1061
|
+
"name: QUALIFY canonical v4 release invocation inputs",
|
|
1062
|
+
"name: APPLY one rooted provider transaction",
|
|
1063
|
+
"name: SETTLE terminal ReleaseReceipt projection",
|
|
1064
|
+
"uses: ./.buildchain/runtime/actions/v4-release-candidate-promote",
|
|
1065
|
+
"publisher-workflow-sha:",
|
|
1066
|
+
"runtime-commit:",
|
|
1067
|
+
"runtime-tree:",
|
|
1068
|
+
"release-invocation-root:",
|
|
1069
|
+
"release-transaction-root:",
|
|
1070
|
+
"release-receipt-root:",
|
|
1112
1071
|
]) {
|
|
1113
1072
|
if (!releaseCandidatePromoteWorkflow.includes(requiredSnippet)) {
|
|
1114
|
-
throw new Error(`release candidate promote workflow missing
|
|
1073
|
+
throw new Error(`release candidate promote workflow missing canonical v4 boundary: ${requiredSnippet}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
for (const retiredSnippet of [
|
|
1077
|
+
"Commit consumer publication authority last",
|
|
1078
|
+
"publish-github-artifact-attestation-evidence.mjs",
|
|
1079
|
+
"capture-package-release-propagation.mjs",
|
|
1080
|
+
]) {
|
|
1081
|
+
if (releaseCandidatePromoteWorkflow.includes(retiredSnippet)) {
|
|
1082
|
+
throw new Error(`release candidate promote workflow retains parallel publication authority: ${retiredSnippet}`);
|
|
1115
1083
|
}
|
|
1116
1084
|
}
|
|
1117
1085
|
const workflowDir = path.join(root, ".github/workflows");
|
|
@@ -20,17 +20,25 @@ import {
|
|
|
20
20
|
evaluateWorkflowBudgets,
|
|
21
21
|
} from "./maintainability-governance.mjs";
|
|
22
22
|
|
|
23
|
-
function collectHotspots(
|
|
23
|
+
function collectHotspots(
|
|
24
|
+
root,
|
|
25
|
+
current,
|
|
26
|
+
limit = 20,
|
|
27
|
+
shallowFallback = [],
|
|
28
|
+
{
|
|
29
|
+
baseRef = process.env.GITHUB_BASE_REF || process.env.GITHUB_REF_NAME || "",
|
|
30
|
+
} = {},
|
|
31
|
+
) {
|
|
24
32
|
const maintained = new Set([
|
|
25
33
|
...Object.keys(current.files || {}),
|
|
26
34
|
...Object.keys(current.tests || {}),
|
|
27
35
|
...Object.keys(current.workflows || {}),
|
|
28
36
|
]);
|
|
29
|
-
//
|
|
30
|
-
// Reuse the audited routes; full clones still re-rank them below.
|
|
31
|
-
// This keeps consumer verification deterministic without inventing history.
|
|
37
|
+
// Release-line reconciliation reuses audited routes across equivalent-tree DAG shapes.
|
|
32
38
|
if (
|
|
33
|
-
gitOutput(root, ["rev-parse", "--is-shallow-repository"]).trim() ===
|
|
39
|
+
gitOutput(root, ["rev-parse", "--is-shallow-repository"]).trim() ===
|
|
40
|
+
"true" ||
|
|
41
|
+
/^(?:alpha|release|publish-gate)\//u.test(baseRef)
|
|
34
42
|
) {
|
|
35
43
|
return shallowFallback.slice(0, limit);
|
|
36
44
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
collectHistoryRows,
|
|
9
9
|
collectRevisionCatalog,
|
|
10
10
|
countsBy,
|
|
11
|
+
ensureCapabilityCutLineage,
|
|
11
12
|
git,
|
|
12
13
|
sha256,
|
|
13
14
|
} from "./v3-v4-capability-catalog.mjs";
|
|
@@ -17,9 +18,8 @@ export const V3_V4_CAPABILITY_INVENTORY_CONTRACT =
|
|
|
17
18
|
export const V3_V4_CAPABILITY_CUTS = Object.freeze({
|
|
18
19
|
priorFamilyV3: "6b96bdad8d9f8ccf9275f27d9370a226a9c78465",
|
|
19
20
|
liveV3: "8493bf140a7f567e76aff3119f3d39ff026afc84",
|
|
20
|
-
liveV4: "
|
|
21
|
+
liveV4: "97edd5b93eb67e5bf36ad55726e08231b47b70c0",
|
|
21
22
|
});
|
|
22
|
-
|
|
23
23
|
const INVENTORY_PATH = "architecture/v3-v4-live-capability-inventory.json";
|
|
24
24
|
const ALLOWED_DISPOSITIONS = new Set([
|
|
25
25
|
"v4-native",
|
|
@@ -92,64 +92,6 @@ function capabilityCutAvailable(root, revision) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export function assertCapabilityCutAncestor({
|
|
96
|
-
root = process.cwd(),
|
|
97
|
-
revision,
|
|
98
|
-
descendant = "HEAD",
|
|
99
|
-
label = "capability cut",
|
|
100
|
-
} = {}) {
|
|
101
|
-
try {
|
|
102
|
-
execFileSync("git", ["merge-base", "--is-ancestor", revision, descendant], {
|
|
103
|
-
cwd: root,
|
|
104
|
-
stdio: "ignore",
|
|
105
|
-
});
|
|
106
|
-
} catch {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`${label} ${revision} must be an ancestor of ${descendant}; regenerate the cut after rebasing instead of relying on a retained local object`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function repositoryIsShallow(root) {
|
|
114
|
-
return (
|
|
115
|
-
execFileSync("git", ["rev-parse", "--is-shallow-repository"], {
|
|
116
|
-
cwd: root,
|
|
117
|
-
encoding: "utf8",
|
|
118
|
-
}).trim() === "true"
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function ensureCapabilityCutAncestor({
|
|
123
|
-
root = process.cwd(),
|
|
124
|
-
revision,
|
|
125
|
-
descendant = "HEAD",
|
|
126
|
-
label = "capability cut",
|
|
127
|
-
} = {}) {
|
|
128
|
-
try {
|
|
129
|
-
assertCapabilityCutAncestor({ root, revision, descendant, label });
|
|
130
|
-
return;
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (!repositoryIsShallow(root)) throw error;
|
|
133
|
-
}
|
|
134
|
-
const descendantCommit = execFileSync(
|
|
135
|
-
"git",
|
|
136
|
-
["rev-parse", `${descendant}^{commit}`],
|
|
137
|
-
{ cwd: root, encoding: "utf8" },
|
|
138
|
-
).trim();
|
|
139
|
-
try {
|
|
140
|
-
execFileSync(
|
|
141
|
-
"git",
|
|
142
|
-
["fetch", "--no-tags", "--depth=128", "origin", descendantCommit],
|
|
143
|
-
{ cwd: root, stdio: "ignore" },
|
|
144
|
-
);
|
|
145
|
-
} catch {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`${label} ${revision} ancestry could not be hydrated from ${descendantCommit} through a bounded origin fetch`,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
assertCapabilityCutAncestor({ root, revision, descendant, label });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
95
|
function ensureCapabilityCutsAvailable(root) {
|
|
154
96
|
const revisions = Object.values(V3_V4_CAPABILITY_CUTS);
|
|
155
97
|
const missing = revisions.filter(
|
|
@@ -170,7 +112,7 @@ function ensureCapabilityCutsAvailable(root) {
|
|
|
170
112
|
`v3/v4 capability cuts are unavailable after a bounded origin fetch: ${unavailable.join(", ")}`,
|
|
171
113
|
);
|
|
172
114
|
}
|
|
173
|
-
|
|
115
|
+
ensureCapabilityCutLineage({
|
|
174
116
|
root,
|
|
175
117
|
revision: V3_V4_CAPABILITY_CUTS.liveV4,
|
|
176
118
|
label: "live v4 capability cut",
|