@kungfu-tech/buildchain 3.0.3-alpha.1 → 3.0.3-alpha.3

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.
@@ -1,12 +1,8 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import crypto from "node:crypto";
3
2
  import fs from "node:fs";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
6
- import {
7
- loadBuildchainConfig,
8
- validateBuildchainConfig,
9
- } from "./buildchain-config.js";
5
+ import { validateBuildchainConfig } from "./buildchain-config.js";
10
6
  import {
11
7
  createBuildchainContractLock,
12
8
  createBuildchainContractWorld,
@@ -22,6 +18,37 @@ import {
22
18
  PUBLICATION_SEALED_BUNDLE_CONTRACT,
23
19
  verifyPublicationSealedBundle,
24
20
  } from "./publication-sealed-bundle.js";
21
+ import {
22
+ PAPER_PATHS,
23
+ commandResult,
24
+ gitResult,
25
+ gitValue,
26
+ normalizeRepository,
27
+ paperConfig,
28
+ parsePaperVersion,
29
+ readJson,
30
+ resolvePaperRepository,
31
+ sha256Text,
32
+ stableJson,
33
+ } from "./paper-repository.js";
34
+
35
+ export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
36
+ export {
37
+ PAPER_WORK_START_PLAN_CONTRACT,
38
+ PAPER_WORK_SUBMIT_PLAN_CONTRACT,
39
+ createPaperWorkStartPlan,
40
+ createPaperWorkSubmitPlan,
41
+ executePaperWorkStart,
42
+ executePaperWorkSubmitPush,
43
+ } from "./paper-work.js";
44
+ export {
45
+ PAPER_FLEET_AUDIT_CONTRACT,
46
+ PAPER_FLEET_UPDATE_PLAN_CONTRACT,
47
+ collectPaperFleetAudit,
48
+ discoverPaperFleet,
49
+ planPaperFleetUpdate,
50
+ writePaperFleetUpdate,
51
+ } from "./paper-fleet.js";
25
52
 
26
53
  export const PAPER_SCAFFOLD_CONTRACT = "kungfu-buildchain-paper-scaffold";
27
54
  export const PAPER_MIGRATION_CONTRACT = "kungfu-buildchain-paper-migration";
@@ -50,22 +77,6 @@ export const PAPER_STATE_ORDER = Object.freeze([
50
77
  "production-visible",
51
78
  ]);
52
79
 
53
- export const PAPER_PATHS = Object.freeze({
54
- config: ".buildchain/buildchain.toml",
55
- versionPin: ".buildchain-version",
56
- contractLock: ".buildchain/contract-lock.json",
57
- buildWorkflow: ".github/workflows/build.yml",
58
- releaseWorkflow: ".github/workflows/paper-release.yml",
59
- reproducibilityReceipt:
60
- ".buildchain/publication/reproducibility-receipt.json",
61
- sealedBundle: ".buildchain/admitted/sealed-bundle.json",
62
- admission: ".buildchain/admitted/publication-admission.json",
63
- capability: ".buildchain/admitted/publication-capability.json",
64
- npmBootstrap: ".buildchain/paper/npm-bootstrap.json",
65
- npmTrust: ".buildchain/paper/npm-trust.json",
66
- provisioningAuthority: ".buildchain/paper/provisioning-authority.json",
67
- visibility: ".buildchain/paper/visibility.json",
68
- });
69
80
  const PAPER_SCAFFOLD_PATHS = Object.freeze([
70
81
  PAPER_PATHS.config,
71
82
  PAPER_PATHS.contractLock,
@@ -100,23 +111,6 @@ function toPosix(value) {
100
111
  .join("/");
101
112
  }
102
113
 
103
- function stableJson(value) {
104
- if (Array.isArray(value)) {
105
- return `[${value.map(stableJson).join(",")}]`;
106
- }
107
- if (value && typeof value === "object") {
108
- return `{${Object.keys(value)
109
- .sort()
110
- .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
111
- .join(",")}}`;
112
- }
113
- return JSON.stringify(value);
114
- }
115
-
116
- function sha256Text(value) {
117
- return `sha256:${crypto.createHash("sha256").update(String(value)).digest("hex")}`;
118
- }
119
-
120
114
  function sha256File(filePath) {
121
115
  return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`;
122
116
  }
@@ -125,21 +119,6 @@ function jsonText(value) {
125
119
  return `${JSON.stringify(value, null, 2)}\n`;
126
120
  }
127
121
 
128
- function readJson(filePath) {
129
- if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
130
- return { exists: false, value: undefined, error: "" };
131
- }
132
- try {
133
- return {
134
- exists: true,
135
- value: JSON.parse(fs.readFileSync(filePath, "utf8")),
136
- error: "",
137
- };
138
- } catch (error) {
139
- return { exists: true, value: undefined, error: error.message };
140
- }
141
- }
142
-
143
122
  function existingFileFact(cwd, relativePath) {
144
123
  const filePath = path.resolve(cwd, relativePath);
145
124
  if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
@@ -160,51 +139,6 @@ function normalizePackageName(value, label = "package name") {
160
139
  return normalized;
161
140
  }
162
141
 
163
- function normalizeRepository(value) {
164
- const normalized = String(value || "")
165
- .trim()
166
- .replace(/^git\+/, "")
167
- .replace(/^git@github\.com:/, "")
168
- .replace(/^ssh:\/\/git@github\.com\//, "")
169
- .replace(/^https?:\/\/github\.com\//, "")
170
- .replace(/\.git$/, "")
171
- .replace(/^\/+|\/+$/g, "");
172
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(normalized)) {
173
- return "";
174
- }
175
- return normalized;
176
- }
177
-
178
- function commandResult(
179
- command,
180
- args,
181
- { cwd, env = process.env, timeout = 15000 } = {},
182
- ) {
183
- const result = spawnSync(command, args, {
184
- cwd,
185
- env,
186
- encoding: "utf8",
187
- timeout,
188
- maxBuffer: 2 * 1024 * 1024,
189
- });
190
- return {
191
- ok: result.status === 0,
192
- status: result.status ?? 1,
193
- stdout: String(result.stdout || "").trim(),
194
- stderr: String(result.stderr || "").trim(),
195
- error: result.error?.message || "",
196
- };
197
- }
198
-
199
- function gitResult(cwd, args) {
200
- return commandResult("git", args, { cwd });
201
- }
202
-
203
- function gitValue(cwd, args) {
204
- const result = gitResult(cwd, args);
205
- return result.ok ? result.stdout : "";
206
- }
207
-
208
142
  function buildchainPackageIdentity(buildchainRoot, explicitVersion = "") {
209
143
  const packageJson = readJson(
210
144
  path.resolve(buildchainRoot, "package.json"),
@@ -238,11 +172,30 @@ function runtimeGitSha(buildchainRoot, buildchainVersion = "") {
238
172
  return GIT_SHA_PATTERN.test(gitHead) ? gitHead : "";
239
173
  }
240
174
 
241
- function runtimeAcceptedAt(buildchainRoot, sha) {
175
+ function runtimeAcceptedAt(buildchainRoot, sha, buildchainVersion = "") {
242
176
  if (!sha) return "1970-01-01T00:00:00.000Z";
243
177
  const value = gitValue(buildchainRoot, ["show", "-s", "--format=%cI", sha]);
244
- if (!value) return "1970-01-01T00:00:00.000Z";
245
- const parsed = new Date(value);
178
+ let acceptedAt = value;
179
+ if (!acceptedAt && buildchainVersion) {
180
+ const identity = buildchainPackageIdentity(
181
+ buildchainRoot,
182
+ buildchainVersion,
183
+ );
184
+ const observed = commandResult(
185
+ "npm",
186
+ [
187
+ "view",
188
+ `${identity.name}@${identity.version}`,
189
+ "time",
190
+ "--json",
191
+ `--registry=${NPM_REGISTRY}`,
192
+ ],
193
+ { cwd: buildchainRoot },
194
+ );
195
+ const published = observed.ok ? safeParseJson(observed.stdout) : null;
196
+ acceptedAt = String(published?.[identity.version] || "");
197
+ }
198
+ const parsed = new Date(acceptedAt);
246
199
  return Number.isNaN(parsed.valueOf())
247
200
  ? "1970-01-01T00:00:00.000Z"
248
201
  : parsed.toISOString();
@@ -541,16 +494,58 @@ clean:
541
494
  `;
542
495
  }
543
496
 
544
- function scaffoldPackageJson({ name, packageName, repository }) {
545
- return jsonText({
546
- name: packageName,
497
+ function paperPackageScripts(current = {}) {
498
+ return {
499
+ ...current,
500
+ "buildchain:paper": "buildchain paper",
501
+ "paper:preflight": "buildchain paper preflight --json",
502
+ "paper:work:start": "buildchain paper work start",
503
+ "paper:work:submit": "buildchain paper work submit",
504
+ "paper:status": "buildchain paper status --json",
505
+ };
506
+ }
507
+
508
+ function managedPaperPackageJson(current, buildchainVersion) {
509
+ if (!/^3\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(buildchainVersion)) {
510
+ throw new Error(
511
+ "paper repositories require an exact Buildchain v3 version",
512
+ );
513
+ }
514
+ const packageManager = current.packageManager || "pnpm@11.7.0";
515
+ if (!packageManager.startsWith("pnpm@")) {
516
+ throw new Error("paper repositories require a pnpm packageManager");
517
+ }
518
+ return {
519
+ ...current,
547
520
  private: true,
548
- description: `${name} publication source repository.`,
549
- repository: {
550
- type: "git",
551
- url: `git+https://github.com/${repository}.git`,
521
+ scripts: paperPackageScripts(current.scripts),
522
+ devDependencies: {
523
+ ...(current.devDependencies || {}),
524
+ "@kungfu-tech/buildchain": buildchainVersion,
552
525
  },
553
- license: "Apache-2.0",
526
+ packageManager,
527
+ };
528
+ }
529
+
530
+ function scaffoldPackageJson({
531
+ name,
532
+ packageName,
533
+ repository,
534
+ buildchainVersion,
535
+ }) {
536
+ return jsonText({
537
+ ...managedPaperPackageJson(
538
+ {
539
+ name: packageName,
540
+ description: `${name} publication source repository.`,
541
+ repository: {
542
+ type: "git",
543
+ url: `git+https://github.com/${repository}.git`,
544
+ },
545
+ license: "Apache-2.0",
546
+ },
547
+ buildchainVersion,
548
+ ),
554
549
  });
555
550
  }
556
551
 
@@ -562,9 +557,10 @@ This repository is a Buildchain-governed publication artifact source.
562
557
  ## Local workflow
563
558
 
564
559
  \`\`\`sh
565
- buildchain paper preflight --json
566
- buildchain paper build
567
- buildchain paper status --json
560
+ pnpm paper:preflight
561
+ pnpm paper:work:start -- <topic>
562
+ pnpm paper:work:submit
563
+ make pdf
568
564
  \`\`\`
569
565
 
570
566
  The public package identity is \`${packageName}\`. Buildchain owns reproducible
@@ -636,16 +632,21 @@ function scaffoldFiles({
636
632
  ).value;
637
633
  const acceptedAt =
638
634
  existingLock?.buildchain?.acceptedAt ||
639
- runtimeAcceptedAt(buildchainRoot, buildchainSha);
635
+ runtimeAcceptedAt(buildchainRoot, buildchainSha, buildchainVersion);
640
636
  const contractLock = createBuildchainContractLock({
641
- buildchainRef: buildchainSha,
637
+ buildchainRef,
642
638
  resolvedSha: buildchainSha,
643
639
  contractWorld,
644
640
  acceptedAt,
645
641
  });
646
642
  const contractLockText = jsonText(contractLock);
647
- const buildWorkflow = scaffoldBuildWorkflow(buildchainSha);
648
- const releaseWorkflow = scaffoldReleaseWorkflow(buildchainSha);
643
+ const buildWorkflow = scaffoldBuildWorkflow(buildchainSha, {
644
+ artifactName: name,
645
+ });
646
+ const releaseWorkflow = scaffoldReleaseWorkflow(buildchainSha, {
647
+ artifactPaths: "_build/main.pdf",
648
+ releasePassportProductName: title,
649
+ });
649
650
  const provisioningAuthority = createPaperProvisioningAuthority({
650
651
  repository,
651
652
  packageName,
@@ -673,7 +674,15 @@ function scaffoldFiles({
673
674
  [PAPER_PATHS.releaseWorkflow, releaseWorkflow],
674
675
  [PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
675
676
  ["Makefile", scaffoldMakefile()],
676
- ["package.json", scaffoldPackageJson({ name, packageName, repository })],
677
+ [
678
+ "package.json",
679
+ scaffoldPackageJson({
680
+ name,
681
+ packageName,
682
+ repository,
683
+ buildchainVersion,
684
+ }),
685
+ ],
677
686
  ["README.md", scaffoldReadme({ title, packageName })],
678
687
  ["docs/MAP.md", scaffoldMap()],
679
688
  ["paper/main.tex", scaffoldMainTex(title)],
@@ -928,13 +937,17 @@ function migrationFiles({
928
937
  path.resolve(cwd, PAPER_PATHS.contractLock),
929
938
  ).value;
930
939
  const contractLock = createBuildchainContractLock({
931
- buildchainRef: runtimeSha,
940
+ buildchainRef: "v3",
932
941
  resolvedSha: runtimeSha,
933
942
  contractWorld: runtimeContractWorld(buildchainRoot),
934
943
  acceptedAt:
935
944
  existingLock?.buildchain?.resolvedSha === runtimeSha
936
945
  ? existingLock.buildchain.acceptedAt
937
- : runtimeAcceptedAt(buildchainRoot, runtimeSha),
946
+ : runtimeAcceptedAt(
947
+ buildchainRoot,
948
+ runtimeSha,
949
+ runtimeIdentity.version,
950
+ ),
938
951
  });
939
952
  const contractLockText = jsonText(contractLock);
940
953
  const buildWorkflow = scaffoldBuildWorkflow(runtimeSha, {
@@ -953,13 +966,34 @@ function migrationFiles({
953
966
  buildWorkflow,
954
967
  releaseWorkflow,
955
968
  });
956
- return new Map([
969
+ const currentPackage = readJson(path.resolve(cwd, "package.json"));
970
+ if (!currentPackage.exists || currentPackage.error || !currentPackage.value) {
971
+ throw new Error("paper migration requires a valid package.json");
972
+ }
973
+ const packageJson = managedPaperPackageJson(
974
+ currentPackage.value,
975
+ runtimeIdentity.version,
976
+ );
977
+ const files = new Map([
957
978
  [PAPER_PATHS.contractLock, contractLockText],
958
979
  [PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
959
980
  [PAPER_PATHS.buildWorkflow, buildWorkflow],
960
981
  [PAPER_PATHS.releaseWorkflow, releaseWorkflow],
961
982
  [PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
983
+ ["package.json", jsonText(packageJson)],
962
984
  ]);
985
+ const verifyWorkflowPath = path.resolve(cwd, PAPER_PATHS.verifyWorkflow);
986
+ if (fs.existsSync(verifyWorkflowPath)) {
987
+ const currentVerifyWorkflow = fs.readFileSync(verifyWorkflowPath, "utf8");
988
+ const migratedVerifyWorkflow = currentVerifyWorkflow.replace(
989
+ /(uses:\s*kungfu-systems\/buildchain\/\.github\/workflows\/check\.yml@)[^\s]+/g,
990
+ `$1${runtimeSha}`,
991
+ );
992
+ if (migratedVerifyWorkflow !== currentVerifyWorkflow) {
993
+ files.set(PAPER_PATHS.verifyWorkflow, migratedVerifyWorkflow);
994
+ }
995
+ }
996
+ return files;
963
997
  }
964
998
 
965
999
  export function planPaperMigration({
@@ -1063,6 +1097,12 @@ export function planPaperMigration({
1063
1097
  description:
1064
1098
  "Write the reviewed Buildchain-owned control files without changing paper content or publication configuration.",
1065
1099
  },
1100
+ {
1101
+ id: "refresh-pnpm-lock",
1102
+ command: "pnpm install --lockfile-only",
1103
+ description:
1104
+ "Bind the exact Buildchain v3 dependency into pnpm-lock.yaml after the reviewed package update.",
1105
+ },
1066
1106
  ],
1067
1107
  };
1068
1108
  Object.defineProperty(result, "_plannedFiles", {
@@ -1133,26 +1173,6 @@ export function writePaperMigration(plan) {
1133
1173
  };
1134
1174
  }
1135
1175
 
1136
- function paperConfig(cwd) {
1137
- const loaded = loadBuildchainConfig(cwd);
1138
- if (!loaded) {
1139
- return {
1140
- loaded: undefined,
1141
- error: `${PAPER_PATHS.config} is missing`,
1142
- };
1143
- }
1144
- if (loaded.config.project?.type !== "publication-artifact") {
1145
- return {
1146
- loaded,
1147
- error: 'project.type must be "publication-artifact"',
1148
- };
1149
- }
1150
- if (!loaded.config.publication) {
1151
- return { loaded, error: "[publication] is missing" };
1152
- }
1153
- return { loaded, error: "" };
1154
- }
1155
-
1156
1176
  function state(id, status, reason, evidence = []) {
1157
1177
  return {
1158
1178
  id,
@@ -1591,20 +1611,6 @@ export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1591
1611
  };
1592
1612
  }
1593
1613
 
1594
- export function resolvePaperRepository(cwd = process.cwd()) {
1595
- const packagePath = path.resolve(cwd, "package.json");
1596
- const sourcePackage = readJson(packagePath).value;
1597
- const configured =
1598
- typeof sourcePackage?.repository === "string"
1599
- ? sourcePackage.repository
1600
- : sourcePackage?.repository?.url;
1601
- const fromPackage = normalizeRepository(configured);
1602
- if (fromPackage) return fromPackage;
1603
- return normalizeRepository(
1604
- gitValue(cwd, ["config", "--get", "remote.origin.url"]),
1605
- );
1606
- }
1607
-
1608
1614
  function validatePaperProvisioningAuthority(cwd) {
1609
1615
  const authorityPath = path.resolve(cwd, PAPER_PATHS.provisioningAuthority);
1610
1616
  const source = readJson(authorityPath);
@@ -2527,23 +2533,6 @@ export function createPaperBuildPlan({
2527
2533
  };
2528
2534
  }
2529
2535
 
2530
- function parsePaperVersion(version) {
2531
- const normalized = String(version || "")
2532
- .trim()
2533
- .replace(/^v/, "");
2534
- const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
2535
- if (!match) {
2536
- throw new Error("publication.version must be semver before planning Alpha");
2537
- }
2538
- return {
2539
- version: normalized,
2540
- major: Number(match[1]),
2541
- minor: Number(match[2]),
2542
- patch: Number(match[3]),
2543
- prerelease: match[4] || "",
2544
- };
2545
- }
2546
-
2547
2536
  function resolvePaperChannelRef(cwd, ref) {
2548
2537
  const candidates = [
2549
2538
  {
@@ -1,6 +1,12 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import {
5
+ commandId,
6
+ enumerateCliCommandsFromBin,
7
+ } from "./public-surface-cli.js";
8
+
9
+ export { enumerateCliCommandsFromBin } from "./public-surface-cli.js";
4
10
 
5
11
  export const BUILDCHAIN_PUBLIC_SURFACE_AUDIT_CONTRACT = "kungfu-buildchain-public-surface-reverse-audit";
6
12
 
@@ -40,82 +46,6 @@ function listDirectories(root, dir) {
40
46
  .sort();
41
47
  }
42
48
 
43
- function commandId(first = "", second = "", third = "") {
44
- const head = String(first || "").trim();
45
- const sub = String(second || "").trim();
46
- const leaf = String(third || "").trim();
47
- const normalizedLeaf = leaf === "..." ? "" : leaf;
48
- if (!head) return "";
49
- if (["-h", "--help", "help"].includes(head)) return "help";
50
- if (["-v", "--version", "version"].includes(head)) return "version";
51
- if (head === "release" && ["--dry-run", "dry-run", "explain"].includes(sub)) return "release-dry-run";
52
- if (head === "release" && sub === "line") return "release-line-open";
53
- if (head === "release") return "release-transaction";
54
- if (head === "transaction") return "transaction-inspect";
55
- if (head === "collect" && sub) return `collect-${sub}`;
56
- if (head === "verify" && sub) return `verify-${sub}`;
57
- if (head === "explain" && sub) return `explain-${sub}`;
58
- if (head === "inspect" && sub) return `inspect-${sub}`;
59
- if (head === "npm" && sub) return `npm-${sub}`;
60
- if (head === "lifecycle" && sub) return "lifecycle";
61
- if (head === "log" && sub) return "logging";
62
- if (head === "diagnostics" && sub) return `diagnostics-${sub}`;
63
- if (head === "facts" && sub) return "build-facts";
64
- if (head === "kfd") {
65
- if (!sub || sub === "...") return "kfd";
66
- if (sub === "schema") return normalizedLeaf ? `kfd-schema-${normalizedLeaf}` : "kfd-schema";
67
- if (sub === "upstream") return normalizedLeaf ? `kfd-upstream-${normalizedLeaf}` : "kfd-upstream";
68
- if (/^[1-9][0-9]*$/.test(sub)) return normalizedLeaf ? `kfd-${sub}-${normalizedLeaf}` : `kfd-${sub}`;
69
- return `kfd-${sub}`;
70
- }
71
- if (head === "sample" && sub) return `sample-${sub}`;
72
- if (head === "badges" && sub) return `badges-${sub}`;
73
- if (head === "homebrew" && sub) return `homebrew-${sub}`;
74
- if (head === "release-propagation") return "release-propagation";
75
- if (head === "publish-source") return "publish-source";
76
- if (head === "publication-artifact" && sub) return `publication-artifact-${sub}`;
77
- if (head === "publication-artifact") return "publication-artifact";
78
- if (head === "publication" && sub) return `publication-artifact-${sub}`;
79
- if (head === "publication") return "publication-artifact";
80
- if (head === "paper" && sub === "bootstrap" && normalizedLeaf === "npm") return "paper-bootstrap-npm";
81
- if (head === "paper" && sub) return `paper-${sub}`;
82
- if (head === "paper") return "paper";
83
- if (head === "build-contract") return "build-contract";
84
- if (head === "infra-contract") return "infra-contract";
85
- if (head === "web-surface") return "web-surface";
86
- return head;
87
- }
88
-
89
- export function enumerateCliCommandsFromBin({ root = process.cwd(), binPath = "bin/buildchain.mjs" } = {}) {
90
- const source = readText(root, binPath);
91
- const dispatchSource = [
92
- source,
93
- ...listFiles(root, "bin/internal", (name) => name.endsWith(".mjs"))
94
- .map((relPath) => readText(root, relPath)),
95
- ].join("\n");
96
- const usageMatch = source.match(/return `Usage:\n([\s\S]*?)`;\n}/);
97
- const usage = usageMatch?.[1] || "";
98
- const usageCommands = [];
99
- for (const line of usage.split(/\r?\n/)) {
100
- const match = line.trim().match(/^buildchain\s+([^\s]+)(?:\s+([^\s]+))?(?:\s+([^\s]+))?/);
101
- if (!match) continue;
102
- usageCommands.push({
103
- id: commandId(match[1], match[2], match[3]),
104
- usage: line.trim().replace(/\s+/g, " "),
105
- });
106
- }
107
- const dispatchCommands = [...dispatchSource.matchAll(/if\s*\(\s*command\s*===\s*"([^"]+)"/g)]
108
- .map((match) => commandId(match[1]));
109
- return uniqueSorted([
110
- ...usageCommands.map((entry) => entry.id),
111
- ...dispatchCommands,
112
- ]).map((id) => ({
113
- id,
114
- source: "bin/buildchain.mjs",
115
- usage: usageCommands.find((entry) => entry.id === id)?.usage || `buildchain ${id}`,
116
- }));
117
- }
118
-
119
49
  function parseYamlWorkflowCall(text) {
120
50
  const lines = text.split(/\r?\n/);
121
51
  const result = { reusable: false, inputs: [], secrets: [], outputs: [] };
@@ -333,8 +263,26 @@ export function collectPublicSurfaceReverseAudit({
333
263
  mode: "closed-world-enumerable",
334
264
  scope: "Buildchain CLI usage/dispatch, reusable workflow inputs, action inputs, site pages, and documentation command references",
335
265
  residualRisk: [
336
- "Shell commands delegated through helper scripts are only counted when exposed through bin/buildchain.mjs usage or docs.",
337
- "YAML parsing is limited to first-class action/workflow inputs, not arbitrary step environment variables.",
266
+ {
267
+ id: "public-surface-helper-command-enumeration",
268
+ definedBy: "https://kfd.libkungfu.dev/schemas/kfd-2/trust-taxonomy.schema.json#/$defs/residualRisk",
269
+ riskType: "natural-language-semantic-risk",
270
+ trustImpact: "downgrade-warning",
271
+ machineProvability: "not-machine-verifiable",
272
+ agentAction: "semantic-review-required",
273
+ owner: "Buildchain maintainers",
274
+ reason: "Shell commands delegated through helper scripts are counted only when exposed through bin/buildchain.mjs usage or documentation.",
275
+ },
276
+ {
277
+ id: "public-surface-yaml-enumeration",
278
+ definedBy: "https://kfd.libkungfu.dev/schemas/kfd-2/trust-taxonomy.schema.json#/$defs/residualRisk",
279
+ riskType: "natural-language-semantic-risk",
280
+ trustImpact: "downgrade-warning",
281
+ machineProvability: "not-machine-verifiable",
282
+ agentAction: "semantic-review-required",
283
+ owner: "Buildchain maintainers",
284
+ reason: "YAML enumeration covers first-class action and workflow inputs, not arbitrary step environment variables.",
285
+ },
338
286
  ],
339
287
  },
340
288
  };