@kungfu-tech/buildchain 2.8.2-alpha.1 → 2.8.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.
@@ -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");
@@ -153,6 +167,17 @@ if (!coreIndexSource.includes("KFD2_RELEASE_TRUST_PASSPORT_CONTRACT")) {
153
167
  if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
154
168
  throw new Error("packages/core/index.js must export KFD-2 trust proof contract");
155
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
+ }
156
181
  for (const requiredSnippet of [
157
182
  "createKfd1ReleaseGateEvidence",
158
183
  "resolveKfd1Metadata",
@@ -207,6 +232,20 @@ for (const requiredSnippet of [
207
232
  throw new Error(`release passport doc missing KFD-1 gate snippet: ${requiredSnippet}`);
208
233
  }
209
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
+ }
210
249
  const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
211
250
  for (const requiredSnippet of [
212
251
  "Floating Ref Contract Lock",
@@ -294,6 +333,7 @@ for (const requiredSnippet of [
294
333
  "github.event.workflow_run.event == 'push'",
295
334
  "target-sha: ${{ github.event.workflow_run.head_sha || inputs.sha || github.sha }}",
296
335
  "github-release: true",
336
+ "release-passport-buildchain-self-kfd: true",
297
337
  "publish-required-artifacts-json: \"[]\"",
298
338
  "release-passport-impact-json: >-",
299
339
  "Buildchain v2.8 promotes KFD self-verification",
@@ -311,17 +351,21 @@ for (const requiredSnippet of [
311
351
  const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/release-candidate-promote.yml"), "utf8");
312
352
  for (const requiredSnippet of [
313
353
  "release-passport-kfd-1-witness-jsons:",
314
- "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 }}",
315
355
  "release-passport-kfd-2-claim-jsons:",
316
- "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 }}",
317
357
  "release-passport-kfd-3-prebuild-witness-jsons:",
318
- "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 }}",
319
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 }}",
320
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",
321
365
  "github-release:",
322
- "Publish GitHub Release evidence",
323
- "scripts/ensure-github-release.mjs",
324
- "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 }}",
325
369
  "require-publish-source-lock: \"true\"",
326
370
  "publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
327
371
  "publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
@@ -377,6 +421,31 @@ for (const retiredWorkflow of [
377
421
  }
378
422
  }
379
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
+ }
380
449
  for (const forbiddenSnippet of [
381
450
  "run: node scripts/release-candidate-resolver.mjs",
382
451
  "uses: ./actions/promote-buildchain-ref",
@@ -493,7 +562,7 @@ for (const artifact of [
493
562
  }
494
563
  }
495
564
 
496
- 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"]) {
497
566
  if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
498
567
  throw new Error(`site bundle missing ${siteFile}`);
499
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",