@kungfu-tech/buildchain 3.0.6-alpha.0 → 3.0.6-alpha.10

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 (83) hide show
  1. package/README.md +4 -4
  2. package/actions/promote-buildchain-ref/README.md +13 -1
  3. package/bin/buildchain.mjs +13 -1
  4. package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
  5. package/contracts/release-candidate-recovery-v1.schema.json +104 -0
  6. package/dist/site/buildchain-contract.json +134 -34
  7. package/dist/site/buildchain-site.json +178 -47
  8. package/dist/site/capability-registry.json +5 -5
  9. package/dist/site/cli-registry.json +40 -4
  10. package/dist/site/controller-registry.json +60 -4
  11. package/dist/site/kfd-claims.json +190 -20
  12. package/dist/site/kfd-upstream-aggregate.json +9 -9
  13. package/dist/site/manual-registry.json +10 -10
  14. package/dist/site/node-api-registry.json +1295 -80
  15. package/dist/site/page-registry.json +164 -33
  16. package/dist/site/public-surface-audit.json +428 -21
  17. package/dist/site/publication-authority-registry.json +66 -1
  18. package/dist/site/publication-registry.json +4 -4
  19. package/dist/site/release-provenance.json +2 -0
  20. package/dist/site/site-manifest.json +14 -14
  21. package/dist/site/workflow-registry.json +203 -15
  22. package/docs/MAP.md +2 -0
  23. package/docs/auditable-demo.md +58 -11
  24. package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
  25. package/docs/cli-reference.md +154 -0
  26. package/docs/dev-alpha-candidate-patrol.md +13 -5
  27. package/docs/dev-delivery-warrant.md +158 -0
  28. package/docs/node-api-reference.md +105 -44
  29. package/docs/publication-authority.md +11 -0
  30. package/docs/publish-transaction.md +10 -1
  31. package/docs/release-candidate.md +140 -12
  32. package/docs/release-governance.md +66 -1
  33. package/docs/reusable-build-surface.md +11 -1
  34. package/docs/shifu-gate-profiles.md +12 -1
  35. package/docs/versioning.md +2 -0
  36. package/package.json +5 -2
  37. package/packages/core/buildchain-contract.js +30 -2
  38. package/packages/core/buildchain-publication-authority.js +3 -1
  39. package/packages/core/channel-candidate.js +2 -21
  40. package/packages/core/channel-promotion-baseline.js +199 -0
  41. package/packages/core/dev-delivery-candidate-identity.js +94 -0
  42. package/packages/core/dev-delivery-common.js +73 -0
  43. package/packages/core/dev-delivery-proof.js +252 -0
  44. package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
  45. package/packages/core/dev-delivery-warrant-settlement.js +73 -0
  46. package/packages/core/dev-delivery-warrant.js +591 -0
  47. package/packages/core/index.js +7 -0
  48. package/packages/core/publication-sealed-bundle.js +10 -1
  49. package/packages/core/release-candidate-recovery.js +539 -0
  50. package/scripts/audit-publication-control-plane.mjs +12 -4
  51. package/scripts/auditable-demo-bundle-verification.mjs +148 -0
  52. package/scripts/auditable-demo-platform.mjs +86 -50
  53. package/scripts/auditable-demo-presentation.mjs +83 -0
  54. package/scripts/auditable-demo-renditions.mjs +264 -0
  55. package/scripts/auditable-demo.mjs +24 -30
  56. package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
  57. package/scripts/aws-windows-jit-controller.mjs +1 -0
  58. package/scripts/aws-windows-jit-core.mjs +1 -1
  59. package/scripts/build-contract-core.mjs +58 -3
  60. package/scripts/buildchain-cli-help.mjs +8 -0
  61. package/scripts/buildchain-patrol.mjs +9 -0
  62. package/scripts/check-inventory.mjs +25 -3
  63. package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
  64. package/scripts/dev-delivery-proof.mjs +193 -0
  65. package/scripts/dev-delivery-warrant.mjs +426 -0
  66. package/scripts/dev-pr-auto-merge.mjs +497 -55
  67. package/scripts/dev-pr-delivery-warrant.mjs +209 -0
  68. package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
  69. package/scripts/gate-profile-core.mjs +24 -0
  70. package/scripts/generate-channel-promotion-workflow.mjs +30 -14
  71. package/scripts/generate-site-bundle.mjs +2 -2
  72. package/scripts/git-fetch-process-tree.mjs +142 -0
  73. package/scripts/lifecycle-substage-evidence.mjs +274 -0
  74. package/scripts/locked-source-checkout.mjs +6 -3
  75. package/scripts/publication-candidate-sealer.mjs +104 -0
  76. package/scripts/release-candidate-resolver.mjs +91 -5
  77. package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
  78. package/scripts/resolve-build-contract.mjs +7 -0
  79. package/scripts/resume-from-candidate-run.mjs +598 -0
  80. package/scripts/route-offline-runners.mjs +1 -0
  81. package/scripts/run-lifecycle-core.mjs +9 -9
  82. package/scripts/shifu-gate-profile.mjs +10 -16
  83. package/scripts/site-capability-metadata.mjs +15 -0
@@ -0,0 +1,209 @@
1
+ import fs from "node:fs";
2
+
3
+ const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
4
+ const SHA_PATTERN = /^[0-9a-f]{40}$/;
5
+ const ADMISSION_SCHEMA = "kungfu.buildchain.dev-pr-admission/v1";
6
+
7
+ function mismatch(code) {
8
+ const error = new Error(code);
9
+ error.code = code;
10
+ throw error;
11
+ }
12
+
13
+ function requireMatch(condition, code) {
14
+ if (!condition) mismatch(code);
15
+ }
16
+
17
+ function readWarrantResult(file) {
18
+ try {
19
+ return JSON.parse(fs.readFileSync(file, "utf8"));
20
+ } catch (cause) {
21
+ const error = new Error(`delivery Warrant result is unreadable: ${cause.message}`);
22
+ error.code = "invalid-delivery-warrant-result";
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ function exactActiveReadback(result) {
28
+ requireMatch(result.schema === "kungfu.buildchain.dev-delivery-command-result/v1", "unsupported-delivery-warrant-result");
29
+ requireMatch(result.mode === "execute", "delivery-warrant-not-executed");
30
+ requireMatch(SHA_PATTERN.test(String(result.after?.commitSha || "")), "delivery-warrant-commit-readback-missing");
31
+ requireMatch(ROOT_PATTERN.test(String(result.after?.stateRoot || "")), "delivery-warrant-state-readback-missing");
32
+ requireMatch(result.observation?.schema === "kungfu.buildchain.dev-delivery-queue-observation/v1", "delivery-warrant-observation-missing");
33
+ requireMatch(result.observation.stateRoot === result.after.stateRoot, "delivery-warrant-observation-root-mismatch");
34
+ const warrant = result.observation.activeWarrant;
35
+ const candidate = result.observation.activeCandidate;
36
+ requireMatch(warrant?.schema === "kungfu.buildchain.dev-delivery-warrant/v1", "delivery-warrant-missing");
37
+ requireMatch(candidate?.candidateId === warrant.candidateId, "delivery-warrant-candidate-readback-missing");
38
+ requireMatch(!result.warrant || JSON.stringify(result.warrant) === JSON.stringify(warrant), "delivery-warrant-readback-mismatch");
39
+ return { warrant, candidate };
40
+ }
41
+
42
+ function exactWarrantBinding({ result, warrant, candidate, options, pullRequest }) {
43
+ requireMatch(warrant.repository === options.repository.fullName, "delivery-warrant-repository-mismatch");
44
+ requireMatch(warrant.protectedBase === options.targetBranch, "delivery-warrant-base-mismatch");
45
+ requireMatch(Number(warrant.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-pr-mismatch");
46
+ requireMatch(String(warrant.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-head-mismatch");
47
+ requireMatch(Number(candidate.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-candidate-pr-mismatch");
48
+ requireMatch(String(candidate.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-candidate-head-mismatch");
49
+ requireMatch(ROOT_PATTERN.test(String(warrant.fencingToken || "")), "delivery-warrant-fencing-missing");
50
+ requireMatch(Number.isInteger(Number(warrant.generation)) && Number(warrant.generation) >= 1, "delivery-warrant-generation-invalid");
51
+ requireMatch(Number.isFinite(Date.parse(warrant.expiresAt)) && Date.parse(warrant.expiresAt) > Date.now(), "delivery-warrant-expired");
52
+ requireMatch(ROOT_PATTERN.test(String(result.receiptRoot || "")), "delivery-warrant-receipt-root-missing");
53
+ }
54
+
55
+ export function createDevPrAdmissionReceipt({ options, pr = {}, state, reason, readiness, decision = {}, queue = null, warrant = null, labels = [], nextAction }) {
56
+ const observedHeadSha = String(pr.head?.sha || "").toLowerCase();
57
+ return {
58
+ schema: ADMISSION_SCHEMA,
59
+ repository: options.repository.fullName,
60
+ targetBranch: options.targetBranch,
61
+ pullRequestNumber: options.targetPullRequestNumber,
62
+ pullRequestUrl: pr.html_url || `https://github.com/${options.repository.fullName}/pull/${options.targetPullRequestNumber}`,
63
+ expectedHeadSha: options.expectedHeadSha,
64
+ observedHeadSha,
65
+ observedBaseBranch: pr.base?.ref || "",
66
+ headRepository: pr.head?.repo?.full_name || "",
67
+ headRef: pr.head?.ref || "",
68
+ observedLabels: [...labels].sort(),
69
+ policy: {
70
+ readyLabel: options.readyLabel,
71
+ blockLabels: options.blockLabels,
72
+ allowedHeadPrefixes: options.allowedHeadPrefixes,
73
+ requiredChecks: options.requiredChecks,
74
+ requireApproval: options.requireApproval,
75
+ sameRepositoryOnly: options.sameRepositoryOnly,
76
+ landingMode: options.landingMode,
77
+ queueAdmissionContext: options.queueAdmissionContext,
78
+ diagnosticContext: options.diagnosticContext,
79
+ },
80
+ readiness: {
81
+ label: options.readyLabel,
82
+ observed: readiness?.observed === true,
83
+ established: readiness?.established === true,
84
+ mutationAuthorized: !options.dryRun,
85
+ },
86
+ approval: decision.approval || { required: options.requireApproval, passed: false },
87
+ checks: decision.checks || { required: options.requiredChecks, entries: [], passed: false },
88
+ projectCut: decision.projectCut || null,
89
+ queue,
90
+ deliveryWarrant: warrant,
91
+ autoMergeEnabled: Boolean(pr.auto_merge || pr.autoMergeRequest),
92
+ state,
93
+ reason,
94
+ decision: state,
95
+ qualification: ["ready", "queued"].includes(state),
96
+ nextAction: nextAction({ options, state, reason, observedHeadSha }),
97
+ };
98
+ }
99
+
100
+ export function readDeliveryWarrantResult(options, pullRequest) {
101
+ if (options.warrantMode === "off") return null;
102
+ if (!options.warrantResultPath) mismatch("missing-delivery-warrant");
103
+ const result = readWarrantResult(options.warrantResultPath);
104
+ const { warrant, candidate } = exactActiveReadback(result);
105
+ exactWarrantBinding({ result, warrant, candidate, options, pullRequest });
106
+ return {
107
+ stateRef: result.stateRef || "",
108
+ stateCommit: result.after.commitSha,
109
+ stateRoot: result.after.stateRoot,
110
+ receiptRoot: result.receiptRoot,
111
+ candidateId: warrant.candidateId,
112
+ fencingToken: warrant.fencingToken,
113
+ generation: warrant.generation,
114
+ issuedAt: warrant.issuedAt,
115
+ expiresAt: warrant.expiresAt,
116
+ };
117
+ }
118
+
119
+ export async function runSourceQualification({ options, pullRequest, readiness, client, evaluate, admissionState, createReceipt, root, publishDiagnostic, reject }) {
120
+ const decision = await evaluate(pullRequest, { ...options, landingMode: options.landingMode, dryRun: true }, client);
121
+ if (decision.observedHeadSha && String(decision.observedHeadSha).toLowerCase() !== options.expectedHeadSha) {
122
+ return reject("stale", "head-sha-drift-during-source-qualification", readiness);
123
+ }
124
+ const state = decision.action === "would-merge" ? "ready" : admissionState(decision);
125
+ const receipt = createReceipt({
126
+ options,
127
+ pr: pullRequest,
128
+ state,
129
+ reason: state === "ready" ? "source-qualified-exact-head" : decision.reason,
130
+ readiness,
131
+ decision,
132
+ queue: null,
133
+ warrant: null,
134
+ });
135
+ const result = {
136
+ schema: "kungfu.buildchain.dev-pr-admission-result/v1",
137
+ ok: state === "ready",
138
+ mode: options.dryRun ? "plan" : "execute",
139
+ outcome: state === "ready" ? "source-qualified" : "targeted-admission-failed",
140
+ receipt,
141
+ receiptRoot: root(receipt),
142
+ diagnostic: null,
143
+ };
144
+ if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
145
+ return result;
146
+ }
147
+
148
+ export async function admitExistingQueueEntry({ options, pullRequest, readiness, client, entry, warrant, createReceipt, root, publishDiagnostic }) {
149
+ if (!entry) return null;
150
+ const receipt = createReceipt({
151
+ options,
152
+ pr: pullRequest,
153
+ state: "queued",
154
+ reason: "already-enqueued-exact-head",
155
+ readiness,
156
+ queue: { enabled: true, entry },
157
+ warrant,
158
+ });
159
+ const result = {
160
+ schema: "kungfu.buildchain.dev-pr-admission-result/v1",
161
+ ok: true,
162
+ mode: options.dryRun ? "plan" : "execute",
163
+ outcome: "admitted",
164
+ receipt,
165
+ receiptRoot: root(receipt),
166
+ diagnostic: null,
167
+ };
168
+ if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
169
+ return result;
170
+ }
171
+
172
+ export async function runTargetedQueueAdmission({ options, pullRequest, readiness, client, warrant, runController, admissionState, createReceipt, root, publishDiagnostic }) {
173
+ const targetedClient = Object.create(client);
174
+ targetedClient.listPullRequests = async () => [pullRequest];
175
+ const controller = await runController({ ...options, targetPullRequestNumber: 0 }, targetedClient);
176
+ controller.runKind = "targeted-admission-evaluation";
177
+ controller.outcome = controller.actions.length === 0 ? "target-not-admitted" : "target-action-selected";
178
+ controller.qualification = false;
179
+ controller.noOp = controller.actions.length === 0;
180
+ const entry = controller.evaluated.find((value) => value.number === pullRequest.number) || { action: "skip", reason: "target-not-selected" };
181
+ const state = admissionState(entry);
182
+ const receipt = createReceipt({
183
+ options,
184
+ pr: pullRequest,
185
+ state,
186
+ reason: entry.reason,
187
+ readiness,
188
+ decision: entry,
189
+ queue: {
190
+ enabled: controller.mergeQueue?.enabled === true,
191
+ predecessor: entry.admissionReceipt?.predecessor || null,
192
+ entry: entry.queueEntry || null,
193
+ },
194
+ warrant,
195
+ });
196
+ const admitted = ["ready", "queued"].includes(state);
197
+ const result = {
198
+ schema: "kungfu.buildchain.dev-pr-admission-result/v1",
199
+ ok: admitted,
200
+ mode: options.dryRun ? "plan" : "execute",
201
+ outcome: admitted ? "admitted" : "targeted-admission-failed",
202
+ receipt,
203
+ receiptRoot: root(receipt),
204
+ diagnostic: null,
205
+ controller,
206
+ };
207
+ if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
208
+ return result;
209
+ }
@@ -86,10 +86,8 @@ export function validateArtifactSigningAuthorityRun(
86
86
  ) {
87
87
  throw new Error("Buildchain signing authority repository mismatch");
88
88
  }
89
- if (
90
- run.path &&
91
- !String(run.path).startsWith(`.github/workflows/${AUTHORITY_WORKFLOW}@`)
92
- ) {
89
+ const runPath = String(run.path || "").split("@", 1)[0];
90
+ if (run.path && runPath !== `.github/workflows/${AUTHORITY_WORKFLOW}`) {
93
91
  throw new Error("Buildchain signing authority workflow path mismatch");
94
92
  }
95
93
  return run;
@@ -67,6 +67,25 @@ function uniqueStrings(value, label) {
67
67
  return normalized;
68
68
  }
69
69
 
70
+ export function normalizeGateEnvironment(value = {}, label = "environment") {
71
+ const environment = assertObject(value, label);
72
+ return Object.fromEntries(
73
+ Object.entries(environment)
74
+ .sort(([left], [right]) => left.localeCompare(right))
75
+ .map(([name, entry]) => {
76
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
77
+ throw new Error(`${label} has invalid environment name: ${name}`);
78
+ }
79
+ if (!["string", "number", "boolean"].includes(typeof entry)) {
80
+ throw new Error(
81
+ `${label}.${name} must be a string, number, or boolean`,
82
+ );
83
+ }
84
+ return [name, String(entry)];
85
+ }),
86
+ );
87
+ }
88
+
70
89
  function inferShifuPlatform(platform) {
71
90
  const explicit = String(platform.platform || "")
72
91
  .trim()
@@ -121,6 +140,10 @@ export function normalizeGatePlatform(platform, index = 0) {
121
140
  platform.capabilities || ["node"],
122
141
  `platforms[${index}].capabilities`,
123
142
  ).sort(),
143
+ environment: normalizeGateEnvironment(
144
+ platform.environment || {},
145
+ `platforms[${index}].environment`,
146
+ ),
124
147
  required: platform.required !== false,
125
148
  };
126
149
  }
@@ -259,6 +282,7 @@ export function createGateExecutionMatrix({
259
282
  platform: platform.platform,
260
283
  runner: platform.runner,
261
284
  capabilities: platform.capabilities,
285
+ environment: platform.environment,
262
286
  required: platform.required,
263
287
  profile,
264
288
  includeAdvisory: Boolean(includeAdvisory),
@@ -130,8 +130,8 @@ function validateWorkflowRoute(name, route, expectedLogicalRef) {
130
130
  if (!/^\.github\/workflows\/[.a-z0-9-]+\.ya?ml$/.test(route.workflowPath || "")) {
131
131
  throw new Error(`promotion shell ${name} workflowPath must name a reusable workflow`);
132
132
  }
133
- if (!/^(?:v[0-9]+(?:-alpha)?|[0-9a-f]{40})$/.test(route.callRef || "")) {
134
- throw new Error(`promotion shell ${name} callRef must be an official channel ref or exact SHA`);
133
+ if (!/^(?:v[0-9]+(?:-alpha)?|train\/[A-Za-z0-9._+\/-]+|[0-9a-f]{40})$/.test(route.callRef || "")) {
134
+ throw new Error(`promotion shell ${name} callRef must be an official channel ref, trusted train, or exact SHA`);
135
135
  }
136
136
  if (typeof route.forwardInternalInputs !== "boolean") {
137
137
  throw new Error(`promotion shell ${name} forwardInternalInputs must be boolean`);
@@ -240,29 +240,45 @@ jobs:
240
240
  id: router
241
241
  shell: bash
242
242
  env:
243
- BUILDCHAIN_ROUTER_WORKFLOW_REF: \${{ job.workflow_ref }}
244
- BUILDCHAIN_ROUTER_WORKFLOW_REPOSITORY: \${{ job.workflow_repository }}
245
- BUILDCHAIN_ROUTER_WORKFLOW_SHA: \${{ job.workflow_sha }}
243
+ BUILDCHAIN_ROUTER_REPOSITORY: \${{ inputs.buildchain-repository }}
244
+ BUILDCHAIN_ROUTER_REF: \${{ inputs.buildchain-ref }}
245
+ BUILDCHAIN_RESUME_RUN_ID: \${{ inputs.resume-candidate-run-id }}
246
+ BUILDCHAIN_RESUME_RUNTIME_SHA: \${{ inputs.resume-buildchain-runtime-sha }}
246
247
  run: |
247
248
  set -euo pipefail
248
- workflow_ref="\${BUILDCHAIN_ROUTER_WORKFLOW_REF}"
249
- repository="\${BUILDCHAIN_ROUTER_WORKFLOW_REPOSITORY}"
250
- ref="\${workflow_ref##*@}"
249
+ repository="\${BUILDCHAIN_ROUTER_REPOSITORY}"
250
+ ref="\${BUILDCHAIN_ROUTER_REF}"
251
251
  ref="\${ref#refs/heads/}"
252
252
  ref="\${ref#refs/tags/}"
253
- parsed_repository="\${workflow_ref%%/.github/workflows/*}"
254
- if [[ -z "\${repository}" || "\${repository}" != "\${parsed_repository}" || -z "\${ref}" ]]; then
255
- echo "::error::Unable to resolve promotion router source from job.workflow_ref=\${workflow_ref}"
253
+ if [[ ! "\${repository}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ || -z "\${ref}" || "\${ref}" = /* || "\${ref}" = *..* ]]; then
254
+ echo "::error::Unable to resolve promotion router source from repository/ref inputs"
256
255
  exit 1
257
256
  fi
258
- if [[ ! "\${BUILDCHAIN_ROUTER_WORKFLOW_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then
259
- echo "::error::job.workflow_sha is not an exact commit SHA"
257
+
258
+ remote_url="https://github.com/\${repository}.git"
259
+ refs="$(git ls-remote "\${remote_url}" "refs/heads/\${ref}" "refs/tags/\${ref}" "refs/tags/\${ref}^{}")"
260
+ head_sha="$(printf '%s\\n' "\${refs}" | awk -v name="refs/heads/\${ref}" '$2 == name { print tolower($1) }')"
261
+ tag_sha="$(printf '%s\\n' "\${refs}" | awk -v peeled="refs/tags/\${ref}^{}" -v name="refs/tags/\${ref}" '$2 == peeled { print tolower($1); found=1 } $2 == name && !found { fallback=tolower($1) } END { if (!found && fallback != "") print fallback }')"
262
+ if [[ -n "\${head_sha}" && -n "\${tag_sha}" && "\${head_sha}" != "\${tag_sha}" ]]; then
263
+ echo "::error::Promotion router ref is ambiguous between branch and tag"
264
+ exit 1
265
+ fi
266
+ sha="\${head_sha:-\${tag_sha}}"
267
+ if [[ ! "\${sha}" =~ ^[0-9a-f]{40}$ ]]; then
268
+ echo "::error::Promotion router ref did not resolve to one exact commit SHA"
260
269
  exit 1
261
270
  fi
271
+ if [[ -n "\${BUILDCHAIN_RESUME_RUN_ID}" ]]; then
272
+ expected_sha="\${BUILDCHAIN_RESUME_RUNTIME_SHA,,}"
273
+ if [[ ! "\${expected_sha}" =~ ^[0-9a-f]{40}$ || "\${sha}" != "\${expected_sha}" ]]; then
274
+ echo "::error::Recovery router ref does not match resume-buildchain-runtime-sha"
275
+ exit 1
276
+ fi
277
+ fi
262
278
  {
263
279
  echo "repository=\${repository}"
264
280
  echo "ref=\${ref}"
265
- echo "sha=\${BUILDCHAIN_ROUTER_WORKFLOW_SHA,,}"
281
+ echo "sha=\${sha}"
266
282
  } >> "\${GITHUB_OUTPUT}"
267
283
 
268
284
  - name: Checkout promotion router
@@ -56,7 +56,6 @@ const requireFromHere = createRequire(import.meta.url);
56
56
  function readText(rel) {
57
57
  return fs.readFileSync(path.join(root, rel), "utf8");
58
58
  }
59
-
60
59
  function readJson(rel) {
61
60
  return JSON.parse(readText(rel));
62
61
  }
@@ -518,7 +517,7 @@ function workflowCapabilityGroup(entry) {
518
517
  if (["web-surface", "release-propagation"].includes(entry.id)) return capabilityGroup("site-and-propagation");
519
518
  if (["build", "release-candidate-promote", "publication-artifact", "paper-release"].includes(entry.id)) return capabilityGroup("reusable-build");
520
519
  if (["buildchain-ref-promotion", "release-line-bootstrap"].includes(entry.id)) return capabilityGroup("release-passport-trust");
521
- if (entry.id.includes("patrol") || entry.id.includes("dev-pr-auto-merge")) return capabilityGroup("governance-versioning");
520
+ if (entry.id.includes("patrol") || entry.id.includes("dev-pr-auto-merge") || entry.id.includes("dev-delivery-warrant") || entry.id.includes("buildchain-dev-delivery")) return capabilityGroup("governance-versioning");
522
521
  if (entry.status === "repository-internal" || entry.status === "compatibility-fixture") return capabilityGroup("api-cli-reference");
523
522
  return capabilityGroup("api-cli-reference");
524
523
  }
@@ -778,6 +777,7 @@ function buildSiteBundle() {
778
777
  ["paper-release", "reusable-build"],
779
778
  ["release-propagation", "release-propagation"],
780
779
  ["dev-pr-auto-merge", "dev-governance"],
780
+ ["buildchain-dev-delivery", "dev-governance"],
781
781
  ["github-governance-audit", "dev-governance"],
782
782
  ["binary-distribution", "release-passport"],
783
783
  ["github-artifact-attestation", "release-passport"],
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, spawn } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+
6
+ const INTERNAL_COMMAND = "--buildchain-internal-git-fetch";
7
+ const TIMEOUT_EXIT_CODE = 124;
8
+ const scriptPath = fileURLToPath(import.meta.url);
9
+
10
+ async function terminateProcessTree(child, graceMs) {
11
+ if (!child?.pid) return;
12
+ if (process.platform === "win32") {
13
+ await new Promise((resolve) => {
14
+ const killer = spawn(
15
+ "taskkill",
16
+ ["/pid", String(child.pid), "/t", "/f"],
17
+ {
18
+ stdio: "ignore",
19
+ windowsHide: true,
20
+ },
21
+ );
22
+ const fallback = setTimeout(() => {
23
+ try {
24
+ child.kill("SIGKILL");
25
+ } catch {
26
+ // The process tree may already be gone.
27
+ }
28
+ resolve();
29
+ }, graceMs);
30
+ killer.once("error", () => {
31
+ clearTimeout(fallback);
32
+ try {
33
+ child.kill("SIGKILL");
34
+ } catch {
35
+ // The process tree may already be gone.
36
+ }
37
+ resolve();
38
+ });
39
+ killer.once("close", () => {
40
+ clearTimeout(fallback);
41
+ resolve();
42
+ });
43
+ });
44
+ return;
45
+ }
46
+ try {
47
+ process.kill(-child.pid, "SIGTERM");
48
+ } catch {
49
+ return;
50
+ }
51
+ await new Promise((resolve) => setTimeout(resolve, graceMs));
52
+ try {
53
+ process.kill(-child.pid, "SIGKILL");
54
+ } catch {
55
+ // The process group exited during the grace period.
56
+ }
57
+ }
58
+
59
+ async function runInternalGitFetch() {
60
+ const payload = JSON.parse(fs.readFileSync(0, "utf8"));
61
+ const timeoutMs = Math.max(1, Number(payload.timeoutMs || 60000));
62
+ const graceMs = Math.max(
63
+ 50,
64
+ Number(process.env.BUILDCHAIN_GIT_TIMEOUT_GRACE_MS || 2000),
65
+ );
66
+ const child = spawn("git", payload.args, {
67
+ cwd: payload.cwd,
68
+ env: process.env,
69
+ detached: process.platform !== "win32",
70
+ stdio: ["ignore", "pipe", "pipe"],
71
+ windowsHide: true,
72
+ });
73
+ const stdout = [];
74
+ const stderr = [];
75
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
76
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
77
+ let timedOut = false;
78
+ let spawnError;
79
+ const closed = new Promise((resolve) => {
80
+ child.once("error", (error) => {
81
+ spawnError = error;
82
+ resolve({ code: 1, signal: "" });
83
+ });
84
+ child.once("close", (code, signal) => resolve({ code, signal }));
85
+ });
86
+ const timer = setTimeout(() => {
87
+ timedOut = true;
88
+ void terminateProcessTree(child, graceMs);
89
+ }, timeoutMs);
90
+ const result = await closed;
91
+ clearTimeout(timer);
92
+ if (stdout.length) process.stdout.write(Buffer.concat(stdout));
93
+ if (stderr.length) process.stderr.write(Buffer.concat(stderr));
94
+ if (spawnError)
95
+ console.error(
96
+ `buildchain: failed to start git fetch: ${spawnError.message}`,
97
+ );
98
+ if (timedOut)
99
+ console.error(
100
+ `buildchain: git fetch timed out after ${timeoutMs}ms; process tree terminated`,
101
+ );
102
+ if (result.signal && !timedOut)
103
+ console.error(`buildchain: git fetch terminated by ${result.signal}`);
104
+ process.exitCode = timedOut
105
+ ? TIMEOUT_EXIT_CODE
106
+ : spawnError || result.signal
107
+ ? 1
108
+ : (result.code ?? 1);
109
+ }
110
+
111
+ export function runGitFetchSync({ args, cwd, env, timeoutMs, stdio }) {
112
+ const commandStdio = Array.isArray(stdio)
113
+ ? ["pipe", stdio[1] || "pipe", stdio[2] || "pipe"]
114
+ : ["pipe", stdio, stdio];
115
+ try {
116
+ const output = execFileSync(
117
+ process.execPath,
118
+ [scriptPath, INTERNAL_COMMAND],
119
+ {
120
+ cwd,
121
+ env,
122
+ encoding: "utf8",
123
+ stdio: commandStdio,
124
+ input: JSON.stringify({ args, cwd, timeoutMs }),
125
+ windowsHide: true,
126
+ },
127
+ );
128
+ return output ? String(output).trim() : "";
129
+ } catch (error) {
130
+ if (error?.status === TIMEOUT_EXIT_CODE) error.code = "ETIMEDOUT";
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ if (
136
+ process.argv[1] &&
137
+ import.meta.url === pathToFileURL(process.argv[1]).href
138
+ ) {
139
+ if (process.argv[2] !== INTERNAL_COMMAND)
140
+ throw new Error("internal git fetch command required");
141
+ await runInternalGitFetch();
142
+ }