@kungfu-tech/buildchain 2.11.14-alpha.4 → 2.11.14-alpha.6

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.
@@ -413,6 +413,7 @@
413
413
  "buildchain-repository",
414
414
  "checkout-cache-fallback",
415
415
  "checkout-cache-fetch-attempts",
416
+ "checkout-cache-github-timeout-seconds",
416
417
  "checkout-cache-mirror-url-template",
417
418
  "checkout-cache-mode",
418
419
  "checkout-cache-reference-repository-template",
@@ -443,7 +444,7 @@
443
444
  "verify-command",
444
445
  "working-directory"
445
446
  ],
446
- "inputCount": 49
447
+ "inputCount": 50
447
448
  },
448
449
  {
449
450
  "id": ".bump-minor-version",
@@ -2539,8 +2540,8 @@
2539
2540
  "workflowRegistryPath": "dist/site/workflow-registry.json",
2540
2541
  "pageRegistryPath": "dist/site/page-registry.json",
2541
2542
  "cliRegistryDigest": "3c0729f5351eb82fe26625fa5aabf30903f45f68af2431e02860f85ada468ac9",
2542
- "workflowRegistryDigest": "7052aa984ab49647ce2c9f96669caa32f92da57d8b2e506ac172089bedd793cf",
2543
- "pageRegistryDigest": "61593cdf75b8e38e825b78e733d00da0f7ce54d9bbcf2eb646037eaecd42e9c7"
2543
+ "workflowRegistryDigest": "0fc20324b4154e352176378665daecb3cb7a74179796e3777eb2f6fc93a50092",
2544
+ "pageRegistryDigest": "51d61683cd241eb9f5e693dcbff5bb4c7589fba194bc6bf1c8ca550007b561e3"
2544
2545
  },
2545
2546
  "comparison": {
2546
2547
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-10T13:58:52.360Z",
5
- "publishedAt": "2026-07-10T13:58:52.360Z",
4
+ "generatedAt": "2026-07-10T16:41:11.001Z",
5
+ "publishedAt": "2026-07-10T16:41:11.001Z",
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": "94327df75abe657d53fe6b2d1137af68fb37d53c",
22
+ "sourceRevision": "4bbf92ba595952dbd22f8656829928991e84f448",
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.4",
35
+ "version": "2.11.14-alpha.6",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-release-model",
4
4
  "exactTags": "v-prefixed exact tags are immutable release records.",
5
- "floatingTags": "vX, vX.Y, and vX.Y-alpha are channel pointers updated by Buildchain transactions.",
5
+ "floatingTags": "vX, vX-alpha, vX.Y, and vX.Y-alpha are channel pointers updated by Buildchain transactions; vX-alpha follows the highest minor in major X with a published alpha.",
6
6
  "channelBranches": [
7
7
  "dev/vX/vX.Y",
8
8
  "alpha/vX/vX.Y",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-10T13:58:52.360Z",
5
- "publishedAt": "2026-07-10T13:58:52.360Z",
4
+ "generatedAt": "2026-07-10T16:41:11.001Z",
5
+ "publishedAt": "2026-07-10T16:41:11.001Z",
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": "94327df75abe657d53fe6b2d1137af68fb37d53c",
22
+ "sourceRevision": "4bbf92ba595952dbd22f8656829928991e84f448",
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.4",
40
+ "version": "2.11.14-alpha.6",
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:7dc5b9adc5f23456ddc3a3f7c46f2cdf3ddd38d9546a45267291ba3fe567e0c9"
56
+ "digest": "sha256:53b605e57a5ac2b8d4ed911a4fbb4d1a39352db143db183aa551f9226f2d7a6a"
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:4f3699841c5a189d199db045d526b11f76ad0dff1464d6a63ca98ceadf1bb215"
208
+ "digest": "sha256:b9335434f95e6173f31d50983f8ab492378b065fa203a3b68bee447487883cea"
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:00b5e4c3ffd91d3f050a6e880f76dd36716660a731db029f636b372707ee3910"
224
+ "digest": "sha256:8a70f9f2699bcec6102164d6ae67f13549f3ef962d12441fbb80f9d49a1cc2e1"
225
225
  },
226
226
  {
227
227
  "id": "release-flow",
@@ -229,7 +229,7 @@
229
229
  "path": "docs/release-flow.md",
230
230
  "plane": "verify",
231
231
  "exists": true,
232
- "digest": "sha256:5b855ab3863e197b2ab257536b1f3ff85c79bca1fe962c45089b3f4c5ab81189"
232
+ "digest": "sha256:c05905de6a905d1ce0dd977986175b8bc1ad908c6c2eee946809693ea2cb91de"
233
233
  },
234
234
  {
235
235
  "id": "runtime-train-validation",
@@ -27,6 +27,7 @@
27
27
  "buildchain-repository",
28
28
  "checkout-cache-fallback",
29
29
  "checkout-cache-fetch-attempts",
30
+ "checkout-cache-github-timeout-seconds",
30
31
  "checkout-cache-mirror-url-template",
31
32
  "checkout-cache-mode",
32
33
  "checkout-cache-reference-repository-template",
@@ -57,7 +58,7 @@
57
58
  "verify-command",
58
59
  "working-directory"
59
60
  ],
60
- "inputCount": 49,
61
+ "inputCount": 50,
61
62
  "surface": "reusable-workflow",
62
63
  "capabilityGroup": "api-cli-reference",
63
64
  "status": "active"
package/docs/MAP.md CHANGED
@@ -111,7 +111,7 @@ replace them.
111
111
 
112
112
  - **ABV / old workflows / old action repositories** -> [`release-governance.md`](release-governance.md)
113
113
  and [`migration-inventory.md`](migration-inventory.md).
114
- - **v2 / v2.0 / v2.0-alpha / exact tags / floating tags** ->
114
+ - **v2 / v2-alpha / v2.0 / v2.0-alpha / exact tags / floating tags** ->
115
115
  [`release-governance.md`](release-governance.md) and
116
116
  [`release-flow.md`](release-flow.md).
117
117
  - **v2.1 vs v2.2 / when to open a new minor line** ->
@@ -53,6 +53,7 @@ for the versioned policy and evidence contract.
53
53
  | Exact alpha tag | `v2.0.3-alpha.0` | immutable | audit ref for one tested prerelease |
54
54
  | Exact release tag | `v2.0.2` | immutable | audit ref for one production release |
55
55
  | Floating alpha tag | `v2.0-alpha` | moves | latest test channel for a minor line |
56
+ | Floating major alpha tag | `v2-alpha` | moves | latest test channel on the highest published alpha minor for a major line |
56
57
  | Floating minor tag | `v2.0` | moves | latest production patch on a minor line |
57
58
  | Floating major tag | `v2` | moves | selected stable major entrypoint |
58
59
 
@@ -68,8 +69,8 @@ refs/tags/v*.*.*
68
69
  ```
69
70
 
70
71
  Do not apply immutable-tag rulesets to every `refs/tags/v*` ref. Buildchain
71
- must be able to update floating channel tags such as `v2`, `v2.0`, and
72
- `v2.0-alpha` after the exact tag and publish evidence are valid. A ruleset that
72
+ must be able to update floating channel tags such as `v2`, `v2.0`, `v2.0-alpha`,
73
+ and `v2-alpha` after the exact tag and publish evidence are valid. A ruleset that
73
74
  matches all `v*` tags also matches floating tags, so release finalization can
74
75
  fail with GitHub protected-ref errors even though the exact release tag and
75
76
  published artifacts are already durable.
@@ -77,7 +78,7 @@ published artifacts are already durable.
77
78
  The intended governance split is:
78
79
 
79
80
  - exact tags such as `v2.0.14` and `v2.0.15-alpha.0` are immutable audit refs;
80
- - floating tags such as `v2`, `v2.0`, and `v2.0-alpha` are mutable channel refs
81
+ - floating tags such as `v2`, `v2.0`, `v2.0-alpha`, and `v2-alpha` are mutable channel refs
81
82
  owned by the Buildchain promotion token;
82
83
  - protected branches still require reviewed channel PRs before Buildchain can
83
84
  move any exact or floating release refs.
@@ -134,6 +135,7 @@ sequenceDiagram
134
135
  Promote->>Promote: write and verify version state
135
136
  Promote->>Tags: create or reuse vX.Y.Z-alpha.N
136
137
  Promote->>Tags: move vX.Y-alpha
138
+ Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor
137
139
  Promote->>Alpha: move alpha/vX/vX.Y
138
140
  Promote->>Dev: move dev/vX/vX.Y
139
141
  ```
@@ -143,6 +145,7 @@ Result:
143
145
  ```text
144
146
  vX.Y.Z-alpha.N
145
147
  vX.Y-alpha
148
+ vX-alpha when X.Y is the highest published alpha minor
146
149
  alpha/vX/vX.Y
147
150
  dev/vX/vX.Y
148
151
  ```
@@ -177,6 +180,7 @@ sequenceDiagram
177
180
  Promote->>Promote: prepare vX.Y.(Z+1)-alpha.0
178
181
  Promote->>Tags: create or reuse vX.Y.(Z+1)-alpha.0
179
182
  Promote->>Tags: move vX.Y-alpha
183
+ Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor
180
184
  Promote->>Alpha: move alpha/vX/vX.Y
181
185
  Promote->>Dev: move dev/vX/vX.Y
182
186
  ```
@@ -195,6 +199,7 @@ point at the production version-state commit, while:
195
199
  ```text
196
200
  vX.Y.(Z+1)-alpha.0
197
201
  vX.Y-alpha
202
+ vX-alpha when X.Y is the highest published alpha minor
198
203
  alpha/vX/vX.Y
199
204
  dev/vX/vX.Y
200
205
  ```
@@ -235,6 +240,7 @@ It should also prepare:
235
240
  ```text
236
241
  v2.0.3-alpha.0 exact next alpha tag
237
242
  v2.0-alpha floating alpha tag
243
+ v2-alpha floating major alpha tag when v2.0 is the highest published alpha minor
238
244
  alpha/v2/v2.0 alpha channel branch
239
245
  dev/v2/v2.0 development channel branch
240
246
  ```
@@ -128,13 +128,14 @@ This is why Buildchain maintains both exact and floating refs:
128
128
  - `v2` is the selected stable major-line entrypoint;
129
129
  - `v2.0.3-alpha.0` is immutable alpha evidence;
130
130
  - `v2.0-alpha` is the latest test channel for the `2.0` line.
131
+ - `v2-alpha` is the latest test channel on the highest published alpha minor in major `2`.
131
132
 
132
133
  A release does not mean "minor is complete." It means "this patch on this minor
133
134
  line is now production."
134
135
 
135
136
  GitHub repository rules must preserve that distinction. Exact tags such as
136
137
  `v2.0.2` and `v2.0.3-alpha.0` should be immutable. Floating channel tags such as
137
- `v2`, `v2.0`, and `v2.0-alpha` must remain movable by the Buildchain promotion
138
+ `v2`, `v2.0`, `v2.0-alpha`, and `v2-alpha` must remain movable by the Buildchain promotion
138
139
  token after governance checks and publish evidence pass. A tag ruleset that
139
140
  protects every `refs/tags/v*` ref is too broad because it also locks the
140
141
  floating channel tags that Buildchain is required to update. Prefer exact-tag
@@ -159,9 +160,12 @@ Buildchain then:
159
160
  6. Moves `dev/vX/vX.Y` to the same generated alpha commit when this is a
160
161
  fast-forward update.
161
162
  7. Moves `vX.Y-alpha` to the same generated alpha commit.
163
+ 8. Moves `vX-alpha` only when `X.Y` is the highest minor in major `X` with a published alpha; older minor alpha work records a skip and cannot move the major channel backwards.
162
164
 
163
165
  This keeps the test channel self-describing. If a consumer checks out
164
- `v2.0-alpha`, the manifests and exact alpha tag agree.
166
+ `v2.0-alpha` or `v2-alpha`, the manifests and exact alpha tag agree. The major
167
+ alpha ref removes routine consumer edits when Buildchain opens a newer minor,
168
+ while exact tags and SHAs remain the reproducible audit choice.
165
169
 
166
170
  If `dev/vX/vX.Y` has already advanced before generated alpha version-state
167
171
  bookkeeping can sync back, Buildchain records `skipped-non-fast-forward` for the
@@ -201,6 +205,7 @@ Buildchain then:
201
205
  `vX.Y.(Z+1)-alpha.0`.
202
206
  10. Moves `alpha/vX/vX.Y`, `dev/vX/vX.Y`, and `vX.Y-alpha` to that next alpha
203
207
  commit.
208
+ 11. Moves `vX-alpha` to that next alpha only when this remains the highest published alpha minor.
204
209
 
205
210
  The production channel and the test channel therefore intentionally diverge
206
211
  after release: production stays on the release commit, while alpha/dev continue
@@ -513,6 +518,7 @@ When the loop succeeds, maintainers and consumers can rely on these facts:
513
518
  not a hidden manual button;
514
519
  - every test channel has an exact alpha tag such as `v2.0.3-alpha.0`;
515
520
  - every alpha minor line has a floating tag such as `v2.0-alpha`;
521
+ - every major with a published alpha has a cross-minor floating tag such as `v2-alpha`, owned by its highest published alpha minor;
516
522
  - version manifests match the tag visible from the same commit;
517
523
  - production releases are derived from the alpha tree that was tested;
518
524
  - manual non-dry-run promotion cannot bypass PR review and verification;
@@ -233,6 +233,7 @@ jobs:
233
233
  checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}
234
234
  checkout-cache-fallback: github
235
235
  checkout-cache-timeout-seconds: 60
236
+ checkout-cache-github-timeout-seconds: 600
236
237
  checkout-cache-fetch-attempts: 3
237
238
  ```
238
239
 
@@ -256,10 +257,16 @@ Do not read cache URLs or reference paths from PR-controlled files such as
256
257
  `.buildchain/buildchain.toml`. These values are trusted workflow inputs or repo/org
257
258
  variables. Buildchain does not pass GitHub credentials to cache mirrors or
258
259
  reference repositories. If it must fall back to GitHub, the workflow token is
259
- used only for the GitHub fetch path. Retryable timeout and transient network
260
- failures use the bounded `checkout-cache-fetch-attempts` budget; permanent
261
- failures stop immediately. Diagnostics record the configured budget and actual
262
- GitHub fetch attempts before exact HEAD/tree verification.
260
+ used only for the GitHub fetch path. Cache attempts use
261
+ `checkout-cache-timeout-seconds`; the potentially larger GitHub fallback uses
262
+ the independent `checkout-cache-github-timeout-seconds` budget (600 seconds by
263
+ default). Buildchain fetches the advertised source ref before trying an exact
264
+ SHA, so a cache hit or stale-cache seed can contribute objects and the fallback
265
+ does not first waste a full timeout on an unadvertised SHA. Retryable timeout
266
+ and transient network failures use the bounded `checkout-cache-fetch-attempts`
267
+ budget; permanent failures stop immediately. Diagnostics record both timeout
268
+ budgets and the actual GitHub fetch attempts before exact HEAD/tree
269
+ verification.
263
270
 
264
271
  Each platform diagnostics artifact includes `source-checkout.json` and embeds a
265
272
  compact `sourceCheckout` summary in `diagnostics.json`: mode, transport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.11.14-alpha.4",
3
+ "version": "2.11.14-alpha.6",
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",
@@ -23,7 +23,7 @@ function parseTags(input) {
23
23
  : String(input || "").split(",").map((tag) => tag.trim()).filter(Boolean);
24
24
  for (const tag of tags) {
25
25
  if (
26
- !/^v\d+$|^v\d+\.\d+$|^v\d+\.\d+-alpha$|^v\d+\.\d+\.\d+$|^v\d+\.\d+\.\d+-alpha\.\d+$/.test(tag)
26
+ !/^v\d+$|^v\d+-alpha$|^v\d+\.\d+$|^v\d+\.\d+-alpha$|^v\d+\.\d+\.\d+$|^v\d+\.\d+\.\d+-alpha\.\d+$/.test(tag)
27
27
  ) {
28
28
  throw new Error(`Unsupported buildchain dry-run tag: ${tag}`);
29
29
  }
@@ -42,6 +42,7 @@ function getPromotionRule(targetRef, sourceRef = "") {
42
42
  sourceRef: sourceRef || "release/vN/vN.M",
43
43
  releasePrefix: nextMajor ? `v${nextMajor}.0` : "v(N+1).0",
44
44
  majorTag: nextMajor ? `v${nextMajor}` : "v(N+1)",
45
+ majorAlphaTag: nextMajor ? `v${nextMajor}-alpha` : "v(N+1)-alpha",
45
46
  minorTag: nextMajor ? `v${nextMajor}.0` : "v(N+1).0",
46
47
  alphaTag: nextMajor ? `v${nextMajor}.0-alpha` : "v(N+1).0-alpha",
47
48
  exactReleasePattern: nextMajor ? `v${nextMajor}.0.0` : "v(N+1).0.0",
@@ -69,6 +70,7 @@ function getPromotionRule(targetRef, sourceRef = "") {
69
70
  minor,
70
71
  releasePrefix,
71
72
  majorTag: `v${major}`,
73
+ majorAlphaTag: `v${major}-alpha`,
72
74
  minorTag: releasePrefix,
73
75
  alphaTag: `${releasePrefix}-alpha`,
74
76
  sourceRef: channel === "alpha" ? `dev/v${major}/v${major}.${minor}` : `alpha/v${major}/v${major}.${minor}`,
@@ -193,7 +195,10 @@ function explainReleaseLineDryRun({
193
195
  kind: "alpha",
194
196
  action: "would create or reuse immutable alpha evidence tag",
195
197
  });
196
- plan.floatingRefs.push({ ref: rule.alphaTag, kind: "tag", action: "would move to alpha version-state commit" });
198
+ plan.floatingRefs.push(
199
+ { ref: rule.alphaTag, kind: "tag", action: "would move to alpha version-state commit" },
200
+ { ref: rule.majorAlphaTag, kind: "tag", action: "would move when this is the highest published alpha minor" },
201
+ );
197
202
  plan.branchUpdates.push(
198
203
  { ref: targetRef, action: "would move to alpha version-state commit" },
199
204
  { ref: `dev/v${rule.major}/v${rule.major}.${rule.minor}`, action: "would align dev with the published alpha state" },
@@ -211,6 +216,7 @@ function explainReleaseLineDryRun({
211
216
  { ref: rule.minorTag, kind: "tag", action: "would move to production release commit" },
212
217
  { ref: rule.majorTag, kind: "tag", action: "would move when no newer minor line owns the major tag" },
213
218
  { ref: rule.alphaTag, kind: "tag", action: "would move to next alpha version-state commit" },
219
+ { ref: rule.majorAlphaTag, kind: "tag", action: "would move when this is the highest published alpha minor" },
214
220
  );
215
221
  plan.branchUpdates.push(
216
222
  { ref: targetRef, action: "would move to production release commit" },
@@ -234,6 +240,7 @@ function explainReleaseLineDryRun({
234
240
  { ref: rule.minorTag, kind: "tag", action: "would move to next-major release commit" },
235
241
  { ref: rule.majorTag, kind: "tag", action: "would move to next-major release commit" },
236
242
  { ref: rule.alphaTag, kind: "tag", action: "would move to next-major alpha commit" },
243
+ { ref: rule.majorAlphaTag, kind: "tag", action: "would move to next-major alpha commit" },
237
244
  );
238
245
  plan.branchUpdates.push(
239
246
  { ref: targetRef, action: "would move to next-major release commit" },
@@ -872,7 +872,7 @@ function buildSiteBundle() {
872
872
  schemaVersion: 1,
873
873
  contract: "kungfu-buildchain-release-model",
874
874
  exactTags: "v-prefixed exact tags are immutable release records.",
875
- floatingTags: "vX, vX.Y, and vX.Y-alpha are channel pointers updated by Buildchain transactions.",
875
+ floatingTags: "vX, vX-alpha, vX.Y, and vX.Y-alpha are channel pointers updated by Buildchain transactions; vX-alpha follows the highest minor in major X with a published alpha.",
876
876
  channelBranches: ["dev/vX/vX.Y", "alpha/vX/vX.Y", "release/vX/vX.Y", "publish-gate/major"],
877
877
  protectedDevelopmentBranches: ["dev/vX/vX.Y"],
878
878
  releasePassport: {
@@ -182,35 +182,64 @@ function checkoutFetchedCommit(targetPath, sha, timeoutMs) {
182
182
  git(["checkout", "--force", "--detach", sha], { cwd: targetPath, timeoutMs });
183
183
  }
184
184
 
185
- function fetchSha({ targetPath, remoteName, remoteUrl, sha, fetchRef, timeoutMs, env = {} }) {
185
+ function retryableGitFetchError(error) {
186
+ const code = String(error?.code || "").toUpperCase();
187
+ if (["ETIMEDOUT", "ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ENETUNREACH", "EPIPE"].includes(code)) return true;
188
+ return /timed?\s*out|timeout|connection (?:reset|refused)|remote end hung up|early eof|rpc failed|http (?:429|5\d\d)|temporary failure|network is unreachable/i.test(String(error?.message || error || ""));
189
+ }
190
+
191
+ export function fetchSourceCommit({
192
+ targetPath,
193
+ remoteName,
194
+ remoteUrl,
195
+ sha,
196
+ fetchRef,
197
+ timeoutMs,
198
+ env = {},
199
+ runGit = git,
200
+ containsCommit = hasCommit,
201
+ }) {
186
202
  try {
187
- git(["remote", "remove", remoteName], { cwd: targetPath, timeoutMs, stdio: "ignore" });
203
+ runGit(["remote", "remove", remoteName], { cwd: targetPath, timeoutMs, stdio: "ignore" });
188
204
  } catch {
189
205
  // The remote is optional; a fresh checkout target will not have it yet.
190
206
  }
191
207
  try {
192
- git(["remote", "add", remoteName, remoteUrl], { cwd: targetPath, timeoutMs });
208
+ runGit(["remote", "add", remoteName, remoteUrl], { cwd: targetPath, timeoutMs });
193
209
  } catch {
194
- git(["remote", "set-url", remoteName, remoteUrl], { cwd: targetPath, timeoutMs });
210
+ runGit(["remote", "set-url", remoteName, remoteUrl], { cwd: targetPath, timeoutMs });
195
211
  }
196
- const fetchArgs = ["fetch", "--no-tags", "--depth=1", remoteName, `+${sha}:refs/buildchain/source`];
197
- try {
198
- git(fetchArgs, { cwd: targetPath, timeoutMs, env, stdio: "ignore" });
199
- return;
200
- } catch (error) {
201
- if (!fetchRef) {
202
- throw error;
212
+
213
+ if (fetchRef) {
214
+ try {
215
+ runGit(["fetch", "--no-tags", "--depth=1", remoteName, `+${fetchRef}:refs/buildchain/source-ref`], {
216
+ cwd: targetPath,
217
+ timeoutMs,
218
+ env,
219
+ stdio: "ignore",
220
+ });
221
+ if (containsCommit(targetPath, sha, timeoutMs)) {
222
+ return { selector: "ref" };
223
+ }
224
+ } catch (error) {
225
+ // A retryable transport failure belongs to the bounded outer retry. Do
226
+ // not immediately spend the same timeout again on an unadvertised SHA.
227
+ if (retryableGitFetchError(error)) {
228
+ throw error;
229
+ }
203
230
  }
204
231
  }
205
- git(["fetch", "--no-tags", "--depth=1", remoteName, `+${fetchRef}:refs/buildchain/source-ref`], {
232
+
233
+ runGit(["fetch", "--no-tags", "--depth=1", remoteName, `+${sha}:refs/buildchain/source`], {
206
234
  cwd: targetPath,
207
235
  timeoutMs,
208
236
  env,
209
237
  stdio: "ignore",
210
238
  });
211
- if (!hasCommit(targetPath, sha, timeoutMs)) {
212
- throw new Error(`fetched ${fetchRef}, but ${sha} is not available`);
239
+ if (!containsCommit(targetPath, sha, timeoutMs)) {
240
+ throw new Error(`fetched ${fetchRef || sha}, but ${sha} is not available`);
213
241
  }
242
+ return { selector: "sha" };
214
243
  }
215
244
 
216
245
  export function runBoundedFetch({ attempts = 1, fetch, onAttempt = () => {}, onRetry = () => {}, shouldRetry = () => true }) {
@@ -232,12 +261,6 @@ export function runBoundedFetch({ attempts = 1, fetch, onAttempt = () => {}, onR
232
261
  throw lastError;
233
262
  }
234
263
 
235
- function retryableGitFetchError(error) {
236
- const code = String(error?.code || "").toUpperCase();
237
- if (["ETIMEDOUT", "ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ENETUNREACH", "EPIPE"].includes(code)) return true;
238
- return /timed?\s*out|timeout|connection (?:reset|refused)|remote end hung up|early eof|rpc failed|http (?:429|5\d\d)|temporary failure|network is unreachable/i.test(String(error?.message || error || ""));
239
- }
240
-
241
264
  function githubRemoteUrl({ repository, serverUrl = "https://github.com" }) {
242
265
  const base = String(serverUrl || "https://github.com").replace(/\/+$/, "");
243
266
  return `${base}/${repository}.git`;
@@ -277,6 +300,7 @@ export function lockedSourceCheckout({
277
300
  referenceRepositoryTemplate = readEnv("BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE"),
278
301
  fallback = readEnv("BUILDCHAIN_CHECKOUT_CACHE_FALLBACK", "github"),
279
302
  timeoutSeconds = Number(readEnv("BUILDCHAIN_CHECKOUT_CACHE_TIMEOUT_SECONDS", "60") || 60),
303
+ githubTimeoutSeconds = Number(readEnv("BUILDCHAIN_CHECKOUT_CACHE_GITHUB_TIMEOUT_SECONDS", "600") || 600),
280
304
  fetchAttempts = Number(readEnv("BUILDCHAIN_CHECKOUT_CACHE_FETCH_ATTEMPTS", "3") || 3),
281
305
  diagnosticsPath = readEnv("BUILDCHAIN_SOURCE_CHECKOUT_DIAGNOSTICS_PATH", ".buildchain/diagnostics/source-checkout.json"),
282
306
  githubToken = readEnv("GITHUB_TOKEN"),
@@ -291,6 +315,7 @@ export function lockedSourceCheckout({
291
315
  const treeSha = sourceTreeSha ? assertSha(sourceTreeSha, "sourceTreeSha") : "";
292
316
  const repoParts = splitRepository(repository);
293
317
  const timeoutMs = Math.max(1, Number(timeoutSeconds || 60)) * 1000;
318
+ const githubTimeoutMs = Math.max(1, Number(githubTimeoutSeconds || 600)) * 1000;
294
319
  const normalizedFetchAttempts = Math.max(1, Math.floor(Number(fetchAttempts) || 3));
295
320
  const targetPath = ensureCheckoutTarget(checkoutPath, workspace);
296
321
  git(["init"], { cwd: targetPath, timeoutMs });
@@ -312,6 +337,7 @@ export function lockedSourceCheckout({
312
337
  mode: normalizedMode,
313
338
  fallback: normalizedFallback,
314
339
  timeoutSeconds: Math.max(1, Number(timeoutSeconds || 60)),
340
+ githubTimeoutSeconds: Math.max(1, Number(githubTimeoutSeconds || 600)),
315
341
  fetchAttempts: normalizedFetchAttempts,
316
342
  mirror: sanitizeIdentity(renderedMirrorUrl),
317
343
  referenceRepository: sanitizeIdentity(renderedReferenceRepository),
@@ -348,7 +374,7 @@ export function lockedSourceCheckout({
348
374
  checkoutFetchedCommit(targetPath, sha, timeoutMs);
349
375
  } else if (renderedMirrorUrl) {
350
376
  evidence.cache.transport = "mirror-url";
351
- fetchSha({
377
+ fetchSourceCommit({
352
378
  targetPath,
353
379
  remoteName: "buildchain-cache",
354
380
  remoteUrl: renderedMirrorUrl,
@@ -384,7 +410,15 @@ export function lockedSourceCheckout({
384
410
  try {
385
411
  const fetchResult = runBoundedFetch({
386
412
  attempts: normalizedFetchAttempts,
387
- fetch: () => fetchSha({ targetPath, remoteName: "origin", remoteUrl, sha, fetchRef, timeoutMs, env: githubAuthEnv(githubToken) }),
413
+ fetch: () => fetchSourceCommit({
414
+ targetPath,
415
+ remoteName: "origin",
416
+ remoteUrl,
417
+ sha,
418
+ fetchRef,
419
+ timeoutMs: githubTimeoutMs,
420
+ env: githubAuthEnv(githubToken),
421
+ }),
388
422
  onAttempt: ({ attempt }) => { evidence.cache.githubFetchAttempts = attempt; },
389
423
  onRetry: ({ attempt, limit, error }) => console.log(`buildchain: GitHub source fetch failed, retry ${attempt + 1}/${limit}: ${error.message}`),
390
424
  shouldRetry: retryableGitFetchError,