@kungfu-tech/buildchain 3.0.3-alpha.0 → 3.0.3-alpha.2
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 +32 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +19 -1
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +41 -241
- 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 +0 -45
- 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 +161 -42
- package/dist/site/buildchain-site.json +180 -84
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +932 -98
- package/dist/site/controller-registry.json +97 -5
- package/dist/site/kfd-claims.json +316 -23
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +371 -56
- package/dist/site/page-registry.json +155 -59
- package/dist/site/public-surface-audit.json +286 -24
- 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 +570 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +35 -12
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +143 -12
- package/docs/consumer-issue-reporting.md +19 -1
- package/docs/github-governance-authority.md +2 -2
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +185 -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 +31 -31
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +95 -27
- package/docs/runtime-train-validation.md +20 -2
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +6 -4
- package/docs/web-surface-deployments.md +62 -0
- package/package.json +7 -5
- package/packages/core/README.md +61 -4
- package/packages/core/buildchain-contract.js +8 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +10 -25
- package/packages/core/index.js +40 -0
- package/packages/core/paper-fleet.js +260 -0
- package/packages/core/paper-repository.js +210 -0
- package/packages/core/paper-work.js +365 -0
- package/packages/core/paper.js +3180 -0
- package/packages/core/public-surface-audit.js +26 -75
- package/packages/core/public-surface-cli.js +117 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -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 +483 -201
- package/packages/core/release-propagation.js +175 -1
- package/scripts/auditable-demo.mjs +119 -5
- 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-cli-help.mjs +256 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +69 -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 +268 -43
- package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +68 -188
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +7 -0
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/paper-work-fleet-cli.mjs +563 -0
- package/scripts/paper.mjs +631 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +23 -2
- package/scripts/seal-artifact-signing-requests.mjs +13 -6
- package/scripts/site-capability-metadata.mjs +202 -0
- package/scripts/stable-candidate-qualification.mjs +42 -0
- package/scripts/web-surface-core.mjs +272 -86
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { verifyPublicationReproducibility } from "../packages/core/publication-reproducibility.js";
|
|
5
|
+
|
|
6
|
+
function readFlag(args, name, fallback = "") {
|
|
7
|
+
const index = args.indexOf(`--${name}`);
|
|
8
|
+
return index === -1 ? fallback : args[index + 1] || "";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function hasFlag(args, name) {
|
|
12
|
+
return args.includes(`--${name}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function runPublicationReproducibilityCli(args = process.argv.slice(2)) {
|
|
16
|
+
const result = verifyPublicationReproducibility({
|
|
17
|
+
cwd: readFlag(args, "cwd", process.cwd()),
|
|
18
|
+
sourceSha: readFlag(args, "source-sha", ""),
|
|
19
|
+
output: readFlag(
|
|
20
|
+
args,
|
|
21
|
+
"output",
|
|
22
|
+
".buildchain/publication/reproducibility-receipt.json",
|
|
23
|
+
),
|
|
24
|
+
promote: hasFlag(args, "promote"),
|
|
25
|
+
keepWorkspaces: hasFlag(args, "keep-workspaces"),
|
|
26
|
+
pullToolchain: !hasFlag(args, "no-toolchain-pull"),
|
|
27
|
+
packageName: readFlag(args, "package-name", ""),
|
|
28
|
+
allowUnpinnedToolchain: hasFlag(args, "allow-unpinned-toolchain"),
|
|
29
|
+
});
|
|
30
|
+
if (hasFlag(args, "json")) {
|
|
31
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
32
|
+
} else {
|
|
33
|
+
process.stdout.write(
|
|
34
|
+
[
|
|
35
|
+
`publication-reproducibility=${result.status}`,
|
|
36
|
+
`publication-reproducibility-qualifying=${result.qualifying}`,
|
|
37
|
+
`publication-reproducibility-receipt=${result.outputPath}`,
|
|
38
|
+
`publication-reproducibility-digest=${result.receiptDigest}`,
|
|
39
|
+
"",
|
|
40
|
+
].join("\n"),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (!result.qualifying && !hasFlag(args, "allow-unpinned-toolchain")) {
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
} else if (result.status !== "passed") {
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT &&
|
|
53
|
+
process.argv[1] &&
|
|
54
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
55
|
+
) {
|
|
56
|
+
try {
|
|
57
|
+
runPublicationReproducibilityCli();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`publication-reproducibility: ${error.message}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import {
|
|
6
|
+
createReleasePropagationReceipt,
|
|
6
7
|
planReleasePropagation,
|
|
7
8
|
readReleasePropagationJson,
|
|
8
9
|
writeReleasePropagationLock,
|
|
@@ -16,6 +17,14 @@ function usage() {
|
|
|
16
17
|
buildchain release-propagation write-lock --plan <json-or-path>
|
|
17
18
|
[--target <id-or-repo>] [--cwd <dir>]
|
|
18
19
|
[--output <file>] [--json]
|
|
20
|
+
buildchain release-propagation receipt --plan <json-or-path>
|
|
21
|
+
--lock-result <json-or-path>
|
|
22
|
+
--pr-outcome <json-or-path>
|
|
23
|
+
[--target <id-or-repo>]
|
|
24
|
+
[--staging-state <state>]
|
|
25
|
+
[--production-state <state>]
|
|
26
|
+
[--observed-at <iso-8601>]
|
|
27
|
+
[--output <file>] [--json]
|
|
19
28
|
`;
|
|
20
29
|
}
|
|
21
30
|
|
|
@@ -90,6 +99,36 @@ export function runReleasePropagationCli(argv = process.argv.slice(2)) {
|
|
|
90
99
|
}
|
|
91
100
|
return;
|
|
92
101
|
}
|
|
102
|
+
if (mode === "receipt") {
|
|
103
|
+
const plan = readReleasePropagationJson(readFlag(args, "plan"), {
|
|
104
|
+
label: "--plan",
|
|
105
|
+
cwd: process.cwd(),
|
|
106
|
+
});
|
|
107
|
+
const lockResult = readReleasePropagationJson(readFlag(args, "lock-result"), {
|
|
108
|
+
label: "--lock-result",
|
|
109
|
+
cwd: process.cwd(),
|
|
110
|
+
});
|
|
111
|
+
const prOutcome = readReleasePropagationJson(readFlag(args, "pr-outcome"), {
|
|
112
|
+
label: "--pr-outcome",
|
|
113
|
+
cwd: process.cwd(),
|
|
114
|
+
});
|
|
115
|
+
const receipt = createReleasePropagationReceipt({
|
|
116
|
+
plan,
|
|
117
|
+
target: readFlag(args, "target", ""),
|
|
118
|
+
lockResult,
|
|
119
|
+
prOutcome,
|
|
120
|
+
stagingState: readFlag(args, "staging-state", "pending"),
|
|
121
|
+
productionState: readFlag(args, "production-state", "not-requested"),
|
|
122
|
+
observedAt: readFlag(args, "observed-at", ""),
|
|
123
|
+
});
|
|
124
|
+
writeOutput(readFlag(args, "output", ""), receipt);
|
|
125
|
+
if (hasFlag(args, "json")) {
|
|
126
|
+
printJson(receipt);
|
|
127
|
+
} else {
|
|
128
|
+
process.stdout.write(`release propagation receipt: ${receipt.receiptSha256}\n`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
93
132
|
throw new Error(`unsupported release-propagation command: ${mode}`);
|
|
94
133
|
}
|
|
95
134
|
|
|
@@ -23,6 +23,12 @@ export function resolveBuildContractCli() {
|
|
|
23
23
|
const resolved = resolveRunnerMatrix({
|
|
24
24
|
runnerPreset: process.env.BUILDCHAIN_RUNNER_PRESET || "github-hosted",
|
|
25
25
|
platformsJson: process.env.BUILDCHAIN_PLATFORMS_JSON || "",
|
|
26
|
+
awsCodeBuildProject:
|
|
27
|
+
process.env.BUILDCHAIN_AWS_CODEBUILD_PROJECT || "",
|
|
28
|
+
awsEc2WindowsRunnerLabel:
|
|
29
|
+
process.env.BUILDCHAIN_AWS_EC2_WINDOWS_RUNNER_LABEL || "",
|
|
30
|
+
awsEc2MacosRunnerLabel:
|
|
31
|
+
process.env.BUILDCHAIN_AWS_EC2_MACOS_RUNNER_LABEL || "",
|
|
26
32
|
linuxContainerPreset: process.env.BUILDCHAIN_LINUX_CONTAINER_PRESET || "",
|
|
27
33
|
linuxContainerImage: process.env.BUILDCHAIN_LINUX_CONTAINER_IMAGE || "",
|
|
28
34
|
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import {
|
|
4
|
+
resolveRunnerMatrix,
|
|
5
|
+
writeGitHubOutputs,
|
|
6
|
+
} from "./build-contract-core.mjs";
|
|
7
|
+
|
|
8
|
+
export const HOSTED_RUNNER_FALLBACKS = Object.freeze({
|
|
9
|
+
"linux-x64": '["ubuntu-24.04"]',
|
|
10
|
+
"macos-arm64": '["macos-15"]',
|
|
11
|
+
"windows-x64": '["windows-2022"]',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function runnerLabels(runner) {
|
|
15
|
+
const labels = JSON.parse(String(runner || "[]"));
|
|
16
|
+
if (
|
|
17
|
+
!Array.isArray(labels) ||
|
|
18
|
+
labels.some((label) => typeof label !== "string")
|
|
19
|
+
) {
|
|
20
|
+
throw new Error("runner must be a JSON array of label strings");
|
|
21
|
+
}
|
|
22
|
+
return labels;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function runnerHasLabels(runner, requiredLabels) {
|
|
26
|
+
const labels = new Set(
|
|
27
|
+
(runner.labels || []).map((label) => String(label?.name || label)),
|
|
28
|
+
);
|
|
29
|
+
return requiredLabels.every((label) => labels.has(label));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function routeOfflineRunners({
|
|
33
|
+
platforms,
|
|
34
|
+
runners,
|
|
35
|
+
fallbacks = HOSTED_RUNNER_FALLBACKS,
|
|
36
|
+
}) {
|
|
37
|
+
const routes = [];
|
|
38
|
+
const routedPlatforms = platforms.map((platform) => {
|
|
39
|
+
const requiredLabels = runnerLabels(platform.runner);
|
|
40
|
+
if (!requiredLabels.includes("self-hosted")) {
|
|
41
|
+
routes.push({
|
|
42
|
+
id: platform.id,
|
|
43
|
+
status: "hosted",
|
|
44
|
+
selected: "declared-runner",
|
|
45
|
+
});
|
|
46
|
+
return platform;
|
|
47
|
+
}
|
|
48
|
+
const matching = runners.filter((runner) =>
|
|
49
|
+
runnerHasLabels(runner, requiredLabels),
|
|
50
|
+
);
|
|
51
|
+
const online = matching.filter((runner) => runner.status === "online");
|
|
52
|
+
const busyOnline = online.filter((runner) => runner.busy);
|
|
53
|
+
const fallbackRunner = fallbacks[platform.id] || "";
|
|
54
|
+
const offline = online.length === 0;
|
|
55
|
+
const selected =
|
|
56
|
+
offline && fallbackRunner
|
|
57
|
+
? "github-hosted-offline-fallback"
|
|
58
|
+
: "self-hosted";
|
|
59
|
+
routes.push({
|
|
60
|
+
id: platform.id,
|
|
61
|
+
status: offline ? "offline" : "online",
|
|
62
|
+
selected,
|
|
63
|
+
matchingRunnerCount: matching.length,
|
|
64
|
+
onlineRunnerCount: online.length,
|
|
65
|
+
busyOnlineRunnerCount: busyOnline.length,
|
|
66
|
+
fallbackAvailable: Boolean(fallbackRunner),
|
|
67
|
+
});
|
|
68
|
+
if (selected !== "github-hosted-offline-fallback") {
|
|
69
|
+
return platform;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...platform,
|
|
73
|
+
runner: fallbackRunner,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
platforms: routedPlatforms,
|
|
78
|
+
platformsJson: JSON.stringify(routedPlatforms),
|
|
79
|
+
fallbackCount: routes.filter(
|
|
80
|
+
(route) => route.selected === "github-hosted-offline-fallback",
|
|
81
|
+
).length,
|
|
82
|
+
routing: {
|
|
83
|
+
schema: "buildchain.runner-offline-routing/v1",
|
|
84
|
+
status: "observed",
|
|
85
|
+
routes,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function fetchGitHubJson({ url, token, fetchImpl }) {
|
|
91
|
+
const response = await fetchImpl(url, {
|
|
92
|
+
headers: {
|
|
93
|
+
accept: "application/vnd.github+json",
|
|
94
|
+
authorization: `Bearer ${token}`,
|
|
95
|
+
"x-github-api-version": "2022-11-28",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`runner inventory request failed with status ${response.status}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return response.json();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function listRepositoryRunners({
|
|
107
|
+
repository,
|
|
108
|
+
token,
|
|
109
|
+
apiUrl = "https://api.github.com",
|
|
110
|
+
fetchImpl = fetch,
|
|
111
|
+
}) {
|
|
112
|
+
const repositoryPayload = await fetchGitHubJson({
|
|
113
|
+
url: `${apiUrl}/repos/${repository}`,
|
|
114
|
+
token,
|
|
115
|
+
fetchImpl,
|
|
116
|
+
});
|
|
117
|
+
const ownerLogin = String(repositoryPayload?.owner?.login || "").trim();
|
|
118
|
+
const ownerType = String(repositoryPayload?.owner?.type || "").trim();
|
|
119
|
+
const inventoryPath =
|
|
120
|
+
ownerType === "Organization" && ownerLogin
|
|
121
|
+
? `/orgs/${ownerLogin}/actions/runners`
|
|
122
|
+
: `/repos/${repository}/actions/runners`;
|
|
123
|
+
const runners = [];
|
|
124
|
+
for (let page = 1; ; page += 1) {
|
|
125
|
+
const payload = await fetchGitHubJson({
|
|
126
|
+
url: `${apiUrl}${inventoryPath}?per_page=100&page=${page}`,
|
|
127
|
+
token,
|
|
128
|
+
fetchImpl,
|
|
129
|
+
});
|
|
130
|
+
const pageRunners = Array.isArray(payload.runners) ? payload.runners : [];
|
|
131
|
+
runners.push(...pageRunners);
|
|
132
|
+
if (pageRunners.length < 100) {
|
|
133
|
+
return {
|
|
134
|
+
runners,
|
|
135
|
+
inventoryScope:
|
|
136
|
+
ownerType === "Organization" && ownerLogin
|
|
137
|
+
? "organization"
|
|
138
|
+
: "repository",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function resolveOfflineRunnerFallback({
|
|
145
|
+
runnerPreset,
|
|
146
|
+
platformsJson = "",
|
|
147
|
+
repository,
|
|
148
|
+
token,
|
|
149
|
+
apiUrl,
|
|
150
|
+
fetchImpl,
|
|
151
|
+
}) {
|
|
152
|
+
const base = resolveRunnerMatrix({ runnerPreset, platformsJson });
|
|
153
|
+
const selfHosted = base.platforms.filter((platform) =>
|
|
154
|
+
runnerLabels(platform.runner).includes("self-hosted"),
|
|
155
|
+
);
|
|
156
|
+
if (selfHosted.length === 0) {
|
|
157
|
+
return {
|
|
158
|
+
platformsJson: base.platformsJson,
|
|
159
|
+
fallbackCount: 0,
|
|
160
|
+
routing: {
|
|
161
|
+
schema: "buildchain.runner-offline-routing/v1",
|
|
162
|
+
status: "not-applicable",
|
|
163
|
+
routes: [],
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (!String(token || "").trim()) {
|
|
168
|
+
return {
|
|
169
|
+
platformsJson: base.platformsJson,
|
|
170
|
+
fallbackCount: 0,
|
|
171
|
+
routing: {
|
|
172
|
+
schema: "buildchain.runner-offline-routing/v1",
|
|
173
|
+
status: "unavailable",
|
|
174
|
+
reason: "runner-inventory-token-not-projected",
|
|
175
|
+
routes: [],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const inventory = await listRepositoryRunners({
|
|
181
|
+
repository,
|
|
182
|
+
token,
|
|
183
|
+
apiUrl,
|
|
184
|
+
fetchImpl,
|
|
185
|
+
});
|
|
186
|
+
const routed = routeOfflineRunners({
|
|
187
|
+
platforms: base.platforms,
|
|
188
|
+
runners: inventory.runners,
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
...routed,
|
|
192
|
+
routing: {
|
|
193
|
+
...routed.routing,
|
|
194
|
+
inventoryScope: inventory.inventoryScope,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
} catch {
|
|
198
|
+
return {
|
|
199
|
+
platformsJson: base.platformsJson,
|
|
200
|
+
fallbackCount: 0,
|
|
201
|
+
routing: {
|
|
202
|
+
schema: "buildchain.runner-offline-routing/v1",
|
|
203
|
+
status: "unavailable",
|
|
204
|
+
reason: "runner-inventory-unavailable",
|
|
205
|
+
routes: [],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function main() {
|
|
212
|
+
const resolved = await resolveOfflineRunnerFallback({
|
|
213
|
+
runnerPreset: process.env.BUILDCHAIN_RUNNER_PRESET || "github-hosted",
|
|
214
|
+
platformsJson: process.env.BUILDCHAIN_PLATFORMS_JSON || "",
|
|
215
|
+
repository: process.env.GITHUB_REPOSITORY || "",
|
|
216
|
+
token: process.env.BUILDCHAIN_RUNNER_INVENTORY_TOKEN || "",
|
|
217
|
+
apiUrl: process.env.GITHUB_API_URL || "https://api.github.com",
|
|
218
|
+
});
|
|
219
|
+
writeGitHubOutputs({
|
|
220
|
+
"platforms-json": resolved.platformsJson,
|
|
221
|
+
"fallback-count": String(resolved.fallbackCount),
|
|
222
|
+
"routing-json": JSON.stringify(resolved.routing),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
process.argv[1] &&
|
|
228
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
229
|
+
) {
|
|
230
|
+
main().catch((error) => {
|
|
231
|
+
console.error(
|
|
232
|
+
`::error::${String(error.message || error).replace(/\r?\n/g, "%0A")}`,
|
|
233
|
+
);
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
@@ -130,6 +130,16 @@ function collectArtifactFiles(root, patterns) {
|
|
|
130
130
|
return [...files].sort();
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function signingArtifactPathsForPlatform({ loadedConfig, cwd, platformId }) {
|
|
134
|
+
const declarations = loadedConfig?.config?.signing?.artifacts || [];
|
|
135
|
+
return declarations
|
|
136
|
+
.filter(
|
|
137
|
+
(entry) =>
|
|
138
|
+
entry.platforms.length === 0 || entry.platforms.includes(platformId),
|
|
139
|
+
)
|
|
140
|
+
.map((entry) => path.resolve(cwd, entry.path));
|
|
141
|
+
}
|
|
142
|
+
|
|
133
143
|
function readProcessSummaryArtifact(filePath) {
|
|
134
144
|
if (!filePath) {
|
|
135
145
|
return undefined;
|
|
@@ -435,11 +445,35 @@ export function runLifecycle({
|
|
|
435
445
|
const resolvedDiagnosticsProcessSamplesPath = path.join(diagnosticsDir, "process-samples.jsonl");
|
|
436
446
|
const resolvedSourceCheckoutPath = path.resolve(resolvedWorkspace, ".buildchain/diagnostics/source-checkout.json");
|
|
437
447
|
const resolvedDiagnosticsSourceCheckoutPath = path.join(diagnosticsDir, "source-checkout.json");
|
|
448
|
+
const resolvedCompilerCachePreparationPath = path.resolve(
|
|
449
|
+
resolvedWorkspace,
|
|
450
|
+
process.env.BUILDCHAIN_COMPILER_CACHE_PREPARATION_PATH ||
|
|
451
|
+
".buildchain/diagnostics/compiler-cache-preparation.json",
|
|
452
|
+
);
|
|
453
|
+
const compilerCachePreparationRelative = path.relative(
|
|
454
|
+
resolvedWorkspace,
|
|
455
|
+
resolvedCompilerCachePreparationPath,
|
|
456
|
+
);
|
|
457
|
+
if (
|
|
458
|
+
compilerCachePreparationRelative.startsWith("..") ||
|
|
459
|
+
path.isAbsolute(compilerCachePreparationRelative)
|
|
460
|
+
) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
"BUILDCHAIN_COMPILER_CACHE_PREPARATION_PATH must remain inside the workflow workspace",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
const resolvedDiagnosticsCompilerCachePreparationPath = path.join(
|
|
466
|
+
diagnosticsDir,
|
|
467
|
+
"compiler-cache-preparation.json",
|
|
468
|
+
);
|
|
438
469
|
const resolvedDiagnosticsManifestPath = path.join(diagnosticsDir, "diagnostics-manifest.json");
|
|
439
470
|
const relativeDiagnosticsEventsPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsEventsPath));
|
|
440
471
|
const relativeDiagnosticsProcessSummaryPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsProcessSummaryPath));
|
|
441
472
|
const relativeDiagnosticsProcessSamplesPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsProcessSamplesPath));
|
|
442
473
|
const relativeDiagnosticsSourceCheckoutPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsSourceCheckoutPath));
|
|
474
|
+
const relativeDiagnosticsCompilerCachePreparationPath = toPosix(
|
|
475
|
+
path.relative(resolvedWorkspace, resolvedDiagnosticsCompilerCachePreparationPath),
|
|
476
|
+
);
|
|
443
477
|
const relativeDiagnosticsManifestPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsManifestPath));
|
|
444
478
|
const logRunId = crypto.randomUUID();
|
|
445
479
|
const frameworkLog = createBuildchainLogger({
|
|
@@ -622,9 +656,22 @@ export function runLifecycle({
|
|
|
622
656
|
const sourceCheckoutArtifact = fs.existsSync(resolvedSourceCheckoutPath)
|
|
623
657
|
? JSON.parse(fs.readFileSync(resolvedSourceCheckoutPath, "utf8"))
|
|
624
658
|
: undefined;
|
|
659
|
+
const compilerCachePreparationArtifact = fs.existsSync(resolvedCompilerCachePreparationPath)
|
|
660
|
+
? JSON.parse(fs.readFileSync(resolvedCompilerCachePreparationPath, "utf8"))
|
|
661
|
+
: undefined;
|
|
625
662
|
fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });
|
|
626
663
|
const scanStartedAt = Date.now();
|
|
627
|
-
const
|
|
664
|
+
const signingArtifactPaths = stageName === "build"
|
|
665
|
+
? signingArtifactPathsForPlatform({
|
|
666
|
+
loadedConfig,
|
|
667
|
+
cwd: resolvedCwd,
|
|
668
|
+
platformId,
|
|
669
|
+
})
|
|
670
|
+
: [];
|
|
671
|
+
const files = collectArtifactFiles(resolvedWorkspace, [
|
|
672
|
+
...artifactPaths,
|
|
673
|
+
...signingArtifactPaths,
|
|
674
|
+
]);
|
|
628
675
|
const manifestFiles = files.map((file) => {
|
|
629
676
|
const stat = fs.statSync(file);
|
|
630
677
|
return {
|
|
@@ -743,6 +790,7 @@ export function runLifecycle({
|
|
|
743
790
|
lifecycleObservability,
|
|
744
791
|
processSummary: processSummaryArtifact?.summary,
|
|
745
792
|
sourceCheckout: sourceCheckoutArtifact,
|
|
793
|
+
compilerCachePreparation: compilerCachePreparationArtifact,
|
|
746
794
|
links: {
|
|
747
795
|
artifactName,
|
|
748
796
|
platformId,
|
|
@@ -757,6 +805,9 @@ export function runLifecycle({
|
|
|
757
805
|
...(processSummaryArtifact ? { diagnosticsProcessSummary: relativeDiagnosticsProcessSummaryPath } : {}),
|
|
758
806
|
...(processSummaryArtifact?.samplesPath ? { diagnosticsProcessSamples: relativeDiagnosticsProcessSamplesPath } : {}),
|
|
759
807
|
...(sourceCheckoutArtifact ? { sourceCheckout: relativeDiagnosticsSourceCheckoutPath } : {}),
|
|
808
|
+
...(compilerCachePreparationArtifact
|
|
809
|
+
? { compilerCachePreparation: relativeDiagnosticsCompilerCachePreparationPath }
|
|
810
|
+
: {}),
|
|
760
811
|
},
|
|
761
812
|
}),
|
|
762
813
|
);
|
|
@@ -778,6 +829,12 @@ export function runLifecycle({
|
|
|
778
829
|
if (sourceCheckoutArtifact) {
|
|
779
830
|
copyIfExists(resolvedSourceCheckoutPath, resolvedDiagnosticsSourceCheckoutPath);
|
|
780
831
|
}
|
|
832
|
+
if (compilerCachePreparationArtifact) {
|
|
833
|
+
copyIfExists(
|
|
834
|
+
resolvedCompilerCachePreparationPath,
|
|
835
|
+
resolvedDiagnosticsCompilerCachePreparationPath,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
781
838
|
writeDiagnosticsSidecarManifest(resolvedDiagnosticsManifestPath, {
|
|
782
839
|
workspace: resolvedWorkspace,
|
|
783
840
|
artifactName,
|
|
@@ -789,6 +846,10 @@ export function runLifecycle({
|
|
|
789
846
|
{ kind: "process-summary", filePath: resolvedDiagnosticsProcessSummaryPath },
|
|
790
847
|
{ kind: "process-samples", filePath: resolvedDiagnosticsProcessSamplesPath },
|
|
791
848
|
{ kind: "source-checkout", filePath: resolvedDiagnosticsSourceCheckoutPath },
|
|
849
|
+
{
|
|
850
|
+
kind: "compiler-cache-preparation",
|
|
851
|
+
filePath: resolvedDiagnosticsCompilerCachePreparationPath,
|
|
852
|
+
},
|
|
792
853
|
],
|
|
793
854
|
});
|
|
794
855
|
console.log(`buildchain_manifest=${path.relative(resolvedWorkspace, resolvedManifestPath)}`);
|
|
@@ -98,7 +98,15 @@ export function resolveRuntimeSelection({
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
const normalized = normalizeRequestedRuntimeRef(requested);
|
|
101
|
-
const
|
|
101
|
+
const sameRepositoryWorkflow = String(workflowRef || "").startsWith(
|
|
102
|
+
`${buildchainRepository}/.github/workflows/`,
|
|
103
|
+
);
|
|
104
|
+
const pinnedSelfRuntime =
|
|
105
|
+
sameRepositoryWorkflow &&
|
|
106
|
+
normalized.exactSha &&
|
|
107
|
+
EXACT_SHA_RE.test(workflowShellRef) &&
|
|
108
|
+
normalized.ref.toLowerCase() === workflowShellRef.toLowerCase();
|
|
109
|
+
const runtimeOverride = !normalized.officialChannel && !pinnedSelfRuntime;
|
|
102
110
|
return {
|
|
103
111
|
requestedRef: requested,
|
|
104
112
|
runtimeRef: normalized.ref,
|
|
@@ -107,7 +115,11 @@ export function resolveRuntimeSelection({
|
|
|
107
115
|
runtimeOverride,
|
|
108
116
|
workflowShellRef: workflowShellRef || defaultStableRef,
|
|
109
117
|
rollbackRef: workflowShellRef || defaultStableRef,
|
|
110
|
-
trustDecision: normalized.officialChannel
|
|
118
|
+
trustDecision: normalized.officialChannel
|
|
119
|
+
? "official-channel"
|
|
120
|
+
: pinnedSelfRuntime
|
|
121
|
+
? "pinned-self"
|
|
122
|
+
: "override-requested",
|
|
111
123
|
};
|
|
112
124
|
}
|
|
113
125
|
|
|
@@ -117,6 +129,7 @@ export function validateRuntimeOverrideTrust({
|
|
|
117
129
|
eventAction = "",
|
|
118
130
|
actorPermission = "",
|
|
119
131
|
sameRepositoryPullRequest = false,
|
|
132
|
+
sameRepositoryWorkflow = false,
|
|
120
133
|
pullRequestHeadSha = "",
|
|
121
134
|
workflowShellSha = "",
|
|
122
135
|
} = {}) {
|
|
@@ -129,6 +142,14 @@ export function validateRuntimeOverrideTrust({
|
|
|
129
142
|
const normalizedRequested = String(requestedRef || "").trim().toLowerCase();
|
|
130
143
|
const normalizedHeadSha = String(pullRequestHeadSha || "").trim().toLowerCase();
|
|
131
144
|
const normalizedWorkflowShellSha = String(workflowShellSha || "").trim().toLowerCase();
|
|
145
|
+
if (
|
|
146
|
+
sameRepositoryWorkflow === true &&
|
|
147
|
+
EXACT_SHA_RE.test(normalizedRequested) &&
|
|
148
|
+
EXACT_SHA_RE.test(normalizedWorkflowShellSha) &&
|
|
149
|
+
normalizedRequested === normalizedWorkflowShellSha
|
|
150
|
+
) {
|
|
151
|
+
return { ok: true, decision: "pinned-self" };
|
|
152
|
+
}
|
|
132
153
|
if (
|
|
133
154
|
eventName === "pull_request" &&
|
|
134
155
|
eventAction === "closed" &&
|
|
@@ -233,18 +233,16 @@ function verifyLifecycleBinding({
|
|
|
233
233
|
);
|
|
234
234
|
const relativeSubject = toPosix(path.relative(workspace, subjectPath));
|
|
235
235
|
const files = descriptor.entries.filter(
|
|
236
|
-
(entry) => entry.type !== "directory",
|
|
236
|
+
(entry) => entry.type !== "directory" && entry.type !== "symlink",
|
|
237
237
|
);
|
|
238
|
+
let matchedFiles = 0;
|
|
238
239
|
for (const entry of files) {
|
|
239
|
-
if (entry.type === "symlink") continue;
|
|
240
240
|
const manifestPath = fs.statSync(subjectPath).isFile()
|
|
241
241
|
? relativeSubject
|
|
242
242
|
: `${relativeSubject}/${entry.path}`;
|
|
243
243
|
const observed = byPath.get(manifestPath);
|
|
244
|
-
if (!observed)
|
|
245
|
-
|
|
246
|
-
`signed artifact file is absent from lifecycle manifest: ${manifestPath}`,
|
|
247
|
-
);
|
|
244
|
+
if (!observed) continue;
|
|
245
|
+
matchedFiles += 1;
|
|
248
246
|
if (
|
|
249
247
|
`sha256:${observed.sha256}` !== entry.digest ||
|
|
250
248
|
Number(observed.size) !== entry.bytes
|
|
@@ -254,6 +252,15 @@ function verifyLifecycleBinding({
|
|
|
254
252
|
);
|
|
255
253
|
}
|
|
256
254
|
}
|
|
255
|
+
// A signing declaration is an independent artifact inventory and must not
|
|
256
|
+
// require consumers to repeat its path in workflow artifact-paths. When the
|
|
257
|
+
// lifecycle manifest does cover the subject, require complete coverage so a
|
|
258
|
+
// truncated or stale manifest cannot weaken the exact descriptor binding.
|
|
259
|
+
if (matchedFiles !== 0 && matchedFiles !== files.length) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`signed artifact is only partially represented in lifecycle manifest: ${relativeSubject}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
export function sealArtifactSigningRequests({
|