@kungfu-tech/buildchain 4.0.8 → 4.0.9-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 -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 +34 -2
- package/architecture/internal-capabilities.json +17 -4
- package/architecture/maintainability-debt.json +34 -123
- package/architecture/maintainability-policy.json +19 -210
- package/architecture/release-topology.json +6 -5
- package/architecture/universal-workflow-bootstrap.json +5 -4
- package/architecture/universal-workflow-train-admission.json +2 -2
- 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 +39 -144
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +1 -1
- package/dist/site/controller-registry.json +30 -868
- package/dist/site/kfd-claims.json +132 -394
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +34 -34
- package/dist/site/page-registry.json +30 -135
- package/dist/site/public-surface-audit.json +81 -401
- package/dist/site/publication-registry.json +2 -2
- package/dist/site/site-manifest.json +8 -8
- package/dist/site/workflow-registry.json +135 -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 +79 -79
- package/docs/reusable-build-surface.md +159 -1430
- package/docs/runtime-train-validation.md +45 -172
- 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/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 +81 -54
- 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,429 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
import fs from "node:fs";
|
|
4
|
-
import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
|
|
5
3
|
import path from "node:path";
|
|
6
4
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
-
const sourcePath = path.join(root, ".github/workflows/.build.yml");
|
|
11
|
-
const targetPath = path.join(root, ".github/workflows/build.yml");
|
|
12
|
-
|
|
13
|
-
function blockBetween(source, start, end) {
|
|
14
|
-
const startIndex = source.indexOf(start);
|
|
15
|
-
const endIndex = source.indexOf(end, startIndex + start.length);
|
|
16
|
-
if (startIndex < 0 || endIndex < 0) throw new Error(`unable to locate workflow block: ${start.trim()}`);
|
|
17
|
-
return source.slice(startIndex + start.length, endIndex);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function inputNames(inputBlock) {
|
|
21
|
-
return [...inputBlock.matchAll(/^ ([a-z0-9-]+):$/gm)].map((match) => match[1]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function routerInputs(inputBlock) {
|
|
25
|
-
const adjusted = inputBlock.replace(
|
|
26
|
-
/( buildchain-contract-lock-path:\n(?: .*\n)*? default:) "buildchain\.contract-lock\.json"/,
|
|
27
|
-
'$1 ""',
|
|
28
|
-
);
|
|
29
|
-
return [
|
|
30
|
-
" buildchain-channel:",
|
|
31
|
-
' description: "Buildchain runtime channel: auto, alpha, or stable"',
|
|
32
|
-
' default: "auto"',
|
|
33
|
-
" type: string",
|
|
34
|
-
" required: false",
|
|
35
|
-
" buildchain-alpha-contract-lock-path:",
|
|
36
|
-
' description: "Consumer-owned contract lock selected for the alpha channel"',
|
|
37
|
-
' default: ".buildchain/alpha-contract-lock.json"',
|
|
38
|
-
" type: string",
|
|
39
|
-
" required: false",
|
|
40
|
-
" buildchain-stable-contract-lock-path:",
|
|
41
|
-
' description: "Consumer-owned contract lock selected for the stable channel"',
|
|
42
|
-
' default: ".buildchain/contract-lock.json"',
|
|
43
|
-
" type: string",
|
|
44
|
-
" required: false",
|
|
45
|
-
adjusted.trimEnd(),
|
|
46
|
-
].join("\n");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function routerOutputs(outputBlock) {
|
|
50
|
-
const forwarded = outputBlock
|
|
51
|
-
.replace(/^ value: \$\{\{ jobs\.[^.]+\.outputs\.([^ }]+) \}\}$/gm, " value: ${{ jobs.override.outputs.$1 || jobs.alpha.outputs.$1 || jobs.stable.outputs.$1 }}")
|
|
52
|
-
.replaceAll("build-lifecycle", "build-channel-router")
|
|
53
|
-
.replace("value: ${{ jobs.override.outputs.controller-plan-artifact || jobs.alpha.outputs.controller-plan-artifact || jobs.stable.outputs.controller-plan-artifact }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-artifact }}")
|
|
54
|
-
.replace("value: ${{ jobs.override.outputs.controller-plan-json || jobs.alpha.outputs.controller-plan-json || jobs.stable.outputs.controller-plan-json }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-json }}")
|
|
55
|
-
.replace("value: ${{ jobs.override.outputs.controller-plan-digest || jobs.alpha.outputs.controller-plan-digest || jobs.stable.outputs.controller-plan-digest }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-digest }}")
|
|
56
|
-
.replace("value: ${{ jobs.override.outputs.controller-receipt-artifact || jobs.alpha.outputs.controller-receipt-artifact || jobs.stable.outputs.controller-receipt-artifact }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-artifact }}")
|
|
57
|
-
.replace("value: ${{ jobs.override.outputs.controller-receipt-json || jobs.alpha.outputs.controller-receipt-json || jobs.stable.outputs.controller-receipt-json }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-json }}")
|
|
58
|
-
.replace("value: ${{ jobs.override.outputs.controller-receipt-digest || jobs.alpha.outputs.controller-receipt-digest || jobs.stable.outputs.controller-receipt-digest }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-digest }}")
|
|
59
|
-
.replace("value: ${{ jobs.override.outputs.controller-receipt-status || jobs.alpha.outputs.controller-receipt-status || jobs.stable.outputs.controller-receipt-status }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-status }}")
|
|
60
|
-
.replace(
|
|
61
|
-
/( credential-island-macos-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.override\.outputs\.artifact-name \|\| jobs\.alpha\.outputs\.artifact-name \|\| jobs\.stable\.outputs\.artifact-name \}\}/,
|
|
62
|
-
"$1 ${{ jobs.override.outputs.credential-island-macos-artifact || jobs.alpha.outputs.credential-island-macos-artifact || jobs.stable.outputs.credential-island-macos-artifact }}",
|
|
63
|
-
)
|
|
64
|
-
.replace(
|
|
65
|
-
/( credential-island-macos-manifest-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.override\.outputs\.manifest-artifact-name \|\| jobs\.alpha\.outputs\.manifest-artifact-name \|\| jobs\.stable\.outputs\.manifest-artifact-name \}\}/,
|
|
66
|
-
"$1 ${{ jobs.override.outputs.credential-island-macos-manifest-artifact || jobs.alpha.outputs.credential-island-macos-manifest-artifact || jobs.stable.outputs.credential-island-macos-manifest-artifact }}",
|
|
67
|
-
);
|
|
68
|
-
return [
|
|
69
|
-
" buildchain-channel:",
|
|
70
|
-
' description: "Resolved Buildchain channel: alpha or stable"',
|
|
71
|
-
" value: ${{ jobs.resolve-channel.outputs.channel }}",
|
|
72
|
-
" buildchain-channel-selection-source:",
|
|
73
|
-
' description: "Evidence source used to select the Buildchain channel"',
|
|
74
|
-
" value: ${{ jobs.resolve-channel.outputs.selection-source }}",
|
|
75
|
-
" buildchain-channel-reason:",
|
|
76
|
-
' description: "Human-readable Buildchain channel selection reason"',
|
|
77
|
-
" value: ${{ jobs.resolve-channel.outputs.reason }}",
|
|
78
|
-
forwarded.trimEnd(),
|
|
79
|
-
].join("\n");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function forwardedInputs(names) {
|
|
83
|
-
return names
|
|
84
|
-
.map((name) => {
|
|
85
|
-
if (name === "buildchain-ref") return ` buildchain-ref: \${{ needs.resolve-channel.outputs.buildchain-ref }}`;
|
|
86
|
-
if (name === "buildchain-expected-channel") {
|
|
87
|
-
return " buildchain-expected-channel: ${{ needs.resolve-channel.outputs.channel }}";
|
|
88
|
-
}
|
|
89
|
-
if (name === "buildchain-expected-major") {
|
|
90
|
-
return " buildchain-expected-major: ${{ needs.resolve-channel.outputs.major }}";
|
|
91
|
-
}
|
|
92
|
-
if (name === "buildchain-contract-lock-path") {
|
|
93
|
-
return " buildchain-contract-lock-path: ${{ needs.resolve-channel.outputs.contract-lock-path }}";
|
|
94
|
-
}
|
|
95
|
-
if (name === "buildchain-visible-workflow") {
|
|
96
|
-
return " buildchain-visible-workflow: .github/workflows/build.yml";
|
|
97
|
-
}
|
|
98
|
-
return ` ${name}: \${{ inputs.${name} }}`;
|
|
99
|
-
})
|
|
100
|
-
.join("\n");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function routerControllerPlanJob() {
|
|
104
|
-
return ` controller-plan:
|
|
105
|
-
name: Plan channel router controller evidence
|
|
106
|
-
needs: resolve-channel
|
|
107
|
-
runs-on: \${{ fromJSON(inputs.control-runner-json) }}
|
|
108
|
-
outputs:
|
|
109
|
-
controller-plan-artifact: \${{ steps.names.outputs.controller-plan-artifact }}
|
|
110
|
-
controller-plan-json: \${{ steps.plan.outputs.controller-plan-json }}
|
|
111
|
-
controller-plan-digest: \${{ steps.plan.outputs.controller-plan-digest }}
|
|
112
|
-
runtime-sha: \${{ steps.identities.outputs.runtime-sha }}
|
|
113
|
-
steps:
|
|
114
|
-
- name: Checkout Buildchain controller workflow shell
|
|
115
|
-
uses: actions/checkout@v7.0.0
|
|
116
|
-
with:
|
|
117
|
-
repository: \${{ needs.resolve-channel.outputs.router-repository }}
|
|
118
|
-
ref: \${{ needs.resolve-channel.outputs.router-sha }}
|
|
119
|
-
path: .buildchain/controller-runtime
|
|
120
|
-
persist-credentials: false
|
|
121
|
-
|
|
122
|
-
- name: Resolve controller workflow shell identity
|
|
123
|
-
id: identities
|
|
124
|
-
shell: bash
|
|
125
|
-
run: |
|
|
126
|
-
runtime_sha="$(git -C .buildchain/controller-runtime rev-parse HEAD)"
|
|
127
|
-
contract_digest="$(node -e 'const fs=require("fs"); const value=JSON.parse(fs.readFileSync(".buildchain/controller-runtime/dist/site/buildchain-contract.json","utf8")); process.stdout.write(value.contractDigest)')"
|
|
128
|
-
{
|
|
129
|
-
echo "runtime-sha=\${runtime_sha}"
|
|
130
|
-
echo "contract-digest=\${contract_digest}"
|
|
131
|
-
} >> "$GITHUB_OUTPUT"
|
|
132
|
-
|
|
133
|
-
- name: Create channel router controller plan
|
|
134
|
-
id: plan
|
|
135
|
-
run: node .buildchain/controller-runtime/scripts/controller-evidence.mjs --mode plan
|
|
136
|
-
env:
|
|
137
|
-
BUILDCHAIN_CONTROLLER_ID: build-channel-router
|
|
138
|
-
BUILDCHAIN_CONTROLLER_SOURCE_REPOSITORY: \${{ github.repository }}
|
|
139
|
-
BUILDCHAIN_CONTROLLER_SOURCE_SHA: \${{ github.sha }}
|
|
140
|
-
BUILDCHAIN_CONTROLLER_RUNTIME_REF: \${{ needs.resolve-channel.outputs.router-ref }}
|
|
141
|
-
BUILDCHAIN_CONTROLLER_RUNTIME_SHA: \${{ steps.identities.outputs.runtime-sha }}
|
|
142
|
-
BUILDCHAIN_CONTROLLER_CONTRACT_DIGEST: \${{ steps.identities.outputs.contract-digest }}
|
|
143
|
-
BUILDCHAIN_CONTROLLER_REGISTRY: .buildchain/controller-runtime/dist/site/controller-registry.json
|
|
144
|
-
BUILDCHAIN_CONTROLLER_INPUTS_JSON: \${{ toJSON(inputs) }}
|
|
145
|
-
BUILDCHAIN_CONTROLLER_INPUT_BOUNDARY: workflow-call
|
|
146
|
-
BUILDCHAIN_CONTROLLER_PLAN_PATH: .buildchain/controller/plan.json
|
|
147
|
-
|
|
148
|
-
- name: Resolve channel router controller plan artifact
|
|
149
|
-
id: names
|
|
150
|
-
run: echo "controller-plan-artifact=buildchain-channel-controller-plan-\${SOURCE_SHA}" >> "$GITHUB_OUTPUT"
|
|
151
|
-
env:
|
|
152
|
-
SOURCE_SHA: \${{ github.sha }}
|
|
153
|
-
|
|
154
|
-
- name: Upload channel router controller plan
|
|
155
|
-
uses: actions/upload-artifact@v7.0.1
|
|
156
|
-
with:
|
|
157
|
-
name: \${{ steps.names.outputs.controller-plan-artifact }}
|
|
158
|
-
path: .buildchain/controller/plan.json
|
|
159
|
-
if-no-files-found: error`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function routerControllerReceiptJob() {
|
|
163
|
-
return ` controller-receipt:
|
|
164
|
-
name: Finalize channel router controller evidence
|
|
165
|
-
needs:
|
|
166
|
-
- resolve-channel
|
|
167
|
-
- controller-plan
|
|
168
|
-
- override
|
|
169
|
-
- alpha
|
|
170
|
-
- stable
|
|
171
|
-
if: \${{ always() && needs.controller-plan.result == 'success' }}
|
|
172
|
-
runs-on: \${{ fromJSON(inputs.control-runner-json) }}
|
|
173
|
-
outputs:
|
|
174
|
-
controller-receipt-artifact: \${{ steps.names.outputs.controller-receipt-artifact }}
|
|
175
|
-
controller-receipt-json: \${{ steps.receipt.outputs.controller-receipt-json }}
|
|
176
|
-
controller-receipt-digest: \${{ steps.receipt.outputs.controller-receipt-digest }}
|
|
177
|
-
controller-receipt-status: \${{ steps.receipt.outputs.controller-receipt-status }}
|
|
178
|
-
steps:
|
|
179
|
-
- name: Checkout Buildchain controller workflow shell
|
|
180
|
-
uses: actions/checkout@v7.0.0
|
|
181
|
-
with:
|
|
182
|
-
repository: \${{ needs.resolve-channel.outputs.router-repository }}
|
|
183
|
-
ref: \${{ needs.controller-plan.outputs.runtime-sha }}
|
|
184
|
-
path: .buildchain/controller-runtime
|
|
185
|
-
persist-credentials: false
|
|
186
|
-
|
|
187
|
-
- name: Download channel router controller plan
|
|
188
|
-
uses: actions/download-artifact@v7.0.0
|
|
189
|
-
with:
|
|
190
|
-
name: \${{ needs.controller-plan.outputs.controller-plan-artifact }}
|
|
191
|
-
path: .buildchain/controller
|
|
192
|
-
|
|
193
|
-
- name: Create channel router controller receipt
|
|
194
|
-
id: receipt
|
|
195
|
-
run: node .buildchain/controller-runtime/scripts/controller-evidence.mjs --mode receipt
|
|
196
|
-
env:
|
|
197
|
-
BUILDCHAIN_CONTROLLER_PLAN_PATH: .buildchain/controller/plan.json
|
|
198
|
-
BUILDCHAIN_CONTROLLER_STAGES_JSON: >-
|
|
199
|
-
[{"id":"resolve-channel","status":"\${{ needs.resolve-channel.result }}"},{"id":"override","status":"\${{ needs.override.result }}"},{"id":"alpha","status":"\${{ needs.alpha.result }}"},{"id":"stable","status":"\${{ needs.stable.result }}"},{"id":"aggregate","status":"\${{ needs.resolve-channel.outputs.runtime-override == 'true' && needs.override.result || needs.resolve-channel.outputs.channel == 'alpha' && needs.alpha.result || needs.stable.result }}"}]
|
|
200
|
-
BUILDCHAIN_CONTROLLER_EVIDENCE_JSON: \${{ (needs.override.outputs.controller-receipt-digest != '' || needs.alpha.outputs.controller-receipt-digest != '' || needs.stable.outputs.controller-receipt-digest != '') && format('[{{"kind":"nested-controller-receipt","digest":"{0}"}}]', needs.override.outputs.controller-receipt-digest || needs.alpha.outputs.controller-receipt-digest || needs.stable.outputs.controller-receipt-digest) || '[]' }}
|
|
201
|
-
BUILDCHAIN_CONTROLLER_REASON_CODE: \${{ ((needs.resolve-channel.outputs.runtime-override == 'true' && needs.override.result == 'success') || (needs.resolve-channel.outputs.runtime-override != 'true' && needs.resolve-channel.outputs.channel == 'alpha' && needs.alpha.result == 'success') || (needs.resolve-channel.outputs.runtime-override != 'true' && needs.resolve-channel.outputs.channel == 'stable' && needs.stable.result == 'success')) && '' || 'nested-build-incomplete' }}
|
|
202
|
-
BUILDCHAIN_CONTROLLER_REASON_SUMMARY: Nested build controller did not complete successfully
|
|
203
|
-
BUILDCHAIN_CONTROLLER_RECEIPT_ARTIFACT: buildchain-channel-controller-receipt-\${{ github.sha }}
|
|
204
|
-
BUILDCHAIN_CONTROLLER_RECEIPT_PATH: .buildchain/controller/receipt.json
|
|
205
|
-
|
|
206
|
-
- name: Resolve channel router controller receipt artifact
|
|
207
|
-
id: names
|
|
208
|
-
run: echo "controller-receipt-artifact=buildchain-channel-controller-receipt-\${SOURCE_SHA}" >> "$GITHUB_OUTPUT"
|
|
209
|
-
env:
|
|
210
|
-
SOURCE_SHA: \${{ github.sha }}
|
|
211
|
-
|
|
212
|
-
- name: Upload channel router controller receipt
|
|
213
|
-
if: \${{ always() && steps.receipt.outcome == 'success' }}
|
|
214
|
-
uses: actions/upload-artifact@v7.0.1
|
|
215
|
-
with:
|
|
216
|
-
name: \${{ steps.names.outputs.controller-receipt-artifact }}
|
|
217
|
-
path: .buildchain/controller/receipt.json
|
|
218
|
-
if-no-files-found: error
|
|
219
|
-
|
|
220
|
-
- name: Enforce qualifying channel router controller receipt
|
|
221
|
-
if: \${{ steps.receipt.outputs.controller-receipt-qualifying != 'true' }}
|
|
222
|
-
run: |
|
|
223
|
-
echo "::error::channel router controller receipt is not qualifying: \${{ steps.receipt.outputs.controller-receipt-status }}"
|
|
224
|
-
exit 1`;
|
|
225
|
-
}
|
|
5
|
+
import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
|
|
226
6
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- alpha
|
|
234
|
-
- stable
|
|
235
|
-
- controller-receipt
|
|
236
|
-
if: \${{ always() }}
|
|
237
|
-
runs-on: \${{ fromJSON(inputs.control-runner-json) }}
|
|
238
|
-
steps:
|
|
239
|
-
- name: Enforce public channel router aggregate
|
|
240
|
-
shell: bash
|
|
241
|
-
env:
|
|
242
|
-
CHANNEL: \${{ needs.resolve-channel.outputs.channel }}
|
|
243
|
-
RUNTIME_OVERRIDE: \${{ needs.resolve-channel.outputs.runtime-override }}
|
|
244
|
-
OVERRIDE_RESULT: \${{ needs.override.result }}
|
|
245
|
-
ALPHA_RESULT: \${{ needs.alpha.result }}
|
|
246
|
-
STABLE_RESULT: \${{ needs.stable.result }}
|
|
247
|
-
CONTROLLER_RECEIPT_RESULT: \${{ needs.controller-receipt.result }}
|
|
248
|
-
run: |
|
|
249
|
-
set -euo pipefail
|
|
250
|
-
selected_result="\${STABLE_RESULT}"
|
|
251
|
-
if [[ "\${RUNTIME_OVERRIDE}" = "true" ]]; then
|
|
252
|
-
selected_result="\${OVERRIDE_RESULT}"
|
|
253
|
-
elif [[ "\${CHANNEL}" = "alpha" ]]; then
|
|
254
|
-
selected_result="\${ALPHA_RESULT}"
|
|
255
|
-
fi
|
|
256
|
-
if [[ "\${selected_result}" != "success" || "\${CONTROLLER_RECEIPT_RESULT}" != "success" ]]; then
|
|
257
|
-
echo "::error::Buildchain channel router did not qualify: channel=\${CHANNEL} selected=\${selected_result} controller-receipt=\${CONTROLLER_RECEIPT_RESULT}"
|
|
258
|
-
exit 1
|
|
259
|
-
fi
|
|
260
|
-
echo "Buildchain channel router aggregate passed."`;
|
|
7
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
8
|
+
function block(source, start, end) {
|
|
9
|
+
const first = source.indexOf(start);
|
|
10
|
+
const last = source.indexOf(end, first + start.length);
|
|
11
|
+
if (first < 0 || last < 0) throw new Error(`Missing build contract block: ${start.trim()}`);
|
|
12
|
+
return source.slice(first + start.length, last).trimEnd();
|
|
261
13
|
}
|
|
262
14
|
|
|
263
|
-
function
|
|
264
|
-
const inputs =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const names = inputNames(inputs);
|
|
268
|
-
if (!names.includes("buildchain-ref") || !names.includes("publish-channel")) {
|
|
269
|
-
throw new Error("source build workflow is missing channel-router inputs");
|
|
15
|
+
export function generateChannelBuildWorkflow(source) {
|
|
16
|
+
const inputs = block(source, " inputs:\n", " secrets:\n");
|
|
17
|
+
if (JSON.stringify([...inputs.matchAll(/^ ([a-z0-9-]+):$/gmu)].map((match) => match[1])) !== '["config-path"]') {
|
|
18
|
+
throw new Error("Ordinary builds expose only the optional configuration locator");
|
|
270
19
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
override:
|
|
281
|
-
name: Build trusted runtime override
|
|
282
|
-
needs:
|
|
283
|
-
- resolve-channel
|
|
284
|
-
- controller-plan
|
|
285
|
-
if: \${{ needs.resolve-channel.outputs.runtime-override == 'true' }}
|
|
286
|
-
uses: ./.github/workflows/.build.yml
|
|
287
|
-
permissions:
|
|
288
|
-
actions: read
|
|
289
|
-
contents: read
|
|
290
|
-
issues: write
|
|
291
|
-
id-token: write
|
|
292
|
-
with:
|
|
20
|
+
const secrets = block(source, " secrets:\n", " outputs:\n");
|
|
21
|
+
const outputs = block(source, " outputs:\n", "\npermissions:\n")
|
|
22
|
+
.replace(/(^ ([a-z0-9-]+):\n(?: description:.*\n)*) value:.*$/gmu, "$1 value: ${{ jobs.build.outputs.$2 }}");
|
|
23
|
+
return `# Generated by scripts/generate-channel-build-workflow.mjs. Do not edit directly.
|
|
24
|
+
name: Buildchain Build
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
workflow_call:
|
|
28
|
+
inputs:
|
|
293
29
|
${inputs}
|
|
294
|
-
secrets:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if: \${{ needs.resolve-channel.outputs.runtime-override != 'true' && needs.resolve-channel.outputs.channel == 'stable' }}
|
|
30
|
+
secrets:
|
|
31
|
+
${secrets}
|
|
32
|
+
outputs:
|
|
33
|
+
buildchain-channel:
|
|
34
|
+
description: "Channel bound to the called workflow ref"
|
|
35
|
+
value: \${{ jobs.build.outputs.buildchain-runtime-class }}
|
|
36
|
+
buildchain-channel-selection-source:
|
|
37
|
+
description: "Runtime identity authority"
|
|
38
|
+
value: "called-workflow"
|
|
39
|
+
buildchain-channel-reason:
|
|
40
|
+
description: "Buildchain runtime is the exact called workflow"
|
|
41
|
+
value: \${{ jobs.build.outputs.buildchain-runtime-ref }}
|
|
42
|
+
${outputs}
|
|
43
|
+
|
|
44
|
+
permissions:
|
|
45
|
+
actions: read
|
|
46
|
+
contents: read
|
|
47
|
+
issues: write
|
|
48
|
+
id-token: write
|
|
49
|
+
|
|
50
|
+
jobs:
|
|
51
|
+
build:
|
|
52
|
+
name: Build configured project
|
|
318
53
|
uses: ./.github/workflows/.build.yml
|
|
319
|
-
permissions:
|
|
320
|
-
actions: read
|
|
321
|
-
contents: read
|
|
322
|
-
issues: write
|
|
323
|
-
id-token: write
|
|
324
54
|
with:
|
|
325
|
-
|
|
55
|
+
config-path: \${{ inputs.config-path }}
|
|
326
56
|
secrets: inherit
|
|
327
57
|
`;
|
|
328
|
-
return `${workflow.slice(0, index)}${jobs}`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function bindRouterCheckoutToWorkflowSha(workflow) {
|
|
332
|
-
return workflow
|
|
333
|
-
.replace(
|
|
334
|
-
" BUILDCHAIN_ROUTER_WORKFLOW_REF: ${{ job.workflow_ref }}\n",
|
|
335
|
-
[
|
|
336
|
-
" BUILDCHAIN_ROUTER_WORKFLOW_REF: ${{ job.workflow_ref }}",
|
|
337
|
-
" BUILDCHAIN_ROUTER_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }}",
|
|
338
|
-
" BUILDCHAIN_ROUTER_WORKFLOW_SHA: ${{ job.workflow_sha }}",
|
|
339
|
-
"",
|
|
340
|
-
].join("\n"),
|
|
341
|
-
)
|
|
342
|
-
.replace(
|
|
343
|
-
[
|
|
344
|
-
' repository="${workflow_ref%%/.github/workflows/*}"',
|
|
345
|
-
' ref="${workflow_ref##*@}"',
|
|
346
|
-
' ref="${ref#refs/heads/}"',
|
|
347
|
-
' ref="${ref#refs/tags/}"',
|
|
348
|
-
' if [[ -z "${repository}" || "${repository}" = "${workflow_ref}" || -z "${ref}" ]]; then',
|
|
349
|
-
' echo "Unable to resolve Buildchain router source from job.workflow_ref=${workflow_ref}" >&2',
|
|
350
|
-
" exit 1",
|
|
351
|
-
" fi",
|
|
352
|
-
].join("\n"),
|
|
353
|
-
[
|
|
354
|
-
' repository="${BUILDCHAIN_ROUTER_WORKFLOW_REPOSITORY}"',
|
|
355
|
-
' ref="${workflow_ref##*@}"',
|
|
356
|
-
' ref="${ref#refs/heads/}"',
|
|
357
|
-
' ref="${ref#refs/tags/}"',
|
|
358
|
-
' parsed_repository="${workflow_ref%%/.github/workflows/*}"',
|
|
359
|
-
' if [[ -z "${repository}" || "${repository}" != "${parsed_repository}" || -z "${ref}" ]]; then',
|
|
360
|
-
' echo "::error::Unable to resolve Buildchain router source from job.workflow_ref=${workflow_ref}"',
|
|
361
|
-
" exit 1",
|
|
362
|
-
" fi",
|
|
363
|
-
' if [[ ! "${BUILDCHAIN_ROUTER_WORKFLOW_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then',
|
|
364
|
-
' echo "::error::job.workflow_sha is not an exact commit SHA"',
|
|
365
|
-
" exit 1",
|
|
366
|
-
" fi",
|
|
367
|
-
].join("\n"),
|
|
368
|
-
)
|
|
369
|
-
.replace(
|
|
370
|
-
' echo "ref=${ref}"\n',
|
|
371
|
-
' echo "ref=${ref}"\n echo "sha=${BUILDCHAIN_ROUTER_WORKFLOW_SHA,,}"\n',
|
|
372
|
-
)
|
|
373
|
-
.replace(
|
|
374
|
-
" ref: ${{ steps.router.outputs.ref }}\n path: .buildchain/router",
|
|
375
|
-
" ref: ${{ steps.router.outputs.sha }}\n path: .buildchain/router",
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function generateChannelBuildWorkflow(source) {
|
|
380
|
-
const inputBlock = blockBetween(source, " inputs:\n", " secrets:\n");
|
|
381
|
-
const names = inputNames(inputBlock);
|
|
382
|
-
const packageVersion = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version || "";
|
|
383
|
-
const major = Number(String(packageVersion).split(".")[0]);
|
|
384
|
-
if (!Number.isInteger(major) || major < 1) throw new Error("package.json version must declare a positive major");
|
|
385
|
-
const generated = bindRouterCheckoutToWorkflowSha(
|
|
386
|
-
splitBuildJobsByChannel(generateChannelBuildWorkflowBase(source), names, major),
|
|
387
|
-
)
|
|
388
|
-
.replaceAll("runs-on: ubuntu-24.04", "runs-on: ${{ fromJSON(inputs.control-runner-json) }}")
|
|
389
|
-
.replace(
|
|
390
|
-
" buildchain-ref: ${{ steps.channel.outputs.buildchain-ref }}\n",
|
|
391
|
-
" buildchain-ref: ${{ steps.channel.outputs.buildchain-ref }}\n major: ${{ steps.channel.outputs.major }}\n runtime-override: ${{ steps.channel.outputs.runtime-override }}\n",
|
|
392
|
-
)
|
|
393
|
-
.replace(
|
|
394
|
-
"\npermissions:\n contents: read\n",
|
|
395
|
-
"\npermissions:\n actions: read\n contents: read\n",
|
|
396
|
-
)
|
|
397
|
-
.replace(
|
|
398
|
-
" contract-lock-path: ${{ steps.lock.outputs.path }}\n",
|
|
399
|
-
[
|
|
400
|
-
" contract-lock-path: ${{ steps.lock.outputs.path }}",
|
|
401
|
-
" router-repository: ${{ steps.router.outputs.repository }}",
|
|
402
|
-
" router-ref: ${{ steps.router.outputs.ref }}",
|
|
403
|
-
" router-sha: ${{ steps.router.outputs.sha }}",
|
|
404
|
-
"",
|
|
405
|
-
].join("\n"),
|
|
406
|
-
)
|
|
407
|
-
.replace("\n alpha:\n", `\n${routerControllerPlanJob()}\n\n alpha:\n`);
|
|
408
|
-
return migrateUniversalWorkflowFacade(
|
|
409
|
-
`${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`,
|
|
410
|
-
".github/workflows/build.yml",
|
|
411
|
-
);
|
|
412
58
|
}
|
|
413
59
|
|
|
414
60
|
function main() {
|
|
415
|
-
const
|
|
416
|
-
const
|
|
61
|
+
const generated = generateChannelBuildWorkflow(fs.readFileSync(path.join(root, ".github/workflows/.build.yml"), "utf8"));
|
|
62
|
+
const target = ".github/workflows/build.yml";
|
|
417
63
|
if (process.argv.includes("--check")) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
console.error(".github/workflows/build.yml is stale; run node scripts/generate-channel-build-workflow.mjs");
|
|
421
|
-
process.exitCode = 1;
|
|
64
|
+
if (fs.readFileSync(path.join(root, target), "utf8") !== generated) {
|
|
65
|
+
throw new Error("build.yml is stale; run node scripts/generate-channel-build-workflow.mjs");
|
|
422
66
|
}
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
writeWorkflowSource(root, path.relative(root, targetPath), generated);
|
|
67
|
+
} else writeWorkflowSource(root, target, generated);
|
|
426
68
|
}
|
|
427
|
-
|
|
428
|
-
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
429
|
-
if (isMain) main();
|
|
69
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main();
|
|
@@ -588,7 +588,7 @@ function actionCapabilityGroup(id) {
|
|
|
588
588
|
if (id === "github-artifact-attestation") return capabilityGroup("release-passport-trust");
|
|
589
589
|
if (id === "release-tail") return capabilityGroup("release-passport-trust");
|
|
590
590
|
if (id === "promote-buildchain-ref") return capabilityGroup("release-passport-trust");
|
|
591
|
-
if (id === "run-lifecycle" || id === "validate-config") return capabilityGroup("reusable-build");
|
|
591
|
+
if (id.startsWith("build-") || id === "run-lifecycle" || id === "validate-config") return capabilityGroup("reusable-build");
|
|
592
592
|
if (id === "report-buildchain-issue") return capabilityGroup("observability-diagnostics");
|
|
593
593
|
return capabilityGroup("api-cli-reference");
|
|
594
594
|
}
|
|
@@ -905,6 +905,7 @@ function buildSiteBundle() {
|
|
|
905
905
|
...publicSurfaceLifecycle({
|
|
906
906
|
owner: "buildchain-actions",
|
|
907
907
|
maturity: "stable",
|
|
908
|
+
introducedVersion: entry.id.startsWith("build-") ? "4.0.9-alpha.0" : undefined,
|
|
908
909
|
nonDuplicationRationale: "Existing action identity retained as the canonical composite or JavaScript action boundary.",
|
|
909
910
|
}),
|
|
910
911
|
})),
|
|
@@ -53,6 +53,7 @@ function activeFacadePaths() {
|
|
|
53
53
|
(relative) =>
|
|
54
54
|
relative !== contract.bootstrap.publicWorkflow &&
|
|
55
55
|
relative !== contract.bootstrap.consumerRecoveryWorkflow &&
|
|
56
|
+
!contract.configurationGovernedWorkflows.includes(relative) &&
|
|
56
57
|
!retired.has(relative),
|
|
57
58
|
);
|
|
58
59
|
}
|
package/scripts/init-repo.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
nextDevelopmentWorkflowHeader,
|
|
11
11
|
} from "../packages/core/next-development-projection.js";
|
|
12
12
|
|
|
13
|
-
const BUILDCHAIN_WORKFLOW_REF = "kungfu-systems/buildchain/.github/workflows
|
|
13
|
+
const BUILDCHAIN_WORKFLOW_REF = "kungfu-systems/buildchain/.github/workflows/build.yml@v4";
|
|
14
14
|
const DEFAULT_PUBLICATION_LATEX_IMAGE = "ghcr.io/kungfu-systems/build-images/latex-pdf-builder";
|
|
15
15
|
const DEFAULT_PUBLICATION_LATEX_DIGEST = "sha256:c20f3809e96836c1c78e97c76939d12f1de3fed0ea9b7c40c43332ec2ea480f8";
|
|
16
16
|
const DEFAULT_PUBLICATION_LATEX_COMMAND = "latexmk -pdf -outdir=_build paper/main.tex";
|
|
@@ -365,41 +365,27 @@ jobs:
|
|
|
365
365
|
`;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
function workflowYaml(
|
|
369
|
-
return `${nextDevelopmentWorkflowHeader()}#
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
# Persist only @v4 or @v4-alpha; pass temporary train/SHA runtimes through workflow_dispatch.
|
|
368
|
+
function workflowYaml() {
|
|
369
|
+
return `${nextDevelopmentWorkflowHeader()}# Project build settings live only in .buildchain/buildchain.toml.
|
|
370
|
+
# Commit .buildchain/contract-lock.json and .buildchain/alpha-contract-lock.json.
|
|
371
|
+
# The called ref selects the runtime; architecture/platform-stage-checkpoints.json owns checkpoint projections.
|
|
373
372
|
name: Build
|
|
374
373
|
|
|
375
374
|
on:
|
|
376
375
|
workflow_dispatch:
|
|
377
|
-
inputs:
|
|
378
|
-
buildchain-ref:
|
|
379
|
-
description: "Temporary Buildchain runtime ref for trusted manual validation"
|
|
380
|
-
required: false
|
|
381
|
-
default: ""
|
|
382
376
|
pull_request:
|
|
383
377
|
push:
|
|
384
|
-
branches:
|
|
385
|
-
- "dev/**"
|
|
386
|
-
- "alpha/**"
|
|
387
|
-
- "release/**"
|
|
388
|
-
- "publish-gate/**"
|
|
378
|
+
branches: ["dev/**", "alpha/**", "release/**"]
|
|
389
379
|
|
|
390
380
|
permissions:
|
|
381
|
+
actions: read
|
|
391
382
|
contents: read
|
|
383
|
+
issues: write
|
|
384
|
+
id-token: write
|
|
392
385
|
|
|
393
386
|
jobs:
|
|
394
387
|
build:
|
|
395
388
|
uses: ${BUILDCHAIN_WORKFLOW_REF}
|
|
396
|
-
with:
|
|
397
|
-
working-directory: "."
|
|
398
|
-
buildchain-ref: \${{ inputs.buildchain-ref || '' }}
|
|
399
|
-
runner-preset: "${runnerPreset}"
|
|
400
|
-
artifact-name-template: "${artifactName}"
|
|
401
|
-
artifact-paths: |
|
|
402
|
-
${workflowArtifactPaths(type)}
|
|
403
389
|
`;
|
|
404
390
|
}
|
|
405
391
|
|
|
@@ -425,11 +411,11 @@ export function initBuildchainRepo({
|
|
|
425
411
|
force = false,
|
|
426
412
|
packageManager = "",
|
|
427
413
|
runnerPreset = "github-hosted",
|
|
428
|
-
artifactName = "
|
|
414
|
+
artifactName = "",
|
|
429
415
|
} = {}) {
|
|
430
416
|
const resolvedCwd = path.resolve(cwd);
|
|
431
417
|
const manager = detectOrDefaultPackageManager(resolvedCwd, packageManager);
|
|
432
|
-
|
|
418
|
+
let toml = appendNextDevelopmentToml((() => {
|
|
433
419
|
if (type === "package") {
|
|
434
420
|
return packageToml(resolvedCwd, manager);
|
|
435
421
|
}
|
|
@@ -451,6 +437,19 @@ export function initBuildchainRepo({
|
|
|
451
437
|
throw new Error("init --type must be one of package, native, web-surface, infra-contract, publication-artifact, or anchored-package");
|
|
452
438
|
})());
|
|
453
439
|
|
|
440
|
+
|
|
441
|
+
if (type !== "publication-artifact") {
|
|
442
|
+
const paths = workflowArtifactPaths(type).split("\n").map((entry) => entry.trim());
|
|
443
|
+
toml += `
|
|
444
|
+
[build]
|
|
445
|
+
environment = ${JSON.stringify(runnerPreset)}
|
|
446
|
+
|
|
447
|
+
[build.artifacts]
|
|
448
|
+
name = ${JSON.stringify(artifactName || repoName(resolvedCwd))}
|
|
449
|
+
paths = ${JSON.stringify(paths)}
|
|
450
|
+
`;
|
|
451
|
+
}
|
|
452
|
+
|
|
454
453
|
const agentsPath = path.join(resolvedCwd, "AGENTS.md");
|
|
455
454
|
const currentAgents = fs.existsSync(agentsPath)
|
|
456
455
|
? fs.readFileSync(agentsPath, "utf8")
|
|
@@ -509,7 +508,7 @@ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.met
|
|
|
509
508
|
force: process.argv.includes("--force"),
|
|
510
509
|
packageManager: readArg("package-manager", ""),
|
|
511
510
|
runnerPreset: readArg("runner-preset", "github-hosted"),
|
|
512
|
-
artifactName: readArg("artifact-name", "
|
|
511
|
+
artifactName: readArg("artifact-name", ""),
|
|
513
512
|
});
|
|
514
513
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
515
514
|
} catch (error) {
|