@kungfu-tech/buildchain 2.14.1-alpha.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.
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +13 -13
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/page-registry.json +6 -6
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/docs/publish-transaction.md +14 -0
- package/docs/release-governance.md +11 -1
- package/docs/web-surface-deployments.md +12 -2
- package/package.json +1 -1
- package/scripts/dev-merge-queue.mjs +64 -0
- package/scripts/dev-pr-auto-merge.mjs +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
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": "
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
272
|
+
"digest": "sha256:856a7602cf1c2d3a0a540cdf0d64566914f79dde24417c92089f0466bee25df1"
|
|
273
273
|
}
|
|
274
274
|
],
|
|
275
275
|
"facts": [
|
|
@@ -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
|
|
@@ -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
|
-
-
|
|
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:
|
|
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.
|
|
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",
|
|
@@ -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
|
}
|
|
@@ -48,7 +48,7 @@ function choiceOption(value, valid, fallback, field) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function normalizeRepo(value) {
|
|
51
|
-
const text = String(value || "").trim();
|
|
51
|
+
const text = String(value?.fullName || value || "").trim();
|
|
52
52
|
const match = text.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
53
53
|
if (!match) throw new Error(`repository must be owner/repo, got: ${text || "<empty>"}`);
|
|
54
54
|
return { owner: match[1], repo: match[2], fullName: `${match[1]}/${match[2]}` };
|