@kungfu-tech/buildchain 3.0.8 → 3.0.9-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.
Files changed (79) hide show
  1. package/actions/promote-buildchain-ref/README.md +8 -7
  2. package/bin/buildchain.mjs +34 -33
  3. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  4. package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +16 -0
  5. package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +57 -0
  6. package/contracts/release-cut-v1.schema.json +1 -0
  7. package/contracts/release-train-transition-v1.schema.json +1 -0
  8. package/dist/site/agent-index.json +0 -1
  9. package/dist/site/artifact-schemas.json +0 -2
  10. package/dist/site/badge-endpoint-registry.json +9 -9
  11. package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
  12. package/dist/site/badges/v1/kfd-8/declared.json +1 -1
  13. package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
  14. package/dist/site/badges/v1/kfd-8/draft.json +1 -1
  15. package/dist/site/badges/v1/kfd-8/failed.json +1 -1
  16. package/dist/site/badges/v1/kfd-8/missing.json +1 -1
  17. package/dist/site/badges/v1/kfd-8/passed.json +1 -1
  18. package/dist/site/badges/v1/kfd-8/planned.json +1 -1
  19. package/dist/site/buildchain-contract.json +38 -34
  20. package/dist/site/buildchain-site.json +59 -39
  21. package/dist/site/capability-registry.json +1 -1
  22. package/dist/site/cli-registry.json +8 -7
  23. package/dist/site/controller-registry.json +13 -5
  24. package/dist/site/kfd-claims.json +35 -8
  25. package/dist/site/kfd-upstream-aggregate.json +10 -10
  26. package/dist/site/manual-registry.json +9 -9
  27. package/dist/site/node-api-registry.json +1143 -320
  28. package/dist/site/page-registry.json +46 -26
  29. package/dist/site/public-surface-audit.json +19 -9
  30. package/dist/site/publication-registry.json +4 -4
  31. package/dist/site/release-passport-check-manifest.json +33 -5
  32. package/dist/site/release-provenance.json +2 -1
  33. package/dist/site/schemas/release-passport-v1.schema.json +3 -0
  34. package/dist/site/site-manifest.json +13 -13
  35. package/dist/site/workflow-registry.json +19 -9
  36. package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
  37. package/docs/cli-reference.md +6 -6
  38. package/docs/cli.md +2 -3
  39. package/docs/dev-alpha-candidate-patrol.md +45 -20
  40. package/docs/dev-delivery-warrant.md +12 -2
  41. package/docs/kfd-support.md +7 -7
  42. package/docs/node-api-reference.md +295 -246
  43. package/docs/release-passport.md +25 -12
  44. package/docs/release-train.md +54 -4
  45. package/docs/reusable-build-surface.md +26 -9
  46. package/docs/runtime-train-validation.md +6 -0
  47. package/docs/versioning.md +3 -2
  48. package/package.json +4 -3
  49. package/packages/core/artifact-verification-envelope.js +114 -0
  50. package/packages/core/buildchain-channel-identity.js +119 -0
  51. package/packages/core/buildchain-compatibility-proof.js +32 -0
  52. package/packages/core/buildchain-contract.js +7 -0
  53. package/packages/core/controller-evidence.js +2 -1
  54. package/packages/core/dev-alpha-active-release-train.js +460 -0
  55. package/packages/core/dev-alpha-candidate-selection.js +181 -0
  56. package/packages/core/dev-delivery-warrant.js +15 -4
  57. package/packages/core/index.js +4 -5
  58. package/packages/core/kfd-adopter-manifest.js +390 -0
  59. package/packages/core/kfd-product-gates.js +0 -300
  60. package/packages/core/release-blocker-priority.js +192 -0
  61. package/packages/core/release-passport-contract.js +219 -6
  62. package/packages/core/release-passport.js +139 -242
  63. package/packages/core/release-train.js +220 -0
  64. package/scripts/aws-macos-jit-controller-core.mjs +76 -0
  65. package/scripts/aws-macos-jit-controller.mjs +29 -0
  66. package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
  67. package/scripts/buildchain-channel-router.mjs +18 -2
  68. package/scripts/buildchain-cli-help.mjs +4 -2
  69. package/scripts/buildchain-contract-lock.mjs +11 -1
  70. package/scripts/check-inventory.mjs +4 -4
  71. package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
  72. package/scripts/generate-buildchain-kfd-witnesses.mjs +88 -94
  73. package/scripts/generate-channel-build-workflow.mjs +78 -31
  74. package/scripts/generate-site-bundle.mjs +1 -8
  75. package/scripts/promotion-channel-router.mjs +5 -3
  76. package/scripts/release-train-self-dogfood.mjs +579 -0
  77. package/scripts/runtime-ref-core.mjs +4 -17
  78. package/scripts/site-capability-metadata.mjs +3 -1
  79. package/dist/site/schemas/kfd-support-projection-v1.schema.json +0 -106
@@ -313,6 +313,13 @@ fails closed if any claim is missing source bindings, machine evidence, hashes,
313
313
  artifact coordinates, verification result, audit boundary, responsibility, or
314
314
  residual risk.
315
315
 
316
+ The same self-promotion boundary extends the Buildchain-owned KFD witness
317
+ generator against the transaction's exact source SHA. It produces the standard
318
+ full-cut adopter manifest, the non-qualifying KFD-4/5/7 product gates, and the
319
+ one-way legacy projection before the final passport is collected. An explicitly
320
+ supplied adopter manifest still takes precedence; an omitted self-release
321
+ manifest no longer silently produces a KFD-1/2/3-only passport.
322
+
316
323
  ### KFD-3 collaboration-interface release gate
317
324
 
318
325
  KFD-3 asks a different release question than KFD-1. KFD-1 proves that named
@@ -399,18 +406,24 @@ instead of trusting docs or release notes.
399
406
 
400
407
  ### KFD support projection
401
408
 
402
- Pass `--kfd-support-matrix-json` together with three
403
- `--kfd-product-gate-json` arguments for KFD-4, KFD-5, and KFD-7. The collector
404
- verifies the product-owned KFD-1..13 matrix against the installed
405
- `@kungfu-tech/kfd` standards, binds each gate to the exact release source and
406
- fresh evidence cut, and emits `kfd-support.json`. The identical projection is
407
- embedded as `kfdSupport` in `buildchain.release.json`.
408
-
409
- Release verification fails closed when the sibling is missing or differs,
410
- when a gate expires, when matrix and gate status disagree, or when a matrix
411
- widens KFD-4/5 candidate, KFD-6 unsupported, KFD-8..13 draft, or any non-shipped
412
- state. A passed projection is release evidence only; product qualification,
413
- activation, certification, and support ownership remain outside Buildchain.
409
+ Pass the standard full-cut declaration with `--kfd-adopter-manifest-json`
410
+ together with three `--kfd-product-gate-json` arguments for KFD-4, KFD-5, and
411
+ KFD-7. The collector invokes the verifier from the exact installed
412
+ `@kungfu-tech/kfd` package, binds the package artifact, registry and verifier
413
+ roots, the exact Buildchain source, the manifest/report/bundle witness roots,
414
+ and the existing product-gate roots. It emits `kfd-adopter-manifest.json`,
415
+ `kfd-adopter-manifest-gate.json`, and a legacy `kfd-support.json` projection.
416
+ The release passport and `artifact-evidence.json` carry the same rooted
417
+ `kfdAdopter` binding.
418
+
419
+ The adopter manifest is the sole declaration authority. The old
420
+ `--kfd-support-matrix-json` input is retained only as a compatibility check: if
421
+ supplied with the manifest, it must exactly equal the generated legacy
422
+ projection. Release verification fails closed when any sibling, package root,
423
+ source root, witness root, gate root, passport binding, or artifact-evidence
424
+ binding differs. A passed binding is release evidence only; product
425
+ qualification, activation, certification, and support ownership remain outside
426
+ Buildchain.
414
427
 
415
428
  ### Floating Buildchain contract lock
416
429
 
@@ -8,12 +8,12 @@ confidence: high
8
8
  sensitivity: public
9
9
  evidence_grade: A
10
10
  review_state: self-reviewed
11
- last_reviewed: 2026-08-10
11
+ last_reviewed: 2026-08-11
12
12
  ai_provenance:
13
13
  model_family: GPT-5
14
14
  product: Codex
15
- generated_at: 2026-08-10
16
- visible_context: Buildchain v3 release candidate, recovery, Dev to Alpha Candidate Patrol, Warrant, publication transaction, and release governance sources.
15
+ generated_at: 2026-08-11
16
+ visible_context: Buildchain v3 release candidate, recovery, Dev to Alpha Candidate Patrol, rooted blocker repair, Warrant, publication transaction, and release governance sources.
17
17
  invisible_context_boundary: Live provider state and credentials were not read.
18
18
  ---
19
19
 
@@ -38,8 +38,9 @@ own those effects and persist the rooted contract in their declared store.
38
38
  - a canonical creation timestamp.
39
39
 
40
40
  The resulting `cutRoot` covers all of those fields. A generation greater than
41
- one must name the prior cut root and one of four supersession causes:
41
+ one must name the prior cut root and one of five supersession causes:
42
42
 
43
+ - `release-blocker-repair` (reserved for the rooted repair contract below);
43
44
  - `incompatible-semantics`;
44
45
  - `alpha-base-incompatibility`;
45
46
  - `invalid-authority`; or
@@ -74,6 +75,55 @@ an invalid transition fails closed.
74
75
  changing `trainRoot`, `cutRoot`, candidate identity, generation, or state. The
75
76
  same exact observation is idempotent.
76
77
 
78
+ ## Rooted release-blocker repair
79
+
80
+ `createReleaseBlockerRepair()` starts only from the active train's exact
81
+ `repair-required` state root. It creates generation N+1 from the frozen cut,
82
+ changing only the candidate commit and tree while preserving the original Dev
83
+ cut, Alpha base, runtime, route, and prior cut root. The prior train receives an
84
+ explicit `release-blocker-repair` supersession transition; later Dev movement
85
+ alone still cannot advance a generation.
86
+
87
+ The repair binds one semantic patch root to a cut landing and a Dev
88
+ forward-port. A successful cut landing makes the successor candidate buildable
89
+ even while the Dev forward-port is in conflict. Publication remains blocked
90
+ until `settleReleaseBlockerDevLanding()` compare-and-swap checks the repair root
91
+ and records a Dev landing with the same patch root. A landed but different Dev
92
+ patch produces a rooted `cut-dev-patch-root-mismatch` gate rather than
93
+ publication authority.
94
+
95
+ Buildchain v3 Candidate Patrol resolves the open managed candidate's persisted
96
+ train before it considers a new qualified development head. It reads back the
97
+ candidate ref and tree, Alpha base, and exact Buildchain runtime. Matching
98
+ coordinates resume the frozen candidate; a newer development head becomes a
99
+ single rooted observation. Candidate, tree, base, runtime, or route drift emits
100
+ a rooted hold and stops settlement. Only a validated train already carrying an
101
+ enumerated `superseded` transition is reported as superseded.
102
+
103
+ ## Buildchain self-dogfood campaign
104
+
105
+ Buildchain qualifies the complete v3 mechanism with
106
+ `scripts/release-train-self-dogfood.mjs`. The campaign composes one frozen
107
+ Release Cut, moving-dev observation, deterministic failed build, successor
108
+ repair, cut/dev patch-root settlement, publication gate, and bounded Delivery
109
+ Warrant priority. It also proves that an active Warrant is not preempted and
110
+ that duplicate events, invalid authority, and unrelated priority claims fail
111
+ closed.
112
+
113
+ The command accepts an exact evidence input and emits a replayable rooted
114
+ report:
115
+
116
+ ```sh
117
+ node scripts/release-train-self-dogfood.mjs \
118
+ --input /path/to/exact-protected-delivery.json \
119
+ --output /tmp/buildchain-release-train-self-dogfood.json
120
+ ```
121
+
122
+ A passing report requires a merged protected PR, exact-head approval,
123
+ merge-group CI success, tree-equivalent merge readback, artifact evidence, and
124
+ installed-product evidence. Synthetic or unit-only evidence is useful for
125
+ negative tests but is not sufficient for release qualification.
126
+
77
127
  ## Readback and legacy state
78
128
 
79
129
  `validateReleaseCut()` checks the standalone immutable cut and its canonical
@@ -66,10 +66,25 @@ release events, and non-semver release-like tags fail before the build matrix;
66
66
  they never guess alpha for a stable release.
67
67
 
68
68
  The router automatically selects `.buildchain/alpha-contract-lock.json` for
69
- alpha and `.buildchain/contract-lock.json` for stable. A repository can override
70
- the common path with `buildchain-contract-lock-path`, or override one channel
71
- with `buildchain-alpha-contract-lock-path` /
72
- `buildchain-stable-contract-lock-path`.
69
+ alpha and `.buildchain/contract-lock.json` for stable. The generated workflow
70
+ uses two static advanced-workflow calls: `@vN-alpha` for alpha and `@vN` for
71
+ stable. A repository can override the common path with
72
+ `buildchain-contract-lock-path`, or override one channel with
73
+ `buildchain-alpha-contract-lock-path` / `buildchain-stable-contract-lock-path`,
74
+ but the path is never channel authority. The selected file's
75
+ `buildchain.ref` must prove the same channel and major as the workflow shell and
76
+ runtime.
77
+
78
+ Channel binding is independent of the current Buildchain major. Every
79
+ channel-bound run validates the complete triad before compatibility drift:
80
+
81
+ - stable = shell `vN` + runtime `vN` + a lock whose ref is stable `vN`;
82
+ - alpha = shell `vN-alpha` + runtime `vN-alpha` + a lock whose ref is alpha
83
+ `vN-alpha`.
84
+
85
+ Exact release refs are classified the same way. A missing lock, an ambiguous
86
+ identity, a major mismatch, or any stable/alpha mixture fails even when the
87
+ contract digest is unchanged or the drift would otherwise be additive.
73
88
 
74
89
  Only repositories changing the default policy need extra routing input:
75
90
 
@@ -279,7 +294,8 @@ shell. The full train validation protocol is documented in
279
294
  [`runtime-train-validation.md`](runtime-train-validation.md).
280
295
 
281
296
  For one-off manual validation, a trusted maintainer can run the caller workflow
282
- with a temporary runtime override:
297
+ with a temporary runtime override. The override inherits the caller's declared
298
+ alpha or stable lane; trust authorizes the opaque ref, not cross-channel use:
283
299
 
284
300
  ```yaml
285
301
  on:
@@ -1092,10 +1108,11 @@ jobs:
1092
1108
  ```
1093
1109
 
1094
1110
  Existing callers may keep `buildchain-contract-lock-path`; a non-empty explicit
1095
- path overrides channel-specific selection for compatibility. Migration only
1096
- requires adding the two channel lock inputs and may retain the remaining common
1097
- promotion declaration unchanged. Consumers must not call the dot-prefixed
1098
- advanced workflow directly.
1111
+ path overrides channel-specific path selection only. Its lock content must
1112
+ still prove the selected channel and major. Migration only requires adding the
1113
+ two channel lock inputs and may retain the remaining common promotion
1114
+ declaration unchanged. Consumers must not call the dot-prefixed advanced
1115
+ workflow directly.
1099
1116
 
1100
1117
  `buildchain-issue-app-id` and `buildchain-issue-app-private-key` are optional
1101
1118
  but recommended for cross-repository consumers. They should identify a GitHub
@@ -165,6 +165,12 @@ maintain, or admin permission on the caller repository. Pull requests,
165
165
  including fork-originated pull requests, cannot use train or exact-SHA
166
166
  overrides.
167
167
 
168
+ That permission does not create a third channel. A train, authority ref, or
169
+ exact SHA must be bound to an alpha or stable shell lane, and the consumer lock
170
+ must prove that same lane and major. Trusted overrides can replace the runtime
171
+ coordinate for validation; they cannot combine a stable shell or lock with an
172
+ alpha runtime, or the reverse.
173
+
168
174
  Runtime train validation covers Buildchain runtime scripts, CLI code, local
169
175
  actions, configuration parsing, and lifecycle behavior. It cannot validate
170
176
  changes that require the outer reusable workflow YAML itself to change, such as
@@ -79,8 +79,9 @@ repository changes are patch".
79
79
 
80
80
  | Date | Action | Line | Faces | Class | Rationale | PR |
81
81
  | ---------- | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
82
- | 2026-08-10 | extend-minor | `v3.0` | release-cut-contract, release-train-state-machine, release-train-node-export | additive | Add a provider-neutral rooted Release Cut and idempotent Release Train state contract that freezes candidate, tree, Alpha base, runtime, generation, and authority while treating later Dev movement as observation rather than implicit supersession. | |
83
- | 2026-08-05 | extend-minor | `v3.0` | auditable-demo-scenario, auditable-demo-capture, auditable-demo-adapter | additive | Add an opt-in deterministic readable-playback declaration that preserves captured terminal payloads and order while normalizing only presentation timing; omitted playback continues to use observed PTY timestamps, and existing composition modes remain unchanged. | |
82
+ | 2026-08-11 | extend-minor | `v3.0` | release-blocker-repair-contract, release-blocker-priority-claim, release-train-node-export, dev-delivery-warrant-node-export | additive | Add rooted successor candidate generations on a frozen Release Cut, exact semantic patch identity across cut and Dev landings, publication blocking through conflict or mismatch, and a narrow non-preemptive Warrant blocker lane. | |
83
+ | 2026-08-10 | extend-minor | `v3.0` | release-cut-contract, release-train-state-machine, release-train-node-export | additive | Add a provider-neutral rooted Release Cut and idempotent Release Train state contract that freezes candidate, tree, Alpha base, runtime, generation, and authority while treating later Dev movement as observation rather than implicit supersession. | |
84
+ | 2026-08-05 | extend-minor | `v3.0` | auditable-demo-scenario, auditable-demo-capture, auditable-demo-adapter | additive | Add an opt-in deterministic readable-playback declaration that preserves captured terminal payloads and order while normalizing only presentation timing; omitted playback continues to use observed PTY timestamps, and existing composition modes remain unchanged. | |
84
85
  | 2026-08-04 | extend-minor | `v3.0` | dev-delivery-warrant-queue, source-qualification-proof, integration-delivery-proof, dev-delivery-cli, reusable-dev-delivery-workflows | additive | Add durable fair and fenced protected-dev scheduling plus split source/integration proof contracts. Existing PR admission remains available through explicit `off` and `shadow` rollout modes; no existing command, export, or release flow is removed. | |
85
86
  | 2026-08-01 | extend-minor | `v3.0` | cli-reference-registry, node-api-symbol-registry, golden-path-manual | additive | Add generated, drift-checked CLI and Node API reference registries to the public site bundle, plus a package-tested first-user Golden Path; existing command execution and import semantics remain unchanged. | |
86
87
  | 2026-07-31 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-media-profile, auditable-demo-media-receipt | additive | Add an opt-in responsive profile that binds source-resolution and exact 1280x720 MP4/WebM/GIF renditions to one Gate and receipt while rejecting upscales, aspect-ratio drift, and profile changes between Gate-only and full-render paths. | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "3.0.8",
3
+ "version": "3.0.9-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",
@@ -33,6 +33,8 @@
33
33
  "./issue-reporting": "./packages/core/issue-reporting.js",
34
34
  "./kfd": "./packages/core/kfd.js",
35
35
  "./kfd-product-gates": "./packages/core/kfd-product-gates.js",
36
+ "./kfd-adopter-manifest": "./packages/core/kfd-adopter-manifest.js",
37
+ "./kfd-adopter-release-binding": "./packages/core/artifact-verification-envelope.js",
36
38
  "./kfd-agent-hub": "./packages/core/kfd-agent-hub.js",
37
39
  "./buildchain-layout": "./packages/core/buildchain-layout.js",
38
40
  "./release-line-bootstrap": "./packages/core/release-line-bootstrap.js",
@@ -87,7 +89,6 @@
87
89
  "./site/schemas/release-passport-v1.schema.json": "./dist/site/schemas/release-passport-v1.schema.json",
88
90
  "./site/schemas/kfd-agent-hub-adoption.schema.json": "./dist/site/schemas/kfd-agent-hub-adoption.schema.json",
89
91
  "./site/schemas/kfd-product-gate-input-v1.schema.json": "./dist/site/schemas/kfd-product-gate-input-v1.schema.json",
90
- "./site/schemas/kfd-support-projection-v1.schema.json": "./dist/site/schemas/kfd-support-projection-v1.schema.json",
91
92
  "./site/buildchain-contract.json": "./dist/site/buildchain-contract.json",
92
93
  "./site/product-mechanism.json": "./dist/site/product-mechanism.json",
93
94
  "./site/release-provenance.json": "./dist/site/release-provenance.json",
@@ -139,7 +140,7 @@
139
140
  "typescript": "^5.9.0"
140
141
  },
141
142
  "dependencies": {
142
- "@kungfu-tech/kfd": "1.0.0-alpha.53",
143
+ "@kungfu-tech/kfd": "1.0.0-alpha.60",
143
144
  "ajv": "8.20.0",
144
145
  "smol-toml": "1.7.0"
145
146
  }
@@ -1,4 +1,11 @@
1
1
  import crypto from "node:crypto";
2
+ import kfdPackageJson from "@kungfu-tech/kfd/package.json" with { type: "json" };
3
+ import kfdSelfAdopterManifest from "@kungfu-tech/kfd/adopter-conformance/adopters/kfd/manifest.json" with { type: "json" };
4
+ import {
5
+ validateKfdAdopterManifestGate,
6
+ validateKfdLegacySupportMatrixProjection,
7
+ } from "./kfd-adopter-manifest.js";
8
+ import { kfdProductGateDigest } from "./kfd-product-gates.js";
2
9
 
3
10
  export const ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT =
4
11
  "kungfu-buildchain-artifact-verification-envelope";
@@ -6,6 +13,8 @@ export const ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT =
6
13
  "kungfu-buildchain-artifact-verification-envelope-check";
7
14
  export const KFX_ADMISSION_INPUTS_CONTRACT =
8
15
  "kungfu-buildchain-kfx-admission-inputs";
16
+ export const KFD_ADOPTER_RELEASE_BINDING_CONTRACT =
17
+ "kungfu-buildchain-kfd-adopter-release-binding";
9
18
 
10
19
  const ARTIFACT_VERIFICATION_CONTRACT =
11
20
  "kungfu-buildchain-artifact-verification";
@@ -384,3 +393,108 @@ export function projectArtifactVerificationEnvelopeToKfx({
384
393
  kfdAssessment: jsonClone(envelope.kfdAssessment),
385
394
  };
386
395
  }
396
+
397
+ export function installedKfdPackageArtifactRoot() {
398
+ const cut = kfdSelfAdopterManifest?.kfdCut?.package;
399
+ if (cut?.name !== kfdPackageJson.name || cut?.version !== kfdPackageJson.version
400
+ || !SHA256_PATTERN.test(String(cut?.artifactRoot || ""))) {
401
+ throw new Error("installed KFD package does not expose a matching rooted self-adopter package cut");
402
+ }
403
+ return cut.artifactRoot;
404
+ }
405
+
406
+ function kfdBindingBody({ manifest, manifestGate, legacyProjection, paths }) {
407
+ return {
408
+ schemaVersion: 1,
409
+ contract: KFD_ADOPTER_RELEASE_BINDING_CONTRACT,
410
+ status: "passed",
411
+ qualifying: false,
412
+ selfCertified: false,
413
+ authority: {
414
+ manifestPath: paths.manifest,
415
+ manifestContract: manifest.contract,
416
+ manifestRoot: manifestGate.authority.manifestRoot,
417
+ gatePath: paths.gate,
418
+ gateRoot: manifestGate.gateRoot,
419
+ },
420
+ source: jsonClone(manifestGate.source),
421
+ standardPackage: jsonClone(manifestGate.standardPackage),
422
+ witness: {
423
+ decisionRoot: manifestGate.decisionWitness.root,
424
+ verificationReportRoot: manifestGate.manifestVerificationReportRoot,
425
+ bundleRoot: manifestGate.manifestBundleRoot,
426
+ },
427
+ legacyProjection: {
428
+ path: paths.legacy,
429
+ contract: legacyProjection.contract,
430
+ root: kfdProductGateDigest(legacyProjection),
431
+ authorityRoot: legacyProjection.authority.root,
432
+ gateRoot: legacyProjection.authority.gateRoot,
433
+ },
434
+ nonClaims: [
435
+ "This binding proves exact release-envelope consumption, not KFD certification or release authorization.",
436
+ "The legacy support matrix remains a projection of the standard adopter manifest authority.",
437
+ ],
438
+ };
439
+ }
440
+
441
+ export function createKfdAdopterReleaseBinding({
442
+ manifest,
443
+ manifestGate,
444
+ legacyProjection,
445
+ manifestPath = "kfd-adopter-manifest.json",
446
+ gatePath = "kfd-adopter-manifest-gate.json",
447
+ legacyProjectionPath = "kfd-support.json",
448
+ expectedSourceSha = "",
449
+ } = {}) {
450
+ const gate = validateKfdAdopterManifestGate(manifestGate, {
451
+ expectedSourceSha,
452
+ checkedAt: manifestGate?.checkedAt,
453
+ });
454
+ const legacy = validateKfdLegacySupportMatrixProjection(legacyProjection, { manifest, manifestGate });
455
+ if (!gate.valid || !legacy.valid) {
456
+ throw new Error(`KFD adopter release binding inputs are invalid: ${JSON.stringify([...gate.issues, ...legacy.issues])}`);
457
+ }
458
+ const body = kfdBindingBody({
459
+ manifest,
460
+ manifestGate,
461
+ legacyProjection,
462
+ paths: { manifest: manifestPath, gate: gatePath, legacy: legacyProjectionPath },
463
+ });
464
+ return { ...body, bindingRoot: kfdProductGateDigest(body) };
465
+ }
466
+
467
+ export function validateKfdAdopterReleaseBinding(binding, {
468
+ manifest,
469
+ manifestGate,
470
+ legacyProjection,
471
+ expectedSourceSha = "",
472
+ } = {}) {
473
+ const issues = [];
474
+ if (!binding || binding.schemaVersion !== 1 || binding.contract !== KFD_ADOPTER_RELEASE_BINDING_CONTRACT) {
475
+ return { valid: false, issues: [{ code: "adopter-release-binding-contract", path: "", message: `binding must use ${KFD_ADOPTER_RELEASE_BINDING_CONTRACT} v1` }] };
476
+ }
477
+ const copy = jsonClone(binding);
478
+ const bindingRoot = copy.bindingRoot;
479
+ delete copy.bindingRoot;
480
+ if (bindingRoot !== kfdProductGateDigest(copy)) {
481
+ issues.push({ code: "adopter-release-binding-root", path: "bindingRoot", message: "release binding root does not match its content" });
482
+ }
483
+ try {
484
+ const expected = createKfdAdopterReleaseBinding({
485
+ manifest,
486
+ manifestGate,
487
+ legacyProjection,
488
+ manifestPath: binding.authority?.manifestPath,
489
+ gatePath: binding.authority?.gatePath,
490
+ legacyProjectionPath: binding.legacyProjection?.path,
491
+ expectedSourceSha,
492
+ });
493
+ if (stableJson(binding) !== stableJson(expected)) {
494
+ issues.push({ code: "adopter-release-binding-drift", path: "", message: "release binding differs from the exact manifest, gate, or legacy projection closure" });
495
+ }
496
+ } catch (error) {
497
+ issues.push({ code: "adopter-release-binding-authority", path: "", message: error.message });
498
+ }
499
+ return { valid: issues.length === 0, issues };
500
+ }
@@ -0,0 +1,119 @@
1
+ const EXACT_SHA_RE = /^[0-9a-f]{40}$/i;
2
+ const OFFICIAL_REF_RE = /^v(\d+)(?:\.\d+)?(-alpha)?$/;
3
+ const STABLE_RELEASE_RE = /^v(\d+)\.\d+\.\d+$/;
4
+ const ALPHA_RELEASE_RE = /^v(\d+)\.\d+\.\d+-alpha\.\d+$/;
5
+ const TRAIN_REF_RE = /^train\/v(\d+)\/v(\d+)\.\d+\/[A-Za-z0-9._/-]+$/;
6
+ const AUTHORITY_REF_RE = /^authority\/v(\d+)\/v(\d+)\.\d+\/[A-Za-z0-9._/-]+$/;
7
+
8
+ export const BUILDCHAIN_CHANNELS = new Set(["stable", "alpha"]);
9
+
10
+ export function normalizeBuildchainRef(ref = "") {
11
+ const value = String(ref || "").trim();
12
+ const workflowRef = value.includes("/.github/workflows/") ? value.split("@").pop() || "" : value;
13
+ return workflowRef.replace(/^refs\/(?:heads|tags)\//, "");
14
+ }
15
+
16
+ export function parseBuildchainRefIdentity(ref = "") {
17
+ const value = normalizeBuildchainRef(ref);
18
+ if (!value) return { ref: value, kind: "missing", channel: null, major: null };
19
+ if (EXACT_SHA_RE.test(value)) return { ref: value, kind: "exact-sha", channel: null, major: null };
20
+
21
+ for (const [pattern, channel, kind] of [
22
+ [OFFICIAL_REF_RE, null, "official-channel"],
23
+ [STABLE_RELEASE_RE, "stable", "release"],
24
+ [ALPHA_RELEASE_RE, "alpha", "release"],
25
+ ]) {
26
+ const match = value.match(pattern);
27
+ if (match) {
28
+ return {
29
+ ref: value,
30
+ kind,
31
+ channel: channel || (match[2] ? "alpha" : "stable"),
32
+ major: Number(match[1]),
33
+ };
34
+ }
35
+ }
36
+
37
+ for (const [pattern, kind] of [
38
+ [TRAIN_REF_RE, "train"],
39
+ [AUTHORITY_REF_RE, "authority"],
40
+ ]) {
41
+ const match = value.match(pattern);
42
+ if (match) {
43
+ return {
44
+ ref: value,
45
+ kind,
46
+ channel: null,
47
+ major: Number(match[1]),
48
+ coherentMajor: match[1] === match[2],
49
+ };
50
+ }
51
+ }
52
+ return { ref: value, kind: "development", channel: null, major: null };
53
+ }
54
+
55
+ function expectedIdentity({ workflowShellRef, expectedChannel, expectedMajor }) {
56
+ const shell = parseBuildchainRefIdentity(workflowShellRef);
57
+ const channel =
58
+ String(expectedChannel || "")
59
+ .trim()
60
+ .toLowerCase() || shell.channel;
61
+ const majorText = String(expectedMajor ?? "").trim();
62
+ const major = majorText ? Number(majorText.replace(/^v/, "")) : shell.major;
63
+ return { shell, channel, major };
64
+ }
65
+
66
+ function expectedReasons(expected, { expectedChannel, expectedMajor }) {
67
+ const reasons = [];
68
+ if (!BUILDCHAIN_CHANNELS.has(expected.channel)) reasons.push("Buildchain channel is missing or ambiguous");
69
+ if (!Number.isInteger(expected.major) || expected.major < 1) reasons.push("Buildchain major is missing or ambiguous");
70
+ if (expected.shell.channel && expected.shell.channel !== expected.channel) {
71
+ reasons.push(`workflow shell ${expected.shell.ref} is ${expected.shell.channel}, expected ${expected.channel}`);
72
+ }
73
+ if (Number.isInteger(expected.shell.major) && expected.shell.major !== expected.major) {
74
+ reasons.push(`workflow shell ${expected.shell.ref} is v${expected.shell.major}, expected v${expected.major}`);
75
+ }
76
+ if (!expected.shell.channel && (!expectedChannel || !expectedMajor)) {
77
+ reasons.push(`workflow shell ${expected.shell.ref || "(missing)"} does not declare a stable or alpha channel`);
78
+ }
79
+ return reasons;
80
+ }
81
+
82
+ function runtimeReasons(expected, runtime, allowOpaqueRuntime) {
83
+ if (runtime.channel) {
84
+ return [runtime.channel !== expected.channel ? `runtime ${runtime.ref} is ${runtime.channel}, expected ${expected.channel}` : "", runtime.major !== expected.major ? `runtime ${runtime.ref} is v${runtime.major}, expected v${expected.major}` : ""].filter(Boolean);
85
+ }
86
+ if (new Set(["train", "authority", "exact-sha"]).has(runtime.kind) && allowOpaqueRuntime) {
87
+ return [runtime.coherentMajor === false ? `runtime ${runtime.ref} has inconsistent major segments` : "", Number.isInteger(runtime.major) && runtime.major !== expected.major ? `runtime ${runtime.ref} is v${runtime.major}, expected v${expected.major}` : ""].filter(Boolean);
88
+ }
89
+ return [`runtime ${runtime.ref || "(missing)"} does not prove the ${expected.channel || "selected"} channel`];
90
+ }
91
+
92
+ function lockReasons(expected, lock, lockMajor, lockMajorLine) {
93
+ if (!lock.ref) return ["Buildchain contract lock is required for a channel-bound run"];
94
+ return [lock.channel !== expected.channel ? `contract lock ref ${lock.ref} does not prove the ${expected.channel} channel` : "", lock.major !== expected.major ? `contract lock ref ${lock.ref} does not prove major v${expected.major}` : "", lockMajorLine && lockMajor.major !== expected.major ? `contract lock major line ${lockMajor.ref} does not match v${expected.major}` : ""].filter(Boolean);
95
+ }
96
+
97
+ export function evaluateBuildchainChannelBinding({ workflowShellRef = "", runtimeRef = "", lockRef = "", lockMajorLine = "", expectedChannel = "", expectedMajor = "", allowOpaqueRuntime = false } = {}) {
98
+ const expected = expectedIdentity({
99
+ workflowShellRef,
100
+ expectedChannel,
101
+ expectedMajor,
102
+ });
103
+ const runtime = parseBuildchainRefIdentity(runtimeRef);
104
+ const lock = parseBuildchainRefIdentity(lockRef);
105
+ const lockMajor = parseBuildchainRefIdentity(lockMajorLine);
106
+ const reasons = [...expectedReasons(expected, { expectedChannel, expectedMajor }), ...runtimeReasons(expected, runtime, allowOpaqueRuntime), ...lockReasons(expected, lock, lockMajor, lockMajorLine)];
107
+
108
+ return {
109
+ ok: reasons.length === 0,
110
+ status: reasons.length === 0 ? "channel-bound" : "channel-binding-mismatch",
111
+ channel: expected.channel || null,
112
+ major: Number.isInteger(expected.major) ? expected.major : null,
113
+ shell: expected.shell,
114
+ runtime,
115
+ lock,
116
+ lockMajor,
117
+ reasons,
118
+ };
119
+ }
@@ -7,6 +7,7 @@ import {
7
7
  devDeliveryRepository as repository,
8
8
  devDeliveryText as text,
9
9
  } from "./dev-delivery-common.js";
10
+ import { evaluateBuildchainChannelBinding } from "./buildchain-channel-identity.js";
10
11
 
11
12
  export const BUILDCHAIN_COMPATIBILITY_PROOF_SCHEMA = "kungfu.buildchain.compatibility-proof/v1";
12
13
  export const BUILDCHAIN_COMPATIBILITY_PROOF_REGISTRY_SCHEMA = "kungfu.buildchain.compatibility-proof-registry/v1";
@@ -437,6 +438,19 @@ function evaluateSurfaceRelation({ oldSurface, nextSurface, proofRegistry, major
437
438
  };
438
439
  }
439
440
 
441
+ function evaluateChannelBinding({ lock, workflowShellRef, runtimeRef, expectedChannel, expectedMajor, allowOpaqueRuntime }) {
442
+ if (!workflowShellRef && !expectedChannel && !expectedMajor) return null;
443
+ return evaluateBuildchainChannelBinding({
444
+ workflowShellRef,
445
+ runtimeRef,
446
+ lockRef: lock?.buildchain?.ref || "",
447
+ lockMajorLine: lock?.buildchain?.majorLine || "",
448
+ expectedChannel,
449
+ expectedMajor,
450
+ allowOpaqueRuntime,
451
+ });
452
+ }
453
+
440
454
  export function evaluateBuildchainContractLock({
441
455
  lock,
442
456
  current,
@@ -444,11 +458,29 @@ export function evaluateBuildchainContractLock({
444
458
  runtimeSha = "",
445
459
  runtimeClass = "",
446
460
  compatibilityPolicy = "",
461
+ workflowShellRef = "",
462
+ expectedChannel = "",
463
+ expectedMajor = "",
464
+ allowOpaqueRuntime = false,
447
465
  } = {}) {
448
466
  if (!current || current.contract !== BUILDCHAIN_CONTRACT) {
449
467
  throw new Error("current must be a Buildchain runtime contract world");
450
468
  }
451
469
  assertBuildchainCompatibilityProjection(current);
470
+ const binding = evaluateChannelBinding({
471
+ lock, workflowShellRef, runtimeRef, expectedChannel, expectedMajor, allowOpaqueRuntime,
472
+ });
473
+ if (binding?.ok === false) {
474
+ return {
475
+ ok: false,
476
+ status: binding.status,
477
+ drift: false,
478
+ compatible: false,
479
+ issueRecommended: false,
480
+ reasons: binding.reasons,
481
+ channelBinding: binding,
482
+ };
483
+ }
452
484
  if (!new Set(["stable", "alpha"]).has(runtimeClass)) {
453
485
  return {
454
486
  ok: true,
@@ -9,6 +9,13 @@ import {
9
9
  import { createControllerRegistry } from "./controller-evidence.js";
10
10
  import { enumerateWorkflowInputs } from "./public-surface-audit.js";
11
11
 
12
+ export {
13
+ BUILDCHAIN_CHANNELS,
14
+ evaluateBuildchainChannelBinding,
15
+ normalizeBuildchainRef,
16
+ parseBuildchainRefIdentity,
17
+ } from "./buildchain-channel-identity.js";
18
+
12
19
  export {
13
20
  BUILDCHAIN_COMPATIBILITY_PROOF_REGISTRY_SCHEMA,
14
21
  BUILDCHAIN_COMPATIBILITY_PROOF_SCHEMA,
@@ -53,7 +53,8 @@ const CONTROLLER_SPECS = [
53
53
  workflowId: "build",
54
54
  version: 1,
55
55
  capabilities: ["channel-selection", "runtime-selection", "build-delegation"],
56
- stages: ["resolve-channel", "build", "aggregate"],
56
+ stages: ["resolve-channel", "alpha", "stable", "aggregate"],
57
+ optionalStages: ["alpha", "stable"],
57
58
  evidence: ["nested-controller-receipt", "controller-receipt"],
58
59
  },
59
60
  {