@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.
Files changed (115) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +32 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +19 -1
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +41 -241
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +0 -45
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +161 -42
  19. package/dist/site/buildchain-site.json +180 -84
  20. package/dist/site/capability-registry.json +6 -6
  21. package/dist/site/cli-registry.json +932 -98
  22. package/dist/site/controller-registry.json +97 -5
  23. package/dist/site/kfd-claims.json +316 -23
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +371 -56
  27. package/dist/site/page-registry.json +155 -59
  28. package/dist/site/public-surface-audit.json +286 -24
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +570 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +35 -12
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +143 -12
  37. package/docs/consumer-issue-reporting.md +19 -1
  38. package/docs/github-governance-authority.md +2 -2
  39. package/docs/lifecycle-protocol.md +20 -2
  40. package/docs/migration-inventory.md +24 -5
  41. package/docs/ownership.md +19 -1
  42. package/docs/publication-artifacts.md +185 -18
  43. package/docs/publication-authority.md +4 -4
  44. package/docs/publish-transaction.md +78 -31
  45. package/docs/release-candidate.md +24 -0
  46. package/docs/release-flow.md +50 -32
  47. package/docs/release-governance.md +31 -31
  48. package/docs/release-passport.md +4 -0
  49. package/docs/release-propagation.md +57 -12
  50. package/docs/reusable-build-surface.md +95 -27
  51. package/docs/runtime-train-validation.md +20 -2
  52. package/docs/stable-candidate-patrol.md +6 -6
  53. package/docs/toolkit-observability.md +22 -0
  54. package/docs/versioning.md +6 -4
  55. package/docs/web-surface-deployments.md +62 -0
  56. package/package.json +7 -5
  57. package/packages/core/README.md +61 -4
  58. package/packages/core/buildchain-contract.js +8 -4
  59. package/packages/core/cache-evidence.js +1 -0
  60. package/packages/core/diagnostics.js +105 -30
  61. package/packages/core/github-governance-authority.js +10 -25
  62. package/packages/core/index.js +40 -0
  63. package/packages/core/paper-fleet.js +260 -0
  64. package/packages/core/paper-repository.js +210 -0
  65. package/packages/core/paper-work.js +365 -0
  66. package/packages/core/paper.js +3180 -0
  67. package/packages/core/public-surface-audit.js +26 -75
  68. package/packages/core/public-surface-cli.js +117 -0
  69. package/packages/core/publication-artifact.js +1 -6
  70. package/packages/core/publication-authority.js +1 -0
  71. package/packages/core/publication-reproducibility.js +947 -0
  72. package/packages/core/publication-sealed-bundle.js +190 -0
  73. package/packages/core/publish-transaction.js +147 -17
  74. package/packages/core/release-candidate.js +234 -0
  75. package/packages/core/release-passport.js +483 -201
  76. package/packages/core/release-propagation.js +175 -1
  77. package/scripts/auditable-demo.mjs +119 -5
  78. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  79. package/scripts/aws-macos-jit-core.mjs +378 -0
  80. package/scripts/aws-macos-jit.mjs +123 -0
  81. package/scripts/aws-runner-burst-core.mjs +248 -0
  82. package/scripts/aws-runner-burst.mjs +79 -0
  83. package/scripts/aws-windows-jit-core.mjs +374 -0
  84. package/scripts/aws-windows-jit.mjs +121 -0
  85. package/scripts/build-contract-core.mjs +69 -6
  86. package/scripts/build-standalone-binary.mjs +23 -0
  87. package/scripts/buildchain-cli-help.mjs +256 -0
  88. package/scripts/buildchain-contract-lock.mjs +1 -1
  89. package/scripts/check-internal-architecture.mjs +69 -2
  90. package/scripts/check-inventory.mjs +69 -2
  91. package/scripts/check-javascript-syntax.mjs +31 -0
  92. package/scripts/check-maintainability.mjs +371 -0
  93. package/scripts/compiler-cache-evidence.mjs +221 -0
  94. package/scripts/dev-alpha-candidate-patrol.mjs +268 -43
  95. package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
  96. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  97. package/scripts/generate-release-candidate-passport.mjs +4 -0
  98. package/scripts/generate-site-bundle.mjs +68 -188
  99. package/scripts/import-artifact-signing-results.mjs +64 -5
  100. package/scripts/inspect-artifact-signing-requests.mjs +7 -0
  101. package/scripts/maintainability-metrics.mjs +339 -0
  102. package/scripts/npm-publish-transaction.mjs +57 -6
  103. package/scripts/paper-work-fleet-cli.mjs +563 -0
  104. package/scripts/paper.mjs +631 -0
  105. package/scripts/promotion-channel-router.mjs +16 -4
  106. package/scripts/publication-reproducibility.mjs +62 -0
  107. package/scripts/release-propagation.mjs +39 -0
  108. package/scripts/resolve-build-contract.mjs +6 -0
  109. package/scripts/route-offline-runners.mjs +236 -0
  110. package/scripts/run-lifecycle-core.mjs +62 -1
  111. package/scripts/runtime-ref-core.mjs +23 -2
  112. package/scripts/seal-artifact-signing-requests.mjs +13 -6
  113. package/scripts/site-capability-metadata.mjs +202 -0
  114. package/scripts/stable-candidate-qualification.mjs +42 -0
  115. 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 files = collectArtifactFiles(resolvedWorkspace, artifactPaths);
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 runtimeOverride = !normalized.officialChannel;
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 ? "official-channel" : "override-requested",
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
- throw new Error(
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({