@kungfu-tech/buildchain 4.0.2-alpha.2 → 4.0.2-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/architecture/agent-change-map.md +52 -1
- package/architecture/ci-lane-change-budget.json +349 -0
- package/architecture/internal-capabilities.json +44 -1
- package/architecture/maintainability-debt.json +53 -33
- package/architecture/maintainability-policy.json +20 -15
- package/architecture/v3-core-mechanism-inventory.json +1 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +5 -5
- package/architecture/v4-release-invocation-fixtures.json +20 -0
- package/architecture/v4-release-topology.json +160 -84
- package/architecture/v4-runtime-semantic-closure.json +4 -1
- package/architecture/v4-universal-workflow-bootstrap.json +171 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/contracts/buildchain-v2-residuals-v1.json +0 -54
- package/contracts/v4-release-invocation-v1.schema.json +50 -2
- package/dist/site/buildchain-contract.json +87 -42
- package/dist/site/buildchain-site.json +15 -10
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +46 -10
- package/dist/site/kfd-claims.json +174 -196
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +95 -20
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +178 -198
- package/dist/site/publication-authority-registry.json +67 -57
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +209 -259
- package/docs/node-api-reference.md +25 -22
- package/docs/reusable-build-surface.md +16 -0
- package/package.json +2 -2
- package/packages/core/buildchain-publication-authority.js +3 -2
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/dev-delivery-candidate-identity.js +18 -0
- package/packages/core/dev-delivery-execution-transfer.js +6 -7
- 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 +42 -26
- package/packages/core/dev-delivery-warrant.js +8 -0
- package/packages/core/dev-delivery-writer-protocol-transition.js +72 -0
- package/packages/core/release-tail-product-capabilities.js +29 -0
- package/packages/core/release-tail-provider-plane.js +3 -3
- package/packages/core/v4-canonical-contracts.js +6 -0
- package/packages/core/v4-product-publication.js +387 -0
- 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 +72 -3
- package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
- package/scripts/check-inventory.mjs +13 -22
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-release-topology.mjs +296 -40
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +270 -0
- package/scripts/dev-delivery-warrant-store.mjs +73 -7
- package/scripts/dev-delivery-warrant-transition.mjs +109 -0
- package/scripts/dev-delivery-warrant.mjs +84 -100
- package/scripts/dev-pr-auto-merge.mjs +15 -15
- package/scripts/generate-channel-build-workflow.mjs +5 -1
- package/scripts/generate-channel-promotion-workflow.mjs +5 -3
- package/scripts/generate-v4-universal-workflow-facades.mjs +415 -0
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/release-candidate-resolver.mjs +23 -24
- package/scripts/resume-from-candidate-run.mjs +16 -17
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v4-product-publication-intent.mjs +114 -0
- package/scripts/v4-release-candidate-adapter.mjs +41 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +617 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const contractPath = path.join(
|
|
8
|
+
root,
|
|
9
|
+
"architecture/v4-universal-workflow-bootstrap.json",
|
|
10
|
+
);
|
|
11
|
+
const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
|
12
|
+
const laneBudgetPath = path.join(
|
|
13
|
+
root,
|
|
14
|
+
"architecture/ci-lane-change-budget.json",
|
|
15
|
+
);
|
|
16
|
+
const recoveryTemplatePath = path.join(
|
|
17
|
+
root,
|
|
18
|
+
contract.bootstrap.consumerRecoveryTemplate,
|
|
19
|
+
);
|
|
20
|
+
const recoveryWorkflowPath = path.join(
|
|
21
|
+
root,
|
|
22
|
+
contract.bootstrap.consumerRecoveryWorkflow,
|
|
23
|
+
);
|
|
24
|
+
const universalInput = ` universal-request-json:
|
|
25
|
+
description: "Versioned exact-candidate request envelope; empty preserves the compatibility path"
|
|
26
|
+
default: ""
|
|
27
|
+
required: false
|
|
28
|
+
type: string
|
|
29
|
+
`;
|
|
30
|
+
const bootstrapJob = ` universal-bootstrap:
|
|
31
|
+
name: Universal exact-candidate execution
|
|
32
|
+
if: \${{ inputs.universal-request-json != '' }}
|
|
33
|
+
uses: ./.github/workflows/bootstrap.yml
|
|
34
|
+
with:
|
|
35
|
+
request-json: \${{ inputs.universal-request-json }}
|
|
36
|
+
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
function fail(message) {
|
|
40
|
+
throw new Error(message);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function activeFacadePaths() {
|
|
44
|
+
const retired = new Set(contract.retiredWorkflowSurfaces || []);
|
|
45
|
+
return contract.inventoryWorkflows.filter(
|
|
46
|
+
(relative) =>
|
|
47
|
+
relative !== contract.bootstrap.publicWorkflow &&
|
|
48
|
+
relative !== contract.bootstrap.consumerRecoveryWorkflow &&
|
|
49
|
+
!retired.has(relative),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sourceRevision() {
|
|
54
|
+
const revision = contract.migration.facadeSourceRevision;
|
|
55
|
+
if (!/^[0-9a-f]{40}$/u.test(revision || ""))
|
|
56
|
+
fail("migration.facadeSourceRevision must be an exact commit");
|
|
57
|
+
return revision;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function materializeSourceRevision() {
|
|
61
|
+
const revision = sourceRevision();
|
|
62
|
+
try {
|
|
63
|
+
execFileSync("git", ["cat-file", "-e", `${revision}^{commit}`], {
|
|
64
|
+
cwd: root,
|
|
65
|
+
stdio: "ignore",
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
} catch {}
|
|
69
|
+
try {
|
|
70
|
+
execFileSync(
|
|
71
|
+
"git",
|
|
72
|
+
["fetch", "--no-tags", "--depth=1", "origin", revision],
|
|
73
|
+
{ cwd: root, stdio: "pipe" },
|
|
74
|
+
);
|
|
75
|
+
execFileSync("git", ["cat-file", "-e", `${revision}^{commit}`], {
|
|
76
|
+
cwd: root,
|
|
77
|
+
stdio: "ignore",
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const detail = String(error?.stderr || error?.message || error).trim();
|
|
81
|
+
fail(
|
|
82
|
+
`facade source revision ${revision} is unavailable and could not be hydrated from origin${detail ? `: ${detail}` : ""}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function frozenFacadeSource(relative) {
|
|
88
|
+
return execFileSync("git", ["show", `${sourceRevision()}:${relative}`], {
|
|
89
|
+
cwd: root,
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isChannelGeneratedFacade(relative) {
|
|
95
|
+
return (contract.migration.channelGeneratedFacades || []).includes(relative);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function addUniversalInput(source, relative) {
|
|
99
|
+
if (source.includes(" universal-request-json:\n")) return source;
|
|
100
|
+
const workflowCall = source.match(/^(\s*)workflow_call:\s*$/mu);
|
|
101
|
+
if (!workflowCall) fail(`${relative} has no workflow_call mapping`);
|
|
102
|
+
const callIndent = workflowCall[1];
|
|
103
|
+
const inputHeader = `${callIndent} inputs:\n`;
|
|
104
|
+
const callEnd = workflowCall.index + workflowCall[0].length;
|
|
105
|
+
if (source.slice(callEnd + 1).startsWith(inputHeader)) {
|
|
106
|
+
const insertion = callEnd + 1 + inputHeader.length;
|
|
107
|
+
return `${source.slice(0, insertion)}${universalInput}${source.slice(insertion)}`;
|
|
108
|
+
}
|
|
109
|
+
return `${source.slice(0, callEnd + 1)}\n${inputHeader}${universalInput}${source.slice(callEnd + 1)}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function conditionExpression(source, relative, jobId) {
|
|
113
|
+
const child = source.match(/^(\s+)[A-Za-z0-9_-]+:/mu);
|
|
114
|
+
const indent = child?.[1] || " ";
|
|
115
|
+
const match = source.match(new RegExp(`^${indent}if:\\s*(.*)$`, "mu"));
|
|
116
|
+
if (!match)
|
|
117
|
+
return `${indent}if: \${{ inputs.universal-request-json == '' }}\n`;
|
|
118
|
+
const suffix = match[1].trim();
|
|
119
|
+
if (suffix === ">-" || suffix === "|") {
|
|
120
|
+
const start = match.index + match[0].length + 1;
|
|
121
|
+
const tail = source.slice(start);
|
|
122
|
+
const endMatch = tail.match(/^ [A-Za-z0-9_-]+:/mu);
|
|
123
|
+
const end = endMatch ? start + endMatch.index : source.length;
|
|
124
|
+
const raw = source.slice(start, end).trim();
|
|
125
|
+
const inner = raw
|
|
126
|
+
.replace(/^\$\{\{\s*/u, "")
|
|
127
|
+
.replace(/\s*\}\}$/u, "")
|
|
128
|
+
.split("\n")
|
|
129
|
+
.map((line) => line.trim())
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.join("\n ");
|
|
132
|
+
if (!inner) fail(`${relative}#${jobId} has an empty multiline if`);
|
|
133
|
+
return {
|
|
134
|
+
start: match.index,
|
|
135
|
+
end,
|
|
136
|
+
value: `${indent}if: >-\n${indent} \${{\n${indent} inputs.universal-request-json == '' &&\n${indent} (\n${indent} ${inner}\n${indent} )\n${indent} }}\n`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const inner = suffix.startsWith("${{")
|
|
140
|
+
? suffix.replace(/^\$\{\{\s*/u, "").replace(/\s*\}\}$/u, "")
|
|
141
|
+
: suffix;
|
|
142
|
+
return {
|
|
143
|
+
start: match.index,
|
|
144
|
+
end:
|
|
145
|
+
match.index +
|
|
146
|
+
match[0].length +
|
|
147
|
+
(source[match.index + match[0].length] === "\n" ? 1 : 0),
|
|
148
|
+
value: `${indent}if: \${{ inputs.universal-request-json == '' && (${inner}) }}\n`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function guardCompatibilityJobs(source, relative) {
|
|
153
|
+
const jobs = source.match(/^jobs:\s*$/mu);
|
|
154
|
+
if (!jobs) fail(`${relative} has no jobs mapping`);
|
|
155
|
+
const bodyStart = jobs.index + jobs[0].length + 1;
|
|
156
|
+
const body = source.slice(bodyStart);
|
|
157
|
+
const headers = [...body.matchAll(/^ ([A-Za-z0-9_-]+):\s*$/gmu)];
|
|
158
|
+
if (headers.length === 0) fail(`${relative} has no jobs`);
|
|
159
|
+
let rewritten = body;
|
|
160
|
+
for (let index = headers.length - 1; index >= 0; index -= 1) {
|
|
161
|
+
const header = headers[index];
|
|
162
|
+
if (header[1] === "universal-bootstrap") continue;
|
|
163
|
+
const start = header.index + header[0].length + 1;
|
|
164
|
+
const end = headers[index + 1]?.index ?? body.length;
|
|
165
|
+
const job = body.slice(start, end);
|
|
166
|
+
if (job.includes("inputs.universal-request-json == ''")) continue;
|
|
167
|
+
const condition = conditionExpression(job, relative, header[1]);
|
|
168
|
+
if (typeof condition === "string") {
|
|
169
|
+
rewritten = `${rewritten.slice(0, start)}${condition}${rewritten.slice(start)}`;
|
|
170
|
+
} else {
|
|
171
|
+
rewritten = `${rewritten.slice(0, start + condition.start)}${condition.value}${rewritten.slice(start + condition.end)}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const withGuards = `${source.slice(0, bodyStart)}${rewritten}`;
|
|
175
|
+
return withGuards.includes(" universal-bootstrap:\n")
|
|
176
|
+
? withGuards
|
|
177
|
+
: `${withGuards.slice(0, bodyStart)}${bootstrapJob}${withGuards.slice(bodyStart)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function migrateV4UniversalWorkflowFacade(source, relative) {
|
|
181
|
+
return guardCompatibilityJobs(addUniversalInput(source, relative), relative);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function verify(source, relative) {
|
|
185
|
+
for (const snippet of [
|
|
186
|
+
" universal-request-json:\n",
|
|
187
|
+
" universal-bootstrap:\n",
|
|
188
|
+
"uses: ./.github/workflows/bootstrap.yml",
|
|
189
|
+
]) {
|
|
190
|
+
if (!source.includes(snippet))
|
|
191
|
+
fail(`${relative} is missing ${snippet.trim()}`);
|
|
192
|
+
}
|
|
193
|
+
const jobs = source.slice(source.search(/^jobs:\s*$/mu));
|
|
194
|
+
const headers = [...jobs.matchAll(/^ ([A-Za-z0-9_-]+):\s*$/gmu)];
|
|
195
|
+
for (let index = 0; index < headers.length; index += 1) {
|
|
196
|
+
const header = headers[index];
|
|
197
|
+
if (header[1] === "universal-bootstrap") continue;
|
|
198
|
+
const end = headers[index + 1]?.index ?? jobs.length;
|
|
199
|
+
const block = jobs.slice(header.index, end);
|
|
200
|
+
if (!block.includes("inputs.universal-request-json == ''"))
|
|
201
|
+
fail(`${relative}#${header[1]} can overlap universal execution`);
|
|
202
|
+
}
|
|
203
|
+
if (!isChannelGeneratedFacade(relative)) {
|
|
204
|
+
const expected = migrateV4UniversalWorkflowFacade(
|
|
205
|
+
frozenFacadeSource(relative),
|
|
206
|
+
relative,
|
|
207
|
+
);
|
|
208
|
+
if (source !== expected)
|
|
209
|
+
fail(
|
|
210
|
+
`${relative} differs from the exact generated facade rooted at ${sourceRevision()}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function laneBudget({ laneId, authorityClass, triggerClass, minutes, metric }) {
|
|
216
|
+
return {
|
|
217
|
+
laneId,
|
|
218
|
+
authorityClass,
|
|
219
|
+
triggerClass,
|
|
220
|
+
concurrencyPolicy: { mode: "none", cancelInProgress: false },
|
|
221
|
+
expectedRunnerMinutes: minutes,
|
|
222
|
+
cancellationBehavior: "finish-started",
|
|
223
|
+
sloImpact: {
|
|
224
|
+
mergeCritical: false,
|
|
225
|
+
metric,
|
|
226
|
+
expectedContributionSeconds: 0,
|
|
227
|
+
rationale:
|
|
228
|
+
"The opt-in universal path preserves the default compatibility lane and executes only an exact admitted runtime.",
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function updateLaneBudgets() {
|
|
234
|
+
const policy = JSON.parse(fs.readFileSync(laneBudgetPath, "utf8"));
|
|
235
|
+
const mixed = new Set([
|
|
236
|
+
".github/workflows/release-governance-reconcile.yml",
|
|
237
|
+
".github/workflows/v4-adopter-delivery.yml",
|
|
238
|
+
]);
|
|
239
|
+
const facadeLanes = activeFacadePaths().map(
|
|
240
|
+
(relative) => `${relative}#universal-bootstrap`,
|
|
241
|
+
);
|
|
242
|
+
policy.declarationFamilies = [
|
|
243
|
+
{
|
|
244
|
+
...laneBudget({
|
|
245
|
+
laneId: "",
|
|
246
|
+
authorityClass: "governed-delegation",
|
|
247
|
+
triggerClass: "reusable",
|
|
248
|
+
minutes: 60,
|
|
249
|
+
metric: "opt-in exact-candidate execution latency",
|
|
250
|
+
}),
|
|
251
|
+
laneIds: facadeLanes.filter(
|
|
252
|
+
(laneId) => !mixed.has(laneId.split("#", 1)[0]),
|
|
253
|
+
),
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
...laneBudget({
|
|
257
|
+
laneId: "",
|
|
258
|
+
authorityClass: "governed-delegation",
|
|
259
|
+
triggerClass: "mixed",
|
|
260
|
+
minutes: 60,
|
|
261
|
+
metric: "mixed-trigger exact-candidate execution latency",
|
|
262
|
+
}),
|
|
263
|
+
laneIds: facadeLanes.filter((laneId) =>
|
|
264
|
+
mixed.has(laneId.split("#", 1)[0]),
|
|
265
|
+
),
|
|
266
|
+
},
|
|
267
|
+
].map(({ laneId: _laneId, ...family }) => family);
|
|
268
|
+
const universalLaneIds = new Set([
|
|
269
|
+
".github/workflows/bootstrap.yml#admit",
|
|
270
|
+
".github/workflows/bootstrap.yml#execute",
|
|
271
|
+
".github/workflows/bootstrap.yml#settle",
|
|
272
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-admit",
|
|
273
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-execute",
|
|
274
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-settle",
|
|
275
|
+
".github/workflows/universal-bootstrap-dogfood.yml#public-bootstrap",
|
|
276
|
+
".github/workflows/universal-bootstrap-dogfood.yml#prepare",
|
|
277
|
+
".github/workflows/universal-bootstrap-dogfood.yml#primary-conformance",
|
|
278
|
+
".github/workflows/universal-bootstrap-dogfood.yml#recovery-conformance",
|
|
279
|
+
".github/workflows/universal-bootstrap-dogfood.yml#primary-alpha",
|
|
280
|
+
".github/workflows/universal-bootstrap-dogfood.yml#recovery-alpha",
|
|
281
|
+
".github/workflows/universal-bootstrap-dogfood.yml#primary-stable",
|
|
282
|
+
".github/workflows/universal-bootstrap-dogfood.yml#recovery-stable",
|
|
283
|
+
".github/workflows/universal-bootstrap-dogfood.yml#reconcile",
|
|
284
|
+
]);
|
|
285
|
+
policy.declarations = policy.declarations.filter(
|
|
286
|
+
({ laneId }) => !universalLaneIds.has(laneId),
|
|
287
|
+
);
|
|
288
|
+
policy.declarations.push(
|
|
289
|
+
laneBudget({
|
|
290
|
+
laneId: ".github/workflows/bootstrap.yml#admit",
|
|
291
|
+
authorityClass: "evidence",
|
|
292
|
+
triggerClass: "reusable",
|
|
293
|
+
minutes: 10,
|
|
294
|
+
metric: "exact-candidate admission latency",
|
|
295
|
+
}),
|
|
296
|
+
laneBudget({
|
|
297
|
+
laneId: ".github/workflows/bootstrap.yml#execute",
|
|
298
|
+
authorityClass: "governed-delegation",
|
|
299
|
+
triggerClass: "reusable",
|
|
300
|
+
minutes: 60,
|
|
301
|
+
metric: "exact-candidate execution latency",
|
|
302
|
+
}),
|
|
303
|
+
laneBudget({
|
|
304
|
+
laneId: ".github/workflows/bootstrap.yml#settle",
|
|
305
|
+
authorityClass: "evidence",
|
|
306
|
+
triggerClass: "reusable",
|
|
307
|
+
minutes: 10,
|
|
308
|
+
metric: "universal terminal settlement latency",
|
|
309
|
+
}),
|
|
310
|
+
laneBudget({
|
|
311
|
+
laneId:
|
|
312
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-admit",
|
|
313
|
+
authorityClass: "evidence",
|
|
314
|
+
triggerClass: "reusable",
|
|
315
|
+
minutes: 10,
|
|
316
|
+
metric: "consumer-owned exact-candidate admission latency",
|
|
317
|
+
}),
|
|
318
|
+
laneBudget({
|
|
319
|
+
laneId:
|
|
320
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-execute",
|
|
321
|
+
authorityClass: "governed-delegation",
|
|
322
|
+
triggerClass: "reusable",
|
|
323
|
+
minutes: 60,
|
|
324
|
+
metric: "consumer-owned exact-candidate execution latency",
|
|
325
|
+
}),
|
|
326
|
+
laneBudget({
|
|
327
|
+
laneId:
|
|
328
|
+
".github/workflows/universal-bootstrap-recovery.yml#recovery-settle",
|
|
329
|
+
authorityClass: "evidence",
|
|
330
|
+
triggerClass: "reusable",
|
|
331
|
+
minutes: 10,
|
|
332
|
+
metric: "consumer-owned terminal settlement latency",
|
|
333
|
+
}),
|
|
334
|
+
laneBudget({
|
|
335
|
+
laneId: ".github/workflows/universal-bootstrap-dogfood.yml#prepare",
|
|
336
|
+
authorityClass: "evidence",
|
|
337
|
+
triggerClass: "mixed",
|
|
338
|
+
minutes: 10,
|
|
339
|
+
metric: "Train-first self-dogfood admission latency",
|
|
340
|
+
}),
|
|
341
|
+
...[
|
|
342
|
+
"primary-conformance",
|
|
343
|
+
"recovery-conformance",
|
|
344
|
+
"primary-alpha",
|
|
345
|
+
"recovery-alpha",
|
|
346
|
+
"primary-stable",
|
|
347
|
+
"recovery-stable",
|
|
348
|
+
].map((job) =>
|
|
349
|
+
laneBudget({
|
|
350
|
+
laneId: `.github/workflows/universal-bootstrap-dogfood.yml#${job}`,
|
|
351
|
+
authorityClass: "governed-delegation",
|
|
352
|
+
triggerClass: "mixed",
|
|
353
|
+
minutes: 60,
|
|
354
|
+
metric: "Train-first exact-candidate self-dogfood latency",
|
|
355
|
+
}),
|
|
356
|
+
),
|
|
357
|
+
laneBudget({
|
|
358
|
+
laneId: ".github/workflows/universal-bootstrap-dogfood.yml#reconcile",
|
|
359
|
+
authorityClass: "evidence",
|
|
360
|
+
triggerClass: "mixed",
|
|
361
|
+
minutes: 10,
|
|
362
|
+
metric: "primary and recovery equivalence latency",
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
fs.writeFileSync(laneBudgetPath, `${JSON.stringify(policy, null, 2)}\n`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function main() {
|
|
369
|
+
const check = process.argv.includes("--check");
|
|
370
|
+
const fresh = process.argv.includes("--fresh");
|
|
371
|
+
materializeSourceRevision();
|
|
372
|
+
if (check) {
|
|
373
|
+
if (
|
|
374
|
+
fs.readFileSync(recoveryWorkflowPath, "utf8") !==
|
|
375
|
+
fs.readFileSync(recoveryTemplatePath, "utf8")
|
|
376
|
+
)
|
|
377
|
+
fail(
|
|
378
|
+
"consumer recovery workflow differs from its pre-positioned template",
|
|
379
|
+
);
|
|
380
|
+
} else {
|
|
381
|
+
fs.writeFileSync(
|
|
382
|
+
recoveryWorkflowPath,
|
|
383
|
+
fs.readFileSync(recoveryTemplatePath, "utf8"),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
for (const relative of activeFacadePaths()) {
|
|
387
|
+
const target = path.join(root, relative);
|
|
388
|
+
const source = fresh
|
|
389
|
+
? frozenFacadeSource(relative)
|
|
390
|
+
: fs.readFileSync(target, "utf8");
|
|
391
|
+
if (check) verify(source, relative);
|
|
392
|
+
else
|
|
393
|
+
fs.writeFileSync(
|
|
394
|
+
target,
|
|
395
|
+
migrateV4UniversalWorkflowFacade(source, relative),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (!check) {
|
|
399
|
+
contract.bootstrapGovernedWorkflows = [
|
|
400
|
+
contract.bootstrap.publicWorkflow,
|
|
401
|
+
...activeFacadePaths(),
|
|
402
|
+
].sort();
|
|
403
|
+
contract.migration.compatibilityFacades = "generated-dual-path";
|
|
404
|
+
fs.writeFileSync(contractPath, `${JSON.stringify(contract, null, 2)}\n`);
|
|
405
|
+
updateLaneBudgets();
|
|
406
|
+
process.stdout.write(
|
|
407
|
+
`${JSON.stringify({ migrated: activeFacadePaths().length })}\n`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const isMain =
|
|
413
|
+
process.argv[1] &&
|
|
414
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
415
|
+
if (isMain) main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
isGeneratedFacadePublicTransition,
|
|
6
|
+
loadUniversalFacadeMigration,
|
|
7
|
+
} from "./universal-facade-maintainability.mjs";
|
|
8
|
+
|
|
9
|
+
function readJson(root, file) {
|
|
10
|
+
return JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readJsonAtRevision(root, revision, file) {
|
|
14
|
+
return JSON.parse(
|
|
15
|
+
execFileSync("git", ["show", `${revision}:${file}`], {
|
|
16
|
+
cwd: root,
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function publicSurfaceContract(entry, kind) {
|
|
23
|
+
if (kind === "cli") return { id: entry.id, usage: entry.usage };
|
|
24
|
+
if (kind === "node")
|
|
25
|
+
return {
|
|
26
|
+
export: entry.export,
|
|
27
|
+
specifier: entry.specifier,
|
|
28
|
+
target: entry.target,
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
id: entry.id,
|
|
32
|
+
path: entry.path,
|
|
33
|
+
reusable: entry.reusable,
|
|
34
|
+
inputs: entry.inputs || [],
|
|
35
|
+
secrets: entry.secrets || [],
|
|
36
|
+
outputs: entry.outputs || [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function evaluatePublicSurface({
|
|
41
|
+
root,
|
|
42
|
+
revision,
|
|
43
|
+
policy,
|
|
44
|
+
migration = loadUniversalFacadeMigration(root),
|
|
45
|
+
}) {
|
|
46
|
+
const issues = [];
|
|
47
|
+
const capabilityGroups = new Set(
|
|
48
|
+
readJson(root, "dist/site/capability-registry.json").groups.map(
|
|
49
|
+
(entry) => entry.id,
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
const definitions = [
|
|
53
|
+
["dist/site/cli-registry.json", "commands", "cli", "id"],
|
|
54
|
+
["dist/site/node-api-registry.json", "exports", "node", "export"],
|
|
55
|
+
["dist/site/workflow-registry.json", "workflows", "workflow", "id"],
|
|
56
|
+
["dist/site/workflow-registry.json", "actions", "action", "id"],
|
|
57
|
+
];
|
|
58
|
+
for (const [file, collection, kind, key] of definitions) {
|
|
59
|
+
const current = readJson(root, file)[collection] || [];
|
|
60
|
+
const baseline = readJsonAtRevision(root, revision, file)[collection] || [];
|
|
61
|
+
const previousByKey = new Map(baseline.map((entry) => [entry[key], entry]));
|
|
62
|
+
for (const entry of current) {
|
|
63
|
+
const label = `${kind}:${entry[key]}`;
|
|
64
|
+
for (const field of policy.publicSurfacePolicy.requiredLifecycleFields) {
|
|
65
|
+
if (
|
|
66
|
+
!Object.prototype.hasOwnProperty.call(entry, field) ||
|
|
67
|
+
typeof entry[field] !== "string"
|
|
68
|
+
)
|
|
69
|
+
issues.push(`${label}: lifecycle field ${field} is missing`);
|
|
70
|
+
}
|
|
71
|
+
if (!capabilityGroups.has(entry.capabilityGroup))
|
|
72
|
+
issues.push(
|
|
73
|
+
`${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`,
|
|
74
|
+
);
|
|
75
|
+
const previous = previousByKey.get(entry[key]);
|
|
76
|
+
const currentContract = publicSurfaceContract(entry, kind);
|
|
77
|
+
if (
|
|
78
|
+
previous &&
|
|
79
|
+
JSON.stringify(currentContract) !==
|
|
80
|
+
JSON.stringify(publicSurfaceContract(previous, kind))
|
|
81
|
+
) {
|
|
82
|
+
const approval = policy.approvedPublicSurfaceTransitions?.[label];
|
|
83
|
+
const approvedContract =
|
|
84
|
+
approval?.fromRevision === revision &&
|
|
85
|
+
String(approval?.rationale || "").trim()
|
|
86
|
+
? approval.contract
|
|
87
|
+
: null;
|
|
88
|
+
const generated = isGeneratedFacadePublicTransition({
|
|
89
|
+
entry,
|
|
90
|
+
kind,
|
|
91
|
+
migration,
|
|
92
|
+
baseContract:
|
|
93
|
+
approvedContract || publicSurfaceContract(previous, kind),
|
|
94
|
+
currentContract,
|
|
95
|
+
});
|
|
96
|
+
if (
|
|
97
|
+
!generated &&
|
|
98
|
+
(!approvedContract ||
|
|
99
|
+
JSON.stringify(approvedContract) !==
|
|
100
|
+
JSON.stringify(currentContract))
|
|
101
|
+
)
|
|
102
|
+
issues.push(
|
|
103
|
+
`${label}: existing public contract drifted from ${revision}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (!previous && !entry.nonDuplicationRationale)
|
|
107
|
+
issues.push(
|
|
108
|
+
`${label}: new public surface requires a non-duplication rationale`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return issues;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { evaluatePublicSurface, publicSurfaceContract };
|
|
@@ -13,7 +13,6 @@ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
|
13
13
|
import { v4PublicationQualificationRoot, validateV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_WORKFLOW_FILE = "build-surface-fixture.yml";
|
|
16
|
-
|
|
17
16
|
function env(name, fallback = "") {
|
|
18
17
|
return process.env[name] || fallback;
|
|
19
18
|
}
|
|
@@ -21,7 +20,6 @@ function env(name, fallback = "") {
|
|
|
21
20
|
export function releaseCandidateDownloadEnabled(value = "true") {
|
|
22
21
|
return String(value || "true").trim().toLowerCase() !== "false";
|
|
23
22
|
}
|
|
24
|
-
|
|
25
23
|
function splitRepository(repository) {
|
|
26
24
|
const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
27
25
|
if (!match) {
|
|
@@ -44,7 +42,9 @@ function assertSha(value, label = "sha") {
|
|
|
44
42
|
|
|
45
43
|
export const releaseCandidateRuntimeSha = (passport) =>
|
|
46
44
|
assertSha(passport?.buildchain?.sha, "release candidate Passport Buildchain runtime SHA").toLowerCase();
|
|
47
|
-
|
|
45
|
+
export const resolveFreshPublicationVersion = ({ sealedBundle, candidateVersion = "" } = {}) =>
|
|
46
|
+
String(sealedBundle?.manifest?.npm?.version || candidateVersion || "").trim();
|
|
47
|
+
const optionalText = (value) => String(value || "");
|
|
48
48
|
function githubHeaders(token) {
|
|
49
49
|
const headers = {
|
|
50
50
|
accept: "application/vnd.github+json",
|
|
@@ -110,13 +110,13 @@ function digestFileSync(filePath, algorithm, encoding) {
|
|
|
110
110
|
return hash.digest(encoding);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
export function selectMergedChannelPullRequest({ pullRequests = [], targetRef, repository }) {
|
|
113
|
+
export function selectMergedChannelPullRequest({ pullRequests = [], targetRef, targetSha = "", repository }) {
|
|
114
114
|
const normalizedTarget = normalizeBranch(targetRef);
|
|
115
115
|
const candidates = pullRequests.filter((pr) => {
|
|
116
|
-
const
|
|
117
|
-
const merged = Boolean(pr.merged_at || pr.mergedAt || pr.
|
|
118
|
-
const
|
|
119
|
-
return merged &&
|
|
116
|
+
const baseRepo = pr.base?.repo?.full_name || pr.baseRepository?.nameWithOwner;
|
|
117
|
+
const merged = Boolean(pr.merged_at || pr.mergedAt || pr.merged === true);
|
|
118
|
+
const rooted = !repository || (baseRepo || pr.head?.repo?.full_name) === repository;
|
|
119
|
+
return merged && rooted && (!targetSha || (pr.merge_commit_sha || pr.mergeCommit?.oid) === targetSha) && normalizeBranch(pr.base?.ref || pr.baseRefName || "") === normalizedTarget;
|
|
120
120
|
});
|
|
121
121
|
candidates.sort((left, right) => {
|
|
122
122
|
const leftTime = Date.parse(left.merged_at || left.updated_at || left.closed_at || "");
|
|
@@ -513,11 +513,11 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
513
513
|
});
|
|
514
514
|
const channelPullRequest = selectMergedChannelPullRequest({
|
|
515
515
|
pullRequests: Array.isArray(pulls) ? pulls : [],
|
|
516
|
-
targetRef: normalizedTarget,
|
|
516
|
+
targetRef: normalizedTarget, targetSha: sha,
|
|
517
517
|
repository: repoInfo.fullName,
|
|
518
518
|
});
|
|
519
519
|
if (!channelPullRequest) {
|
|
520
|
-
throw new Error(`no
|
|
520
|
+
throw new Error(`no exact merged channel PR rooted in ${repoInfo.fullName} found for ${sha} into ${normalizedTarget}`);
|
|
521
521
|
}
|
|
522
522
|
let pullRequest = channelPullRequest;
|
|
523
523
|
if (majorGateTarget) {
|
|
@@ -725,16 +725,6 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
725
725
|
const noun = publishArtifactKind === "npm" ? "npm package tarballs" : "platform manifests";
|
|
726
726
|
throw new Error(`expected at least ${minimumPayloadCount} downloaded ${noun}, found ${downloadedRequiredArtifactCount}`);
|
|
727
727
|
}
|
|
728
|
-
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
729
|
-
const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
|
|
730
|
-
manifests,
|
|
731
|
-
version: passport.target?.version || "",
|
|
732
|
-
kind: publishArtifactKind,
|
|
733
|
-
tarballPaths: npmTarballPaths,
|
|
734
|
-
mainPackage: publishPackageMain,
|
|
735
|
-
});
|
|
736
|
-
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|
|
737
|
-
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
|
|
738
728
|
const sealedBundle = publishArtifactKind === "npm"
|
|
739
729
|
? createResolvedPublicationSealedBundle({
|
|
740
730
|
bundleRoot: payloadDir,
|
|
@@ -750,9 +740,17 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
750
740
|
releaseAssetPaths,
|
|
751
741
|
})
|
|
752
742
|
: undefined;
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
743
|
+
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8"))), publicationVersion = resolveFreshPublicationVersion({ sealedBundle, candidateVersion: passport.target?.version });
|
|
744
|
+
const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
|
|
745
|
+
manifests,
|
|
746
|
+
version: publicationVersion,
|
|
747
|
+
kind: publishArtifactKind,
|
|
748
|
+
tarballPaths: npmTarballPaths,
|
|
749
|
+
mainPackage: publishPackageMain,
|
|
750
|
+
});
|
|
751
|
+
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|
|
752
|
+
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
|
|
753
|
+
const sealedBundleManifestPath = sealedBundle ? path.join(resolvedOutput, "sealed-bundle.json") : "";
|
|
756
754
|
if (sealedBundleManifestPath) {
|
|
757
755
|
fs.writeFileSync(sealedBundleManifestPath, `${JSON.stringify(sealedBundle.manifest, null, 2)}\n`);
|
|
758
756
|
}
|
|
@@ -772,6 +770,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
772
770
|
sealedBundleManifest: sealedBundleManifestPath ? outputPath(sealedBundleManifestPath) : "",
|
|
773
771
|
},
|
|
774
772
|
version: passport.target?.version || "",
|
|
773
|
+
publicationVersion,
|
|
775
774
|
candidateHash: passport.candidateHash || "",
|
|
776
775
|
payloadCount: payloadArtifacts.length,
|
|
777
776
|
platformManifestCount: platformManifestPaths.length,
|
|
@@ -811,6 +810,7 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
811
810
|
"release-candidate-publication-qualification-path": result.paths?.publicationQualification || "",
|
|
812
811
|
"release-candidate-publication-qualification-root": result.publicationQualificationRoot || "",
|
|
813
812
|
"release-candidate-version": result.version || "",
|
|
813
|
+
"release-candidate-publication-version": optionalText(result.publicationVersion),
|
|
814
814
|
"release-candidate-source-sha": result.artifacts?.sourceSha || "",
|
|
815
815
|
"release-candidate-artifact": result.artifacts?.passport || "",
|
|
816
816
|
"release-candidate-build-summary-artifact": result.artifacts?.summary || "",
|
|
@@ -843,7 +843,6 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
843
843
|
console.log(JSON.stringify(result, null, 2));
|
|
844
844
|
return result;
|
|
845
845
|
}
|
|
846
|
-
|
|
847
846
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
848
847
|
try {
|
|
849
848
|
await resolveReleaseCandidateArtifactsCli();
|