@kungfu-tech/buildchain 3.0.2 → 3.0.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +146 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- package/scripts/web-surface-production-decision.mjs +19 -3
|
@@ -4,8 +4,14 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
createArtifactSigningReceipt,
|
|
9
|
+
validateArtifactSigningRequest,
|
|
10
|
+
} from "../packages/core/artifact-signing.js";
|
|
11
|
+
import {
|
|
12
|
+
artifactSigningEvidenceDigest,
|
|
13
|
+
createArtifactSigningResult,
|
|
14
|
+
} from "../packages/core/artifact-signing-result.js";
|
|
9
15
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
10
16
|
|
|
11
17
|
function required(value, label) {
|
|
@@ -21,7 +27,8 @@ function sha256File(filePath) {
|
|
|
21
27
|
function resolveBelow(root, relative, label) {
|
|
22
28
|
const target = path.resolve(root, required(relative, label));
|
|
23
29
|
const rel = path.relative(path.resolve(root), target);
|
|
24
|
-
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
30
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
31
|
+
throw new Error(`${label} escapes its root`);
|
|
25
32
|
return target;
|
|
26
33
|
}
|
|
27
34
|
|
|
@@ -30,67 +37,214 @@ export function finalizeNativeArtifactSigningResult({
|
|
|
30
37
|
requestPath = process.env.BUILDCHAIN_SIGNING_REQUEST_PATH,
|
|
31
38
|
signedPayload = process.env.BUILDCHAIN_SIGNED_PAYLOAD,
|
|
32
39
|
evidencePath = process.env.BUILDCHAIN_SIGNING_EVIDENCE,
|
|
40
|
+
credentialArtifactRoot =
|
|
41
|
+
process.env.BUILDCHAIN_SIGNING_CREDENTIAL_ARTIFACT_ROOT,
|
|
33
42
|
outputRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
|
|
34
43
|
checks = process.env.BUILDCHAIN_SIGNING_VERIFICATION_CHECKS,
|
|
35
44
|
} = {}) {
|
|
36
45
|
const requests = path.resolve(required(requestRoot, "signing request root"));
|
|
37
|
-
const request = JSON.parse(
|
|
46
|
+
const request = JSON.parse(
|
|
47
|
+
fs.readFileSync(
|
|
48
|
+
resolveBelow(requests, requestPath, "request path"),
|
|
49
|
+
"utf8",
|
|
50
|
+
),
|
|
51
|
+
);
|
|
38
52
|
const requestCheck = validateArtifactSigningRequest(request);
|
|
39
|
-
if (!requestCheck.ok)
|
|
53
|
+
if (!requestCheck.ok)
|
|
54
|
+
throw new Error(
|
|
55
|
+
`invalid signing request: ${requestCheck.issues.join(", ")}`,
|
|
56
|
+
);
|
|
40
57
|
if (request.signature.semantics !== "native-platform-signature") {
|
|
41
|
-
throw new Error(
|
|
58
|
+
throw new Error(
|
|
59
|
+
"native result finalizer rejects non-native signature profiles",
|
|
60
|
+
);
|
|
42
61
|
}
|
|
43
62
|
const payloadSource = path.resolve(required(signedPayload, "signed payload"));
|
|
44
|
-
const evidenceSource = path.resolve(
|
|
45
|
-
|
|
63
|
+
const evidenceSource = path.resolve(
|
|
64
|
+
required(evidencePath, "signing evidence"),
|
|
65
|
+
);
|
|
66
|
+
const resultDirectory = path.resolve(
|
|
67
|
+
required(outputRoot, "signing result root"),
|
|
68
|
+
);
|
|
46
69
|
fs.mkdirSync(path.join(resultDirectory, "payload"), { recursive: true });
|
|
47
|
-
const payloadPath = path.join(
|
|
70
|
+
const payloadPath = path.join(
|
|
71
|
+
resultDirectory,
|
|
72
|
+
"payload",
|
|
73
|
+
path.basename(payloadSource),
|
|
74
|
+
);
|
|
48
75
|
fs.copyFileSync(payloadSource, payloadPath, fs.constants.COPYFILE_EXCL);
|
|
49
76
|
const evidenceOutput = path.join(resultDirectory, "provider-evidence.json");
|
|
50
77
|
fs.copyFileSync(evidenceSource, evidenceOutput, fs.constants.COPYFILE_EXCL);
|
|
51
78
|
const evidenceDocument = JSON.parse(fs.readFileSync(evidenceOutput, "utf8"));
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
const appBundleResult = request.artifact.kind === "app-bundle";
|
|
80
|
+
const providerEvidencePassed = appBundleResult
|
|
81
|
+
? evidenceDocument.schema ===
|
|
82
|
+
"buildchain.macos-credential-island-evidence/v1" &&
|
|
83
|
+
evidenceDocument.status === "accepted" &&
|
|
84
|
+
evidenceDocument.source?.repository === request.source.repository &&
|
|
85
|
+
evidenceDocument.source?.sha === request.source.sha &&
|
|
86
|
+
evidenceDocument.source?.treeSha === request.source.treeSha &&
|
|
87
|
+
evidenceDocument.buildchain?.runtimeSha === request.runtime.sha &&
|
|
88
|
+
evidenceDocument.app?.architecture === request.artifact.arch &&
|
|
89
|
+
evidenceDocument.notarization?.application?.status === "Accepted" &&
|
|
90
|
+
evidenceDocument.notarization?.diskImage?.status === "Accepted"
|
|
91
|
+
: evidenceDocument.status === "passed" &&
|
|
92
|
+
evidenceDocument.provider === request.signature.provider;
|
|
93
|
+
if (!providerEvidencePassed) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"provider evidence does not prove the requested native signature",
|
|
96
|
+
);
|
|
54
97
|
}
|
|
55
|
-
|
|
98
|
+
let credentialOutput = "";
|
|
99
|
+
let credentialEvidence = [];
|
|
100
|
+
if (appBundleResult) {
|
|
101
|
+
const credentialSource = path.resolve(
|
|
102
|
+
required(
|
|
103
|
+
credentialArtifactRoot,
|
|
104
|
+
"credential artifact root for app-bundle result",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
const credentialManifest = path.join(credentialSource, "manifest.json");
|
|
108
|
+
if (!fs.statSync(credentialManifest).isFile())
|
|
109
|
+
throw new Error("credential artifact manifest is missing");
|
|
110
|
+
credentialOutput = path.join(resultDirectory, "credential-artifact");
|
|
111
|
+
fs.cpSync(credentialSource, credentialOutput, {
|
|
112
|
+
recursive: true,
|
|
113
|
+
force: false,
|
|
114
|
+
errorOnExist: true,
|
|
115
|
+
});
|
|
116
|
+
const manifestOutput = path.join(credentialOutput, "manifest.json");
|
|
117
|
+
const manifest = JSON.parse(fs.readFileSync(manifestOutput, "utf8"));
|
|
118
|
+
if (!Array.isArray(manifest.files) || manifest.files.length === 0)
|
|
119
|
+
throw new Error("credential artifact manifest contains no files");
|
|
120
|
+
credentialEvidence = [
|
|
121
|
+
{
|
|
122
|
+
kind: "credential-artifact-manifest",
|
|
123
|
+
path: "credential-artifact/manifest.json",
|
|
124
|
+
digest: sha256File(manifestOutput),
|
|
125
|
+
},
|
|
126
|
+
...manifest.files.map((file, index) => {
|
|
127
|
+
const filePath = resolveBelow(
|
|
128
|
+
credentialOutput,
|
|
129
|
+
file.path,
|
|
130
|
+
`credential artifact file ${index}`,
|
|
131
|
+
);
|
|
132
|
+
const observed = sha256File(filePath);
|
|
133
|
+
if (observed !== `sha256:${String(file.sha256 || "").toLowerCase()}`)
|
|
134
|
+
throw new Error(
|
|
135
|
+
`credential artifact file digest mismatch: ${file.path}`,
|
|
136
|
+
);
|
|
137
|
+
return {
|
|
138
|
+
kind: `credential-artifact-${index}`,
|
|
139
|
+
path: `credential-artifact/${String(file.path).replaceAll("\\", "/")}`,
|
|
140
|
+
digest: observed,
|
|
141
|
+
};
|
|
142
|
+
}),
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
const evidence = [
|
|
146
|
+
{
|
|
147
|
+
kind: `${request.signature.profile}-verification`,
|
|
148
|
+
path: "provider-evidence.json",
|
|
149
|
+
digest: sha256File(evidenceOutput),
|
|
150
|
+
},
|
|
151
|
+
...credentialEvidence,
|
|
152
|
+
];
|
|
56
153
|
const payloadDigest = sha256File(payloadPath);
|
|
57
154
|
const receipt = createArtifactSigningReceipt({
|
|
58
155
|
request,
|
|
59
|
-
result: {
|
|
60
|
-
|
|
156
|
+
result: {
|
|
157
|
+
artifactDigest: payloadDigest,
|
|
158
|
+
evidenceDigest: artifactSigningEvidenceDigest(evidence),
|
|
159
|
+
},
|
|
160
|
+
signatures: [
|
|
161
|
+
{ kind: request.signature.profile, digest: evidence[0].digest },
|
|
162
|
+
],
|
|
61
163
|
});
|
|
62
|
-
fs.writeFileSync(
|
|
63
|
-
|
|
164
|
+
fs.writeFileSync(
|
|
165
|
+
path.join(resultDirectory, "receipt.json"),
|
|
166
|
+
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
167
|
+
);
|
|
168
|
+
const verificationChecks = String(checks || "")
|
|
169
|
+
.split(",")
|
|
170
|
+
.map((value) => value.trim())
|
|
171
|
+
.filter(Boolean);
|
|
172
|
+
if (
|
|
173
|
+
verificationChecks.length === 0 &&
|
|
174
|
+
Array.isArray(evidenceDocument.checks)
|
|
175
|
+
) {
|
|
176
|
+
verificationChecks.push(
|
|
177
|
+
...evidenceDocument.checks
|
|
178
|
+
.map((value) => String(value).trim())
|
|
179
|
+
.filter(Boolean),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (verificationChecks.length === 0 && appBundleResult) {
|
|
183
|
+
verificationChecks.push(
|
|
184
|
+
...Object.entries(evidenceDocument.verification || {})
|
|
185
|
+
.filter(([, passed]) => passed === true)
|
|
186
|
+
.map(([name]) => name),
|
|
187
|
+
"application-notarytool-accepted",
|
|
188
|
+
"disk-image-notarytool-accepted",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (verificationChecks.length === 0)
|
|
192
|
+
throw new Error("provider evidence contains no verification checks");
|
|
64
193
|
const result = createArtifactSigningResult({
|
|
65
194
|
request,
|
|
66
195
|
receipt,
|
|
67
|
-
payload: {
|
|
196
|
+
payload: {
|
|
197
|
+
path: `payload/${path.basename(payloadPath)}`,
|
|
198
|
+
bytes: fs.statSync(payloadPath).size,
|
|
199
|
+
digest: payloadDigest,
|
|
200
|
+
},
|
|
68
201
|
evidence,
|
|
69
|
-
verification: {
|
|
202
|
+
verification: {
|
|
203
|
+
status: "passed",
|
|
204
|
+
provider: request.signature.provider,
|
|
205
|
+
checks: verificationChecks,
|
|
206
|
+
},
|
|
70
207
|
});
|
|
71
|
-
fs.writeFileSync(
|
|
208
|
+
fs.writeFileSync(
|
|
209
|
+
path.join(resultDirectory, "result.json"),
|
|
210
|
+
`${JSON.stringify(result, null, 2)}\n`,
|
|
211
|
+
);
|
|
72
212
|
const index = {
|
|
73
213
|
schemaVersion: 1,
|
|
74
214
|
contract: "kungfu-buildchain-artifact-signing-result-index/v1",
|
|
75
|
-
results: [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
215
|
+
results: [
|
|
216
|
+
{
|
|
217
|
+
id: request.artifact.id,
|
|
218
|
+
requestDigest: request.digest,
|
|
219
|
+
resultDigest: result.digest,
|
|
220
|
+
result: "result.json",
|
|
221
|
+
payload: result.artifact.path,
|
|
222
|
+
receipt: "receipt.json",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
83
225
|
};
|
|
84
|
-
fs.writeFileSync(
|
|
85
|
-
|
|
226
|
+
fs.writeFileSync(
|
|
227
|
+
path.join(resultDirectory, "index.json"),
|
|
228
|
+
`${JSON.stringify(index, null, 2)}\n`,
|
|
229
|
+
);
|
|
230
|
+
writeGitHubOutputs({
|
|
231
|
+
"result-root": resultDirectory,
|
|
232
|
+
"result-index": path.join(resultDirectory, "index.json"),
|
|
233
|
+
"credential-artifact-root": credentialOutput,
|
|
234
|
+
});
|
|
86
235
|
return index;
|
|
87
236
|
}
|
|
88
237
|
|
|
89
|
-
if (
|
|
238
|
+
if (
|
|
239
|
+
process.argv[1] &&
|
|
240
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
241
|
+
) {
|
|
90
242
|
try {
|
|
91
243
|
finalizeNativeArtifactSigningResult();
|
|
92
244
|
} catch (error) {
|
|
93
|
-
console.error(
|
|
245
|
+
console.error(
|
|
246
|
+
`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
|
|
247
|
+
);
|
|
94
248
|
process.exitCode = 1;
|
|
95
249
|
}
|
|
96
250
|
}
|
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
export const GATE_MATRIX_CONTRACT = "buildchain.shifu-gate-matrix/v1";
|
|
4
4
|
export const GATE_AGGREGATE_CONTRACT = "buildchain.shifu-gate-aggregate/v1";
|
|
5
|
+
export const GATE_JOB_CONTROL_PLANE_OVERHEAD_MINUTES = 30;
|
|
5
6
|
|
|
6
7
|
function stableValue(value) {
|
|
7
8
|
if (Array.isArray(value)) return value.map(stableValue);
|
|
@@ -265,7 +266,11 @@ export function createGateExecutionMatrix({
|
|
|
265
266
|
planDigest: inspected.planDigest,
|
|
266
267
|
timeoutMinutes: Math.max(
|
|
267
268
|
1,
|
|
268
|
-
Math.min(
|
|
269
|
+
Math.min(
|
|
270
|
+
360,
|
|
271
|
+
Math.ceil(timeoutSeconds / 60) +
|
|
272
|
+
GATE_JOB_CONTROL_PLANE_OVERHEAD_MINUTES,
|
|
273
|
+
),
|
|
269
274
|
),
|
|
270
275
|
gates: inspected.gates.map((gate) => ({
|
|
271
276
|
id: gate.id,
|
|
@@ -284,24 +284,15 @@ jobs:
|
|
|
284
284
|
--publication-channel "\${{ inputs.channel }}"
|
|
285
285
|
--target-ref "\${{ inputs.target-ref || github.ref_name }}"
|
|
286
286
|
--router-ref "\${{ steps.router.outputs.ref }}"
|
|
287
|
+
--router-sha "\${{ steps.router.outputs.sha }}"
|
|
287
288
|
|
|
288
289
|
- name: Authorize trusted runtime override
|
|
289
290
|
if: \${{ steps.route.outputs.override-used == 'true' }}
|
|
290
291
|
uses: actions/github-script@v8
|
|
291
292
|
with:
|
|
292
293
|
script: |
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
297
|
-
owner: context.repo.owner,
|
|
298
|
-
repo: context.repo.repo,
|
|
299
|
-
username: context.actor,
|
|
300
|
-
});
|
|
301
|
-
const level = permission.data.user?.permissions || permission.data.permission || "none";
|
|
302
|
-
if (!["write", "maintain", "admin"].includes(level)) {
|
|
303
|
-
throw new Error(\`promotion runtime override requires write, maintain, or admin permission; actor has \${level}\`);
|
|
304
|
-
}
|
|
294
|
+
const script = require(process.env.GITHUB_WORKSPACE + "/.buildchain/router/scripts/authorize-promotion-runtime-override.cjs");
|
|
295
|
+
await script.authorizePromotionRuntimeOverride({ github, context });
|
|
305
296
|
|
|
306
297
|
- name: Resolve immutable promotion identities
|
|
307
298
|
id: identities
|
|
@@ -29,6 +29,8 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
29
29
|
const gateAggregate = gateAggregateJson ? JSON.parse(gateAggregateJson) : undefined;
|
|
30
30
|
const controllerReceiptJson = env("BUILDCHAIN_CONTROLLER_RECEIPT_JSON");
|
|
31
31
|
const controllerReceipts = controllerReceiptJson ? [JSON.parse(controllerReceiptJson)] : [];
|
|
32
|
+
const familyEvidenceJson = env("BUILDCHAIN_RC_FAMILY_EVIDENCE_JSON");
|
|
33
|
+
const familyEvidence = familyEvidenceJson ? JSON.parse(familyEvidenceJson) : undefined;
|
|
32
34
|
const passport = createReleaseCandidatePassport({
|
|
33
35
|
repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""),
|
|
34
36
|
pullRequest: {
|
|
@@ -51,6 +53,7 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
51
53
|
workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""),
|
|
52
54
|
},
|
|
53
55
|
gateAggregate,
|
|
56
|
+
familyEvidence,
|
|
54
57
|
controllerReceipts,
|
|
55
58
|
workflow: {
|
|
56
59
|
name: env("GITHUB_WORKFLOW"),
|
|
@@ -80,6 +83,7 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
80
83
|
candidateHash: passport.candidateHash,
|
|
81
84
|
platformCount: passport.platformMatrix.length,
|
|
82
85
|
gateProfileEvidence: passport.gateProfileEvidence,
|
|
86
|
+
familyEvidence: passport.familyEvidence,
|
|
83
87
|
controllerReceipts: passport.controllerReceipts || [],
|
|
84
88
|
}),
|
|
85
89
|
});
|
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
} from "../packages/core/public-surface-audit.js";
|
|
35
35
|
import { createSurfaceTimestampPolicy } from "../packages/core/surface-manifest.js";
|
|
36
36
|
import { projectHomepageIntro } from "./site-bundle-homepage.mjs";
|
|
37
|
+
import {
|
|
38
|
+
BUILDCHAIN_COMMAND_REGISTRY,
|
|
39
|
+
resolveBuildchainCommand,
|
|
40
|
+
} from "../bin/internal/command-registry.mjs";
|
|
37
41
|
|
|
38
42
|
const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
|
|
39
43
|
const PUBLICATION_RELEASE_REGISTRY_CONTRACT = "kungfu-buildchain-publication-release-registry";
|
|
@@ -322,6 +326,18 @@ function capabilityGroup(id) {
|
|
|
322
326
|
return id;
|
|
323
327
|
}
|
|
324
328
|
|
|
329
|
+
function publicSurfaceLifecycle({ owner, maturity, nonDuplicationRationale }) {
|
|
330
|
+
return {
|
|
331
|
+
owner,
|
|
332
|
+
maturity,
|
|
333
|
+
introducedVersion: "pre-3.0.2-alpha.4",
|
|
334
|
+
compatibilityPromise: "preserved-through-the-v3-major-line",
|
|
335
|
+
deprecationReplacement: "",
|
|
336
|
+
sunsetCondition: "explicit-breaking-change-review-in-a-future-major-line",
|
|
337
|
+
nonDuplicationRationale,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
325
341
|
const manualMetaById = new Map(Object.entries({
|
|
326
342
|
map: { capabilityGroup: "getting-started", audience: ["agent", "consumer"], maturity: "stable", order: 10 },
|
|
327
343
|
install: { capabilityGroup: "getting-started", audience: ["consumer"], maturity: "stable", order: 20 },
|
|
@@ -477,12 +493,22 @@ function cliCommandMeta(id) {
|
|
|
477
493
|
"portable-cache-plan": { group: "observability-diagnostics", purpose: "Validate a consumer-neutral cache manifest and emit exact GitHub Actions cache inputs." },
|
|
478
494
|
"portable-cache-receipt": { group: "observability-diagnostics", purpose: "Seal exact, compatible, miss, or corrupt provider evidence against one cache plan." },
|
|
479
495
|
"npm-dry-run": { group: "release-passport-trust", purpose: "Verify npm publish shape before a release transaction." },
|
|
496
|
+
paper: { group: "reusable-build", purpose: "Inspect the unified paper repository and publication command families." },
|
|
497
|
+
"paper-alpha": { group: "release-passport-trust", purpose: "Plan or open the protected dev-to-alpha paper publication PR without direct publication." },
|
|
498
|
+
"paper-bootstrap-npm": { group: "release-passport-trust", purpose: "Dry-run or explicitly bootstrap the public npm package and bind GitHub Trusted Publishing." },
|
|
499
|
+
"paper-build": { group: "reusable-build", purpose: "Plan or execute the existing two-clean-build reproducibility gate for a paper." },
|
|
500
|
+
"paper-migrate": { group: "getting-started", purpose: "Plan or write the bounded Buildchain-owned control-file migration for an existing paper repository." },
|
|
501
|
+
"paper-preflight": { group: "reusable-build", purpose: "Report exact source, runtime, permission, npm trust, deterministic build, and release-state readiness." },
|
|
502
|
+
"paper-resume": { group: "release-passport-trust", purpose: "Plan or dispatch recovery of the exact existing sealed paper release transaction." },
|
|
503
|
+
"paper-scaffold": { group: "getting-started", purpose: "Plan or write an idempotent, no-overwrite, release-ready paper repository scaffold." },
|
|
504
|
+
"paper-status": { group: "reusable-build", purpose: "Report explicit evidence for every paper lifecycle state without unsupported inference." },
|
|
480
505
|
"publish-source": { group: "release-passport-trust", purpose: "Create, inspect, or verify publish-gate source-lock refs." },
|
|
481
506
|
"publication-artifact": { group: "reusable-build", purpose: "Generate publication artifact manifests, passports, and source bundles for paper/report repositories." },
|
|
482
507
|
"publication-artifact-manifest": { group: "reusable-build", purpose: "Write a site-consumable publication artifact manifest, publication passport, and source bundle." },
|
|
483
508
|
"publication-artifact-npm-package": { group: "reusable-build", purpose: "Synthesize the declared npm paper package from a publication artifact manifest, passport, registry, source bundle, and primary artifact." },
|
|
484
509
|
project: { group: "release-passport-trust", purpose: "Inspect read-only consumer projection command families." },
|
|
485
510
|
"project-kfx-admission": { group: "release-passport-trust", purpose: "Project one verified sealed envelope into direct KFX admission inputs without reconstructing evidence." },
|
|
511
|
+
"publication-artifact-reproducibility": { group: "reusable-build", purpose: "Prove exact PDF, source bundle, publication evidence, and npm tarball bytes across two independent clean builds." },
|
|
486
512
|
"release-dry-run": { group: "governance-versioning", purpose: "Explain what a channel merge would publish before the PR is merged." },
|
|
487
513
|
"release-line-open": { group: "governance-versioning", purpose: "Plan or write the initial version-state commit for a new minor release line." },
|
|
488
514
|
"release-governance": { group: "governance-versioning", purpose: "Reconcile a managed branch's Buildchain aggregate check to an already-tested pull request SHA." },
|
|
@@ -527,6 +553,9 @@ function nodeApiMeta(exportName) {
|
|
|
527
553
|
"./portable-dev-cache": { group: "observability-diagnostics", summary: "Portable dependency/compiler cache plan, exact-root verification, and provider receipt APIs." },
|
|
528
554
|
"./publication-artifact": { group: "reusable-build", summary: "Publication artifact manifest, source bundle, and publication passport APIs." },
|
|
529
555
|
"./publication-package": { group: "reusable-build", summary: "Publication npm package synthesis APIs for Buildchain-managed paper release presets." },
|
|
556
|
+
"./publication-reproducibility": { group: "reusable-build", summary: "Two-clean-build publication byte reproducibility receipt APIs." },
|
|
557
|
+
"./publication-sealed-bundle": { group: "reusable-build", summary: "Build-once publication bundle manifests and exact-byte resume verification APIs." },
|
|
558
|
+
"./paper": { group: "reusable-build", summary: "Unified paper scaffold, preflight, npm bootstrap, build, Alpha, status, and resume planning APIs." },
|
|
530
559
|
"./publication-authority": { group: "release-passport-trust", summary: "Sealed publication authority registry, runner provenance, control-plane audit, admission, and independent verification APIs." },
|
|
531
560
|
"./publication-control-plane-audit": { group: "release-passport-trust", summary: "Read-only publication control-plane snapshot evaluation APIs." },
|
|
532
561
|
"./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
|
|
@@ -564,6 +593,45 @@ function nodeApiMeta(exportName) {
|
|
|
564
593
|
return { capabilityGroup: capabilityGroup(meta.group), summary: meta.summary, audience: ["developer", "agent"], maturity: "stable" };
|
|
565
594
|
}
|
|
566
595
|
|
|
596
|
+
function createCliRegistry(packageJson) {
|
|
597
|
+
const commands = enumerateCliCommandsFromBin({ root });
|
|
598
|
+
const documentedTopLevelCommands = new Set();
|
|
599
|
+
for (const entry of commands) {
|
|
600
|
+
const command = entry.usage.split(/\s+/)[1] || "";
|
|
601
|
+
const registration = resolveBuildchainCommand(command);
|
|
602
|
+
if (!registration) {
|
|
603
|
+
throw new Error(`CLI help documents an unregistered top-level command: ${command}`);
|
|
604
|
+
}
|
|
605
|
+
documentedTopLevelCommands.add(registration.id);
|
|
606
|
+
}
|
|
607
|
+
for (const entry of BUILDCHAIN_COMMAND_REGISTRY) {
|
|
608
|
+
if (!documentedTopLevelCommands.has(entry.id)) {
|
|
609
|
+
throw new Error(`CLI runtime command is absent from help enumeration: ${entry.id}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
schemaVersion: 1,
|
|
614
|
+
contract: "kungfu-buildchain-cli-registry",
|
|
615
|
+
binary: "buildchain",
|
|
616
|
+
npmPackage: packageJson.name,
|
|
617
|
+
commandSource: "bin/internal/command-registry.mjs plus bin/buildchain.mjs help enumeration",
|
|
618
|
+
commands: commands.map((entry) => {
|
|
619
|
+
const meta = cliCommandMeta(entry.id);
|
|
620
|
+
return {
|
|
621
|
+
...entry,
|
|
622
|
+
purpose: meta.purpose,
|
|
623
|
+
capabilityGroup: meta.capabilityGroup,
|
|
624
|
+
audience: meta.audience,
|
|
625
|
+
...publicSurfaceLifecycle({
|
|
626
|
+
owner: "buildchain-cli",
|
|
627
|
+
maturity: meta.maturity,
|
|
628
|
+
nonDuplicationRationale: "Existing command identity retained for CLI compatibility and discoverability.",
|
|
629
|
+
}),
|
|
630
|
+
};
|
|
631
|
+
}),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
567
635
|
function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nodeApiRegistry, workflowRegistry }) {
|
|
568
636
|
const groupCounts = new Map(CAPABILITY_GROUPS.map((group) => [group.id, {
|
|
569
637
|
manualCount: 0,
|
|
@@ -779,23 +847,7 @@ function buildSiteBundle() {
|
|
|
779
847
|
artifactDigestScope: "npm package dist/site JSON files",
|
|
780
848
|
});
|
|
781
849
|
|
|
782
|
-
const cliRegistry =
|
|
783
|
-
schemaVersion: 1,
|
|
784
|
-
contract: "kungfu-buildchain-cli-registry",
|
|
785
|
-
binary: "buildchain",
|
|
786
|
-
npmPackage: packageJson.name,
|
|
787
|
-
commandSource: "bin/buildchain.mjs reverse enumeration",
|
|
788
|
-
commands: enumerateCliCommandsFromBin({ root }).map((entry) => {
|
|
789
|
-
const meta = cliCommandMeta(entry.id);
|
|
790
|
-
return {
|
|
791
|
-
...entry,
|
|
792
|
-
purpose: meta.purpose,
|
|
793
|
-
capabilityGroup: meta.capabilityGroup,
|
|
794
|
-
audience: meta.audience,
|
|
795
|
-
maturity: meta.maturity,
|
|
796
|
-
};
|
|
797
|
-
}),
|
|
798
|
-
};
|
|
850
|
+
const cliRegistry = createCliRegistry(packageJson);
|
|
799
851
|
|
|
800
852
|
const manualRegistry = {
|
|
801
853
|
schemaVersion: 1,
|
|
@@ -874,7 +926,11 @@ function buildSiteBundle() {
|
|
|
874
926
|
summary: meta.summary,
|
|
875
927
|
capabilityGroup: meta.capabilityGroup,
|
|
876
928
|
audience: meta.audience,
|
|
877
|
-
|
|
929
|
+
...publicSurfaceLifecycle({
|
|
930
|
+
owner: "buildchain-core",
|
|
931
|
+
maturity: meta.maturity,
|
|
932
|
+
nonDuplicationRationale: "Existing package subpath retained as the canonical API boundary for this capability.",
|
|
933
|
+
}),
|
|
878
934
|
};
|
|
879
935
|
}),
|
|
880
936
|
docs: [
|
|
@@ -937,6 +993,11 @@ function buildSiteBundle() {
|
|
|
937
993
|
status: statusById.get(entry.id) || "active",
|
|
938
994
|
}),
|
|
939
995
|
status: statusById.get(entry.id) || "active",
|
|
996
|
+
...publicSurfaceLifecycle({
|
|
997
|
+
owner: "buildchain-workflows",
|
|
998
|
+
maturity: statusById.get(entry.id) || "active",
|
|
999
|
+
nonDuplicationRationale: "Existing workflow identity retained for caller compatibility and repository orchestration.",
|
|
1000
|
+
}),
|
|
940
1001
|
};
|
|
941
1002
|
}),
|
|
942
1003
|
actionSource: "actions/*/action.yml reverse input enumeration",
|
|
@@ -944,6 +1005,11 @@ function buildSiteBundle() {
|
|
|
944
1005
|
...entry,
|
|
945
1006
|
capabilityGroup: actionCapabilityGroup(entry.id),
|
|
946
1007
|
status: "active",
|
|
1008
|
+
...publicSurfaceLifecycle({
|
|
1009
|
+
owner: "buildchain-actions",
|
|
1010
|
+
maturity: "stable",
|
|
1011
|
+
nonDuplicationRationale: "Existing action identity retained as the canonical composite or JavaScript action boundary.",
|
|
1012
|
+
}),
|
|
947
1013
|
})),
|
|
948
1014
|
};
|
|
949
1015
|
const controllerRegistry = createControllerRegistry({ workflows: workflowRegistry.workflows });
|
|
@@ -20,6 +20,30 @@ function resolveBelow(root, relative, label) {
|
|
|
20
20
|
return target;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function projectCredentialArtifact(source, destination) {
|
|
24
|
+
const manifest = JSON.parse(
|
|
25
|
+
fs.readFileSync(path.join(source, "manifest.json"), "utf8"),
|
|
26
|
+
);
|
|
27
|
+
if (!Array.isArray(manifest.files) || manifest.files.length === 0)
|
|
28
|
+
throw new Error("credential artifact manifest contains no files");
|
|
29
|
+
for (const [index, file] of manifest.files.entries()) {
|
|
30
|
+
const from = resolveBelow(
|
|
31
|
+
source,
|
|
32
|
+
file.path,
|
|
33
|
+
`credential artifact file ${index}`,
|
|
34
|
+
);
|
|
35
|
+
if (!fs.statSync(from).isFile() || fs.lstatSync(from).isSymbolicLink())
|
|
36
|
+
throw new Error(`credential artifact file is not regular: ${file.path}`);
|
|
37
|
+
const to = resolveBelow(
|
|
38
|
+
destination,
|
|
39
|
+
file.path,
|
|
40
|
+
`credential artifact projection ${index}`,
|
|
41
|
+
);
|
|
42
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
43
|
+
fs.copyFileSync(from, to, fs.constants.COPYFILE_EXCL);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
export function importArtifactSigningResults({
|
|
24
48
|
workspace = process.env.GITHUB_WORKSPACE || process.cwd(),
|
|
25
49
|
cwd = process.env.BUILDCHAIN_SIGNING_CWD || ".",
|
|
@@ -41,29 +65,64 @@ export function importArtifactSigningResults({
|
|
|
41
65
|
byId.set(entry.id, request);
|
|
42
66
|
}
|
|
43
67
|
const imported = [];
|
|
68
|
+
const credentialArtifacts = [];
|
|
44
69
|
for (const entry of resultIndex.results || []) {
|
|
45
70
|
const request = byId.get(entry.id);
|
|
46
71
|
if (!request) throw new Error(`result has no local sealed request: ${entry.id}`);
|
|
47
72
|
const resultPath = resolveBelow(results, entry.result, "result path");
|
|
48
73
|
const result = JSON.parse(fs.readFileSync(resultPath, "utf8"));
|
|
49
74
|
const payload = resolveBelow(path.dirname(resultPath), result.artifact.path, "signed payload");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
if (request.artifact.kind === "app-bundle") {
|
|
76
|
+
const credentialArtifact = path.join(
|
|
77
|
+
path.dirname(resultPath),
|
|
78
|
+
"credential-artifact",
|
|
79
|
+
);
|
|
80
|
+
if (!fs.statSync(credentialArtifact).isDirectory())
|
|
81
|
+
throw new Error("native app signing result has no credential artifact");
|
|
82
|
+
projectCredentialArtifact(credentialArtifact, sourceRoot);
|
|
83
|
+
} else {
|
|
84
|
+
const target = resolveBelow(
|
|
85
|
+
sourceRoot,
|
|
86
|
+
request.artifact.path,
|
|
87
|
+
"consumer artifact path",
|
|
88
|
+
);
|
|
89
|
+
const mode = fs.statSync(target).mode;
|
|
90
|
+
fs.copyFileSync(payload, target);
|
|
91
|
+
fs.chmodSync(target, mode);
|
|
92
|
+
}
|
|
54
93
|
const destination = path.resolve(sourceRoot, evidenceRoot, entry.id);
|
|
55
94
|
const rel = path.relative(sourceRoot, destination);
|
|
56
95
|
if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error("imported signing evidence escapes consumer source root");
|
|
57
96
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
58
97
|
fs.cpSync(path.dirname(resultPath), destination, { recursive: true, force: false, errorOnExist: true });
|
|
98
|
+
if (request.artifact.kind === "app-bundle") {
|
|
99
|
+
credentialArtifacts.push({
|
|
100
|
+
id: entry.id,
|
|
101
|
+
root: path.join(destination, "credential-artifact"),
|
|
102
|
+
manifest: path.join(
|
|
103
|
+
destination,
|
|
104
|
+
"credential-artifact",
|
|
105
|
+
"manifest.json",
|
|
106
|
+
),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
59
109
|
imported.push({ id: entry.id, path: request.artifact.path, resultDigest: result.digest });
|
|
60
110
|
}
|
|
111
|
+
if (credentialArtifacts.length > 1)
|
|
112
|
+
throw new Error(
|
|
113
|
+
"one platform artifact may project at most one native app release payload",
|
|
114
|
+
);
|
|
61
115
|
writeGitHubOutputs({
|
|
62
116
|
"imported-count": String(imported.length),
|
|
63
117
|
"imported-json": JSON.stringify(imported),
|
|
64
118
|
"evidence-root": path.resolve(sourceRoot, evidenceRoot),
|
|
119
|
+
"credential-artifact-count": String(credentialArtifacts.length),
|
|
120
|
+
"credential-artifact-root":
|
|
121
|
+
credentialArtifacts.length === 1 ? credentialArtifacts[0].root : "",
|
|
122
|
+
"credential-manifest-path":
|
|
123
|
+
credentialArtifacts.length === 1 ? credentialArtifacts[0].manifest : "",
|
|
65
124
|
});
|
|
66
|
-
return { ...verification, imported };
|
|
125
|
+
return { ...verification, imported, credentialArtifacts };
|
|
67
126
|
}
|
|
68
127
|
|
|
69
128
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|