@kungfu-tech/buildchain 2.12.0 → 2.12.1-alpha.10

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 (42) hide show
  1. package/bin/buildchain.mjs +18 -0
  2. package/dist/site/buildchain-contract.json +8 -8
  3. package/dist/site/buildchain-site.json +92 -26
  4. package/dist/site/capability-registry.json +5 -5
  5. package/dist/site/cli-registry.json +12 -0
  6. package/dist/site/kfd-claims.json +182 -14
  7. package/dist/site/kfd-upstream-aggregate.json +1 -1
  8. package/dist/site/manual-registry.json +8 -7
  9. package/dist/site/node-api-registry.json +20 -7
  10. package/dist/site/page-registry.json +81 -15
  11. package/dist/site/public-surface-audit.json +95 -10
  12. package/dist/site/publication-registry.json +4 -4
  13. package/dist/site/release-provenance.json +1 -0
  14. package/dist/site/site-manifest.json +11 -11
  15. package/dist/site/workflow-registry.json +68 -3
  16. package/docs/MAP.md +3 -0
  17. package/docs/binary-distribution.md +20 -1
  18. package/docs/cli.md +17 -0
  19. package/docs/kfd-support.md +44 -1
  20. package/docs/lifecycle-protocol.md +12 -0
  21. package/docs/release-governance.md +23 -0
  22. package/docs/reusable-build-surface.md +69 -0
  23. package/docs/stable-candidate-patrol.md +188 -0
  24. package/package.json +3 -1
  25. package/packages/core/buildchain-config.js +58 -0
  26. package/packages/core/buildchain-kfd-claims.js +51 -2
  27. package/packages/core/buildchain-layout.js +46 -0
  28. package/packages/core/index.js +19 -0
  29. package/packages/core/kfd3-surface-register.js +59 -2
  30. package/packages/core/stable-candidate-ledger.js +276 -0
  31. package/scripts/build-standalone-binary.mjs +2 -1
  32. package/scripts/check-inventory.mjs +4 -1
  33. package/scripts/generate-site-bundle.mjs +9 -1
  34. package/scripts/init-repo.mjs +1 -1
  35. package/scripts/locked-source-checkout.mjs +52 -12
  36. package/scripts/npm-publish-dry-run.mjs +1 -1
  37. package/scripts/publication-artifact.mjs +1 -1
  38. package/scripts/publication-package.mjs +1 -1
  39. package/scripts/release-propagation.mjs +1 -1
  40. package/scripts/stable-candidate-patrol.mjs +448 -0
  41. package/scripts/stable-candidate-policy.mjs +25 -0
  42. package/scripts/stable-candidate-qualification.mjs +239 -0
@@ -303,6 +303,15 @@ Compatible work should still be batched until a stable release has a concrete
303
303
  consumer need. Changing the interval, canary set, attestors, product path
304
304
  boundary, or soak time is a reviewed policy change.
305
305
 
306
+ Repositories that want a predictable scheduled stable window can use
307
+ [`Stable Candidate Patrol`](stable-candidate-patrol.md). It persists each exact
308
+ alpha independently, qualifies it after repository-declared checks and soak,
309
+ and selects the newest qualified non-revoked candidate. A newer soaking alpha
310
+ does not invalidate an older qualified candidate. The selected tree enters the
311
+ existing strict `publish-gate/release/<line>/<version> -> release/<line>` PR
312
+ path, so scheduled selection changes release intent timing without weakening
313
+ source locks, review, verification, publish transactions, or passports.
314
+
306
315
  If release finalization is resumed after generated version-state bookkeeping was
307
316
  partially applied, Buildchain applies the same recovery rule: the current
308
317
  release head may be the generated commit, or a historical merge commit that
@@ -403,6 +412,13 @@ Consumers that want Buildchain to own the check wrapper can call
403
412
  `lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when
404
413
  either declaration is missing or the command exits non-zero.
405
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
+
406
422
  Typical consumer wrapper:
407
423
 
408
424
  ```yaml
@@ -445,6 +461,7 @@ patrol workflow family:
445
461
  | `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |
446
462
  | `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |
447
463
  | `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |
464
+ | `.github/workflows/stable-candidate-patrol.yml` | repository-selected release window | qualify immutable alpha candidates and open the exact source-lock stable PR |
448
465
 
449
466
  The cadence names describe patrol intensity, not release cadence:
450
467
 
@@ -453,6 +470,12 @@ The cadence names describe patrol intensity, not release cadence:
453
470
  - monthly patrol is for structural drift checks that should not block ordinary
454
471
  development velocity.
455
472
 
473
+ Stable Candidate Patrol is separate from those maintenance cadences because its
474
+ caller-owned cron is a release-intent window. Its candidate ledger and selection
475
+ remain generic; registry-specific side effects still run through the normal
476
+ repository `lifecycle.publish` transaction. See
477
+ [`stable-candidate-patrol.md`](stable-candidate-patrol.md).
478
+
456
479
  Consumers should schedule thin callers and keep their YAML declarative. For
457
480
  example:
458
481
 
@@ -162,6 +162,43 @@ Artifact names, manifest paths, expected artifact checks, publish-source locks,
162
162
  and aggregate summaries are the same in both jobs. The split is an execution
163
163
  detail, not a different artifact contract.
164
164
 
165
+ ## Native Rust Toolchains
166
+
167
+ Native lifecycle jobs can request an isolated Rust installation instead of
168
+ depending on a self-hosted runner user's PATH:
169
+
170
+ ```yaml
171
+ with:
172
+ setup-rust: true
173
+ rust-toolchain: "1.96.0"
174
+ rustup-dist-server: "https://rsproxy.cn"
175
+ rustup-update-root: "https://rsproxy.cn/rustup"
176
+ cargo-registry-index: ${{ vars.BUILDCHAIN_CARGO_REGISTRY_INDEX }}
177
+ ```
178
+
179
+ `setup-rust` defaults to `false`, so existing consumers are unchanged. When it
180
+ is enabled, Buildchain installs `rust-toolchain` before the install, build, and
181
+ verify lifecycle stages on every native matrix platform. Windows uses the
182
+ official rustup bootstrap through `cmd.exe` and `curl.exe` into runner-temporary
183
+ Cargo and rustup homes, so it works under a restrictive PowerShell execution
184
+ policy and the service account does not depend on another user's PATH or mutate
185
+ host toolchain state. Pin an exact toolchain for release builds. Linux container jobs continue
186
+ to obtain Rust from their digest-pinned image contract; Buildchain does not
187
+ mutate that container surface.
188
+
189
+ The rustup server inputs are optional and default to Rust's official servers.
190
+ Consumers behind a slow cross-border link may select a trusted transport mirror;
191
+ rustup still verifies the selected toolchain's distribution metadata and
192
+ component checksums.
193
+
194
+ `cargo-registry-index` is also optional. When set, Buildchain exposes it to
195
+ Cargo as `CARGO_REGISTRIES_CRATES_IO_INDEX` for every native lifecycle stage,
196
+ so a self-hosted runner can use a repository or organization variable without
197
+ committing private LAN topology to public workflow YAML. The endpoint must be a
198
+ crates.io-compatible index whose `config.json` download contract serves the
199
+ matching checksum-verified crate archives. An empty value preserves Cargo's
200
+ normal crates.io behavior.
201
+
165
202
  The container image provides `fnm` but does not preinstall Node. Buildchain uses
166
203
  `fnm` inside the container to install the requested `node-version` before it
167
204
  runs Buildchain runtime scripts or lifecycle actions.
@@ -289,6 +326,38 @@ also pass `buildchain-ref: v2-alpha` explicitly; official floating refs are
289
326
  ordinary channel selections and are allowed on pull requests and pushes. Train
290
327
  refs and exact SHAs remain trusted manual overrides.
291
328
 
329
+ ## Shifu Cache Profile Passthrough
330
+
331
+ Buildchain can carry one trusted Shifu cache-profile reference and its exact
332
+ digest into lifecycle execution. Its contract is an opaque reference and digest
333
+ only. This surface is deliberately opaque:
334
+ Buildchain does not fetch the profile, parse JSON, select cache services,
335
+ rewrite bindings, decide fallback, or emit Shifu resolution evidence. Those
336
+ semantics remain owned by the consumer's pinned Shifu implementation.
337
+
338
+ ```yaml
339
+ jobs:
340
+ build:
341
+ uses: kungfu-systems/buildchain/.github/workflows/build.yml@v2
342
+ with:
343
+ shifu-cache-profile-ref: ${{ vars.SHIFU_CACHE_PROFILE_REF }}
344
+ shifu-cache-profile-digest: ${{ vars.SHIFU_CACHE_PROFILE_DIGEST }}
345
+ ```
346
+
347
+ The reusable workflow passes the pair as `SHIFU_CACHE_PROFILE_REF` and
348
+ `SHIFU_CACHE_PROFILE_DIGEST` to install, build, and verify lifecycle commands.
349
+ The consumer must invoke its Shifu cache-aware execution surface. An empty pair
350
+ preserves existing behavior; a consumer Shifu should fail closed when exactly
351
+ one value is present or the resolved bytes do not match the expected digest.
352
+
353
+ Use trusted repository or organization variables rather than PR-controlled
354
+ files for private/LAN references. The variables must remain secret-free; any
355
+ credentials use a separate provider-approved secret surface and must not be
356
+ embedded in the profile reference. This passthrough is separate from
357
+ Buildchain's locked source checkout cache below: Buildchain owns checkout
358
+ transport and source identity, while Shifu owns post-checkout execution cache
359
+ bindings and receipts.
360
+
292
361
  ## Locked Source Checkout Cache
293
362
 
294
363
  Self-hosted runners that build large repositories can opt into a locked checkout
@@ -0,0 +1,188 @@
1
+ ---
2
+ status: preview
3
+ period: ongoing
4
+ theme: stable-candidate-patrol
5
+ doc_type: architecture-and-usage
6
+ source_level: local-files
7
+ confidence: high
8
+ sensitivity: public
9
+ evidence_grade: A
10
+ review_state: self-reviewed
11
+ last_reviewed: 2026-07-11
12
+ ai_provenance:
13
+ model_family: GPT-5
14
+ product: Codex
15
+ generated_at: 2026-07-11
16
+ visible_context: Buildchain candidate passport, stable gate, Patrol, publish transaction, exact source-lock PR contracts, tests, and user consensus.
17
+ invisible_context_boundary: No credentials, private logs, or unpublished consumer content were used.
18
+ ---
19
+
20
+ # Stable Candidate Patrol
21
+
22
+ Buildchain can treat every exact alpha as an independent stable candidate. A
23
+ new alpha creates a new candidate; it does not silently revoke an older alpha
24
+ that already completed its checks and soak interval.
25
+
26
+ ## Candidate lifecycle
27
+
28
+ The durable ledger contract is `kungfu-buildchain-stable-candidate-ledger`:
29
+
30
+ ```text
31
+ registered -> soaking -> qualified -> promoted
32
+ \-> revoked
33
+ ```
34
+
35
+ Each entry binds an exact alpha version to one immutable commit SHA. Registering
36
+ the same version at another SHA fails closed. Qualification records the required
37
+ checks, their completion times, the derived soak start, and elapsed time.
38
+
39
+ Scheduled selection chooses the newest `qualified` candidate that is neither
40
+ `revoked` nor already `promoted`. A newer alpha that is still `soaking` does not
41
+ hide an older qualified candidate. Once a stable version is promoted, remaining
42
+ alphas for that exact stable version are closed because that immutable stable
43
+ version has been consumed; their later product changes continue through the
44
+ next patch alpha prepared by the normal release transaction.
45
+
46
+ For Buildchain's own release line, successful Alpha Self-Dogfood starts an
47
+ idempotent qualification producer for the exact alpha SHA. It dispatches the
48
+ existing `Build Surface Fixture` at the immutable exact tag, runs the existing
49
+ `site-libkungfu-dev` no-apply canary with the exact SHA, and writes the declared
50
+ commit-status attestation only after that authoritative canary succeeds. The
51
+ producer creates evidence only: Patrol still owns qualification and selection,
52
+ and the normal source-lock PR, stable gate, transaction, and branch protections
53
+ remain mandatory.
54
+
55
+ Cross-repository dispatch and repository-local attestation use separate tokens.
56
+ The promotion token can start the no-apply consumer workflow, while the
57
+ repository-scoped Actions token writes the status as `github-actions[bot]` only
58
+ after the producer has observed the successful authoritative workflow run. The
59
+ stable gate still verifies the workflow identity, exact runtime SHA, target URL,
60
+ and allowed attestor before accepting that status.
61
+
62
+ ## Repository policy
63
+
64
+ Declare the default once in `.buildchain/buildchain.toml`:
65
+
66
+ ```toml
67
+ [release.stable]
68
+ strategy = "latest-qualified-alpha"
69
+ timezone = "Asia/Shanghai"
70
+ publish_at = "03:00"
71
+ minimum_soak_seconds = 3600
72
+ required_checks = [
73
+ "alpha-release",
74
+ "workflow:Build",
75
+ "status:buildchain-canary/consumer",
76
+ ]
77
+ auto_promote = true
78
+ auto_merge = true
79
+ ```
80
+
81
+ Check identifiers use these forms:
82
+
83
+ - `alpha-release`: the exact GitHub prerelease publication fact;
84
+ - `workflow:<name>`: a successful Actions workflow run on the exact candidate SHA;
85
+ - `status:<context>`: a successful commit status on the exact candidate SHA;
86
+ - an unprefixed value: an exact status context or check-run name.
87
+
88
+ Candidate discovery uses immutable exact alpha Git tags. When a repository also
89
+ publishes GitHub prereleases, `alpha-release` binds qualification to that public
90
+ release fact. Repositories that intentionally disable GitHub Releases omit
91
+ `alpha-release` and declare their own exact-SHA workflow/status evidence; tag
92
+ commit time remains the earliest possible soak start.
93
+
94
+ `publish_at` and `timezone` are the auditable policy declaration. GitHub only
95
+ starts scheduled workflows from caller-owned cron, so the thin caller keeps the
96
+ matching UTC trigger:
97
+
98
+ ```yaml
99
+ name: Stable Candidate Patrol
100
+
101
+ on:
102
+ schedule:
103
+ - cron: "0 19 * * *" # 03:00 Asia/Shanghai
104
+ workflow_dispatch:
105
+ inputs:
106
+ release-now:
107
+ description: Exact alpha selected by explicit human authority
108
+ required: false
109
+ default: ""
110
+
111
+ permissions:
112
+ contents: write
113
+ pull-requests: write
114
+ checks: read
115
+ statuses: read
116
+
117
+ jobs:
118
+ stable:
119
+ uses: kungfu-systems/buildchain/.github/workflows/stable-candidate-patrol.yml@v2
120
+ with:
121
+ release-now: ${{ inputs.release-now }}
122
+ dry-run: false
123
+ secrets:
124
+ promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
125
+ ```
126
+
127
+ The promotion token must be repository-owned and capable of creating the
128
+ 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
132
+ generated PR may use auto-merge, but it never bypasses the target branch checks.
133
+
134
+ ## Exact-source stable promotion
135
+
136
+ For a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:
137
+
138
+ ```text
139
+ publish-gate/release/v2/v2.12/2.12.0-alpha.4
140
+ ```
141
+
142
+ and opens it against `release/v2/v2.12`. This is an existing strict Buildchain
143
+ governance path. The PR freezes the qualified candidate even if `v2.12-alpha`
144
+ or `alpha/v2/v2.12` has already moved to alpha.5. Normal Verify,
145
+ release-candidate resolution, source-tree equivalence, publish transaction,
146
+ passport, registry, tag, and floating-ref checks still run.
147
+
148
+ ## Hold, revoke, and immediate release
149
+
150
+ Persistent repository controls can be supplied as reusable-workflow inputs or
151
+ repository variables:
152
+
153
+ ```text
154
+ BUILDCHAIN_STABLE_HOLD=true
155
+ BUILDCHAIN_STABLE_HOLD_REASON=release freeze
156
+ BUILDCHAIN_STABLE_REVOKED_ALPHA_VERSIONS=2.12.0-alpha.5,2.12.0-alpha.7
157
+ BUILDCHAIN_STABLE_REVOKE_REASON=consumer regression
158
+ ```
159
+
160
+ Revocation is explicit evidence; publishing a newer alpha alone is not
161
+ revocation. A manual `workflow_dispatch` `release-now` chooses one exact,
162
+ non-revoked candidate immediately. It may bypass the scheduled soak decision,
163
+ but cannot change candidate SHA, reuse a consumed stable version, or bypass the
164
+ source-lock PR and publish transaction.
165
+
166
+ For Buildchain's own stable gate, Patrol automatically projects that explicit
167
+ human decision into the exact-candidate `BUILDCHAIN_STABLE_RELEASE_NOW` and
168
+ reason variables. A later Patrol run removes them after it observes the public
169
+ stable release. This is an internal compatibility projection, not a manual user
170
+ step; the durable authority record remains the candidate ledger entry and PR.
171
+
172
+ ## Durable recovery
173
+
174
+ The default ledger ref is derived from the release line, for example:
175
+
176
+ ```text
177
+ buildchain/candidate-ledger/v2/v2.12
178
+ ```
179
+
180
+ It stores `.buildchain/stable-candidate-ledger.json`. Patrol runs are serialized
181
+ per repository and release line. Repeated runs reuse the same exact source-lock
182
+ branch and PR, while later runs observe the public stable release and mark the
183
+ candidate `promoted`.
184
+
185
+ When Patrol is enabled after a stable version already exists, it reconstructs
186
+ that consumed patch from GitHub Release truth and closes historical alpha
187
+ candidates for the same stable version. It never attempts to republish an
188
+ already claimed exact stable version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.12.0",
3
+ "version": "2.12.1-alpha.10",
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",
@@ -30,6 +30,7 @@
30
30
  "./publication-package": "./packages/core/publication-package.js",
31
31
  "./kfd-gate": "./packages/core/kfd-gate.js",
32
32
  "./release-candidate": "./packages/core/release-candidate.js",
33
+ "./stable-candidate-ledger": "./packages/core/stable-candidate-ledger.js",
33
34
  "./release-passport": "./packages/core/release-passport.js",
34
35
  "./release-propagation": "./packages/core/release-propagation.js",
35
36
  "./surface-manifest": "./packages/core/surface-manifest.js",
@@ -83,6 +84,7 @@
83
84
  "check:site": "node scripts/generate-site-bundle.mjs --check",
84
85
  "test:unit": "node --test tests/*.test.mjs",
85
86
  "build": "pnpm -r --filter \"./actions/**\" build",
87
+ "binary:build": "node scripts/build-standalone-binary.mjs",
86
88
  "package": "npm pack --dry-run --json --registry=https://registry.npmjs.org/",
87
89
  "pack:check": "npm pack --dry-run --json --registry=https://registry.npmjs.org/"
88
90
  },
@@ -19,6 +19,7 @@ const SUPPORTED_PUBLISH_MODES = new Set(["publish-final-version", "promote-exist
19
19
  const SUPPORTED_PUBLISH_AUTH = new Set(["trusted-publishing", "npm-token"]);
20
20
  const SUPPORTED_PUBLISH_KINDS = new Set(["npm-package", "npm-paper-package"]);
21
21
  const SUPPORTED_PACKAGE_SET_ORDER = new Set(["as-provided", "platforms-first-main-last"]);
22
+ const SUPPORTED_STABLE_RELEASE_STRATEGIES = new Set(["manual", "latest-qualified-alpha"]);
22
23
  const SUPPORTED_NATIVE_COMPILER_CACHE = new Set(["auto", "ccache", "sccache", "none"]);
23
24
  const WEB_SURFACE_CHANNELS = ["preview", "staging", "production"];
24
25
  const SUPPORTED_CHANNEL_VISIBILITY = new Set(["ephemeral", "protected", "public", "internal"]);
@@ -180,6 +181,9 @@ export function normalizeBuildchainConfig(config) {
180
181
  if (normalized.publish !== undefined) {
181
182
  normalized.publish = normalizePublishSection(normalized.publish);
182
183
  }
184
+ if (normalized.release !== undefined) {
185
+ normalized.release = normalizeReleaseSection(normalized.release);
186
+ }
183
187
  if (normalized.channels !== undefined) {
184
188
  normalized.channels = normalizeChannelsSection(normalized.channels, normalized.project);
185
189
  }
@@ -215,6 +219,59 @@ export function normalizeBuildchainConfig(config) {
215
219
  return normalized;
216
220
  }
217
221
 
222
+ function normalizeReleaseSection(release) {
223
+ assertPlainObject(release, "release");
224
+ const normalized = { ...release };
225
+ if (release.stable !== undefined) {
226
+ assertPlainObject(release.stable, "release.stable");
227
+ const strategy = release.stable.strategy === undefined
228
+ ? "manual"
229
+ : assertString(release.stable.strategy, "release.stable.strategy");
230
+ if (!SUPPORTED_STABLE_RELEASE_STRATEGIES.has(strategy)) {
231
+ throw new Error("release.stable.strategy must be one of manual or latest-qualified-alpha");
232
+ }
233
+ const minimumSoakSeconds = release.stable.minimum_soak_seconds === undefined
234
+ ? 3600
235
+ : Number(release.stable.minimum_soak_seconds);
236
+ if (!Number.isInteger(minimumSoakSeconds) || minimumSoakSeconds < 0) {
237
+ throw new Error("release.stable.minimum_soak_seconds must be a non-negative integer");
238
+ }
239
+ normalized.stable = {
240
+ strategy,
241
+ timezone: release.stable.timezone === undefined
242
+ ? "UTC"
243
+ : assertString(release.stable.timezone, "release.stable.timezone"),
244
+ publishAt: release.stable.publish_at === undefined
245
+ ? "03:00"
246
+ : assertString(release.stable.publish_at, "release.stable.publish_at"),
247
+ minimumSoakSeconds,
248
+ requiredChecks: normalizeStringArray(release.stable.required_checks, "release.stable.required_checks"),
249
+ ledgerRef: release.stable.ledger_ref === undefined
250
+ ? ""
251
+ : assertString(release.stable.ledger_ref, "release.stable.ledger_ref"),
252
+ autoPromote: optionalBoolean(release.stable.auto_promote, strategy === "latest-qualified-alpha"),
253
+ autoMerge: optionalBoolean(release.stable.auto_merge, false),
254
+ };
255
+ if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(normalized.stable.publishAt)) {
256
+ throw new Error("release.stable.publish_at must be HH:MM in 24-hour time");
257
+ }
258
+ }
259
+ return normalized;
260
+ }
261
+
262
+ export function getStableReleasePolicy(loadedConfig) {
263
+ return loadedConfig?.config?.release?.stable || {
264
+ strategy: "manual",
265
+ timezone: "UTC",
266
+ publishAt: "03:00",
267
+ minimumSoakSeconds: 3600,
268
+ requiredChecks: [],
269
+ ledgerRef: "",
270
+ autoPromote: false,
271
+ autoMerge: false,
272
+ };
273
+ }
274
+
218
275
  function normalizeFactsSection(facts) {
219
276
  assertPlainObject(facts, "facts");
220
277
  return {
@@ -1389,6 +1446,7 @@ export function validateBuildchainConfig(
1389
1446
  })),
1390
1447
  lifecycleStages,
1391
1448
  publish: loadedConfig.config.publish,
1449
+ release: loadedConfig.config.release,
1392
1450
  facts: loadedConfig.config.facts,
1393
1451
  publication: loadedConfig.config.publication,
1394
1452
  };
@@ -6,6 +6,7 @@ import {
6
6
  BUILDCHAIN_PUBLIC_SURFACE_AUDIT_CONTRACT,
7
7
  collectPublicSurfaceReverseAudit,
8
8
  } from "./public-surface-audit.js";
9
+ import { normalizeKfd3DistributionDeclaration } from "./kfd3-surface-register.js";
9
10
 
10
11
  export const BUILDCHAIN_KFD_CLAIM_REGISTRY_CONTRACT = "kungfu-buildchain-kfd-claim-registry";
11
12
  export const BUILDCHAIN_KFD_COLLABORATION_INTERFACE_CONTRACT = "kungfu-buildchain-kfd-collaboration-interface";
@@ -307,6 +308,23 @@ export function createBuildchainPublicClaimDefinitions() {
307
308
  "dist/site/public-surface-audit.json",
308
309
  ],
309
310
  },
311
+ {
312
+ id: "claim:buildchain-shifu-kfd-discovery",
313
+ claim: "Buildchain exposes a versioned repository-layout discovery contract and KFD-3 distribution declarations so Shifu can locate the registry and determine jurisdiction without copying Buildchain layout paths.",
314
+ sourcePaths: [
315
+ "packages/core/buildchain-layout.js",
316
+ "packages/core/buildchain-kfd-claims.js",
317
+ "packages/core/kfd3-surface-register.js",
318
+ "bin/buildchain.mjs",
319
+ "scripts/build-standalone-binary.mjs",
320
+ "docs/kfd-support.md",
321
+ ],
322
+ artifactPaths: [
323
+ "dist/site/kfd-claims.json",
324
+ "dist/site/cli-registry.json",
325
+ "dist/site/node-api-registry.json",
326
+ ],
327
+ },
310
328
  {
311
329
  id: "claim:buildchain-readme-badge-facts",
312
330
  claim: "Buildchain README status badges are generated from repository-owned facts and verified release-passport evidence, not hand-maintained README prose.",
@@ -431,6 +449,37 @@ export function createBuildchainKfdSurfaceRegistry({ root = process.cwd() } = {}
431
449
  ];
432
450
  const siteConsumptionContracts = SITE_CONTRACT_FILES.map((relPath) => surface(`site:${relPath}`, "site-consumption-contract", relPath));
433
451
  const controlSurfaces = WORKFLOW_AND_ACTION_FILES.map((relPath) => surface(`control:${relPath}`, relPath.includes("actions/") ? "action" : "workflow", relPath));
452
+ const standaloneDistribution = surface(
453
+ "distribution:buildchain-standalone",
454
+ "binary-distribution",
455
+ "scripts/build-standalone-binary.mjs",
456
+ {
457
+ name: "Buildchain standalone binary distribution",
458
+ artifactPath: "dist/binary",
459
+ evidencePath: ".github/workflows/binary-distribution.yml",
460
+ distribution: normalizeKfd3DistributionDeclaration({
461
+ registrar: "shifu",
462
+ tasks: ["binary:build"],
463
+ artifacts: [
464
+ {
465
+ kind: "binary",
466
+ platform: "linux",
467
+ pathGlob: "dist/binary/buildchain-x86_64-unknown-linux-gnu.tar.gz",
468
+ },
469
+ {
470
+ kind: "binary",
471
+ platform: "macos",
472
+ pathGlob: "dist/binary/buildchain-aarch64-apple-darwin.tar.gz",
473
+ },
474
+ {
475
+ kind: "binary",
476
+ platform: "windows",
477
+ pathGlob: "dist/binary/buildchain-x86_64-pc-windows-msvc.zip",
478
+ },
479
+ ],
480
+ }, { surfaceId: "distribution:buildchain-standalone" }),
481
+ },
482
+ );
434
483
  const reverseEnumeratedSurfaces = uniqueById([
435
484
  ...reverseAudit.enumerated.cliCommands.map((entry) => surface(`cli:${entry.id}`, "cli-command", "bin/buildchain.mjs", {
436
485
  name: entry.usage,
@@ -463,8 +512,8 @@ export function createBuildchainKfdSurfaceRegistry({ root = process.cwd() } = {}
463
512
  siteConsumptionContracts,
464
513
  reverseEnumeratedSurfaces,
465
514
  },
466
- additionalSurfaces: controlSurfaces,
467
- publicSurfaceCount: docs.length + schemas.length + standardsMetadata.length + exports.length + siteConsumptionContracts.length + reverseEnumeratedSurfaces.length + controlSurfaces.length,
515
+ additionalSurfaces: [...controlSurfaces, standaloneDistribution],
516
+ publicSurfaceCount: docs.length + schemas.length + standardsMetadata.length + exports.length + siteConsumptionContracts.length + reverseEnumeratedSurfaces.length + controlSurfaces.length + 1,
468
517
  };
469
518
  }
470
519
 
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  export const BUILDCHAIN_DIR = ".buildchain";
5
+ export const BUILDCHAIN_VERSION_PIN_PATH = ".buildchain-version";
6
+ export const BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT = "kungfu-buildchain-layout-discovery";
5
7
  export const BUILDCHAIN_CONFIG_PATH = ".buildchain/buildchain.toml";
6
8
  export const LEGACY_BUILDCHAIN_CONFIG_PATH = "buildchain.toml";
7
9
  export const BUILDCHAIN_CONTRACT_LOCK_PATH = ".buildchain/contract-lock.json";
@@ -94,6 +96,50 @@ export function resolveReleasePassportPath(cwd = process.cwd()) {
94
96
  ]);
95
97
  }
96
98
 
99
+ export function createBuildchainLayoutDiscovery({
100
+ cwd = process.cwd(),
101
+ buildchainVersion = "",
102
+ } = {}) {
103
+ const resolvedCwd = path.resolve(cwd);
104
+ const configPath = resolveBuildchainConfigPath(resolvedCwd);
105
+ const kfd3RegistryPath = resolveKfd3SurfaceRegistryPath(resolvedCwd);
106
+ return {
107
+ schemaVersion: 1,
108
+ contract: BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT,
109
+ buildchain: {
110
+ version: String(buildchainVersion || ""),
111
+ versionPinPath: BUILDCHAIN_VERSION_PIN_PATH,
112
+ },
113
+ repository: {
114
+ root: resolvedCwd,
115
+ managed: fs.existsSync(repoPath(resolvedCwd, configPath))
116
+ || fs.existsSync(repoPath(resolvedCwd, BUILDCHAIN_VERSION_PIN_PATH))
117
+ || fs.existsSync(repoPath(resolvedCwd, kfd3RegistryPath)),
118
+ configPath,
119
+ },
120
+ kfd: {
121
+ root: BUILDCHAIN_KFD_ROOT,
122
+ registries: {
123
+ "kfd-3": {
124
+ standard: "kfd-3",
125
+ schemaVersion: 1,
126
+ path: kfd3RegistryPath,
127
+ canonicalPath: BUILDCHAIN_KFD3_SURFACE_REGISTRY_PATH,
128
+ exists: fs.existsSync(repoPath(resolvedCwd, kfd3RegistryPath)),
129
+ },
130
+ },
131
+ },
132
+ shifu: {
133
+ jurisdiction: {
134
+ source: "kfd-3-distribution-declaration",
135
+ registryStandard: "kfd-3",
136
+ field: "surfaces[].distribution.registrar",
137
+ value: "shifu",
138
+ },
139
+ },
140
+ };
141
+ }
142
+
97
143
  export function discoverBuildchainRepoFiles(cwd = process.cwd()) {
98
144
  const files = [
99
145
  {
@@ -3,6 +3,7 @@ export {
3
3
  getLifecycleStage,
4
4
  getNativeDiagnosticsProfile,
5
5
  getPublishContract,
6
+ getStableReleasePolicy,
6
7
  getVersionStrategy,
7
8
  loadBuildchainConfig,
8
9
  loadConfiguredAnchorManifest,
@@ -13,6 +14,20 @@ export {
13
14
  validateBuildchainConfig,
14
15
  } from "./buildchain-config.js";
15
16
 
17
+ export {
18
+ STABLE_CANDIDATE_LEDGER_CONTRACT,
19
+ STABLE_CANDIDATE_STATES,
20
+ createStableCandidateLedger,
21
+ markStableCandidatePromoted,
22
+ normalizeStableCandidateLedger,
23
+ qualifyStableCandidate,
24
+ registerStableCandidate,
25
+ revokeStableCandidate,
26
+ selectStableCandidate,
27
+ setStableCandidateHold,
28
+ stableCandidatePromotionRefs,
29
+ } from "./stable-candidate-ledger.js";
30
+
16
31
  export {
17
32
  assertPackageManager,
18
33
  commandForKungfuUpgrade,
@@ -277,6 +292,8 @@ export {
277
292
  BUILDCHAIN_CONFIG_PATH,
278
293
  BUILDCHAIN_CONTRACT_LOCK_PATH,
279
294
  BUILDCHAIN_DIR,
295
+ BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT,
296
+ BUILDCHAIN_VERSION_PIN_PATH,
280
297
  BUILDCHAIN_GENERATED_DIRS,
281
298
  BUILDCHAIN_KFD_ROOT,
282
299
  BUILDCHAIN_KFD1_CONTRACT_WORLD_WITNESS_PATH,
@@ -302,6 +319,7 @@ export {
302
319
  LEGACY_BUILDCHAIN_KFD3_SURFACE_REGISTRY_PATHS,
303
320
  LEGACY_BUILDCHAIN_RELEASE_PASSPORT_PATH,
304
321
  discoverBuildchainRepoFiles,
322
+ createBuildchainLayoutDiscovery,
305
323
  migrateBuildchainLayout,
306
324
  planBuildchainLayoutMigration,
307
325
  resolveBuildchainConfigPath,
@@ -344,6 +362,7 @@ export {
344
362
  auditKfd3Surfaces,
345
363
  createKfd3SurfaceWitness,
346
364
  detectKfd3Surfaces,
365
+ normalizeKfd3DistributionDeclaration,
347
366
  queryKfd3Capabilities,
348
367
  readKfd3SurfaceRegistry,
349
368
  registerKfd3Surfaces,