@kungfu-tech/buildchain 2.14.1 → 2.14.2-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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-17T00:55:24.253Z",
5
- "publishedAt": "2026-07-17T00:55:24.253Z",
4
+ "generatedAt": "2026-07-17T02:13:15.755Z",
5
+ "publishedAt": "2026-07-17T02:13:15.755Z",
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": "d962181c3023e587488a5276b10307007cc5a13f",
22
+ "sourceRevision": "dd861c4e31186a2b68ee4d624bea517757b1e2e2",
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.1",
35
+ "version": "2.14.2-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-17T00:55:24.253Z",
5
- "publishedAt": "2026-07-17T00:55:24.253Z",
4
+ "generatedAt": "2026-07-17T02:13:15.755Z",
5
+ "publishedAt": "2026-07-17T02:13:15.755Z",
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": "d962181c3023e587488a5276b10307007cc5a13f",
22
+ "sourceRevision": "dd861c4e31186a2b68ee4d624bea517757b1e2e2",
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.1",
40
+ "version": "2.14.2-alpha.0",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -229,7 +229,7 @@
229
229
  "path": "docs/publish-transaction.md",
230
230
  "plane": "verify",
231
231
  "exists": true,
232
- "digest": "sha256:cc2f4c9d2b9988962a237bbc7e8da83385d45585e2b2dd105ff4c4334a74c05d"
232
+ "digest": "sha256:950ca4fb2f7ab0a7d40775593e3864f8bb1229f8543b266b00154af82c619e95"
233
233
  },
234
234
  {
235
235
  "id": "release-governance",
@@ -237,7 +237,7 @@
237
237
  "path": "docs/release-governance.md",
238
238
  "plane": "why",
239
239
  "exists": true,
240
- "digest": "sha256:5cf4bf4332119531f6aaa1c123d7f64e6af4a61611c4e4a33d40bd9fb46448cf"
240
+ "digest": "sha256:511d214a8d9a24125f64c5cb363aeeb3351a1236eec96f5fa6f49832d5fcba98"
241
241
  },
242
242
  {
243
243
  "id": "release-flow",
@@ -269,7 +269,7 @@
269
269
  "path": "docs/web-surface-deployments.md",
270
270
  "plane": "use",
271
271
  "exists": true,
272
- "digest": "sha256:797d8f0e81974a49125b17256bb8b4a85fc60673e06d6bbecbbf399651163437"
272
+ "digest": "sha256:856a7602cf1c2d3a0a540cdf0d64566914f79dde24417c92089f0466bee25df1"
273
273
  }
274
274
  ],
275
275
  "facts": [
@@ -879,6 +879,7 @@
879
879
  "cadence",
880
880
  "capabilities",
881
881
  "dry-run",
882
+ "landing-mode",
882
883
  "max-actions",
883
884
  "merge-method",
884
885
  "mode",
@@ -888,7 +889,7 @@
888
889
  "same-repository-only",
889
890
  "target-branch"
890
891
  ],
891
- "inputCount": 15,
892
+ "inputCount": 16,
892
893
  "secrets": [
893
894
  "github-token"
894
895
  ],
@@ -1001,6 +1002,7 @@
1001
1002
  "buildchain-ref",
1002
1003
  "buildchain-repository",
1003
1004
  "dry-run",
1005
+ "landing-mode",
1004
1006
  "max-merges",
1005
1007
  "merge-method",
1006
1008
  "ready-label",
@@ -1009,18 +1011,20 @@
1009
1011
  "same-repository-only",
1010
1012
  "target-branch"
1011
1013
  ],
1012
- "inputCount": 12,
1014
+ "inputCount": 13,
1013
1015
  "secrets": [
1014
1016
  "github-token"
1015
1017
  ],
1016
1018
  "secretCount": 1,
1017
1019
  "outputs": [
1020
+ "action-count",
1021
+ "enqueued-count",
1018
1022
  "evaluated-count",
1019
1023
  "final-base-sha",
1020
1024
  "merged-count",
1021
1025
  "skipped-count"
1022
1026
  ],
1023
- "outputCount": 4,
1027
+ "outputCount": 6,
1024
1028
  "surface": "dev-governance",
1025
1029
  "capabilityGroup": "governance-versioning",
1026
1030
  "status": "active"
@@ -1162,6 +1166,7 @@
1162
1166
  "buildchain-ref",
1163
1167
  "buildchain-repository",
1164
1168
  "dry-run",
1169
+ "landing-mode",
1165
1170
  "max-actions",
1166
1171
  "merge-method",
1167
1172
  "ready-label",
@@ -1170,7 +1175,7 @@
1170
1175
  "same-repository-only",
1171
1176
  "target-branch"
1172
1177
  ],
1173
- "inputCount": 12,
1178
+ "inputCount": 13,
1174
1179
  "secrets": [
1175
1180
  "github-token"
1176
1181
  ],
@@ -395,6 +395,20 @@ last transient error seen before a rerun.
395
395
  An exact tag at an unrelated SHA is still a material conflict and blocks
396
396
  recovery.
397
397
 
398
+ Governed retries distinguish unrelated channel advancement from advancement
399
+ made by their own durable transaction. An unrelated descendant remains an
400
+ auditable `superseded-promotion` no-op. When the target ref is exactly the
401
+ recorded `release_sha` for the requested source, target, and expected version,
402
+ Buildchain resumes finalization, restores publish evidence, and emits the
403
+ release-passport paths needed by downstream controller receipts.
404
+
405
+ Publication authority planning applies the same occupied-version rule as the
406
+ later mutation step. If a current alpha transaction already contains published
407
+ material and regenerating version state would create new release material, the
408
+ planner advances to the next alpha before sealing authority. It never seals the
409
+ old published version and then lets the publisher discover a different version
410
+ inside the mutation boundary.
411
+
398
412
  If finalization fails after an exact Git tag is created, the next run reads the
399
413
  durable `finalizing` state, verifies the exact tag points at the recorded
400
414
  release SHA, and retries the remaining floating refs. An exact tag at a
@@ -411,7 +411,8 @@ buildchain dev merge-queue \
411
411
  --repository owner/repository \
412
412
  --branch dev/v4/v4.0 \
413
413
  --workflow .github/workflows/source-acceptance.yml \
414
- --workflow .github/workflows/affected-native-pr.yml
414
+ --workflow .github/workflows/affected-native-pr.yml \
415
+ --bypass-app dedicated-release-app
415
416
  ```
416
417
 
417
418
  After reviewing the plan, repeat with `--apply`. Buildchain creates or updates
@@ -423,6 +424,15 @@ protection. The ruleset uses the first merge method that the repository itself
423
424
  allows, and fails closed when the repository has no enabled merge method.
424
425
  Re-running the command is idempotent.
425
426
 
427
+ Merge-queue rules also reject generated post-publish version-state ref updates.
428
+ When the sealed promotion workflow uses a dedicated GitHub App, user, or team
429
+ already declared by release governance, repeat `--bypass-app`, `--bypass-user`,
430
+ or `--bypass-team` to project that exact actor into the ruleset. Bypass actors
431
+ are never inferred and broad repository or organization roles are not accepted.
432
+ This keeps ordinary feature PRs on the predecessor-aligned queue path while the
433
+ sealed publication authority can finish its machine-verified bookkeeping. The
434
+ dry-run receipt exposes the exact actor IDs before `--apply` changes GitHub.
435
+
426
436
  Buildchain provides the reusable
427
437
  `.github/workflows/dev-pr-auto-merge.yml` workflow for repositories that want a
428
438
  scheduled or manual "merge ready dev PRs" pass. The consumer repository owns
@@ -435,9 +445,21 @@ The workflow defaults are conservative. A PR is skipped unless it targets the
435
445
  configured dev line, is not a draft, has the ready label, has no block label,
436
446
  comes from the same repository, uses an allowed work-branch prefix, has a
437
447
  current approval, is mergeable, and has the configured required checks passing.
438
- After each merge, the next PR is re-evaluated before it can move the protected
439
- dev branch. This prevents one merge from silently making the next candidate
440
- stale or conflicting.
448
+ `landing-mode: auto` reads the target branch's native merge-queue state. When a
449
+ queue exists, Buildchain never calls the direct merge endpoint: it admits at
450
+ most one PR against the observed target-branch SHA and immutable PR head, then
451
+ calls GraphQL `enqueuePullRequest` with `expectedHeadOid`. GitHub's
452
+ `merge_group` checks remain the final authority for the projected merge.
453
+
454
+ The admission receipt records the expected and observed base/head SHAs, policy
455
+ checks, decision, reason, and active predecessor. Buildchain re-reads the base,
456
+ head, mergeability, and native queue immediately before enqueueing. Base or
457
+ head drift fails closed, an active queue entry blocks admission, and a rejected
458
+ ready predecessor leaves its PR open while later PRs receive
459
+ `blocked-by-predecessor`. Workflow concurrency serializes Buildchain-owned
460
+ admission runs; GitHub still owns the atomic queue and protected-ref update.
461
+ Repositories may explicitly select `landing-mode: direct` only when the target
462
+ branch has no native queue. Queue presence always disables the direct path.
441
463
 
442
464
  The canonical consumer required check context is `check / check`, matching the
443
465
  reusable workflow call plus its `check` job. Buildchain's own `Verify` workflow
@@ -497,6 +519,7 @@ jobs:
497
519
  ready-label: ready
498
520
  block-labels: blocked,do-not-merge
499
521
  max-merges: 1
522
+ landing-mode: auto
500
523
  dry-run: ${{ inputs.dry-run || false }}
501
524
  ```
502
525
 
@@ -745,7 +745,10 @@ When enabled, Buildchain owns the full release apply state machine:
745
745
  operator can verify staging from the PR page and use merge as the approval
746
746
  action. Consumers do not need to hand-write `gh pr create` or production
747
747
  release-intent glue.
748
- - Production runs when `production-apply` is true and either:
748
+ - `production-apply=true` enables the production capability; it does not request
749
+ production for every event. Ordinary `main` pushes remain staging-only and
750
+ can create or update a release PR. Production runs only when the capability
751
+ is enabled and either:
749
752
  - a `workflow_dispatch` passes `production-approved=true` and the triggering
750
753
  actor currently has `write`, `maintain`, or `admin` permission; or
751
754
  - `production-release-on-main=true` and the `main` push commit is associated
@@ -794,7 +797,7 @@ jobs:
794
797
  build-command: npm run build
795
798
  verify-command: npm run check
796
799
  artifact-path: dist
797
- production-apply: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
800
+ production-apply: true
798
801
  production-release-on-main: true
799
802
  production-release-label: buildchain-release
800
803
  production-release-head-prefix: release/
@@ -804,6 +807,13 @@ jobs:
804
807
  production-environment: production
805
808
  ```
806
809
 
810
+ Keep `production-apply` enabled in the caller when the repository supports
811
+ production. Buildchain derives whether the current event may use that
812
+ capability: an ordinary `main` push plans and applies staging, a matching
813
+ reviewed release PR merge authorizes production, and an approved trusted manual
814
+ dispatch authorizes production. Inputs from an untrusted event cannot turn that
815
+ decision on.
816
+
807
817
  `production-release-pr-mode` controls the post-staging handoff:
808
818
 
809
819
  | Mode | Behavior |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.14.1",
3
+ "version": "2.14.2-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",
@@ -91,6 +91,7 @@ export function normalizePatrolOptions(options = {}) {
91
91
  sameRepositoryOnly: options.sameRepositoryOnly ?? process.env.BUILDCHAIN_PATROL_SAME_REPOSITORY_ONLY,
92
92
  maxActions: intOption(options.maxActions ?? process.env.BUILDCHAIN_PATROL_MAX_ACTIONS, 1),
93
93
  mergeMethod: String(options.mergeMethod || process.env.BUILDCHAIN_PATROL_MERGE_METHOD || "merge").trim(),
94
+ landingMode: String(options.landingMode || process.env.BUILDCHAIN_PATROL_LANDING_MODE || "auto").trim(),
94
95
  dryRun: boolOption(options.dryRun ?? process.env.BUILDCHAIN_PATROL_DRY_RUN, true),
95
96
  outputPath: String(options.outputPath || process.env.BUILDCHAIN_PATROL_OUTPUT_PATH || DEFAULT_OUTPUT_PATH),
96
97
  };
@@ -167,6 +168,7 @@ export async function runBuildchainPatrol(optionsInput = {}, clientInput) {
167
168
  sameRepositoryOnly: options.sameRepositoryOnly,
168
169
  maxMerges: options.maxActions,
169
170
  mergeMethod: options.mergeMethod,
171
+ landingMode: options.landingMode,
170
172
  dryRun: options.dryRun,
171
173
  outputPath: path.join(path.dirname(options.outputPath), "dev-pr-auto-merge.json"),
172
174
  },
@@ -178,7 +180,7 @@ export async function runBuildchainPatrol(optionsInput = {}, clientInput) {
178
180
  result: mergeResult,
179
181
  });
180
182
  result.summary.evaluatedCount += mergeResult.evaluated.length;
181
- result.summary.actionCount += mergeResult.merged.length;
183
+ result.summary.actionCount += mergeResult.actions.length;
182
184
  result.summary.skippedCount += mergeResult.skipped.length;
183
185
  }
184
186
 
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { spawnSync } from "node:child_process";
5
5
 
6
6
  const RULESET_PREFIX = "Buildchain dev merge queue";
7
+ const RULESET_BYPASS_TYPES = new Set(["Integration", "Team", "User"]);
7
8
 
8
9
  function positiveInteger(value, label, fallback) {
9
10
  const parsed = Number(value || fallback);
@@ -19,6 +20,53 @@ function requiredString(value, label) {
19
20
  return normalized;
20
21
  }
21
22
 
23
+ function normalizeRulesetBypassActors(actors = []) {
24
+ const normalized = [];
25
+ const seen = new Set();
26
+ for (const actor of actors) {
27
+ const actorType = requiredString(actor?.actor_type, "ruleset bypass actor type");
28
+ const actorId = Number(actor?.actor_id);
29
+ if (!RULESET_BYPASS_TYPES.has(actorType)) {
30
+ throw new Error(`ruleset bypass actor type must be Integration, Team, or User, got '${actorType}'`);
31
+ }
32
+ if (!Number.isInteger(actorId) || actorId < 1) {
33
+ throw new Error("ruleset bypass actor id must be a positive integer");
34
+ }
35
+ const key = `${actorType}:${actorId}`;
36
+ if (seen.has(key)) continue;
37
+ seen.add(key);
38
+ normalized.push({ actor_id: actorId, actor_type: actorType, bypass_mode: "always" });
39
+ }
40
+ return normalized;
41
+ }
42
+
43
+ export async function resolveRulesetBypassActors({
44
+ api,
45
+ repository,
46
+ apps = [],
47
+ users = [],
48
+ teams = [],
49
+ } = {}) {
50
+ const [owner] = requiredString(repository, "repository").split("/");
51
+ const actors = [];
52
+ for (const slug of apps) {
53
+ const app = await api.request("GET", `apps/${encodeURIComponent(requiredString(slug, "bypass app"))}`);
54
+ actors.push({ actor_id: app.id, actor_type: "Integration" });
55
+ }
56
+ for (const login of users) {
57
+ const user = await api.request("GET", `users/${encodeURIComponent(requiredString(login, "bypass user"))}`);
58
+ actors.push({ actor_id: user.id, actor_type: "User" });
59
+ }
60
+ for (const slug of teams) {
61
+ const team = await api.request(
62
+ "GET",
63
+ `orgs/${encodeURIComponent(owner)}/teams/${encodeURIComponent(requiredString(slug, "bypass team"))}`,
64
+ );
65
+ actors.push({ actor_id: team.id, actor_type: "Team" });
66
+ }
67
+ return normalizeRulesetBypassActors(actors);
68
+ }
69
+
22
70
  export function selectMergeQueueMethod(repositorySettings = {}) {
23
71
  const candidates = [
24
72
  ["MERGE", repositorySettings.allow_merge_commit],
@@ -64,6 +112,7 @@ export function createDevMergeQueuePlan({
64
112
  protection,
65
113
  repositorySettings,
66
114
  rulesets = [],
115
+ bypassActors = [],
67
116
  checkResponseTimeoutMinutes = 120,
68
117
  maxEntriesToBuild = 1,
69
118
  } = {}) {
@@ -84,6 +133,7 @@ export function createDevMergeQueuePlan({
84
133
  name: rulesetName,
85
134
  target: "branch",
86
135
  enforcement: "active",
136
+ bypass_actors: normalizeRulesetBypassActors(bypassActors),
87
137
  conditions: {
88
138
  ref_name: {
89
139
  include: [`refs/heads/${normalizedBranch}`],
@@ -144,6 +194,9 @@ export async function reconcileDevMergeQueue({
144
194
  branch,
145
195
  workflows,
146
196
  apply = false,
197
+ bypassApps = [],
198
+ bypassUsers = [],
199
+ bypassTeams = [],
147
200
  checkResponseTimeoutMinutes = 120,
148
201
  maxEntriesToBuild = 1,
149
202
  } = {}) {
@@ -151,6 +204,13 @@ export async function reconcileDevMergeQueue({
151
204
  const repositorySettings = await api.request("GET", `repos/${repository}`);
152
205
  const protection = await api.request("GET", `repos/${repository}/branches/${encodedBranch}/protection`);
153
206
  const rulesets = await api.request("GET", `repos/${repository}/rulesets?includes_parents=false&per_page=100`);
207
+ const bypassActors = await resolveRulesetBypassActors({
208
+ api,
209
+ repository,
210
+ apps: bypassApps,
211
+ users: bypassUsers,
212
+ teams: bypassTeams,
213
+ });
154
214
  const plan = createDevMergeQueuePlan({
155
215
  repository,
156
216
  branch,
@@ -158,6 +218,7 @@ export async function reconcileDevMergeQueue({
158
218
  protection,
159
219
  repositorySettings,
160
220
  rulesets,
221
+ bypassActors,
161
222
  checkResponseTimeoutMinutes,
162
223
  maxEntriesToBuild,
163
224
  });
@@ -245,6 +306,9 @@ async function main(args = process.argv.slice(2)) {
245
306
  apply: args.includes("--apply"),
246
307
  checkResponseTimeoutMinutes: readFlag(args, "check-response-timeout-minutes", "120"),
247
308
  maxEntriesToBuild: readFlag(args, "max-entries-to-build", "1"),
309
+ bypassApps: readRepeatedFlag(args, "bypass-app"),
310
+ bypassUsers: readRepeatedFlag(args, "bypass-user"),
311
+ bypassTeams: readRepeatedFlag(args, "bypass-team"),
248
312
  });
249
313
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
250
314
  }