@kungfu-tech/buildchain 4.0.8 → 4.0.9-alpha.1
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/AGENTS.md +12 -19
- package/CONTRIBUTING.md +6 -9
- package/README.md +11 -1
- package/architecture/agent-change-map.md +11 -4
- package/architecture/build-environments.json +71 -0
- package/architecture/build-orchestration.json +18 -0
- package/architecture/ci-lane-change-budget.json +51 -2
- package/architecture/internal-capabilities.json +17 -4
- package/architecture/maintainability-debt.json +37 -126
- package/architecture/maintainability-policy.json +19 -210
- package/architecture/release-topology.json +9 -7
- package/architecture/universal-workflow-bootstrap.json +5 -4
- package/architecture/universal-workflow-train-admission.json +2 -2
- package/architecture/workflow-taxonomy.json +13 -0
- package/bin/buildchain.mjs +1 -1
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/dist/site/buildchain-contract.json +41 -1131
- package/dist/site/buildchain-site.json +48 -153
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/cli-registry.json +1 -1
- package/dist/site/controller-registry.json +30 -868
- package/dist/site/kfd-claims.json +148 -396
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +7 -7
- package/dist/site/node-api-registry.json +37 -37
- package/dist/site/page-registry.json +36 -141
- package/dist/site/public-surface-audit.json +93 -402
- package/dist/site/publication-authority-registry.json +22 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +11 -11
- package/dist/site/workflow-registry.json +165 -391
- package/docs/MAP.md +5 -4
- package/docs/cli-reference.md +1 -1
- package/docs/getting-started.md +8 -7
- package/docs/node-api-reference.md +85 -85
- package/docs/release-governance.md +19 -18
- package/docs/reusable-build-surface.md +160 -1430
- package/docs/runtime-train-validation.md +45 -172
- package/docs/workflow-catalog.md +1 -0
- package/package.json +1 -1
- package/packages/core/build-configuration.js +117 -0
- package/packages/core/buildchain-config.js +2 -0
- package/packages/core/buildchain-contract.js +14 -44
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/controller-evidence.js +2 -9
- package/scripts/buildchain-cli-help.mjs +1 -1
- package/scripts/check-floating-consumer-policy-contract.mjs +4 -7
- package/scripts/check-inventory.mjs +91 -77
- package/scripts/check-maintainability.mjs +23 -8
- package/scripts/check-universal-workflow-bootstrap.mjs +12 -2
- package/scripts/generate-channel-build-workflow.mjs +50 -410
- package/scripts/generate-site-bundle.mjs +2 -1
- package/scripts/generate-universal-workflow-facades.mjs +1 -0
- package/scripts/init-repo.mjs +25 -26
- package/scripts/resolve-build-configuration.mjs +105 -0
- package/scripts/universal-workflow-self-dogfood.mjs +8 -1
- package/scripts/verify-golden-path.mjs +3 -3
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { parseWorkflowCallJobs } from "../packages/core/workflow-yaml-contract.js";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
3
5
|
import {
|
|
4
6
|
assertPublicSurfaceReverseAudit,
|
|
5
7
|
collectPublicSurfaceReverseAudit,
|
|
6
8
|
} from "../packages/core/public-surface-audit.js";
|
|
7
|
-
import { evaluateBuildchainContractLock } from "../packages/core/buildchain-contract.js";
|
|
9
|
+
import { createBuildchainContractLock, evaluateBuildchainContractLock, finalizeBuildchainContractWorld } from "../packages/core/buildchain-contract.js";
|
|
8
10
|
import {
|
|
9
|
-
canAdmitSelfDogfoodLockEvaluation,
|
|
10
|
-
contractForSelfDogfoodEvaluation,
|
|
11
11
|
hasQualifiedSelfDogfoodBootstrapAuthority,
|
|
12
12
|
resolveSelfDogfoodMajor,
|
|
13
13
|
} from "../packages/core/self-dogfood-version.js";
|
|
@@ -161,6 +161,7 @@ const requiredPaths = [
|
|
|
161
161
|
".github/workflows/self-ops-patrol-weekly.yml",
|
|
162
162
|
".github/workflows/self-ops-patrol-monthly.yml",
|
|
163
163
|
".github/workflows/self-build-alpha-dogfood.yml",
|
|
164
|
+
".github/workflows/self-build-stable-dogfood.yml",
|
|
164
165
|
".github/workflows/release-candidate-promote.yml",
|
|
165
166
|
".github/workflows/.release-candidate-promote.yml",
|
|
166
167
|
".github/workflows/release-propagation.yml",
|
|
@@ -196,16 +197,9 @@ if (rootPackage.name !== "@kungfu-tech/buildchain") {
|
|
|
196
197
|
if (rootPackage.private !== false) {
|
|
197
198
|
throw new Error("root package must be publishable with private=false");
|
|
198
199
|
}
|
|
199
|
-
const selfDogfoodWorkflow = fs.readFileSync(
|
|
200
|
-
path.join(root, ".github/workflows/self-build-alpha-dogfood.yml"),
|
|
201
|
-
"utf8",
|
|
202
|
-
);
|
|
203
200
|
const selfDogfoodAlphaLock = JSON.parse(
|
|
204
201
|
fs.readFileSync(path.join(root, ".buildchain/alpha-contract-lock.json"), "utf8"),
|
|
205
202
|
);
|
|
206
|
-
const currentBuildchainContract = JSON.parse(
|
|
207
|
-
fs.readFileSync(path.join(root, "dist/site/buildchain-contract.json"), "utf8"),
|
|
208
|
-
);
|
|
209
203
|
const selfDogfoodBootstrapAuthority = JSON.parse(
|
|
210
204
|
fs.readFileSync(path.join(root, "architecture/bootstrap-authority.json"), "utf8"),
|
|
211
205
|
);
|
|
@@ -227,34 +221,66 @@ if (!/^[0-9a-f]{40}$/.test(selfDogfoodAlphaLock.buildchain?.resolvedSha || ""))
|
|
|
227
221
|
if (selfDogfoodAlphaLock.buildchain?.compatibilityPolicy !== "major-compatible") {
|
|
228
222
|
throw new Error("Buildchain self-dogfood alpha lock must enforce major-compatible policy");
|
|
229
223
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
224
|
+
// Consumer locks describe accepted published runtimes, not this unpublished producer.
|
|
225
|
+
// Validate their immutable source worlds here; hosted admission checks floating drift.
|
|
226
|
+
for (const channel of ["alpha-contract-lock.json", "contract-lock.json"]) {
|
|
227
|
+
const lock = JSON.parse(
|
|
228
|
+
fs.readFileSync(path.join(root, ".buildchain", channel), "utf8"),
|
|
229
|
+
);
|
|
230
|
+
const sha = lock.buildchain?.resolvedSha;
|
|
231
|
+
if (!/^[0-9a-f]{40}$/u.test(sha || ""))
|
|
232
|
+
throw new Error(`${channel}: invalid accepted SHA`);
|
|
233
|
+
let world;
|
|
234
|
+
try {
|
|
235
|
+
world = JSON.parse(
|
|
236
|
+
execFileSync(
|
|
237
|
+
"git",
|
|
238
|
+
["show", `${sha}:dist/site/buildchain-contract.json`],
|
|
239
|
+
{ cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
} catch {
|
|
243
|
+
const response = await fetch(
|
|
244
|
+
`https://raw.githubusercontent.com/kungfu-systems/buildchain/${sha}/dist/site/buildchain-contract.json`,
|
|
245
|
+
{ signal: AbortSignal.timeout(30000) },
|
|
246
|
+
);
|
|
247
|
+
if (!response.ok)
|
|
248
|
+
throw new Error(
|
|
249
|
+
`${channel}: accepted source contract unavailable (${response.status})`,
|
|
250
|
+
);
|
|
251
|
+
world = await response.json();
|
|
252
|
+
}
|
|
253
|
+
const verified = finalizeBuildchainContractWorld(world);
|
|
254
|
+
for (const field of ["contractDigest", "compatibilityDigest"]) {
|
|
255
|
+
if (
|
|
256
|
+
world[field] !== verified[field] ||
|
|
257
|
+
lock.buildchain[field] !== world[field]
|
|
258
|
+
)
|
|
259
|
+
throw new Error(`${channel}: accepted ${field} mismatch`);
|
|
260
|
+
}
|
|
261
|
+
const accepted = createBuildchainContractLock({ contractWorld: verified }).buildchain;
|
|
262
|
+
if (JSON.stringify(lock.buildchain.surfaces) !== JSON.stringify(accepted.surfaces) || lock.buildchain.majorLine !== verified.majorLine || lock.buildchain.compatibilityPolicy !== "major-compatible") throw new Error(`${channel}: accepted surface set or policy mismatch`);
|
|
263
|
+
const ref = channel.startsWith("alpha")
|
|
264
|
+
? `v${selfDogfoodMajor}-alpha`
|
|
265
|
+
: `v${selfDogfoodMajor}`;
|
|
266
|
+
const evaluation = evaluateBuildchainContractLock({
|
|
267
|
+
lock,
|
|
268
|
+
current: verified,
|
|
269
|
+
runtimeRef: ref,
|
|
270
|
+
runtimeSha: sha,
|
|
271
|
+
runtimeClass: channel.startsWith("alpha") ? "alpha" : "stable",
|
|
272
|
+
workflowShellRef: ref,
|
|
273
|
+
});
|
|
274
|
+
if (!evaluation.ok || lock.buildchain.ref !== ref)
|
|
275
|
+
throw new Error(`${channel}: invalid accepted runtime contract`);
|
|
276
|
+
}
|
|
277
|
+
for (const [channel, ref] of [["alpha", `v${selfDogfoodMajor}-alpha`], ["stable", `v${selfDogfoodMajor}`]]) {
|
|
278
|
+
const workflow = fs.readFileSync(path.join(root, `.github/workflows/self-build-${channel}-dogfood.yml`), "utf8");
|
|
279
|
+
if (!workflow.includes(`/.github/workflows/build.yml@${ref}`) || /steps:|buildchain-channel:|runner-preset:|working-directory:/u.test(workflow)) throw new Error(`${channel} self-dogfood must remain a thin public TOML build caller`);
|
|
280
|
+
if (!workflow.includes(`group: buildchain-${channel}-self-dogfood-`) || !workflow.includes("cancel-in-progress: false")) throw new Error(`${channel} self-dogfood must serialize its own runs`);
|
|
281
|
+
const expectedInputs = channel === "alpha" ? [] : ["config-path"];
|
|
282
|
+
const call = parseWorkflowCallJobs(workflow).find((job) => job.id === `${channel}-consumer`);
|
|
283
|
+
if (JSON.stringify(Object.keys(call.with || {})) !== JSON.stringify(expectedInputs)) throw new Error(`${channel} self-dogfood input contract drift`);
|
|
258
284
|
}
|
|
259
285
|
const reusableBuildWorkflow = fs.readFileSync(
|
|
260
286
|
path.join(root, ".github/workflows/.build.yml"),
|
|
@@ -262,8 +288,8 @@ const reusableBuildWorkflow = fs.readFileSync(
|
|
|
262
288
|
);
|
|
263
289
|
for (const requiredSnippet of [
|
|
264
290
|
"BUILDCHAIN_WORKFLOW_REF: ${{ job.workflow_ref }}",
|
|
265
|
-
"
|
|
266
|
-
|
|
291
|
+
"BUILDCHAIN_WORKFLOW_SHA: ${{ job.workflow_sha }}",
|
|
292
|
+
"Resolve rooted build plan from TOML",
|
|
267
293
|
]) {
|
|
268
294
|
if (!reusableBuildWorkflow.includes(requiredSnippet)) {
|
|
269
295
|
throw new Error(`reusable build workflow missing called-workflow identity: ${requiredSnippet}`);
|
|
@@ -343,31 +369,13 @@ const promotionOverrideAuthorization = fs.readFileSync(
|
|
|
343
369
|
if (!promotionOverrideAuthorization.includes("promotion runtime override is only allowed for trusted workflow_dispatch runs")) {
|
|
344
370
|
throw new Error("promotion runtime override authorization must remain fail closed");
|
|
345
371
|
}
|
|
346
|
-
for (const
|
|
347
|
-
"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
"needs.resolve-channel.outputs.runtime-override != 'true' && needs.resolve-channel.outputs.channel == 'stable'",
|
|
351
|
-
"needs.resolve-channel.outputs.buildchain-ref",
|
|
352
|
-
"needs.resolve-channel.outputs.contract-lock-path",
|
|
353
|
-
]) {
|
|
354
|
-
if (!channelBuildWorkflow.includes(requiredSnippet)) {
|
|
355
|
-
throw new Error(`channel build workflow missing routing contract: ${requiredSnippet}`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if ((channelBuildWorkflow.match(/uses: \.\/\.github\/workflows\/\.build\.yml/g) || []).length !== 3) {
|
|
359
|
-
throw new Error("channel build workflow must bind override, alpha, and stable to the exact caller workflow shell");
|
|
360
|
-
}
|
|
361
|
-
if (channelBuildWorkflow.includes("uses: kungfu-systems/buildchain/.github/workflows/.build.yml@")) {
|
|
362
|
-
throw new Error("channel build workflow must not statically fetch another channel shell before its ref exists");
|
|
372
|
+
for (const workflow of [channelBuildWorkflow, reusableBuildWorkflow]) {
|
|
373
|
+
const block = workflow.split(" inputs:\n")[1].split(" secrets:\n")[0];
|
|
374
|
+
const names = [...block.matchAll(/^ ([a-z0-9-]+):$/gm)].map((match) => match[1]);
|
|
375
|
+
if (JSON.stringify(names) !== '["config-path"]') throw new Error("ordinary build inputs must contain only config-path");
|
|
363
376
|
}
|
|
364
|
-
|
|
365
|
-
"
|
|
366
|
-
"cancel-in-progress: false",
|
|
367
|
-
]) {
|
|
368
|
-
if (!selfDogfoodWorkflow.includes(requiredSnippet)) {
|
|
369
|
-
throw new Error(`Buildchain self-dogfood workflow missing promotion serialization: ${requiredSnippet}`);
|
|
370
|
-
}
|
|
377
|
+
if ((channelBuildWorkflow.match(/uses: \.\/\.github\/workflows\/\.build\.yml/g) || []).length !== 1) {
|
|
378
|
+
throw new Error("public build facade must invoke its exact backbone once");
|
|
371
379
|
}
|
|
372
380
|
const actionlintConfig = fs.readFileSync(
|
|
373
381
|
path.join(root, ".github/actionlint.yaml"),
|
|
@@ -780,8 +788,9 @@ const registeredActionIds = (workflowRegistry.actions || []).map((entry) => entr
|
|
|
780
788
|
const readmeActionIndex = fs.readFileSync(path.join(root, "README.md"), "utf8");
|
|
781
789
|
const mapActionIndex = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
|
|
782
790
|
const retrospectiveActionIndex = fs.readFileSync(path.join(root, ".github/retrospectives/2026-07-10-buildchain-consolidation.md"), "utf8");
|
|
783
|
-
|
|
784
|
-
|
|
791
|
+
const buildOwners = JSON.parse(fs.readFileSync(path.join(root, "architecture/build-orchestration.json"), "utf8")).owners;
|
|
792
|
+
if (registeredActionIds.length !== 8 + Object.keys(buildOwners).length) {
|
|
793
|
+
throw new Error(`workflow-registry.json must expose the eight public actions and owned build composites, got ${registeredActionIds.length}`);
|
|
785
794
|
}
|
|
786
795
|
for (const actionId of registeredActionIds) {
|
|
787
796
|
if (!readmeActionIndex.includes(`actions/${actionId}`) || !mapActionIndex.includes(`actions/${actionId}`)) {
|
|
@@ -925,16 +934,16 @@ for (const requiredSnippet of [
|
|
|
925
934
|
}
|
|
926
935
|
const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
|
|
927
936
|
for (const requiredSnippet of [
|
|
928
|
-
"
|
|
929
|
-
"
|
|
930
|
-
"buildchain
|
|
931
|
-
"
|
|
932
|
-
"
|
|
933
|
-
"
|
|
934
|
-
"
|
|
935
|
-
"
|
|
936
|
-
"
|
|
937
|
-
"
|
|
937
|
+
"config-path",
|
|
938
|
+
"buildchain.toml",
|
|
939
|
+
".buildchain/contract-lock.json",
|
|
940
|
+
".buildchain/alpha-contract-lock.json",
|
|
941
|
+
"architecture/build-environments.json",
|
|
942
|
+
"build.contract",
|
|
943
|
+
"cache roots",
|
|
944
|
+
"exact consumer source",
|
|
945
|
+
"build-lifecycle-stage",
|
|
946
|
+
"build-artifact-transfer",
|
|
938
947
|
]) {
|
|
939
948
|
if (!reusableBuildSurfaceDoc.includes(requiredSnippet)) {
|
|
940
949
|
throw new Error(`reusable build surface doc missing contract lock snippet: ${requiredSnippet}`);
|
|
@@ -1319,7 +1328,12 @@ if (!Array.isArray(inventory.migratedActions) || inventory.migratedActions.lengt
|
|
|
1319
1328
|
throw new Error("migratedActions must be empty; buildchain v2 only ships native actions");
|
|
1320
1329
|
}
|
|
1321
1330
|
const shippedActions = internalActions;
|
|
1322
|
-
|
|
1331
|
+
if (inventory.compositeActionOwnership !== "architecture/build-orchestration.json#owners") throw new Error("composite action ownership must use the build orchestration contract");
|
|
1332
|
+
const compositeNames = Object.keys(buildOwners).map((file) => {
|
|
1333
|
+
if (!/^actions\/[^/]+\/action\.yml$/.test(file) || !/using:\s*composite/.test(fs.readFileSync(path.join(root, file), "utf8"))) throw new Error(`invalid owned composite action: ${file}`);
|
|
1334
|
+
return path.posix.basename(path.posix.dirname(file));
|
|
1335
|
+
});
|
|
1336
|
+
const shippedActionNames = [...shippedActions.map((action) => action.path.replace(/^actions\//, "")), ...compositeNames].sort();
|
|
1323
1337
|
|
|
1324
1338
|
if (JSON.stringify(actualActions) !== JSON.stringify(shippedActionNames)) {
|
|
1325
1339
|
throw new Error(
|
|
@@ -82,15 +82,30 @@ function revisionAvailable(root, revision) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function ensureRevisionAvailable(
|
|
85
|
+
function ensureRevisionAvailable(
|
|
86
|
+
root,
|
|
87
|
+
revision,
|
|
88
|
+
{
|
|
89
|
+
fetchRevision = () =>
|
|
90
|
+
gitOutput(root, ["fetch", "--no-tags", "--depth=1", "origin", revision]),
|
|
91
|
+
} = {},
|
|
92
|
+
) {
|
|
86
93
|
if (revisionAvailable(root, revision)) return false;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
95
|
+
try {
|
|
96
|
+
fetchRevision();
|
|
97
|
+
break;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const detail = String(error?.stderr || error?.message || error).trim();
|
|
100
|
+
if (
|
|
101
|
+
attempt < 2 &&
|
|
102
|
+
/shallow file has changed since we read it/u.test(detail)
|
|
103
|
+
)
|
|
104
|
+
continue;
|
|
105
|
+
throw new Error(
|
|
106
|
+
`maintainability revision ${revision} is unavailable and could not be fetched from origin${detail ? `: ${detail}` : ""}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
94
109
|
}
|
|
95
110
|
if (!revisionAvailable(root, revision)) {
|
|
96
111
|
throw new Error(
|
|
@@ -75,13 +75,23 @@ for (const relative of contract.retiredWorkflowSurfaces) {
|
|
|
75
75
|
const activeWorkflows = contract.inventoryWorkflows.filter(
|
|
76
76
|
(relative) =>
|
|
77
77
|
!contract.retiredWorkflowSurfaces.includes(relative) &&
|
|
78
|
+
!contract.configurationGovernedWorkflows.includes(relative) &&
|
|
78
79
|
relative !== contract.bootstrap.consumerRecoveryWorkflow,
|
|
79
80
|
);
|
|
80
81
|
assert.deepEqual(
|
|
81
82
|
contract.bootstrapGovernedWorkflows,
|
|
82
83
|
activeWorkflows,
|
|
83
|
-
"every active
|
|
84
|
-
);
|
|
84
|
+
"every active reusable workflow must have its declared configuration or Bootstrap authority",
|
|
85
|
+
);
|
|
86
|
+
assert.deepEqual(contract.configurationGovernedWorkflows, [
|
|
87
|
+
".github/workflows/.build.yml", ".github/workflows/build.yml",
|
|
88
|
+
]);
|
|
89
|
+
for (const relative of contract.configurationGovernedWorkflows) {
|
|
90
|
+
const source = fs.readFileSync(path.join(root, relative), "utf8");
|
|
91
|
+
const inputBlock = source.split(" inputs:\n")[1].split(" secrets:\n")[0];
|
|
92
|
+
assert.deepEqual([...inputBlock.matchAll(/^ ([a-z0-9-]+):$/gmu)].map((match) => match[1]), ["config-path"]);
|
|
93
|
+
assert.doesNotMatch(source, /universal-request-json/u);
|
|
94
|
+
}
|
|
85
95
|
for (const relative of contract.bootstrapGovernedWorkflows) {
|
|
86
96
|
assert.ok(
|
|
87
97
|
contract.inventoryWorkflows.includes(relative),
|