@kungfu-tech/buildchain 2.8.2-alpha.0 → 2.8.2-alpha.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.
@@ -21,6 +21,7 @@ export const AGENT_INDEX_CONTRACT = "kungfu-buildchain-agent-index";
21
21
  export const PRODUCT_MECHANISM_CONTRACT = "kungfu-buildchain-product-mechanism";
22
22
  export const RELEASE_CHECK_REPORT_CONTRACT = "kungfu-buildchain-release-check-report";
23
23
  export const KFD2_RELEASE_TRUST_PASSPORT_CONTRACT = "kungfu-buildchain-kfd-2-release-trust-passport-audit";
24
+ export const KFD2_TRUST_PROOF_CONTRACT = "kungfu-buildchain-kfd-2-trust-proof";
24
25
 
25
26
  const CONTRACTS = new Set([
26
27
  RELEASE_PASSPORT_CONTRACT,
@@ -571,6 +572,9 @@ function normalizeKfd2Claim(raw = {}, index = 0) {
571
572
  : {};
572
573
  const responsibility = claim.responsibility && typeof claim.responsibility === "object" && !Array.isArray(claim.responsibility) ? claim.responsibility : {};
573
574
  const residualRisk = arrayOrEmpty(claim.residualRisk || claim.residual_risk);
575
+ const trustProof = claim.trustProof && typeof claim.trustProof === "object" && !Array.isArray(claim.trustProof)
576
+ ? claim.trustProof
577
+ : undefined;
574
578
  const missingBindings = [];
575
579
  if (sourceBindings.length === 0) missingBindings.push("declared-sources");
576
580
  if (machineEvidence.length === 0) missingBindings.push("machine-readable-evidence");
@@ -597,6 +601,7 @@ function normalizeKfd2Claim(raw = {}, index = 0) {
597
601
  auditBoundary,
598
602
  responsibility,
599
603
  residualRisk,
604
+ ...(trustProof ? { trustProof } : {}),
600
605
  proseOnly,
601
606
  missingBindings,
602
607
  status,
@@ -642,6 +647,17 @@ function kfd2ClaimFromKfd1World(world = {}, index = 0) {
642
647
  }
643
648
 
644
649
  function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
650
+ const witnessHashes = {
651
+ prebuildWitnessSha256: entry.preBuildWitnessSha256,
652
+ artifactWitnessSha256: entry.artifactWitnessSha256,
653
+ prebuildCanonicalSha256: entry.witnessEvidence?.prebuild?.canonicalSha256 || "",
654
+ artifactCanonicalSha256: entry.witnessEvidence?.artifact?.canonicalSha256 || "",
655
+ };
656
+ const reverseAuditBoundary = entry.reverseAudit?.auditBoundary || entry.auditBoundary;
657
+ const residualRisk = arrayOrEmpty(entry.residualRisk);
658
+ const proofResult = entry.trustProof?.result === "pass"
659
+ ? (residualRisk.length > 0 ? "downgraded" : "passed")
660
+ : "failed";
645
661
  return normalizeKfd2Claim({
646
662
  id: `kfd-3:${entry.id || index + 1}`,
647
663
  public: true,
@@ -658,10 +674,7 @@ function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
658
674
  { id: "reverse-audit", result: entry.reverseAudit?.status || "" },
659
675
  ],
660
676
  hashes: {
661
- prebuildWitnessSha256: entry.preBuildWitnessSha256,
662
- artifactWitnessSha256: entry.artifactWitnessSha256,
663
- prebuildCanonicalSha256: entry.witnessEvidence?.prebuild?.canonicalSha256 || "",
664
- artifactCanonicalSha256: entry.witnessEvidence?.artifact?.canonicalSha256 || "",
677
+ ...witnessHashes,
665
678
  },
666
679
  artifacts: entry.artifactWitness?.artifact?.name || entry.artifactWitness?.artifact?.path
667
680
  ? [entry.artifactWitness.artifact]
@@ -673,10 +686,25 @@ function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
673
686
  },
674
687
  auditBoundary: entry.auditBoundary,
675
688
  responsibility: entry.responsibility,
676
- residualRisk: entry.residualRisk,
677
- status: entry.trustProof?.result === "pass"
678
- ? (arrayOrEmpty(entry.residualRisk).length > 0 ? "downgraded" : "passed")
679
- : "failed",
689
+ residualRisk,
690
+ trustProof: {
691
+ contract: KFD2_TRUST_PROOF_CONTRACT,
692
+ source: "kfd-3-collaboration-interface-release-gate",
693
+ result: proofResult,
694
+ statement: entry.trustProof?.statement || "",
695
+ kfd3TrustProof: {
696
+ contract: entry.trustProof?.contract || "",
697
+ result: entry.trustProof?.result || "",
698
+ releaseStatus: entry.trustProof?.releaseStatus || entry.releaseStatus || "",
699
+ },
700
+ witnessHashes,
701
+ declaredCapabilityVerification: entry.declaredCapabilityVerification,
702
+ reverseAudit: entry.reverseAudit,
703
+ reverseAuditBoundary,
704
+ residualRisk,
705
+ responsibility: entry.responsibility,
706
+ },
707
+ status: proofResult,
680
708
  }, index);
681
709
  }
682
710
 
@@ -1220,6 +1248,48 @@ function validateKfd2ReleaseTrustPassportAudit(section, issues) {
1220
1248
  proseOnly: Boolean(claim.proseOnly),
1221
1249
  }));
1222
1250
  }
1251
+ if (String(claim.id || "").startsWith("kfd-3:")) {
1252
+ const trustProof = claim.trustProof && typeof claim.trustProof === "object" && !Array.isArray(claim.trustProof)
1253
+ ? claim.trustProof
1254
+ : undefined;
1255
+ if (!trustProof || trustProof.contract !== KFD2_TRUST_PROOF_CONTRACT) {
1256
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof`, "KFD-3 generated public claims must include a KFD-2 trust proof", {
1257
+ id: claim.id || "",
1258
+ expectedContract: KFD2_TRUST_PROOF_CONTRACT,
1259
+ }));
1260
+ } else {
1261
+ if (!trustProof.witnessHashes?.prebuildWitnessSha256 || !trustProof.witnessHashes?.artifactWitnessSha256) {
1262
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.witnessHashes`, "KFD-2 trust proof must preserve KFD-3 witness hashes", {
1263
+ id: claim.id || "",
1264
+ }));
1265
+ }
1266
+ if (!trustProof.declaredCapabilityVerification?.result) {
1267
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.declaredCapabilityVerification`, "KFD-2 trust proof must preserve declared capability verification", {
1268
+ id: claim.id || "",
1269
+ }));
1270
+ }
1271
+ if (!trustProof.reverseAudit?.status) {
1272
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.reverseAudit`, "KFD-2 trust proof must preserve reverse audit result", {
1273
+ id: claim.id || "",
1274
+ }));
1275
+ }
1276
+ if (!trustProof.reverseAuditBoundary || typeof trustProof.reverseAuditBoundary !== "object" || Array.isArray(trustProof.reverseAuditBoundary)) {
1277
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.reverseAuditBoundary`, "KFD-2 trust proof must preserve the reverse audit boundary", {
1278
+ id: claim.id || "",
1279
+ }));
1280
+ }
1281
+ if (!Array.isArray(trustProof.residualRisk)) {
1282
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.residualRisk`, "KFD-2 trust proof must preserve residual risk as an array", {
1283
+ id: claim.id || "",
1284
+ }));
1285
+ }
1286
+ if (!trustProof.responsibility?.registryFactsOwner || !trustProof.responsibility?.artifactVerificationOwner || !trustProof.responsibility?.releasePassportProofOwner) {
1287
+ issues.push(issue("error", `kfd-2.claims[${index}].trustProof.responsibility`, "KFD-2 trust proof must preserve KFD-3 responsibility state", {
1288
+ id: claim.id || "",
1289
+ }));
1290
+ }
1291
+ }
1292
+ }
1223
1293
  }
1224
1294
  if (section.status === "failed") {
1225
1295
  issues.push(issue("error", "kfd-2.status", "kfd-2 release trust passport audit must not contain failed public claims"));
@@ -30,6 +30,7 @@ const requiredPaths = [
30
30
  "scripts/create-release-bundle.mjs",
31
31
  "scripts/ensure-github-release.mjs",
32
32
  "scripts/generate-site-bundle.mjs",
33
+ "scripts/generate-buildchain-kfd-witnesses.mjs",
33
34
  "scripts/generate-release-candidate-passport.mjs",
34
35
  "scripts/artifact-relay-s3.mjs",
35
36
  "scripts/npm-publish-dry-run.mjs",
@@ -105,6 +106,18 @@ if (rootPackage.exports?.["./release-passport"] !== "./packages/core/release-pas
105
106
  if (rootPackage.exports?.["./release-propagation"] !== "./packages/core/release-propagation.js") {
106
107
  throw new Error("root package must export @kungfu-tech/buildchain/release-propagation");
107
108
  }
109
+ if (rootPackage.exports?.["./buildchain-kfd-claims"] !== "./packages/core/buildchain-kfd-claims.js") {
110
+ throw new Error("root package must export @kungfu-tech/buildchain/buildchain-kfd-claims");
111
+ }
112
+ if (rootPackage.exports?.["./site/manual-registry.json"] !== "./dist/site/manual-registry.json") {
113
+ throw new Error("root package must export @kungfu-tech/buildchain/site/manual-registry.json");
114
+ }
115
+ if (rootPackage.exports?.["./site/node-api-registry.json"] !== "./dist/site/node-api-registry.json") {
116
+ throw new Error("root package must export @kungfu-tech/buildchain/site/node-api-registry.json");
117
+ }
118
+ if (rootPackage.exports?.["./site/kfd-claims.json"] !== "./dist/site/kfd-claims.json") {
119
+ throw new Error("root package must export @kungfu-tech/buildchain/site/kfd-claims.json");
120
+ }
108
121
  if (rootPackage.publishConfig?.access !== "public") {
109
122
  throw new Error("root package publishConfig.access must be public");
110
123
  }
@@ -128,6 +141,7 @@ const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8")
128
141
  const coreIndexSource = fs.readFileSync(path.join(root, "packages/core/index.js"), "utf8");
129
142
  const versioningDoc = fs.readFileSync(path.join(root, "docs/versioning.md"), "utf8");
130
143
  const cliDoc = fs.readFileSync(path.join(root, "docs/cli.md"), "utf8");
144
+ const docsMap = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
131
145
  const installDoc = fs.readFileSync(path.join(root, "docs/install.md"), "utf8");
132
146
  if (!cliSource.startsWith("#!/usr/bin/env node")) {
133
147
  throw new Error("bin/buildchain.mjs must be executable with a node shebang");
@@ -150,6 +164,20 @@ if (!coreIndexSource.includes("createBuildchainContractWorld")) {
150
164
  if (!coreIndexSource.includes("KFD2_RELEASE_TRUST_PASSPORT_CONTRACT")) {
151
165
  throw new Error("packages/core/index.js must export KFD-2 release trust passport contract");
152
166
  }
167
+ if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
168
+ throw new Error("packages/core/index.js must export KFD-2 trust proof contract");
169
+ }
170
+ for (const requiredSnippet of [
171
+ "createBuildchainKfdClaimRegistry",
172
+ "createBuildchainKfd1Witness",
173
+ "createBuildchainKfd2Claims",
174
+ "createBuildchainKfd3PrebuildWitness",
175
+ "BUILDCHAIN_AGENT_MANUALS",
176
+ ]) {
177
+ if (!coreIndexSource.includes(requiredSnippet)) {
178
+ throw new Error(`packages/core/index.js must export Buildchain self KFD claim API: ${requiredSnippet}`);
179
+ }
180
+ }
153
181
  for (const requiredSnippet of [
154
182
  "createKfd1ReleaseGateEvidence",
155
183
  "resolveKfd1Metadata",
@@ -204,6 +232,20 @@ for (const requiredSnippet of [
204
232
  throw new Error(`release passport doc missing KFD-1 gate snippet: ${requiredSnippet}`);
205
233
  }
206
234
  }
235
+ for (const requiredSnippet of [
236
+ "Capability Coverage",
237
+ "KFD-1 / KFD-2 / KFD-3",
238
+ "floating `@v2`",
239
+ "npm publish transactions",
240
+ "GitHub Release",
241
+ "release propagation",
242
+ "manual-registry.json",
243
+ "node-api-registry.json",
244
+ ]) {
245
+ if (!docsMap.includes(requiredSnippet)) {
246
+ throw new Error(`documentation map missing capability coverage snippet: ${requiredSnippet}`);
247
+ }
248
+ }
207
249
  const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
208
250
  for (const requiredSnippet of [
209
251
  "Floating Ref Contract Lock",
@@ -291,6 +333,7 @@ for (const requiredSnippet of [
291
333
  "github.event.workflow_run.event == 'push'",
292
334
  "target-sha: ${{ github.event.workflow_run.head_sha || inputs.sha || github.sha }}",
293
335
  "github-release: true",
336
+ "release-passport-buildchain-self-kfd: true",
294
337
  "publish-required-artifacts-json: \"[]\"",
295
338
  "release-passport-impact-json: >-",
296
339
  "Buildchain v2.8 promotes KFD self-verification",
@@ -308,17 +351,21 @@ for (const requiredSnippet of [
308
351
  const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/release-candidate-promote.yml"), "utf8");
309
352
  for (const requiredSnippet of [
310
353
  "release-passport-kfd-1-witness-jsons:",
311
- "release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons }}",
354
+ "release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-1-witness-jsons }}",
312
355
  "release-passport-kfd-2-claim-jsons:",
313
- "release-passport-kfd-2-claim-jsons: ${{ inputs.release-passport-kfd-2-claim-jsons }}",
356
+ "release-passport-kfd-2-claim-jsons: ${{ inputs.release-passport-kfd-2-claim-jsons || steps.buildchain-self-kfd.outputs.kfd-2-claim-jsons }}",
314
357
  "release-passport-kfd-3-prebuild-witness-jsons:",
315
- "release-passport-kfd-3-prebuild-witness-jsons: ${{ inputs.release-passport-kfd-3-prebuild-witness-jsons }}",
358
+ "release-passport-kfd-3-prebuild-witness-jsons: ${{ inputs.release-passport-kfd-3-prebuild-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-3-prebuild-witness-jsons }}",
316
359
  "release-passport-kfd-3-artifact-witness-jsons:",
360
+ "release-passport-kfd-3-artifact-witness-jsons: ${{ inputs.release-passport-kfd-3-artifact-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-3-artifact-witness-jsons }}",
317
361
  "release-passport-kfd-3-artifact-verify-command:",
362
+ "release-passport-buildchain-self-kfd:",
363
+ "Generate Buildchain self KFD release claims",
364
+ "generate-buildchain-kfd-witnesses.mjs",
318
365
  "github-release:",
319
- "Publish GitHub Release evidence",
320
- "scripts/ensure-github-release.mjs",
321
- "gh release upload \"${RELEASE_TAG}\" \"${upload_args[@]}\" --clobber",
366
+ "github-release: ${{ inputs.github-release }}",
367
+ "github-release-title: ${{ inputs.github-release-title }}",
368
+ "github-release-notes: ${{ inputs.github-release-notes }}",
322
369
  "require-publish-source-lock: \"true\"",
323
370
  "publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
324
371
  "publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
@@ -374,6 +421,31 @@ for (const retiredWorkflow of [
374
421
  }
375
422
  }
376
423
  }
424
+ const promoteBuildchainRefAction = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/action.yml"), "utf8");
425
+ const promoteBuildchainRefIndex = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/index.js"), "utf8");
426
+ for (const requiredSnippet of [
427
+ "github-release:",
428
+ "github-release-title:",
429
+ "github-release-notes:",
430
+ "github-release-url:",
431
+ "github-release-action:",
432
+ ]) {
433
+ if (!promoteBuildchainRefAction.includes(requiredSnippet)) {
434
+ throw new Error(`promote-buildchain-ref action missing GitHub Release surface: ${requiredSnippet}`);
435
+ }
436
+ }
437
+ for (const requiredSnippet of [
438
+ "ensureGitHubRelease",
439
+ "publishGitHubReleaseEvidence",
440
+ "collectGitHubReleaseEvidenceAssets",
441
+ "uploadReleaseAsset",
442
+ "result.publishTransaction?.state === \"complete\"",
443
+ "result.publishTransaction?.finalizationNeeded !== true",
444
+ ]) {
445
+ if (!promoteBuildchainRefIndex.includes(requiredSnippet)) {
446
+ throw new Error(`promote-buildchain-ref index missing semver GitHub Release implementation: ${requiredSnippet}`);
447
+ }
448
+ }
377
449
  for (const forbiddenSnippet of [
378
450
  "run: node scripts/release-candidate-resolver.mjs",
379
451
  "uses: ./actions/promote-buildchain-ref",
@@ -490,7 +562,7 @@ for (const artifact of [
490
562
  }
491
563
  }
492
564
 
493
- for (const siteFile of ["buildchain-site.json", "site-manifest.json", "cli-registry.json", "release-model.json", "buildchain-contract.json"]) {
565
+ for (const siteFile of ["buildchain-site.json", "site-manifest.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "release-model.json", "buildchain-contract.json"]) {
494
566
  if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
495
567
  throw new Error(`site bundle missing ${siteFile}`);
496
568
  }
@@ -168,7 +168,15 @@ async function main() {
168
168
  console.log(`github-release-make-latest=${result.metadata.makeLatest}`);
169
169
  }
170
170
 
171
- if (import.meta.url === pathToFileURL(process.argv[1]).href) {
171
+ function isCliEntrypoint() {
172
+ const entry = process.argv[1] || "";
173
+ return (
174
+ entry.replace(/\\/g, "/").endsWith("/ensure-github-release.mjs") &&
175
+ import.meta.url === pathToFileURL(entry).href
176
+ );
177
+ }
178
+
179
+ if (isCliEntrypoint()) {
172
180
  main().catch((error) => {
173
181
  console.error(error.message);
174
182
  process.exit(1);
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { pathToFileURL } from "node:url";
6
+ import {
7
+ createBuildchainKfd1Witness,
8
+ createBuildchainKfd2Claims,
9
+ createBuildchainKfd3ArtifactWitness,
10
+ createBuildchainKfd3PrebuildWitness,
11
+ } from "../packages/core/buildchain-kfd-claims.js";
12
+ import { writeGitHubOutputs } from "./build-contract-core.mjs";
13
+
14
+ function parseArgs(argv = process.argv.slice(2)) {
15
+ const args = {
16
+ cwd: process.cwd(),
17
+ outputDir: ".buildchain/kfd",
18
+ sourceSha: process.env.BUILDCHAIN_SOURCE_SHA || "",
19
+ };
20
+ for (let index = 0; index < argv.length; index += 1) {
21
+ const arg = argv[index];
22
+ if (arg === "--cwd") {
23
+ args.cwd = argv[++index] || args.cwd;
24
+ } else if (arg === "--output-dir") {
25
+ args.outputDir = argv[++index] || args.outputDir;
26
+ } else if (arg === "--source-sha") {
27
+ args.sourceSha = argv[++index] || args.sourceSha;
28
+ } else if (arg === "--help") {
29
+ args.help = true;
30
+ } else {
31
+ throw new Error(`unknown argument: ${arg}`);
32
+ }
33
+ }
34
+ return args;
35
+ }
36
+
37
+ function gitSha(cwd) {
38
+ try {
39
+ return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim();
40
+ } catch {
41
+ return "";
42
+ }
43
+ }
44
+
45
+ function writeJson(filePath, value) {
46
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
47
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
48
+ }
49
+
50
+ function toRepoRelative(cwd, filePath) {
51
+ return path.relative(cwd, filePath).replace(/\\/g, "/");
52
+ }
53
+
54
+ export function generateBuildchainKfdWitnesses({
55
+ cwd = process.cwd(),
56
+ outputDir = ".buildchain/kfd",
57
+ sourceSha = "",
58
+ emitOutputs = true,
59
+ } = {}) {
60
+ const root = path.resolve(cwd);
61
+ const outDir = path.resolve(root, outputDir);
62
+ const resolvedSourceSha = sourceSha || gitSha(root);
63
+ const paths = {
64
+ kfd1Witness: path.join(outDir, "buildchain-kfd-1-witness.json"),
65
+ kfd3PrebuildWitness: path.join(outDir, "buildchain-kfd-3-prebuild-witness.json"),
66
+ kfd3ArtifactWitness: path.join(outDir, "buildchain-kfd-3-artifact-witness.json"),
67
+ kfd2ClaimsDir: path.join(outDir, "kfd-2-claims"),
68
+ };
69
+ writeJson(paths.kfd1Witness, createBuildchainKfd1Witness({ root, sourceSha: resolvedSourceSha }));
70
+ writeJson(paths.kfd3PrebuildWitness, createBuildchainKfd3PrebuildWitness({ root, sourceSha: resolvedSourceSha }));
71
+ writeJson(paths.kfd3ArtifactWitness, createBuildchainKfd3ArtifactWitness({ root, sourceSha: resolvedSourceSha }));
72
+ const witnessFiles = {
73
+ "kfd-1-witness": toRepoRelative(root, paths.kfd1Witness),
74
+ "kfd-3-prebuild-witness": toRepoRelative(root, paths.kfd3PrebuildWitness),
75
+ "kfd-3-artifact-witness": toRepoRelative(root, paths.kfd3ArtifactWitness),
76
+ };
77
+ const kfd2ClaimPaths = createBuildchainKfd2Claims({ root, witnessFiles }).map((claim) => {
78
+ const slug = String(claim.id || "claim")
79
+ .replace(/^claim:/, "")
80
+ .replace(/[^0-9A-Za-z._-]+/g, "-")
81
+ .replace(/^-+|-+$/g, "") || "claim";
82
+ const claimPath = path.join(paths.kfd2ClaimsDir, `${slug}.json`);
83
+ writeJson(claimPath, claim);
84
+ return claimPath;
85
+ });
86
+ const outputs = {
87
+ "kfd-1-witness-jsons": toRepoRelative(root, paths.kfd1Witness),
88
+ "kfd-2-claim-jsons": kfd2ClaimPaths.map((claimPath) => toRepoRelative(root, claimPath)).join(","),
89
+ "kfd-3-prebuild-witness-jsons": toRepoRelative(root, paths.kfd3PrebuildWitness),
90
+ "kfd-3-artifact-witness-jsons": toRepoRelative(root, paths.kfd3ArtifactWitness),
91
+ "source-sha": resolvedSourceSha,
92
+ "output-dir": toRepoRelative(root, outDir),
93
+ };
94
+ if (emitOutputs) {
95
+ writeGitHubOutputs(outputs);
96
+ }
97
+ return {
98
+ schemaVersion: 1,
99
+ contract: "kungfu-buildchain-self-kfd-witness-generation",
100
+ outputs,
101
+ };
102
+ }
103
+
104
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
105
+ try {
106
+ const args = parseArgs();
107
+ if (args.help) {
108
+ process.stdout.write("Usage: generate-buildchain-kfd-witnesses [--cwd <repo>] [--output-dir <dir>] [--source-sha <sha>]\n");
109
+ process.exit(0);
110
+ }
111
+ process.stdout.write(`${JSON.stringify(generateBuildchainKfdWitnesses(args), null, 2)}\n`);
112
+ } catch (error) {
113
+ console.error(`buildchain self KFD witnesses: ${error.message}`);
114
+ process.exitCode = 1;
115
+ }
116
+ }
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import crypto from "node:crypto";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { createBuildchainContractWorld } from "../packages/core/buildchain-contract.js";
7
+ import {
8
+ BUILDCHAIN_AGENT_MANUALS,
9
+ createBuildchainKfdClaimRegistry,
10
+ } from "../packages/core/buildchain-kfd-claims.js";
6
11
 
7
12
  const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
8
13
  const root = path.resolve(import.meta.dirname, "..");
@@ -21,6 +26,13 @@ function stableJson(value) {
21
26
  return `${JSON.stringify(value, null, 2)}\n`;
22
27
  }
23
28
 
29
+ function sha256File(rel) {
30
+ const filePath = path.join(root, rel);
31
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
32
+ ? `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`
33
+ : "";
34
+ }
35
+
24
36
  function existingJson(filePath) {
25
37
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
26
38
  }
@@ -32,30 +44,14 @@ function docEntry(id, title, pathName, plane) {
32
44
  path: pathName,
33
45
  plane,
34
46
  exists: fs.existsSync(path.join(root, pathName)),
47
+ digest: sha256File(pathName),
35
48
  };
36
49
  }
37
50
 
38
51
  function buildSiteBundle() {
39
52
  const packageJson = readJson("package.json");
40
53
  const inventory = readJson("tests/buildchain-inventory.json");
41
- const docs = [
42
- docEntry("install", "Install and verify Buildchain", "docs/install.md", "use"),
43
- docEntry("release-passport", "Release Passport protocol", "docs/release-passport.md", "verify"),
44
- docEntry("release-propagation", "Release propagation", "docs/release-propagation.md", "use"),
45
- docEntry("binary-distribution", "Binary distribution contract", "docs/binary-distribution.md", "verify"),
46
- docEntry("toolkit-observability", "Toolkit observability", "docs/toolkit-observability.md", "use"),
47
- docEntry("site-bundle-contract", "Site bundle contract", "docs/site-bundle-contract.md", "use"),
48
- docEntry("product-mechanism", "Product mechanism", "docs/product-mechanism.md", "why"),
49
- docEntry("cli", "CLI and npm package", "docs/cli.md", "use"),
50
- docEntry("lifecycle-protocol", "Lifecycle protocol", "docs/lifecycle-protocol.md", "use"),
51
- docEntry("reusable-build-surface", "Reusable build surface", "docs/reusable-build-surface.md", "use"),
52
- docEntry("publish-transaction", "Publish transaction", "docs/publish-transaction.md", "verify"),
53
- docEntry("release-governance", "Release governance", "docs/release-governance.md", "why"),
54
- docEntry("release-flow", "Release flow", "docs/release-flow.md", "verify"),
55
- docEntry("versioning", "Versioning", "docs/versioning.md", "why"),
56
- docEntry("web-surface-deployments", "Web surface deployments", "docs/web-surface-deployments.md", "use"),
57
- docEntry("infra-contract", "Infra Contract", "docs/infra-contract.md", "use"),
58
- ];
54
+ const docs = BUILDCHAIN_AGENT_MANUALS.map((manual) => docEntry(manual.id, manual.title, manual.path, manual.plane));
59
55
 
60
56
  const cliRegistry = {
61
57
  schemaVersion: 1,
@@ -80,6 +76,52 @@ function buildSiteBundle() {
80
76
  ],
81
77
  };
82
78
 
79
+ const manualRegistry = {
80
+ schemaVersion: 1,
81
+ contract: "kungfu-buildchain-agent-manual-registry",
82
+ package: packageJson.name,
83
+ source: "npm package docs/",
84
+ manuals: docs.map((entry) => ({
85
+ id: entry.id,
86
+ title: entry.title,
87
+ path: entry.path,
88
+ plane: entry.plane,
89
+ digest: entry.digest,
90
+ })),
91
+ requiredAgentManuals: [
92
+ "docs/MAP.md",
93
+ "docs/install.md",
94
+ "docs/cli.md",
95
+ "docs/reusable-build-surface.md",
96
+ "docs/release-candidate.md",
97
+ "docs/release-governance.md",
98
+ "docs/release-passport.md",
99
+ "docs/publish-transaction.md",
100
+ "docs/site-bundle-contract.md",
101
+ ],
102
+ guidance: "Agent consumers should use this registry to find packaged Buildchain manuals before inferring behavior from workflow snippets or release notes.",
103
+ };
104
+
105
+ const nodeApiRegistry = {
106
+ schemaVersion: 1,
107
+ contract: "kungfu-buildchain-node-api-registry",
108
+ package: packageJson.name,
109
+ moduleSystem: packageJson.type || "module",
110
+ exports: Object.entries(packageJson.exports || {})
111
+ .filter(([specifier]) => !specifier.startsWith("./site/") && specifier !== "./package.json")
112
+ .map(([specifier, target]) => ({
113
+ specifier: specifier === "." ? packageJson.name : `${packageJson.name}/${specifier.replace(/^\.\//, "")}`,
114
+ export: specifier,
115
+ target,
116
+ digest: typeof target === "string" ? sha256File(target.replace(/^\.\//, "")) : "",
117
+ })),
118
+ docs: [
119
+ { id: "cli-and-node-package", path: "docs/cli.md", digest: sha256File("docs/cli.md") },
120
+ { id: "site-bundle-contract", path: "docs/site-bundle-contract.md", digest: sha256File("docs/site-bundle-contract.md") },
121
+ ],
122
+ guidance: "These are the public Node import surfaces shipped by the npm package. Agents should prefer these exports over internal file paths.",
123
+ };
124
+
83
125
  const workflowRegistry = {
84
126
  schemaVersion: 1,
85
127
  contract: "kungfu-buildchain-workflow-registry",
@@ -124,6 +166,17 @@ function buildSiteBundle() {
124
166
  alphaDistTag: "alpha",
125
167
  stableDistTag: "latest",
126
168
  },
169
+ githubRelease: {
170
+ exactTagRelease: true,
171
+ prereleaseTags: "semver prerelease tags set prerelease=true and make_latest=false",
172
+ stableTags: "stable semver tags set make_latest=true",
173
+ evidenceAssets: [
174
+ "publish evidence JSON",
175
+ "buildchain.release.json",
176
+ "release passport assets",
177
+ ],
178
+ owner: "promote-buildchain-ref",
179
+ },
127
180
  };
128
181
 
129
182
  const artifactSchemas = {
@@ -144,10 +197,13 @@ function buildSiteBundle() {
144
197
  "buildchain-site.json",
145
198
  "site-manifest.json",
146
199
  "cli-registry.json",
200
+ "manual-registry.json",
201
+ "node-api-registry.json",
147
202
  "workflow-registry.json",
148
203
  "release-model.json",
149
204
  "artifact-schemas.json",
150
205
  "buildchain-contract.json",
206
+ "kfd-claims.json",
151
207
  "product-mechanism.json",
152
208
  "release-provenance.json",
153
209
  "agent-index.json",
@@ -211,11 +267,14 @@ function buildSiteBundle() {
211
267
  docs,
212
268
  facts: [
213
269
  "cli-registry.json",
270
+ "manual-registry.json",
271
+ "node-api-registry.json",
214
272
  "workflow-registry.json",
215
273
  "release-model.json",
216
274
  "artifact-schemas.json",
217
275
  "product-mechanism.json",
218
276
  "release-provenance.json",
277
+ "kfd-claims.json",
219
278
  "agent-index.json",
220
279
  ],
221
280
  };
@@ -228,9 +287,12 @@ function buildSiteBundle() {
228
287
  "product-mechanism.json",
229
288
  "release-model.json",
230
289
  "cli-registry.json",
290
+ "manual-registry.json",
291
+ "node-api-registry.json",
231
292
  "workflow-registry.json",
232
293
  "artifact-schemas.json",
233
294
  "buildchain-contract.json",
295
+ "kfd-claims.json",
234
296
  ],
235
297
  instruction: "Use this bundle as the package-owned fact source for Buildchain pages. Do not infer current release mechanics from prose alone.",
236
298
  };
@@ -252,10 +314,13 @@ function buildSiteBundle() {
252
314
  "buildchain-site.json": siteBundle,
253
315
  "site-manifest.json": siteManifest,
254
316
  "cli-registry.json": cliRegistry,
317
+ "manual-registry.json": manualRegistry,
318
+ "node-api-registry.json": nodeApiRegistry,
255
319
  "workflow-registry.json": workflowRegistry,
256
320
  "release-model.json": releaseModel,
257
321
  "artifact-schemas.json": artifactSchemas,
258
322
  "buildchain-contract.json": createBuildchainContractWorld({ root }),
323
+ "kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
259
324
  "product-mechanism.json": productMechanism,
260
325
  "release-provenance.json": releaseProvenance,
261
326
  "agent-index.json": agentIndex,
@@ -263,7 +328,7 @@ function buildSiteBundle() {
263
328
  }
264
329
 
265
330
  export function writeSiteBundle({ check = false } = {}) {
266
- const files = buildSiteBundle();
331
+ let files = buildSiteBundle();
267
332
  const mismatches = [];
268
333
  for (const [name, value] of Object.entries(files)) {
269
334
  const filePath = path.join(outputDir, name);
@@ -272,13 +337,32 @@ export function writeSiteBundle({ check = false } = {}) {
272
337
  if (existingJson(filePath) !== next) {
273
338
  mismatches.push(path.relative(root, filePath));
274
339
  }
275
- } else {
276
- writeJson(filePath, value);
277
340
  }
278
341
  }
279
342
  if (mismatches.length > 0) {
280
343
  throw new Error(`site bundle is stale: ${mismatches.join(", ")}`);
281
344
  }
345
+ if (!check) {
346
+ for (let iteration = 0; iteration < 5; iteration += 1) {
347
+ for (const [name, value] of Object.entries(files)) {
348
+ writeJson(path.join(outputDir, name), value);
349
+ }
350
+ const nextFiles = buildSiteBundle();
351
+ const stable = Object.entries(nextFiles).every(([name, value]) => (
352
+ existingJson(path.join(outputDir, name)) === stableJson(value)
353
+ ));
354
+ files = nextFiles;
355
+ if (stable) {
356
+ break;
357
+ }
358
+ if (iteration === 4) {
359
+ throw new Error("site bundle did not converge after 5 generations");
360
+ }
361
+ }
362
+ for (const [name, value] of Object.entries(files)) {
363
+ writeJson(path.join(outputDir, name), value);
364
+ }
365
+ }
282
366
  return {
283
367
  schemaVersion: 1,
284
368
  contract: "kungfu-buildchain-site-bundle-generation",