@kungfu-tech/buildchain 2.14.3-alpha.0 → 2.14.3-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 (30) hide show
  1. package/dist/site/buildchain-contract.json +132 -26
  2. package/dist/site/buildchain-site.json +16 -16
  3. package/dist/site/capability-registry.json +1 -1
  4. package/dist/site/controller-registry.json +48 -4
  5. package/dist/site/kfd-claims.json +111 -8
  6. package/dist/site/kfd-upstream-aggregate.json +1 -1
  7. package/dist/site/manual-registry.json +4 -4
  8. package/dist/site/node-api-registry.json +6 -6
  9. package/dist/site/page-registry.json +8 -8
  10. package/dist/site/public-surface-audit.json +124 -6
  11. package/dist/site/publication-authority-registry.json +26 -1
  12. package/dist/site/publication-registry.json +4 -4
  13. package/dist/site/site-manifest.json +8 -8
  14. package/dist/site/workflow-registry.json +124 -3
  15. package/docs/migration-inventory.md +14 -0
  16. package/docs/release-candidate.md +3 -0
  17. package/docs/release-governance.md +5 -4
  18. package/docs/reusable-build-surface.md +36 -1
  19. package/package.json +1 -1
  20. package/packages/core/buildchain-contract.js +41 -0
  21. package/packages/core/buildchain-kfd-claims.js +4 -0
  22. package/packages/core/buildchain-publication-authority.js +1 -0
  23. package/packages/core/controller-evidence.js +1 -1
  24. package/packages/core/publication-control-plane-audit.js +2 -0
  25. package/packages/core/release-passport.js +39 -0
  26. package/scripts/audit-publication-control-plane.mjs +6 -1
  27. package/scripts/check-inventory.mjs +39 -2
  28. package/scripts/generate-channel-promotion-workflow.mjs +413 -0
  29. package/scripts/promotion-channel-router.mjs +125 -0
  30. package/scripts/stable-candidate-patrol.mjs +55 -6
@@ -89,6 +89,7 @@ export function createBuildchainContractWorld({
89
89
  "publication-artifact",
90
90
  "paper-release",
91
91
  "release-candidate-promote",
92
+ ".release-candidate-promote",
92
93
  "release-propagation",
93
94
  ]);
94
95
  const hasCompleteControllerSource = [...controllerWorkflowIds]
@@ -173,11 +174,17 @@ export function createBuildchainContractWorld({
173
174
  requiredInputs: ["channel"],
174
175
  requiredOutputs: ["promoted-sha", "built-source-sha", "release-candidate-artifact"],
175
176
  breakingDefaults: {
177
+ channelDefault: "auto",
178
+ alphaShellDefault: `${majorLine}-alpha`,
179
+ stableShellDefault: majorLine,
176
180
  promoteOnlyReleaseCandidate: true,
177
181
  requiredStatusCheck: "check / check",
178
182
  },
179
183
  optionalInputs: [
184
+ "buildchain-channel",
180
185
  "buildchain-ref",
186
+ "buildchain-alpha-contract-lock-path",
187
+ "buildchain-stable-contract-lock-path",
181
188
  "release-candidate-workflow-file",
182
189
  "release-candidate-workflow-name",
183
190
  "publish-required-artifacts-json",
@@ -210,6 +217,9 @@ export function createBuildchainContractWorld({
210
217
  ],
211
218
  },
212
219
  guarantees: [
220
+ "one declarative promotion job selects matching alpha or stable workflow shell, runtime, and contract lock",
221
+ "router, shell, runtime, lock, publication channel, target ref, and override identities are bound before candidate download",
222
+ "train and exact-SHA runtime overrides remain limited to trusted workflow_dispatch actors",
213
223
  "promotion reuses PR-stage release-candidate artifacts",
214
224
  "promotion does not run the heavy native build matrix",
215
225
  "built source and promotion channel SHA are recorded separately",
@@ -218,6 +228,37 @@ export function createBuildchainContractWorld({
218
228
  "GitHub Release passport and evidence publication is delegated to promote-buildchain-ref after the semver release transaction completes",
219
229
  ],
220
230
  }),
231
+ surface(root, {
232
+ id: "advanced-release-candidate-promote",
233
+ kind: "workflow",
234
+ path: ".github/workflows/.release-candidate-promote.yml",
235
+ publicRef: `${pkg.repository ? "kungfu-systems/buildchain" : "buildchain"}/.github/workflows/.release-candidate-promote.yml@${majorLine}`,
236
+ requiredInputs: ["channel"],
237
+ requiredOutputs: ["promoted-sha", "built-source-sha", "release-candidate-artifact"],
238
+ breakingDefaults: {
239
+ promoteOnlyReleaseCandidate: true,
240
+ requiredStatusCheck: "check / check",
241
+ },
242
+ optionalInputs: [
243
+ "buildchain-ref",
244
+ "buildchain-contract-lock-path",
245
+ "promotion-router-ref",
246
+ "promotion-router-sha",
247
+ "promotion-shell-ref",
248
+ "promotion-shell-sha",
249
+ "promotion-runtime-ref",
250
+ "promotion-runtime-sha",
251
+ "promotion-contract-lock-path",
252
+ "promotion-contract-lock-digest",
253
+ "promotion-publication-channel",
254
+ "promotion-target-ref",
255
+ "promotion-override-used",
256
+ ],
257
+ guarantees: [
258
+ "advanced promotion verifies routed shell, runtime, lock, publication channel, and target bindings before candidate resolution",
259
+ "promotion reuses PR-stage release-candidate artifacts and does not run the heavy native build matrix",
260
+ ],
261
+ }),
221
262
  surface(root, {
222
263
  id: "web-surface",
223
264
  kind: "workflow",
@@ -82,6 +82,7 @@ const SCHEMA_AND_STANDARD_FILES = Object.freeze([
82
82
 
83
83
  const WORKFLOW_AND_ACTION_FILES = Object.freeze([
84
84
  ".github/workflows/.build.yml",
85
+ ".github/workflows/.release-candidate-promote.yml",
85
86
  ".github/workflows/release-candidate-promote.yml",
86
87
  ".github/workflows/buildchain-ref-promotion.yml",
87
88
  ".github/workflows/release-propagation.yml",
@@ -372,6 +373,7 @@ export function createBuildchainPublicClaimDefinitions() {
372
373
  "packages/core/buildchain-contract.js",
373
374
  "scripts/buildchain-contract-lock.mjs",
374
375
  ".github/workflows/.build.yml",
376
+ ".github/workflows/.release-candidate-promote.yml",
375
377
  ".github/workflows/release-candidate-promote.yml",
376
378
  "docs/reusable-build-surface.md",
377
379
  ],
@@ -387,6 +389,7 @@ export function createBuildchainPublicClaimDefinitions() {
387
389
  "actions/promote-buildchain-ref/index.js",
388
390
  "actions/promote-buildchain-ref/action.yml",
389
391
  "scripts/ensure-github-release.mjs",
392
+ ".github/workflows/.release-candidate-promote.yml",
390
393
  ".github/workflows/release-candidate-promote.yml",
391
394
  "docs/release-governance.md",
392
395
  ],
@@ -415,6 +418,7 @@ export function createBuildchainPublicClaimDefinitions() {
415
418
  sourcePaths: [
416
419
  "scripts/npm-publish-transaction.mjs",
417
420
  "actions/promote-buildchain-ref/index.js",
421
+ ".github/workflows/.release-candidate-promote.yml",
418
422
  ".github/workflows/release-candidate-promote.yml",
419
423
  "docs/publish-transaction.md",
420
424
  ],
@@ -5,6 +5,7 @@ import { createPublicationAuthorityRegistry } from "./publication-authority.js";
5
5
 
6
6
  const DESCRIPTORS = Object.freeze([
7
7
  [".github/workflows/.build.yml", "non-publication-oidc"],
8
+ [".github/workflows/.release-candidate-promote.yml", "product-publication", true, ["npm-publish", "github-release", "channel-ref"], "trusted-publishing", "none", "fixed", "caller-bound"],
8
9
  [".github/workflows/.publication-authority.yml", "evidence-publication"],
9
10
  [".github/workflows/.release-verify.yml", "governance-write"],
10
11
  [".github/workflows/.sam-release.yml", "retired-deny"],
@@ -72,7 +72,7 @@ const CONTROLLER_SPECS = [
72
72
  },
73
73
  {
74
74
  id: "release-candidate-promotion",
75
- workflowId: "release-candidate-promote",
75
+ workflowId: ".release-candidate-promote",
76
76
  version: 1,
77
77
  capabilities: ["promotion-preflight", "artifact-admission", "publication-authority", "publish-transaction", "release-passport"],
78
78
  stages: ["preflight", "admit-release-candidate", "publication-authority", "publish", "passport", "aggregate"],
@@ -76,6 +76,8 @@ export function evaluatePublicationControlPlaneSnapshot({
76
76
  branchPolicy.requiredStatusCheck === requiredStatusCheck &&
77
77
  branchPolicy.requiredStatusChecks.includes(requiredStatusCheck) &&
78
78
  branchPolicy.requiredCheckPassed === true &&
79
+ /^[0-9a-f]{40}$/i.test(String(branchPolicy.requiredCheckSha || "")) &&
80
+ branchPolicy.requiredCheckSha === branchPolicy.pullRequestHeadSha &&
79
81
  branchPolicy.sourceSha === branchPolicy.headSha &&
80
82
  branchPolicy.mergedPullRequest === true &&
81
83
  branchPolicy.baseRef === branch &&
@@ -926,6 +926,43 @@ function normalizeTransaction(value = undefined) {
926
926
  };
927
927
  }
928
928
 
929
+ function normalizePromotionRouting(value = undefined) {
930
+ if (value === undefined) return undefined;
931
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
932
+ throw new Error("release.promotionRouting must be a JSON object");
933
+ }
934
+ if (value.contract !== "buildchain.promotion-routing/v1") {
935
+ throw new Error("release.promotionRouting contract must be buildchain.promotion-routing/v1");
936
+ }
937
+ for (const [label, field] of [
938
+ ["router.ref", value.router?.ref],
939
+ ["router.sha", value.router?.sha],
940
+ ["shell.ref", value.shell?.ref],
941
+ ["shell.sha", value.shell?.sha],
942
+ ["runtime.requestedRef", value.runtime?.requestedRef],
943
+ ["runtime.resolvedSha", value.runtime?.resolvedSha],
944
+ ["contractLock.path", value.contractLock?.path],
945
+ ["contractLock.digest", value.contractLock?.digest],
946
+ ["publication.channel", value.publication?.channel],
947
+ ["publication.targetRef", value.publication?.targetRef],
948
+ ]) {
949
+ if (!String(field || "").trim()) throw new Error(`release.promotionRouting.${label} is required`);
950
+ }
951
+ for (const [label, sha] of [
952
+ ["router.sha", value.router.sha],
953
+ ["shell.sha", value.shell.sha],
954
+ ["runtime.resolvedSha", value.runtime.resolvedSha],
955
+ ]) {
956
+ if (!/^[0-9a-f]{40}$/i.test(String(sha))) {
957
+ throw new Error(`release.promotionRouting.${label} must be a 40-character Git SHA`);
958
+ }
959
+ }
960
+ if (!/^sha256:[0-9a-f]{64}$/i.test(String(value.contractLock.digest))) {
961
+ throw new Error("release.promotionRouting.contractLock.digest must be a sha256 digest");
962
+ }
963
+ return JSON.parse(JSON.stringify(value));
964
+ }
965
+
929
966
  export function createArtifactEvidence({ assets = [], repository = "", tag = "", sourceSha = "", workflow = {} } = {}) {
930
967
  const normalizedAssets = assets.map((asset, index) => normalizeAsset(asset, index));
931
968
  return {
@@ -1002,6 +1039,7 @@ export function createReleasePassport({
1002
1039
  const normalizedPackageSet = normalizePackageSet(packageSet, { packageName, packageVersion, publish });
1003
1040
  const normalizedTrustedPublishing = normalizeTrustedPublishing(trustedPublishing, { workflow, publish });
1004
1041
  const normalizedTransaction = normalizeTransaction(transaction);
1042
+ const normalizedPromotionRouting = normalizePromotionRouting(release.promotionRouting);
1005
1043
  const normalizedBuildSummary = buildSummary ? normalizeEvidenceDocument(buildSummary, "buildSummary") : undefined;
1006
1044
  const normalizedBuildFacts = (buildFacts || [])
1007
1045
  .map((fact, index) => normalizeEvidenceDocument(fact, `buildFacts[${index}]`))
@@ -1150,6 +1188,7 @@ export function createReleasePassport({
1150
1188
  ...(anchorManifest ? { anchorManifest } : {}),
1151
1189
  ...(normalizedTrustedPublishing ? { trustedPublishing: normalizedTrustedPublishing } : {}),
1152
1190
  ...(normalizedTransaction ? { transaction: normalizedTransaction } : {}),
1191
+ ...(normalizedPromotionRouting ? { promotionRouting: normalizedPromotionRouting } : {}),
1153
1192
  ...(normalizedBuildSummary ? { buildSummary: normalizedBuildSummary } : {}),
1154
1193
  ...(normalizedBuildFacts.length > 0 ? { buildFacts: normalizedBuildFacts } : {}),
1155
1194
  ...(normalizedPlatformArtifactManifests.length > 0 ? { platformArtifactManifests: normalizedPlatformArtifactManifests } : {}),
@@ -278,7 +278,10 @@ function main() {
278
278
  const independentApprovals = [...latestReviews.values()].filter((review) =>
279
279
  review.state === "APPROVED" && review.user?.login !== mergedPullRequest?.user?.login
280
280
  );
281
- const checkRuns = githubJson(`repos/${repository}/commits/${sourceSha}/check-runs?per_page=100`, "source check runs");
281
+ const pullRequestHeadSha = String(mergedPullRequest?.head?.sha || "").toLowerCase();
282
+ const checkRuns = /^[0-9a-f]{40}$/.test(pullRequestHeadSha)
283
+ ? githubJson(`repos/${repository}/commits/${pullRequestHeadSha}/check-runs?per_page=100`, "merged pull-request head check runs")
284
+ : { check_runs: [] };
282
285
  const requiredStatusCheckPolicy = branchState.protection?.required_status_checks || {};
283
286
  const requiredStatusChecks = requiredStatusCheckPolicy.contexts || [];
284
287
  const requiredCheckSource = (requiredStatusCheckPolicy.checks || []).find((entry) => entry.context === requiredStatusCheck);
@@ -295,10 +298,12 @@ function main() {
295
298
  (!requiredCheckSource?.app_id || entry.app?.id === requiredCheckSource.app_id)
296
299
  ),
297
300
  requiredCheckAppId: requiredCheckSource?.app_id || 0,
301
+ requiredCheckSha: pullRequestHeadSha,
298
302
  sourceSha,
299
303
  headSha: String(branchState.commit?.sha || "").toLowerCase(),
300
304
  mergedPullRequest: Boolean(mergedPullRequest),
301
305
  pullRequestNumber: mergedPullRequest?.number || 0,
306
+ pullRequestHeadSha,
302
307
  baseRef: mergedPullRequest?.base?.ref || "",
303
308
  headRepository: mergedPullRequest?.head?.repo?.full_name || "",
304
309
  approvalCount: independentApprovals.length,
@@ -6,6 +6,10 @@ import {
6
6
  } from "../packages/core/public-surface-audit.js";
7
7
  import { evaluateBuildchainContractLock } from "../packages/core/buildchain-contract.js";
8
8
  import { generateChannelBuildWorkflow } from "./generate-channel-build-workflow.mjs";
9
+ import {
10
+ generateChannelPromotionWorkflow,
11
+ parsePromotionShellRouting,
12
+ } from "./generate-channel-promotion-workflow.mjs";
9
13
 
10
14
  const root = process.cwd();
11
15
  const sharedActionTsupConfig = fs.readFileSync(path.join(root, "scripts/tsup-action.config.mjs"), "utf8");
@@ -44,6 +48,8 @@ const requiredPaths = [
44
48
  "scripts/release-line-dry-run.mjs",
45
49
  "scripts/buildchain-channel-router.mjs",
46
50
  "scripts/generate-channel-build-workflow.mjs",
51
+ "scripts/promotion-channel-router.mjs",
52
+ "scripts/generate-channel-promotion-workflow.mjs",
47
53
  "scripts/build-standalone-binary.mjs",
48
54
  "scripts/create-release-bundle.mjs",
49
55
  "scripts/ensure-github-release.mjs",
@@ -81,6 +87,7 @@ const requiredPaths = [
81
87
  ".github/workflows/buildchain-patrol-monthly.yml",
82
88
  ".github/workflows/buildchain-alpha-self-dogfood.yml",
83
89
  ".github/workflows/release-candidate-promote.yml",
90
+ ".github/workflows/.release-candidate-promote.yml",
84
91
  ".github/workflows/release-propagation.yml",
85
92
  ".github/workflows/npm-publish.yml",
86
93
  ".github/workflows/paper-release.yml",
@@ -174,6 +181,36 @@ const channelBuildWorkflow = fs.readFileSync(
174
181
  if (channelBuildWorkflow !== generateChannelBuildWorkflow(reusableBuildWorkflow)) {
175
182
  throw new Error("generated channel build workflow is stale");
176
183
  }
184
+ const advancedPromotionWorkflow = fs.readFileSync(
185
+ path.join(root, ".github/workflows/.release-candidate-promote.yml"),
186
+ "utf8",
187
+ );
188
+ const channelPromotionWorkflow = fs.readFileSync(
189
+ path.join(root, ".github/workflows/release-candidate-promote.yml"),
190
+ "utf8",
191
+ );
192
+ const promotionShellRouting = parsePromotionShellRouting(
193
+ fs.readFileSync(path.join(root, ".buildchain/promotion-shell-routing.json"), "utf8"),
194
+ { major: Number(selfDogfoodMajor) },
195
+ );
196
+ if (channelPromotionWorkflow !== generateChannelPromotionWorkflow(advancedPromotionWorkflow, {
197
+ major: Number(selfDogfoodMajor),
198
+ shellRouting: promotionShellRouting,
199
+ })) {
200
+ throw new Error("generated channel promotion workflow is stale");
201
+ }
202
+ for (const requiredSnippet of [
203
+ "buildchain-channel:",
204
+ `/.github/workflows/.release-candidate-promote.yml@v${selfDogfoodMajor}-alpha`,
205
+ `/release-candidate-promote.yml@${promotionShellRouting.stable.callRef}`,
206
+ `STABLE_SHELL_REF: v${selfDogfoodMajor}`,
207
+ "promotion-contract-lock-digest:",
208
+ "promotion runtime override is only allowed for trusted workflow_dispatch runs",
209
+ ]) {
210
+ if (!channelPromotionWorkflow.includes(requiredSnippet)) {
211
+ throw new Error(`channel promotion workflow missing routing contract: ${requiredSnippet}`);
212
+ }
213
+ }
177
214
  for (const requiredSnippet of [
178
215
  "buildchain-channel:",
179
216
  "uses: ./.github/workflows/.build.yml",
@@ -814,7 +851,7 @@ for (const forbiddenSnippet of [
814
851
  for (const requiredSnippet of [
815
852
  "id-token: write",
816
853
  "actions: read",
817
- "uses: ./.github/workflows/release-candidate-promote.yml",
854
+ "uses: ./.github/workflows/.release-candidate-promote.yml",
818
855
  "github.event.workflow_run.event == 'push'",
819
856
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
820
857
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",
@@ -828,7 +865,7 @@ for (const requiredSnippet of [
828
865
  throw new Error(`buildchain ref promotion workflow missing npm transaction snippet: ${requiredSnippet}`);
829
866
  }
830
867
  }
831
- const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/release-candidate-promote.yml"), "utf8");
868
+ const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/.release-candidate-promote.yml"), "utf8");
832
869
  for (const requiredSnippet of [
833
870
  "release-passport-kfd-1-witness-jsons:",
834
871
  "release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons }}",