@kungfu-tech/buildchain 2.11.14-alpha.7 → 2.12.0-alpha.0

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.
@@ -4,7 +4,7 @@
4
4
  "status": "passed",
5
5
  "summary": {
6
6
  "cliCommandCount": 75,
7
- "workflowCount": 38,
7
+ "workflowCount": 39,
8
8
  "actionCount": 4,
9
9
  "sitePageCount": 45,
10
10
  "docCommandRefCount": 237,
@@ -684,6 +684,66 @@
684
684
  ],
685
685
  "inputCount": 1
686
686
  },
687
+ {
688
+ "id": "build",
689
+ "path": ".github/workflows/build.yml",
690
+ "inputs": [
691
+ "artifact-name",
692
+ "artifact-name-template",
693
+ "artifact-paths",
694
+ "artifact-relay-s3-bucket",
695
+ "artifact-relay-s3-download-role-arn",
696
+ "artifact-relay-s3-oidc-audience",
697
+ "artifact-relay-s3-prefix",
698
+ "artifact-relay-s3-region",
699
+ "artifact-relay-s3-role-arn",
700
+ "artifact-relay-s3-upload-role-arn",
701
+ "artifact-retention-days",
702
+ "artifact-transfer-mode",
703
+ "build-command",
704
+ "buildchain-alpha-contract-lock-path",
705
+ "buildchain-channel",
706
+ "buildchain-contract-compatibility-policy",
707
+ "buildchain-contract-drift-issue-mode",
708
+ "buildchain-contract-lock-path",
709
+ "buildchain-ref",
710
+ "buildchain-repository",
711
+ "buildchain-stable-contract-lock-path",
712
+ "checkout-cache-fallback",
713
+ "checkout-cache-fetch-attempts",
714
+ "checkout-cache-github-timeout-seconds",
715
+ "checkout-cache-mirror-url-template",
716
+ "checkout-cache-mode",
717
+ "checkout-cache-reference-repository-template",
718
+ "checkout-cache-timeout-seconds",
719
+ "expected-artifacts-json",
720
+ "install-command",
721
+ "linux-container-image",
722
+ "linux-container-preset",
723
+ "node-version",
724
+ "platforms-json",
725
+ "process-sample-interval-ms",
726
+ "process-summary-path",
727
+ "publish-anchor-request-json",
728
+ "publish-channel",
729
+ "publish-refs-json",
730
+ "publish-registry",
731
+ "publish-source-ref",
732
+ "release-candidate",
733
+ "requested-parallelism",
734
+ "require-build",
735
+ "require-install",
736
+ "require-trusted-event",
737
+ "require-verify",
738
+ "runner-preset",
739
+ "sample-process-tree",
740
+ "setup-node",
741
+ "untrusted-policy",
742
+ "verify-command",
743
+ "working-directory"
744
+ ],
745
+ "inputCount": 53
746
+ },
687
747
  {
688
748
  "id": "buildchain-alpha-self-dogfood",
689
749
  "path": ".github/workflows/buildchain-alpha-self-dogfood.yml",
@@ -2546,8 +2606,8 @@
2546
2606
  "workflowRegistryPath": "dist/site/workflow-registry.json",
2547
2607
  "pageRegistryPath": "dist/site/page-registry.json",
2548
2608
  "cliRegistryDigest": "3c0729f5351eb82fe26625fa5aabf30903f45f68af2431e02860f85ada468ac9",
2549
- "workflowRegistryDigest": "5875dd6440e9585dc8ce86fc61af8546a60ae1140ed25789199adead2846d4fe",
2550
- "pageRegistryDigest": "de7377d68eb8b3069b83a2096de8ea24153090e746649015388224b9997e2340"
2609
+ "workflowRegistryDigest": "2bd177fe6e3cbcd382ba30d90c96f35865ef17fc29e451715696cb076d7da933",
2610
+ "pageRegistryDigest": "e496f039ccd56f2be611433c075b5150a4ede2b04682bb7af0ee4716b1f5eb6a"
2551
2611
  },
2552
2612
  "comparison": {
2553
2613
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-11T00:08:19.877Z",
5
- "publishedAt": "2026-07-11T00:08:19.877Z",
4
+ "generatedAt": "2026-07-11T01:35:09.048Z",
5
+ "publishedAt": "2026-07-11T01:35:09.048Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "7d3f049ffc8b567c3afad9fa9f8e974b50fd3b90",
22
+ "sourceRevision": "3dde1345af4880782c221385158a8a9f3674fcad",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "package": {
34
34
  "name": "@kungfu-tech/buildchain",
35
- "version": "2.11.14-alpha.7",
35
+ "version": "2.12.0-alpha.0",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-11T00:08:19.877Z",
5
- "publishedAt": "2026-07-11T00:08:19.877Z",
4
+ "generatedAt": "2026-07-11T01:35:09.048Z",
5
+ "publishedAt": "2026-07-11T01:35:09.048Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "7d3f049ffc8b567c3afad9fa9f8e974b50fd3b90",
22
+ "sourceRevision": "3dde1345af4880782c221385158a8a9f3674fcad",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.11.14-alpha.7",
40
+ "version": "2.12.0-alpha.0",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -53,7 +53,7 @@
53
53
  "path": "docs/MAP.md",
54
54
  "plane": "use",
55
55
  "exists": true,
56
- "digest": "sha256:02987c6bf99441eddc630fa4050fd109c15923047a4591e26d54a714c124539a"
56
+ "digest": "sha256:bad18d05d3d03b77bf64d8697fc351d58b1372a355a4f25c6719bf08984a79a2"
57
57
  },
58
58
  {
59
59
  "id": "install",
@@ -205,7 +205,7 @@
205
205
  "path": "docs/reusable-build-surface.md",
206
206
  "plane": "use",
207
207
  "exists": true,
208
- "digest": "sha256:4611e4ae47091687f50b489ccc6caba911f13bb1f73b27a5d44581d8a6450d06"
208
+ "digest": "sha256:670528f1710001a488b2c544db7c104a4770c0ce7d62afa6c6017e473677fd03"
209
209
  },
210
210
  {
211
211
  "id": "publish-transaction",
@@ -221,7 +221,7 @@
221
221
  "path": "docs/release-governance.md",
222
222
  "plane": "why",
223
223
  "exists": true,
224
- "digest": "sha256:69c4146640626bc03b93ac178e250745e45939ba9443f3ba27207a35522036c4"
224
+ "digest": "sha256:db9077f3748954dee3d28cd1e418dd537fec498df31155c560f7929b622888cb"
225
225
  },
226
226
  {
227
227
  "id": "release-flow",
@@ -245,7 +245,7 @@
245
245
  "path": "docs/versioning.md",
246
246
  "plane": "why",
247
247
  "exists": true,
248
- "digest": "sha256:2c79f7d1a072a40c75d889beac38ae843b2170587fc570ce40722d3968b6506e"
248
+ "digest": "sha256:400027d5e34d5c22752f7664bb9ca9ce99c04e7be298183d0303f3fb04fa7747"
249
249
  },
250
250
  {
251
251
  "id": "web-surface-deployments",
@@ -59,7 +59,7 @@
59
59
  "working-directory"
60
60
  ],
61
61
  "inputCount": 50,
62
- "surface": "reusable-workflow",
62
+ "surface": "reusable-build",
63
63
  "capabilityGroup": "api-cli-reference",
64
64
  "status": "active"
65
65
  },
@@ -343,6 +343,69 @@
343
343
  "capabilityGroup": "api-cli-reference",
344
344
  "status": "repository-internal"
345
345
  },
346
+ {
347
+ "id": "build",
348
+ "path": ".github/workflows/build.yml",
349
+ "inputs": [
350
+ "artifact-name",
351
+ "artifact-name-template",
352
+ "artifact-paths",
353
+ "artifact-relay-s3-bucket",
354
+ "artifact-relay-s3-download-role-arn",
355
+ "artifact-relay-s3-oidc-audience",
356
+ "artifact-relay-s3-prefix",
357
+ "artifact-relay-s3-region",
358
+ "artifact-relay-s3-role-arn",
359
+ "artifact-relay-s3-upload-role-arn",
360
+ "artifact-retention-days",
361
+ "artifact-transfer-mode",
362
+ "build-command",
363
+ "buildchain-alpha-contract-lock-path",
364
+ "buildchain-channel",
365
+ "buildchain-contract-compatibility-policy",
366
+ "buildchain-contract-drift-issue-mode",
367
+ "buildchain-contract-lock-path",
368
+ "buildchain-ref",
369
+ "buildchain-repository",
370
+ "buildchain-stable-contract-lock-path",
371
+ "checkout-cache-fallback",
372
+ "checkout-cache-fetch-attempts",
373
+ "checkout-cache-github-timeout-seconds",
374
+ "checkout-cache-mirror-url-template",
375
+ "checkout-cache-mode",
376
+ "checkout-cache-reference-repository-template",
377
+ "checkout-cache-timeout-seconds",
378
+ "expected-artifacts-json",
379
+ "install-command",
380
+ "linux-container-image",
381
+ "linux-container-preset",
382
+ "node-version",
383
+ "platforms-json",
384
+ "process-sample-interval-ms",
385
+ "process-summary-path",
386
+ "publish-anchor-request-json",
387
+ "publish-channel",
388
+ "publish-refs-json",
389
+ "publish-registry",
390
+ "publish-source-ref",
391
+ "release-candidate",
392
+ "requested-parallelism",
393
+ "require-build",
394
+ "require-install",
395
+ "require-trusted-event",
396
+ "require-verify",
397
+ "runner-preset",
398
+ "sample-process-tree",
399
+ "setup-node",
400
+ "untrusted-policy",
401
+ "verify-command",
402
+ "working-directory"
403
+ ],
404
+ "inputCount": 53,
405
+ "surface": "channel-build-router",
406
+ "capabilityGroup": "reusable-build",
407
+ "status": "active"
408
+ },
346
409
  {
347
410
  "id": "buildchain-alpha-self-dogfood",
348
411
  "path": ".github/workflows/buildchain-alpha-self-dogfood.yml",
package/docs/MAP.md CHANGED
@@ -95,6 +95,7 @@ replace them.
95
95
  | How do I add timestamped logs inside build scripts? | [`toolkit-observability.md`](toolkit-observability.md) | use | stable |
96
96
  | What package-owned facts should buildchain.libkungfu.dev render? | [`site-bundle-contract.md`](site-bundle-contract.md) | use | stable |
97
97
  | How do I call the reusable build workflow? | [`reusable-build-surface.md`](reusable-build-surface.md) | use | stable |
98
+ | How do I use one build job that follows alpha during development and stable for releases? | [`reusable-build-surface.md`](reusable-build-surface.md#automatic-channel-router) | use | preview |
98
99
  | How do self-hosted runners relay large artifacts through S3 before GitHub artifacts? | [`reusable-build-surface.md`](reusable-build-surface.md#artifact-transfer-relay) | use | stable |
99
100
  | How do self-hosted runners reuse local Git checkout caches without weakening source locks? | [`reusable-build-surface.md`](reusable-build-surface.md#locked-source-checkout-cache) | use | stable |
100
101
  | How do I validate an unreleased Buildchain runtime train while keeping `@v2`? | [`runtime-train-validation.md`](runtime-train-validation.md) | use | stable |
@@ -170,21 +170,24 @@ while exact tags and SHAs remain the reproducible audit choice.
170
170
  ### Buildchain Alpha Self-Dogfood
171
171
 
172
172
  Buildchain continuously consumes its own current major alpha through
173
- `.github/workflows/buildchain-alpha-self-dogfood.yml`. The workflow calls the
174
- released outer reusable workflow at `@v2-alpha` without a runtime override, so
175
- both the workflow shell and its default runtime must resolve through the same
176
- floating alpha channel. A parallel stable lane checks out `v2` and runs the
177
- same declared install, build, and verify lifecycle directly. The direct stable
178
- lane is intentional: it preserves stable-runtime compatibility without
179
- requiring the previous stable outer workflow to understand a self-call identity
180
- fix that has not reached stable yet. Both lanes use a single GitHub-hosted Linux
181
- fixture to keep the recurring check bounded.
173
+ `.github/workflows/buildchain-alpha-self-dogfood.yml`. Both lanes call the
174
+ released channel router at `build.yml@v2-alpha`. The auto lane must resolve
175
+ `v2-alpha`; the explicit stable lane must resolve `v2`. Both execute the same
176
+ declared install, build, and verify fixture, proving that a single consumer
177
+ surface routes to distinct released runtimes without duplicating lifecycle
178
+ configuration in the consumer.
182
179
 
183
180
  The reusable build trust gate reads `job.workflow_ref`, which identifies the
184
181
  called workflow and its selected ref. It does not infer the runtime from
185
182
  `github.workflow_ref`, because GitHub defines that context as the caller
186
183
  workflow identity during a reusable call.
187
184
 
185
+ The router selects `.buildchain/alpha-contract-lock.json` for alpha and
186
+ `.buildchain/contract-lock.json` for stable. The alpha lock records the exact
187
+ reviewed alpha SHA and compatibility digest; it does not replace the stable
188
+ consumer lock. A later alpha with only compatible additive drift continues,
189
+ while a changed breaking digest fails until the new alpha contract is reviewed.
190
+
188
191
  The evidence job resolves `v2-alpha` and `v2` through the GitHub refs API,
189
192
  compares those immutable SHAs with the reusable workflow outputs, verifies the
190
193
  `alpha` and `stable` classifications, and uploads a JSON evidence artifact.
@@ -5,9 +5,69 @@ Buildchain's release semantics but cannot be described as a simple Node package.
5
5
  The first target shape is `libnode`: expensive native builds, multiple operating
6
6
  systems, self-hosted runner labels, and release artifacts that must be auditable.
7
7
 
8
- ## Workflow
8
+ ## Automatic Channel Router
9
9
 
10
- Stable consumers call:
10
+ The preferred consumer surface is one reusable workflow call. After v2.12
11
+ reaches the stable major ref, consumers keep this configuration for both alpha
12
+ development and stable release work:
13
+
14
+ ```yaml
15
+ jobs:
16
+ build:
17
+ uses: kungfu-systems/buildchain/.github/workflows/build.yml@v2
18
+ permissions:
19
+ contents: read
20
+ issues: write
21
+ id-token: write
22
+ with:
23
+ working-directory: .
24
+ artifact-name: libnode
25
+ runner-preset: kungfu-v4-self-hosted
26
+ publish-channel: none
27
+ secrets: inherit
28
+ ```
29
+
30
+ `buildchain-channel` defaults to `auto`. Selection uses this precedence:
31
+
32
+ 1. an explicit `buildchain-ref` train, SHA, or official channel;
33
+ 2. an explicit `buildchain-channel: alpha|stable`;
34
+ 3. `publish-channel: alpha|release|major`;
35
+ 4. GitHub release prerelease metadata;
36
+ 5. a canonical semver tag;
37
+ 6. non-release PR, push, dispatch, schedule, and workflow-run events default to
38
+ alpha.
39
+
40
+ The resolved runtime is `vN-alpha` for development and prerelease intent and
41
+ `vN` for stable release intent. Unknown custom publish channels, malformed
42
+ release events, and non-semver release-like tags fail before the build matrix;
43
+ they never guess alpha for a stable release.
44
+
45
+ The router automatically selects `.buildchain/alpha-contract-lock.json` for
46
+ alpha and `.buildchain/contract-lock.json` for stable. A repository can override
47
+ the common path with `buildchain-contract-lock-path`, or override one channel
48
+ with `buildchain-alpha-contract-lock-path` /
49
+ `buildchain-stable-contract-lock-path`.
50
+
51
+ Only repositories changing the default policy need extra routing input:
52
+
53
+ ```yaml
54
+ with:
55
+ buildchain-channel: stable
56
+ ```
57
+
58
+ During the v2.12 prerelease evaluation window, canaries use
59
+ `build.yml@v2-alpha`. The same router then selects `v2-alpha` or stable `v2` as
60
+ the runtime. Production consumers should adopt `build.yml@v2` after the router
61
+ has reached stable; this keeps the routing shell itself on a stable ref.
62
+
63
+ The router is generated from `.build.yml`'s input/output surface. Run
64
+ `node scripts/generate-channel-build-workflow.mjs` after changing the advanced
65
+ build workflow; inventory and unit tests reject a stale generated router.
66
+
67
+ ## Advanced Workflow
68
+
69
+ Consumers that need direct workflow-shell or runtime control call the advanced
70
+ surface:
11
71
 
12
72
  ```yaml
13
73
  jobs:
@@ -214,7 +274,7 @@ The lock is intentionally small. It does not copy the full contract. The full
214
274
  contract remains in the Buildchain ref and package; the consumer records only
215
275
  what it accepted and the policy used to compare future floating-ref movement.
216
276
 
217
- Alpha-channel consumers select the matching workflow shell:
277
+ Advanced alpha-channel consumers select the matching workflow shell:
218
278
 
219
279
  ```yaml
220
280
  jobs:
@@ -61,6 +61,7 @@ repository changes are patch".
61
61
 
62
62
  | Date | Action | Line | Faces | Class | Rationale | PR |
63
63
  | --- | --- | --- | --- | --- | --- | --- |
64
+ | 2026-07-11 | open-minor | `v2.12` | channel-build-router, channel-selection-protocol | additive | The public `build.yml` reusable workflow lets consumers declare one build job while Buildchain selects generic major alpha for development/prerelease intent and stable major for production release intent, with explicit overrides, separate locks, and fail-closed ambiguity handling. | |
64
65
  | 2026-07-08 | open-minor | `v2.9` | build-facts-contract | additive | Build Facts add a public CLI command family, Node API export, config protocol, module/product fact contracts, release-passport evidence section, and Kungfu legacy buildinfo projection from the same source facts. | |
65
66
  | 2026-07-06 | open-minor | `v2.8` | kfd-1-contract-world-release-gate, kfd-2-release-trust-passport-audit, kfd-3-collaboration-interface-trust-proof, publish-source-lock-enforcement, required-check-protection | additive | KFD release gates add KFD-1 self contract verification, KFD-2 public release trust claim audit, KFD-3 collaboration-interface trust proofs, publish-side source-lock enforcement for promote-only wrappers, and protected channel required checks repaired to bind GitHub Actions check runs instead of legacy commit status contexts. | |
66
67
  | 2026-07-04 | open-minor | `v2.5` | scheduled-integration-governance | additive | Scheduled integration governance adds scheduled feature-branch discovery, conflict-free integration, reporting, and agent-visible governance automation for dev-line maintenance. | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.11.14-alpha.7",
3
+ "version": "2.12.0-alpha.0",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -111,6 +111,36 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
111
111
  "retryable GitHub fallback fetches use a bounded attempt budget before exact source SHA and tree verification",
112
112
  ],
113
113
  }),
114
+ surface(root, {
115
+ id: "channel-build-router",
116
+ kind: "workflow",
117
+ path: ".github/workflows/build.yml",
118
+ publicRef: `${pkg.repository ? "kungfu-systems/buildchain" : "buildchain"}/.github/workflows/build.yml@${majorLine}`,
119
+ requiredInputs: [],
120
+ requiredOutputs: ["buildchain-channel", "buildchain-runtime-sha", "build-summary-artifact"],
121
+ breakingDefaults: {
122
+ channelDefault: "auto",
123
+ developmentDefault: "alpha",
124
+ prereleaseDefault: "alpha",
125
+ stableReleaseDefault: "stable",
126
+ ambiguousReleasePolicy: "fail-closed",
127
+ },
128
+ optionalInputs: [
129
+ "buildchain-channel",
130
+ "buildchain-ref",
131
+ "buildchain-alpha-contract-lock-path",
132
+ "buildchain-stable-contract-lock-path",
133
+ "buildchain-contract-lock-path",
134
+ ],
135
+ guarantees: [
136
+ "consumers declare one build job while Buildchain selects the matching generic major channel",
137
+ "development and prerelease intent select vN-alpha",
138
+ "stable release intent selects vN",
139
+ "ambiguous release-like intent fails before build jobs",
140
+ "explicit train and exact-SHA overrides retain the reusable build trust gate",
141
+ "alpha and stable channel selections use separate consumer contract locks by default",
142
+ ],
143
+ }),
114
144
  surface(root, {
115
145
  id: "release-candidate-promote",
116
146
  kind: "workflow",
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const CHANNELS = new Set(["auto", "alpha", "stable"]);
8
+ const STABLE_PUBLISH_CHANNELS = new Set(["release", "major"]);
9
+ const OFFICIAL_REF = /^v(\d+)(?:\.\d+)?(?:-alpha)?$/;
10
+ const EXACT_SHA = /^[0-9a-f]{40}$/i;
11
+ const TRAIN_REF = /^(?:refs\/heads\/)?train\/v(\d+)\/v\d+\.\d+\/[A-Za-z0-9._/-]+$/;
12
+ const SEMVER_TAG = /^refs\/tags\/v?\d+\.\d+\.\d+(?:-([0-9A-Za-z.-]+))?$/;
13
+
14
+ function normalized(value) {
15
+ return String(value ?? "").trim();
16
+ }
17
+
18
+ function parseBoolean(value, name) {
19
+ const text = normalized(value).toLowerCase();
20
+ if (text === "true") return true;
21
+ if (text === "false") return false;
22
+ throw new Error(`${name} must be true or false`);
23
+ }
24
+
25
+ function readPackageVersion(cwd) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8")).version || "";
28
+ } catch {
29
+ return "";
30
+ }
31
+ }
32
+
33
+ function majorFrom(value) {
34
+ const text = normalized(value).replace(/^refs\/(?:heads|tags)\//, "");
35
+ const official = text.match(OFFICIAL_REF);
36
+ if (official) return Number(official[1]);
37
+ const train = text.match(TRAIN_REF);
38
+ if (train) return Number(train[1]);
39
+ const embedded = text.match(/(?:^|\/)v(\d+)(?:$|[./-])/);
40
+ if (embedded) return Number(embedded[1]);
41
+ const version = text.match(/^(\d+)\.\d+\.\d+/);
42
+ return version ? Number(version[1]) : undefined;
43
+ }
44
+
45
+ function resolveMajor({ requestedRef, routerRef, packageVersion }) {
46
+ for (const candidate of [requestedRef, routerRef, packageVersion]) {
47
+ const major = majorFrom(candidate);
48
+ if (Number.isInteger(major)) return major;
49
+ }
50
+ throw new Error("unable to derive the Buildchain major; call the router from a vN ref or provide a vN buildchain-ref");
51
+ }
52
+
53
+ function classifyRequestedRef(value) {
54
+ const ref = normalized(value).replace(/^refs\/(?:heads|tags)\//, "");
55
+ if (!ref) return { kind: "none", ref };
56
+ if (EXACT_SHA.test(ref)) return { kind: "override", ref };
57
+ if (TRAIN_REF.test(value) || TRAIN_REF.test(ref)) {
58
+ return { kind: "override", ref: normalized(value).replace(/^refs\/heads\//, "") };
59
+ }
60
+ const official = ref.match(OFFICIAL_REF);
61
+ if (official) {
62
+ return {
63
+ kind: ref.endsWith("-alpha") ? "alpha" : "stable",
64
+ ref,
65
+ major: Number(official[1]),
66
+ };
67
+ }
68
+ throw new Error("buildchain-ref must be an official vN/vN.M channel, a train ref, or an exact 40-character SHA");
69
+ }
70
+
71
+ function selected(channel, major, source, reason) {
72
+ return {
73
+ channel,
74
+ major,
75
+ buildchainRef: channel === "alpha" ? `v${major}-alpha` : `v${major}`,
76
+ selectionSource: source,
77
+ reason,
78
+ };
79
+ }
80
+
81
+ export function resolveBuildchainChannel({
82
+ requestedChannel = "auto",
83
+ requestedRef = "",
84
+ publishChannel = "none",
85
+ eventName = "",
86
+ gitRef = "",
87
+ releasePrerelease = "",
88
+ routerRef = "",
89
+ packageVersion = "",
90
+ } = {}) {
91
+ const channel = normalized(requestedChannel || "auto").toLowerCase();
92
+ if (!CHANNELS.has(channel)) {
93
+ throw new Error("buildchain-channel must be auto, alpha, or stable");
94
+ }
95
+
96
+ const explicitRef = classifyRequestedRef(requestedRef);
97
+ const major = resolveMajor({ requestedRef: explicitRef.ref, routerRef, packageVersion });
98
+ if (explicitRef.kind !== "none") {
99
+ if (channel !== "auto" && explicitRef.kind !== "override" && channel !== explicitRef.kind) {
100
+ throw new Error(`buildchain-channel=${channel} conflicts with buildchain-ref=${explicitRef.ref}`);
101
+ }
102
+ if (channel !== "auto" && explicitRef.kind === "override") {
103
+ throw new Error("train and exact-SHA buildchain-ref overrides require buildchain-channel=auto");
104
+ }
105
+ return {
106
+ channel: explicitRef.kind,
107
+ major,
108
+ buildchainRef: explicitRef.ref,
109
+ selectionSource: "explicit-buildchain-ref",
110
+ reason: `explicit Buildchain runtime ref ${explicitRef.ref}`,
111
+ };
112
+ }
113
+
114
+ if (channel !== "auto") {
115
+ return selected(channel, major, "explicit-channel", `explicit buildchain-channel=${channel}`);
116
+ }
117
+
118
+ const publishing = normalized(publishChannel || "none").toLowerCase();
119
+ if (publishing === "alpha") {
120
+ return selected("alpha", major, "publish-channel", "publish-channel=alpha");
121
+ }
122
+ if (STABLE_PUBLISH_CHANNELS.has(publishing)) {
123
+ return selected("stable", major, "publish-channel", `publish-channel=${publishing}`);
124
+ }
125
+ if (publishing !== "none") {
126
+ throw new Error(`custom publish-channel=${publishing} requires an explicit buildchain-channel`);
127
+ }
128
+
129
+ if (eventName === "release") {
130
+ if (normalized(releasePrerelease) === "") {
131
+ throw new Error("release events require github.event.release.prerelease to select a Buildchain channel");
132
+ }
133
+ const prerelease = parseBoolean(releasePrerelease, "releasePrerelease");
134
+ return selected(
135
+ prerelease ? "alpha" : "stable",
136
+ major,
137
+ "release-event",
138
+ prerelease ? "GitHub prerelease event" : "GitHub stable release event",
139
+ );
140
+ }
141
+
142
+ const ref = normalized(gitRef);
143
+ if (ref.startsWith("refs/tags/")) {
144
+ const tag = ref.match(SEMVER_TAG);
145
+ if (!tag) {
146
+ throw new Error(`tag ${ref} is not a canonical semver release tag; set buildchain-channel explicitly`);
147
+ }
148
+ return selected(
149
+ tag[1] ? "alpha" : "stable",
150
+ major,
151
+ "semver-tag",
152
+ tag[1] ? `prerelease tag ${ref}` : `stable tag ${ref}`,
153
+ );
154
+ }
155
+
156
+ return selected("alpha", major, "development-default", `non-release ${eventName || "unknown"} event`);
157
+ }
158
+
159
+ function parseArgs(argv) {
160
+ const values = {};
161
+ for (let index = 0; index < argv.length; index += 1) {
162
+ const token = argv[index];
163
+ if (!token.startsWith("--")) throw new Error(`unexpected argument: ${token}`);
164
+ const key = token.slice(2);
165
+ const value = argv[index + 1];
166
+ if (value === undefined || value.startsWith("--")) throw new Error(`missing value for --${key}`);
167
+ values[key] = value;
168
+ index += 1;
169
+ }
170
+ return values;
171
+ }
172
+
173
+ function appendOutputs(file, result) {
174
+ const entries = {
175
+ channel: result.channel,
176
+ major: String(result.major),
177
+ "buildchain-ref": result.buildchainRef,
178
+ "selection-source": result.selectionSource,
179
+ reason: result.reason,
180
+ };
181
+ fs.appendFileSync(file, Object.entries(entries).map(([key, value]) => `${key}=${value}\n`).join(""));
182
+ }
183
+
184
+ function appendSummary(file, result) {
185
+ fs.appendFileSync(
186
+ file,
187
+ [
188
+ "## Buildchain channel router",
189
+ "",
190
+ `- selected channel: \`${result.channel}\``,
191
+ `- selected runtime ref: \`${result.buildchainRef}\``,
192
+ `- selection source: \`${result.selectionSource}\``,
193
+ `- reason: ${result.reason}`,
194
+ "",
195
+ ].join("\n"),
196
+ );
197
+ }
198
+
199
+ async function main() {
200
+ const args = parseArgs(process.argv.slice(2));
201
+ const cwd = path.resolve(args.cwd || process.cwd());
202
+ const result = resolveBuildchainChannel({
203
+ requestedChannel: args.channel,
204
+ requestedRef: args["buildchain-ref"],
205
+ publishChannel: args["publish-channel"],
206
+ eventName: args["event-name"],
207
+ gitRef: args.ref,
208
+ releasePrerelease: args["release-prerelease"],
209
+ routerRef: args["router-ref"],
210
+ packageVersion: readPackageVersion(cwd),
211
+ });
212
+ if (process.env.GITHUB_OUTPUT) appendOutputs(process.env.GITHUB_OUTPUT, result);
213
+ if (process.env.GITHUB_STEP_SUMMARY) appendSummary(process.env.GITHUB_STEP_SUMMARY, result);
214
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
215
+ }
216
+
217
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
218
+ if (isMain) {
219
+ main().catch((error) => {
220
+ console.error(`buildchain-channel-router: ${error.message}`);
221
+ process.exitCode = 1;
222
+ });
223
+ }