@kungfu-tech/buildchain 4.1.3 → 4.1.4

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 (199) hide show
  1. package/AGENTS.md +11 -13
  2. package/CONTRIBUTING.md +32 -9
  3. package/README.md +56 -140
  4. package/actions/release/promotion/ref/README.md +1 -1
  5. package/architecture/action-taxonomy.json +1 -1
  6. package/architecture/agent-change-map.md +32 -45
  7. package/architecture/build-orchestration.json +1 -1
  8. package/architecture/capability-state-machine-manifest.json +5 -5
  9. package/architecture/ci-lane-change-budget.json +92 -92
  10. package/architecture/code-layout.json +2 -12
  11. package/architecture/core-mechanisms.json +38 -39
  12. package/architecture/cross-platform-adopter-qualification.json +1 -1
  13. package/architecture/dev-delivery-orchestration.json +2 -2
  14. package/architecture/floating-consumer-policy.json +3 -3
  15. package/architecture/internal-capabilities.json +27 -28
  16. package/architecture/maintainability-debt.json +60 -94
  17. package/architecture/maintainability-policy.json +7 -13
  18. package/architecture/minimal-consumer-migration.json +43 -5014
  19. package/architecture/platform-stage-checkpoints.json +1 -1
  20. package/architecture/release-tail-contract.json +2 -2
  21. package/architecture/release-topology.json +31 -25
  22. package/architecture/stage-capsule-qualification.json +1 -1
  23. package/architecture/universal-workflow-bootstrap.json +60 -60
  24. package/architecture/universal-workflow-capability-policy.json +3 -3
  25. package/architecture/universal-workflow-fault-campaign.json +7 -7
  26. package/architecture/workflow-taxonomy.json +59 -60
  27. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  28. package/dist/site/buildchain-contract.json +138 -66
  29. package/dist/site/buildchain-site.json +257 -331
  30. package/dist/site/capability-registry.json +4 -4
  31. package/dist/site/cli-registry.json +6 -142
  32. package/dist/site/controller-registry.json +19 -19
  33. package/dist/site/kfd-claims.json +481 -529
  34. package/dist/site/kfd-upstream-aggregate.json +1 -1
  35. package/dist/site/manual-registry.json +22 -22
  36. package/dist/site/node-api-registry.json +82 -339
  37. package/dist/site/page-registry.json +229 -304
  38. package/dist/site/public-surface-audit.json +544 -754
  39. package/dist/site/publication-authority-registry.json +316 -316
  40. package/dist/site/publication-registry.json +1 -1
  41. package/dist/site/site-manifest.json +23 -23
  42. package/dist/site/workflow-registry.json +1071 -1027
  43. package/docs/MAP.md +28 -20
  44. package/docs/auditable-demo.md +17 -49
  45. package/docs/bootstrap-recovery.md +46 -37
  46. package/docs/cli-reference.md +2 -46
  47. package/docs/cli.md +97 -186
  48. package/docs/code-organization.md +7 -5
  49. package/docs/dev-alpha-candidate-patrol.md +19 -42
  50. package/docs/dev-delivery-orchestration.md +15 -11
  51. package/docs/dev-delivery-qualification-landing-adr.md +10 -9
  52. package/docs/dev-delivery-warrant.md +17 -16
  53. package/docs/dev-qualification-patrol.md +25 -20
  54. package/docs/getting-started.md +80 -139
  55. package/docs/github-artifact-attestation.md +31 -81
  56. package/docs/install.md +66 -30
  57. package/docs/lifecycle-protocol.md +19 -57
  58. package/docs/migration-inventory.md +17 -45
  59. package/docs/next-development-transition.md +13 -13
  60. package/docs/node-api-reference.md +122 -138
  61. package/docs/oci-publication.md +13 -10
  62. package/docs/publication-artifacts.md +128 -486
  63. package/docs/publication-authority.md +15 -45
  64. package/docs/publish-transaction.md +13 -11
  65. package/docs/release-candidate.md +126 -355
  66. package/docs/release-discussions.md +36 -67
  67. package/docs/release-flow.md +80 -339
  68. package/docs/release-governance.md +114 -962
  69. package/docs/release-promotion-request.md +45 -43
  70. package/docs/release-propagation.md +13 -27
  71. package/docs/release-tail-provider-plane.md +38 -65
  72. package/docs/reusable-build-surface.md +78 -196
  73. package/docs/runtime-train-validation.md +8 -6
  74. package/docs/stable-candidate-patrol.md +13 -66
  75. package/docs/v4-adopter-delivery.md +15 -15
  76. package/docs/v4-declarative-release-tail-migration.md +19 -17
  77. package/docs/v4-production-release.md +11 -6
  78. package/docs/v4-publication-rehearsal.md +12 -27
  79. package/docs/v4-stage-capsule.md +14 -20
  80. package/docs/v4-tail-reseal.md +34 -20
  81. package/docs/web-surface-deployments.md +26 -264
  82. package/docs/workflow-catalog.md +31 -31
  83. package/docs/workflow-path-migration.md +10 -4
  84. package/package.json +5 -4
  85. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  86. package/packages/core/adoption/cli/output.mjs +1 -9
  87. package/packages/core/adoption/cli/product-gate.mjs +1 -15
  88. package/packages/core/adoption/cli/project.mjs +9 -10
  89. package/packages/core/adoption/cli/standards.mjs +1 -14
  90. package/packages/core/adoption/cli/support.mjs +0 -2
  91. package/packages/core/adoption/cli/surface-register.mjs +0 -2
  92. package/packages/core/adoption/commands/init-repo.mjs +77 -485
  93. package/packages/core/adoption/consumer-init.js +279 -0
  94. package/packages/core/adoption/qualification/admission.js +1 -1
  95. package/packages/core/build/cli/cache.mjs +0 -2
  96. package/packages/core/build/cli/facts.mjs +0 -3
  97. package/packages/core/build/cli/homebrew.mjs +0 -4
  98. package/packages/core/build/cli/lifecycle.mjs +0 -3
  99. package/packages/core/build/github-artifact-attestation.js +2 -2
  100. package/packages/core/build/platform-stage-checkpoints.js +1 -1
  101. package/packages/core/build/signing/dispatch.js +1 -1
  102. package/packages/core/consumer/buildchain-config.js +9 -0
  103. package/packages/core/consumer/contract/inspection.js +351 -9
  104. package/packages/core/consumer/contract/javascript-syntax.js +439 -0
  105. package/packages/core/consumer/contract/javascript-values.js +61 -0
  106. package/packages/core/consumer/contract/javascript.js +555 -0
  107. package/packages/core/consumer/contract/local-validation.js +84 -0
  108. package/packages/core/contracts/buildchain-contract.js +39 -8
  109. package/packages/core/contracts/cli/introspection.mjs +1 -9
  110. package/packages/core/contracts/public-surface-cli.js +0 -1
  111. package/packages/core/dev-delivery/dev-delivery-contract-surface.js +2 -2
  112. package/packages/core/governance/buildchain-publication-authority.js +24 -24
  113. package/packages/core/governance/cli/doctor.mjs +58 -42
  114. package/packages/core/governance/commands/architecture.mjs +6 -1
  115. package/packages/core/governance/commands/audit-publication-control-plane.mjs +1 -1
  116. package/packages/core/governance/commands/workflow-friction-report.mjs +1 -1
  117. package/packages/core/governance/github-governance-authority.js +2 -2
  118. package/packages/core/governance/publication/collection.js +1 -1
  119. package/packages/core/index.js +1 -3
  120. package/packages/core/observability/cli/candidate.mjs +0 -2
  121. package/packages/core/observability/cli/context.mjs +0 -5
  122. package/packages/core/observability/cli/diagnostics.mjs +0 -7
  123. package/packages/core/observability/cli/events.mjs +2 -10
  124. package/packages/core/paper/commands/paper-work-fleet-cli.mjs +1 -1
  125. package/packages/core/paper/commands/paper.mjs +33 -422
  126. package/packages/core/paper/consumer-status.js +72 -0
  127. package/packages/core/paper/operations/identity.js +0 -3
  128. package/packages/core/paper/operations/migration.js +460 -0
  129. package/packages/core/paper/operations/preflight-checks.js +8 -68
  130. package/packages/core/paper/operations/scaffold.js +16 -326
  131. package/packages/core/paper/operations/status.js +27 -53
  132. package/packages/core/paper/paper-agent-entry.js +36 -147
  133. package/packages/core/paper/paper-fleet.js +24 -39
  134. package/packages/core/paper/paper-repository.js +16 -0
  135. package/packages/core/paper/paper-runtime-channels.js +1 -1
  136. package/packages/core/paper/paper-scaffold-content.js +153 -162
  137. package/packages/core/paper/paper-work.js +3 -10
  138. package/packages/core/paper/paper.js +43 -5
  139. package/packages/core/providers/github/artifact-attestation.js +1 -1
  140. package/packages/core/providers/github/pipeline-context-artifacts.js +172 -0
  141. package/packages/core/publication/authority/control-plane.js +2 -2
  142. package/packages/core/publication/authority/seal.js +1 -1
  143. package/packages/core/publication/cli/dispatch.mjs +1 -12
  144. package/packages/core/publication/pipeline/actions.js +16 -9
  145. package/packages/core/publication/pipeline/apply.js +17 -9
  146. package/packages/core/publication/pipeline/context.js +29 -0
  147. package/packages/core/publication/pipeline/prepare.js +15 -6
  148. package/packages/core/publication/pipeline/recovery-materials.js +63 -1
  149. package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
  150. package/packages/core/publication/pipeline/stable-entry.js +12 -7
  151. package/packages/core/publication/pipeline/stable-products.js +21 -19
  152. package/packages/core/publication/pipeline/version-actions.js +15 -6
  153. package/packages/core/publication/publication-rehearsal-projection.js +0 -9
  154. package/packages/core/release/cli/dispatch.mjs +1 -4
  155. package/packages/core/release/cli/inspection-handlers.mjs +1 -33
  156. package/packages/core/release/cli/release-handlers.mjs +2 -19
  157. package/packages/core/release/cli/verification-handlers.mjs +4 -25
  158. package/packages/core/release/next-development-projection.js +13 -13
  159. package/packages/core/release/passport/locations.js +13 -8
  160. package/packages/core/release/release-passport.js +70 -5
  161. package/packages/core/web/cli/badges.mjs +0 -4
  162. package/packages/core/web/publication-admission.js +1 -1
  163. package/packages/core/web/publication-capability.js +1 -1
  164. package/packages/core/web/readme-badges.js +1 -1
  165. package/packages/core/workflow/cli/main.mjs +1 -4
  166. package/packages/core/workflow/cli/process.mjs +1 -1
  167. package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
  168. package/packages/core/workflow/engine/conformance.js +1 -1
  169. package/packages/core/workflow/pipeline/build.js +21 -1
  170. package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
  171. package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
  172. package/scripts/check-code-layout.mjs +2 -0
  173. package/scripts/check-core-mechanism-inventory.mjs +29 -13
  174. package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
  175. package/scripts/check-inventory.mjs +27 -27
  176. package/scripts/check-maintainability.mjs +28 -5
  177. package/scripts/check-public-dogfood-contract.mjs +3 -3
  178. package/scripts/check-release-topology.mjs +1 -1
  179. package/scripts/check-self-consumer-contract.mjs +7 -7
  180. package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
  181. package/scripts/generate-channel-promotion-workflow.mjs +1 -1
  182. package/scripts/generate-minimal-consumer-contract.mjs +21 -1
  183. package/scripts/generate-site-bundle.mjs +2 -1
  184. package/scripts/generate-tail-reseal-fixture.mjs +3 -3
  185. package/scripts/inventory/promotion.mjs +1 -1
  186. package/scripts/next-development-self-dogfood-harness.mjs +1 -1
  187. package/scripts/next-development-self-dogfood.mjs +4 -4
  188. package/scripts/site-capability-metadata.mjs +2 -6
  189. package/scripts/source-metric-lineage.mjs +62 -7
  190. package/scripts/verify-golden-path.mjs +31 -35
  191. package/templates/native-dev-delivery.yml +1 -1
  192. package/templates/universal-buildchain-bootstrap.yml +1 -1
  193. package/packages/core/paper/operations/bootstrap.js +0 -75
  194. package/packages/core/paper/operations/plans.js +0 -213
  195. package/packages/core/paper/operations/provisioning.js +0 -113
  196. package/packages/core/paper/operations/scaffold-config.js +0 -77
  197. package/packages/core/paper/operations/scaffold-workflows.js +0 -116
  198. package/packages/core/paper/operations/workspace.js +0 -60
  199. package/packages/core/paper/paper-npm-bootstrap.js +0 -492
@@ -3,12 +3,15 @@ import path from "node:path";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
5
  import { bindConsumerSource } from "../../consumer/contract/identity.js";
6
+ import { inspectConsumerContract } from "../../consumer/contract/inspection.js";
7
+ import { validateConsumerWiring } from "../../consumer/contract/local-validation.js";
6
8
  import { recordDigest } from "../../release/discussion/envelope.js";
7
9
  import { consumerCommandSession } from "../../runtime/consumer-shell.js";
8
10
  import { createNativeChildEnvironment } from "../../dev-delivery/native/execution.js";
9
11
 
10
12
  function inspectTrackedBytes(cwd, tree) {
11
13
  const root = fs.realpathSync(cwd);
14
+ const files = Object.create(null);
12
15
  const entries = execFileSync(
13
16
  "git",
14
17
  ["-C", root, "ls-tree", "-r", "-z", tree],
@@ -48,7 +51,13 @@ function inspectTrackedBytes(cwd, tree) {
48
51
  throw new Error(
49
52
  `Build tracked source bytes differ from the admitted tree: ${file}`,
50
53
  );
54
+ Object.defineProperty(files, file, {
55
+ enumerable: true,
56
+ get: () =>
57
+ link ? fs.readlinkSync(absolute) : fs.readFileSync(absolute, "utf8"),
58
+ });
51
59
  }
60
+ return files;
52
61
  }
53
62
 
54
63
  export function inspectPipelineSource(cwd, expected) {
@@ -82,7 +91,18 @@ export function inspectPipelineSource(cwd, expected) {
82
91
  "Build source has tracked modifications at its verification boundary",
83
92
  );
84
93
  // Index flags and local Git filters cannot qualify different product bytes.
85
- inspectTrackedBytes(cwd, expected.tree);
94
+ const files = inspectTrackedBytes(cwd, expected.tree);
95
+ const { channel } = validateConsumerWiring(cwd, expected.configPath);
96
+ const inspection = inspectConsumerContract(files, {
97
+ channel,
98
+ configPath: expected.configPath,
99
+ });
100
+ if (inspection.controlIssues.length)
101
+ throw new Error(
102
+ `Consumer control wiring is invalid: ${inspection.controlIssues.join("; ")}`,
103
+ );
104
+ // Unresolved product language/tool edges are not a static safety proof.
105
+ // Execution remains confined to the runtime's read-only product jobs below.
86
106
  return plan;
87
107
  }
88
108
 
@@ -65,9 +65,13 @@ export async function observePipelineWorker(session, current, queue, host) {
65
65
  run,
66
66
  };
67
67
  }
68
+ // Retained executions keep the workflow identity that actually produced them.
69
+ // The old path is read-only evidence, never a new dispatch or consumer alias.
68
70
  const entries = (run.referenced_workflows || []).filter((entry) =>
69
- entry.path?.startsWith(
70
- "kungfu-systems/buildchain/.github/workflows/public-ops-dev-auto-merge.yml@",
71
+ [".ops-dev-auto-merge.yml", "public-ops-dev-auto-merge.yml"].some((file) =>
72
+ entry.path?.startsWith(
73
+ `kungfu-systems/buildchain/.github/workflows/${file}@`,
74
+ ),
71
75
  ),
72
76
  );
73
77
  if (entries.length !== 1) return null;
@@ -64,13 +64,12 @@ function validateEntries(policy, errors) {
64
64
  if (
65
65
  entry.path &&
66
66
  !(
67
- [".build", "build", "buildchain", "buildchain-recover"].includes(
68
- entry.id,
69
- ) && entry.path === `.github/workflows/${entry.id}.yml`
67
+ [".build", "buildchain", "buildchain-recover"].includes(entry.id) &&
68
+ entry.path === `.github/workflows/${entry.id}.yml`
70
69
  )
71
70
  )
72
71
  errors.push(
73
- `${entry.id}: explicit path is reserved for build APIs and the generated consumer pair`,
72
+ `${entry.id}: explicit path is reserved for the internal build engine and generated consumer pair`,
74
73
  );
75
74
  const file = workflowPath(entry);
76
75
  if (!WORKFLOW.test(file) || paths.has(file))
@@ -82,6 +81,33 @@ function validateEntries(policy, errors) {
82
81
  );
83
82
  validateEntryOwnership(entry, errors);
84
83
  }
84
+ const publicEntries = policy.entries.filter(
85
+ (entry) => entry.role === "public",
86
+ );
87
+ if (
88
+ policy.entries
89
+ .filter((entry) => entry.role === "self")
90
+ .map((entry) => entry.id)
91
+ .sort()
92
+ .join(",") !== "buildchain,buildchain-recover"
93
+ )
94
+ errors.push(
95
+ "repository event roots must be exactly the generated consumer pair",
96
+ );
97
+ if (
98
+ publicEntries.length !== 2 ||
99
+ ["pipeline", "recover"].some(
100
+ (purpose) =>
101
+ !publicEntries.some(
102
+ (entry) =>
103
+ entry.id === purpose &&
104
+ entry.category === "ops" &&
105
+ entry.purpose === purpose &&
106
+ entry.invocation === "reusable",
107
+ ),
108
+ )
109
+ )
110
+ errors.push("consumer public entries must be exactly pipeline and recover");
85
111
  return policy.entries;
86
112
  }
87
113
 
@@ -100,11 +126,9 @@ export function discoverWorkflowFiles(root) {
100
126
  return visit(directory).sort();
101
127
  }
102
128
 
103
- function validateWorkflow(root, entry, declared, errors) {
129
+ function validateWorkflow(entry, text, declared, errors) {
130
+ if (typeof text !== "string") return;
104
131
  const file = workflowPath(entry),
105
- absolute = path.join(root, file);
106
- if (!fs.existsSync(absolute) || !fs.lstatSync(absolute).isFile()) return;
107
- const text = fs.readFileSync(absolute, "utf8"),
108
132
  document = parseWorkflowDocument(text);
109
133
  if (
110
134
  (entry.invocation === "reusable") !==
@@ -258,23 +282,41 @@ function validateGateIntegration(root, errors) {
258
282
 
259
283
  export function checkWorkflowTaxonomy(
260
284
  root,
261
- { integration = true, documentation = true } = {},
285
+ { integration = true, documentation = true, files } = {},
262
286
  ) {
263
287
  const errors = [],
264
- policy = readWorkflowTaxonomy(root),
288
+ policy = files
289
+ ? JSON.parse(files[TAXONOMY_PATH] || "null")
290
+ : readWorkflowTaxonomy(root),
265
291
  entries = validateEntries(policy, errors);
266
292
  if (errors.length) return { ok: false, errors };
293
+ files ??= Object.fromEntries(
294
+ discoverWorkflowFiles(root).map((file) => {
295
+ const absolute = path.join(root, file);
296
+ return [
297
+ file,
298
+ fs.lstatSync(absolute).isFile()
299
+ ? fs.readFileSync(absolute, "utf8")
300
+ : null,
301
+ ];
302
+ }),
303
+ );
267
304
  const declared = new Set(entries.map(workflowPath)),
268
- observed = new Set(discoverWorkflowFiles(root));
305
+ observed = new Set(
306
+ Object.keys(files).filter((file) =>
307
+ /^\.github\/workflows\/.*\.ya?ml$/iu.test(file),
308
+ ),
309
+ );
269
310
  for (const file of observed) {
270
311
  if (!declared.has(file)) errors.push(`unregistered workflow: ${file}`);
271
- if (!fs.lstatSync(path.join(root, file)).isFile())
312
+ if (typeof files[file] !== "string")
272
313
  errors.push(`workflow must be a regular file: ${file}`);
273
314
  }
274
315
  for (const file of declared)
275
316
  if (!observed.has(file))
276
317
  errors.push(`registered workflow missing: ${file}`);
277
- for (const entry of entries) validateWorkflow(root, entry, declared, errors);
318
+ for (const entry of entries)
319
+ validateWorkflow(entry, files[workflowPath(entry)], declared, errors);
278
320
  if (integration) validateGateIntegration(root, errors);
279
321
  if (documentation) {
280
322
  const file = path.join(root, TAXONOMY_DOC);
@@ -324,9 +366,9 @@ export function renderWorkflowCatalog(policy) {
324
366
  "",
325
367
  "Generated from `architecture/workflow-taxonomy.json`. Every workflow has one canonical implementation; alternate paths and historical forwarding aliases are rejected.",
326
368
  "",
327
- "Public workflows own the consumer API. Component workflows own reusable multi-job topology. Self workflows own Buildchain repository automation. Actions own execution steps; JS adapters and Rust/WASM own implementation.",
369
+ "The two public workflows own normal pipeline execution and exact-attempt recovery. Component workflows are internal runtime implementation, including once-only setup and the dispatch signing service; consumers do not wire these components. Self workflows are the generated consumer pair. Actions own execution steps; JS adapters and Rust/WASM own implementation.",
328
370
  "",
329
- "Names use `public-<category>-<purpose>.yml`, `.<category>-<purpose>.yml`, and the generated self callers `buildchain.yml` and `buildchain-recover.yml`, with categories `build`, `release`, and `ops`. The primary build API and backbone use `build.yml` and `.build.yml`.",
371
+ "Names use `public-ops-pipeline.yml`, `public-ops-recover.yml`, internal `.<category>-<purpose>.yml`, and the generated self callers `buildchain.yml` and `buildchain-recover.yml`. Categories are `build`, `release`, and `ops`; the internal build engine retains `.build.yml`.",
330
372
  "",
331
373
  "Register ownership before adding a workflow. `pnpm run check:workflows` validates source, calls, required CI integration and independent review ownership. `pnpm run generate:workflows` regenerates this catalog.",
332
374
  "",
@@ -366,6 +408,8 @@ function validateEntryOwnership(entry, errors) {
366
408
  errors.push(`${entry.id}: invalid invocation`);
367
409
  if ((entry.role === "self") !== (entry.invocation === "repository"))
368
410
  errors.push(`${entry.id}: role and invocation disagree`);
369
- if (entry.role === "component" && entry.invocation !== "reusable")
370
- errors.push(`${entry.id}: component must be reusable`);
411
+ if (entry.invocation === "dispatch-service" && entry.role !== "component")
412
+ errors.push(
413
+ `${entry.id}: dispatch services must remain internal components`,
414
+ );
371
415
  }
@@ -204,6 +204,8 @@ export function inspectPublicActionNodes(actions, publicNodes) {
204
204
  const issues = [];
205
205
  if (!Array.isArray(publicNodes))
206
206
  return ["public Action registry must be an array"];
207
+ if (publicNodes.length)
208
+ issues.push("Actions are internal runtime nodes; consumers use pipeline and recovery workflows");
207
209
  const known = new Set(
208
210
  actions.map((action) => action.directory.replace(/^actions\//u, "")),
209
211
  );
@@ -34,15 +34,24 @@ function registrySurfaceIds(root) {
34
34
  (entry) => `cli:${entry.id}`,
35
35
  );
36
36
  const workflows = loadJson(root, "dist/site/workflow-registry.json");
37
- const workflowIds = workflows.workflows.flatMap((entry) => [
38
- `workflow:${entry.id}`,
39
- ...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
37
+ const workflowIds = workflows.workflows.flatMap((entry) =>
38
+ [entry.id, ...(entry.taxonomy ? [entry.taxonomy.id] : [])].map((id) => [
39
+ `workflow:${id}`,
40
+ entry.apiRole === "public" ? "public" : "internal",
41
+ ]),
42
+ );
43
+ const actionIds = workflows.actions.map((entry) => [
44
+ `action:${entry.id}`,
45
+ entry.apiRole === "public" ? "public" : "internal",
40
46
  ]);
41
- const actionIds = workflows.actions.map((entry) => `action:${entry.id}`);
42
47
  const exports = Object.keys(loadJson(root, "package.json").exports).map(
43
48
  (entry) => `export:${entry}`,
44
49
  );
45
- return new Set([...cli, ...workflowIds, ...actionIds, ...exports]);
50
+ return new Map(
51
+ [...cli, ...exports]
52
+ .map((id) => [id, "public"])
53
+ .concat(workflowIds, actionIds),
54
+ );
46
55
  }
47
56
 
48
57
  function listFiles(root, relativeRoot) {
@@ -99,12 +108,12 @@ function validateOwnership(mechanism, ownedSources, issues) {
99
108
  }
100
109
 
101
110
  function validateSurfaces(mechanism, surfaces, issues) {
102
- for (const surface of mechanism.publicSurfaces || []) {
103
- if (!surfaces.has(surface))
104
- issues.push(
105
- `${mechanism.id}: public surface is absent from generated/package registries: ${surface}`,
106
- );
107
- }
111
+ for (const role of ["public", "internal"])
112
+ for (const surface of mechanism[`${role}Surfaces`] || [])
113
+ if (surfaces.get(surface) !== role)
114
+ issues.push(
115
+ `${mechanism.id}: ${role} surface is absent or has a different role in generated/package registries: ${surface}`,
116
+ );
108
117
  }
109
118
 
110
119
  function validateGitRefStore(root, mechanism, issues) {
@@ -140,12 +149,15 @@ function validateReverseScanReferences(root, reverseScan, issues) {
140
149
  function validateSurfaceKinds(inventory, issues) {
141
150
  const surfaceKinds = new Set(
142
151
  (inventory.mechanisms || []).flatMap((mechanism) =>
143
- (mechanism.publicSurfaces || []).map((surface) => surface.split(":")[0]),
152
+ [
153
+ ...(mechanism.publicSurfaces || []),
154
+ ...(mechanism.internalSurfaces || []),
155
+ ].map((surface) => surface.split(":")[0]),
144
156
  ),
145
157
  );
146
158
  for (const kind of ["action", "cli", "export", "workflow"]) {
147
159
  if (!surfaceKinds.has(kind))
148
- issues.push(`reverse scan has no ${kind} public surface coverage`);
160
+ issues.push(`reverse scan has no ${kind} surface coverage`);
149
161
  }
150
162
  return surfaceKinds;
151
163
  }
@@ -329,6 +341,10 @@ function checkCoreMechanismInventory({
329
341
  (sum, entry) => sum + entry.publicSurfaces.length,
330
342
  0,
331
343
  ),
344
+ internalSurfaces: inventory.mechanisms.reduce(
345
+ (sum, entry) => sum + (entry.internalSurfaces || []).length,
346
+ 0,
347
+ ),
332
348
  dependencyCycles: inventory.maintainability.dependencyCycles,
333
349
  ...reverseScan,
334
350
  };
@@ -116,10 +116,10 @@ export function checkFloatingConsumerPolicyContract() {
116
116
  assertOrdered("actions/publication/candidate/plan/action.yml", [
117
117
  "Admit source checkout", "Enforce v4 floating consumer policy", "Resolve controller identities",
118
118
  ]);
119
- const publication = YAML.parse(read(".github/workflows/public-build-publication.yml"));
119
+ const publication = YAML.parse(read(".github/workflows/.build-publication.yml"));
120
120
  if (!Object.values(publication.jobs).some((job) => job.steps?.some((step) => step.uses?.endsWith("/actions/publication/candidate/plan"))))
121
121
  fail("publication does not invoke its consumer policy node");
122
- const stageCanary = read(".github/workflows/public-build-stage-capsule-canary.yml");
122
+ const stageCanary = read(".github/workflows/.build-stage-capsule-canary.yml");
123
123
  if (
124
124
  !stageCanary.includes("consumer-admission:") ||
125
125
  !jobDependsOn(stageCanary, "qualify", "consumer-admission")
@@ -128,7 +128,7 @@ export function checkFloatingConsumerPolicyContract() {
128
128
  "Stage Capsule qualification is not transitively gated by consumer admission",
129
129
  );
130
130
  }
131
- const promotion = read(".github/workflows/public-release-promote.yml");
131
+ const promotion = read(".github/workflows/.release-candidate-promote.yml");
132
132
  if (
133
133
  !promotion.includes("consumer-admission:") ||
134
134
  !jobDependsOn(promotion, "invoke", "consumer-admission")
@@ -146,23 +146,23 @@ const requiredPaths = [
146
146
  ".buildchain/alpha-contract-lock.json",
147
147
  ".buildchain/release-impact.json",
148
148
  ".github/actionlint.yaml",
149
- ".github/workflows/public-ops-release-governance.yml",
150
- ".github/workflows/public-ops-dev-auto-merge.yml",
151
- ".github/workflows/public-ops-patrol.yml",
152
- ".github/workflows/public-ops-patrol-daily.yml",
153
- ".github/workflows/public-ops-patrol-weekly.yml",
154
- ".github/workflows/public-ops-patrol-monthly.yml",
155
- ".github/workflows/public-ops-observed-evidence.yml",
156
- ".github/workflows/public-release-promote.yml",
149
+ ".github/workflows/.ops-release-governance.yml",
150
+ ".github/workflows/.ops-dev-auto-merge.yml",
151
+ ".github/workflows/.ops-patrol.yml",
152
+ ".github/workflows/.ops-patrol-daily.yml",
153
+ ".github/workflows/.ops-patrol-weekly.yml",
154
+ ".github/workflows/.ops-patrol-monthly.yml",
155
+ ".github/workflows/.ops-observed-evidence.yml",
156
+ ".github/workflows/.release-candidate-promote.yml",
157
157
  ".github/workflows/.release-promote.yml",
158
- ".github/workflows/public-release-propagation.yml",
159
- ".github/workflows/public-release-paper.yml",
158
+ ".github/workflows/.release-propagation.yml",
159
+ ".github/workflows/.release-paper.yml",
160
160
  ".github/workflows/.release-binary-assets.yml",
161
161
  ".github/workflows/.build.yml",
162
162
  ".github/workflows/.build-gate-profile.yml",
163
163
  ".github/workflows/.build-demo-adapter.yml",
164
- ".github/workflows/public-build-demo.yml",
165
- ".github/workflows/build.yml",
164
+ ".github/workflows/.build-demo.yml",
165
+ ".github/workflows/.build-candidate.yml",
166
166
  "fixtures/libnode-shaped/buildchain.toml",
167
167
  "fixtures/libnode-shaped/.github/workflows/build.yml",
168
168
  "fixtures/libnode-shaped/package.json",
@@ -281,7 +281,7 @@ for (const requiredSnippet of [
281
281
  }
282
282
  }
283
283
  const channelBuildWorkflow = fs.readFileSync(
284
- path.join(root, ".github/workflows/build.yml"),
284
+ path.join(root, ".github/workflows/.build-candidate.yml"),
285
285
  "utf8",
286
286
  );
287
287
  const advancedPromotionWorkflow = fs.readFileSync(
@@ -289,7 +289,7 @@ const advancedPromotionWorkflow = fs.readFileSync(
289
289
  "utf8",
290
290
  );
291
291
  const channelPromotionWorkflow = fs.readFileSync(
292
- path.join(root, ".github/workflows/public-release-promote.yml"),
292
+ path.join(root, ".github/workflows/.release-candidate-promote.yml"),
293
293
  "utf8",
294
294
  );
295
295
  assertPromotionInventory(root);
@@ -306,7 +306,7 @@ const actionlintConfig = fs.readFileSync(
306
306
  "utf8",
307
307
  );
308
308
  for (const requiredSnippet of [
309
- ".github/workflows/build.yml:",
309
+ ".github/workflows/.build-candidate.yml:",
310
310
  ".github/workflows/.build.yml:",
311
311
  ".github/workflows/.build-gate-profile.yml:",
312
312
  'property "workflow_ref" is not defined in object type',
@@ -859,25 +859,25 @@ for (const requiredSnippet of [
859
859
  }
860
860
  const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
861
861
  for (const requiredSnippet of [
862
- "config-path",
863
- "buildchain.toml",
864
- ".buildchain/contract-lock.json",
865
- ".buildchain/alpha-contract-lock.json",
866
- "architecture/build-environments.json",
867
- "build.contract",
868
- "cache roots",
869
- "exact consumer source",
870
- "run-build-stage",
871
- "transfer-build-artifact",
862
+ ".buildchain/buildchain.toml",
863
+ "schema = 2",
864
+ "[[products]]",
865
+ "public-ops-pipeline.yml@v4",
866
+ "public-ops-recover.yml@v4",
867
+ "Without a lock",
868
+ "exact Git commit, tree and TOML blob",
869
+ "without provider credentials",
870
+ "original attempt",
871
+ "Consumers do not call",
872
872
  ]) {
873
873
  if (!reusableBuildSurfaceDoc.includes(requiredSnippet)) {
874
- throw new Error(`reusable build surface doc missing contract lock snippet: ${requiredSnippet}`);
874
+ throw new Error(`consumer build surface doc missing schema-2 contract snippet: ${requiredSnippet}`);
875
875
  }
876
876
  }
877
877
  for (const [docName, docSource] of Object.entries({ "docs/cli.md": cliDoc, "docs/install.md": installDoc })) {
878
878
  for (const requiredSnippet of [
879
879
  "minimumReleaseAgeExclude",
880
- "@kungfu-tech/buildchain@4.0.0",
880
+ "@kungfu-tech/buildchain@4.1.3",
881
881
  "package/version-specific",
882
882
  ]) {
883
883
  if (!docSource.includes(requiredSnippet)) {
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
- import { projectRenamedMetrics } from "./source-metric-lineage.mjs";
4
+ import {
5
+ projectRenamedMetrics,
6
+ projectWorkflowMetrics,
7
+ } from "./source-metric-lineage.mjs";
8
+ import {
9
+ workflowPath,
10
+ TAXONOMY_PATH,
11
+ } from "../packages/core/workflow/workflow-taxonomy.mjs";
5
12
  import path from "node:path";
6
13
  import { execFileSync } from "node:child_process";
7
14
  import { pathToFileURL } from "node:url";
@@ -417,10 +424,25 @@ function checkMaintainability({ root = process.cwd() } = {}) {
417
424
  if (file.endsWith(".rs")) baselineFiles[file] = metrics;
418
425
  }
419
426
  const baselineTests = testMetricsAtRevision(root, extendedCoverageRevision);
420
- const baselineWorkflows = workflowMetricsAtRevision(
421
- root,
422
- extendedCoverageRevision,
423
- );
427
+ const taxonomyCoordinates = (policy) =>
428
+ policy.entries.map((entry) => ({
429
+ id: entry.id,
430
+ path: workflowPath(entry),
431
+ }));
432
+ const workflowLineage = projectWorkflowMetrics({
433
+ before: taxonomyCoordinates(
434
+ JSON.parse(
435
+ gitOutput(root, [
436
+ "show",
437
+ `${extendedCoverageRevision}:${TAXONOMY_PATH}`,
438
+ ]),
439
+ ),
440
+ ),
441
+ after: taxonomyCoordinates(readJson(root, TAXONOMY_PATH)),
442
+ metrics: workflowMetricsAtRevision(root, extendedCoverageRevision),
443
+ currentPaths: new Set(Object.keys(current.workflows)),
444
+ });
445
+ const baselineWorkflows = workflowLineage.metrics;
424
446
  const issues = evaluateExceptionGovernance({ policy });
425
447
  issues.push(...evaluateExceptionBudget({ policy }));
426
448
  const hotspots = collectHotspots(root, current, 20, debt.hotspots || []);
@@ -469,6 +491,7 @@ function checkMaintainability({ root = process.cwd() } = {}) {
469
491
  trackedFiles: current.repository.trackedFiles,
470
492
  sourceFiles: current.repository.handMaintainedSourceFiles,
471
493
  measurementRenames: lineage.renames,
494
+ workflowMeasurementRenames: workflowLineage.renames,
472
495
  publicSurface: current.publicSurface,
473
496
  hotspots: current.hotspots,
474
497
  governedDebtSurfaces: Object.values(debt.surfaces).reduce(
@@ -11,7 +11,7 @@ const DEFAULT_ROOT = path.resolve(
11
11
  "..",
12
12
  );
13
13
  const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
14
- const REUSABLE_PATH = ".github/workflows/public-build-stage-capsule-canary.yml";
14
+ const REUSABLE_PATH = ".github/workflows/.build-stage-capsule-canary.yml";
15
15
  export const PUBLIC_DOGFOOD_ENTRY_REF = "v4";
16
16
  const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
17
17
  const PRIVATE_SHADOW = ["kungfu", "shadow"].join("-");
@@ -201,7 +201,7 @@ function assertWorkflowInventory(root) {
201
201
  fail(`${relative} invokes the private qualification script directly`);
202
202
  if (
203
203
  relative !== CALLER_PATH &&
204
- text.includes(".github/workflows/public-build-stage-capsule-canary.yml@")
204
+ text.includes(".github/workflows/.build-stage-capsule-canary.yml@")
205
205
  )
206
206
  fail(`${relative} creates a second v4 dogfood caller`);
207
207
  if (relative !== REUSABLE_PATH && relative !== CALLER_PATH)
@@ -275,7 +275,7 @@ function assertArchitecture(root) {
275
275
  !dogfood ||
276
276
  dogfood.callerWorkflow !== CALLER_PATH ||
277
277
  dogfood.reusableWorkflow !==
278
- "kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml" ||
278
+ "kungfu-systems/buildchain/.github/workflows/.build-stage-capsule-canary.yml" ||
279
279
  dogfood.runtimeBinding !== "entry-selected-runtime" ||
280
280
  dogfood.consumerSourceBinding !== "github.sha" ||
281
281
  JSON.stringify(dogfood.executableStages) !==
@@ -532,7 +532,7 @@ export function checkReleaseTopology() {
532
532
  assertPipelinePublicationTopology(ledger, discoverReleaseTopology, root);
533
533
  const preview = ledger.postPublicationScope;
534
534
  assert.deepEqual(preview.workflowPaths, [
535
- ".github/workflows/public-release-oci-compose-preview.yml",
535
+ ".github/workflows/.release-oci-compose-preview.yml",
536
536
  ]);
537
537
  const previewTopology = discoverReleaseTopology(
538
538
  preview.workflowPaths,
@@ -2,12 +2,11 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { parse as yaml } from "yaml";
5
- import { compileConsumerPlan } from "../packages/core/consumer/contract/plan.js";
5
+ import * as consumerPlan from "../packages/core/consumer/contract/plan.js";
6
6
  import { consumerWorkflows } from "../packages/core/consumer/contract/entries.js";
7
7
  import { workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
8
8
 
9
- export const SELF_ENTRY_CHANNEL = "v4-alpha";
10
- export const SELF_CONFIG_PATH = ".buildchain/minimal-consumer.toml";
9
+ export const SELF_ENTRY_CHANNEL = "v4";
11
10
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
11
  const PLATFORMS = ["linux-x64", "macos-arm64", "windows-x64"];
13
12
 
@@ -19,10 +18,11 @@ export function checkSelfConsumerContract(root = ROOT) {
19
18
  const fail = (message) => {
20
19
  throw new Error(`self-consumer-contract: ${message}`);
21
20
  };
22
- const plan = compileConsumerPlan(read(SELF_CONFIG_PATH));
23
- if (read(SELF_CONFIG_PATH) !== read(".buildchain/buildchain.toml"))
24
- fail("transition config must match the canonical product plan");
25
- const callers = consumerWorkflows(SELF_ENTRY_CHANNEL, SELF_CONFIG_PATH);
21
+ const plan = consumerPlan.compileConsumerPlan(read(consumerPlan.CONFIG_PATH));
22
+ const retired = path.join(root, ".buildchain/minimal-consumer.toml");
23
+ if (fs.lstatSync(retired, { throwIfNoEntry: false }))
24
+ fail("retired transition config must not remain in the self consumer tree");
25
+ const callers = consumerWorkflows();
26
26
  for (const [file, bytes] of Object.entries(callers))
27
27
  if (read(file) !== bytes) fail(`${file}: generated caller drift`);
28
28
  const taxonomy = JSON.parse(read("architecture/workflow-taxonomy.json"));
@@ -41,7 +41,7 @@ assert.equal(executor.with["mutation-token"], "${{ inputs.secrets-buildchain-pro
41
41
  assert.equal(executor.with.token, "${{ github.token }}");
42
42
  assert.doesNotMatch(read(contract.bootstrap.publicWorkflow), /admission-policy-json:/u);
43
43
  const template = parse(contract.bootstrap.consumerTemplate);
44
- assert.equal(template.jobs.bootstrap.uses, "kungfu-systems/buildchain/.github/workflows/public-ops-bootstrap.yml@v4");
44
+ assert.equal(template.jobs.bootstrap.uses, "kungfu-systems/buildchain/.github/workflows/.ops-bootstrap.yml@v4");
45
45
  assert.equal(template.jobs.bootstrap.with["runtime-ref"], "${{ inputs.runtime-ref }}");
46
46
  assert.equal(contract.bootstrap.selfDogfoodActive, false);
47
47
  assert.equal(fs.existsSync(path.join(root, contract.bootstrap.selfDogfoodWorkflow)), false,
@@ -41,4 +41,4 @@ export function generateChannelPromotionWorkflow(source) {
41
41
  }, { lineWidth: 0, aliasDuplicateObjects: false });
42
42
  }
43
43
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url))
44
- writeWorkflowSource(root, ".github/workflows/public-release-promote.yml", generateChannelPromotionWorkflow(fs.readFileSync(path.join(root, ".github/workflows/.release-promote.yml"), "utf8")));
44
+ writeWorkflowSource(root, ".github/workflows/.release-candidate-promote.yml", generateChannelPromotionWorkflow(fs.readFileSync(path.join(root, ".github/workflows/.release-promote.yml"), "utf8")));
@@ -5,6 +5,7 @@ import { parse as yaml } from "yaml";
5
5
  import { format } from "prettier";
6
6
  import { standardConsumerExample } from "../packages/core/consumer/contract/examples.js";
7
7
  import { inspectConsumerContract } from "../packages/core/consumer/contract/inspection.js";
8
+ import { validateConsumerWiring } from "../packages/core/consumer/contract/local-validation.js";
8
9
  import { workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
9
10
 
10
11
  const root = process.cwd(),
@@ -13,6 +14,25 @@ const tracked = execFileSync("git", ["ls-files", "-z"], { encoding: "utf8" })
13
14
  .split("\0")
14
15
  .filter(Boolean);
15
16
 
17
+ if (check) {
18
+ const files = Object.create(null);
19
+ for (const file of tracked)
20
+ Object.defineProperty(files, file, {
21
+ enumerable: true,
22
+ get: () => fs.readFileSync(path.join(root, file), "utf8"),
23
+ });
24
+ const { channel } = validateConsumerWiring(root);
25
+ const inspection = inspectConsumerContract(files, { channel });
26
+ if (inspection.controlIssues.length)
27
+ throw new Error(inspection.controlIssues.join("\n"));
28
+ const unresolved = inspection.commandClosures.filter(
29
+ (closure) => !closure.commandEdgesResolved,
30
+ ).length;
31
+ console.log(
32
+ `Self control wiring verified; ${unresolved} product graphs remain unresolved and require read-only runtime execution.`,
33
+ );
34
+ }
35
+
16
36
  function output(file, value) {
17
37
  const target = path.join(root, file);
18
38
  if (check) {
@@ -143,7 +163,7 @@ output(
143
163
  await format(
144
164
  JSON.stringify({
145
165
  schema: "buildchain.consumer-migration/v1",
146
- stage: "self-cutover-in-progress; final-release-qualification-pending",
166
+ stage: "self-default-entry; final-release-qualification-pending",
147
167
  workflows: workflowInventory(),
148
168
  consumerSources: consumerSourceInventory(),
149
169
  }),
@@ -887,8 +887,9 @@ function buildSiteBundle() {
887
887
  ["self-hosted-runner-smoke", "compatibility-fixture"],
888
888
  ]);
889
889
  const engineeringHousekeeper = semanticId.startsWith("engineering-housekeeper");
890
+ if (classification?.role === "component") statusById.set(semanticId, "repository-internal");
890
891
  return {
891
- ...entry,
892
+ ...entry, apiRole: classification?.role === "public" ? "public" : "implementation",
892
893
  ...(classification ? { taxonomy: { id: classification.id, role: classification.role, category: classification.category, purpose: classification.purpose, status: classification.status, canonicalPath: workflowPath(classification), ...(classification.role === "self" && classification.migration ? { contractPath: classification.migration.previousPath } : {}), compatibilityAlias: entry.path !== workflowPath(classification) } } : {}),
893
894
  surface: surfaceById.get(semanticId) || (entry.path.includes("/.") ? "reusable-workflow" : "repository-workflow"),
894
895
  capabilityGroup: workflowCapabilityGroup({
@@ -19,8 +19,8 @@ export function tailResealFixturePolicyReceipt(fixture, policy) {
19
19
  try {
20
20
  fs.mkdirSync(path.join(root, ".github/workflows"), { recursive: true });
21
21
  fs.writeFileSync(
22
- path.join(root, ".github/workflows/build.yml"),
23
- "jobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml@v4-alpha\n",
22
+ path.join(root, ".github/workflows/.build-candidate.yml"),
23
+ "jobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build-stage-capsule-canary.yml@v4-alpha\n",
24
24
  );
25
25
  fs.mkdirSync(path.join(root, ".buildchain"), { recursive: true });
26
26
  for (const [file, ref, resolvedSha] of [
@@ -51,7 +51,7 @@ export function tailResealFixturePolicyReceipt(fixture, policy) {
51
51
  root,
52
52
  repository: fixture.repository,
53
53
  sourceSha: fixture.source.sha,
54
- invokedWorkflow: "public-build-stage-capsule-canary.yml",
54
+ invokedWorkflow: ".build-stage-capsule-canary.yml",
55
55
  expectedInvocationChannel: "alpha",
56
56
  resolvedRuntimeSha: fixture.runtime.sha,
57
57
  policy,
@@ -8,7 +8,7 @@ import { generateChannelPromotionWorkflow } from "../generate-channel-promotion-
8
8
 
9
9
  export function assertPromotionInventory(root) {
10
10
  const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
11
- const publicPath = ".github/workflows/public-release-promote.yml";
11
+ const publicPath = ".github/workflows/.release-candidate-promote.yml";
12
12
  const api = YAML.parse(read(publicPath));
13
13
  assert.equal(
14
14
  read(publicPath),