@kungfu-tech/buildchain 2.14.2 → 2.14.3-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/dist/site/agent-index.json +2 -0
- package/dist/site/artifact-schemas.json +4 -0
- package/dist/site/badge-endpoint-registry.json +320 -0
- package/dist/site/badges/v1/kfd-5/aligned.json +14 -0
- package/dist/site/badges/v1/kfd-5/declared.json +14 -0
- package/dist/site/badges/v1/kfd-5/downgraded.json +14 -0
- package/dist/site/badges/v1/kfd-5/draft.json +14 -0
- package/dist/site/badges/v1/kfd-5/failed.json +14 -0
- package/dist/site/badges/v1/kfd-5/missing.json +14 -0
- package/dist/site/badges/v1/kfd-5/passed.json +14 -0
- package/dist/site/badges/v1/kfd-5/planned.json +14 -0
- package/dist/site/badges/v1/kfd-6/aligned.json +14 -0
- package/dist/site/badges/v1/kfd-6/declared.json +14 -0
- package/dist/site/badges/v1/kfd-6/downgraded.json +14 -0
- package/dist/site/badges/v1/kfd-6/draft.json +14 -0
- package/dist/site/badges/v1/kfd-6/failed.json +14 -0
- package/dist/site/badges/v1/kfd-6/missing.json +14 -0
- package/dist/site/badges/v1/kfd-6/passed.json +14 -0
- package/dist/site/badges/v1/kfd-6/planned.json +14 -0
- package/dist/site/buildchain-contract.json +149 -28
- package/dist/site/buildchain-site.json +25 -23
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/controller-registry.json +61 -5
- package/dist/site/kfd-claims.json +129 -11
- package/dist/site/kfd-upstream-aggregate.json +12 -12
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +20 -7
- package/dist/site/page-registry.json +12 -12
- package/dist/site/public-surface-audit.json +134 -8
- package/dist/site/publication-authority-registry.json +26 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-model.json +3 -1
- package/dist/site/release-passport-check-manifest.json +167 -0
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/schemas/release-passport-v1.schema.json +198 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +128 -4
- package/docs/migration-inventory.md +8 -0
- package/docs/release-candidate.md +3 -0
- package/docs/release-governance.md +5 -4
- package/docs/release-passport.md +19 -0
- package/docs/release-propagation.md +19 -7
- package/docs/reusable-build-surface.md +26 -1
- package/package.json +5 -2
- package/packages/core/buildchain-contract.js +41 -0
- package/packages/core/buildchain-kfd-claims.js +4 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/controller-evidence.js +1 -1
- package/packages/core/index.js +9 -0
- package/packages/core/package-manager.js +32 -0
- package/packages/core/release-passport-contract.js +249 -0
- package/packages/core/release-passport.js +39 -0
- package/scripts/check-inventory.mjs +37 -3
- package/scripts/generate-channel-promotion-workflow.mjs +331 -0
- package/scripts/generate-site-bundle.mjs +15 -0
- package/scripts/promotion-channel-router.mjs +132 -0
- package/scripts/validate-package-manager-contract.mjs +24 -0
|
@@ -0,0 +1,331 @@
|
|
|
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/.release-candidate-promote.yml");
|
|
9
|
+
const targetPath = path.join(root, ".github/workflows/release-candidate-promote.yml");
|
|
10
|
+
const internalInputs = new Set([
|
|
11
|
+
"promotion-router-ref",
|
|
12
|
+
"promotion-router-sha",
|
|
13
|
+
"promotion-shell-ref",
|
|
14
|
+
"promotion-shell-sha",
|
|
15
|
+
"promotion-runtime-ref",
|
|
16
|
+
"promotion-runtime-sha",
|
|
17
|
+
"promotion-contract-lock-path",
|
|
18
|
+
"promotion-contract-lock-digest",
|
|
19
|
+
"promotion-publication-channel",
|
|
20
|
+
"promotion-target-ref",
|
|
21
|
+
"promotion-override-used",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function blockBetween(source, start, end) {
|
|
25
|
+
const startIndex = source.indexOf(start);
|
|
26
|
+
const endIndex = source.indexOf(end, startIndex + start.length);
|
|
27
|
+
if (startIndex < 0 || endIndex < 0) throw new Error(`unable to locate workflow block: ${start.trim()}`);
|
|
28
|
+
return source.slice(startIndex + start.length, endIndex);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function entries(block, indent = 6) {
|
|
32
|
+
const pattern = new RegExp(`^ {${indent}}([a-z0-9-]+):$`, "gm");
|
|
33
|
+
const matches = [...block.matchAll(pattern)];
|
|
34
|
+
return matches.map((match, index) => ({
|
|
35
|
+
name: match[1],
|
|
36
|
+
source: block.slice(match.index, matches[index + 1]?.index ?? block.length).trimEnd(),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function publicInputs(inputBlock) {
|
|
41
|
+
const retained = entries(inputBlock)
|
|
42
|
+
.filter((entry) => !internalInputs.has(entry.name))
|
|
43
|
+
.map((entry) => entry.name === "buildchain-contract-lock-path"
|
|
44
|
+
? entry.source.replace(' default: ".buildchain/contract-lock.json"', ' default: ""')
|
|
45
|
+
: entry.source);
|
|
46
|
+
return [
|
|
47
|
+
" buildchain-channel:",
|
|
48
|
+
' description: "Promotion workflow-shell/runtime channel: auto, alpha, or stable"',
|
|
49
|
+
' default: "auto"',
|
|
50
|
+
" type: string",
|
|
51
|
+
" required: false",
|
|
52
|
+
" buildchain-alpha-contract-lock-path:",
|
|
53
|
+
' description: "Consumer-owned contract lock selected for alpha promotion"',
|
|
54
|
+
' default: ".buildchain/alpha-contract-lock.json"',
|
|
55
|
+
" type: string",
|
|
56
|
+
" required: false",
|
|
57
|
+
" buildchain-stable-contract-lock-path:",
|
|
58
|
+
' description: "Consumer-owned contract lock selected for stable promotion"',
|
|
59
|
+
' default: ".buildchain/contract-lock.json"',
|
|
60
|
+
" type: string",
|
|
61
|
+
" required: false",
|
|
62
|
+
...retained,
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function publicOutputs(outputBlock) {
|
|
67
|
+
const forwarded = entries(outputBlock).map((entry) => entry.source.replace(
|
|
68
|
+
/^ value:.*$/m,
|
|
69
|
+
` value: \${{ jobs.alpha.outputs.${entry.name} || jobs.stable.outputs.${entry.name} }}`,
|
|
70
|
+
));
|
|
71
|
+
const routed = [
|
|
72
|
+
["buildchain-channel", "Selected promotion workflow-shell channel", "channel"],
|
|
73
|
+
["promotion-router-ref", "Router workflow ref", "router-ref"],
|
|
74
|
+
["promotion-router-sha", "Resolved router workflow SHA", "router-sha"],
|
|
75
|
+
["promotion-shell-ref", "Selected advanced promotion workflow-shell ref", "shell-ref"],
|
|
76
|
+
["promotion-shell-sha", "Resolved advanced promotion workflow-shell SHA", "shell-sha"],
|
|
77
|
+
["promotion-runtime-ref", "Selected Buildchain runtime ref", "runtime-ref"],
|
|
78
|
+
["promotion-runtime-sha", "Resolved Buildchain runtime SHA", "runtime-sha"],
|
|
79
|
+
["promotion-contract-lock-path", "Selected consumer contract-lock path", "contract-lock-path"],
|
|
80
|
+
["promotion-contract-lock-digest", "Selected consumer contract-lock sha256 digest", "contract-lock-digest"],
|
|
81
|
+
["promotion-publication-channel", "Derived publication channel", "publication-channel"],
|
|
82
|
+
["promotion-target-ref", "Derived promotion target ref", "target-ref"],
|
|
83
|
+
["promotion-override-used", "Whether a trusted runtime override was used", "override-used"],
|
|
84
|
+
].map(([name, description, output]) => [
|
|
85
|
+
` ${name}:`,
|
|
86
|
+
` description: "${description}"`,
|
|
87
|
+
` value: \${{ jobs.resolve-promotion.outputs.${output} }}`,
|
|
88
|
+
].join("\n"));
|
|
89
|
+
return [...routed, ...forwarded].join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function forwardedInputs(inputNames) {
|
|
93
|
+
return inputNames.map((name) => {
|
|
94
|
+
const routed = {
|
|
95
|
+
"buildchain-ref": "runtime-ref",
|
|
96
|
+
"buildchain-contract-lock-path": "contract-lock-path",
|
|
97
|
+
channel: "publication-channel",
|
|
98
|
+
"target-ref": "target-ref",
|
|
99
|
+
"promotion-router-ref": "router-ref",
|
|
100
|
+
"promotion-router-sha": "router-sha",
|
|
101
|
+
"promotion-shell-ref": "shell-ref",
|
|
102
|
+
"promotion-shell-sha": "shell-sha",
|
|
103
|
+
"promotion-runtime-ref": "runtime-ref",
|
|
104
|
+
"promotion-runtime-sha": "runtime-sha",
|
|
105
|
+
"promotion-contract-lock-path": "contract-lock-path",
|
|
106
|
+
"promotion-contract-lock-digest": "contract-lock-digest",
|
|
107
|
+
"promotion-publication-channel": "publication-channel",
|
|
108
|
+
"promotion-target-ref": "target-ref",
|
|
109
|
+
"promotion-override-used": "override-used",
|
|
110
|
+
}[name];
|
|
111
|
+
return routed
|
|
112
|
+
? ` ${name}: \${{ needs.resolve-promotion.outputs.${routed} }}`
|
|
113
|
+
: ` ${name}: \${{ inputs.${name} }}`;
|
|
114
|
+
}).join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function generateChannelPromotionWorkflow(source, { major = 2 } = {}) {
|
|
118
|
+
const inputs = blockBetween(source, " inputs:\n", " secrets:\n");
|
|
119
|
+
const secrets = blockBetween(source, " secrets:\n", " outputs:\n");
|
|
120
|
+
const outputs = blockBetween(source, " outputs:\n", "\nconcurrency:\n");
|
|
121
|
+
const inputNames = entries(inputs).map((entry) => entry.name);
|
|
122
|
+
for (const required of ["buildchain-ref", "buildchain-contract-lock-path", "channel", "target-ref", ...internalInputs]) {
|
|
123
|
+
if (!inputNames.includes(required)) throw new Error(`advanced promotion workflow missing input: ${required}`);
|
|
124
|
+
}
|
|
125
|
+
const forwarded = forwardedInputs(inputNames);
|
|
126
|
+
return `# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
127
|
+
name: Release Candidate Promote
|
|
128
|
+
|
|
129
|
+
on:
|
|
130
|
+
workflow_call:
|
|
131
|
+
inputs:
|
|
132
|
+
${publicInputs(inputs)}
|
|
133
|
+
secrets:
|
|
134
|
+
${secrets.trimEnd()}
|
|
135
|
+
outputs:
|
|
136
|
+
${publicOutputs(outputs)}
|
|
137
|
+
|
|
138
|
+
permissions:
|
|
139
|
+
actions: read
|
|
140
|
+
checks: write
|
|
141
|
+
contents: write
|
|
142
|
+
id-token: write
|
|
143
|
+
issues: write
|
|
144
|
+
|
|
145
|
+
jobs:
|
|
146
|
+
resolve-promotion:
|
|
147
|
+
name: Resolve promotion workflow shell and runtime
|
|
148
|
+
runs-on: ubuntu-24.04
|
|
149
|
+
permissions:
|
|
150
|
+
contents: read
|
|
151
|
+
outputs:
|
|
152
|
+
channel: \${{ steps.route.outputs.channel }}
|
|
153
|
+
publication-channel: \${{ steps.route.outputs.publication-channel }}
|
|
154
|
+
target-ref: \${{ steps.route.outputs.target-ref }}
|
|
155
|
+
router-ref: \${{ steps.router.outputs.ref }}
|
|
156
|
+
router-sha: \${{ steps.identities.outputs.router-sha }}
|
|
157
|
+
shell-ref: \${{ steps.route.outputs.shell-ref }}
|
|
158
|
+
shell-sha: \${{ steps.identities.outputs.shell-sha }}
|
|
159
|
+
runtime-ref: \${{ steps.route.outputs.runtime-ref }}
|
|
160
|
+
runtime-sha: \${{ steps.identities.outputs.runtime-sha }}
|
|
161
|
+
contract-lock-path: \${{ steps.lock.outputs.path }}
|
|
162
|
+
contract-lock-digest: \${{ steps.lock.outputs.digest }}
|
|
163
|
+
override-used: \${{ steps.route.outputs.override-used }}
|
|
164
|
+
selection-source: \${{ steps.route.outputs.selection-source }}
|
|
165
|
+
reason: \${{ steps.route.outputs.reason }}
|
|
166
|
+
steps:
|
|
167
|
+
- name: Resolve promotion router source
|
|
168
|
+
id: router
|
|
169
|
+
shell: bash
|
|
170
|
+
env:
|
|
171
|
+
BUILDCHAIN_ROUTER_WORKFLOW_REF: \${{ job.workflow_ref }}
|
|
172
|
+
run: |
|
|
173
|
+
set -euo pipefail
|
|
174
|
+
workflow_ref="\${BUILDCHAIN_ROUTER_WORKFLOW_REF}"
|
|
175
|
+
repository="\${workflow_ref%%/.github/workflows/*}"
|
|
176
|
+
ref="\${workflow_ref##*@}"
|
|
177
|
+
ref="\${ref#refs/heads/}"
|
|
178
|
+
ref="\${ref#refs/tags/}"
|
|
179
|
+
if [[ -z "\${repository}" || "\${repository}" = "\${workflow_ref}" || -z "\${ref}" ]]; then
|
|
180
|
+
echo "::error::Unable to resolve promotion router source from job.workflow_ref=\${workflow_ref}"
|
|
181
|
+
exit 1
|
|
182
|
+
fi
|
|
183
|
+
echo "repository=\${repository}" >> "\${GITHUB_OUTPUT}"
|
|
184
|
+
echo "ref=\${ref}" >> "\${GITHUB_OUTPUT}"
|
|
185
|
+
|
|
186
|
+
- name: Checkout promotion router
|
|
187
|
+
uses: actions/checkout@v7.0.0
|
|
188
|
+
with:
|
|
189
|
+
repository: \${{ steps.router.outputs.repository }}
|
|
190
|
+
ref: \${{ steps.router.outputs.ref }}
|
|
191
|
+
path: .buildchain/router
|
|
192
|
+
persist-credentials: false
|
|
193
|
+
|
|
194
|
+
- name: Resolve promotion channel
|
|
195
|
+
id: route
|
|
196
|
+
shell: bash
|
|
197
|
+
run: >-
|
|
198
|
+
node .buildchain/router/scripts/promotion-channel-router.mjs
|
|
199
|
+
--cwd .buildchain/router
|
|
200
|
+
--channel "\${{ inputs.buildchain-channel }}"
|
|
201
|
+
--buildchain-ref "\${{ inputs.buildchain-ref }}"
|
|
202
|
+
--publication-channel "\${{ inputs.channel }}"
|
|
203
|
+
--target-ref "\${{ inputs.target-ref || github.ref_name }}"
|
|
204
|
+
--router-ref "\${{ steps.router.outputs.ref }}"
|
|
205
|
+
|
|
206
|
+
- name: Authorize trusted runtime override
|
|
207
|
+
if: \${{ steps.route.outputs.override-used == 'true' }}
|
|
208
|
+
uses: actions/github-script@v8
|
|
209
|
+
with:
|
|
210
|
+
script: |
|
|
211
|
+
if (context.eventName !== "workflow_dispatch") {
|
|
212
|
+
throw new Error("promotion runtime override is only allowed for trusted workflow_dispatch runs");
|
|
213
|
+
}
|
|
214
|
+
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
215
|
+
owner: context.repo.owner,
|
|
216
|
+
repo: context.repo.repo,
|
|
217
|
+
username: context.actor,
|
|
218
|
+
});
|
|
219
|
+
const level = permission.data.user?.permissions || permission.data.permission || "none";
|
|
220
|
+
if (!["write", "maintain", "admin"].includes(level)) {
|
|
221
|
+
throw new Error(\`promotion runtime override requires write, maintain, or admin permission; actor has \${level}\`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- name: Checkout selected promotion workflow shell
|
|
225
|
+
uses: actions/checkout@v7.0.0
|
|
226
|
+
with:
|
|
227
|
+
repository: \${{ steps.router.outputs.repository }}
|
|
228
|
+
ref: \${{ steps.route.outputs.shell-ref }}
|
|
229
|
+
path: .buildchain/shell
|
|
230
|
+
persist-credentials: false
|
|
231
|
+
|
|
232
|
+
- name: Checkout selected Buildchain runtime
|
|
233
|
+
uses: actions/checkout@v7.0.0
|
|
234
|
+
with:
|
|
235
|
+
repository: \${{ steps.router.outputs.repository }}
|
|
236
|
+
ref: \${{ steps.route.outputs.runtime-ref }}
|
|
237
|
+
path: .buildchain/runtime
|
|
238
|
+
persist-credentials: false
|
|
239
|
+
|
|
240
|
+
- name: Checkout promotion source
|
|
241
|
+
uses: actions/checkout@v7.0.0
|
|
242
|
+
with:
|
|
243
|
+
ref: \${{ inputs.target-sha || github.sha }}
|
|
244
|
+
path: .buildchain/source
|
|
245
|
+
persist-credentials: false
|
|
246
|
+
|
|
247
|
+
- name: Select and verify consumer contract lock
|
|
248
|
+
id: lock
|
|
249
|
+
shell: bash
|
|
250
|
+
env:
|
|
251
|
+
EXPLICIT_PATH: \${{ inputs.buildchain-contract-lock-path }}
|
|
252
|
+
ALPHA_PATH: \${{ inputs.buildchain-alpha-contract-lock-path }}
|
|
253
|
+
STABLE_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}
|
|
254
|
+
CHANNEL: \${{ steps.route.outputs.channel }}
|
|
255
|
+
run: |
|
|
256
|
+
set -euo pipefail
|
|
257
|
+
path="\${EXPLICIT_PATH}"
|
|
258
|
+
if [[ -z "\${path}" ]]; then
|
|
259
|
+
if [[ "\${CHANNEL}" = "alpha" ]]; then path="\${ALPHA_PATH}"; else path="\${STABLE_PATH}"; fi
|
|
260
|
+
fi
|
|
261
|
+
if [[ -z "\${path}" || ! -f ".buildchain/source/\${path}" ]]; then
|
|
262
|
+
echo "::error::Selected promotion contract lock is missing: \${path:-<empty>}"
|
|
263
|
+
exit 1
|
|
264
|
+
fi
|
|
265
|
+
digest="sha256:$(shasum -a 256 ".buildchain/source/\${path}" | awk '{print $1}')"
|
|
266
|
+
echo "path=\${path}" >> "\${GITHUB_OUTPUT}"
|
|
267
|
+
echo "digest=\${digest}" >> "\${GITHUB_OUTPUT}"
|
|
268
|
+
|
|
269
|
+
- name: Resolve immutable promotion identities
|
|
270
|
+
id: identities
|
|
271
|
+
shell: bash
|
|
272
|
+
run: |
|
|
273
|
+
set -euo pipefail
|
|
274
|
+
test -f .buildchain/shell/.github/workflows/.release-candidate-promote.yml
|
|
275
|
+
{
|
|
276
|
+
echo "router-sha=$(git -C .buildchain/router rev-parse HEAD)"
|
|
277
|
+
echo "shell-sha=$(git -C .buildchain/shell rev-parse HEAD)"
|
|
278
|
+
echo "runtime-sha=$(git -C .buildchain/runtime rev-parse HEAD)"
|
|
279
|
+
} >> "\${GITHUB_OUTPUT}"
|
|
280
|
+
|
|
281
|
+
alpha:
|
|
282
|
+
name: Promote with alpha workflow shell
|
|
283
|
+
needs: resolve-promotion
|
|
284
|
+
if: \${{ needs.resolve-promotion.outputs.channel == 'alpha' }}
|
|
285
|
+
uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@v${major}-alpha
|
|
286
|
+
permissions:
|
|
287
|
+
actions: read
|
|
288
|
+
checks: write
|
|
289
|
+
contents: write
|
|
290
|
+
id-token: write
|
|
291
|
+
issues: write
|
|
292
|
+
with:
|
|
293
|
+
${forwarded}
|
|
294
|
+
secrets: inherit
|
|
295
|
+
|
|
296
|
+
stable:
|
|
297
|
+
name: Promote with stable workflow shell
|
|
298
|
+
needs: resolve-promotion
|
|
299
|
+
if: \${{ needs.resolve-promotion.outputs.channel == 'stable' }}
|
|
300
|
+
uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@v${major}
|
|
301
|
+
permissions:
|
|
302
|
+
actions: read
|
|
303
|
+
checks: write
|
|
304
|
+
contents: write
|
|
305
|
+
id-token: write
|
|
306
|
+
issues: write
|
|
307
|
+
with:
|
|
308
|
+
${forwarded}
|
|
309
|
+
secrets: inherit
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function main() {
|
|
314
|
+
const source = fs.readFileSync(sourcePath, "utf8");
|
|
315
|
+
const version = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
316
|
+
const major = Number(String(version).match(/^(\d+)\./)?.[1]);
|
|
317
|
+
if (!Number.isInteger(major)) throw new Error("package version must expose a numeric major");
|
|
318
|
+
const generated = generateChannelPromotionWorkflow(source, { major });
|
|
319
|
+
if (process.argv.includes("--check")) {
|
|
320
|
+
const current = fs.existsSync(targetPath) ? fs.readFileSync(targetPath, "utf8") : "";
|
|
321
|
+
if (current !== generated) {
|
|
322
|
+
console.error(".github/workflows/release-candidate-promote.yml is stale; run node scripts/generate-channel-promotion-workflow.mjs");
|
|
323
|
+
process.exitCode = 1;
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
fs.writeFileSync(targetPath, generated);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
331
|
+
if (isMain) main();
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
createReadmeBadgeEndpointRegistry,
|
|
19
19
|
} from "../packages/core/readme-badges.js";
|
|
20
|
+
import {
|
|
21
|
+
RELEASE_PASSPORT_SCHEMA,
|
|
22
|
+
createReleasePassportCheckManifest,
|
|
23
|
+
} from "../packages/core/release-passport-contract.js";
|
|
20
24
|
import {
|
|
21
25
|
collectPublicSurfaceReverseAudit,
|
|
22
26
|
enumerateActionInputs,
|
|
@@ -496,6 +500,7 @@ function nodeApiMeta(exportName) {
|
|
|
496
500
|
"./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
|
|
497
501
|
"./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
|
|
498
502
|
"./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
|
|
503
|
+
"./release-passport-contract": { group: "release-passport-trust", summary: "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs." },
|
|
499
504
|
"./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
|
|
500
505
|
"./stable-candidate-ledger": { group: "governance-versioning", summary: "Immutable alpha candidate ledger, qualification, revocation, selection, and exact stable source-lock APIs." },
|
|
501
506
|
"./release-propagation": { group: "site-and-propagation", summary: "Release propagation graph, plan, and exact upstream lock APIs." },
|
|
@@ -915,6 +920,8 @@ function buildSiteBundle() {
|
|
|
915
920
|
entrypoint: "buildchain.release.json",
|
|
916
921
|
bundle: "buildchain-release-bundle.tar.gz",
|
|
917
922
|
contract: "kungfu-buildchain-release-passport",
|
|
923
|
+
schema: "schemas/release-passport-v1.schema.json",
|
|
924
|
+
checkManifest: "release-passport-check-manifest.json",
|
|
918
925
|
},
|
|
919
926
|
releasePropagation: {
|
|
920
927
|
graphContract: "kungfu-buildchain-release-propagation-graph",
|
|
@@ -956,6 +963,8 @@ function buildSiteBundle() {
|
|
|
956
963
|
contract: "kungfu-buildchain-artifact-schema-index",
|
|
957
964
|
releasePassport: [
|
|
958
965
|
"buildchain.release.json",
|
|
966
|
+
"release-passport-check-manifest.json",
|
|
967
|
+
"schemas/release-passport-v1.schema.json",
|
|
959
968
|
"artifact-evidence.json",
|
|
960
969
|
"product-mechanism.json",
|
|
961
970
|
"impact.json",
|
|
@@ -981,6 +990,8 @@ function buildSiteBundle() {
|
|
|
981
990
|
"public-surface-audit.json",
|
|
982
991
|
"release-model.json",
|
|
983
992
|
"artifact-schemas.json",
|
|
993
|
+
"release-passport-check-manifest.json",
|
|
994
|
+
"schemas/release-passport-v1.schema.json",
|
|
984
995
|
"buildchain-contract.json",
|
|
985
996
|
"kfd-claims.json",
|
|
986
997
|
"product-mechanism.json",
|
|
@@ -1090,6 +1101,8 @@ function buildSiteBundle() {
|
|
|
1090
1101
|
"publication-authority-registry.json",
|
|
1091
1102
|
"public-surface-audit.json",
|
|
1092
1103
|
"artifact-schemas.json",
|
|
1104
|
+
"release-passport-check-manifest.json",
|
|
1105
|
+
"schemas/release-passport-v1.schema.json",
|
|
1093
1106
|
"buildchain-contract.json",
|
|
1094
1107
|
"kfd-upstream-aggregate.json",
|
|
1095
1108
|
"kfd-claims.json",
|
|
@@ -1260,6 +1273,8 @@ function buildSiteBundle() {
|
|
|
1260
1273
|
"public-surface-audit.json": publicSurfaceAudit,
|
|
1261
1274
|
"release-model.json": releaseModel,
|
|
1262
1275
|
"artifact-schemas.json": artifactSchemas,
|
|
1276
|
+
"release-passport-check-manifest.json": createReleasePassportCheckManifest(),
|
|
1277
|
+
"schemas/release-passport-v1.schema.json": RELEASE_PASSPORT_SCHEMA,
|
|
1263
1278
|
"badge-endpoint-registry.json": badgeEndpointRegistry,
|
|
1264
1279
|
"publication-registry.json": publicationRegistry,
|
|
1265
1280
|
"buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
import { resolveBuildchainChannel } from "./buildchain-channel-router.mjs";
|
|
7
|
+
|
|
8
|
+
const TARGETS = [
|
|
9
|
+
{ pattern: /^alpha\/v(\d+)\/v\d+\.\d+$/, publicationChannel: "alpha", shellChannel: "alpha" },
|
|
10
|
+
{ pattern: /^release\/v(\d+)\/v\d+\.\d+$/, publicationChannel: "release", shellChannel: "stable" },
|
|
11
|
+
{ pattern: /^(?:publish-gate\/major|major-gate)$/, publicationChannel: "major", shellChannel: "stable" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function normalized(value) {
|
|
15
|
+
return String(value ?? "").trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function targetIntent(targetRef, requestedPublicationChannel = "") {
|
|
19
|
+
const ref = normalized(targetRef).replace(/^refs\/heads\//, "");
|
|
20
|
+
const target = TARGETS.find((entry) => entry.pattern.test(ref));
|
|
21
|
+
if (!target) throw new Error(`unsupported promotion target ref: ${ref || "<empty>"}`);
|
|
22
|
+
const requested = normalized(requestedPublicationChannel).toLowerCase();
|
|
23
|
+
if (requested && requested !== target.publicationChannel) {
|
|
24
|
+
throw new Error(`promotion channel ${requested} does not match target ref ${ref} (${target.publicationChannel})`);
|
|
25
|
+
}
|
|
26
|
+
const match = ref.match(target.pattern);
|
|
27
|
+
return {
|
|
28
|
+
targetRef: ref,
|
|
29
|
+
publicationChannel: target.publicationChannel,
|
|
30
|
+
shellChannel: target.shellChannel,
|
|
31
|
+
targetMajor: match?.[1] ? Number(match[1]) : undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolvePromotionChannel({
|
|
36
|
+
requestedChannel = "auto",
|
|
37
|
+
requestedRef = "",
|
|
38
|
+
publicationChannel = "",
|
|
39
|
+
targetRef = "",
|
|
40
|
+
routerRef = "",
|
|
41
|
+
packageVersion = "",
|
|
42
|
+
} = {}) {
|
|
43
|
+
const intent = targetIntent(targetRef, publicationChannel);
|
|
44
|
+
const selected = resolveBuildchainChannel({
|
|
45
|
+
requestedChannel,
|
|
46
|
+
requestedRef,
|
|
47
|
+
publishChannel: intent.publicationChannel,
|
|
48
|
+
eventName: "workflow_call",
|
|
49
|
+
routerRef,
|
|
50
|
+
packageVersion,
|
|
51
|
+
});
|
|
52
|
+
const overrideUsed = selected.channel === "override";
|
|
53
|
+
if (!overrideUsed && selected.channel !== intent.shellChannel) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`promotion target ${intent.targetRef} requires ${intent.shellChannel} shell/runtime, got ${selected.channel}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (Number.isInteger(intent.targetMajor) && selected.major !== intent.targetMajor) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`promotion target ${intent.targetRef} requires Buildchain v${intent.targetMajor}, got v${selected.major}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const shellRef = intent.shellChannel === "alpha" ? `v${selected.major}-alpha` : `v${selected.major}`;
|
|
64
|
+
return {
|
|
65
|
+
targetRef: intent.targetRef,
|
|
66
|
+
publicationChannel: intent.publicationChannel,
|
|
67
|
+
channel: intent.shellChannel,
|
|
68
|
+
major: selected.major,
|
|
69
|
+
shellRef,
|
|
70
|
+
runtimeRef: selected.buildchainRef,
|
|
71
|
+
overrideUsed,
|
|
72
|
+
selectionSource: selected.selectionSource,
|
|
73
|
+
reason: selected.reason,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseArgs(argv) {
|
|
78
|
+
const values = {};
|
|
79
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
80
|
+
const token = argv[index];
|
|
81
|
+
if (!token.startsWith("--")) throw new Error(`unexpected argument: ${token}`);
|
|
82
|
+
const value = argv[index + 1];
|
|
83
|
+
if (value === undefined || value.startsWith("--")) throw new Error(`missing value for ${token}`);
|
|
84
|
+
values[token.slice(2)] = value;
|
|
85
|
+
index += 1;
|
|
86
|
+
}
|
|
87
|
+
return values;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readPackageVersion(cwd) {
|
|
91
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8")).version;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function writeOutputs(file, result) {
|
|
95
|
+
const outputs = {
|
|
96
|
+
"target-ref": result.targetRef,
|
|
97
|
+
"publication-channel": result.publicationChannel,
|
|
98
|
+
channel: result.channel,
|
|
99
|
+
major: String(result.major),
|
|
100
|
+
"shell-ref": result.shellRef,
|
|
101
|
+
"runtime-ref": result.runtimeRef,
|
|
102
|
+
"override-used": String(result.overrideUsed),
|
|
103
|
+
"selection-source": result.selectionSource,
|
|
104
|
+
reason: result.reason,
|
|
105
|
+
};
|
|
106
|
+
fs.appendFileSync(file, Object.entries(outputs).map(([key, value]) => `${key}=${value}\n`).join(""));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function main() {
|
|
110
|
+
const args = parseArgs(process.argv.slice(2));
|
|
111
|
+
const cwd = path.resolve(args.cwd || process.cwd());
|
|
112
|
+
const result = resolvePromotionChannel({
|
|
113
|
+
requestedChannel: args.channel,
|
|
114
|
+
requestedRef: args["buildchain-ref"],
|
|
115
|
+
publicationChannel: args["publication-channel"],
|
|
116
|
+
targetRef: args["target-ref"],
|
|
117
|
+
routerRef: args["router-ref"],
|
|
118
|
+
packageVersion: readPackageVersion(cwd),
|
|
119
|
+
});
|
|
120
|
+
if (process.env.GITHUB_OUTPUT) writeOutputs(process.env.GITHUB_OUTPUT, result);
|
|
121
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
125
|
+
if (isMain) {
|
|
126
|
+
try {
|
|
127
|
+
main();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`promotion-channel-router: ${error.message}`);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { validatePackageManagerContract } from "../packages/core/package-manager.js";
|
|
6
|
+
|
|
7
|
+
const cwd = path.resolve(
|
|
8
|
+
process.env.BUILDCHAIN_PACKAGE_MANAGER_CWD || process.argv[2] || process.cwd(),
|
|
9
|
+
);
|
|
10
|
+
const expectedManager = process.env.BUILDCHAIN_EXPECTED_PACKAGE_MANAGER || "";
|
|
11
|
+
const contract = validatePackageManagerContract({ cwd, expectedManager });
|
|
12
|
+
|
|
13
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
14
|
+
fs.appendFileSync(
|
|
15
|
+
process.env.GITHUB_OUTPUT,
|
|
16
|
+
`name=${contract.name}\ndeclared-spec=${contract.declaredSpec}\ndeclared-version=${contract.declaredVersion}\n`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(JSON.stringify({
|
|
21
|
+
schemaVersion: 1,
|
|
22
|
+
contract: "kungfu-buildchain-consumer-package-manager",
|
|
23
|
+
...contract,
|
|
24
|
+
}, null, 2));
|