@kungfu-tech/buildchain 2.11.14-alpha.7 → 2.12.0-alpha.0
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 -12
- package/README.md +3 -1
- package/dist/site/buildchain-contract.json +44 -5
- package/dist/site/buildchain-site.json +26 -21
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/kfd-claims.json +74 -6
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +4 -4
- package/dist/site/node-api-registry.json +1 -1
- package/dist/site/page-registry.json +17 -12
- package/dist/site/public-surface-audit.json +63 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +8 -8
- package/dist/site/workflow-registry.json +64 -1
- package/docs/MAP.md +1 -0
- package/docs/release-governance.md +12 -9
- package/docs/reusable-build-surface.md +63 -3
- package/docs/versioning.md +1 -0
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +30 -0
- package/scripts/buildchain-channel-router.mjs +223 -0
- package/scripts/check-inventory.mjs +52 -3
- package/scripts/generate-channel-build-workflow.mjs +101 -0
- package/scripts/generate-site-bundle.mjs +2 -1
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
assertPublicSurfaceReverseAudit,
|
|
5
5
|
collectPublicSurfaceReverseAudit,
|
|
6
6
|
} from "../packages/core/public-surface-audit.js";
|
|
7
|
+
import { evaluateBuildchainContractLock } from "../packages/core/buildchain-contract.js";
|
|
8
|
+
import { generateChannelBuildWorkflow } from "./generate-channel-build-workflow.mjs";
|
|
7
9
|
|
|
8
10
|
const root = process.cwd();
|
|
9
11
|
const sharedActionTsupConfig = fs.readFileSync(path.join(root, "scripts/tsup-action.config.mjs"), "utf8");
|
|
@@ -38,6 +40,8 @@ const requiredPaths = [
|
|
|
38
40
|
"docs/toolkit-observability.md",
|
|
39
41
|
"docs/versioning.md",
|
|
40
42
|
"scripts/release-line-dry-run.mjs",
|
|
43
|
+
"scripts/buildchain-channel-router.mjs",
|
|
44
|
+
"scripts/generate-channel-build-workflow.mjs",
|
|
41
45
|
"scripts/build-standalone-binary.mjs",
|
|
42
46
|
"scripts/create-release-bundle.mjs",
|
|
43
47
|
"scripts/ensure-github-release.mjs",
|
|
@@ -58,6 +62,7 @@ const requiredPaths = [
|
|
|
58
62
|
"tests/buildchain-inventory.json",
|
|
59
63
|
".buildchain/buildchain.toml",
|
|
60
64
|
".buildchain/contract-lock.json",
|
|
65
|
+
".buildchain/alpha-contract-lock.json",
|
|
61
66
|
".buildchain/release-impact.json",
|
|
62
67
|
".github/actionlint.yaml",
|
|
63
68
|
".github/workflows/self-hosted-runner-smoke.yml",
|
|
@@ -79,6 +84,7 @@ const requiredPaths = [
|
|
|
79
84
|
".github/workflows/binary-distribution.yml",
|
|
80
85
|
".github/workflows/verify.yml",
|
|
81
86
|
".github/workflows/.build.yml",
|
|
87
|
+
".github/workflows/build.yml",
|
|
82
88
|
".github/workflows/build-surface-fixture.yml",
|
|
83
89
|
".github/workflows/candidate-lab.yml",
|
|
84
90
|
"fixtures/libnode-shaped/buildchain.toml",
|
|
@@ -102,14 +108,39 @@ const selfDogfoodWorkflow = fs.readFileSync(
|
|
|
102
108
|
path.join(root, ".github/workflows/buildchain-alpha-self-dogfood.yml"),
|
|
103
109
|
"utf8",
|
|
104
110
|
);
|
|
111
|
+
const selfDogfoodAlphaLock = JSON.parse(
|
|
112
|
+
fs.readFileSync(path.join(root, ".buildchain/alpha-contract-lock.json"), "utf8"),
|
|
113
|
+
);
|
|
114
|
+
const currentBuildchainContract = JSON.parse(
|
|
115
|
+
fs.readFileSync(path.join(root, "dist/site/buildchain-contract.json"), "utf8"),
|
|
116
|
+
);
|
|
105
117
|
const selfDogfoodMajor = String(rootPackage.version || "").match(/^(\d+)\./)?.[1];
|
|
106
118
|
if (!selfDogfoodMajor) {
|
|
107
119
|
throw new Error("root package version must expose a numeric major for self-dogfood");
|
|
108
120
|
}
|
|
121
|
+
if (selfDogfoodAlphaLock.buildchain?.ref !== `v${selfDogfoodMajor}-alpha`) {
|
|
122
|
+
throw new Error("Buildchain self-dogfood alpha lock must target the current major alpha ref");
|
|
123
|
+
}
|
|
124
|
+
if (!/^[0-9a-f]{40}$/.test(selfDogfoodAlphaLock.buildchain?.resolvedSha || "")) {
|
|
125
|
+
throw new Error("Buildchain self-dogfood alpha lock must record an exact accepted SHA");
|
|
126
|
+
}
|
|
127
|
+
if (selfDogfoodAlphaLock.buildchain?.compatibilityPolicy !== "major-compatible") {
|
|
128
|
+
throw new Error("Buildchain self-dogfood alpha lock must enforce major-compatible policy");
|
|
129
|
+
}
|
|
130
|
+
const selfDogfoodAlphaEvaluation = evaluateBuildchainContractLock({
|
|
131
|
+
lock: selfDogfoodAlphaLock,
|
|
132
|
+
current: currentBuildchainContract,
|
|
133
|
+
runtimeRef: `v${selfDogfoodMajor}-alpha`,
|
|
134
|
+
runtimeSha: "current-development-contract",
|
|
135
|
+
runtimeClass: "alpha",
|
|
136
|
+
});
|
|
137
|
+
if (!selfDogfoodAlphaEvaluation.compatible) {
|
|
138
|
+
throw new Error("Buildchain self-dogfood alpha lock requires review after a breaking contract change");
|
|
139
|
+
}
|
|
109
140
|
for (const requiredSnippet of [
|
|
110
|
-
`/.github/workflows
|
|
111
|
-
|
|
112
|
-
|
|
141
|
+
`/.github/workflows/build.yml@v${selfDogfoodMajor}-alpha`,
|
|
142
|
+
"buildchain-channel: auto",
|
|
143
|
+
"buildchain-channel: stable",
|
|
113
144
|
`const alphaRef = "v${selfDogfoodMajor}-alpha"`,
|
|
114
145
|
`const stableRef = "v${selfDogfoodMajor}"`,
|
|
115
146
|
]) {
|
|
@@ -130,6 +161,23 @@ for (const requiredSnippet of [
|
|
|
130
161
|
throw new Error(`reusable build workflow missing called-workflow identity: ${requiredSnippet}`);
|
|
131
162
|
}
|
|
132
163
|
}
|
|
164
|
+
const channelBuildWorkflow = fs.readFileSync(
|
|
165
|
+
path.join(root, ".github/workflows/build.yml"),
|
|
166
|
+
"utf8",
|
|
167
|
+
);
|
|
168
|
+
if (channelBuildWorkflow !== generateChannelBuildWorkflow(reusableBuildWorkflow)) {
|
|
169
|
+
throw new Error("generated channel build workflow is stale");
|
|
170
|
+
}
|
|
171
|
+
for (const requiredSnippet of [
|
|
172
|
+
"buildchain-channel:",
|
|
173
|
+
"uses: ./.github/workflows/.build.yml",
|
|
174
|
+
"needs.resolve-channel.outputs.buildchain-ref",
|
|
175
|
+
"needs.resolve-channel.outputs.contract-lock-path",
|
|
176
|
+
]) {
|
|
177
|
+
if (!channelBuildWorkflow.includes(requiredSnippet)) {
|
|
178
|
+
throw new Error(`channel build workflow missing routing contract: ${requiredSnippet}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
133
181
|
for (const requiredSnippet of [
|
|
134
182
|
"group: buildchain-release-promotion-${{ github.repository }}",
|
|
135
183
|
"cancel-in-progress: false",
|
|
@@ -143,6 +191,7 @@ const actionlintConfig = fs.readFileSync(
|
|
|
143
191
|
"utf8",
|
|
144
192
|
);
|
|
145
193
|
for (const requiredSnippet of [
|
|
194
|
+
".github/workflows/build.yml:",
|
|
146
195
|
".github/workflows/.build.yml:",
|
|
147
196
|
'property "workflow_ref" is not defined in object type',
|
|
148
197
|
]) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const sourcePath = path.join(root, ".github/workflows/.build.yml");
|
|
9
|
+
const targetPath = path.join(root, ".github/workflows/build.yml");
|
|
10
|
+
|
|
11
|
+
function blockBetween(source, start, end) {
|
|
12
|
+
const startIndex = source.indexOf(start);
|
|
13
|
+
const endIndex = source.indexOf(end, startIndex + start.length);
|
|
14
|
+
if (startIndex < 0 || endIndex < 0) throw new Error(`unable to locate workflow block: ${start.trim()}`);
|
|
15
|
+
return source.slice(startIndex + start.length, endIndex);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function inputNames(inputBlock) {
|
|
19
|
+
return [...inputBlock.matchAll(/^ ([a-z0-9-]+):$/gm)].map((match) => match[1]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function routerInputs(inputBlock) {
|
|
23
|
+
const adjusted = inputBlock.replace(
|
|
24
|
+
/( buildchain-contract-lock-path:\n(?: .*\n)*? default:) "buildchain\.contract-lock\.json"/,
|
|
25
|
+
'$1 ""',
|
|
26
|
+
);
|
|
27
|
+
return [
|
|
28
|
+
" buildchain-channel:",
|
|
29
|
+
' description: "Buildchain runtime channel: auto, alpha, or stable"',
|
|
30
|
+
' default: "auto"',
|
|
31
|
+
" type: string",
|
|
32
|
+
" required: false",
|
|
33
|
+
" buildchain-alpha-contract-lock-path:",
|
|
34
|
+
' description: "Consumer-owned contract lock selected for the alpha channel"',
|
|
35
|
+
' default: ".buildchain/alpha-contract-lock.json"',
|
|
36
|
+
" type: string",
|
|
37
|
+
" required: false",
|
|
38
|
+
" buildchain-stable-contract-lock-path:",
|
|
39
|
+
' description: "Consumer-owned contract lock selected for the stable channel"',
|
|
40
|
+
' default: ".buildchain/contract-lock.json"',
|
|
41
|
+
" type: string",
|
|
42
|
+
" required: false",
|
|
43
|
+
adjusted.trimEnd(),
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function routerOutputs(outputBlock) {
|
|
48
|
+
const forwarded = outputBlock.replace(/^ value: \$\{\{ jobs\.[^.]+\.outputs\.([^ }]+) \}\}$/gm, " value: ${{ jobs.build.outputs.$1 }}");
|
|
49
|
+
return [
|
|
50
|
+
" buildchain-channel:",
|
|
51
|
+
' description: "Resolved Buildchain channel: alpha, stable, or override"',
|
|
52
|
+
" value: ${{ jobs.resolve-channel.outputs.channel }}",
|
|
53
|
+
" buildchain-channel-selection-source:",
|
|
54
|
+
' description: "Evidence source used to select the Buildchain channel"',
|
|
55
|
+
" value: ${{ jobs.resolve-channel.outputs.selection-source }}",
|
|
56
|
+
" buildchain-channel-reason:",
|
|
57
|
+
' description: "Human-readable Buildchain channel selection reason"',
|
|
58
|
+
" value: ${{ jobs.resolve-channel.outputs.reason }}",
|
|
59
|
+
forwarded.trimEnd(),
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function forwardedInputs(names) {
|
|
64
|
+
return names
|
|
65
|
+
.map((name) => {
|
|
66
|
+
if (name === "buildchain-ref") return ` buildchain-ref: \${{ needs.resolve-channel.outputs.buildchain-ref }}`;
|
|
67
|
+
if (name === "buildchain-contract-lock-path") {
|
|
68
|
+
return " buildchain-contract-lock-path: ${{ needs.resolve-channel.outputs.contract-lock-path }}";
|
|
69
|
+
}
|
|
70
|
+
return ` ${name}: \${{ inputs.${name} }}`;
|
|
71
|
+
})
|
|
72
|
+
.join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function generateChannelBuildWorkflow(source) {
|
|
76
|
+
const inputs = blockBetween(source, " inputs:\n", " secrets:\n");
|
|
77
|
+
const secrets = blockBetween(source, " secrets:\n", " outputs:\n");
|
|
78
|
+
const outputs = blockBetween(source, " outputs:\n", "\npermissions:\n");
|
|
79
|
+
const names = inputNames(inputs);
|
|
80
|
+
if (!names.includes("buildchain-ref") || !names.includes("publish-channel")) {
|
|
81
|
+
throw new Error("source build workflow is missing channel-router inputs");
|
|
82
|
+
}
|
|
83
|
+
return `# Generated by scripts/generate-channel-build-workflow.mjs. Do not edit directly.\nname: Buildchain Channel Build\n\non:\n workflow_call:\n inputs:\n${routerInputs(inputs)}\n secrets:\n${secrets.trimEnd()}\n outputs:\n${routerOutputs(outputs)}\n\npermissions:\n contents: read\n issues: write\n id-token: write\n\njobs:\n resolve-channel:\n name: Resolve Buildchain channel\n runs-on: ubuntu-24.04\n outputs:\n channel: \${{ steps.channel.outputs.channel }}\n buildchain-ref: \${{ steps.channel.outputs.buildchain-ref }}\n contract-lock-path: \${{ steps.lock.outputs.path }}\n selection-source: \${{ steps.channel.outputs.selection-source }}\n reason: \${{ steps.channel.outputs.reason }}\n steps:\n - name: Resolve router source\n id: router\n shell: bash\n env:\n BUILDCHAIN_ROUTER_WORKFLOW_REF: \${{ job.workflow_ref }}\n run: |\n set -euo pipefail\n workflow_ref=\"\${BUILDCHAIN_ROUTER_WORKFLOW_REF}\"\n repository=\"\${workflow_ref%%/.github/workflows/*}\"\n ref=\"\${workflow_ref##*@}\"\n ref=\"\${ref#refs/heads/}\"\n ref=\"\${ref#refs/tags/}\"\n if [[ -z \"\${repository}\" || \"\${repository}\" = \"\${workflow_ref}\" || -z \"\${ref}\" ]]; then\n echo \"Unable to resolve Buildchain router source from job.workflow_ref=\${workflow_ref}\" >&2\n exit 1\n fi\n {\n echo \"repository=\${repository}\"\n echo \"ref=\${ref}\"\n } >> \"\${GITHUB_OUTPUT}\"\n\n - name: Checkout Buildchain router\n uses: actions/checkout@v7.0.0\n with:\n repository: \${{ steps.router.outputs.repository }}\n ref: \${{ steps.router.outputs.ref }}\n path: .buildchain/router\n persist-credentials: false\n\n - name: Resolve channel\n id: channel\n shell: bash\n env:\n BUILDCHAIN_CHANNEL: \${{ inputs.buildchain-channel }}\n BUILDCHAIN_REQUESTED_REF: \${{ inputs.buildchain-ref }}\n BUILDCHAIN_PUBLISH_CHANNEL: \${{ inputs.publish-channel }}\n BUILDCHAIN_EVENT_NAME: \${{ github.event_name }}\n BUILDCHAIN_GIT_REF: \${{ github.ref }}\n BUILDCHAIN_RELEASE_PRERELEASE: \${{ github.event.release.prerelease }}\n BUILDCHAIN_ROUTER_REF: \${{ steps.router.outputs.ref }}\n run: |\n node .buildchain/router/scripts/buildchain-channel-router.mjs \\\n --cwd .buildchain/router \\\n --channel \"\${BUILDCHAIN_CHANNEL}\" \\\n --buildchain-ref \"\${BUILDCHAIN_REQUESTED_REF}\" \\\n --publish-channel \"\${BUILDCHAIN_PUBLISH_CHANNEL}\" \\\n --event-name \"\${BUILDCHAIN_EVENT_NAME}\" \\\n --ref \"\${BUILDCHAIN_GIT_REF}\" \\\n --release-prerelease \"\${BUILDCHAIN_RELEASE_PRERELEASE}\" \\\n --router-ref \"\${BUILDCHAIN_ROUTER_REF}\"\n\n - name: Select consumer contract lock\n id: lock\n shell: bash\n env:\n EXPLICIT_PATH: \${{ inputs.buildchain-contract-lock-path }}\n ALPHA_PATH: \${{ inputs.buildchain-alpha-contract-lock-path }}\n STABLE_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}\n CHANNEL: \${{ steps.channel.outputs.channel }}\n run: |\n set -euo pipefail\n path=\"\${EXPLICIT_PATH}\"\n if [[ -z \"\${path}\" ]]; then\n if [[ \"\${CHANNEL}\" = \"alpha\" ]]; then\n path=\"\${ALPHA_PATH}\"\n else\n path=\"\${STABLE_PATH}\"\n fi\n fi\n echo \"path=\${path}\" >> \"\${GITHUB_OUTPUT}\"\n\n build:\n name: Build with resolved channel\n needs: resolve-channel\n uses: ./.github/workflows/.build.yml\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n${forwardedInputs(names)}\n secrets: inherit\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function main() {
|
|
87
|
+
const source = fs.readFileSync(sourcePath, "utf8");
|
|
88
|
+
const generated = generateChannelBuildWorkflow(source);
|
|
89
|
+
if (process.argv.includes("--check")) {
|
|
90
|
+
const current = fs.existsSync(targetPath) ? fs.readFileSync(targetPath, "utf8") : "";
|
|
91
|
+
if (current !== generated) {
|
|
92
|
+
console.error(".github/workflows/build.yml is stale; run node scripts/generate-channel-build-workflow.mjs");
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
fs.writeFileSync(targetPath, generated);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
101
|
+
if (isMain) main();
|
|
@@ -813,7 +813,8 @@ function buildSiteBundle() {
|
|
|
813
813
|
workflowSource: ".github/workflows reverse input enumeration",
|
|
814
814
|
workflows: enumerateWorkflowInputs({ root }).map((entry) => {
|
|
815
815
|
const surfaceById = new Map([
|
|
816
|
-
["build", "
|
|
816
|
+
["build", "channel-build-router"],
|
|
817
|
+
[".build", "reusable-build"],
|
|
817
818
|
["web-surface", "site-app-deployment"],
|
|
818
819
|
["buildchain-ref-promotion", "release-governance"],
|
|
819
820
|
["release-line-bootstrap", "release-governance"],
|