@kungfu-tech/buildchain 4.0.2-alpha.8 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/AGENTS.md +10 -1
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/actions/v4-release-candidate-promote/README.md +28 -0
  6. package/architecture/agent-change-map.md +64 -7
  7. package/architecture/ci-lane-change-budget.json +366 -0
  8. package/architecture/internal-capabilities.json +53 -8
  9. package/architecture/maintainability-debt.json +147 -93
  10. package/architecture/maintainability-policy.json +32 -28
  11. package/architecture/release-tail-contract-inventory.json +6 -6
  12. package/architecture/v4-architecture-constitution.md +23 -20
  13. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  14. package/architecture/v4-delivery-authority-parity.json +2 -2
  15. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  16. package/architecture/v4-floating-consumer-policy.json +2 -2
  17. package/architecture/v4-next-development-parity.json +5 -5
  18. package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
  19. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  20. package/architecture/v4-provider-operation-journal-contract.json +5 -2
  21. package/architecture/v4-publication-facade-patches.json +64 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +74 -39
  24. package/architecture/v4-rust-wasm-production-authority.json +58 -0
  25. package/architecture/v4-stable-publication-fence.json +5 -2
  26. package/architecture/v4-stage-capsule-qualification.json +2 -2
  27. package/architecture/v4-tail-reseal-parity.json +8 -6
  28. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  29. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  30. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  31. package/architecture/workflow-taxonomy.json +1451 -0
  32. package/bin/buildchain.mjs +7 -7
  33. package/contracts/buildchain-v2-residuals-v1.json +57 -48
  34. package/contracts/dev-delivery-authority-v2.schema.json +16 -2
  35. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  36. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  37. package/dist/site/buildchain-contract.json +93 -48
  38. package/dist/site/buildchain-site.json +222 -69
  39. package/dist/site/capability-registry.json +4 -4
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2585 -1000
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +13 -13
  44. package/dist/site/node-api-registry.json +120 -115
  45. package/dist/site/page-registry.json +204 -51
  46. package/dist/site/public-surface-audit.json +3205 -1219
  47. package/dist/site/publication-authority-registry.json +1124 -362
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  50. package/dist/site/site-manifest.json +17 -17
  51. package/dist/site/workflow-registry.json +5061 -1659
  52. package/docs/MAP.md +1 -0
  53. package/docs/auditable-demo.md +1 -1
  54. package/docs/cli.md +21 -8
  55. package/docs/dev-delivery-warrant.md +1 -1
  56. package/docs/engineering-housekeeper.md +3 -3
  57. package/docs/kfd-support.md +26 -2
  58. package/docs/next-development-transition.md +2 -2
  59. package/docs/node-api-reference.md +190 -190
  60. package/docs/publication-authority.md +2 -2
  61. package/docs/release-candidate.md +3 -3
  62. package/docs/release-flow.md +67 -19
  63. package/docs/release-governance.md +8 -8
  64. package/docs/release-passport.md +27 -2
  65. package/docs/reusable-build-surface.md +16 -0
  66. package/docs/v4-adopter-delivery.md +2 -2
  67. package/docs/v4-production-release.md +71 -8
  68. package/docs/v4-rust-wasm-production-authority.md +58 -0
  69. package/docs/v4-stage-capsule.md +2 -2
  70. package/docs/v4-tail-reseal.md +1 -1
  71. package/docs/workflow-catalog.md +154 -0
  72. package/docs/workflow-path-migration.md +109 -0
  73. package/package.json +10 -6
  74. package/packages/core/artifact-verification-envelope.js +5 -5
  75. package/packages/core/buildchain-contract.js +1 -1
  76. package/packages/core/buildchain-kfd-claims.js +2 -2
  77. package/packages/core/buildchain-publication-authority.js +42 -26
  78. package/packages/core/buildchain-v4-domain.wasm +0 -0
  79. package/packages/core/ci-lane-change-budget.js +14 -1
  80. package/packages/core/controller-evidence.js +4 -1
  81. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  82. package/packages/core/dev-delivery-warrant-state.js +3 -4
  83. package/packages/core/dev-delivery-warrant.js +12 -2
  84. package/packages/core/github-governance-authority.js +4 -4
  85. package/packages/core/kfd-adopter-manifest.js +46 -47
  86. package/packages/core/next-development-projection.js +2 -2
  87. package/packages/core/paper-agent-entry.js +30 -4
  88. package/packages/core/paper-fleet.js +1 -1
  89. package/packages/core/paper-runtime-channels.js +348 -0
  90. package/packages/core/paper-scaffold-content.js +10 -0
  91. package/packages/core/paper.js +37 -71
  92. package/packages/core/release-line-dry-run.js +1 -1
  93. package/packages/core/release-passport-contract.js +5 -5
  94. package/packages/core/release-passport.js +5 -4
  95. package/packages/core/release-tail-provider-plane.js +68 -1048
  96. package/packages/core/v4-canonical-contracts.js +10 -75
  97. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  98. package/packages/core/v4-domain-wasm.js +216 -0
  99. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  100. package/packages/core/v4-floating-consumer-policy.js +1 -1
  101. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  102. package/packages/core/v4-product-publication.js +19 -349
  103. package/packages/core/v4-protected-publication-source.js +19 -21
  104. package/packages/core/v4-provider-operation-journal.js +18 -487
  105. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  106. package/packages/core/v4-release-activation-shadow.js +13 -500
  107. package/packages/core/v4-release-invocation.js +28 -389
  108. package/packages/core/v4-stable-publication-fence.js +13 -342
  109. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  110. package/scripts/binary-publication-evidence.mjs +135 -0
  111. package/scripts/build-contract-core.mjs +1 -14
  112. package/scripts/build-v4-domain-wasm.mjs +189 -0
  113. package/scripts/check-action-bundles.mjs +12 -2
  114. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  115. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  116. package/scripts/check-inventory.mjs +35 -44
  117. package/scripts/check-maintainability.mjs +16 -113
  118. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  119. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  120. package/scripts/check-v4-release-topology.mjs +90 -33
  121. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  122. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  123. package/scripts/check-workflow-taxonomy.mjs +6 -0
  124. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  125. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  126. package/scripts/dev-delivery-authority.mjs +8 -8
  127. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  128. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  129. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  130. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  131. package/scripts/dev-delivery-warrant.mjs +76 -99
  132. package/scripts/dev-pr-auto-merge.mjs +15 -15
  133. package/scripts/generate-channel-build-workflow.mjs +7 -2
  134. package/scripts/generate-channel-promotion-workflow.mjs +7 -5
  135. package/scripts/generate-site-bundle.mjs +11 -11
  136. package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
  137. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  138. package/scripts/github-output.mjs +31 -0
  139. package/scripts/maintainability-metrics.mjs +16 -5
  140. package/scripts/maintainability-public-surface.mjs +115 -0
  141. package/scripts/next-development-review.mjs +307 -0
  142. package/scripts/paper-work-fleet-cli.mjs +2 -0
  143. package/scripts/release-asset-client.mjs +91 -0
  144. package/scripts/release-candidate-resolver.mjs +9 -9
  145. package/scripts/source-verification-evidence.mjs +344 -0
  146. package/scripts/stable-candidate-qualification.mjs +1 -1
  147. package/scripts/universal-facade-maintainability.mjs +82 -0
  148. package/scripts/v4-product-publication-intent.mjs +13 -6
  149. package/scripts/v4-publication-settlement.mjs +211 -0
  150. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  151. package/scripts/v4-universal-workflow-engine.mjs +618 -0
  152. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  153. package/scripts/verify-version-state-delta.mjs +173 -0
  154. package/scripts/workflow-taxonomy.mjs +507 -0
  155. package/templates/native-dev-delivery.yml +4 -7
  156. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  157. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -0,0 +1,507 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import {
5
+ parseWorkflowDocument,
6
+ parseYamlUses,
7
+ } from "../packages/core/workflow-yaml-contract.js";
8
+
9
+ export const TAXONOMY_PATH = "architecture/workflow-taxonomy.json";
10
+ export const TAXONOMY_DOC = "docs/workflow-catalog.md";
11
+ const ROLES = ["public", "component", "self"];
12
+ const CATEGORIES = ["build", "release", "ops"];
13
+ const SLUG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
14
+ const WORKFLOW = /^\.github\/workflows\/[.a-z0-9-]+\.ya?ml$/u;
15
+ const VERSION_TOKEN = /(?:^|[.-])v\d+(?:[.-]|$)/iu;
16
+ const PROTECTED = [
17
+ TAXONOMY_PATH,
18
+ "scripts/workflow-taxonomy.mjs",
19
+ "scripts/check-workflow-taxonomy.mjs",
20
+ "scripts/generate-workflow-taxonomy.mjs",
21
+ "scripts/check-workflows.sh",
22
+ "tests/workflow-taxonomy.test.mjs",
23
+ ".github/workflows/self-build-verify.yml",
24
+ "package.json",
25
+ ".buildchain/buildchain.toml",
26
+ ];
27
+
28
+ export function workflowPath(entry) {
29
+ const prefix = entry.role === "component" ? "." : `${entry.role}-`;
30
+ return `.github/workflows/${prefix}${entry.category}-${entry.purpose}.yml`;
31
+ }
32
+
33
+ export function readWorkflowTaxonomy(root) {
34
+ const file = path.join(root, TAXONOMY_PATH);
35
+ return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : null;
36
+ }
37
+
38
+ function validateEntryRole(entry, errors) {
39
+ if (!ROLES.includes(entry.role)) errors.push(`${entry.id}: invalid role`);
40
+ if (!CATEGORIES.includes(entry.category))
41
+ errors.push(`${entry.id}: invalid category`);
42
+ if (!SLUG.test(entry.purpose || ""))
43
+ errors.push(`${entry.id}: invalid purpose slug`);
44
+ if (VERSION_TOKEN.test(path.posix.basename(workflowPath(entry))))
45
+ errors.push(
46
+ `${entry.id}: workflow filenames cannot contain version tokens`,
47
+ );
48
+ for (const field of ["summary", "owner", "rationale"]) {
49
+ if (typeof entry[field] !== "string" || !entry[field].trim())
50
+ errors.push(`${entry.id}: missing ${field}`);
51
+ }
52
+ if (!["active", "preview", "compatibility", "retired"].includes(entry.status))
53
+ errors.push(`${entry.id}: invalid lifecycle status`);
54
+ if (
55
+ !["reusable", "dispatch-service", "repository"].includes(entry.invocation)
56
+ )
57
+ errors.push(`${entry.id}: invalid invocation`);
58
+ if ((entry.role === "self") !== (entry.invocation === "repository"))
59
+ errors.push(`${entry.id}: role and invocation disagree`);
60
+ if (entry.role === "component" && entry.invocation !== "reusable")
61
+ errors.push(`${entry.id}: component must be reusable`);
62
+ }
63
+
64
+ function validateMigration(policy, entry, declared, errors) {
65
+ if (entry.retiredAlias) {
66
+ if (
67
+ entry.compatibility ||
68
+ entry.retiredAlias.path !== entry.migration?.previousPath ||
69
+ !entry.retiredAlias.reason?.trim() ||
70
+ entry.retiredAlias.migrationGuide !== "docs/workflow-path-migration.md"
71
+ )
72
+ errors.push(`${entry.id}: invalid alias retirement record`);
73
+ }
74
+ if (entry.migration) {
75
+ if (
76
+ !WORKFLOW.test(entry.migration.previousPath || "") ||
77
+ !/^[a-f0-9]{64}$/u.test(entry.migration.originalSha256 || "")
78
+ )
79
+ errors.push(`${entry.id}: invalid exact migration identity`);
80
+ if (
81
+ policy.migrationSources?.[entry.migration.previousPath] !==
82
+ entry.migration.originalSha256
83
+ )
84
+ errors.push(
85
+ `${entry.id}: migration source is not in the reviewed baseline`,
86
+ );
87
+ }
88
+ if (entry.compatibility) {
89
+ const alias = entry.compatibility;
90
+ if (VERSION_TOKEN.test(path.posix.basename(alias.path || "")))
91
+ errors.push(
92
+ `${entry.id}: version-prefixed compatibility aliases are retired`,
93
+ );
94
+ if (entry.role === "self")
95
+ errors.push(
96
+ `${entry.id}: repository event wrappers cannot have duplicate aliases`,
97
+ );
98
+ if (
99
+ !WORKFLOW.test(alias.path || "") ||
100
+ alias.path !== entry.migration?.previousPath
101
+ )
102
+ errors.push(
103
+ `${entry.id}: compatibility must name one exact migrated path`,
104
+ );
105
+ if (!alias.reason?.trim() || !alias.removalCondition?.trim())
106
+ errors.push(
107
+ `${entry.id}: compatibility requires a reason and removal condition`,
108
+ );
109
+ declared.push(alias.path);
110
+ }
111
+ }
112
+
113
+ function validateEntries(policy, errors) {
114
+ if (policy?.schema !== "buildchain.workflow-taxonomy/v1")
115
+ errors.push("taxonomy schema is missing or unknown");
116
+ if (JSON.stringify(policy?.roles) !== JSON.stringify(ROLES))
117
+ errors.push("role vocabulary must be public/component/self");
118
+ if (JSON.stringify(policy?.categories) !== JSON.stringify(CATEGORIES))
119
+ errors.push("category vocabulary must be build/release/ops");
120
+ if (!/^[a-f0-9]{40}$/u.test(policy?.migrationBaseRevision || ""))
121
+ errors.push("migration baseline must be an exact commit");
122
+ if (!Array.isArray(policy?.entries) || !policy.entries.length) {
123
+ errors.push("taxonomy entries are missing");
124
+ return [];
125
+ }
126
+ const ids = new Set();
127
+ const paths = new Set();
128
+ for (const entry of policy.entries) {
129
+ if (!entry.id || ids.has(entry.id))
130
+ errors.push(`duplicate or missing identity: ${entry.id}`);
131
+ ids.add(entry.id);
132
+ validateEntryRole(entry, errors);
133
+ const canonical = workflowPath(entry);
134
+ const declared = [canonical];
135
+ validateMigration(policy, entry, declared, errors);
136
+ for (const file of declared) {
137
+ if (!WORKFLOW.test(file) || paths.has(file))
138
+ errors.push(`${entry.id}: invalid or duplicate path ${file}`);
139
+ paths.add(file);
140
+ }
141
+ }
142
+ return policy.entries;
143
+ }
144
+
145
+ export function discoverWorkflowFiles(root) {
146
+ const directory = path.join(root, ".github/workflows");
147
+ if (!fs.existsSync(directory)) return [];
148
+ const visit = (dir) =>
149
+ fs.readdirSync(dir, { withFileTypes: true }).flatMap((item) => {
150
+ const file = path.join(dir, item.name);
151
+ if (item.isDirectory()) return visit(file);
152
+ return /\.ya?ml$/iu.test(item.name)
153
+ ? [path.relative(root, file).split(path.sep).join("/")]
154
+ : [];
155
+ });
156
+ return visit(directory).sort();
157
+ }
158
+
159
+ function validateRepositoryReferences(file, document, text, declared, errors) {
160
+ const references = document.callJobs.flatMap((job) =>
161
+ Object.entries(job.with)
162
+ .filter(
163
+ ([key, value]) =>
164
+ /(?:source|handoff)-workflow-id$|(?:expected|candidate)-workflow-file$/u.test(
165
+ key,
166
+ ) && value.kind === "string",
167
+ )
168
+ .map(([, value]) => value.value),
169
+ );
170
+ for (const match of text.matchAll(
171
+ /\bgh workflow run ([.a-z0-9-]+\.ya?ml)\b/gu,
172
+ ))
173
+ references.push(match[1]);
174
+ for (const name of references) {
175
+ const target = name.startsWith(".github/")
176
+ ? name
177
+ : `.github/workflows/${name}`;
178
+ if (!declared.has(target))
179
+ errors.push(`${file}: dangling repository workflow reference ${name}`);
180
+ }
181
+ }
182
+
183
+ function validateWorkflow(root, entry, declared, errors) {
184
+ const file = workflowPath(entry);
185
+ const absolute = path.join(root, file);
186
+ if (!fs.existsSync(absolute) || !fs.lstatSync(absolute).isFile()) return;
187
+ const text = fs.readFileSync(absolute, "utf8");
188
+ if (
189
+ entry.retiredAlias &&
190
+ text.includes("BUILDCHAIN_INVOKED_WORKFLOW:") &&
191
+ !text.includes(`BUILDCHAIN_INVOKED_WORKFLOW: ${file}\n`)
192
+ )
193
+ errors.push(
194
+ `${file}: consumer admission must bind the canonical invoked workflow`,
195
+ );
196
+ const document = parseWorkflowDocument(text);
197
+ if (entry.role === "self")
198
+ validateRepositoryReferences(file, document, text, declared, errors);
199
+ const reusable = document.triggers.includes("workflow_call");
200
+ if ((entry.invocation === "reusable") !== reusable)
201
+ errors.push(`${file}: workflow_call does not match registered invocation`);
202
+ if (
203
+ entry.invocation === "dispatch-service" &&
204
+ !document.triggers.includes("workflow_dispatch")
205
+ )
206
+ errors.push(`${file}: dispatch service lacks workflow_dispatch`);
207
+ if (
208
+ entry.role !== "self" &&
209
+ document.triggers.some(
210
+ (trigger) => !["workflow_call", "workflow_dispatch"].includes(trigger),
211
+ )
212
+ )
213
+ errors.push(
214
+ `${file}: public/component entry has repository event triggers`,
215
+ );
216
+ if (entry.compatibility) {
217
+ const alias = path.join(root, entry.compatibility.path);
218
+ if (fs.existsSync(alias) && fs.readFileSync(alias, "utf8") !== text)
219
+ errors.push(
220
+ `${file}: compatibility implementation drift at ${entry.compatibility.path}`,
221
+ );
222
+ }
223
+ for (const call of parseYamlUses(text)) {
224
+ const remote = call.value.match(
225
+ /^kungfu-systems\/buildchain\/(\.github\/workflows\/[^@]+)@/u,
226
+ );
227
+ if (remote && VERSION_TOKEN.test(path.posix.basename(remote[1])))
228
+ errors.push(
229
+ `${file}:${call.line}: retired version-prefixed workflow call ${remote[1]}`,
230
+ );
231
+ const local = call.value.match(/^\.\/(\.github\/workflows\/[^@]+)$/u);
232
+ if (local && !declared.has(local[1]))
233
+ errors.push(
234
+ `${file}:${call.line}: dangling workflow reference ${local[1]}`,
235
+ );
236
+ }
237
+ }
238
+
239
+ function validateGateIntegration(root, errors) {
240
+ const read = (file) =>
241
+ fs.existsSync(path.join(root, file))
242
+ ? fs.readFileSync(path.join(root, file), "utf8")
243
+ : "";
244
+ const pkg = JSON.parse(read("package.json") || "{}");
245
+ const command = pkg.scripts?.["check:workflows"];
246
+ if (
247
+ command !==
248
+ "node scripts/check-workflow-taxonomy.mjs && bash scripts/check-workflows.sh"
249
+ )
250
+ errors.push(
251
+ "check:workflows must execute taxonomy before actionlint without a bypass",
252
+ );
253
+ if (
254
+ !/(?:^|&&)\s*pnpm run check:workflows\s*(?:&&|$)/u.test(
255
+ pkg.scripts?.check || "",
256
+ )
257
+ )
258
+ errors.push("required check chain does not enforce check:workflows");
259
+ const { parse: parseToml } = createRequire(import.meta.url)("smol-toml");
260
+ const config = parseToml(read(".buildchain/buildchain.toml"));
261
+ if (
262
+ !config.lifecycle?.verify?.commands?.includes(
263
+ "corepack pnpm@11.7.0 run check",
264
+ )
265
+ )
266
+ errors.push(
267
+ "declared verify lifecycle must execute the full required check",
268
+ );
269
+ const verify = read(".github/workflows/self-build-verify.yml");
270
+ const document = parseWorkflowDocument(verify);
271
+ for (const trigger of ["pull_request", "merge_group", "push"]) {
272
+ if (!document.triggers.some((item) => item.split(":")[0] === trigger))
273
+ errors.push(`required Verify workflow lacks ${trigger}`);
274
+ }
275
+ if (
276
+ !verify.includes("Run declared verify lifecycle") ||
277
+ !verify.includes("--required")
278
+ )
279
+ errors.push("required Verify lifecycle integration is missing");
280
+ const owners = read(".github/CODEOWNERS")
281
+ .split(/\r?\n/u)
282
+ .map((line) => line.trim());
283
+ for (const file of PROTECTED) {
284
+ if (!owners.includes(`/${file} @kungfu-origin`))
285
+ errors.push(`independent review ownership missing for ${file}`);
286
+ }
287
+ if (
288
+ !owners.includes("/.github/workflows/* @kungfu-origin") ||
289
+ !owners.includes("/.github/CODEOWNERS @kungfu-origin")
290
+ )
291
+ errors.push(
292
+ "workflow and CODEOWNERS review ownership must remain explicit",
293
+ );
294
+ }
295
+
296
+ export function checkWorkflowTaxonomy(
297
+ root,
298
+ { integration = true, documentation = true } = {},
299
+ ) {
300
+ const errors = [];
301
+ const policy = readWorkflowTaxonomy(root);
302
+ const entries = validateEntries(policy, errors);
303
+ if (errors.length) return { ok: false, errors };
304
+ const declared = new Set(
305
+ entries.flatMap((entry) => [
306
+ workflowPath(entry),
307
+ ...(entry.compatibility ? [entry.compatibility.path] : []),
308
+ ]),
309
+ );
310
+ const observed = new Set(discoverWorkflowFiles(root));
311
+ for (const file of observed) {
312
+ if (VERSION_TOKEN.test(path.posix.basename(file)))
313
+ errors.push(`workflow filenames cannot contain version tokens: ${file}`);
314
+ if (!declared.has(file)) errors.push(`unregistered workflow: ${file}`);
315
+ if (!fs.lstatSync(path.join(root, file)).isFile())
316
+ errors.push(`workflow must be a regular file: ${file}`);
317
+ }
318
+ for (const file of declared)
319
+ if (!observed.has(file))
320
+ errors.push(`registered workflow missing: ${file}`);
321
+ for (const entry of entries) validateWorkflow(root, entry, declared, errors);
322
+ if (integration) validateGateIntegration(root, errors);
323
+ if (documentation) {
324
+ const file = path.join(root, TAXONOMY_DOC);
325
+ if (
326
+ !fs.existsSync(file) ||
327
+ fs.readFileSync(file, "utf8") !== renderWorkflowCatalog(policy)
328
+ )
329
+ errors.push("generated workflow catalog is stale");
330
+ }
331
+ return {
332
+ ok: errors.length === 0,
333
+ canonicalCount: entries.length,
334
+ compatibilityCount: entries.filter((entry) => entry.compatibility).length,
335
+ fileCount: observed.size,
336
+ errors,
337
+ };
338
+ }
339
+
340
+ // Existing lane/debt baselines name immutable pre-migration identities. Count
341
+ // one logical implementation only after proving every physical alias identical.
342
+ export function projectWorkflowIdentities(root, workflows) {
343
+ const policy = readWorkflowTaxonomy(root);
344
+ if (!policy) return workflows;
345
+ const result = checkWorkflowTaxonomy(root, {
346
+ integration: false,
347
+ documentation: false,
348
+ });
349
+ if (!result.ok) throw new Error(result.errors.join("\n"));
350
+ const byPath = new Map(
351
+ workflows.map((workflow) => [workflow.path, workflow]),
352
+ );
353
+ return policy.entries
354
+ .map((entry) => {
355
+ const workflow = byPath.get(workflowPath(entry));
356
+ if (!workflow)
357
+ throw new Error(`missing canonical workflow: ${workflowPath(entry)}`);
358
+ return {
359
+ ...workflow,
360
+ path: entry.migration?.previousPath || workflow.path,
361
+ };
362
+ })
363
+ .sort((a, b) => a.path.localeCompare(b.path));
364
+ }
365
+
366
+ export function writeWorkflowSource(root, relative, text) {
367
+ const policy = readWorkflowTaxonomy(root);
368
+ const entry = policy?.entries.find(
369
+ (item) =>
370
+ workflowPath(item) === relative || item.compatibility?.path === relative,
371
+ );
372
+ if (policy?.entries.some((item) => item.retiredAlias?.path === relative))
373
+ throw new Error(`${relative}: cannot regenerate a retired workflow alias`);
374
+ const outputs = entry
375
+ ? [
376
+ workflowPath(entry),
377
+ ...(entry.compatibility ? [entry.compatibility.path] : []),
378
+ ]
379
+ : [relative];
380
+ for (const file of outputs) fs.writeFileSync(path.join(root, file), text);
381
+ }
382
+
383
+ export function rewriteRepositoryWorkflowPaths(root, text) {
384
+ for (const entry of readWorkflowTaxonomy(root)?.entries || []) {
385
+ if (entry.role === "self" && entry.migration)
386
+ text = text.replaceAll(entry.migration.previousPath, workflowPath(entry));
387
+ if (entry.retiredAlias)
388
+ text = text.replaceAll(entry.retiredAlias.path, workflowPath(entry));
389
+ if (entry.id === "build-surface-fixture")
390
+ text = text.replaceAll(
391
+ "build-surface-fixture.yml",
392
+ path.posix.basename(workflowPath(entry)),
393
+ );
394
+ }
395
+ return text;
396
+ }
397
+
398
+ // Historical inventories and debt ledgers retain their original identity. File
399
+ // access resolves only explicitly retired aliases to the registered implementation.
400
+ export function currentWorkflowPath(root, relative) {
401
+ const entry = readWorkflowTaxonomy(root)?.entries.find(
402
+ (item) => item.retiredAlias?.path === relative,
403
+ );
404
+ return entry ? workflowPath(entry) : relative;
405
+ }
406
+
407
+ export function workflowCompatibilityIdentity(root, relative, text) {
408
+ const entry = readWorkflowTaxonomy(root)?.entries.find(
409
+ (item) => workflowPath(item) === relative && item.compatibility,
410
+ );
411
+ if (!entry) return relative;
412
+ const source = text ?? fs.readFileSync(path.join(root, relative), "utf8");
413
+ if (
414
+ source !==
415
+ fs.readFileSync(path.join(root, entry.compatibility.path), "utf8")
416
+ )
417
+ throw new Error(`${relative}: compatibility implementation drift`);
418
+ return entry.compatibility.path;
419
+ }
420
+
421
+ export function renderWorkflowCatalog(policy) {
422
+ const lines = [
423
+ "---",
424
+ "status: active",
425
+ "period: ongoing",
426
+ "theme: workflow-taxonomy",
427
+ "doc_type: technical-reference",
428
+ "source_level: local-files",
429
+ "confidence: high",
430
+ "sensitivity: public",
431
+ "evidence_grade: B",
432
+ "review_state: unreviewed",
433
+ "last_reviewed: 2026-09-05",
434
+ "ai_provenance:",
435
+ " model_family: GPT-6",
436
+ " product: Codex",
437
+ " generated_at: 2026-09-05",
438
+ " visible_context: Repository workflow source and the approved naming scheme",
439
+ " invisible_context_boundary: No credentials or unpublished provider configuration inspected",
440
+ "---",
441
+ "",
442
+ "# Workflow catalog",
443
+ "",
444
+ "Generated from `architecture/workflow-taxonomy.json`; edit that source, then run",
445
+ "`pnpm run generate:workflows`. Every YAML file, including compatibility paths,",
446
+ "is checked by the existing required workflow check.",
447
+ "",
448
+ "## Naming and ownership",
449
+ "",
450
+ "`public-<build|release|ops>-<purpose>.yml` is a consumer entry;",
451
+ "`.<build|release|ops>-<purpose>.yml` is an advanced component;",
452
+ "`self-<build|release|ops>-<purpose>.yml` is Buildchain's own automation.",
453
+ "Public is a product role, not GitHub access control. Dispatch services are",
454
+ "marked separately from reusable workflows. Keep workflow names, job IDs,",
455
+ "permissions, and trigger semantics stable during path migration.",
456
+ "",
457
+ "## Channel rollout",
458
+ "",
459
+ "Canonical names become callable on a channel only after that channel publishes",
460
+ "them. Existing `@v4`/`@v4-alpha` consumers keep their exact registered legacy",
461
+ "paths until provider readback proves canonical availability. New examples below",
462
+ "describe the target surface, not a claim that both channels already publish it.",
463
+ "Compatibility files are generated byte-identical projections, not independently",
464
+ "editable implementations. They retain full jobs to preserve called-workflow",
465
+ "identity, permission envelopes, and required check names without extra nesting.",
466
+ "",
467
+ "The controller registry records the actual source path separately from a migrated",
468
+ "repository controller contractPath. This preserves the established logical",
469
+ "contract identity while exposing its current execution source. New workflows",
470
+ "do not acquire historical identities. Public controllers retain their real",
471
+ "compatibility paths while those paths remain supported.",
472
+ "",
473
+ "## Adding or changing a workflow",
474
+ "",
475
+ "Register a stable identity, role, category, purpose, owner, lifecycle status,",
476
+ "invocation and concrete summary before creating its derived path. A new entry",
477
+ "must explain its scope in review. New aliases cannot be invented: compatibility",
478
+ "must name an exact migration source with a reason and removal condition.",
479
+ "The policy, generator, checker, tests, required CI chain and CODEOWNERS require",
480
+ "independent `kungfu-origin` review. No wildcard compatibility admission is allowed.",
481
+ "Workflow filenames cannot contain version tokens such as `v4` or `v5`.",
482
+ "Retired aliases cannot be recreated or called; see [path migration](workflow-path-migration.md).",
483
+ "",
484
+ "A role, category or implementation edit must preserve or deliberately update",
485
+ "all related declarations. Run `pnpm run generate:workflows` and",
486
+ "`pnpm run check:workflows`; the full `pnpm run check` remains required.",
487
+ "",
488
+ ];
489
+ for (const role of ROLES) {
490
+ lines.push(
491
+ `## ${role}`,
492
+ "",
493
+ "| Canonical workflow | Category | Invocation | Status | Purpose | Compatibility path |",
494
+ "| --- | --- | --- | --- | --- | --- |",
495
+ );
496
+ for (const entry of policy.entries
497
+ .filter((item) => item.role === role)
498
+ .sort((a, b) => workflowPath(a).localeCompare(workflowPath(b)))) {
499
+ const name = path.posix.basename(workflowPath(entry));
500
+ lines.push(
501
+ `| [${name}](../${workflowPath(entry)}) | ${entry.category} | ${entry.invocation} | ${entry.status} | ${entry.summary.replaceAll("|", "\\|")} | ${entry.compatibility ? `\`${path.posix.basename(entry.compatibility.path)}\`` : "—"} |`,
502
+ );
503
+ }
504
+ lines.push("");
505
+ }
506
+ return `${lines.join("\n").trimEnd()}\n`;
507
+ }
@@ -38,11 +38,8 @@ on:
38
38
  source-patch-root:
39
39
  description: "Semantic patch root"
40
40
  required: true
41
- assignment-root:
42
- description: "Native Assignment root"
43
- required: true
44
- initiative-root:
45
- description: "Parent Initiative root"
41
+ native-roots-json:
42
+ description: "Machine-supplied Work sourceRoot or historical root pair"
46
43
  required: true
47
44
  plan-root:
48
45
  description: "Native qualification plan root"
@@ -117,8 +114,8 @@ jobs:
117
114
  delivery-warrant-mode: required
118
115
  handoff-workflow-id: native-dev-delivery.yml
119
116
  source-workflow-id: ${{ inputs.source-workflow-id || '' }}
120
- assignment-root: ${{ github.event.client_payload.candidate.assignmentRoot || inputs.assignment-root }}
121
- initiative-root: ${{ github.event.client_payload.candidate.initiativeRoot || inputs.initiative-root }}
117
+ assignment-root: ${{ github.event.client_payload.candidate.assignmentRoot || fromJSON(inputs.native-roots-json || '{}').assignmentRoot || '' }}
118
+ initiative-root: ${{ github.event.client_payload.candidate.initiativeRoot || fromJSON(inputs.native-roots-json || '{}').initiativeRoot || '' }}
122
119
  source-identity-root: ${{ github.event.client_payload.candidate.sourceIdentityRoot || inputs.source-identity-root }}
123
120
  source-patch-root: ${{ github.event.client_payload.candidate.sourcePatchRoot || inputs.source-patch-root }}
124
121
  plan-root: ${{ github.event.client_payload.candidate.planRoot || inputs.plan-root }}