@kungfu-tech/buildchain 2.14.13 → 2.14.14-alpha.1

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.
@@ -3690,7 +3690,7 @@
3690
3690
  "pageRegistryPath": "dist/site/page-registry.json",
3691
3691
  "cliRegistryDigest": "90c8650f870c350ca187c612c37f2459dcbfe265aa9b1d5b375ee5c4fb31fab6",
3692
3692
  "workflowRegistryDigest": "02950eef2769b1bd2d899099ef23c7b9ae716b18d602e25aef169d788bc6ece0",
3693
- "pageRegistryDigest": "01c3418ba850dcf1bc678be943f60082c609c0984c5101cc2181bb9803cd6aa4"
3693
+ "pageRegistryDigest": "2121b86c6c11ad27c69b5b00bda989c1203ddb3f7cb4a864df375a62e13fced6"
3694
3694
  },
3695
3695
  "comparison": {
3696
3696
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-21T20:06:36.670Z",
5
- "publishedAt": "2026-07-21T20:06:36.670Z",
4
+ "generatedAt": "2026-07-21T22:45:53.347Z",
5
+ "publishedAt": "2026-07-21T22:45:53.347Z",
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": "d82f8d214b48af62d41cb47d8c3b25838e16d6f1",
22
+ "sourceRevision": "b72c725311d1e37adc98b25a4555bb61ca8023ca",
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.14.13",
35
+ "version": "2.14.14-alpha.1",
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-21T20:06:36.670Z",
5
- "publishedAt": "2026-07-21T20:06:36.670Z",
4
+ "generatedAt": "2026-07-21T22:45:53.347Z",
5
+ "publishedAt": "2026-07-21T22:45:53.347Z",
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": "d82f8d214b48af62d41cb47d8c3b25838e16d6f1",
22
+ "sourceRevision": "b72c725311d1e37adc98b25a4555bb61ca8023ca",
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.14.13",
40
+ "version": "2.14.14-alpha.1",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -165,7 +165,7 @@
165
165
  "path": "docs/migration-inventory.md",
166
166
  "plane": "verify",
167
167
  "exists": true,
168
- "digest": "sha256:a5f7271a065544f7ac525dbddfe3d4b9f621b5284c730089f086dd9f961e883c"
168
+ "digest": "sha256:18f98635bb16a4e43cac2d46443b3e812c305f708f071929dc6a46a8cd5dccd5"
169
169
  },
170
170
  {
171
171
  "id": "ownership",
@@ -221,7 +221,7 @@
221
221
  "path": "docs/reusable-build-surface.md",
222
222
  "plane": "use",
223
223
  "exists": true,
224
- "digest": "sha256:dea8ec2f1f2ea75e569a989988e9d6f76067e1d0ae496dadc34ef1dcfbfa35dd"
224
+ "digest": "sha256:9c125f6d2b519c96718f45da62ac1a891ab618bcb331d3b14c4b88b5158fc4a9"
225
225
  },
226
226
  {
227
227
  "id": "publish-transaction",
@@ -269,7 +269,7 @@
269
269
  "path": "docs/web-surface-deployments.md",
270
270
  "plane": "use",
271
271
  "exists": true,
272
- "digest": "sha256:856a7602cf1c2d3a0a540cdf0d64566914f79dde24417c92089f0466bee25df1"
272
+ "digest": "sha256:efbfc97a947978e61c79dac9d67f87050b1bef2297617303340af8aa499703c9"
273
273
  }
274
274
  ],
275
275
  "facts": [
@@ -46,7 +46,7 @@ do not duplicate alpha and stable jobs or call
46
46
  `.release-candidate-promote.yml` directly.
47
47
 
48
48
  Buildchain also keeps the workflow-file layout transition declarative in
49
- `.buildchain/promotion-shell-routing.json`. Stable `v2.14.12` contains the hidden
49
+ `.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden
50
50
  advanced workflow, so the stable lane is pinned to that implementation's
51
51
  immutable release SHA and forwards the full internal identity surface. This is
52
52
  provider-owned compatibility state; consumers still keep the same single public
@@ -754,7 +754,7 @@ promotion starts:
754
754
  `vN`, and `buildchain-stable-contract-lock-path`.
755
755
 
756
756
  The generated router also owns the stable-shell layout transition through
757
- `.buildchain/promotion-shell-routing.json`. Stable `v2.14.12` contains the hidden
757
+ `.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden
758
758
  advanced workflow, so the stable lane calls that workflow at the exact immutable
759
759
  SHA behind the released `v2` state and forwards the complete internal promotion
760
760
  identity surface. The logical shell identity remains `vN`, and the router
@@ -456,7 +456,12 @@ compatibility evidence, but root correctness comes from the viewer-request
456
456
  rewrite contract, not from extensionless S3 keys. If Buildchain-managed mode
457
457
  finds a distribution with a different viewer-request function, apply fails
458
458
  closed and records that conflict in the apply result instead of silently serving
459
- 403s. The reusable workflow uploads `buildchain-web-surface-*-diagnostics`
459
+ 403s. If CloudFront rejects `UpdateDistribution` because its optimistic-lock
460
+ ETag became stale, Buildchain re-reads the distribution and retries with the
461
+ new ETag at most twice. A concurrent update that already attached the intended
462
+ function is accepted as converged; a different viewer-request function or any
463
+ non-ETag AWS error still fails immediately. The reusable workflow uploads
464
+ `buildchain-web-surface-*-diagnostics`
460
465
  artifacts containing the apply and health JSON so the failing AWS operation or
461
466
  HTTP check is visible from the consumer run.
462
467
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.14.13",
3
+ "version": "2.14.14-alpha.1",
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",
@@ -15,6 +15,9 @@ const FUNCTION_CODE = `function handler(event) {
15
15
  }
16
16
  `;
17
17
 
18
+ const DISTRIBUTION_UPDATE_MAX_ATTEMPTS = 3;
19
+ const DISTRIBUTION_UPDATE_RETRY_DELAY_MS = 250;
20
+
18
21
  function readArg(name, fallback = "") {
19
22
  const flag = `--${name}`;
20
23
  const index = process.argv.indexOf(flag);
@@ -31,7 +34,7 @@ function runAws(args, { allowFailure = false } = {}) {
31
34
  });
32
35
  if (result.error) {
33
36
  if (allowFailure) {
34
- return { ok: false, stdout: "", stderr: String(result.error.message || result.error) };
37
+ return { ok: false, status: null, stdout: "", stderr: String(result.error.message || result.error) };
35
38
  }
36
39
  throw result.error;
37
40
  }
@@ -39,11 +42,11 @@ function runAws(args, { allowFailure = false } = {}) {
39
42
  const stderr = String(result.stderr || "");
40
43
  if (result.status !== 0) {
41
44
  if (allowFailure) {
42
- return { ok: false, stdout, stderr };
45
+ return { ok: false, status: result.status, stdout, stderr };
43
46
  }
44
47
  throw new Error(`aws ${args.join(" ")} failed with exit code ${result.status}${stderr.trim() ? `: ${stderr.trim()}` : ""}`);
45
48
  }
46
- return { ok: true, stdout, stderr };
49
+ return { ok: true, status: result.status, stdout, stderr };
47
50
  }
48
51
 
49
52
  function runAwsJson(args, options = {}) {
@@ -58,6 +61,22 @@ function functionConfig(comment) {
58
61
  return `Comment=${comment.replace(/,/g, " ")},Runtime=cloudfront-js-2.0`;
59
62
  }
60
63
 
64
+ function awsFailureMessage(args, result) {
65
+ const status = result.status === null || result.status === undefined
66
+ ? ""
67
+ : ` with exit code ${result.status}`;
68
+ const stderr = String(result.stderr || "").trim();
69
+ return `aws ${args.join(" ")} failed${status}${stderr ? `: ${stderr}` : ""}`;
70
+ }
71
+
72
+ function isStaleDistributionEtag(result) {
73
+ return result.ok === false && /\bPreconditionFailed\b/.test(String(result.stderr || ""));
74
+ }
75
+
76
+ function sleepSync(milliseconds) {
77
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
78
+ }
79
+
61
80
  function writeFunctionCode(tmpDir) {
62
81
  const file = path.join(tmpDir, "directory-index-rewrite.js");
63
82
  fs.writeFileSync(file, FUNCTION_CODE);
@@ -130,57 +149,85 @@ function publishFunction({ name, etag }) {
130
149
  }
131
150
 
132
151
  function attachFunction({ distributionId, functionArn }) {
133
- const current = runAwsJson(["cloudfront", "get-distribution-config", "--id", distributionId]);
134
- const etag = current.data?.ETag || "";
135
- const config = current.data?.DistributionConfig;
136
- if (!etag || !config) {
137
- throw new Error(`cloudfront get-distribution-config did not return ETag and DistributionConfig for ${distributionId}`);
138
- }
139
- const defaultBehavior = config.DefaultCacheBehavior || {};
140
- const existing = defaultBehavior.FunctionAssociations || { Quantity: 0, Items: [] };
141
- const items = Array.isArray(existing.Items) ? [...existing.Items] : [];
142
- const currentViewer = items.find((item) => item.EventType === "viewer-request");
143
- if (currentViewer && currentViewer.FunctionARN && currentViewer.FunctionARN !== functionArn) {
144
- throw new Error(
145
- `CloudFront distribution ${distributionId} already has a different viewer-request function ${currentViewer.FunctionARN}; ` +
146
- "remove or migrate it before Buildchain can manage directory-index rewrites",
147
- );
148
- }
149
- const nextItems = [
150
- ...items.filter((item) => item.EventType !== "viewer-request"),
151
- {
152
- EventType: "viewer-request",
153
- FunctionARN: functionArn,
154
- },
155
- ];
156
- config.DefaultCacheBehavior = {
157
- ...defaultBehavior,
158
- FunctionAssociations: {
159
- Quantity: nextItems.length,
160
- Items: nextItems,
161
- },
162
- };
163
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-cloudfront-distribution-"));
164
- try {
165
- const configFile = path.join(tmpDir, "distribution-config.json");
166
- fs.writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`);
167
- runAws([
168
- "cloudfront",
169
- "update-distribution",
170
- "--id",
171
- distributionId,
172
- "--if-match",
173
- etag,
174
- "--distribution-config",
175
- `file://${configFile}`,
176
- ]);
177
- } finally {
178
- fs.rmSync(tmpDir, { recursive: true, force: true });
152
+ let previousViewerRequestFunction;
153
+ for (let attempt = 1; attempt <= DISTRIBUTION_UPDATE_MAX_ATTEMPTS; attempt += 1) {
154
+ const current = runAwsJson(["cloudfront", "get-distribution-config", "--id", distributionId]);
155
+ const etag = current.data?.ETag || "";
156
+ const config = current.data?.DistributionConfig;
157
+ if (!etag || !config) {
158
+ throw new Error(`cloudfront get-distribution-config did not return ETag and DistributionConfig for ${distributionId}`);
159
+ }
160
+ const defaultBehavior = config.DefaultCacheBehavior || {};
161
+ const existing = defaultBehavior.FunctionAssociations || { Quantity: 0, Items: [] };
162
+ const items = Array.isArray(existing.Items) ? [...existing.Items] : [];
163
+ const currentViewer = items.find((item) => item.EventType === "viewer-request");
164
+ if (previousViewerRequestFunction === undefined) {
165
+ previousViewerRequestFunction = currentViewer?.FunctionARN || "";
166
+ }
167
+ if (currentViewer && currentViewer.FunctionARN && currentViewer.FunctionARN !== functionArn) {
168
+ throw new Error(
169
+ `CloudFront distribution ${distributionId} already has a different viewer-request function ${currentViewer.FunctionARN}; ` +
170
+ "remove or migrate it before Buildchain can manage directory-index rewrites",
171
+ );
172
+ }
173
+ if (currentViewer?.FunctionARN === functionArn) {
174
+ return {
175
+ previousViewerRequestFunction,
176
+ nextViewerRequestFunction: functionArn,
177
+ };
178
+ }
179
+
180
+ const nextItems = [
181
+ ...items.filter((item) => item.EventType !== "viewer-request"),
182
+ {
183
+ EventType: "viewer-request",
184
+ FunctionARN: functionArn,
185
+ },
186
+ ];
187
+ config.DefaultCacheBehavior = {
188
+ ...defaultBehavior,
189
+ FunctionAssociations: {
190
+ Quantity: nextItems.length,
191
+ Items: nextItems,
192
+ },
193
+ };
194
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-cloudfront-distribution-"));
195
+ let args;
196
+ let updated;
197
+ try {
198
+ const configFile = path.join(tmpDir, "distribution-config.json");
199
+ fs.writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`);
200
+ args = [
201
+ "cloudfront",
202
+ "update-distribution",
203
+ "--id",
204
+ distributionId,
205
+ "--if-match",
206
+ etag,
207
+ "--distribution-config",
208
+ `file://${configFile}`,
209
+ ];
210
+ updated = runAws(args, { allowFailure: true });
211
+ } finally {
212
+ fs.rmSync(tmpDir, { recursive: true, force: true });
213
+ }
214
+ if (updated.ok) {
215
+ return {
216
+ previousViewerRequestFunction,
217
+ nextViewerRequestFunction: functionArn,
218
+ };
219
+ }
220
+ if (!isStaleDistributionEtag(updated)) {
221
+ throw new Error(awsFailureMessage(args, updated));
222
+ }
223
+ if (attempt === DISTRIBUTION_UPDATE_MAX_ATTEMPTS) {
224
+ throw new Error(
225
+ `CloudFront update-distribution still failed after ${attempt} attempts: ${awsFailureMessage(args, updated)}`,
226
+ );
227
+ }
228
+ sleepSync(DISTRIBUTION_UPDATE_RETRY_DELAY_MS * attempt);
179
229
  }
180
- return {
181
- previousViewerRequestFunction: currentViewer?.FunctionARN || "",
182
- nextViewerRequestFunction: functionArn,
183
- };
230
+ throw new Error(`CloudFront update-distribution retry loop ended unexpectedly for ${distributionId}`);
184
231
  }
185
232
 
186
233
  export function ensureCloudFrontDirectoryIndexRewrite({