@kungfu-tech/buildchain 2.12.1 → 2.12.2

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,9 +4,9 @@
4
4
  "status": "passed",
5
5
  "summary": {
6
6
  "cliCommandCount": 76,
7
- "workflowCount": 42,
7
+ "workflowCount": 43,
8
8
  "actionCount": 4,
9
- "sitePageCount": 46,
9
+ "sitePageCount": 47,
10
10
  "docCommandRefCount": 241,
11
11
  "failureCount": 0
12
12
  },
@@ -425,6 +425,7 @@
425
425
  "checkout-cache-reference-repository-template",
426
426
  "checkout-cache-timeout-seconds",
427
427
  "expected-artifacts-json",
428
+ "gate-profile-aggregate-json",
428
429
  "install-command",
429
430
  "linux-container-image",
430
431
  "linux-container-preset",
@@ -456,7 +457,7 @@
456
457
  "verify-command",
457
458
  "working-directory"
458
459
  ],
459
- "inputCount": 57
460
+ "inputCount": 58
460
461
  },
461
462
  {
462
463
  "id": ".bump-minor-version",
@@ -464,6 +465,32 @@
464
465
  "inputs": [],
465
466
  "inputCount": 0
466
467
  },
468
+ {
469
+ "id": ".gate-profile",
470
+ "path": ".github/workflows/.gate-profile.yml",
471
+ "inputs": [
472
+ "artifact-name",
473
+ "artifact-retention-days",
474
+ "buildchain-ref",
475
+ "buildchain-repository",
476
+ "gate-command-json",
477
+ "gate-environment-json",
478
+ "gate-plan-command-json",
479
+ "gate-profile",
480
+ "gate-registry",
481
+ "include-advisory",
482
+ "node-version",
483
+ "platforms-json",
484
+ "require-trusted-event",
485
+ "runner-preset",
486
+ "shifu-cache-profile-digest",
487
+ "shifu-cache-profile-ref",
488
+ "source-ref",
489
+ "untrusted-policy",
490
+ "working-directory"
491
+ ],
492
+ "inputCount": 19
493
+ },
467
494
  {
468
495
  "id": ".release-docker",
469
496
  "path": ".github/workflows/.release-docker.yml",
@@ -730,6 +757,7 @@
730
757
  "checkout-cache-reference-repository-template",
731
758
  "checkout-cache-timeout-seconds",
732
759
  "expected-artifacts-json",
760
+ "gate-profile-aggregate-json",
733
761
  "install-command",
734
762
  "linux-container-image",
735
763
  "linux-container-preset",
@@ -761,7 +789,7 @@
761
789
  "verify-command",
762
790
  "working-directory"
763
791
  ],
764
- "inputCount": 60
792
+ "inputCount": 61
765
793
  },
766
794
  {
767
795
  "id": "buildchain-alpha-self-dogfood",
@@ -864,12 +892,13 @@
864
892
  "inputs": [
865
893
  "buildchain-ref",
866
894
  "buildchain-repository",
895
+ "mode",
867
896
  "node-version",
868
897
  "require-version-state",
869
898
  "upload-artifacts",
870
899
  "working-directory"
871
900
  ],
872
- "inputCount": 6
901
+ "inputCount": 7
873
902
  },
874
903
  {
875
904
  "id": "dev-pr-auto-merge",
@@ -1452,6 +1481,10 @@
1452
1481
  "id": "manual:runtime-train-validation",
1453
1482
  "category": "manual"
1454
1483
  },
1484
+ {
1485
+ "id": "manual:shifu-gate-profiles",
1486
+ "category": "manual"
1487
+ },
1455
1488
  {
1456
1489
  "id": "manual:site-bundle-contract",
1457
1490
  "category": "manual"
@@ -2690,8 +2723,8 @@
2690
2723
  "workflowRegistryPath": "dist/site/workflow-registry.json",
2691
2724
  "pageRegistryPath": "dist/site/page-registry.json",
2692
2725
  "cliRegistryDigest": "ab57fec294efb9dde1612787bfd0d532b5bb95298b5da14a2b51a5e71b9c7dcc",
2693
- "workflowRegistryDigest": "c821196e68a63cefb5686b9005a01aeb7d6cf5248a6146d1e276b87ee765e9f6",
2694
- "pageRegistryDigest": "c4c73230264352bc86f2f4170335ff0c1bb399b4040323627b6f9e10a32d9ea0"
2726
+ "workflowRegistryDigest": "ccc8ab882a30d8c180c109aa484c619d34a5e889c571757f11e555f113c4d338",
2727
+ "pageRegistryDigest": "394ef34331be95e72b6c2c875ce138ea0b3344e539793291dc8126d168a2c1f9"
2695
2728
  },
2696
2729
  "comparison": {
2697
2730
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-13T07:22:41.239Z",
5
- "publishedAt": "2026-07-13T07:22:41.239Z",
4
+ "generatedAt": "2026-07-13T12:43:26.201Z",
5
+ "publishedAt": "2026-07-13T12:43:26.201Z",
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": "699b971025689b6906d085ca456ee81ca6df30e1",
22
+ "sourceRevision": "d61a8cfd6d147aadc73306dbc97bf2ce6b825895",
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.12.1",
35
+ "version": "2.12.2",
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-13T07:22:41.239Z",
5
- "publishedAt": "2026-07-13T07:22:41.239Z",
4
+ "generatedAt": "2026-07-13T12:43:26.201Z",
5
+ "publishedAt": "2026-07-13T12:43:26.201Z",
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": "699b971025689b6906d085ca456ee81ca6df30e1",
22
+ "sourceRevision": "d61a8cfd6d147aadc73306dbc97bf2ce6b825895",
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.12.1",
40
+ "version": "2.12.2",
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:0735009b21cea9227f2c9b9bcbec1219898f4d211776b60aa030ace65821ec80"
56
+ "digest": "sha256:5df6d6110d7c33da4625a2e1ebb6b778eef9c4420379cff342229bc0b02962e1"
57
57
  },
58
58
  {
59
59
  "id": "install",
@@ -197,7 +197,7 @@
197
197
  "path": "docs/lifecycle-protocol.md",
198
198
  "plane": "use",
199
199
  "exists": true,
200
- "digest": "sha256:60c39693878258d275e9d47983b306faed35d6416297c14570429de73284604c"
200
+ "digest": "sha256:3a03359ec58db04f334303256b02020f6039616e8802c19dfc00e76bbb05bf18"
201
201
  },
202
202
  {
203
203
  "id": "reusable-build-surface",
@@ -221,7 +221,7 @@
221
221
  "path": "docs/release-governance.md",
222
222
  "plane": "why",
223
223
  "exists": true,
224
- "digest": "sha256:a868fd50875aa64ae26d8e37a5bfbc4be137ce89be3b0726e97a7a8028a667f4"
224
+ "digest": "sha256:3ae2bbc2df394726f350e58be1a71dd0379b99b19cd6ccf15fbe3b843f3890b6"
225
225
  },
226
226
  {
227
227
  "id": "release-flow",
@@ -34,6 +34,7 @@
34
34
  "checkout-cache-reference-repository-template",
35
35
  "checkout-cache-timeout-seconds",
36
36
  "expected-artifacts-json",
37
+ "gate-profile-aggregate-json",
37
38
  "install-command",
38
39
  "linux-container-image",
39
40
  "linux-container-preset",
@@ -65,7 +66,7 @@
65
66
  "verify-command",
66
67
  "working-directory"
67
68
  ],
68
- "inputCount": 57,
69
+ "inputCount": 58,
69
70
  "surface": "reusable-build",
70
71
  "capabilityGroup": "api-cli-reference",
71
72
  "status": "active"
@@ -79,6 +80,35 @@
79
80
  "capabilityGroup": "api-cli-reference",
80
81
  "status": "active"
81
82
  },
83
+ {
84
+ "id": ".gate-profile",
85
+ "path": ".github/workflows/.gate-profile.yml",
86
+ "inputs": [
87
+ "artifact-name",
88
+ "artifact-retention-days",
89
+ "buildchain-ref",
90
+ "buildchain-repository",
91
+ "gate-command-json",
92
+ "gate-environment-json",
93
+ "gate-plan-command-json",
94
+ "gate-profile",
95
+ "gate-registry",
96
+ "include-advisory",
97
+ "node-version",
98
+ "platforms-json",
99
+ "require-trusted-event",
100
+ "runner-preset",
101
+ "shifu-cache-profile-digest",
102
+ "shifu-cache-profile-ref",
103
+ "source-ref",
104
+ "untrusted-policy",
105
+ "working-directory"
106
+ ],
107
+ "inputCount": 19,
108
+ "surface": "reusable-workflow",
109
+ "capabilityGroup": "api-cli-reference",
110
+ "status": "active"
111
+ },
82
112
  {
83
113
  "id": ".release-docker",
84
114
  "path": ".github/workflows/.release-docker.yml",
@@ -384,6 +414,7 @@
384
414
  "checkout-cache-reference-repository-template",
385
415
  "checkout-cache-timeout-seconds",
386
416
  "expected-artifacts-json",
417
+ "gate-profile-aggregate-json",
387
418
  "install-command",
388
419
  "linux-container-image",
389
420
  "linux-container-preset",
@@ -415,7 +446,7 @@
415
446
  "verify-command",
416
447
  "working-directory"
417
448
  ],
418
- "inputCount": 60,
449
+ "inputCount": 61,
419
450
  "surface": "channel-build-router",
420
451
  "capabilityGroup": "reusable-build",
421
452
  "status": "active"
@@ -548,12 +579,13 @@
548
579
  "inputs": [
549
580
  "buildchain-ref",
550
581
  "buildchain-repository",
582
+ "mode",
551
583
  "node-version",
552
584
  "require-version-state",
553
585
  "upload-artifacts",
554
586
  "working-directory"
555
587
  ],
556
- "inputCount": 6,
588
+ "inputCount": 7,
557
589
  "surface": "repository-workflow",
558
590
  "capabilityGroup": "api-cli-reference",
559
591
  "status": "active"
package/docs/MAP.md CHANGED
@@ -28,7 +28,7 @@ workflow, action, and Node API export also carries a `capabilityGroup`,
28
28
  | --- | --- | --- |
29
29
  | Getting Started | `capability-registry.json`, `product-mechanism.json` | [`install.md`](install.md), [`product-mechanism.md`](product-mechanism.md), [`cli.md`](cli.md) |
30
30
  | Release Passport and Trust | `release-model.json`, `artifact-schemas.json`, `kfd-claims.json` | [`release-passport.md`](release-passport.md), [`release-candidate.md`](release-candidate.md), [`publish-transaction.md`](publish-transaction.md), [`binary-distribution.md`](binary-distribution.md) |
31
- | Reusable Build and Lifecycle | `workflow-registry.json`, `release-model.json` | [`reusable-build-surface.md`](reusable-build-surface.md), [`lifecycle-protocol.md`](lifecycle-protocol.md) |
31
+ | Reusable Build and Lifecycle | `workflow-registry.json`, `release-model.json` | [`reusable-build-surface.md`](reusable-build-surface.md), [`shifu-gate-profiles.md`](shifu-gate-profiles.md), [`lifecycle-protocol.md`](lifecycle-protocol.md) |
32
32
  | KFD Trust and Surface Closure | `kfd-claims.json`, `public-surface-audit.json`, `cli-registry.json`, `node-api-registry.json` | [`kfd-support.md`](kfd-support.md), [`release-passport.md`](release-passport.md) |
33
33
  | Site Bundle, Web Surfaces, and Propagation | `buildchain-site.json`, `site-manifest.json`, `page-registry.json`, `release-model.json` | [`site-bundle-contract.md`](site-bundle-contract.md), [`web-surface-deployments.md`](web-surface-deployments.md), [`release-propagation.md`](release-propagation.md) |
34
34
  | Publication Artifacts | `publication-registry.json`, `workflow-registry.json`, `node-api-registry.json`, `manual-registry.json`, `kungfu-buildchain-publication-artifact-registry` | [`publication-artifacts.md`](publication-artifacts.md), [`reusable-build-surface.md`](reusable-build-surface.md) |
@@ -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 does Buildchain schedule and aggregate a project-owned Shifu Gate profile? | [`shifu-gate-profiles.md`](shifu-gate-profiles.md) | use/verify | draft |
98
99
  | 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 |
99
100
  | 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 |
100
101
  | 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 |
@@ -158,6 +158,9 @@ commands = [
158
158
  "pnpm run package",
159
159
  ]
160
160
 
161
+ [lifecycle.check]
162
+ command = "pnpm run check:source"
163
+
161
164
  [lifecycle.verify]
162
165
  shell = "bash"
163
166
  script = """
@@ -167,6 +170,19 @@ git diff --check
167
170
  """
168
171
  ```
169
172
 
173
+ `lifecycle.check` is the repository-owned source-acceptance gate. It should
174
+ validate the checked-out source revision without entering the build, artifact,
175
+ or release lifecycle. Consumers can run it on GitHub-hosted Linux through
176
+ `.github/workflows/check.yml@v2` with `mode: source`; the reusable workflow runs
177
+ only `lifecycle.install` and `lifecycle.check`. The default `mode: verify`
178
+ continues to run `lifecycle.install` and `lifecycle.verify` for existing callers.
179
+ Both executed stages receive `BUILDCHAIN_CHECK_MODE=source` or
180
+ `BUILDCHAIN_CHECK_MODE=verify`, so a repository whose normal install path can
181
+ compile native tooling can select a provisioning-only install path for source
182
+ acceptance without weakening promotion installs.
183
+ The reusable job name remains `check`, and `upload-artifacts: false` disables
184
+ evidence upload without changing the job conclusion used by branch protection.
185
+
170
186
  Shared environment variables can be declared once:
171
187
 
172
188
  ```toml
@@ -412,6 +412,13 @@ Consumers that want Buildchain to own the check wrapper can call
412
412
  `lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when
413
413
  either declaration is missing or the command exits non-zero.
414
414
 
415
+ Development pull requests that need source acceptance without product build or
416
+ artifact verification can opt into `mode: source`. That mode runs only
417
+ `lifecycle.install` and `lifecycle.check` on GitHub-hosted `ubuntu-24.04` while
418
+ preserving the stable `check / check` required-check context. Existing callers
419
+ remain on `mode: verify` by default, and callers may set
420
+ `upload-artifacts: false` without weakening the job conclusion.
421
+
415
422
  Typical consumer wrapper:
416
423
 
417
424
  ```yaml
@@ -0,0 +1,163 @@
1
+ ---
2
+ status: draft
3
+ period: ongoing
4
+ theme: buildchain-shifu-gate-orchestration
5
+ doc_type: technical-manual
6
+ source_level: local-files
7
+ confidence: high
8
+ sensitivity: public
9
+ evidence_grade: B
10
+ review_state: self-reviewed
11
+ last_reviewed: 2026-07-13
12
+ ai_provenance:
13
+ model_family: GPT-5
14
+ product: Codex
15
+ generated_at: 2026-07-13
16
+ invisible_context_boundary: No private runner configuration, credentials, or unpublished Shifu implementation state was used.
17
+ ---
18
+
19
+ # Shifu Gate profile orchestration
20
+
21
+ Buildchain can schedule and aggregate a project-owned Shifu Gate profile without
22
+ owning that project's gate ids, commands, dependencies, or dev/alpha/release
23
+ policy. The reusable workflow is
24
+ `.github/workflows/.gate-profile.yml`.
25
+
26
+ ## Ownership boundary
27
+
28
+ | Concern | Owner | Enforced surface |
29
+ | ----------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------- |
30
+ | Gate schema, profile planning, execution, receipt qualification | Shifu | `shifu gate plan`, `shifu gate run --profile`, `shifu gate receipt validate` |
31
+ | Concrete gate catalog and profile decisions | Consumer project | project Gate registry and detailed Gate docs |
32
+ | Runner labels and declared capabilities | Consumer workflow / Buildchain preset | `runner-preset` or `platforms-json` |
33
+ | Deterministic runner matrix, immutable checkout, receipt transport, aggregate check | Buildchain | `.gate-profile.yml` and `shifu-gate-profile.mjs` |
34
+ | Whether a profile aggregate is required for dev, alpha, or release | Consumer project | protected-branch required-check policy and caller workflow |
35
+
36
+ Buildchain treats the Shifu plan and receipt as versioned input contracts. It
37
+ does not reimplement policy selection, execute raw shell strings, convert an
38
+ explicit diagnostic gate run into qualification, or mint missing evidence.
39
+
40
+ ## Runner matrix
41
+
42
+ The plan job asks the consumer's Shifu entrypoint for one plan per configured
43
+ platform. A platform is dispatchable only when:
44
+
45
+ - the Shifu plan is qualifying;
46
+ - every required selection is supported on that platform;
47
+ - the runner declares every capability requested by the selected gates; and
48
+ - all platform plans carry the same project id and registry digest.
49
+
50
+ Configured platforms are required by default. A required platform that cannot
51
+ host the profile fails before runner dispatch. A platform with
52
+ `"required": false` may be omitted, but the omission and reasons remain in the
53
+ matrix and aggregate. Matrix entries retain the Shifu plan digest, ordered gate
54
+ groups, required/advisory modes, action ids, definition digests, skips, and
55
+ unsupported selections.
56
+
57
+ `github-hosted` declares only the inherent `node` capability. Projects that
58
+ need a native compiler, product artifacts, devices, or other facilities must
59
+ use a suitable preset or declare a custom matrix. Capabilities are scheduling
60
+ claims, not installation instructions.
61
+
62
+ ```json
63
+ [
64
+ {
65
+ "id": "linux-native",
66
+ "name": "Linux native",
67
+ "platform": "linux",
68
+ "runner": "[\"self-hosted\",\"Linux\",\"X64\",\"product-build\"]",
69
+ "capabilities": ["node", "native-toolchain", "product-artifacts"]
70
+ }
71
+ ]
72
+ ```
73
+
74
+ ## Execution and receipts
75
+
76
+ Every matrix job checks out the exact source SHA planned by Buildchain, invokes
77
+ `shifu gate run --profile`, writes the receipt outside the source checkout, and
78
+ then invokes `shifu gate receipt validate`. Buildchain uploads the original
79
+ receipt and validation result even when the run fails.
80
+
81
+ The fixed `Gate profile / aggregate` job fails closed for missing receipts,
82
+ invalid or stale Shifu validation, dirty or mismatched source SHA, registry or
83
+ plan drift, missing required results, required failures/skips, or gate action
84
+ and definition digest drift. Advisory failures remain visible but do not turn a
85
+ Shifu-qualifying receipt into a required failure. Buildchain's aggregate is
86
+ `buildchain.shifu-gate-aggregate/v1`; its digest covers the matrix, receipts,
87
+ per-gate evidence pointers, omissions, and issues.
88
+
89
+ ## Consumer workflow
90
+
91
+ ```yaml
92
+ jobs:
93
+ gates:
94
+ uses: kungfu-systems/buildchain/.github/workflows/.gate-profile.yml@v2
95
+ with:
96
+ gate-profile: alpha-pr
97
+ runner-preset: kungfu-v4-self-hosted
98
+ include-advisory: true
99
+
100
+ build:
101
+ needs: gates
102
+ uses: kungfu-systems/buildchain/.github/workflows/build.yml@v2
103
+ with:
104
+ release-candidate: true
105
+ gate-profile-aggregate-json: ${{ needs.gates.outputs.gate-aggregate-json }}
106
+ ```
107
+
108
+ The command input is an argv map, not a shell string. The default supports the
109
+ ordinary Shifu launcher names on all three platforms. A project with a
110
+ different launcher can override it without teaching Buildchain project tasks:
111
+
112
+ ```yaml
113
+ gate-command-json: >-
114
+ {"linux":["./tools/shifu"],"macos":["./tools/shifu"],"windows":["./tools/shifu.cmd"]}
115
+ ```
116
+
117
+ `gate-command-json` is the execution command. If execution needs a cache,
118
+ container, or other project-owned wrapper that should not make the read-only
119
+ plan depend on that service, pass a separate lightweight argv map through
120
+ `gate-plan-command-json`. It defaults to the execution command for backward
121
+ compatibility; Buildchain still treats both inputs as argv and never evaluates
122
+ a shell string.
123
+
124
+ Projects may also pass non-sensitive scalar environment through
125
+ `gate-environment-json`; Buildchain validates the JSON shape and forwards it
126
+ without interpreting names or values. Cache profile references use the same
127
+ opaque `shifu-cache-profile-ref` and `shifu-cache-profile-digest` inputs as the
128
+ reusable build. Do not place tokens, credentials, or other secrets in workflow
129
+ inputs or Gate receipts.
130
+
131
+ When a qualifying aggregate is passed to the build workflow, the
132
+ release-candidate passport binds its profile, source SHA, registry digest,
133
+ matrix digest, aggregate digest, receipt count, and result count. A failed,
134
+ non-qualifying, or source-mismatched aggregate cannot produce a valid passport.
135
+ Promote-only release validation preserves that same Gate evidence summary in
136
+ the final Release Passport release identity, so promotion cannot silently drop
137
+ the qualified profile provenance.
138
+
139
+ ## Failure diagnosis and rollback
140
+
141
+ Start with the aggregate artifact, then the platform receipt named in its
142
+ issues. Reproduce the exact project decision with Shifu, for example:
143
+
144
+ ```bash
145
+ ./shifu gate explain <gate-id> --profile <profile>
146
+ ./shifu gate plan <profile> --platform <platform> --json
147
+ ./shifu gate receipt validate <receipt.json> --json
148
+ ```
149
+
150
+ The existing reusable build workflow remains usable without Gate inputs. To
151
+ roll back Gate orchestration, remove the caller's `gates` job and
152
+ `gate-profile-aggregate-json` handoff; this does not alter the consumer's Shifu
153
+ registry or its direct diagnostic commands.
154
+
155
+ ## Validation boundary
156
+
157
+ Unit fixtures prove deterministic matrix generation and required/advisory,
158
+ capability, unsupported, missing, stale, failure, and definition-drift
159
+ propagation. Because a train ref changes runtime scripts but not the outer
160
+ reusable workflow topology, an unreleased `.gate-profile.yml` must also be
161
+ validated through a trusted `workflow_dispatch` canary that references the
162
+ temporary workflow ref or exact SHA. See
163
+ [`runtime-train-validation.md`](runtime-train-validation.md).
@@ -122,15 +122,35 @@ jobs:
122
122
  dry-run: false
123
123
  secrets:
124
124
  promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
125
+ approval-token: ${{ secrets.BUILDCHAIN_APPROVAL_TOKEN }}
125
126
  ```
126
127
 
127
128
  The promotion token must be repository-owned and capable of creating the
128
129
  machine ledger branch, exact source-lock branch, and pull request. Repositories
129
- that require an approving review can set `auto-approve: true` only when their
130
- GitHub Actions policy explicitly allows the caller token to approve the PR;
131
- otherwise a repository-owned App or review bot supplies the approval. The
130
+ that require an approving review can pass an independent repository-owned App,
131
+ bot, or human service-account token as `approval-token`. The approval identity
132
+ must differ from the promotion token identity that opens the PR. When no
133
+ `approval-token` is passed, `auto-approve: true` falls back to the caller
134
+ `github.token` and therefore requires GitHub Actions approval permission. The
132
135
  generated PR may use auto-merge, but it never bypasses the target branch checks.
133
136
 
137
+ Before enabling `auto-approve` and `auto-merge`, the caller repository must
138
+ provide one approval path and enable auto-merge:
139
+
140
+ - pass an independent `approval-token`; or enable **Actions > General >
141
+ Workflow permissions > Allow GitHub Actions to create and approve pull
142
+ requests**, so the caller `github.token` can approve independently from the
143
+ promotion token that opened the PR;
144
+ - **General > Pull Requests > Allow auto-merge**, so Patrol can arm the
145
+ protected merge while required reviews and checks are still pending.
146
+
147
+ GitHub rejects approval from the same identity that opened the pull request;
148
+ Patrol propagates that review failure instead of reporting success.
149
+
150
+ Patrol treats a GraphQL refusal to enable auto-merge as a hard failure. A run
151
+ must not report publication authority when GitHub accepted the HTTP request but
152
+ returned a GraphQL error in the response body.
153
+
134
154
  ## Exact-source stable promotion
135
155
 
136
156
  For a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.12.1",
3
+ "version": "2.12.2",
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",
@@ -100,6 +100,36 @@ function inferVersionFromReleaseManifest(buildSummary = {}) {
100
100
  }
101
101
  }
102
102
 
103
+ function normalizeGateProfileEvidence(gateAggregate = undefined) {
104
+ if (gateAggregate === undefined || gateAggregate === null || gateAggregate === "") return undefined;
105
+ if (!gateAggregate || typeof gateAggregate !== "object" || Array.isArray(gateAggregate)) {
106
+ throw new Error("gateAggregate must be an object");
107
+ }
108
+ if (gateAggregate.contract !== "buildchain.shifu-gate-aggregate/v1") {
109
+ throw new Error("gateAggregate must use buildchain.shifu-gate-aggregate/v1");
110
+ }
111
+ const { digest, ...digestInput } = gateAggregate;
112
+ const expectedDigest = `sha256:${sha256Json(digestInput)}`;
113
+ if (digest !== expectedDigest) {
114
+ throw new Error("gateAggregate digest does not match its content");
115
+ }
116
+ return {
117
+ contract: gateAggregate.contract,
118
+ digest,
119
+ profile: nonEmptyString(gateAggregate.profile, "gateAggregate.profile"),
120
+ sourceSha: nonEmptyString(gateAggregate.sourceSha, "gateAggregate.sourceSha"),
121
+ registry: {
122
+ projectId: nonEmptyString(gateAggregate.registry?.projectId, "gateAggregate.registry.projectId"),
123
+ digest: nonEmptyString(gateAggregate.registry?.digest, "gateAggregate.registry.digest"),
124
+ },
125
+ matrixDigest: nonEmptyString(gateAggregate.matrixDigest, "gateAggregate.matrixDigest"),
126
+ status: nonEmptyString(gateAggregate.status, "gateAggregate.status"),
127
+ qualifying: gateAggregate.qualifying === true,
128
+ receiptCount: Array.isArray(gateAggregate.receipts) ? gateAggregate.receipts.length : 0,
129
+ gateResultCount: Array.isArray(gateAggregate.gates) ? gateAggregate.gates.length : 0,
130
+ };
131
+ }
132
+
103
133
  export function createReleaseCandidatePassport({
104
134
  repository = "",
105
135
  pullRequest = {},
@@ -111,6 +141,7 @@ export function createReleaseCandidatePassport({
111
141
  sourceTreeHash = "",
112
142
  buildSummary = {},
113
143
  buildchain = {},
144
+ gateAggregate = undefined,
114
145
  workflow = {},
115
146
  createdAt = nowIso(),
116
147
  } = {}) {
@@ -122,6 +153,7 @@ export function createReleaseCandidatePassport({
122
153
  || normalizeTargetChannel(normalizedSummary.publishGate?.channel)
123
154
  || normalizeTargetChannel(pullRequest.baseRef);
124
155
  const resolvedVersion = version || normalizedSummary.publishSource?.consumerVersion || inferVersionFromReleaseManifest(normalizedSummary);
156
+ const gateProfileEvidence = normalizeGateProfileEvidence(gateAggregate);
125
157
  const candidate = {
126
158
  schemaVersion: 1,
127
159
  contract: RELEASE_CANDIDATE_PASSPORT_CONTRACT,
@@ -164,6 +196,7 @@ export function createReleaseCandidatePassport({
164
196
  buildSummaryContract: optionalString(normalizedSummary.contract),
165
197
  buildSummaryHash: sha256Json(normalizedSummary),
166
198
  },
199
+ ...(gateProfileEvidence ? { gateProfileEvidence } : {}),
167
200
  };
168
201
  candidate.candidateHash = sha256Json({
169
202
  repository: candidate.repository,
@@ -171,6 +204,7 @@ export function createReleaseCandidatePassport({
171
204
  source: candidate.source,
172
205
  platformMatrix: candidate.platformMatrix,
173
206
  buildchain: candidate.buildchain,
207
+ ...(candidate.gateProfileEvidence ? { gateProfileEvidence: candidate.gateProfileEvidence } : {}),
174
208
  });
175
209
  return candidate;
176
210
  }
@@ -225,5 +259,13 @@ export function validateReleaseCandidatePassport({
225
259
  const expectedHash = sha256Json(buildSummary);
226
260
  check(passport.diagnostics?.buildSummaryHash === expectedHash, "build summary hash mismatch");
227
261
  }
262
+ if (passport.gateProfileEvidence) {
263
+ check(passport.gateProfileEvidence.contract === "buildchain.shifu-gate-aggregate/v1", "gate profile evidence contract mismatch");
264
+ check(passport.gateProfileEvidence.sourceSha === passport.source?.headSha, "gate profile evidence source SHA mismatch");
265
+ check(passport.gateProfileEvidence.status === "pass", "gate profile evidence status must be pass");
266
+ check(passport.gateProfileEvidence.qualifying === true, "gate profile evidence must be qualifying");
267
+ check(Boolean(passport.gateProfileEvidence.digest), "gate profile evidence digest is required");
268
+ check(Boolean(passport.gateProfileEvidence.matrixDigest), "gate profile matrix digest is required");
269
+ }
228
270
  return { ok: errors.length === 0, errors };
229
271
  }