@kungfu-tech/buildchain 4.0.2-alpha.10 → 4.0.2-alpha.12
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 +40 -0
- package/architecture/ci-lane-change-budget.json +332 -0
- package/architecture/internal-capabilities.json +39 -0
- package/architecture/maintainability-debt.json +11 -9
- package/architecture/maintainability-policy.json +7 -7
- package/architecture/v4-release-topology.json +2 -1
- package/architecture/v4-universal-workflow-bootstrap.json +181 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/dist/site/buildchain-contract.json +86 -41
- 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 +154 -68
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +7 -7
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +158 -66
- package/dist/site/publication-authority-registry.json +67 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +189 -67
- package/docs/node-api-reference.md +9 -9
- package/docs/reusable-build-surface.md +16 -0
- package/package.json +2 -2
- package/packages/core/buildchain-publication-authority.js +3 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/release-tail-provider-plane.js +1 -1
- package/packages/core/v4-product-publication.js +1 -1
- package/packages/core/v4-universal-workflow-bootstrap.js +596 -0
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-release-topology.mjs +5 -2
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +257 -0
- package/scripts/generate-channel-build-workflow.mjs +5 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -2
- package/scripts/generate-v4-universal-workflow-facades.mjs +415 -0
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +586 -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
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
evaluateTestBudgets,
|
|
20
20
|
evaluateWorkflowBudgets,
|
|
21
21
|
} from "./maintainability-governance.mjs";
|
|
22
|
+
import { governUniversalFacadeWorkflowMetrics } from "./universal-facade-maintainability.mjs";
|
|
23
|
+
import { evaluatePublicSurface } from "./maintainability-public-surface.mjs";
|
|
22
24
|
|
|
23
25
|
function collectHotspots(
|
|
24
26
|
root,
|
|
@@ -162,116 +164,6 @@ function selectedFunction(metrics, file, name) {
|
|
|
162
164
|
return metrics.files[file]?.functions.find((entry) => entry.name === name);
|
|
163
165
|
}
|
|
164
166
|
|
|
165
|
-
function readJsonAtRevision(root, revision, file) {
|
|
166
|
-
return JSON.parse(gitOutput(root, ["show", `${revision}:${file}`]));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function publicSurfaceContract(entry, kind) {
|
|
170
|
-
if (kind === "cli") return { id: entry.id, usage: entry.usage };
|
|
171
|
-
if (kind === "node")
|
|
172
|
-
return {
|
|
173
|
-
export: entry.export,
|
|
174
|
-
specifier: entry.specifier,
|
|
175
|
-
target: entry.target,
|
|
176
|
-
};
|
|
177
|
-
return {
|
|
178
|
-
id: entry.id,
|
|
179
|
-
path: entry.path,
|
|
180
|
-
reusable: entry.reusable,
|
|
181
|
-
inputs: entry.inputs || [],
|
|
182
|
-
secrets: entry.secrets || [],
|
|
183
|
-
outputs: entry.outputs || [],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function evaluatePublicSurface({ root, revision, policy }) {
|
|
188
|
-
const issues = [];
|
|
189
|
-
const lifecycleFields = policy.publicSurfacePolicy.requiredLifecycleFields;
|
|
190
|
-
const capabilityGroups = new Set(
|
|
191
|
-
readJson(root, "dist/site/capability-registry.json").groups.map(
|
|
192
|
-
(entry) => entry.id,
|
|
193
|
-
),
|
|
194
|
-
);
|
|
195
|
-
const definitions = [
|
|
196
|
-
{
|
|
197
|
-
file: "dist/site/cli-registry.json",
|
|
198
|
-
collection: "commands",
|
|
199
|
-
kind: "cli",
|
|
200
|
-
key: "id",
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
file: "dist/site/node-api-registry.json",
|
|
204
|
-
collection: "exports",
|
|
205
|
-
kind: "node",
|
|
206
|
-
key: "export",
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
file: "dist/site/workflow-registry.json",
|
|
210
|
-
collection: "workflows",
|
|
211
|
-
kind: "workflow",
|
|
212
|
-
key: "id",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
file: "dist/site/workflow-registry.json",
|
|
216
|
-
collection: "actions",
|
|
217
|
-
kind: "action",
|
|
218
|
-
key: "id",
|
|
219
|
-
},
|
|
220
|
-
];
|
|
221
|
-
for (const definition of definitions) {
|
|
222
|
-
const current =
|
|
223
|
-
readJson(root, definition.file)[definition.collection] || [];
|
|
224
|
-
const baseline =
|
|
225
|
-
readJsonAtRevision(root, revision, definition.file)[
|
|
226
|
-
definition.collection
|
|
227
|
-
] || [];
|
|
228
|
-
const baselineByKey = new Map(
|
|
229
|
-
baseline.map((entry) => [entry[definition.key], entry]),
|
|
230
|
-
);
|
|
231
|
-
for (const entry of current) {
|
|
232
|
-
const label = `${definition.kind}:${entry[definition.key]}`;
|
|
233
|
-
for (const field of lifecycleFields) {
|
|
234
|
-
if (
|
|
235
|
-
!Object.prototype.hasOwnProperty.call(entry, field) ||
|
|
236
|
-
typeof entry[field] !== "string"
|
|
237
|
-
) {
|
|
238
|
-
issues.push(`${label}: lifecycle field ${field} is missing`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (!capabilityGroups.has(entry.capabilityGroup)) {
|
|
242
|
-
issues.push(
|
|
243
|
-
`${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
const previous = baselineByKey.get(entry[definition.key]);
|
|
247
|
-
const currentContract = publicSurfaceContract(entry, definition.kind);
|
|
248
|
-
if (
|
|
249
|
-
previous &&
|
|
250
|
-
JSON.stringify(currentContract) !==
|
|
251
|
-
JSON.stringify(publicSurfaceContract(previous, definition.kind))
|
|
252
|
-
) {
|
|
253
|
-
const approval = policy.approvedPublicSurfaceTransitions?.[label];
|
|
254
|
-
const approved =
|
|
255
|
-
approval?.fromRevision === revision &&
|
|
256
|
-
String(approval?.rationale || "").trim() &&
|
|
257
|
-
JSON.stringify(approval?.contract) ===
|
|
258
|
-
JSON.stringify(currentContract);
|
|
259
|
-
if (!approved) {
|
|
260
|
-
issues.push(
|
|
261
|
-
`${label}: existing public contract drifted from ${revision}`,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (!previous && !entry.nonDuplicationRationale) {
|
|
266
|
-
issues.push(
|
|
267
|
-
`${label}: new public surface requires a non-duplication rationale`,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return issues;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
167
|
function evaluateAddedFunctionBudgets({
|
|
276
168
|
file,
|
|
277
169
|
metrics,
|
|
@@ -497,12 +389,18 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
497
389
|
root,
|
|
498
390
|
extendedCoverageRevision,
|
|
499
391
|
);
|
|
392
|
+
const { governed: governedCurrent, migration: facadeMigration } =
|
|
393
|
+
governUniversalFacadeWorkflowMetrics({
|
|
394
|
+
root,
|
|
395
|
+
current,
|
|
396
|
+
workflowMetricsAtRevision,
|
|
397
|
+
});
|
|
500
398
|
const issues = evaluateExceptionGovernance({ policy });
|
|
501
399
|
issues.push(...evaluateExceptionBudget({ policy }));
|
|
502
400
|
const hotspots = collectHotspots(root, current, 20, debt.hotspots || []);
|
|
503
401
|
issues.push(
|
|
504
402
|
...evaluateDebtAuthority({
|
|
505
|
-
current,
|
|
403
|
+
current: governedCurrent,
|
|
506
404
|
policy,
|
|
507
405
|
debt,
|
|
508
406
|
capabilityIds: new Set(
|
|
@@ -518,14 +416,19 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
518
416
|
);
|
|
519
417
|
issues.push(
|
|
520
418
|
...evaluateWorkflowBudgets({
|
|
521
|
-
current,
|
|
419
|
+
current: governedCurrent,
|
|
522
420
|
baselineFiles: baselineWorkflows,
|
|
523
421
|
policy,
|
|
524
422
|
}),
|
|
525
423
|
);
|
|
526
424
|
issues.push(...evaluateRepositoryBudgets({ current, policy }));
|
|
527
425
|
issues.push(
|
|
528
|
-
...evaluatePublicSurface({
|
|
426
|
+
...evaluatePublicSurface({
|
|
427
|
+
root,
|
|
428
|
+
revision: enforcementRevision,
|
|
429
|
+
policy,
|
|
430
|
+
migration: facadeMigration,
|
|
431
|
+
}),
|
|
529
432
|
);
|
|
530
433
|
if (issues.length > 0) {
|
|
531
434
|
throw new Error(`maintainability check failed:\n- ${issues.join("\n- ")}`);
|
|
@@ -180,7 +180,9 @@ function permission(block, name) {
|
|
|
180
180
|
function workflowSnapshot(relative) {
|
|
181
181
|
const source = read(relative);
|
|
182
182
|
const document = parseWorkflowDocument(source);
|
|
183
|
-
const jobs = document.jobs
|
|
183
|
+
const jobs = document.jobs
|
|
184
|
+
.filter((job) => job.id !== "universal-bootstrap")
|
|
185
|
+
.map((job) => {
|
|
184
186
|
const block = jobBlock(source, job.id);
|
|
185
187
|
const uses = job.uses || null;
|
|
186
188
|
return {
|
|
@@ -206,7 +208,7 @@ function workflowSnapshot(relative) {
|
|
|
206
208
|
"direct-publication-command",
|
|
207
209
|
].filter(Boolean),
|
|
208
210
|
};
|
|
209
|
-
|
|
211
|
+
});
|
|
210
212
|
return {
|
|
211
213
|
path: relative,
|
|
212
214
|
triggers: document.triggers,
|
|
@@ -214,6 +216,7 @@ function workflowSnapshot(relative) {
|
|
|
214
216
|
reusableEdges: parseYamlUses(source)
|
|
215
217
|
.map(({ value }) => value)
|
|
216
218
|
.filter((value) => /\.github\/workflows\//u.test(value))
|
|
219
|
+
.filter((value) => !/\/bootstrap\.yml(?:@v4)?$/u.test(value))
|
|
217
220
|
.sort(),
|
|
218
221
|
};
|
|
219
222
|
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const contract = JSON.parse(
|
|
10
|
+
fs.readFileSync(
|
|
11
|
+
path.join(root, "architecture/v4-universal-workflow-bootstrap.json"),
|
|
12
|
+
"utf8",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
const admissionPolicy = JSON.parse(
|
|
16
|
+
fs.readFileSync(
|
|
17
|
+
path.join(root, "architecture/v4-universal-workflow-train-admission.json"),
|
|
18
|
+
"utf8",
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
const fileRoot = (relative) =>
|
|
22
|
+
`sha256:${crypto
|
|
23
|
+
.createHash("sha256")
|
|
24
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
25
|
+
.digest("hex")}`;
|
|
26
|
+
|
|
27
|
+
const workflowRoot = path.join(root, ".github/workflows");
|
|
28
|
+
const discovered = fs
|
|
29
|
+
.readdirSync(workflowRoot)
|
|
30
|
+
.filter((name) => /\.ya?ml$/u.test(name))
|
|
31
|
+
.map((name) => `.github/workflows/${name}`)
|
|
32
|
+
.filter((relative) =>
|
|
33
|
+
/(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(
|
|
34
|
+
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
.sort();
|
|
38
|
+
|
|
39
|
+
assert.equal(
|
|
40
|
+
contract.schema,
|
|
41
|
+
"kungfu-buildchain-v4-universal-workflow-bootstrap/v1",
|
|
42
|
+
);
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
contract.inventoryWorkflows,
|
|
45
|
+
[...new Set(contract.inventoryWorkflows)].sort(),
|
|
46
|
+
"inventoried public workflows must be sorted and duplicate-free",
|
|
47
|
+
);
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
contract.inventoryWorkflows,
|
|
50
|
+
discovered,
|
|
51
|
+
"every active public workflow_call surface must be inventoried",
|
|
52
|
+
);
|
|
53
|
+
for (const relative of contract.inventoryWorkflows)
|
|
54
|
+
assert.ok(
|
|
55
|
+
fs.statSync(path.join(root, relative)).isFile(),
|
|
56
|
+
`inventoried workflow is unavailable: ${relative}`,
|
|
57
|
+
);
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
contract.bootstrapGovernedWorkflows,
|
|
60
|
+
[...new Set(contract.bootstrapGovernedWorkflows)].sort(),
|
|
61
|
+
"Bootstrap-governed workflows must be sorted and duplicate-free",
|
|
62
|
+
);
|
|
63
|
+
assert.deepEqual(
|
|
64
|
+
contract.retiredWorkflowSurfaces,
|
|
65
|
+
[...new Set(contract.retiredWorkflowSurfaces)].sort(),
|
|
66
|
+
"retired workflow surfaces must be sorted and duplicate-free",
|
|
67
|
+
);
|
|
68
|
+
for (const relative of contract.retiredWorkflowSurfaces) {
|
|
69
|
+
assert.ok(
|
|
70
|
+
contract.inventoryWorkflows.includes(relative),
|
|
71
|
+
`retired workflow is outside the inventory: ${relative}`,
|
|
72
|
+
);
|
|
73
|
+
assert.match(
|
|
74
|
+
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
75
|
+
/retired/u,
|
|
76
|
+
`retired workflow does not fail closed explicitly: ${relative}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const activeWorkflows = contract.inventoryWorkflows.filter(
|
|
80
|
+
(relative) =>
|
|
81
|
+
!contract.retiredWorkflowSurfaces.includes(relative) &&
|
|
82
|
+
relative !== contract.bootstrap.consumerRecoveryWorkflow,
|
|
83
|
+
);
|
|
84
|
+
assert.deepEqual(
|
|
85
|
+
contract.bootstrapGovernedWorkflows,
|
|
86
|
+
activeWorkflows,
|
|
87
|
+
"every active public reusable workflow must be Bootstrap-governed",
|
|
88
|
+
);
|
|
89
|
+
for (const relative of contract.bootstrapGovernedWorkflows) {
|
|
90
|
+
assert.ok(
|
|
91
|
+
contract.inventoryWorkflows.includes(relative),
|
|
92
|
+
`Bootstrap-governed workflow is outside the inventory: ${relative}`,
|
|
93
|
+
);
|
|
94
|
+
const source = fs.readFileSync(path.join(root, relative), "utf8");
|
|
95
|
+
assert.ok(
|
|
96
|
+
relative === contract.bootstrap.publicWorkflow ||
|
|
97
|
+
/uses:\s+(?:\.\/)?\.github\/workflows\/bootstrap\.yml/u.test(source) ||
|
|
98
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@/u.test(
|
|
99
|
+
source,
|
|
100
|
+
),
|
|
101
|
+
`workflow is declared governed without a Bootstrap edge: ${relative}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assert.deepEqual(contract.complexityBudget, {
|
|
106
|
+
bootstrapAbis: 1,
|
|
107
|
+
requestEnvelopes: 1,
|
|
108
|
+
candidateResolvers: 1,
|
|
109
|
+
candidateEngines: 1,
|
|
110
|
+
terminalReceiptAuthorities: 1,
|
|
111
|
+
perWorkflowBootstrapImplementations: 0,
|
|
112
|
+
buildchainOnlySelfReleasePaths: 0,
|
|
113
|
+
perIncidentConsumerEdits: 0,
|
|
114
|
+
});
|
|
115
|
+
assert.equal(contract.bootstrap.discoveryRefExecutionAuthority, false);
|
|
116
|
+
assert.equal(contract.bootstrap.exactShaExecutionAuthority, true);
|
|
117
|
+
assert.equal(contract.bootstrap.consumerSourceMutationPerIncident, false);
|
|
118
|
+
assert.equal(contract.bootstrap.buildchainReleasePromotionPerIncident, false);
|
|
119
|
+
assert.equal(contract.bootstrap.selfDogfoodUsesPublicContract, true);
|
|
120
|
+
assert.equal(contract.bootstrap.recoveryDependsOnPublishedBuildchain, false);
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
contract.capabilityAdapters.map(({ id }) => id).sort(),
|
|
123
|
+
[...admissionPolicy.allowedCapabilities].sort(),
|
|
124
|
+
"every admitted capability must have one real candidate adapter",
|
|
125
|
+
);
|
|
126
|
+
for (const adapter of contract.capabilityAdapters) {
|
|
127
|
+
assert.match(adapter.execution, /^exact-candidate-/u);
|
|
128
|
+
assert.ok(adapter.payloadSchema.endsWith("/v1"));
|
|
129
|
+
}
|
|
130
|
+
assert.ok(!admissionPolicy.allowedCapabilities.includes("workflow-contract"));
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
admissionPolicy.contractRoots,
|
|
133
|
+
[
|
|
134
|
+
fileRoot("architecture/v4-universal-workflow-bootstrap.json"),
|
|
135
|
+
fileRoot(contract.bootstrap.faultCampaign),
|
|
136
|
+
fileRoot("packages/core/v4-universal-workflow-bootstrap.js"),
|
|
137
|
+
fileRoot("scripts/v4-universal-workflow-backflow.mjs"),
|
|
138
|
+
fileRoot("scripts/v4-universal-workflow-engine.mjs"),
|
|
139
|
+
fileRoot("scripts/v4-universal-workflow-self-dogfood.mjs"),
|
|
140
|
+
fileRoot(contract.bootstrap.publicWorkflow),
|
|
141
|
+
].sort(),
|
|
142
|
+
);
|
|
143
|
+
assert.doesNotMatch(
|
|
144
|
+
fs.readFileSync(
|
|
145
|
+
path.join(root, contract.bootstrap.candidateEnginePath),
|
|
146
|
+
"utf8",
|
|
147
|
+
),
|
|
148
|
+
/request\.capability\.id\s*===\s*["']workflow-contract["']/u,
|
|
149
|
+
"contract-only validation must not be admitted as successful execution",
|
|
150
|
+
);
|
|
151
|
+
const bootstrapSource = fs.readFileSync(
|
|
152
|
+
path.join(root, contract.bootstrap.publicWorkflow),
|
|
153
|
+
"utf8",
|
|
154
|
+
);
|
|
155
|
+
assert.doesNotMatch(
|
|
156
|
+
bootstrapSource,
|
|
157
|
+
/admission-policy-json:/u,
|
|
158
|
+
"callers must not supply their own admission authority",
|
|
159
|
+
);
|
|
160
|
+
execFileSync(
|
|
161
|
+
process.execPath,
|
|
162
|
+
[path.join(root, contract.migration.compatibilityFacadeGenerator), "--check"],
|
|
163
|
+
{ stdio: "inherit" },
|
|
164
|
+
);
|
|
165
|
+
assert.doesNotMatch(bootstrapSource, /^ permissions:/mu);
|
|
166
|
+
assert.match(bootstrapSource, /settle:[\s\S]*needs: \[admit, execute\]/u);
|
|
167
|
+
assert.match(
|
|
168
|
+
bootstrapSource,
|
|
169
|
+
/ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*map\(select\(\.status == "completed" and \(\.conclusion \/\/ ""\) != ""\)\)[\s\S]*candidate\/architecture\/v4-universal-workflow-train-admission\.json/u,
|
|
170
|
+
);
|
|
171
|
+
const consumerTemplate = fs.readFileSync(
|
|
172
|
+
path.join(root, contract.bootstrap.consumerTemplate),
|
|
173
|
+
"utf8",
|
|
174
|
+
);
|
|
175
|
+
const consumerRecoveryTemplate = fs.readFileSync(
|
|
176
|
+
path.join(root, contract.bootstrap.consumerRecoveryTemplate),
|
|
177
|
+
"utf8",
|
|
178
|
+
);
|
|
179
|
+
const consumerRecoveryWorkflow = fs.readFileSync(
|
|
180
|
+
path.join(root, contract.bootstrap.consumerRecoveryWorkflow),
|
|
181
|
+
"utf8",
|
|
182
|
+
);
|
|
183
|
+
const selfDogfood = fs.readFileSync(
|
|
184
|
+
path.join(root, contract.bootstrap.selfDogfoodWorkflow),
|
|
185
|
+
"utf8",
|
|
186
|
+
);
|
|
187
|
+
assert.match(
|
|
188
|
+
consumerTemplate,
|
|
189
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@v4/u,
|
|
190
|
+
);
|
|
191
|
+
assert.match(
|
|
192
|
+
consumerRecoveryTemplate,
|
|
193
|
+
/recovery-admit:[\s\S]*recovery-execute:/u,
|
|
194
|
+
);
|
|
195
|
+
assert.match(
|
|
196
|
+
consumerRecoveryTemplate,
|
|
197
|
+
/Parse exact recovery coordinates without Buildchain code[\s\S]*Prove exact independent review before candidate code runs[\s\S]*ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*Admit candidate policy and exact-head checks[\s\S]*map\(select\(\.status == "completed" and \(\.conclusion \/\/ ""\) != ""\)\)/u,
|
|
198
|
+
);
|
|
199
|
+
assert.match(
|
|
200
|
+
consumerRecoveryTemplate,
|
|
201
|
+
/recovery-execute:[\s\S]*needs: recovery-admit[\s\S]*contents: write/u,
|
|
202
|
+
);
|
|
203
|
+
assert.doesNotMatch(
|
|
204
|
+
consumerRecoveryTemplate,
|
|
205
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\//u,
|
|
206
|
+
"consumer recovery must not parse any published Buildchain workflow",
|
|
207
|
+
);
|
|
208
|
+
assert.equal(
|
|
209
|
+
consumerRecoveryWorkflow,
|
|
210
|
+
consumerRecoveryTemplate,
|
|
211
|
+
"the Buildchain self-consumer must exercise the exact distributable recovery shell",
|
|
212
|
+
);
|
|
213
|
+
assert.match(
|
|
214
|
+
selfDogfood,
|
|
215
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@train\/v4\/v4\.0\/universal-reusable-workflow-bootstrap/u,
|
|
216
|
+
);
|
|
217
|
+
assert.match(
|
|
218
|
+
selfDogfood,
|
|
219
|
+
/uses:\s+\.\/\.github\/workflows\/universal-bootstrap-recovery\.yml/u,
|
|
220
|
+
);
|
|
221
|
+
assert.doesNotMatch(
|
|
222
|
+
selfDogfood,
|
|
223
|
+
/\.github\/workflows\/bootstrap\.yml@(?:v4|v4-alpha)(?:\s|$)/u,
|
|
224
|
+
"Train self-dogfood must not depend on an alpha or stable Bootstrap tag",
|
|
225
|
+
);
|
|
226
|
+
for (const channel of ["conformance", "alpha", "stable"]) {
|
|
227
|
+
assert.match(selfDogfood, new RegExp(`primary-${channel}:`, "u"));
|
|
228
|
+
assert.match(selfDogfood, new RegExp(`recovery-${channel}:`, "u"));
|
|
229
|
+
}
|
|
230
|
+
assert.match(
|
|
231
|
+
selfDogfood,
|
|
232
|
+
/\.state == "APPROVED"[\s\S]*ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*\.conclusion == "success"/u,
|
|
233
|
+
);
|
|
234
|
+
assert.match(
|
|
235
|
+
consumerRecoveryTemplate,
|
|
236
|
+
/result-json:[\s\S]*value:\s*\$\{\{ jobs\.recovery-execute\.outputs\.result-json \}\}/u,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
console.log(
|
|
240
|
+
JSON.stringify({
|
|
241
|
+
ok: true,
|
|
242
|
+
schema: contract.schema,
|
|
243
|
+
publicWorkflowCount: discovered.length,
|
|
244
|
+
activePublicWorkflowCount: activeWorkflows.length,
|
|
245
|
+
retiredWorkflowCount: contract.retiredWorkflowSurfaces.length,
|
|
246
|
+
inventoriedWorkflowCount: contract.inventoryWorkflows.length,
|
|
247
|
+
inventoryCoveragePercent: 100,
|
|
248
|
+
governedWorkflowCount: contract.bootstrapGovernedWorkflows.length,
|
|
249
|
+
governedCoveragePercent: Number(
|
|
250
|
+
(
|
|
251
|
+
(contract.bootstrapGovernedWorkflows.length / activeWorkflows.length) *
|
|
252
|
+
100
|
|
253
|
+
).toFixed(2),
|
|
254
|
+
),
|
|
255
|
+
migration: contract.migration,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const sourcePath = path.join(root, ".github/workflows/.build.yml");
|
|
@@ -403,7 +404,10 @@ export function generateChannelBuildWorkflow(source) {
|
|
|
403
404
|
].join("\n"),
|
|
404
405
|
)
|
|
405
406
|
.replace("\n alpha:\n", `\n${routerControllerPlanJob()}\n\n alpha:\n`);
|
|
406
|
-
return
|
|
407
|
+
return migrateV4UniversalWorkflowFacade(
|
|
408
|
+
`${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`,
|
|
409
|
+
".github/workflows/build.yml",
|
|
410
|
+
);
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
function main() {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const sourcePath = path.join(root, ".github/workflows/.release-candidate-promote.yml");
|
|
@@ -302,7 +303,7 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
|
|
|
302
303
|
if (!inputNames.includes(required)) throw new Error(`advanced promotion workflow missing input: ${required}`);
|
|
303
304
|
}
|
|
304
305
|
const invokeForwarded = routedInputs(inputNames, alphaRoute, major);
|
|
305
|
-
return `# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
306
|
+
return migrateV4UniversalWorkflowFacade(`# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
306
307
|
name: Release Candidate Promote
|
|
307
308
|
|
|
308
309
|
on:
|
|
@@ -513,7 +514,7 @@ ${consumerAdmissionJob()}
|
|
|
513
514
|
with:
|
|
514
515
|
${invokeForwarded}
|
|
515
516
|
secrets: inherit
|
|
516
|
-
|
|
517
|
+
`, ".github/workflows/release-candidate-promote.yml");
|
|
517
518
|
}
|
|
518
519
|
|
|
519
520
|
function main() {
|