@kungfu-tech/buildchain 4.0.2-alpha.34 → 4.0.2-alpha.36

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 (93) hide show
  1. package/AGENTS.md +9 -0
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/architecture/agent-change-map.md +7 -4
  6. package/architecture/ci-lane-change-budget.json +17 -0
  7. package/architecture/internal-capabilities.json +4 -4
  8. package/architecture/maintainability-debt.json +14 -11
  9. package/architecture/maintainability-policy.json +4 -4
  10. package/architecture/release-tail-contract-inventory.json +6 -6
  11. package/architecture/v4-delivery-authority-parity.json +2 -2
  12. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  13. package/architecture/v4-floating-consumer-policy.json +1 -1
  14. package/architecture/v4-next-development-parity.json +4 -4
  15. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  16. package/architecture/v4-publication-facade-patches.json +53 -0
  17. package/architecture/v4-release-topology.json +56 -33
  18. package/architecture/v4-rust-wasm-production-authority.json +1 -0
  19. package/architecture/v4-stage-capsule-qualification.json +1 -1
  20. package/architecture/v4-universal-workflow-bootstrap.json +1 -1
  21. package/architecture/v4-universal-workflow-fault-campaign.json +1 -1
  22. package/architecture/v4-universal-workflow-train-admission.json +4 -4
  23. package/architecture/workflow-taxonomy.json +1448 -0
  24. package/contracts/buildchain-v2-residuals-v1.json +64 -1
  25. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  26. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  27. package/dist/site/buildchain-contract.json +20 -20
  28. package/dist/site/buildchain-site.json +127 -56
  29. package/dist/site/capability-registry.json +4 -4
  30. package/dist/site/controller-registry.json +5 -4
  31. package/dist/site/kfd-claims.json +2623 -956
  32. package/dist/site/kfd-upstream-aggregate.json +1 -1
  33. package/dist/site/manual-registry.json +10 -10
  34. package/dist/site/node-api-registry.json +26 -21
  35. package/dist/site/page-registry.json +112 -41
  36. package/dist/site/public-surface-audit.json +3205 -1126
  37. package/dist/site/publication-authority-registry.json +1182 -415
  38. package/dist/site/publication-registry.json +4 -4
  39. package/dist/site/site-manifest.json +14 -14
  40. package/dist/site/workflow-registry.json +5460 -1870
  41. package/docs/MAP.md +1 -0
  42. package/docs/auditable-demo.md +1 -1
  43. package/docs/cli.md +3 -3
  44. package/docs/dev-delivery-warrant.md +1 -1
  45. package/docs/engineering-housekeeper.md +3 -3
  46. package/docs/next-development-transition.md +1 -1
  47. package/docs/node-api-reference.md +30 -30
  48. package/docs/publication-authority.md +2 -2
  49. package/docs/release-candidate.md +3 -3
  50. package/docs/release-flow.md +67 -19
  51. package/docs/release-governance.md +8 -8
  52. package/docs/v4-adopter-delivery.md +1 -1
  53. package/docs/v4-production-release.md +71 -8
  54. package/docs/v4-stage-capsule.md +1 -1
  55. package/docs/workflow-catalog.md +152 -0
  56. package/package.json +8 -6
  57. package/packages/core/buildchain-contract.js +1 -1
  58. package/packages/core/buildchain-kfd-claims.js +2 -2
  59. package/packages/core/buildchain-publication-authority.js +40 -25
  60. package/packages/core/buildchain-v4-domain.wasm +0 -0
  61. package/packages/core/controller-evidence.js +4 -1
  62. package/packages/core/github-governance-authority.js +4 -4
  63. package/packages/core/next-development-projection.js +1 -1
  64. package/packages/core/release-line-dry-run.js +1 -1
  65. package/packages/core/v4-canonical-contracts.js +1 -0
  66. package/packages/core/v4-domain-wasm-artifact.js +2 -2
  67. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  68. package/packages/core/v4-floating-consumer-policy.js +1 -1
  69. package/scripts/binary-publication-evidence.mjs +135 -0
  70. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  71. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  72. package/scripts/check-inventory.mjs +22 -22
  73. package/scripts/check-v4-floating-consumer-policy-contract.mjs +1 -1
  74. package/scripts/check-v4-public-dogfood-contract.mjs +18 -5
  75. package/scripts/check-v4-release-topology.mjs +35 -6
  76. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  77. package/scripts/check-v4-universal-workflow-bootstrap.mjs +5 -9
  78. package/scripts/check-workflow-taxonomy.mjs +6 -0
  79. package/scripts/generate-channel-build-workflow.mjs +2 -1
  80. package/scripts/generate-channel-promotion-workflow.mjs +4 -3
  81. package/scripts/generate-site-bundle.mjs +11 -11
  82. package/scripts/generate-v4-universal-workflow-facades.mjs +24 -6
  83. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  84. package/scripts/maintainability-metrics.mjs +16 -5
  85. package/scripts/next-development-review.mjs +307 -0
  86. package/scripts/release-asset-client.mjs +91 -0
  87. package/scripts/release-candidate-resolver.mjs +1 -1
  88. package/scripts/source-verification-evidence.mjs +344 -0
  89. package/scripts/stable-candidate-qualification.mjs +1 -1
  90. package/scripts/v4-publication-settlement.mjs +211 -0
  91. package/scripts/v4-universal-workflow-self-dogfood.mjs +1 -1
  92. package/scripts/verify-version-state-delta.mjs +173 -0
  93. package/scripts/workflow-taxonomy.mjs +468 -0
@@ -0,0 +1,135 @@
1
+ import fs from "node:fs";
2
+ import crypto from "node:crypto";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import {
6
+ SETTLEMENT_ASSET,
7
+ verifyPublicationSettlement,
8
+ } from "./v4-publication-settlement.mjs";
9
+ import { releaseAssetClient } from "./release-asset-client.mjs";
10
+
11
+ export async function readBinaryPublicationEvidence({
12
+ client,
13
+ repository,
14
+ tag,
15
+ sourceSha,
16
+ attempts = 40,
17
+ wait = () => new Promise((resolve) => setTimeout(resolve, 15000)),
18
+ }) {
19
+ for (let attempt = 0; attempt < attempts; attempt++) {
20
+ let release;
21
+ try {
22
+ release = client.release(tag);
23
+ } catch {
24
+ /* Tag creation can precede Release creation. */
25
+ }
26
+ const assets = release?.assets || [];
27
+ const settlements = assets.filter(({ name }) => name === SETTLEMENT_ASSET);
28
+ const passports = assets.filter(
29
+ ({ name }) => name === "buildchain.release.json",
30
+ );
31
+ if (settlements.length > 1 || passports.length > 1)
32
+ throw new Error("ambiguous immutable publication evidence");
33
+ if (settlements.length === 1 && passports.length === 1) {
34
+ const settlement = JSON.parse(client.assetBytes(settlements[0]));
35
+ const publicPassport = JSON.parse(client.assetBytes(passports[0]));
36
+ if (
37
+ settlement.contract !== "buildchain-v4-publication-settlement/v1" ||
38
+ settlement.release.sourceSha !== sourceSha ||
39
+ settlement.release.tag !== tag
40
+ )
41
+ throw new Error("binary publication settlement identity mismatch");
42
+ const tagSha = client.json(
43
+ `repos/${repository}/commits/${encodeURIComponent(tag)}`,
44
+ ).sha;
45
+ if (tagSha !== sourceSha)
46
+ throw new Error("binary source does not match the exact release tag");
47
+ verifyPublicationSettlement(settlement.documents, {
48
+ repository,
49
+ tag,
50
+ sourceSha,
51
+ publicPassport,
52
+ });
53
+ return settlement;
54
+ }
55
+ if (attempt + 1 < attempts) await wait();
56
+ }
57
+ throw new Error(
58
+ "completed v4 publication evidence is not available within the bounded wait",
59
+ );
60
+ }
61
+
62
+ export function prepareBinaryAssetPaths({
63
+ binaryDir,
64
+ passportDir,
65
+ outputDir,
66
+ capabilityPath,
67
+ }) {
68
+ fs.mkdirSync(outputDir, { recursive: true });
69
+ const files = [];
70
+ for (const directory of [binaryDir, passportDir]) {
71
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
72
+ if (!entry.isFile()) continue;
73
+ const source = path.join(directory, entry.name);
74
+ if (
75
+ directory === passportDir &&
76
+ entry.name === "buildchain.release.json"
77
+ ) {
78
+ const renamed = path.join(outputDir, "buildchain.binary.release.json");
79
+ fs.copyFileSync(source, renamed);
80
+ files.push(renamed);
81
+ } else files.push(source);
82
+ }
83
+ }
84
+ if (capabilityPath) {
85
+ const bytes = fs.readFileSync(capabilityPath);
86
+ const digest = crypto.createHash("sha256").update(bytes).digest("hex");
87
+ const retained = path.join(
88
+ outputDir,
89
+ `buildchain.binary.capability-${digest}.json`,
90
+ );
91
+ fs.writeFileSync(retained, bytes);
92
+ files.push(retained);
93
+ }
94
+ return files;
95
+ }
96
+
97
+ async function main(mode) {
98
+ const repository = process.env.GITHUB_REPOSITORY;
99
+ const tag = process.env.RELEASE_TAG;
100
+ const client = releaseAssetClient(repository);
101
+ if (mode === "read") {
102
+ const settlement = await readBinaryPublicationEvidence({
103
+ client,
104
+ repository,
105
+ tag,
106
+ sourceSha: process.env.SOURCE_SHA,
107
+ });
108
+ const directory = ".buildchain/publication-evidence";
109
+ client.write(`${directory}/${SETTLEMENT_ASSET}`, settlement);
110
+ client.write(`${directory}/release.json`, {
111
+ channel: settlement.release.channel,
112
+ });
113
+ } else if (mode === "publish") {
114
+ const release = client.release(tag);
115
+ const files = prepareBinaryAssetPaths({
116
+ binaryDir: "dist/binary",
117
+ passportDir: ".buildchain/release-passport",
118
+ outputDir: ".buildchain/binary-publication",
119
+ capabilityPath: ".buildchain/publication-authority/capability.json",
120
+ });
121
+ const results = await client.publish(release, files);
122
+ client.write(".buildchain/binary-publication/readback.json", {
123
+ repository,
124
+ tag,
125
+ assets: results,
126
+ });
127
+ console.log(JSON.stringify({ published: results.length, repository, tag }));
128
+ } else throw new Error(`unknown binary publication mode: ${mode}`);
129
+ }
130
+
131
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
132
+ main(process.argv[2]).catch((error) => {
133
+ console.error(error.message);
134
+ process.exitCode = 1;
135
+ });
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
+ import { projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import process from "node:process";
@@ -14,7 +15,7 @@ const policyPath = path.join(
14
15
  "ci-lane-change-budget.json",
15
16
  );
16
17
 
17
- const workflows = fs
18
+ const physicalWorkflows = fs
18
19
  .readdirSync(workflowRoot, { withFileTypes: true })
19
20
  .filter((entry) => entry.isFile() && /\.(?:yaml|yml)$/u.test(entry.name))
20
21
  .map((entry) => {
@@ -26,6 +27,8 @@ const workflows = fs
26
27
  })
27
28
  .sort((left, right) => left.path.localeCompare(right.path));
28
29
 
30
+ const workflows = projectWorkflowIdentities(root, physicalWorkflows);
31
+
29
32
  const policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
30
33
  const baselineRevision = String(policy.baseline?.gitRevision || "").trim();
31
34
  if (!/^[0-9a-f]{40}$/u.test(baselineRevision)) {
@@ -34,9 +34,10 @@ 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.map(
38
- (entry) => `workflow:${entry.id}`,
39
- );
37
+ const workflowIds = workflows.workflows.flatMap((entry) => [
38
+ `workflow:${entry.id}`,
39
+ ...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
40
+ ]);
40
41
  const actionIds = workflows.actions.map((entry) => `action:${entry.id}`);
41
42
  const exports = Object.keys(loadJson(root, "package.json").exports).map(
42
43
  (entry) => `export:${entry}`,
@@ -145,39 +145,39 @@ const requiredPaths = [
145
145
  ".buildchain/alpha-contract-lock.json",
146
146
  ".buildchain/release-impact.json",
147
147
  ".github/actionlint.yaml",
148
- ".github/workflows/self-hosted-runner-smoke.yml",
149
- ".github/workflows/buildchain-ref-promotion.yml",
150
- ".github/workflows/buildchain-candidate-recovery-dogfood-failure.yml",
151
- ".github/workflows/release-line-bootstrap.yml",
148
+ ".github/workflows/self-ops-runner-smoke.yml",
149
+ ".github/workflows/self-release-promote.yml",
150
+ ".github/workflows/self-ops-recovery-failure-dogfood.yml",
151
+ ".github/workflows/self-release-line-open.yml",
152
152
  ".github/workflows/release-governance-reconcile.yml",
153
153
  ".github/workflows/dev-pr-auto-merge.yml",
154
- ".github/workflows/buildchain-dev-delivery.yml",
154
+ ".github/workflows/self-ops-dev-delivery.yml",
155
155
  ".github/workflows/buildchain-patrol.yml",
156
156
  ".github/workflows/patrol-daily.yml",
157
157
  ".github/workflows/patrol-weekly.yml",
158
158
  ".github/workflows/patrol-monthly.yml",
159
159
  ".github/workflows/patrol-observed-evidence.yml",
160
- ".github/workflows/buildchain-patrol-daily.yml",
161
- ".github/workflows/buildchain-patrol-weekly.yml",
162
- ".github/workflows/buildchain-patrol-monthly.yml",
163
- ".github/workflows/buildchain-alpha-self-dogfood.yml",
160
+ ".github/workflows/self-ops-patrol-daily.yml",
161
+ ".github/workflows/self-ops-patrol-weekly.yml",
162
+ ".github/workflows/self-ops-patrol-monthly.yml",
163
+ ".github/workflows/self-build-alpha-dogfood.yml",
164
164
  ".github/workflows/release-candidate-promote.yml",
165
165
  ".github/workflows/.release-candidate-promote.yml",
166
166
  ".github/workflows/release-propagation.yml",
167
- ".github/workflows/npm-publish.yml",
167
+ ".github/workflows/self-release-npm-dry-run.yml",
168
168
  ".github/workflows/paper-release.yml",
169
- ".github/workflows/binary-distribution.yml",
169
+ ".github/workflows/self-build-binary-distribution.yml",
170
170
  ".github/workflows/.binary-release-assets.yml",
171
- ".github/workflows/binary-release-assets.yml",
172
- ".github/workflows/verify.yml",
171
+ ".github/workflows/self-release-binary-assets.yml",
172
+ ".github/workflows/self-build-verify.yml",
173
173
  ".github/workflows/.build.yml",
174
174
  ".github/workflows/.gate-profile.yml",
175
175
  ".github/workflows/.auditable-demo.yml",
176
176
  ".github/workflows/.declarative-auditable-demo.yml",
177
- ".github/workflows/auditable-demo.yml",
177
+ ".github/workflows/self-build-demo-dogfood.yml",
178
178
  ".github/workflows/build.yml",
179
- ".github/workflows/build-surface-fixture.yml",
180
- ".github/workflows/candidate-lab.yml",
179
+ ".github/workflows/self-build-fixture.yml",
180
+ ".github/workflows/self-build-candidate-lab.yml",
181
181
  "fixtures/libnode-shaped/buildchain.toml",
182
182
  "fixtures/libnode-shaped/.github/workflows/build.yml",
183
183
  "fixtures/libnode-shaped/package.json"
@@ -197,7 +197,7 @@ if (rootPackage.private !== false) {
197
197
  throw new Error("root package must be publishable with private=false");
198
198
  }
199
199
  const selfDogfoodWorkflow = fs.readFileSync(
200
- path.join(root, ".github/workflows/buildchain-alpha-self-dogfood.yml"),
200
+ path.join(root, ".github/workflows/self-build-alpha-dogfood.yml"),
201
201
  "utf8",
202
202
  );
203
203
  const selfDogfoodAlphaLock = JSON.parse(
@@ -285,7 +285,7 @@ const channelPromotionWorkflow = fs.readFileSync(
285
285
  "utf8",
286
286
  );
287
287
  const boundedAlphaRecoveryWorkflow = fs.readFileSync(
288
- path.join(root, ".github/workflows/buildchain-ref-promotion-recovery.yml"),
288
+ path.join(root, ".github/workflows/self-ops-promotion-recovery.yml"),
289
289
  "utf8",
290
290
  );
291
291
  const promotionShellRouting = parsePromotionShellRouting(
@@ -1000,11 +1000,11 @@ for (const requiredSnippet of [
1000
1000
  if (commonJsSourcePattern.test(standaloneBinaryScript)) {
1001
1001
  throw new Error("scripts/build-standalone-binary.mjs must use ESM syntax");
1002
1002
  }
1003
- const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/npm-publish.yml"), "utf8");
1004
- const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/buildchain-ref-promotion.yml"), "utf8");
1005
- const binaryDistributionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/binary-distribution.yml"), "utf8");
1003
+ const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-release-npm-dry-run.yml"), "utf8");
1004
+ const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-release-promote.yml"), "utf8");
1005
+ const binaryDistributionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-build-binary-distribution.yml"), "utf8");
1006
1006
  const binaryReleaseAssetsWorkflow = fs.readFileSync(path.join(root, ".github/workflows/.binary-release-assets.yml"), "utf8");
1007
- const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-hosted-runner-smoke.yml"), "utf8");
1007
+ const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-ops-runner-smoke.yml"), "utf8");
1008
1008
  const npmDryRunScript = fs.readFileSync(path.join(root, "scripts/npm-publish-dry-run.mjs"), "utf8");
1009
1009
  const npmPublishTransactionScript = fs.readFileSync(path.join(root, "scripts/npm-publish-transaction.mjs"), "utf8");
1010
1010
  const rootPackageJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
@@ -80,7 +80,7 @@ function assertPersistedSelectors() {
80
80
  const selector = match[2];
81
81
  const protectedBootstrap =
82
82
  relative ===
83
- ".github/workflows/buildchain-ref-promotion-recovery.yml" &&
83
+ ".github/workflows/self-ops-promotion-recovery.yml" &&
84
84
  match[1] === ".github/workflows/.release-candidate-promote.yml" &&
85
85
  selector === "alpha/v4/v4.0";
86
86
  const isV4 =
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
 
@@ -8,7 +9,7 @@ const DEFAULT_ROOT = path.resolve(
8
9
  path.dirname(fileURLToPath(import.meta.url)),
9
10
  "..",
10
11
  );
11
- const CALLER_PATH = ".github/workflows/v4-public-consumer-dogfood.yml";
12
+ const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
12
13
  const REUSABLE_PATH = ".github/workflows/v4-stage-capsule-canary.yml";
13
14
  export const V4_PUBLIC_DOGFOOD_ALPHA_REF = "v4-alpha";
14
15
  const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
@@ -93,10 +94,19 @@ function assertReusableWorkflow(root) {
93
94
 
94
95
  function assertWorkflowInventory(root) {
95
96
  const workflowRoot = path.join(root, ".github/workflows");
97
+ const taxonomy = readWorkflowTaxonomy(root);
96
98
  for (const entry of fs.readdirSync(workflowRoot, { withFileTypes: true })) {
97
99
  if (!entry.isFile() || !/\.ya?ml$/u.test(entry.name)) continue;
98
- const relative = `.github/workflows/${entry.name}`;
100
+ let relative = `.github/workflows/${entry.name}`;
99
101
  const text = read(root, relative);
102
+ const canonical = taxonomy?.entries.find(
103
+ (item) => workflowPath(item) === relative && item.compatibility,
104
+ );
105
+ if (canonical) {
106
+ if (read(root, canonical.compatibility.path) !== text)
107
+ fail(`${relative} differs from its public compatibility identity`);
108
+ relative = canonical.compatibility.path;
109
+ }
100
110
  if (
101
111
  relative !== REUSABLE_PATH &&
102
112
  text.includes("scripts/v4-stage-capsule-qualification.mjs")
@@ -113,11 +123,13 @@ function assertWorkflowInventory(root) {
113
123
  }
114
124
 
115
125
  function assertProtectedVerify(root) {
116
- const verify = read(root, ".github/workflows/verify.yml");
126
+ const verify = read(root, ".github/workflows/self-build-verify.yml");
117
127
  for (const required of [
118
128
  "needs: stage-capsule-checkpoints",
119
- "name: Enforce public consumer-only v4 dogfood",
120
- "run: node scripts/check-v4-public-dogfood-contract.mjs",
129
+ "name: Run declared verify lifecycle (full source tests and generated artifact checks)",
130
+ "node .buildchain/runtime/bin/buildchain.mjs lifecycle run verify",
131
+ "run: node scripts/source-verification-evidence.mjs plan",
132
+ "run: node scripts/source-verification-evidence.mjs seal",
121
133
  ])
122
134
  if (!verify.includes(required))
123
135
  fail(`Verify is missing protected gate ${required}`);
@@ -193,6 +205,7 @@ function assertConsumerLifecycle(root) {
193
205
  '[lifecycle.install]\ncommand = "corepack enable pnpm && corepack pnpm@11.7.0 install --frozen-lockfile"',
194
206
  '[lifecycle.build]\ncommand = "corepack pnpm@11.7.0 run build && corepack pnpm@11.7.0 run generate:site"',
195
207
  "[lifecycle.verify]\ncommands = [",
208
+ '"corepack pnpm@11.7.0 run check",',
196
209
  ])
197
210
  if (!lifecycle.includes(declaration))
198
211
  fail(
@@ -3,6 +3,8 @@
3
3
  import assert from "node:assert/strict";
4
4
  import crypto from "node:crypto";
5
5
  import fs from "node:fs";
6
+ import ts from "typescript";
7
+ import { workflowCompatibilityIdentity } from "./workflow-taxonomy.mjs";
6
8
  import path from "node:path";
7
9
  import { fileURLToPath, pathToFileURL } from "node:url";
8
10
 
@@ -21,13 +23,35 @@ function read(relative) {
21
23
  const LOCAL_MODULE_EXTENSIONS = ["", ".js", ".mjs", ".cjs"];
22
24
  const PRIVILEGED_ENTRYPOINTS = [
23
25
  "actions/v4-release-candidate-promote/index.js",
26
+ "scripts/binary-publication-evidence.mjs",
27
+ "scripts/next-development-review.mjs",
28
+ "scripts/v4-publication-settlement.mjs",
24
29
  ];
25
30
 
26
- function localModuleSpecifiers(source) {
31
+ export function localModuleSpecifiers(source) {
27
32
  const specifiers = [];
28
- const expression =
29
- /(?:\bfrom\s+|\bimport\s*\(\s*|\bimport\s+)["'](\.[^"']+)["']/gu;
30
- for (const match of source.matchAll(expression)) specifiers.push(match[1]);
33
+ const visit = (node) => {
34
+ let value;
35
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
36
+ value = node.moduleSpecifier;
37
+ else if (
38
+ ts.isCallExpression(node) &&
39
+ node.expression.kind === ts.SyntaxKind.ImportKeyword
40
+ )
41
+ value = node.arguments[0];
42
+ if (value && ts.isStringLiteral(value) && value.text.startsWith("."))
43
+ specifiers.push(value.text);
44
+ ts.forEachChild(node, visit);
45
+ };
46
+ visit(
47
+ ts.createSourceFile(
48
+ "module.mjs",
49
+ source,
50
+ ts.ScriptTarget.Latest,
51
+ true,
52
+ ts.ScriptKind.JS,
53
+ ),
54
+ );
31
55
  return [...new Set(specifiers)].sort();
32
56
  }
33
57
 
@@ -341,7 +365,12 @@ export function findUnknownV4ReleaseTopology(
341
365
  ) {
342
366
  const declared = new Set(workflowPaths);
343
367
  return allWorkflowPaths
344
- .filter((relative) => !declared.has(relative))
368
+ .filter(
369
+ (relative) =>
370
+ !declared.has(
371
+ workflowCompatibilityIdentity(root, relative, readWorkflow(relative)),
372
+ ),
373
+ )
345
374
  .filter((relative) => {
346
375
  const source = readWorkflow(relative);
347
376
  const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
@@ -431,7 +460,7 @@ function assertAuthorityClosure(ledger) {
431
460
  );
432
461
  assert.equal(
433
462
  closure.recoveryEntry,
434
- ".github/workflows/buildchain-ref-promotion-recovery.yml",
463
+ ".github/workflows/self-ops-promotion-recovery.yml",
435
464
  );
436
465
  const engineSurface = [
437
466
  ".github/workflows/.release-candidate-promote.yml",
@@ -35,7 +35,10 @@ function publicSurfaceIds(root) {
35
35
  return new Set([
36
36
  ...Object.keys(packageJson.exports || {}).map((id) => `export:${id}`),
37
37
  ...(cli.commands || []).map((entry) => `cli:${entry.id}`),
38
- ...(workflows.workflows || []).map((entry) => `workflow:${entry.id}`),
38
+ ...(workflows.workflows || []).flatMap((entry) => [
39
+ `workflow:${entry.id}`,
40
+ ...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
41
+ ]),
39
42
  ...(workflows.actions || []).map((entry) => `action:${entry.id}`),
40
43
  ]);
41
44
  }
@@ -1,4 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
+ import { projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
@@ -25,16 +26,11 @@ const fileRoot = (relative) =>
25
26
  .digest("hex")}`;
26
27
 
27
28
  const workflowRoot = path.join(root, ".github/workflows");
28
- const discovered = fs
29
- .readdirSync(workflowRoot)
29
+ const discovered = projectWorkflowIdentities(root, fs.readdirSync(workflowRoot)
30
30
  .filter((name) => /\.ya?ml$/u.test(name))
31
- .map((name) => `.github/workflows/${name}`)
32
- .filter((relative) =>
33
- /(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(
34
- fs.readFileSync(path.join(root, relative), "utf8"),
35
- ),
36
- )
37
- .sort();
31
+ .map((name) => ({ path: `.github/workflows/${name}`, text: fs.readFileSync(path.join(workflowRoot, name), "utf8") })))
32
+ .filter((entry) => /(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(entry.text))
33
+ .map((entry) => entry.path).sort();
38
34
 
39
35
  assert.equal(
40
36
  contract.schema,
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { checkWorkflowTaxonomy } from "./workflow-taxonomy.mjs";
3
+
4
+ const result = checkWorkflowTaxonomy(process.cwd());
5
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
6
+ if (!result.ok) process.exitCode = 1;
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
@@ -421,7 +422,7 @@ function main() {
421
422
  }
422
423
  return;
423
424
  }
424
- fs.writeFileSync(targetPath, generated);
425
+ writeWorkflowSource(root, path.relative(root, targetPath), generated);
425
426
  }
426
427
 
427
428
  const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import { writeWorkflowSource } from "./workflow-taxonomy.mjs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
@@ -215,9 +216,9 @@ function consumerAdmissionJob() {
215
216
  id: policy
216
217
  env:
217
218
  BUILDCHAIN_CONSUMER_ROOT: .buildchain/consumer
218
- BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/release-candidate-promote.yml' || inputs.publication-publisher-workflow-path }}
219
+ BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/self-ops-promotion-recovery.yml' && '.github/workflows/release-candidate-promote.yml' || inputs.publication-publisher-workflow-path }}
219
220
  BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: \${{ needs.resolve-promotion.outputs.channel }}
220
- BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/.release-candidate-promote.yml' || '.github/workflows/release-candidate-promote.yml' }}
221
+ BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/self-ops-promotion-recovery.yml' && '.github/workflows/.release-candidate-promote.yml' || '.github/workflows/release-candidate-promote.yml' }}
221
222
  BUILDCHAIN_WORKFLOW_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
222
223
  BUILDCHAIN_RUNTIME_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
223
224
  BUILDCHAIN_STABLE_CONTRACT_LOCK_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}
@@ -532,7 +533,7 @@ function main() {
532
533
  }
533
534
  return;
534
535
  }
535
- fs.writeFileSync(targetPath, generated);
536
+ writeWorkflowSource(root, path.relative(root, targetPath), generated);
536
537
  }
537
538
 
538
539
  const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import crypto from "node:crypto";
@@ -755,9 +756,7 @@ function buildSiteBundle() {
755
756
  timestampFieldsParticipateInArtifactDigest: true,
756
757
  artifactDigestScope: "npm package dist/site JSON files",
757
758
  });
758
-
759
759
  const cliRegistry = createCliRegistry(packageJson);
760
-
761
760
  const manualRegistry = {
762
761
  schemaVersion: 1,
763
762
  contract: "kungfu-buildchain-agent-manual-registry",
@@ -805,7 +804,6 @@ function buildSiteBundle() {
805
804
  ],
806
805
  guidance: "Agent consumers should use this registry to find packaged Buildchain manuals before inferring behavior from workflow snippets or release notes.",
807
806
  };
808
-
809
807
  const pageRegistry = {
810
808
  schemaVersion: 1,
811
809
  contract: "kungfu-buildchain-site-page-registry",
@@ -821,14 +819,15 @@ function buildSiteBundle() {
821
819
  capabilityGroups: CAPABILITY_GROUPS.map((group) => group.id),
822
820
  pages,
823
821
  };
824
-
825
822
  const nodeApiRegistry = createSiteNodeApiRegistry({ root, packageJson, nodeApiMeta, sha256File, publicSurfaceLifecycle });
826
-
823
+ const taxonomy = readWorkflowTaxonomy(root);
827
824
  const workflowRegistry = {
828
825
  schemaVersion: 1,
829
826
  contract: "kungfu-buildchain-workflow-registry",
830
827
  workflowSource: ".github/workflows reverse input enumeration",
831
828
  workflows: enumerateWorkflowInputs({ root }).map((entry) => {
829
+ const classification = taxonomy?.entries.find((item) => workflowPath(item) === entry.path || item.compatibility?.path === entry.path);
830
+ const semanticId = classification?.id || entry.id;
832
831
  const surfaceById = new Map([
833
832
  ["build", "channel-build-router"],
834
833
  [".build", "reusable-build"],
@@ -878,18 +877,19 @@ function buildSiteBundle() {
878
877
  ["engineering-housekeeper-monthly", "preview"],
879
878
  ["self-hosted-runner-smoke", "compatibility-fixture"],
880
879
  ]);
881
- const engineeringHousekeeper = entry.id.startsWith("engineering-housekeeper");
880
+ const engineeringHousekeeper = semanticId.startsWith("engineering-housekeeper");
882
881
  return {
883
882
  ...entry,
884
- surface: surfaceById.get(entry.id) || (entry.path.includes("/.") ? "reusable-workflow" : "repository-workflow"),
883
+ ...(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) } } : {}),
884
+ surface: surfaceById.get(semanticId) || (entry.path.includes("/.") ? "reusable-workflow" : "repository-workflow"),
885
885
  capabilityGroup: workflowCapabilityGroup({
886
- ...entry,
887
- status: statusById.get(entry.id) || "active",
886
+ ...entry, id: semanticId,
887
+ status: statusById.get(semanticId) || "active",
888
888
  }),
889
- status: statusById.get(entry.id) || "active",
889
+ status: statusById.get(semanticId) || "active",
890
890
  ...publicSurfaceLifecycle({
891
891
  owner: "buildchain-workflows",
892
- maturity: statusById.get(entry.id) || "active",
892
+ maturity: statusById.get(semanticId) || "active",
893
893
  introducedVersion: engineeringHousekeeper ? packageJson.version : undefined,
894
894
  nonDuplicationRationale: engineeringHousekeeper
895
895
  ? "One reusable policy and evidence boundary owns Engineering Housekeeper execution; scheduled callers contain cadence values only."
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import crypto from "node:crypto";
4
+ import { writeWorkflowSource, rewriteRepositoryWorkflowPaths } from "./workflow-taxonomy.mjs";
3
5
  import path from "node:path";
4
6
  import { execFileSync } from "node:child_process";
5
7
  import { fileURLToPath } from "node:url";
@@ -198,11 +200,27 @@ function addRuntimeBootstrapDependencies(source, relative) {
198
200
  );
199
201
  }
200
202
 
203
+ function applyPublicationFacadePatches(source, relative) {
204
+ const document = JSON.parse(fs.readFileSync(path.join(root,
205
+ "architecture/v4-publication-facade-patches.json"), "utf8"));
206
+ const patch = document.facades.find(entry => entry.workflow === relative);
207
+ if (!patch) return source;
208
+ const digest = value => crypto.createHash("sha256").update(value).digest("hex");
209
+ if (digest(source) === patch.afterSha256) return source;
210
+ if (digest(source) !== patch.beforeSha256) fail(`${relative}: publication facade source drift`);
211
+ for (const { before, after } of patch.replacements) {
212
+ if (source.split(before).length !== 2) fail(`${relative}: ambiguous publication patch`);
213
+ source = source.replace(before, () => after);
214
+ }
215
+ if (digest(source) !== patch.afterSha256) fail(`${relative}: publication facade result drift`);
216
+ return source;
217
+ }
218
+
201
219
  export function migrateV4UniversalWorkflowFacade(source, relative) {
202
- return guardCompatibilityJobs(
220
+ return rewriteRepositoryWorkflowPaths(root, applyPublicationFacadePatches(guardCompatibilityJobs(
203
221
  addUniversalInput(addRuntimeBootstrapDependencies(source, relative), relative),
204
222
  relative,
205
- );
223
+ ), relative));
206
224
  }
207
225
 
208
226
  function verify(source, relative) {
@@ -402,8 +420,8 @@ function main() {
402
420
  "consumer recovery workflow differs from its pre-positioned template",
403
421
  );
404
422
  } else {
405
- fs.writeFileSync(
406
- recoveryWorkflowPath,
423
+ writeWorkflowSource(
424
+ root, path.relative(root, recoveryWorkflowPath),
407
425
  fs.readFileSync(recoveryTemplatePath, "utf8"),
408
426
  );
409
427
  }
@@ -414,8 +432,8 @@ function main() {
414
432
  : fs.readFileSync(target, "utf8");
415
433
  if (check) verify(source, relative);
416
434
  else
417
- fs.writeFileSync(
418
- target,
435
+ writeWorkflowSource(
436
+ root, relative,
419
437
  migrateV4UniversalWorkflowFacade(source, relative),
420
438
  );
421
439
  }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import {
5
+ readWorkflowTaxonomy,
6
+ renderWorkflowCatalog,
7
+ TAXONOMY_DOC,
8
+ workflowPath,
9
+ writeWorkflowSource,
10
+ } from "./workflow-taxonomy.mjs";
11
+
12
+ const root = process.cwd();
13
+ const policy = readWorkflowTaxonomy(root);
14
+ if (!policy) throw new Error("workflow taxonomy is missing");
15
+ for (const entry of policy.entries) {
16
+ writeWorkflowSource(
17
+ root,
18
+ workflowPath(entry),
19
+ fs.readFileSync(path.join(root, workflowPath(entry)), "utf8"),
20
+ );
21
+ }
22
+ fs.writeFileSync(path.join(root, TAXONOMY_DOC), renderWorkflowCatalog(policy));
23
+ process.stdout.write(
24
+ `Generated ${policy.entries.length} canonical workflow declarations and their exact compatibility projections.\n`,
25
+ );