@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
@@ -67,6 +67,63 @@ function normalizeKinds(kinds = []) {
67
67
  return [...new Set(selected.map(normalizeKind))].sort();
68
68
  }
69
69
 
70
+ export function normalizeKfd3DistributionDeclaration(distribution, { surfaceId = "surface" } = {}) {
71
+ if (!distribution || typeof distribution !== "object" || Array.isArray(distribution)) {
72
+ throw new Error(`KFD-3 surface ${surfaceId} distribution must be an object`);
73
+ }
74
+ const registrar = String(distribution.registrar || "").trim();
75
+ if (!registrar) {
76
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.registrar is required`);
77
+ }
78
+ const tasks = Array.isArray(distribution.tasks)
79
+ ? [...new Set(distribution.tasks.map((entry) => String(entry || "").trim()).filter(Boolean))]
80
+ : [];
81
+ if (tasks.length === 0) {
82
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.tasks must contain at least one task`);
83
+ }
84
+ const sourceArtifacts = Array.isArray(distribution.artifacts) ? distribution.artifacts : [];
85
+ if (sourceArtifacts.length === 0) {
86
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts must contain at least one artifact`);
87
+ }
88
+ const artifacts = sourceArtifacts.map((artifact, index) => {
89
+ if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
90
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}] must be an object`);
91
+ }
92
+ const normalized = {
93
+ ...artifact,
94
+ kind: String(artifact.kind || "").trim(),
95
+ platform: String(artifact.platform || "").trim(),
96
+ pathGlob: String(artifact.pathGlob || "").trim(),
97
+ };
98
+ for (const field of ["kind", "platform", "pathGlob"]) {
99
+ if (!normalized[field]) {
100
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}].${field} is required`);
101
+ }
102
+ }
103
+ if (artifact.sha256 !== undefined) {
104
+ normalized.sha256 = String(artifact.sha256 || "").trim().toLowerCase();
105
+ if (!/^[a-f0-9]{64}$/.test(normalized.sha256)) {
106
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}].sha256 must be 64 lowercase hex characters`);
107
+ }
108
+ }
109
+ return normalized;
110
+ });
111
+ return {
112
+ ...distribution,
113
+ registrar,
114
+ tasks,
115
+ artifacts,
116
+ };
117
+ }
118
+
119
+ function normalizeRegistrySurface(entry) {
120
+ if (!entry?.distribution) return entry;
121
+ return {
122
+ ...entry,
123
+ distribution: normalizeKfd3DistributionDeclaration(entry.distribution, { surfaceId: entry.id || "surface" }),
124
+ };
125
+ }
126
+
70
127
  function stableId(value) {
71
128
  return String(value || "")
72
129
  .toLowerCase()
@@ -366,7 +423,7 @@ export function readKfd3SurfaceRegistry({ cwd = process.cwd(), registryPath = ""
366
423
  contract: registry.contract || KFD3_SURFACE_REGISTRY_CONTRACT,
367
424
  product: registry.product || {},
368
425
  registryPath: registry.registryPath || effectiveRegistryPath,
369
- surfaces: Array.isArray(registry.surfaces) ? registry.surfaces : [],
426
+ surfaces: Array.isArray(registry.surfaces) ? registry.surfaces.map(normalizeRegistrySurface) : [],
370
427
  policy: registry.policy || {},
371
428
  };
372
429
  }
@@ -379,7 +436,7 @@ export function writeKfd3SurfaceRegistry({ cwd = process.cwd(), registryPath = "
379
436
  product: registry.product || {},
380
437
  registryPath: effectiveRegistryPath,
381
438
  surfaces: uniqueSurfaces(registry.surfaces || []).map((entry) => ({
382
- ...entry,
439
+ ...normalizeRegistrySurface(entry),
383
440
  state: entry.state || "declared",
384
441
  declaration: entry.declaration || {
385
442
  owner: "product",
@@ -0,0 +1,276 @@
1
+ export const STABLE_CANDIDATE_LEDGER_CONTRACT = "kungfu-buildchain-stable-candidate-ledger";
2
+ export const STABLE_CANDIDATE_STATES = Object.freeze([
3
+ "soaking",
4
+ "qualified",
5
+ "revoked",
6
+ "promoted",
7
+ ]);
8
+
9
+ function text(value = "") {
10
+ return String(value ?? "").trim();
11
+ }
12
+
13
+ function iso(value, label) {
14
+ const normalized = text(value);
15
+ const milliseconds = Date.parse(normalized);
16
+ if (!normalized || !Number.isFinite(milliseconds)) {
17
+ throw new Error(`${label} must be an ISO-8601 timestamp`);
18
+ }
19
+ return new Date(milliseconds).toISOString();
20
+ }
21
+
22
+ function exactAlpha(version) {
23
+ const normalized = text(version).replace(/^v/, "");
24
+ const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)-alpha\.(\d+)$/);
25
+ if (!match) {
26
+ throw new Error(`candidate version must be an exact alpha, got ${version || "<empty>"}`);
27
+ }
28
+ return {
29
+ version: normalized,
30
+ stableVersion: `${match[1]}.${match[2]}.${match[3]}`,
31
+ order: match.slice(1).map(Number),
32
+ };
33
+ }
34
+
35
+ function sha(value, label = "candidate sha") {
36
+ const normalized = text(value).toLowerCase();
37
+ if (!/^[0-9a-f]{40}$/.test(normalized)) {
38
+ throw new Error(`${label} must be a 40-character commit SHA`);
39
+ }
40
+ return normalized;
41
+ }
42
+
43
+ function clone(value) {
44
+ return JSON.parse(JSON.stringify(value));
45
+ }
46
+
47
+ function compareCandidates(left, right) {
48
+ const a = exactAlpha(left.version).order;
49
+ const b = exactAlpha(right.version).order;
50
+ for (let index = 0; index < a.length; index += 1) {
51
+ if (a[index] !== b[index]) return a[index] - b[index];
52
+ }
53
+ return left.publishedAt.localeCompare(right.publishedAt);
54
+ }
55
+
56
+ export function createStableCandidateLedger({ repository, targetBranch, now = new Date().toISOString() } = {}) {
57
+ const normalizedRepository = text(repository);
58
+ const normalizedTargetBranch = text(targetBranch).replace(/^refs\/heads\//, "");
59
+ if (!/^[^/\s]+\/[^/\s]+$/.test(normalizedRepository)) {
60
+ throw new Error(`repository must be owner/repo, got ${repository || "<empty>"}`);
61
+ }
62
+ if (!/^release\/v\d+\/v\d+\.\d+$/.test(normalizedTargetBranch)) {
63
+ throw new Error(`targetBranch must be release/vN/vN.M, got ${targetBranch || "<empty>"}`);
64
+ }
65
+ return {
66
+ schemaVersion: 1,
67
+ contract: STABLE_CANDIDATE_LEDGER_CONTRACT,
68
+ repository: normalizedRepository,
69
+ targetBranch: normalizedTargetBranch,
70
+ hold: { enabled: false, reason: "", updatedAt: iso(now, "now") },
71
+ candidates: [],
72
+ updatedAt: iso(now, "now"),
73
+ };
74
+ }
75
+
76
+ export function normalizeStableCandidateLedger(input, expected = {}) {
77
+ const ledger = clone(input || {});
78
+ if (ledger.contract !== STABLE_CANDIDATE_LEDGER_CONTRACT || Number(ledger.schemaVersion) !== 1) {
79
+ throw new Error(`stable candidate ledger must use ${STABLE_CANDIDATE_LEDGER_CONTRACT} schemaVersion 1`);
80
+ }
81
+ if (expected.repository && ledger.repository !== expected.repository) {
82
+ throw new Error(`stable candidate ledger repository mismatch: ${ledger.repository} != ${expected.repository}`);
83
+ }
84
+ if (expected.targetBranch && ledger.targetBranch !== expected.targetBranch) {
85
+ throw new Error(`stable candidate ledger targetBranch mismatch: ${ledger.targetBranch} != ${expected.targetBranch}`);
86
+ }
87
+ ledger.hold ||= { enabled: false, reason: "", updatedAt: ledger.updatedAt };
88
+ ledger.candidates = (ledger.candidates || []).map((candidate) => {
89
+ const parsed = exactAlpha(candidate.version);
90
+ const state = text(candidate.state);
91
+ if (!STABLE_CANDIDATE_STATES.includes(state)) {
92
+ throw new Error(`unsupported candidate state ${state || "<empty>"}`);
93
+ }
94
+ return {
95
+ ...candidate,
96
+ version: parsed.version,
97
+ stableVersion: parsed.stableVersion,
98
+ sha: sha(candidate.sha),
99
+ publishedAt: iso(candidate.publishedAt, `candidate ${parsed.version} publishedAt`),
100
+ state,
101
+ };
102
+ });
103
+ return ledger;
104
+ }
105
+
106
+ export function registerStableCandidate(ledgerInput, candidateInput, { now = new Date().toISOString() } = {}) {
107
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
108
+ const parsed = exactAlpha(candidateInput.version);
109
+ const candidateSha = sha(candidateInput.sha);
110
+ const existing = ledger.candidates.find((candidate) => candidate.version === parsed.version);
111
+ if (existing) {
112
+ if (existing.sha !== candidateSha) {
113
+ throw new Error(`candidate ${parsed.version} is already bound to ${existing.sha}, not ${candidateSha}`);
114
+ }
115
+ return ledger;
116
+ }
117
+ ledger.candidates.push({
118
+ version: parsed.version,
119
+ stableVersion: parsed.stableVersion,
120
+ sha: candidateSha,
121
+ tag: `v${parsed.version}`,
122
+ publishedAt: iso(candidateInput.publishedAt, `candidate ${parsed.version} publishedAt`),
123
+ state: "soaking",
124
+ qualification: {
125
+ ok: false,
126
+ observedAt: "",
127
+ soakStartedAt: "",
128
+ soakElapsedSeconds: 0,
129
+ requiredSeconds: 0,
130
+ checks: [],
131
+ },
132
+ decision: { reason: "registered", updatedAt: iso(now, "now"), actor: text(candidateInput.actor) },
133
+ });
134
+ ledger.candidates.sort(compareCandidates);
135
+ ledger.updatedAt = iso(now, "now");
136
+ return ledger;
137
+ }
138
+
139
+ export function qualifyStableCandidate(
140
+ ledgerInput,
141
+ observation,
142
+ { minimumSoakSeconds = 3600, now = new Date().toISOString() } = {},
143
+ ) {
144
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
145
+ const version = exactAlpha(observation.version).version;
146
+ const candidate = ledger.candidates.find((entry) => entry.version === version);
147
+ if (!candidate) throw new Error(`candidate ${version} is not registered`);
148
+ if (candidate.sha !== sha(observation.sha)) {
149
+ throw new Error(`candidate ${version} observation SHA does not match ledger`);
150
+ }
151
+ if (["revoked", "promoted"].includes(candidate.state)) return ledger;
152
+
153
+ const checks = Array.isArray(observation.checks) ? observation.checks.map((check) => ({
154
+ id: text(check.id),
155
+ status: text(check.status),
156
+ completedAt: check.completedAt ? iso(check.completedAt, `check ${check.id} completedAt`) : "",
157
+ evidenceUrl: text(check.evidenceUrl),
158
+ })) : [];
159
+ const passed = checks.length > 0 && checks.every((check) => check.id && check.status === "pass" && check.completedAt);
160
+ const latestCheck = passed ? Math.max(...checks.map((check) => Date.parse(check.completedAt))) : NaN;
161
+ const soakStartedAt = passed
162
+ ? new Date(Math.max(Date.parse(candidate.publishedAt), latestCheck)).toISOString()
163
+ : "";
164
+ const elapsedSeconds = passed
165
+ ? Math.max(0, Math.floor((Date.parse(iso(now, "now")) - Date.parse(soakStartedAt)) / 1000))
166
+ : 0;
167
+ candidate.qualification = {
168
+ ok: passed && elapsedSeconds >= Number(minimumSoakSeconds),
169
+ observedAt: iso(now, "now"),
170
+ soakStartedAt,
171
+ soakElapsedSeconds: elapsedSeconds,
172
+ requiredSeconds: Number(minimumSoakSeconds),
173
+ checks,
174
+ };
175
+ candidate.state = candidate.qualification.ok ? "qualified" : "soaking";
176
+ candidate.decision = {
177
+ reason: candidate.qualification.ok ? "qualification-satisfied" : passed ? "soaking" : "checks-incomplete",
178
+ updatedAt: iso(now, "now"),
179
+ actor: text(observation.actor),
180
+ };
181
+ ledger.updatedAt = iso(now, "now");
182
+ return ledger;
183
+ }
184
+
185
+ export function revokeStableCandidate(ledgerInput, versionInput, { reason, actor = "", now = new Date().toISOString() } = {}) {
186
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
187
+ const version = exactAlpha(versionInput).version;
188
+ const candidate = ledger.candidates.find((entry) => entry.version === version);
189
+ if (!candidate) throw new Error(`candidate ${version} is not registered`);
190
+ if (candidate.state === "promoted") throw new Error(`promoted candidate ${version} cannot be revoked`);
191
+ if (!text(reason)) throw new Error("candidate revocation requires a reason");
192
+ candidate.state = "revoked";
193
+ candidate.decision = { reason: text(reason), actor: text(actor), updatedAt: iso(now, "now") };
194
+ ledger.updatedAt = iso(now, "now");
195
+ return ledger;
196
+ }
197
+
198
+ export function setStableCandidateHold(ledgerInput, enabled, { reason = "", now = new Date().toISOString() } = {}) {
199
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
200
+ if (enabled && !text(reason)) throw new Error("enabling stable candidate hold requires a reason");
201
+ ledger.hold = { enabled: Boolean(enabled), reason: text(reason), updatedAt: iso(now, "now") };
202
+ ledger.updatedAt = iso(now, "now");
203
+ return ledger;
204
+ }
205
+
206
+ export function selectStableCandidate(ledgerInput, { releaseNow = "", now = new Date().toISOString() } = {}) {
207
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
208
+ if (ledger.hold.enabled && !releaseNow) {
209
+ return { selected: false, reason: "repository-held", hold: ledger.hold };
210
+ }
211
+ if (releaseNow) {
212
+ const version = exactAlpha(releaseNow).version;
213
+ const candidate = ledger.candidates.find((entry) => entry.version === version);
214
+ if (!candidate) return { selected: false, reason: "release-now-candidate-missing", version };
215
+ if (["revoked", "promoted"].includes(candidate.state)) {
216
+ return { selected: false, reason: `release-now-candidate-${candidate.state}`, candidate };
217
+ }
218
+ return { selected: true, reason: "human-release-now", authority: "human", candidate };
219
+ }
220
+ const candidates = ledger.candidates
221
+ .filter((candidate) => candidate.state === "qualified")
222
+ .sort(compareCandidates);
223
+ const candidate = candidates.at(-1);
224
+ return candidate
225
+ ? { selected: true, reason: "latest-qualified", authority: "policy", candidate }
226
+ : { selected: false, reason: "no-qualified-candidate" };
227
+ }
228
+
229
+ export function markStableCandidatePromoted(
230
+ ledgerInput,
231
+ versionInput,
232
+ { stableTag = "", stableSha = "", now = new Date().toISOString() } = {},
233
+ ) {
234
+ const ledger = normalizeStableCandidateLedger(ledgerInput);
235
+ const version = exactAlpha(versionInput).version;
236
+ const candidate = ledger.candidates.find((entry) => entry.version === version);
237
+ if (!candidate) throw new Error(`candidate ${version} is not registered`);
238
+ if (!candidate.qualification?.ok && candidate.decision?.reason !== "human-release-now") {
239
+ throw new Error(`candidate ${version} is not qualified for promotion`);
240
+ }
241
+ candidate.state = "promoted";
242
+ candidate.promotion = {
243
+ stableTag: text(stableTag) || `v${candidate.stableVersion}`,
244
+ stableSha: stableSha ? sha(stableSha, "stable sha") : "",
245
+ promotedAt: iso(now, "now"),
246
+ };
247
+ candidate.decision = { reason: "promoted", actor: "", updatedAt: iso(now, "now") };
248
+ for (const entry of ledger.candidates) {
249
+ if (entry.version !== version && entry.stableVersion === candidate.stableVersion && entry.state !== "promoted") {
250
+ entry.state = "revoked";
251
+ entry.decision = {
252
+ reason: `stable-version-promoted-by:${version}`,
253
+ actor: "buildchain",
254
+ updatedAt: iso(now, "now"),
255
+ };
256
+ }
257
+ }
258
+ ledger.updatedAt = iso(now, "now");
259
+ return ledger;
260
+ }
261
+
262
+ export function stableCandidatePromotionRefs(candidateInput, targetBranch) {
263
+ const candidate = { ...candidateInput, ...exactAlpha(candidateInput.version) };
264
+ const normalizedTarget = text(targetBranch).replace(/^refs\/heads\//, "");
265
+ const match = normalizedTarget.match(/^release\/(v\d+)\/(v\d+\.\d+)$/);
266
+ if (!match) throw new Error(`targetBranch must be release/vN/vN.M, got ${targetBranch || "<empty>"}`);
267
+ if (`v${candidate.stableVersion.split(".").slice(0, 2).join(".")}` !== match[2]) {
268
+ throw new Error(`candidate ${candidate.version} does not belong to ${normalizedTarget}`);
269
+ }
270
+ return {
271
+ sourceRef: `publish-gate/release/${match[1]}/${match[2]}/${candidate.version}`,
272
+ targetRef: normalizedTarget,
273
+ exactAlphaTag: `v${candidate.version}`,
274
+ stableTag: `v${candidate.stableVersion}`,
275
+ };
276
+ }
@@ -176,9 +176,10 @@ function bundleCli({ cwd, tempDir, version, logger }) {
176
176
  sourcemap: false,
177
177
  dts: false,
178
178
  shims: true,
179
- noExternal: ["smol-toml"],
179
+ noExternal: ["smol-toml", /^@kungfu-tech\\/kfd(?:\\/|$)/],
180
180
  define: {
181
181
  "process.env.BUILDCHAIN_EMBEDDED_PACKAGE_VERSION": ${JSON.stringify(JSON.stringify(version || packageVersion(cwd)))},
182
+ "process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT": ${JSON.stringify(JSON.stringify("1"))},
182
183
  },
183
184
  };
184
185
  `);
@@ -683,6 +683,8 @@ for (const requiredSnippet of [
683
683
  "checkout-cache-mode",
684
684
  "BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE",
685
685
  "sourceCheckout",
686
+ "Shifu Cache Profile Passthrough",
687
+ "opaque reference and digest",
686
688
  ]) {
687
689
  if (!reusableBuildSurfaceDoc.includes(requiredSnippet)) {
688
690
  throw new Error(`reusable build surface doc missing contract lock snippet: ${requiredSnippet}`);
@@ -732,7 +734,8 @@ for (const requiredSnippet of [
732
734
  "../packages/core/logging.js",
733
735
  "standalone.cli-bundle.create",
734
736
  "BUILDCHAIN_EMBEDDED_PACKAGE_VERSION",
735
- "noExternal: [\"smol-toml\"]",
737
+ "BUILDCHAIN_EMBEDDED_ENTRYPOINT",
738
+ "noExternal: [\"smol-toml\",",
736
739
  "--macho-segment-name",
737
740
  "mainFormat: \"commonjs\"",
738
741
  "standalone.sea-blob.create",
@@ -319,6 +319,7 @@ const manualMetaById = new Map(Object.entries({
319
319
  "binary-distribution": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "stable", order: 110 },
320
320
  "publish-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator"], maturity: "stable", order: 120 },
321
321
  "release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
322
+ "stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
322
323
  "reusable-build-surface": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator"], maturity: "stable", order: 200 },
323
324
  "lifecycle-protocol": { capabilityGroup: "reusable-build", audience: ["consumer", "developer"], maturity: "stable", order: 210 },
324
325
  "runtime-train-validation": { capabilityGroup: "governance-versioning", audience: ["maintainer", "consumer"], maturity: "stable", order: 220 },
@@ -394,6 +395,7 @@ function cliCommandMeta(id) {
394
395
  "inspect-artifact": { group: "release-passport-trust", purpose: "Inspect artifact evidence." },
395
396
  "inspect-release": { group: "release-passport-trust", purpose: "Inspect release passport evidence." },
396
397
  kfd: { group: "kfd-trust", purpose: "Inspect KFD standards, schemas, and versioned KFD command families." },
398
+ layout: { group: "kfd-trust", purpose: "Return the versioned repository-layout and KFD registry discovery contract for tools such as Shifu." },
397
399
  "kfd-schema": { group: "kfd-trust", purpose: "Inspect KFD schema command families." },
398
400
  "kfd-schema-list": { group: "kfd-trust", purpose: "List machine-readable schemas exposed by the KFD package standards metadata." },
399
401
  "kfd-schema-show": { group: "kfd-trust", purpose: "Print a machine-readable KFD schema from the KFD package standards metadata." },
@@ -472,12 +474,13 @@ function nodeApiMeta(exportName) {
472
474
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
473
475
  "./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
474
476
  "./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
477
+ "./stable-candidate-ledger": { group: "governance-versioning", summary: "Immutable alpha candidate ledger, qualification, revocation, selection, and exact stable source-lock APIs." },
475
478
  "./release-propagation": { group: "site-and-propagation", summary: "Release propagation graph, plan, and exact upstream lock APIs." },
476
479
  "./release-line-bootstrap": { group: "governance-versioning", summary: "Semver release-line bootstrap planning and version-state APIs." },
477
480
  "./buildchain-contract": { group: "governance-versioning", summary: "Runtime contract world and compatibility digest APIs for floating-ref drift checks." },
478
481
  "./surface-manifest": { group: "site-and-propagation", summary: "Surface manifest timestamp and reproducibility policy APIs." },
479
482
  "./issue-reporting": { group: "observability-diagnostics", summary: "Buildchain-owned issue reporting API for workflow friction feedback." },
480
- "./buildchain-layout": { group: "kfd-trust", summary: "Canonical .buildchain repository layout constants, path resolution, and migration APIs." },
483
+ "./buildchain-layout": { group: "kfd-trust", summary: "Versioned Buildchain repository-layout discovery contract plus canonical .buildchain path resolution and migration APIs." },
481
484
  "./kfd": { group: "kfd-trust", summary: "Unified KFD standards, schema discovery, KFD-1/KFD-2/KFD-3 grouped APIs, KFD-4 schema discovery, upstream KFD aggregate facts, and Buildchain KFD claim helpers." },
482
485
  "./public-surface-audit": { group: "kfd-trust", summary: "Reverse audit APIs for CLI, workflow, action, site page, and documentation command surfaces." },
483
486
  "./kfd-gate": { group: "kfd-trust", summary: "KFD-1/KFD-2/KFD-3 release gate evidence and validation APIs." },
@@ -747,6 +750,7 @@ function buildSiteBundle() {
747
750
  "docs/kfd-support.md",
748
751
  "docs/reusable-build-surface.md",
749
752
  "docs/release-candidate.md",
753
+ "docs/stable-candidate-patrol.md",
750
754
  "docs/release-governance.md",
751
755
  "docs/release-passport.md",
752
756
  "docs/publish-transaction.md",
@@ -827,6 +831,9 @@ function buildSiteBundle() {
827
831
  ["buildchain-patrol-daily", "repository-patrol"],
828
832
  ["buildchain-patrol-weekly", "repository-patrol"],
829
833
  ["buildchain-patrol-monthly", "repository-patrol"],
834
+ ["stable-candidate-patrol", "repository-patrol"],
835
+ ["buildchain-stable-candidate-patrol", "repository-patrol"],
836
+ ["buildchain-stable-candidate-qualification", "repository-patrol"],
830
837
  ["patrol-daily", "repository-patrol"],
831
838
  ["patrol-weekly", "repository-patrol"],
832
839
  ["patrol-monthly", "repository-patrol"],
@@ -835,6 +842,7 @@ function buildSiteBundle() {
835
842
  ["release-propagation", "preview"],
836
843
  ["candidate-lab", "repository-internal"],
837
844
  ["build-surface-fixture", "repository-internal"],
845
+ ["buildchain-stable-candidate-qualification", "repository-internal"],
838
846
  ["self-hosted-runner-smoke", "compatibility-fixture"],
839
847
  ]);
840
848
  return {
@@ -477,7 +477,7 @@ function readArg(name, fallback = "") {
477
477
  return process.argv[index + 1] || "";
478
478
  }
479
479
 
480
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
480
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
481
481
  try {
482
482
  const result = initBuildchainRepo({
483
483
  cwd: readArg("cwd", process.cwd()),
@@ -179,7 +179,13 @@ function hasCommit(targetPath, sha, timeoutMs) {
179
179
  }
180
180
 
181
181
  function checkoutFetchedCommit(targetPath, sha, timeoutMs) {
182
- git(["checkout", "--force", "--detach", sha], { cwd: targetPath, timeoutMs });
182
+ // The locked Git tree is also the byte-level source of release evidence.
183
+ // Override runner-global autocrlf only at checkout time so this also works
184
+ // when a container workspace uses an external Git metadata pointer.
185
+ git(["-c", "core.autocrlf=false", "-c", "core.eol=lf", "checkout", "--force", "--detach", sha], {
186
+ cwd: targetPath,
187
+ timeoutMs,
188
+ });
183
189
  }
184
190
 
185
191
  function retryableGitFetchError(error) {
@@ -194,6 +200,7 @@ export function fetchSourceCommit({
194
200
  remoteUrl,
195
201
  sha,
196
202
  fetchRef,
203
+ sourceTreeSha = "",
197
204
  timeoutMs,
198
205
  env = {},
199
206
  runGit = git,
@@ -216,10 +223,22 @@ export function fetchSourceCommit({
216
223
  cwd: targetPath,
217
224
  timeoutMs,
218
225
  env,
219
- stdio: "ignore",
220
226
  });
221
227
  if (containsCommit(targetPath, sha, timeoutMs)) {
222
- return { selector: "ref" };
228
+ return { selector: "ref", checkoutSha: sha };
229
+ }
230
+ if (/^refs\/pull\/\d+\/merge$/.test(fetchRef) && sourceTreeSha) {
231
+ const fetchedSha = runGit(["rev-parse", "refs/buildchain/source-ref^{commit}"], {
232
+ cwd: targetPath,
233
+ timeoutMs,
234
+ });
235
+ const fetchedTree = runGit(["rev-parse", "refs/buildchain/source-ref^{tree}"], {
236
+ cwd: targetPath,
237
+ timeoutMs,
238
+ });
239
+ if (fetchedTree === sourceTreeSha) {
240
+ return { selector: "ref-tree", checkoutSha: fetchedSha };
241
+ }
223
242
  }
224
243
  } catch (error) {
225
244
  // A retryable transport failure belongs to the bounded outer retry. Do
@@ -234,12 +253,11 @@ export function fetchSourceCommit({
234
253
  cwd: targetPath,
235
254
  timeoutMs,
236
255
  env,
237
- stdio: "ignore",
238
256
  });
239
257
  if (!containsCommit(targetPath, sha, timeoutMs)) {
240
258
  throw new Error(`fetched ${fetchRef || sha}, but ${sha} is not available`);
241
259
  }
242
- return { selector: "sha" };
260
+ return { selector: "sha", checkoutSha: sha };
243
261
  }
244
262
 
245
263
  export function runBoundedFetch({ attempts = 1, fetch, onAttempt = () => {}, onRetry = () => {}, shouldRetry = () => true }) {
@@ -266,18 +284,30 @@ function githubRemoteUrl({ repository, serverUrl = "https://github.com" }) {
266
284
  return `${base}/${repository}.git`;
267
285
  }
268
286
 
269
- function verifyCheckout({ targetPath, sourceSha, sourceTreeSha = "" }) {
287
+ function verifyCheckout({ targetPath, sourceSha, sourceTreeSha = "", fetchRef = "" }) {
270
288
  const head = git(["rev-parse", "HEAD"], { cwd: targetPath });
271
289
  const tree = git(["rev-parse", "HEAD^{tree}"], { cwd: targetPath });
272
290
  const headOk = head === sourceSha;
273
291
  const treeOk = !sourceTreeSha || tree === sourceTreeSha;
274
- if (!headOk) {
292
+ const pullMergeTreeEquivalent = !headOk
293
+ && /^refs\/pull\/\d+\/merge$/.test(fetchRef)
294
+ && Boolean(sourceTreeSha)
295
+ && treeOk;
296
+ if (!headOk && !pullMergeTreeEquivalent) {
275
297
  throw new Error(`locked source checkout head mismatch: expected ${sourceSha}, got ${head}`);
276
298
  }
277
299
  if (!treeOk) {
278
300
  throw new Error(`locked source checkout tree mismatch: expected ${sourceTreeSha}, got ${tree}`);
279
301
  }
280
- return { head, tree, headOk, treeOk };
302
+ return {
303
+ head,
304
+ expectedHead: sourceSha,
305
+ tree,
306
+ headOk,
307
+ treeOk,
308
+ identityOk: headOk || pullMergeTreeEquivalent,
309
+ identityMode: pullMergeTreeEquivalent ? "tree-equivalent-pull-merge" : "commit",
310
+ };
281
311
  }
282
312
 
283
313
  function writeEvidence(filePath, evidence) {
@@ -359,6 +389,7 @@ export function lockedSourceCheckout({
359
389
  durationMs: 0,
360
390
  };
361
391
  let checkoutError;
392
+ let checkoutSha = sha;
362
393
  if (normalizedMode !== "off") {
363
394
  try {
364
395
  if (renderedReferenceRepository) {
@@ -374,15 +405,17 @@ export function lockedSourceCheckout({
374
405
  checkoutFetchedCommit(targetPath, sha, timeoutMs);
375
406
  } else if (renderedMirrorUrl) {
376
407
  evidence.cache.transport = "mirror-url";
377
- fetchSourceCommit({
408
+ const fetchResult = fetchSourceCommit({
378
409
  targetPath,
379
410
  remoteName: "buildchain-cache",
380
411
  remoteUrl: renderedMirrorUrl,
381
412
  sha,
382
413
  fetchRef,
414
+ sourceTreeSha: treeSha,
383
415
  timeoutMs,
384
416
  });
385
- checkoutFetchedCommit(targetPath, sha, timeoutMs);
417
+ checkoutSha = fetchResult.checkoutSha || sha;
418
+ checkoutFetchedCommit(targetPath, checkoutSha, timeoutMs);
386
419
  } else {
387
420
  throw new Error("checkout cache is enabled but no mirror URL or reference repository template was provided");
388
421
  }
@@ -416,6 +449,7 @@ export function lockedSourceCheckout({
416
449
  remoteUrl,
417
450
  sha,
418
451
  fetchRef,
452
+ sourceTreeSha: treeSha,
419
453
  timeoutMs: githubTimeoutMs,
420
454
  env: githubAuthEnv(githubToken),
421
455
  }),
@@ -424,14 +458,20 @@ export function lockedSourceCheckout({
424
458
  shouldRetry: retryableGitFetchError,
425
459
  });
426
460
  evidence.cache.githubFetchAttempts = fetchResult.attempts;
461
+ checkoutSha = fetchResult.value.checkoutSha || sha;
427
462
  } catch (error) {
428
463
  evidence.durationMs = Date.now() - startedAt;
429
464
  writeEvidence(path.resolve(workspace, diagnosticsPath), evidence);
430
465
  throw error;
431
466
  }
432
- checkoutFetchedCommit(targetPath, sha, timeoutMs);
467
+ checkoutFetchedCommit(targetPath, checkoutSha, timeoutMs);
433
468
  }
434
- evidence.verification = verifyCheckout({ targetPath, sourceSha: sha, sourceTreeSha: treeSha });
469
+ evidence.verification = verifyCheckout({
470
+ targetPath,
471
+ sourceSha: sha,
472
+ sourceTreeSha: treeSha,
473
+ fetchRef,
474
+ });
435
475
  evidence.durationMs = Date.now() - startedAt;
436
476
  writeEvidence(path.resolve(workspace, diagnosticsPath), evidence);
437
477
  return evidence;
@@ -155,7 +155,7 @@ function usage() {
155
155
  `;
156
156
  }
157
157
 
158
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
158
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
159
159
  try {
160
160
  const argv = process.argv.slice(2);
161
161
  if (hasFlag(argv, "help") || hasFlag(argv, "h")) {
@@ -57,7 +57,7 @@ export function runPublicationArtifactCli(args = process.argv.slice(2)) {
57
57
  return result;
58
58
  }
59
59
 
60
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
60
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
61
61
  try {
62
62
  runPublicationArtifactCli();
63
63
  } catch (error) {
@@ -28,7 +28,7 @@ export function runPublicationPackageCli(args = process.argv.slice(2)) {
28
28
  return result;
29
29
  }
30
30
 
31
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
31
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
32
32
  try {
33
33
  runPublicationPackageCli();
34
34
  } catch (error) {
@@ -93,6 +93,6 @@ export function runReleasePropagationCli(argv = process.argv.slice(2)) {
93
93
  throw new Error(`unsupported release-propagation command: ${mode}`);
94
94
  }
95
95
 
96
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
96
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
97
97
  runReleasePropagationCli();
98
98
  }