@kungfu-tech/buildchain 4.0.2-alpha.2 → 4.0.2-alpha.20

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 (71) hide show
  1. package/architecture/agent-change-map.md +52 -1
  2. package/architecture/ci-lane-change-budget.json +349 -0
  3. package/architecture/internal-capabilities.json +44 -1
  4. package/architecture/maintainability-debt.json +53 -33
  5. package/architecture/maintainability-policy.json +20 -15
  6. package/architecture/v3-core-mechanism-inventory.json +1 -0
  7. package/architecture/v4-delivery-warrant-shadow-fixtures.json +5 -5
  8. package/architecture/v4-release-invocation-fixtures.json +20 -0
  9. package/architecture/v4-release-topology.json +160 -84
  10. package/architecture/v4-runtime-semantic-closure.json +4 -1
  11. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  12. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  13. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  14. package/contracts/buildchain-v2-residuals-v1.json +0 -54
  15. package/contracts/v4-release-invocation-v1.schema.json +50 -2
  16. package/dist/site/buildchain-contract.json +87 -42
  17. package/dist/site/buildchain-site.json +15 -10
  18. package/dist/site/capability-registry.json +1 -1
  19. package/dist/site/controller-registry.json +46 -10
  20. package/dist/site/kfd-claims.json +174 -196
  21. package/dist/site/kfd-upstream-aggregate.json +1 -1
  22. package/dist/site/manual-registry.json +2 -2
  23. package/dist/site/node-api-registry.json +95 -20
  24. package/dist/site/page-registry.json +9 -4
  25. package/dist/site/public-surface-audit.json +178 -198
  26. package/dist/site/publication-authority-registry.json +67 -57
  27. package/dist/site/publication-registry.json +4 -4
  28. package/dist/site/site-manifest.json +6 -6
  29. package/dist/site/workflow-registry.json +209 -259
  30. package/docs/node-api-reference.md +25 -22
  31. package/docs/reusable-build-surface.md +16 -0
  32. package/package.json +2 -2
  33. package/packages/core/buildchain-publication-authority.js +3 -2
  34. package/packages/core/ci-lane-change-budget.js +14 -1
  35. package/packages/core/dev-delivery-candidate-identity.js +18 -0
  36. package/packages/core/dev-delivery-execution-transfer.js +6 -7
  37. package/packages/core/dev-delivery-warrant-legacy-recovery.js +4 -2
  38. package/packages/core/dev-delivery-warrant-native-compatibility.js +33 -0
  39. package/packages/core/dev-delivery-warrant-state.js +42 -26
  40. package/packages/core/dev-delivery-warrant.js +8 -0
  41. package/packages/core/dev-delivery-writer-protocol-transition.js +72 -0
  42. package/packages/core/release-tail-product-capabilities.js +29 -0
  43. package/packages/core/release-tail-provider-plane.js +3 -3
  44. package/packages/core/v4-canonical-contracts.js +6 -0
  45. package/packages/core/v4-product-publication.js +387 -0
  46. package/packages/core/v4-protected-publication-source.js +93 -0
  47. package/packages/core/v4-publication-qualification.js +1 -0
  48. package/packages/core/v4-release-invocation.js +72 -3
  49. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  50. package/scripts/check-inventory.mjs +13 -22
  51. package/scripts/check-maintainability.mjs +16 -113
  52. package/scripts/check-v4-release-topology.mjs +296 -40
  53. package/scripts/check-v4-universal-workflow-bootstrap.mjs +265 -0
  54. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  55. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  56. package/scripts/dev-delivery-warrant.mjs +84 -100
  57. package/scripts/dev-pr-auto-merge.mjs +15 -15
  58. package/scripts/generate-channel-build-workflow.mjs +5 -1
  59. package/scripts/generate-channel-promotion-workflow.mjs +5 -3
  60. package/scripts/generate-v4-universal-workflow-facades.mjs +415 -0
  61. package/scripts/maintainability-public-surface.mjs +115 -0
  62. package/scripts/release-candidate-resolver.mjs +23 -24
  63. package/scripts/resume-from-candidate-run.mjs +16 -17
  64. package/scripts/universal-facade-maintainability.mjs +82 -0
  65. package/scripts/v4-product-publication-intent.mjs +114 -0
  66. package/scripts/v4-release-candidate-adapter.mjs +41 -0
  67. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  68. package/scripts/v4-universal-workflow-engine.mjs +617 -0
  69. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  70. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  71. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -6,19 +6,21 @@ import {
6
6
  closeDevDeliveryWarrant,
7
7
  createDevDeliveryQueue,
8
8
  createNativeCommandContract,
9
+ fenceDevDeliveryWriterProtocol,
9
10
  heartbeatDevDeliveryWarrant,
10
11
  observeDevDeliveryQueue,
11
12
  qualifyDevDeliveryWarrant,
12
- recoverExpiredDevDeliveryWarrant,
13
13
  reconcileDevDeliveryTerminalEvidence,
14
14
  selectDevDeliveryWarrant,
15
15
  settleDevDeliveryTerminalEvent,
16
16
  submitDevDeliveryCandidate,
17
17
  } from "../packages/core/dev-delivery-warrant.js";
18
18
  import { recoverLegacyTerminalDevDeliveryQueue } from "../packages/core/dev-delivery-warrant-legacy-recovery.js";
19
+ import { reuseExactActiveDevDeliverySourceProof } from "../packages/core/dev-delivery-candidate-identity.js";
19
20
  import { runV4DeliveryWarrantReadCandidate } from "../packages/core/v4-delivery-warrant-read-candidate.js";
20
21
 
21
22
  import { GitHubDevDeliveryStore } from "./dev-delivery-warrant-store.mjs";
23
+ import { persistDevDeliveryTransition } from "./dev-delivery-warrant-transition.mjs";
22
24
  import { devDeliveryCliOptions } from "./dev-delivery-warrant-options.mjs";
23
25
  export { GitHubDevDeliveryStore, devDeliveryCliOptions };
24
26
 
@@ -91,6 +93,13 @@ function requireTerminalEvidenceCas(options) {
91
93
  "terminal evidence reconciliation execute requires expected-old CAS",
92
94
  );
93
95
  }
96
+ if (
97
+ options.command === "fence-writer-protocol" &&
98
+ options.execute &&
99
+ !options.expectedOldStateRoot
100
+ ) {
101
+ throw new Error("writer protocol fence execute requires expected-old CAS");
102
+ }
94
103
  }
95
104
 
96
105
  function normalizeRepository(value) {
@@ -175,6 +184,9 @@ function jsonObject(value, label) {
175
184
  }
176
185
 
177
186
  function transitionFor(command, queue, options) {
187
+ if (command === "fence-writer-protocol") {
188
+ return fenceDevDeliveryWriterProtocol(queue, { now: options.now });
189
+ }
178
190
  if (command === "submit") {
179
191
  const nativeCommandContract = options.environmentRoot
180
192
  ? createNativeCommandContract(options.nativeCommand)
@@ -188,54 +200,55 @@ function transitionFor(command, queue, options) {
188
200
  "native command contract root does not match native-command",
189
201
  );
190
202
  }
203
+ const input = {
204
+ pullRequestNumber: positiveInteger(
205
+ options.pullRequestNumber,
206
+ "pullRequestNumber",
207
+ ),
208
+ sourceHead: exactSha(options.sourceHead, "sourceHead"),
209
+ assignmentRoot: exactRoot(options.assignmentRoot, "assignmentRoot"),
210
+ initiativeRoot: exactRoot(options.initiativeRoot, "initiativeRoot"),
211
+ sourceIdentityRoot: exactRoot(
212
+ options.sourceIdentityRoot,
213
+ "sourceIdentityRoot",
214
+ ),
215
+ sourcePatchRoot: exactRoot(options.sourcePatchRoot, "sourcePatchRoot"),
216
+ sourceProofRoot: exactRoot(options.sourceProofRoot, "sourceProofRoot"),
217
+ planRoot: exactRoot(options.planRoot, "planRoot"),
218
+ closureRoot: exactRoot(options.closureRoot, "closureRoot"),
219
+ dependencyRoot: exactRoot(options.dependencyRoot, "dependencyRoot"),
220
+ toolchainRoot: exactRoot(options.toolchainRoot, "toolchainRoot"),
221
+ ...(options.environmentRoot
222
+ ? {
223
+ environmentRoot: exactRoot(
224
+ options.environmentRoot,
225
+ "environmentRoot",
226
+ ),
227
+ }
228
+ : {}),
229
+ ...(nativeCommandContract ? { nativeCommandContract } : {}),
230
+ affectedPaths: jsonList(options.affectedPaths, "affected paths"),
231
+ shardEvidenceRoots: jsonList(
232
+ options.shardEvidenceRoots,
233
+ "shard evidence roots",
234
+ ),
235
+ sourceWorkflowRunId: options.sourceWorkflowRunId
236
+ ? positiveInteger(options.sourceWorkflowRunId, "sourceWorkflowRunId")
237
+ : 0,
238
+ deliveryClass: options.deliveryClass,
239
+ priority: options.priority || "ordinary",
240
+ ...(options.releaseBlockerPriority
241
+ ? {
242
+ releaseBlockerPriority: jsonObject(
243
+ options.releaseBlockerPriority,
244
+ "release blocker priority",
245
+ ),
246
+ }
247
+ : {}),
248
+ };
191
249
  return submitDevDeliveryCandidate(
192
250
  queue,
193
- {
194
- pullRequestNumber: positiveInteger(
195
- options.pullRequestNumber,
196
- "pullRequestNumber",
197
- ),
198
- sourceHead: exactSha(options.sourceHead, "sourceHead"),
199
- assignmentRoot: exactRoot(options.assignmentRoot, "assignmentRoot"),
200
- initiativeRoot: exactRoot(options.initiativeRoot, "initiativeRoot"),
201
- sourceIdentityRoot: exactRoot(
202
- options.sourceIdentityRoot,
203
- "sourceIdentityRoot",
204
- ),
205
- sourcePatchRoot: exactRoot(options.sourcePatchRoot, "sourcePatchRoot"),
206
- sourceProofRoot: exactRoot(options.sourceProofRoot, "sourceProofRoot"),
207
- planRoot: exactRoot(options.planRoot, "planRoot"),
208
- closureRoot: exactRoot(options.closureRoot, "closureRoot"),
209
- dependencyRoot: exactRoot(options.dependencyRoot, "dependencyRoot"),
210
- toolchainRoot: exactRoot(options.toolchainRoot, "toolchainRoot"),
211
- ...(options.environmentRoot
212
- ? {
213
- environmentRoot: exactRoot(
214
- options.environmentRoot,
215
- "environmentRoot",
216
- ),
217
- }
218
- : {}),
219
- ...(nativeCommandContract ? { nativeCommandContract } : {}),
220
- affectedPaths: jsonList(options.affectedPaths, "affected paths"),
221
- shardEvidenceRoots: jsonList(
222
- options.shardEvidenceRoots,
223
- "shard evidence roots",
224
- ),
225
- sourceWorkflowRunId: options.sourceWorkflowRunId
226
- ? positiveInteger(options.sourceWorkflowRunId, "sourceWorkflowRunId")
227
- : 0,
228
- deliveryClass: options.deliveryClass,
229
- priority: options.priority || "ordinary",
230
- ...(options.releaseBlockerPriority
231
- ? {
232
- releaseBlockerPriority: jsonObject(
233
- options.releaseBlockerPriority,
234
- "release blocker priority",
235
- ),
236
- }
237
- : {}),
238
- },
251
+ reuseExactActiveDevDeliverySourceProof(queue.activeWarrant, input),
239
252
  { now: options.now },
240
253
  );
241
254
  }
@@ -450,51 +463,16 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
450
463
  }
451
464
  if (options.command === "observe") return observeQueue(loaded, options);
452
465
  const initialLoaded = loaded;
453
- let changed = transitionFor(options.command, loaded.queue, options);
454
- let mutates = changed.queue.stateRoot !== loaded.queue.stateRoot;
455
- let write = null;
456
- if (options.execute && mutates) {
457
- if (changed.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
458
- throw new Error(
459
- "transition receipt expected-old root does not match the loaded authority",
460
- );
461
- }
462
- try {
463
- write = await store.write({
464
- stateRef: options.stateRef,
465
- queue: changed.queue,
466
- expectedCommitSha: loaded.commitSha,
467
- expectedStateRoot: loaded.queue.stateRoot,
468
- receiptRoot: changed.receiptRoot,
469
- });
470
- } catch (error) {
471
- if (options.command !== "settle" || options.expectedOldStateRoot)
472
- throw error;
473
- const latest = await store.read({
474
- stateRef: options.stateRef,
475
- protectedBase: options.branch,
476
- now: options.now,
477
- });
478
- const reconciled = transitionFor(options.command, latest.queue, options);
479
- const reconciledMutates =
480
- reconciled.queue.stateRoot !== latest.queue.stateRoot;
481
- if (
482
- reconciledMutates ||
483
- reconciled.receipt.action !== "duplicate-terminal-event-noop"
484
- )
485
- throw error;
486
- loaded = latest;
487
- changed = reconciled;
488
- mutates = false;
489
- concurrencyRecovery = {
490
- schema: "kungfu.buildchain.dev-delivery-concurrency-recovery/v1",
491
- action: "terminal-settlement-race-noop",
492
- initialCommitSha: initialLoaded.commitSha,
493
- observedCommitSha: latest.commitSha,
494
- observedStateRoot: latest.queue.stateRoot,
495
- };
496
- }
497
- }
466
+ const persisted = await persistDevDeliveryTransition({
467
+ store,
468
+ options,
469
+ loaded,
470
+ initialLoaded,
471
+ changed: transitionFor(options.command, loaded.queue, options),
472
+ transitionFor,
473
+ });
474
+ loaded = persisted.loaded;
475
+ concurrencyRecovery = persisted.concurrencyRecovery || concurrencyRecovery;
498
476
  return {
499
477
  schema: "kungfu.buildchain.dev-delivery-command-result/v1",
500
478
  ok: true,
@@ -503,21 +481,26 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
503
481
  stateRef: options.stateRef,
504
482
  before: { commitSha: loaded.commitSha, stateRoot: loaded.queue.stateRoot },
505
483
  after: {
506
- commitSha: write?.commitSha || loaded.commitSha,
507
- stateRoot: changed.queue.stateRoot,
484
+ commitSha: persisted.write?.commitSha || loaded.commitSha,
485
+ stateRoot: persisted.changed.queue.stateRoot,
508
486
  },
509
487
  mutationAuthorized: options.execute,
510
- mutationApplied: Boolean(write),
488
+ mutationApplied: Boolean(persisted.write),
511
489
  concurrencyRecovery,
512
- receipt: changed.receipt,
513
- receiptRoot: changed.receiptRoot,
514
- warrant: changed.warrant || changed.queue.activeWarrant || null,
515
- observation: observeDevDeliveryQueue(changed.queue, { now: options.now }),
490
+ receipt: persisted.changed.receipt,
491
+ receiptRoot: persisted.changed.receiptRoot,
492
+ warrant:
493
+ persisted.changed.warrant ||
494
+ persisted.changed.queue.activeWarrant ||
495
+ null,
496
+ observation: observeDevDeliveryQueue(persisted.changed.queue, {
497
+ now: options.now,
498
+ }),
516
499
  };
517
500
  }
518
501
 
519
502
  function usage() {
520
- return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|recover-legacy-terminal|close|settle|reconcile-terminal-evidence|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nLegacy terminal recovery:\n recover-legacy-terminal --expected-old sha256:... --legacy-terminal-recovery FILE [--execute]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
503
+ return "Usage:\n buildchain dev warrant <fence-writer-protocol|submit|select|heartbeat|qualify|recover|recover-legacy-terminal|close|settle|reconcile-terminal-evidence|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nWriter protocol fence:\n fence-writer-protocol --expected-old sha256:... [--execute]\n\nLegacy terminal recovery:\n recover-legacy-terminal --expected-old sha256:... --legacy-terminal-recovery FILE [--execute]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
521
504
  }
522
505
 
523
506
  async function main() {
@@ -529,6 +512,7 @@ async function main() {
529
512
  const options = devDeliveryCliOptions(args);
530
513
  if (
531
514
  ![
515
+ "fence-writer-protocol",
532
516
  "submit",
533
517
  "select",
534
518
  "heartbeat",
@@ -723,7 +723,6 @@ function renderAdmissionComment(receipt, receiptRoot) {
723
723
  "GitHub auto-merge state is observed evidence only; it is not Buildchain admission authority.",
724
724
  ].join("\n");
725
725
  }
726
-
727
726
  async function publishAdmissionDiagnostic(client, options, receipt, receiptRoot) {
728
727
  const marker = `<!-- ${AGENT_ADMISSION_MARKER} pr=${receipt.pullRequestNumber} head=${receipt.expectedHeadSha} -->`;
729
728
  const body = renderAdmissionComment(receipt, receiptRoot);
@@ -743,7 +742,6 @@ async function publishAdmissionDiagnostic(client, options, receipt, receiptRoot)
743
742
  }
744
743
  return { commentId: comment?.id || null, commentUrl: comment?.html_url || "", statusPublished: Boolean(status) };
745
744
  }
746
-
747
745
  function createAdmissionReceipt({ options, pr = {}, state, reason, readiness, decision = {}, queue = null, warrant = null }) {
748
746
  return createDevPrAdmissionReceipt({ options, pr, state, reason, readiness, decision, queue, warrant, labels: labelsOf(pr), nextAction: nextAdmissionAction });
749
747
  }
@@ -751,16 +749,21 @@ function createAdmissionReceipt({ options, pr = {}, state, reason, readiness, de
751
749
  function targetedFailure({ options, pr, state, reason, readiness, decision, queue }) {
752
750
  const receipt = createAdmissionReceipt({ options, pr, state, reason, readiness, decision, queue });
753
751
  return {
754
- schema: AGENT_ADMISSION_RESULT_SCHEMA,
755
- ok: false,
756
- mode: options.dryRun ? "plan" : "execute",
757
- outcome: "targeted-admission-failed",
758
- receipt,
759
- receiptRoot: contentRoot(receipt),
760
- diagnostic: null,
752
+ schema: AGENT_ADMISSION_RESULT_SCHEMA, ok: false, mode: options.dryRun ? "plan" : "execute",
753
+ outcome: "targeted-admission-failed", receipt, receiptRoot: contentRoot(receipt), diagnostic: null,
761
754
  };
762
755
  }
763
-
756
+ async function readTargetPullRequest(client, number, options) {
757
+ let { pollMergeableAttempts: attempts, pollMergeableDelayMs: delayMs } = options;
758
+ for (; attempts > 1; attempts -= 1) {
759
+ try {
760
+ return await client.getPullRequest(number, { attempts, delayMs });
761
+ } catch {
762
+ await delay(delayMs);
763
+ }
764
+ }
765
+ return client.getPullRequest(number, { attempts, delayMs });
766
+ }
764
767
  export async function runDevPrAdmission(optionsInput = {}, clientInput) {
765
768
  const options = normalizeOptions(optionsInput);
766
769
  if (!options.targetBranch) throw new Error("target branch is required");
@@ -776,12 +779,9 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
776
779
 
777
780
  let pr;
778
781
  try {
779
- pr = await client.getPullRequest(options.targetPullRequestNumber, {
780
- attempts: options.pollMergeableAttempts,
781
- delayMs: options.pollMergeableDelayMs,
782
- });
782
+ pr = await readTargetPullRequest(client, options.targetPullRequestNumber, options);
783
783
  } catch (error) {
784
- return targetedFailure({ options, pr: {}, state: "missing", reason: "pull-request-not-found" });
784
+ return targetedFailure({ options, pr: {}, state: "blocked", reason: "pull-request-read-failed" });
785
785
  }
786
786
 
787
787
  const readinessFence = createPreReadinessQueueFence({ client, options, sleep: delay });
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
6
7
 
7
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
9
  const sourcePath = path.join(root, ".github/workflows/.build.yml");
@@ -403,7 +404,10 @@ export function generateChannelBuildWorkflow(source) {
403
404
  ].join("\n"),
404
405
  )
405
406
  .replace("\n alpha:\n", `\n${routerControllerPlanJob()}\n\n alpha:\n`);
406
- return `${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`;
407
+ return migrateV4UniversalWorkflowFacade(
408
+ `${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`,
409
+ ".github/workflows/build.yml",
410
+ );
407
411
  }
408
412
 
409
413
  function main() {
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
6
7
 
7
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
9
  const sourcePath = path.join(root, ".github/workflows/.release-candidate-promote.yml");
@@ -214,8 +215,9 @@ function consumerAdmissionJob() {
214
215
  id: policy
215
216
  env:
216
217
  BUILDCHAIN_CONSUMER_ROOT: .buildchain/consumer
218
+ BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/release-candidate-promote.yml' || inputs.publication-publisher-workflow-path }}
217
219
  BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: \${{ needs.resolve-promotion.outputs.channel }}
218
- BUILDCHAIN_INVOKED_WORKFLOW: .github/workflows/release-candidate-promote.yml
220
+ BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/.release-candidate-promote.yml' || '.github/workflows/release-candidate-promote.yml' }}
219
221
  BUILDCHAIN_WORKFLOW_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
220
222
  BUILDCHAIN_RUNTIME_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
221
223
  BUILDCHAIN_STABLE_CONTRACT_LOCK_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}
@@ -301,7 +303,7 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
301
303
  if (!inputNames.includes(required)) throw new Error(`advanced promotion workflow missing input: ${required}`);
302
304
  }
303
305
  const invokeForwarded = routedInputs(inputNames, alphaRoute, major);
304
- return `# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
306
+ return migrateV4UniversalWorkflowFacade(`# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
305
307
  name: Release Candidate Promote
306
308
 
307
309
  on:
@@ -512,7 +514,7 @@ ${consumerAdmissionJob()}
512
514
  with:
513
515
  ${invokeForwarded}
514
516
  secrets: inherit
515
- `;
517
+ `, ".github/workflows/release-candidate-promote.yml");
516
518
  }
517
519
 
518
520
  function main() {