@kungfu-tech/buildchain 4.0.2-alpha.34 → 4.0.2-alpha.35
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 +9 -0
- package/CONTRIBUTING.md +24 -0
- package/README.md +2 -2
- package/actions/promote-buildchain-ref/README.md +1 -1
- package/architecture/agent-change-map.md +6 -4
- package/architecture/internal-capabilities.json +4 -4
- package/architecture/maintainability-debt.json +13 -11
- package/architecture/maintainability-policy.json +4 -4
- package/architecture/release-tail-contract-inventory.json +6 -6
- package/architecture/v4-delivery-authority-parity.json +2 -2
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
- package/architecture/v4-floating-consumer-policy.json +1 -1
- package/architecture/v4-next-development-parity.json +4 -4
- package/architecture/v4-platform-stage-checkpoints.json +1 -1
- package/architecture/v4-publication-facade-patches.json +53 -0
- package/architecture/v4-release-topology.json +46 -29
- package/architecture/v4-rust-wasm-production-authority.json +1 -0
- package/architecture/v4-stage-capsule-qualification.json +1 -1
- package/architecture/v4-universal-workflow-bootstrap.json +1 -1
- package/architecture/v4-universal-workflow-fault-campaign.json +1 -1
- package/architecture/v4-universal-workflow-train-admission.json +4 -4
- package/architecture/workflow-taxonomy.json +1437 -0
- package/contracts/buildchain-v2-residuals-v1.json +64 -1
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
- package/dist/site/buildchain-contract.json +20 -20
- package/dist/site/buildchain-site.json +122 -56
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +5 -4
- package/dist/site/kfd-claims.json +2617 -964
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +10 -10
- package/dist/site/node-api-registry.json +26 -21
- package/dist/site/page-registry.json +107 -41
- package/dist/site/public-surface-audit.json +3196 -1128
- package/dist/site/publication-authority-registry.json +1179 -417
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +14 -14
- package/dist/site/workflow-registry.json +5484 -1924
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +3 -3
- package/docs/dev-delivery-warrant.md +1 -1
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/next-development-transition.md +1 -1
- package/docs/node-api-reference.md +30 -30
- package/docs/publication-authority.md +2 -2
- package/docs/release-candidate.md +3 -3
- package/docs/release-flow.md +67 -19
- package/docs/release-governance.md +8 -8
- package/docs/v4-adopter-delivery.md +1 -1
- package/docs/v4-production-release.md +45 -8
- package/docs/v4-stage-capsule.md +1 -1
- package/docs/workflow-catalog.md +151 -0
- package/package.json +8 -6
- package/packages/core/buildchain-contract.js +1 -1
- package/packages/core/buildchain-kfd-claims.js +2 -2
- package/packages/core/buildchain-publication-authority.js +40 -25
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/controller-evidence.js +4 -1
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/next-development-projection.js +1 -1
- package/packages/core/release-line-dry-run.js +1 -1
- package/packages/core/v4-canonical-contracts.js +1 -0
- package/packages/core/v4-domain-wasm-artifact.js +2 -2
- package/packages/core/v4-floating-consumer-evidence.js +1 -1
- package/packages/core/v4-floating-consumer-policy.js +1 -1
- package/scripts/binary-publication-evidence.mjs +135 -0
- package/scripts/check-ci-lane-change-budget.mjs +4 -1
- package/scripts/check-core-mechanism-inventory.mjs +4 -3
- package/scripts/check-inventory.mjs +22 -22
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +1 -1
- package/scripts/check-v4-public-dogfood-contract.mjs +18 -5
- package/scripts/check-v4-release-topology.mjs +5 -2
- package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +5 -9
- package/scripts/check-workflow-taxonomy.mjs +6 -0
- package/scripts/generate-channel-build-workflow.mjs +2 -1
- package/scripts/generate-channel-promotion-workflow.mjs +4 -3
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +24 -6
- package/scripts/generate-workflow-taxonomy.mjs +25 -0
- package/scripts/maintainability-metrics.mjs +16 -5
- package/scripts/release-asset-client.mjs +90 -0
- package/scripts/release-candidate-resolver.mjs +1 -1
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/v4-publication-settlement.mjs +207 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +1 -1
- package/scripts/verify-version-state-delta.mjs +173 -0
- package/scripts/workflow-taxonomy.mjs +468 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
SETTLEMENT_ASSET,
|
|
7
|
+
verifyPublicationSettlement,
|
|
8
|
+
} from "./v4-publication-settlement.mjs";
|
|
9
|
+
import { releaseAssetClient } from "./release-asset-client.mjs";
|
|
10
|
+
|
|
11
|
+
export async function readBinaryPublicationEvidence({
|
|
12
|
+
client,
|
|
13
|
+
repository,
|
|
14
|
+
tag,
|
|
15
|
+
sourceSha,
|
|
16
|
+
attempts = 40,
|
|
17
|
+
wait = () => new Promise((resolve) => setTimeout(resolve, 15000)),
|
|
18
|
+
}) {
|
|
19
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
20
|
+
let release;
|
|
21
|
+
try {
|
|
22
|
+
release = client.release(tag);
|
|
23
|
+
} catch {
|
|
24
|
+
/* Tag creation can precede Release creation. */
|
|
25
|
+
}
|
|
26
|
+
const assets = release?.assets || [];
|
|
27
|
+
const settlements = assets.filter(({ name }) => name === SETTLEMENT_ASSET);
|
|
28
|
+
const passports = assets.filter(
|
|
29
|
+
({ name }) => name === "buildchain.release.json",
|
|
30
|
+
);
|
|
31
|
+
if (settlements.length > 1 || passports.length > 1)
|
|
32
|
+
throw new Error("ambiguous immutable publication evidence");
|
|
33
|
+
if (settlements.length === 1 && passports.length === 1) {
|
|
34
|
+
const settlement = JSON.parse(client.assetBytes(settlements[0]));
|
|
35
|
+
const publicPassport = JSON.parse(client.assetBytes(passports[0]));
|
|
36
|
+
if (
|
|
37
|
+
settlement.contract !== "buildchain-v4-publication-settlement/v1" ||
|
|
38
|
+
settlement.release.sourceSha !== sourceSha ||
|
|
39
|
+
settlement.release.tag !== tag
|
|
40
|
+
)
|
|
41
|
+
throw new Error("binary publication settlement identity mismatch");
|
|
42
|
+
const tagSha = client.json(
|
|
43
|
+
`repos/${repository}/commits/${encodeURIComponent(tag)}`,
|
|
44
|
+
).sha;
|
|
45
|
+
if (tagSha !== sourceSha)
|
|
46
|
+
throw new Error("binary source does not match the exact release tag");
|
|
47
|
+
verifyPublicationSettlement(settlement.documents, {
|
|
48
|
+
repository,
|
|
49
|
+
tag,
|
|
50
|
+
sourceSha,
|
|
51
|
+
publicPassport,
|
|
52
|
+
});
|
|
53
|
+
return settlement;
|
|
54
|
+
}
|
|
55
|
+
if (attempt + 1 < attempts) await wait();
|
|
56
|
+
}
|
|
57
|
+
throw new Error(
|
|
58
|
+
"completed v4 publication evidence is not available within the bounded wait",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function prepareBinaryAssetPaths({
|
|
63
|
+
binaryDir,
|
|
64
|
+
passportDir,
|
|
65
|
+
outputDir,
|
|
66
|
+
capabilityPath,
|
|
67
|
+
}) {
|
|
68
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
69
|
+
const files = [];
|
|
70
|
+
for (const directory of [binaryDir, passportDir]) {
|
|
71
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
72
|
+
if (!entry.isFile()) continue;
|
|
73
|
+
const source = path.join(directory, entry.name);
|
|
74
|
+
if (
|
|
75
|
+
directory === passportDir &&
|
|
76
|
+
entry.name === "buildchain.release.json"
|
|
77
|
+
) {
|
|
78
|
+
const renamed = path.join(outputDir, "buildchain.binary.release.json");
|
|
79
|
+
fs.copyFileSync(source, renamed);
|
|
80
|
+
files.push(renamed);
|
|
81
|
+
} else files.push(source);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (capabilityPath) {
|
|
85
|
+
const bytes = fs.readFileSync(capabilityPath);
|
|
86
|
+
const digest = crypto.createHash("sha256").update(bytes).digest("hex");
|
|
87
|
+
const retained = path.join(
|
|
88
|
+
outputDir,
|
|
89
|
+
`buildchain.binary.capability-${digest}.json`,
|
|
90
|
+
);
|
|
91
|
+
fs.writeFileSync(retained, bytes);
|
|
92
|
+
files.push(retained);
|
|
93
|
+
}
|
|
94
|
+
return files;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main(mode) {
|
|
98
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
99
|
+
const tag = process.env.RELEASE_TAG;
|
|
100
|
+
const client = releaseAssetClient(repository);
|
|
101
|
+
if (mode === "read") {
|
|
102
|
+
const settlement = await readBinaryPublicationEvidence({
|
|
103
|
+
client,
|
|
104
|
+
repository,
|
|
105
|
+
tag,
|
|
106
|
+
sourceSha: process.env.SOURCE_SHA,
|
|
107
|
+
});
|
|
108
|
+
const directory = ".buildchain/publication-evidence";
|
|
109
|
+
client.write(`${directory}/${SETTLEMENT_ASSET}`, settlement);
|
|
110
|
+
client.write(`${directory}/release.json`, {
|
|
111
|
+
channel: settlement.release.channel,
|
|
112
|
+
});
|
|
113
|
+
} else if (mode === "publish") {
|
|
114
|
+
const release = client.release(tag);
|
|
115
|
+
const files = prepareBinaryAssetPaths({
|
|
116
|
+
binaryDir: "dist/binary",
|
|
117
|
+
passportDir: ".buildchain/release-passport",
|
|
118
|
+
outputDir: ".buildchain/binary-publication",
|
|
119
|
+
capabilityPath: ".buildchain/publication-authority/capability.json",
|
|
120
|
+
});
|
|
121
|
+
const results = await client.publish(release, files);
|
|
122
|
+
client.write(".buildchain/binary-publication/readback.json", {
|
|
123
|
+
repository,
|
|
124
|
+
tag,
|
|
125
|
+
assets: results,
|
|
126
|
+
});
|
|
127
|
+
console.log(JSON.stringify({ published: results.length, repository, tag }));
|
|
128
|
+
} else throw new Error(`unknown binary publication mode: ${mode}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
132
|
+
main(process.argv[2]).catch((error) => {
|
|
133
|
+
console.error(error.message);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
|
+
import { projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
@@ -14,7 +15,7 @@ const policyPath = path.join(
|
|
|
14
15
|
"ci-lane-change-budget.json",
|
|
15
16
|
);
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const physicalWorkflows = fs
|
|
18
19
|
.readdirSync(workflowRoot, { withFileTypes: true })
|
|
19
20
|
.filter((entry) => entry.isFile() && /\.(?:yaml|yml)$/u.test(entry.name))
|
|
20
21
|
.map((entry) => {
|
|
@@ -26,6 +27,8 @@ const workflows = fs
|
|
|
26
27
|
})
|
|
27
28
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
28
29
|
|
|
30
|
+
const workflows = projectWorkflowIdentities(root, physicalWorkflows);
|
|
31
|
+
|
|
29
32
|
const policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
|
|
30
33
|
const baselineRevision = String(policy.baseline?.gitRevision || "").trim();
|
|
31
34
|
if (!/^[0-9a-f]{40}$/u.test(baselineRevision)) {
|
|
@@ -34,9 +34,10 @@ function registrySurfaceIds(root) {
|
|
|
34
34
|
(entry) => `cli:${entry.id}`,
|
|
35
35
|
);
|
|
36
36
|
const workflows = loadJson(root, "dist/site/workflow-registry.json");
|
|
37
|
-
const workflowIds = workflows.workflows.
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const workflowIds = workflows.workflows.flatMap((entry) => [
|
|
38
|
+
`workflow:${entry.id}`,
|
|
39
|
+
...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
|
|
40
|
+
]);
|
|
40
41
|
const actionIds = workflows.actions.map((entry) => `action:${entry.id}`);
|
|
41
42
|
const exports = Object.keys(loadJson(root, "package.json").exports).map(
|
|
42
43
|
(entry) => `export:${entry}`,
|
|
@@ -145,39 +145,39 @@ const requiredPaths = [
|
|
|
145
145
|
".buildchain/alpha-contract-lock.json",
|
|
146
146
|
".buildchain/release-impact.json",
|
|
147
147
|
".github/actionlint.yaml",
|
|
148
|
-
".github/workflows/self-
|
|
149
|
-
".github/workflows/
|
|
150
|
-
".github/workflows/
|
|
151
|
-
".github/workflows/release-line-
|
|
148
|
+
".github/workflows/self-ops-runner-smoke.yml",
|
|
149
|
+
".github/workflows/self-release-promote.yml",
|
|
150
|
+
".github/workflows/self-ops-recovery-failure-dogfood.yml",
|
|
151
|
+
".github/workflows/self-release-line-open.yml",
|
|
152
152
|
".github/workflows/release-governance-reconcile.yml",
|
|
153
153
|
".github/workflows/dev-pr-auto-merge.yml",
|
|
154
|
-
".github/workflows/
|
|
154
|
+
".github/workflows/self-ops-dev-delivery.yml",
|
|
155
155
|
".github/workflows/buildchain-patrol.yml",
|
|
156
156
|
".github/workflows/patrol-daily.yml",
|
|
157
157
|
".github/workflows/patrol-weekly.yml",
|
|
158
158
|
".github/workflows/patrol-monthly.yml",
|
|
159
159
|
".github/workflows/patrol-observed-evidence.yml",
|
|
160
|
-
".github/workflows/
|
|
161
|
-
".github/workflows/
|
|
162
|
-
".github/workflows/
|
|
163
|
-
".github/workflows/
|
|
160
|
+
".github/workflows/self-ops-patrol-daily.yml",
|
|
161
|
+
".github/workflows/self-ops-patrol-weekly.yml",
|
|
162
|
+
".github/workflows/self-ops-patrol-monthly.yml",
|
|
163
|
+
".github/workflows/self-build-alpha-dogfood.yml",
|
|
164
164
|
".github/workflows/release-candidate-promote.yml",
|
|
165
165
|
".github/workflows/.release-candidate-promote.yml",
|
|
166
166
|
".github/workflows/release-propagation.yml",
|
|
167
|
-
".github/workflows/npm-
|
|
167
|
+
".github/workflows/self-release-npm-dry-run.yml",
|
|
168
168
|
".github/workflows/paper-release.yml",
|
|
169
|
-
".github/workflows/binary-distribution.yml",
|
|
169
|
+
".github/workflows/self-build-binary-distribution.yml",
|
|
170
170
|
".github/workflows/.binary-release-assets.yml",
|
|
171
|
-
".github/workflows/
|
|
172
|
-
".github/workflows/verify.yml",
|
|
171
|
+
".github/workflows/self-release-binary-assets.yml",
|
|
172
|
+
".github/workflows/self-build-verify.yml",
|
|
173
173
|
".github/workflows/.build.yml",
|
|
174
174
|
".github/workflows/.gate-profile.yml",
|
|
175
175
|
".github/workflows/.auditable-demo.yml",
|
|
176
176
|
".github/workflows/.declarative-auditable-demo.yml",
|
|
177
|
-
".github/workflows/
|
|
177
|
+
".github/workflows/self-build-demo-dogfood.yml",
|
|
178
178
|
".github/workflows/build.yml",
|
|
179
|
-
".github/workflows/build-
|
|
180
|
-
".github/workflows/candidate-lab.yml",
|
|
179
|
+
".github/workflows/self-build-fixture.yml",
|
|
180
|
+
".github/workflows/self-build-candidate-lab.yml",
|
|
181
181
|
"fixtures/libnode-shaped/buildchain.toml",
|
|
182
182
|
"fixtures/libnode-shaped/.github/workflows/build.yml",
|
|
183
183
|
"fixtures/libnode-shaped/package.json"
|
|
@@ -197,7 +197,7 @@ if (rootPackage.private !== false) {
|
|
|
197
197
|
throw new Error("root package must be publishable with private=false");
|
|
198
198
|
}
|
|
199
199
|
const selfDogfoodWorkflow = fs.readFileSync(
|
|
200
|
-
path.join(root, ".github/workflows/
|
|
200
|
+
path.join(root, ".github/workflows/self-build-alpha-dogfood.yml"),
|
|
201
201
|
"utf8",
|
|
202
202
|
);
|
|
203
203
|
const selfDogfoodAlphaLock = JSON.parse(
|
|
@@ -285,7 +285,7 @@ const channelPromotionWorkflow = fs.readFileSync(
|
|
|
285
285
|
"utf8",
|
|
286
286
|
);
|
|
287
287
|
const boundedAlphaRecoveryWorkflow = fs.readFileSync(
|
|
288
|
-
path.join(root, ".github/workflows/
|
|
288
|
+
path.join(root, ".github/workflows/self-ops-promotion-recovery.yml"),
|
|
289
289
|
"utf8",
|
|
290
290
|
);
|
|
291
291
|
const promotionShellRouting = parsePromotionShellRouting(
|
|
@@ -1000,11 +1000,11 @@ for (const requiredSnippet of [
|
|
|
1000
1000
|
if (commonJsSourcePattern.test(standaloneBinaryScript)) {
|
|
1001
1001
|
throw new Error("scripts/build-standalone-binary.mjs must use ESM syntax");
|
|
1002
1002
|
}
|
|
1003
|
-
const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/npm-
|
|
1004
|
-
const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/
|
|
1005
|
-
const binaryDistributionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/binary-distribution.yml"), "utf8");
|
|
1003
|
+
const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-release-npm-dry-run.yml"), "utf8");
|
|
1004
|
+
const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-release-promote.yml"), "utf8");
|
|
1005
|
+
const binaryDistributionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-build-binary-distribution.yml"), "utf8");
|
|
1006
1006
|
const binaryReleaseAssetsWorkflow = fs.readFileSync(path.join(root, ".github/workflows/.binary-release-assets.yml"), "utf8");
|
|
1007
|
-
const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-
|
|
1007
|
+
const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-ops-runner-smoke.yml"), "utf8");
|
|
1008
1008
|
const npmDryRunScript = fs.readFileSync(path.join(root, "scripts/npm-publish-dry-run.mjs"), "utf8");
|
|
1009
1009
|
const npmPublishTransactionScript = fs.readFileSync(path.join(root, "scripts/npm-publish-transaction.mjs"), "utf8");
|
|
1010
1010
|
const rootPackageJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
@@ -80,7 +80,7 @@ function assertPersistedSelectors() {
|
|
|
80
80
|
const selector = match[2];
|
|
81
81
|
const protectedBootstrap =
|
|
82
82
|
relative ===
|
|
83
|
-
".github/workflows/
|
|
83
|
+
".github/workflows/self-ops-promotion-recovery.yml" &&
|
|
84
84
|
match[1] === ".github/workflows/.release-candidate-promote.yml" &&
|
|
85
85
|
selector === "alpha/v4/v4.0";
|
|
86
86
|
const isV4 =
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ const DEFAULT_ROOT = path.resolve(
|
|
|
8
9
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
9
10
|
"..",
|
|
10
11
|
);
|
|
11
|
-
const CALLER_PATH = ".github/workflows/
|
|
12
|
+
const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
|
|
12
13
|
const REUSABLE_PATH = ".github/workflows/v4-stage-capsule-canary.yml";
|
|
13
14
|
export const V4_PUBLIC_DOGFOOD_ALPHA_REF = "v4-alpha";
|
|
14
15
|
const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
|
|
@@ -93,10 +94,19 @@ function assertReusableWorkflow(root) {
|
|
|
93
94
|
|
|
94
95
|
function assertWorkflowInventory(root) {
|
|
95
96
|
const workflowRoot = path.join(root, ".github/workflows");
|
|
97
|
+
const taxonomy = readWorkflowTaxonomy(root);
|
|
96
98
|
for (const entry of fs.readdirSync(workflowRoot, { withFileTypes: true })) {
|
|
97
99
|
if (!entry.isFile() || !/\.ya?ml$/u.test(entry.name)) continue;
|
|
98
|
-
|
|
100
|
+
let relative = `.github/workflows/${entry.name}`;
|
|
99
101
|
const text = read(root, relative);
|
|
102
|
+
const canonical = taxonomy?.entries.find(
|
|
103
|
+
(item) => workflowPath(item) === relative && item.compatibility,
|
|
104
|
+
);
|
|
105
|
+
if (canonical) {
|
|
106
|
+
if (read(root, canonical.compatibility.path) !== text)
|
|
107
|
+
fail(`${relative} differs from its public compatibility identity`);
|
|
108
|
+
relative = canonical.compatibility.path;
|
|
109
|
+
}
|
|
100
110
|
if (
|
|
101
111
|
relative !== REUSABLE_PATH &&
|
|
102
112
|
text.includes("scripts/v4-stage-capsule-qualification.mjs")
|
|
@@ -113,11 +123,13 @@ function assertWorkflowInventory(root) {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
function assertProtectedVerify(root) {
|
|
116
|
-
const verify = read(root, ".github/workflows/verify.yml");
|
|
126
|
+
const verify = read(root, ".github/workflows/self-build-verify.yml");
|
|
117
127
|
for (const required of [
|
|
118
128
|
"needs: stage-capsule-checkpoints",
|
|
119
|
-
"name:
|
|
120
|
-
"
|
|
129
|
+
"name: Run declared verify lifecycle (full source tests and generated artifact checks)",
|
|
130
|
+
"node .buildchain/runtime/bin/buildchain.mjs lifecycle run verify",
|
|
131
|
+
"run: node scripts/source-verification-evidence.mjs plan",
|
|
132
|
+
"run: node scripts/source-verification-evidence.mjs seal",
|
|
121
133
|
])
|
|
122
134
|
if (!verify.includes(required))
|
|
123
135
|
fail(`Verify is missing protected gate ${required}`);
|
|
@@ -193,6 +205,7 @@ function assertConsumerLifecycle(root) {
|
|
|
193
205
|
'[lifecycle.install]\ncommand = "corepack enable pnpm && corepack pnpm@11.7.0 install --frozen-lockfile"',
|
|
194
206
|
'[lifecycle.build]\ncommand = "corepack pnpm@11.7.0 run build && corepack pnpm@11.7.0 run generate:site"',
|
|
195
207
|
"[lifecycle.verify]\ncommands = [",
|
|
208
|
+
'"corepack pnpm@11.7.0 run check",',
|
|
196
209
|
])
|
|
197
210
|
if (!lifecycle.includes(declaration))
|
|
198
211
|
fail(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
6
|
+
import { workflowCompatibilityIdentity } from "./workflow-taxonomy.mjs";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
9
|
|
|
@@ -21,6 +22,8 @@ function read(relative) {
|
|
|
21
22
|
const LOCAL_MODULE_EXTENSIONS = ["", ".js", ".mjs", ".cjs"];
|
|
22
23
|
const PRIVILEGED_ENTRYPOINTS = [
|
|
23
24
|
"actions/v4-release-candidate-promote/index.js",
|
|
25
|
+
"scripts/binary-publication-evidence.mjs",
|
|
26
|
+
"scripts/v4-publication-settlement.mjs",
|
|
24
27
|
];
|
|
25
28
|
|
|
26
29
|
function localModuleSpecifiers(source) {
|
|
@@ -341,7 +344,7 @@ export function findUnknownV4ReleaseTopology(
|
|
|
341
344
|
) {
|
|
342
345
|
const declared = new Set(workflowPaths);
|
|
343
346
|
return allWorkflowPaths
|
|
344
|
-
.filter((relative) => !declared.has(relative))
|
|
347
|
+
.filter((relative) => !declared.has(workflowCompatibilityIdentity(root, relative, readWorkflow(relative))))
|
|
345
348
|
.filter((relative) => {
|
|
346
349
|
const source = readWorkflow(relative);
|
|
347
350
|
const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
|
|
@@ -431,7 +434,7 @@ function assertAuthorityClosure(ledger) {
|
|
|
431
434
|
);
|
|
432
435
|
assert.equal(
|
|
433
436
|
closure.recoveryEntry,
|
|
434
|
-
".github/workflows/
|
|
437
|
+
".github/workflows/self-ops-promotion-recovery.yml",
|
|
435
438
|
);
|
|
436
439
|
const engineSurface = [
|
|
437
440
|
".github/workflows/.release-candidate-promote.yml",
|
|
@@ -35,7 +35,10 @@ function publicSurfaceIds(root) {
|
|
|
35
35
|
return new Set([
|
|
36
36
|
...Object.keys(packageJson.exports || {}).map((id) => `export:${id}`),
|
|
37
37
|
...(cli.commands || []).map((entry) => `cli:${entry.id}`),
|
|
38
|
-
...(workflows.workflows || []).
|
|
38
|
+
...(workflows.workflows || []).flatMap((entry) => [
|
|
39
|
+
`workflow:${entry.id}`,
|
|
40
|
+
...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
|
|
41
|
+
]),
|
|
39
42
|
...(workflows.actions || []).map((entry) => `action:${entry.id}`),
|
|
40
43
|
]);
|
|
41
44
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
|
|
2
3
|
import crypto from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
@@ -25,16 +26,11 @@ const fileRoot = (relative) =>
|
|
|
25
26
|
.digest("hex")}`;
|
|
26
27
|
|
|
27
28
|
const workflowRoot = path.join(root, ".github/workflows");
|
|
28
|
-
const discovered = fs
|
|
29
|
-
.readdirSync(workflowRoot)
|
|
29
|
+
const discovered = projectWorkflowIdentities(root, fs.readdirSync(workflowRoot)
|
|
30
30
|
.filter((name) => /\.ya?ml$/u.test(name))
|
|
31
|
-
.map((name) => `.github/workflows/${name}
|
|
32
|
-
.filter((
|
|
33
|
-
|
|
34
|
-
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
35
|
-
),
|
|
36
|
-
)
|
|
37
|
-
.sort();
|
|
31
|
+
.map((name) => ({ path: `.github/workflows/${name}`, text: fs.readFileSync(path.join(workflowRoot, name), "utf8") })))
|
|
32
|
+
.filter((entry) => /(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(entry.text))
|
|
33
|
+
.map((entry) => entry.path).sort();
|
|
38
34
|
|
|
39
35
|
assert.equal(
|
|
40
36
|
contract.schema,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
@@ -421,7 +422,7 @@ function main() {
|
|
|
421
422
|
}
|
|
422
423
|
return;
|
|
423
424
|
}
|
|
424
|
-
|
|
425
|
+
writeWorkflowSource(root, path.relative(root, targetPath), generated);
|
|
425
426
|
}
|
|
426
427
|
|
|
427
428
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
@@ -215,9 +216,9 @@ function consumerAdmissionJob() {
|
|
|
215
216
|
id: policy
|
|
216
217
|
env:
|
|
217
218
|
BUILDCHAIN_CONSUMER_ROOT: .buildchain/consumer
|
|
218
|
-
BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/
|
|
219
|
+
BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/self-ops-promotion-recovery.yml' && '.github/workflows/release-candidate-promote.yml' || inputs.publication-publisher-workflow-path }}
|
|
219
220
|
BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: \${{ needs.resolve-promotion.outputs.channel }}
|
|
220
|
-
BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/
|
|
221
|
+
BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/self-ops-promotion-recovery.yml' && '.github/workflows/.release-candidate-promote.yml' || '.github/workflows/release-candidate-promote.yml' }}
|
|
221
222
|
BUILDCHAIN_WORKFLOW_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
|
|
222
223
|
BUILDCHAIN_RUNTIME_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
|
|
223
224
|
BUILDCHAIN_STABLE_CONTRACT_LOCK_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}
|
|
@@ -532,7 +533,7 @@ function main() {
|
|
|
532
533
|
}
|
|
533
534
|
return;
|
|
534
535
|
}
|
|
535
|
-
|
|
536
|
+
writeWorkflowSource(root, path.relative(root, targetPath), generated);
|
|
536
537
|
}
|
|
537
538
|
|
|
538
539
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import crypto from "node:crypto";
|
|
@@ -755,9 +756,7 @@ function buildSiteBundle() {
|
|
|
755
756
|
timestampFieldsParticipateInArtifactDigest: true,
|
|
756
757
|
artifactDigestScope: "npm package dist/site JSON files",
|
|
757
758
|
});
|
|
758
|
-
|
|
759
759
|
const cliRegistry = createCliRegistry(packageJson);
|
|
760
|
-
|
|
761
760
|
const manualRegistry = {
|
|
762
761
|
schemaVersion: 1,
|
|
763
762
|
contract: "kungfu-buildchain-agent-manual-registry",
|
|
@@ -805,7 +804,6 @@ function buildSiteBundle() {
|
|
|
805
804
|
],
|
|
806
805
|
guidance: "Agent consumers should use this registry to find packaged Buildchain manuals before inferring behavior from workflow snippets or release notes.",
|
|
807
806
|
};
|
|
808
|
-
|
|
809
807
|
const pageRegistry = {
|
|
810
808
|
schemaVersion: 1,
|
|
811
809
|
contract: "kungfu-buildchain-site-page-registry",
|
|
@@ -821,14 +819,15 @@ function buildSiteBundle() {
|
|
|
821
819
|
capabilityGroups: CAPABILITY_GROUPS.map((group) => group.id),
|
|
822
820
|
pages,
|
|
823
821
|
};
|
|
824
|
-
|
|
825
822
|
const nodeApiRegistry = createSiteNodeApiRegistry({ root, packageJson, nodeApiMeta, sha256File, publicSurfaceLifecycle });
|
|
826
|
-
|
|
823
|
+
const taxonomy = readWorkflowTaxonomy(root);
|
|
827
824
|
const workflowRegistry = {
|
|
828
825
|
schemaVersion: 1,
|
|
829
826
|
contract: "kungfu-buildchain-workflow-registry",
|
|
830
827
|
workflowSource: ".github/workflows reverse input enumeration",
|
|
831
828
|
workflows: enumerateWorkflowInputs({ root }).map((entry) => {
|
|
829
|
+
const classification = taxonomy?.entries.find((item) => workflowPath(item) === entry.path || item.compatibility?.path === entry.path);
|
|
830
|
+
const semanticId = classification?.id || entry.id;
|
|
832
831
|
const surfaceById = new Map([
|
|
833
832
|
["build", "channel-build-router"],
|
|
834
833
|
[".build", "reusable-build"],
|
|
@@ -878,18 +877,19 @@ function buildSiteBundle() {
|
|
|
878
877
|
["engineering-housekeeper-monthly", "preview"],
|
|
879
878
|
["self-hosted-runner-smoke", "compatibility-fixture"],
|
|
880
879
|
]);
|
|
881
|
-
const engineeringHousekeeper =
|
|
880
|
+
const engineeringHousekeeper = semanticId.startsWith("engineering-housekeeper");
|
|
882
881
|
return {
|
|
883
882
|
...entry,
|
|
884
|
-
|
|
883
|
+
...(classification ? { taxonomy: { id: classification.id, role: classification.role, category: classification.category, purpose: classification.purpose, status: classification.status, canonicalPath: workflowPath(classification), ...(classification.role === "self" && classification.migration ? { contractPath: classification.migration.previousPath } : {}), compatibilityAlias: entry.path !== workflowPath(classification) } } : {}),
|
|
884
|
+
surface: surfaceById.get(semanticId) || (entry.path.includes("/.") ? "reusable-workflow" : "repository-workflow"),
|
|
885
885
|
capabilityGroup: workflowCapabilityGroup({
|
|
886
|
-
...entry,
|
|
887
|
-
status: statusById.get(
|
|
886
|
+
...entry, id: semanticId,
|
|
887
|
+
status: statusById.get(semanticId) || "active",
|
|
888
888
|
}),
|
|
889
|
-
status: statusById.get(
|
|
889
|
+
status: statusById.get(semanticId) || "active",
|
|
890
890
|
...publicSurfaceLifecycle({
|
|
891
891
|
owner: "buildchain-workflows",
|
|
892
|
-
maturity: statusById.get(
|
|
892
|
+
maturity: statusById.get(semanticId) || "active",
|
|
893
893
|
introducedVersion: engineeringHousekeeper ? packageJson.version : undefined,
|
|
894
894
|
nonDuplicationRationale: engineeringHousekeeper
|
|
895
895
|
? "One reusable policy and evidence boundary owns Engineering Housekeeper execution; scheduled callers contain cadence values only."
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { writeWorkflowSource, rewriteRepositoryWorkflowPaths } from "./workflow-taxonomy.mjs";
|
|
3
5
|
import path from "node:path";
|
|
4
6
|
import { execFileSync } from "node:child_process";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -198,11 +200,27 @@ function addRuntimeBootstrapDependencies(source, relative) {
|
|
|
198
200
|
);
|
|
199
201
|
}
|
|
200
202
|
|
|
203
|
+
function applyPublicationFacadePatches(source, relative) {
|
|
204
|
+
const document = JSON.parse(fs.readFileSync(path.join(root,
|
|
205
|
+
"architecture/v4-publication-facade-patches.json"), "utf8"));
|
|
206
|
+
const patch = document.facades.find(entry => entry.workflow === relative);
|
|
207
|
+
if (!patch) return source;
|
|
208
|
+
const digest = value => crypto.createHash("sha256").update(value).digest("hex");
|
|
209
|
+
if (digest(source) === patch.afterSha256) return source;
|
|
210
|
+
if (digest(source) !== patch.beforeSha256) fail(`${relative}: publication facade source drift`);
|
|
211
|
+
for (const { before, after } of patch.replacements) {
|
|
212
|
+
if (source.split(before).length !== 2) fail(`${relative}: ambiguous publication patch`);
|
|
213
|
+
source = source.replace(before, () => after);
|
|
214
|
+
}
|
|
215
|
+
if (digest(source) !== patch.afterSha256) fail(`${relative}: publication facade result drift`);
|
|
216
|
+
return source;
|
|
217
|
+
}
|
|
218
|
+
|
|
201
219
|
export function migrateV4UniversalWorkflowFacade(source, relative) {
|
|
202
|
-
return guardCompatibilityJobs(
|
|
220
|
+
return rewriteRepositoryWorkflowPaths(root, applyPublicationFacadePatches(guardCompatibilityJobs(
|
|
203
221
|
addUniversalInput(addRuntimeBootstrapDependencies(source, relative), relative),
|
|
204
222
|
relative,
|
|
205
|
-
);
|
|
223
|
+
), relative));
|
|
206
224
|
}
|
|
207
225
|
|
|
208
226
|
function verify(source, relative) {
|
|
@@ -402,8 +420,8 @@ function main() {
|
|
|
402
420
|
"consumer recovery workflow differs from its pre-positioned template",
|
|
403
421
|
);
|
|
404
422
|
} else {
|
|
405
|
-
|
|
406
|
-
recoveryWorkflowPath,
|
|
423
|
+
writeWorkflowSource(
|
|
424
|
+
root, path.relative(root, recoveryWorkflowPath),
|
|
407
425
|
fs.readFileSync(recoveryTemplatePath, "utf8"),
|
|
408
426
|
);
|
|
409
427
|
}
|
|
@@ -414,8 +432,8 @@ function main() {
|
|
|
414
432
|
: fs.readFileSync(target, "utf8");
|
|
415
433
|
if (check) verify(source, relative);
|
|
416
434
|
else
|
|
417
|
-
|
|
418
|
-
|
|
435
|
+
writeWorkflowSource(
|
|
436
|
+
root, relative,
|
|
419
437
|
migrateV4UniversalWorkflowFacade(source, relative),
|
|
420
438
|
);
|
|
421
439
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
readWorkflowTaxonomy,
|
|
6
|
+
renderWorkflowCatalog,
|
|
7
|
+
TAXONOMY_DOC,
|
|
8
|
+
workflowPath,
|
|
9
|
+
writeWorkflowSource,
|
|
10
|
+
} from "./workflow-taxonomy.mjs";
|
|
11
|
+
|
|
12
|
+
const root = process.cwd();
|
|
13
|
+
const policy = readWorkflowTaxonomy(root);
|
|
14
|
+
if (!policy) throw new Error("workflow taxonomy is missing");
|
|
15
|
+
for (const entry of policy.entries) {
|
|
16
|
+
writeWorkflowSource(
|
|
17
|
+
root,
|
|
18
|
+
workflowPath(entry),
|
|
19
|
+
fs.readFileSync(path.join(root, workflowPath(entry)), "utf8"),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(path.join(root, TAXONOMY_DOC), renderWorkflowCatalog(policy));
|
|
23
|
+
process.stdout.write(
|
|
24
|
+
`Generated ${policy.entries.length} canonical workflow declarations and their exact compatibility projections.\n`,
|
|
25
|
+
);
|