@kungfu-tech/buildchain 2.8.16 → 2.8.17

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 (55) hide show
  1. package/README.md +4 -4
  2. package/dist/site/agent-index.json +2 -1
  3. package/dist/site/artifact-schemas.json +1 -0
  4. package/dist/site/badge-endpoint-registry.json +659 -0
  5. package/dist/site/badges/v1/buildchain-release-passport/aligned.json +14 -0
  6. package/dist/site/badges/v1/buildchain-release-passport/declared.json +14 -0
  7. package/dist/site/badges/v1/buildchain-release-passport/downgraded.json +14 -0
  8. package/dist/site/badges/v1/buildchain-release-passport/draft.json +14 -0
  9. package/dist/site/badges/v1/buildchain-release-passport/failed.json +14 -0
  10. package/dist/site/badges/v1/buildchain-release-passport/missing.json +14 -0
  11. package/dist/site/badges/v1/buildchain-release-passport/passed.json +14 -0
  12. package/dist/site/badges/v1/buildchain-release-passport/planned.json +14 -0
  13. package/dist/site/badges/v1/kfd-1/aligned.json +14 -0
  14. package/dist/site/badges/v1/kfd-1/declared.json +14 -0
  15. package/dist/site/badges/v1/kfd-1/downgraded.json +14 -0
  16. package/dist/site/badges/v1/kfd-1/draft.json +14 -0
  17. package/dist/site/badges/v1/kfd-1/failed.json +14 -0
  18. package/dist/site/badges/v1/kfd-1/missing.json +14 -0
  19. package/dist/site/badges/v1/kfd-1/passed.json +14 -0
  20. package/dist/site/badges/v1/kfd-1/planned.json +14 -0
  21. package/dist/site/badges/v1/kfd-2/aligned.json +14 -0
  22. package/dist/site/badges/v1/kfd-2/declared.json +14 -0
  23. package/dist/site/badges/v1/kfd-2/downgraded.json +14 -0
  24. package/dist/site/badges/v1/kfd-2/draft.json +14 -0
  25. package/dist/site/badges/v1/kfd-2/failed.json +14 -0
  26. package/dist/site/badges/v1/kfd-2/missing.json +14 -0
  27. package/dist/site/badges/v1/kfd-2/passed.json +14 -0
  28. package/dist/site/badges/v1/kfd-2/planned.json +14 -0
  29. package/dist/site/badges/v1/kfd-3/aligned.json +14 -0
  30. package/dist/site/badges/v1/kfd-3/declared.json +14 -0
  31. package/dist/site/badges/v1/kfd-3/downgraded.json +14 -0
  32. package/dist/site/badges/v1/kfd-3/draft.json +14 -0
  33. package/dist/site/badges/v1/kfd-3/failed.json +14 -0
  34. package/dist/site/badges/v1/kfd-3/missing.json +14 -0
  35. package/dist/site/badges/v1/kfd-3/passed.json +14 -0
  36. package/dist/site/badges/v1/kfd-3/planned.json +14 -0
  37. package/dist/site/buildchain-contract.json +6 -6
  38. package/dist/site/buildchain-site.json +25 -19
  39. package/dist/site/manual-registry.json +4 -4
  40. package/dist/site/node-api-registry.json +5 -5
  41. package/dist/site/page-registry.json +15 -10
  42. package/dist/site/site-manifest.json +9 -8
  43. package/docs/MAP.md +3 -0
  44. package/docs/readme-badges.md +48 -7
  45. package/docs/reusable-build-surface.md +49 -0
  46. package/docs/web-surface-deployments.md +7 -5
  47. package/package.json +1 -1
  48. package/packages/core/diagnostics.js +16 -0
  49. package/packages/core/index.js +3 -0
  50. package/packages/core/readme-badges.js +235 -15
  51. package/scripts/check-inventory.mjs +22 -1
  52. package/scripts/generate-site-bundle.mjs +17 -0
  53. package/scripts/locked-source-checkout.mjs +386 -0
  54. package/scripts/run-lifecycle-core.mjs +12 -0
  55. package/scripts/web-surface-core.mjs +1 -8
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-07T15:39:12.336Z",
5
- "publishedAt": "2026-07-07T15:39:12.336Z",
4
+ "generatedAt": "2026-07-08T00:24:24.491Z",
5
+ "publishedAt": "2026-07-08T00:24:24.491Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "19d02b246e1242f10bdb93a2a85d2f38b25c79b0",
22
+ "sourceRevision": "186482073690d8f922f5f4ecbea20849dda724a4",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.8.16",
40
+ "version": "2.8.17",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -53,7 +53,7 @@
53
53
  "path": "docs/MAP.md",
54
54
  "plane": "use",
55
55
  "exists": true,
56
- "digest": "sha256:f4300eed89765cee0df38eb92de4d9d3adce4fbf954c735aead3bea74f05d975"
56
+ "digest": "sha256:03f52660d03223fff7daf500f995645d78a46fffd8cddec9d8c01bbee0863145"
57
57
  },
58
58
  {
59
59
  "id": "install",
@@ -85,7 +85,7 @@
85
85
  "path": "docs/readme-badges.md",
86
86
  "plane": "use",
87
87
  "exists": true,
88
- "digest": "sha256:3f4a44ea7f3b70cadc78a343fe53ade74b78fa369c19630cc9b926c1e460f66f"
88
+ "digest": "sha256:c886d5d469d143571ae68497643be754b1616b562fddc780a5708f49b4c46242"
89
89
  },
90
90
  {
91
91
  "id": "homebrew",
@@ -181,7 +181,7 @@
181
181
  "path": "docs/reusable-build-surface.md",
182
182
  "plane": "use",
183
183
  "exists": true,
184
- "digest": "sha256:165011e2a932461805925c714c2c1c07f1ee60c4b3a614b6dcdda4b81ec22d68"
184
+ "digest": "sha256:e76aefe23b83f1b99b8c13cee1ca99760fd9902a41fbec25e193426329826340"
185
185
  },
186
186
  {
187
187
  "id": "publish-transaction",
@@ -229,7 +229,7 @@
229
229
  "path": "docs/web-surface-deployments.md",
230
230
  "plane": "use",
231
231
  "exists": true,
232
- "digest": "sha256:4c7334fe2284fec3c1249124b0ff85f1242eb7a76d4b2c537c8b2cb6e0e598c1"
232
+ "digest": "sha256:5063ebb76db3cd36797561782e6c741e30d60bfa0dac8f798dbbd34c4ab2b3c0"
233
233
  }
234
234
  ],
235
235
  "facts": [
@@ -240,6 +240,7 @@
240
240
  "workflow-registry.json",
241
241
  "release-model.json",
242
242
  "artifact-schemas.json",
243
+ "badge-endpoint-registry.json",
243
244
  "product-mechanism.json",
244
245
  "release-provenance.json",
245
246
  "kfd-claims.json",
package/docs/MAP.md CHANGED
@@ -69,6 +69,7 @@ replace them.
69
69
  | What package-owned facts should buildchain.libkungfu.dev render? | [`site-bundle-contract.md`](site-bundle-contract.md) | use | stable |
70
70
  | How do I call the reusable build workflow? | [`reusable-build-surface.md`](reusable-build-surface.md) | use | stable |
71
71
  | How do self-hosted runners relay large artifacts through S3 before GitHub artifacts? | [`reusable-build-surface.md`](reusable-build-surface.md#artifact-transfer-relay) | use | stable |
72
+ | How do self-hosted runners reuse local Git checkout caches without weakening source locks? | [`reusable-build-surface.md`](reusable-build-surface.md#locked-source-checkout-cache) | use | stable |
72
73
  | How do I validate an unreleased Buildchain runtime train while keeping `@v2`? | [`runtime-train-validation.md`](runtime-train-validation.md) | use | stable |
73
74
  | How do I deploy a site/app preview, staging, or production surface? | [`web-surface-deployments.md`](web-surface-deployments.md) | use | stable |
74
75
  | How do I publish observed infrastructure contracts for downstream consumers? | [`infra-contract.md`](infra-contract.md) | use | preview |
@@ -102,6 +103,8 @@ replace them.
102
103
  [`../fixtures/libnode-shaped/README.md`](../fixtures/libnode-shaped/README.md).
103
104
  - **S3 artifact relay / self-hosted runner artifact transfer** ->
104
105
  [`reusable-build-surface.md`](reusable-build-surface.md#artifact-transfer-relay).
106
+ - **local Git checkout cache / self-hosted source transport** ->
107
+ [`reusable-build-surface.md`](reusable-build-surface.md#locked-source-checkout-cache).
105
108
  - **runtime train validation / temporary `buildchain-ref` override** ->
106
109
  [`runtime-train-validation.md`](runtime-train-validation.md) and
107
110
  [`reusable-build-surface.md`](reusable-build-surface.md).
@@ -2,8 +2,8 @@
2
2
 
3
3
  Buildchain can generate a managed README badge block from repository-owned
4
4
  facts. The README keeps only a projection; the source facts remain in
5
- `buildchain.toml`, package metadata, workflow files, and the repository's own
6
- release passport.
5
+ `buildchain.toml`, package metadata, workflow files, KFD standards metadata,
6
+ and the repository's own release passport.
7
7
 
8
8
  The managed block is delimited by:
9
9
 
@@ -32,11 +32,15 @@ import {
32
32
  `collectReadmeBadgeFacts({ cwd })` returns a machine-readable object with
33
33
  contract `kungfu-buildchain-readme-badge-facts`. It collects repository
34
34
  identity, package name/version/license, configured platforms, configured
35
- workflow status badges, release passport location and verification result, and
36
- KFD badge state. When present, it also summarizes local KFD claim registry and
37
- product-mechanism facts from the package-owned site bundle, so downstream
38
- agents can connect README badges back to Buildchain's KFD/source-of-truth
39
- surfaces.
35
+ workflow status badges, the repository's own Buildchain Release Passport
36
+ location and verification result, and KFD badge state. KFD badge labels,
37
+ human-facing concept text, standard document links, schema IDs, and interface
38
+ contracts are read from
39
+ `@kungfu-tech/kfd/standards.json` when the package is installed, or from an
40
+ explicit `kfd_standards` path/URL. When present, it also summarizes local KFD
41
+ claim registry and product-mechanism facts from the package-owned site bundle,
42
+ so downstream agents can connect README badges back to Buildchain's
43
+ KFD/source-of-truth surfaces.
40
44
 
41
45
  `renderReadmeBadgeBlock(facts)` renders deterministic Markdown from that facts
42
46
  object. `checkReadmeBadgeBlock({ readmeText, facts })` compares the current
@@ -78,6 +82,7 @@ cannot be inferred safely:
78
82
  ```toml
79
83
  [badges]
80
84
  release_passport = "https://github.com/example/project/releases/latest/download/buildchain.release.json"
85
+ kfd_standards = "node_modules/@kungfu-tech/kfd/standards.json"
81
86
  kfd_1 = "declared"
82
87
  kfd_2 = "planned"
83
88
  kfd_3 = "aligned"
@@ -90,11 +95,47 @@ workflows = ["verify.yml", "build.yml"]
90
95
  then the repository's latest GitHub Release asset when the GitHub repository
91
96
  can be discovered.
92
97
 
98
+ The generated `Buildchain Release Passport` badge is a repository capability
99
+ badge: it says whether the current repository has a Buildchain release passport
100
+ that can be verified. It does not report the upstream `kungfu-systems/buildchain`
101
+ repository status. Buildchain's own README dogfoods the same rule because its
102
+ configured `release_passport` points at Buildchain's own release passport.
103
+
104
+ Buildchain-owned badges use stable hosted image URLs by default:
105
+
106
+ ```text
107
+ https://buildchain.libkungfu.dev/badges/v1/{badge}/{state}.svg
108
+ ```
109
+
110
+ The URL is part of the Buildchain badge contract. Consumers do not need to
111
+ regenerate README files when Buildchain replaces the placeholder badge logo
112
+ with a formal logo; the hosted endpoint owns logo rendering. The package-owned
113
+ site bundle publishes `badge-endpoint-registry.json` plus Shields-compatible
114
+ JSON payloads under `badges/v1/**` so the site repository can serve or render
115
+ the exact SVG endpoints without inventing badge facts.
116
+
117
+ Forks or private deployments can override the image host with:
118
+
119
+ ```toml
120
+ [badges]
121
+ badge_endpoint_base_url = "https://example.com/buildchain-badges/v1"
122
+ ```
123
+
124
+ `kfd_standards` is optional. If omitted, Buildchain tries the installed
125
+ `@kungfu-tech/kfd/standards.json` package export. Use the explicit path or URL
126
+ only when a repository deliberately vendors KFD standards metadata or validates
127
+ against a local KFD development checkout. The KFD standards metadata controls
128
+ the badge vocabulary; the release passport still controls whether a repository
129
+ may display a KFD state as `passed`.
130
+
93
131
  ## KFD Badge Rules
94
132
 
95
133
  KFD passed is evidence-backed. A repository may display `KFD-1 passed`,
96
134
  `KFD-2 passed`, or `KFD-3 passed` only when its own release passport verifies
97
135
  successfully and the corresponding passport section has `status: "passed"`.
136
+ The KFD badge vocabulary comes from KFD standards metadata, not Buildchain
137
+ private strings: for example KFD-2 uses the `releaseTrustPassport` concept from
138
+ `@kungfu-tech/kfd/standards.json`.
98
139
 
99
140
  When no release passport exists yet, or when the passport cannot be verified,
100
141
  Buildchain downgrades each KFD badge to the explicit local declaration such as
@@ -214,6 +214,55 @@ The lock is intentionally small. It does not copy the full contract. The full
214
214
  contract remains in the Buildchain ref and package; the consumer records only
215
215
  what it accepted and the policy used to compare future floating-ref movement.
216
216
 
217
+ ## Locked Source Checkout Cache
218
+
219
+ Self-hosted runners that build large repositories can opt into a locked source
220
+ checkout cache. This changes only the Git object transport. Buildchain still
221
+ resolves `publish-source-sha` before any build runner starts, checks out that
222
+ exact commit, and verifies `HEAD` plus the resolved source tree SHA before
223
+ lifecycle commands run.
224
+
225
+ ```yaml
226
+ jobs:
227
+ build:
228
+ uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2
229
+ with:
230
+ runner-preset: kungfu-v4-self-hosted
231
+ checkout-cache-mode: auto
232
+ checkout-cache-mirror-url-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE }}
233
+ checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}
234
+ checkout-cache-fallback: github
235
+ checkout-cache-timeout-seconds: 60
236
+ ```
237
+
238
+ `checkout-cache-mode` accepts:
239
+
240
+ | Mode | Behavior |
241
+ | --- | --- |
242
+ | `off` | Default. Buildchain fetches the locked commit from GitHub. |
243
+ | `auto` | Try the trusted cache first; on miss, record the miss and fall back according to `checkout-cache-fallback`. |
244
+ | `require` | Require the cache to provide the locked commit and fail before lifecycle work if unavailable. |
245
+
246
+ The cache can be a local/LAN mirror URL template or a runner-local bare
247
+ reference repository template. Templates support `{owner}`, `{repo}`,
248
+ `{repository}`, `{repositorySlug}`, and `{sha}`. The workflow also reads
249
+ repository or organization variables named
250
+ `BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE` and
251
+ `BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE`, so consumers can keep
252
+ private LAN topology out of repository YAML.
253
+
254
+ Do not read cache URLs or reference paths from PR-controlled files such as
255
+ `buildchain.toml`. These values are trusted workflow inputs or repo/org
256
+ variables. Buildchain does not pass GitHub credentials to cache mirrors or
257
+ reference repositories. If it must fall back to GitHub, the workflow token is
258
+ used only for the GitHub fetch path.
259
+
260
+ Each platform diagnostics artifact includes `source-checkout.json` and embeds a
261
+ compact `sourceCheckout` summary in `diagnostics.json`: mode, transport,
262
+ hit/miss, fallback reason, duration, final HEAD verification, and tree
263
+ verification. Remote URLs are sanitized and local reference paths are represented
264
+ by a short display name plus fingerprint, not by secret-bearing credentials.
265
+
217
266
  When a Buildchain maintainer asks for downstream validation, the expected
218
267
  request is:
219
268
 
@@ -457,18 +457,20 @@ node scripts/web-surface.mjs \
457
457
  --output .buildchain/web-surface-production-health.json
458
458
  ```
459
459
 
460
- The health check fetches every surface root URL and the nested smoke URLs
461
- recorded in each surface binding. Nested smoke URLs are derived from the
460
+ The health check fetches every surface root URL and any nested smoke URLs
461
+ recorded in each surface binding. Nested smoke URLs are derived from nested HTML
462
462
  artifact files under the surface path prefix, with directory index resolution
463
463
  such as `dist/buildchain/docs/index.html` becoming `/docs/` on the buildchain
464
- preview host. This fails closed when a deploy reports success but a child page
465
- returns 403 or another unexpected status. Production additionally fails if a
464
+ preview host. If a surface has no nested HTML route, Buildchain records only
465
+ the root smoke URL; absence of nested HTML is not a deployment failure. When a
466
+ nested route is present, the check fails closed if a deploy reports success but
467
+ that child page returns 403 or another unexpected status. Production additionally fails if a
466
468
  response is unreachable, returns an unexpected status, or still sends
467
469
  `x-robots-tag: noindex`. The health check also verifies that each surface
468
470
  binding recorded a deployment manifest pointer. The production release passport
469
471
  embeds the deploy plan, apply result, production preflight, and health check so
470
472
  a reviewer or agent can audit why the production site changed and whether every
471
- declared host and nested route was actually covered.
473
+ declared host and every existing nested route was actually covered.
472
474
 
473
475
  ## Cleanup Plans
474
476
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.8.16",
3
+ "version": "2.8.17",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -33,6 +33,8 @@ export const BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT =
33
33
  "kungfu-buildchain-process-sample-report";
34
34
  export const BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT =
35
35
  "kungfu-buildchain-process-sample-summary";
36
+ export const BUILDCHAIN_LOCKED_SOURCE_CHECKOUT_CONTRACT =
37
+ "kungfu-buildchain-locked-source-checkout-cache";
36
38
 
37
39
  const DEFAULT_SECRET_KEY_PATTERN =
38
40
  /(authorization|cookie|credential|password|passwd|private[_-]?key|secret|token|api[_-]?key)/i;
@@ -1084,6 +1086,7 @@ export function createDiagnosticsArtifact({
1084
1086
  processSamples = [],
1085
1087
  processSummary = undefined,
1086
1088
  requestedParallelism = 0,
1089
+ sourceCheckout = undefined,
1087
1090
  links = {},
1088
1091
  } = {}) {
1089
1092
  const resolvedCwd = path.resolve(cwd);
@@ -1107,6 +1110,7 @@ export function createDiagnosticsArtifact({
1107
1110
  git: collectGitDiagnostics({ cwd: resolvedCwd }),
1108
1111
  lifecycleObservability: lifecycleObservability || summarizeLifecycleObservability({ events, logPath }),
1109
1112
  process: processSummary || summarizeProcessSamples({ samples: processSamples, requestedParallelism }),
1113
+ ...(sourceCheckout ? { sourceCheckout } : {}),
1110
1114
  links,
1111
1115
  };
1112
1116
  }
@@ -1509,6 +1513,18 @@ export function summarizeDiagnosticsArtifacts(inputs = []) {
1509
1513
  ? entry.native.cacheDirs
1510
1514
  : [],
1511
1515
  process: entry.process || {},
1516
+ sourceCheckout: entry.sourceCheckout
1517
+ ? {
1518
+ mode: entry.sourceCheckout.policy?.mode || "",
1519
+ transport: entry.sourceCheckout.cache?.transport || "",
1520
+ hit: Boolean(entry.sourceCheckout.cache?.hit),
1521
+ fallbackUsed: Boolean(entry.sourceCheckout.cache?.fallbackUsed),
1522
+ fallbackReason: entry.sourceCheckout.cache?.fallbackReason || "",
1523
+ durationMs: Number(entry.sourceCheckout.durationMs || 0),
1524
+ headOk: Boolean(entry.sourceCheckout.verification?.headOk),
1525
+ treeOk: Boolean(entry.sourceCheckout.verification?.treeOk),
1526
+ }
1527
+ : undefined,
1512
1528
  diagnosticsContract,
1513
1529
  ...(diagnosticsManifest ? { diagnosticsManifest } : {}),
1514
1530
  links: entry.links || {},
@@ -60,6 +60,7 @@ export {
60
60
  BUILDCHAIN_DIAGNOSTICS_MANIFEST_CONTRACT,
61
61
  BUILDCHAIN_DIAGNOSTICS_SUMMARY_CONTRACT,
62
62
  BUILDCHAIN_LIFECYCLE_OBSERVABILITY_CONTRACT,
63
+ BUILDCHAIN_LOCKED_SOURCE_CHECKOUT_CONTRACT,
63
64
  BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT,
64
65
  BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT,
65
66
  classifyProcessCommand,
@@ -196,8 +197,10 @@ export {
196
197
  README_BADGE_BLOCK_END,
197
198
  README_BADGE_BLOCK_START,
198
199
  README_BADGE_FACTS_CONTRACT,
200
+ README_BADGE_HOSTED_BASE_URL,
199
201
  checkReadmeBadgeBlock,
200
202
  collectReadmeBadgeFacts,
203
+ createReadmeBadgeEndpointRegistry,
201
204
  readReadme,
202
205
  renderReadmeBadgeBlock,
203
206
  updateReadmeBadgeBlock,
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import crypto from "node:crypto";
5
+ import { createRequire } from "node:module";
5
6
  import { loadBuildchainConfig } from "./buildchain-config.js";
6
7
  import {
7
8
  readJsonFromLocation,
@@ -11,6 +12,7 @@ import {
11
12
  export const README_BADGE_FACTS_CONTRACT = "kungfu-buildchain-readme-badge-facts";
12
13
  export const README_BADGE_BLOCK_START = "<!-- buildchain:badges:start -->";
13
14
  export const README_BADGE_BLOCK_END = "<!-- buildchain:badges:end -->";
15
+ export const README_BADGE_HOSTED_BASE_URL = "https://buildchain.libkungfu.dev/badges/v1";
14
16
 
15
17
  const KFD_KEYS = [
16
18
  { key: "kfd-1", id: "kfd1", label: "KFD-1", text: "contract world" },
@@ -18,6 +20,14 @@ const KFD_KEYS = [
18
20
  { key: "kfd-3", id: "kfd3", label: "KFD-3", text: "collaboration interface" },
19
21
  ];
20
22
 
23
+ const KFD_BADGE_CONCEPTS = {
24
+ "kfd-1": "contractWorld",
25
+ "kfd-2": "releaseTrustPassport",
26
+ "kfd-3": "collaborationInterface",
27
+ };
28
+
29
+ const requireFromHere = createRequire(import.meta.url);
30
+
21
31
  const STATE_COLORS = {
22
32
  passed: "2ea44f",
23
33
  aligned: "0969da",
@@ -30,6 +40,22 @@ const STATE_COLORS = {
30
40
  unknown: "6e7781",
31
41
  };
32
42
 
43
+ const BUILDCHAIN_BADGE_IDS = [
44
+ "kfd-1",
45
+ "kfd-2",
46
+ "kfd-3",
47
+ "buildchain-release-passport",
48
+ ];
49
+
50
+ const BUILDCHAIN_BADGE_LOGO_PLACEHOLDER = {
51
+ contract: "kungfu-buildchain-badge-logo-policy",
52
+ mode: "hosted-placeholder",
53
+ placeholder: "buildchain-monogram",
54
+ futureLogoUpdateRequiresConsumerAction: false,
55
+ owner: "Buildchain site badge endpoint",
56
+ note: "README image URLs point at stable Buildchain-hosted badge endpoints; the rendered logo is controlled by the endpoint, not by consumer README text.",
57
+ };
58
+
33
59
  function readJsonIfExists(filePath) {
34
60
  if (!filePath || !fs.existsSync(filePath)) {
35
61
  return undefined;
@@ -37,6 +63,18 @@ function readJsonIfExists(filePath) {
37
63
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
38
64
  }
39
65
 
66
+ function readPackageJsonForResolvedFile(filePath) {
67
+ let current = path.dirname(filePath);
68
+ while (current && current !== path.dirname(current)) {
69
+ const packagePath = path.join(current, "package.json");
70
+ if (fs.existsSync(packagePath)) {
71
+ return readJsonIfExists(packagePath) || {};
72
+ }
73
+ current = path.dirname(current);
74
+ }
75
+ return {};
76
+ }
77
+
40
78
  function readTextIfExists(filePath) {
41
79
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
42
80
  }
@@ -59,6 +97,61 @@ function badgeUrl({ label, message, color }) {
59
97
  return `https://img.shields.io/badge/${encodeBadge(label)}-${encodeBadge(message)}-${color || "6e7781"}.svg`;
60
98
  }
61
99
 
100
+ function badgeEndpointBaseUrl(badgeConfig = {}) {
101
+ return String(
102
+ badgeConfig.badge_endpoint_base_url ||
103
+ badgeConfig.badgeEndpointBaseUrl ||
104
+ README_BADGE_HOSTED_BASE_URL,
105
+ ).replace(/\/+$/, "");
106
+ }
107
+
108
+ function hostedBadgeUrl({ badgeConfig = {}, id, state }) {
109
+ return `${badgeEndpointBaseUrl(badgeConfig)}/${encodeURIComponent(id)}/${encodeURIComponent(state || "unknown")}.svg`;
110
+ }
111
+
112
+ export function createReadmeBadgeEndpointRegistry({ kfdSpecs = KFD_KEYS } = {}) {
113
+ const states = Object.keys(STATE_COLORS).filter((state) => state !== "unknown");
114
+ const specs = [
115
+ ...kfdSpecs.map((entry) => ({
116
+ id: entry.key,
117
+ label: entry.label,
118
+ messageTemplate: `${entry.text} {state}`,
119
+ linkRole: "repository-release-passport",
120
+ })),
121
+ {
122
+ id: "buildchain-release-passport",
123
+ label: "buildchain",
124
+ messageTemplate: "release passport {state}",
125
+ linkRole: "repository-release-passport",
126
+ },
127
+ ];
128
+ return {
129
+ schemaVersion: 1,
130
+ contract: "kungfu-buildchain-readme-badge-endpoint-registry",
131
+ endpointBaseUrl: README_BADGE_HOSTED_BASE_URL,
132
+ imagePathTemplate: "/badges/v1/{badge}/{state}.svg",
133
+ jsonPathTemplate: "/badges/v1/{badge}/{state}.json",
134
+ consumerActionForLogoChange: "none",
135
+ logoPolicy: BUILDCHAIN_BADGE_LOGO_PLACEHOLDER,
136
+ shieldsEndpointCompatible: true,
137
+ badges: specs.map((spec) => ({
138
+ ...spec,
139
+ states: states.map((state) => ({
140
+ state,
141
+ path: `badges/v1/${spec.id}/${state}.json`,
142
+ svgPath: `badges/v1/${spec.id}/${state}.svg`,
143
+ payload: {
144
+ schemaVersion: 1,
145
+ label: spec.label,
146
+ message: spec.messageTemplate.replace("{state}", state),
147
+ color: STATE_COLORS[state] || STATE_COLORS.unknown,
148
+ logoPolicy: BUILDCHAIN_BADGE_LOGO_PLACEHOLDER,
149
+ },
150
+ })),
151
+ })),
152
+ };
153
+ }
154
+
62
155
  function normalizeState(value, fallback = "planned") {
63
156
  const normalized = String(value || fallback).trim().toLowerCase();
64
157
  if (["passed", "aligned", "declared", "planned", "draft", "downgraded", "failed", "missing"].includes(normalized)) {
@@ -81,6 +174,128 @@ function normalizeStringArray(value) {
81
174
  .filter(Boolean);
82
175
  }
83
176
 
177
+ function resolveLocalFactPath({ cwd, location }) {
178
+ const value = String(location || "").trim();
179
+ if (!value || /^https?:\/\//.test(value)) {
180
+ return "";
181
+ }
182
+ return path.isAbsolute(value) ? value : path.resolve(cwd, value);
183
+ }
184
+
185
+ function kfdStandardsLocation({ cwd, badgeConfig = {} }) {
186
+ return String(
187
+ badgeConfig.kfd_standards ||
188
+ badgeConfig.kfdStandards ||
189
+ badgeConfig.kfd_standards_path ||
190
+ badgeConfig.kfdStandardsPath ||
191
+ process.env.BUILDCHAIN_KFD_STANDARDS_PATH ||
192
+ "",
193
+ ).trim();
194
+ }
195
+
196
+ async function loadKfdStandards({ cwd, badgeConfig = {} }) {
197
+ const configured = kfdStandardsLocation({ cwd, badgeConfig });
198
+ if (configured) {
199
+ const localPath = resolveLocalFactPath({ cwd, location: configured });
200
+ const value = /^https?:\/\//.test(configured)
201
+ ? await readJsonFromLocation(configured)
202
+ : readJsonIfExists(localPath);
203
+ if (!value || typeof value !== "object") {
204
+ throw new Error(`KFD standards metadata is not readable: ${configured}`);
205
+ }
206
+ return {
207
+ location: configured,
208
+ resolvedPath: localPath,
209
+ source: /^https?:\/\//.test(configured) ? "configured-url" : "configured-path",
210
+ sha256: localPath && fs.existsSync(localPath) ? sha256File(localPath) : hashText(JSON.stringify(value)),
211
+ package: {},
212
+ value,
213
+ error: "",
214
+ };
215
+ }
216
+ try {
217
+ const resolvedPath = requireFromHere.resolve("@kungfu-tech/kfd/standards.json", {
218
+ paths: [cwd],
219
+ });
220
+ const value = readJsonIfExists(resolvedPath);
221
+ const packageJson = readPackageJsonForResolvedFile(resolvedPath);
222
+ return {
223
+ location: "@kungfu-tech/kfd/standards.json",
224
+ resolvedPath,
225
+ source: "package-export",
226
+ sha256: sha256File(resolvedPath),
227
+ package: {
228
+ name: packageJson.name || "@kungfu-tech/kfd",
229
+ version: packageJson.version || "",
230
+ },
231
+ value,
232
+ error: "",
233
+ };
234
+ } catch (error) {
235
+ return {
236
+ location: "@kungfu-tech/kfd/standards.json",
237
+ resolvedPath: "",
238
+ source: "fallback",
239
+ sha256: "",
240
+ package: {},
241
+ value: undefined,
242
+ error: error.message,
243
+ };
244
+ }
245
+ }
246
+
247
+ function summarizeKfdStandards(loaded) {
248
+ const standards = loaded?.value?.standards && typeof loaded.value.standards === "object"
249
+ ? loaded.value.standards
250
+ : {};
251
+ const summary = {
252
+ contract: loaded?.value?.contract || "",
253
+ schemaVersion: loaded?.value?.schemaVersion || undefined,
254
+ metadataSchema: loaded?.value?.metadataSchema || undefined,
255
+ source: loaded?.source || "fallback",
256
+ location: loaded?.location || "",
257
+ sha256: loaded?.sha256 || "",
258
+ package: loaded?.package || {},
259
+ error: loaded?.error || "",
260
+ standards: {},
261
+ };
262
+ for (const fallback of KFD_KEYS) {
263
+ const standard = standards[fallback.key] || {};
264
+ const conceptKey = KFD_BADGE_CONCEPTS[fallback.key];
265
+ summary.standards[fallback.key] = {
266
+ key: standard.key || fallback.key,
267
+ id: standard.id || fallback.label,
268
+ label: standard.label || fallback.label,
269
+ title: standard.title || "",
270
+ status: standard.status || "",
271
+ revision: standard.revision || undefined,
272
+ documentUrl: standard.document?.url || "",
273
+ documentPath: standard.document?.path || "",
274
+ documentSha256: standard.document?.sha256 || "",
275
+ badgeText: standard.concepts?.[conceptKey] || fallback.text,
276
+ interfaceContract: conceptKey ? standard.interfaces?.[conceptKey]?.contract || "" : "",
277
+ schemaId: conceptKey ? standard.interfaces?.[conceptKey]?.schemaId || standard.schemaIds?.[conceptKey] || "" : "",
278
+ };
279
+ }
280
+ return summary;
281
+ }
282
+
283
+ function kfdBadgeSpecs(kfdStandards) {
284
+ return KFD_KEYS.map((fallback) => {
285
+ const standard = kfdStandards?.standards?.[fallback.key] || {};
286
+ return {
287
+ ...fallback,
288
+ label: standard.label || fallback.label,
289
+ text: standard.badgeText || fallback.text,
290
+ title: standard.title || "",
291
+ standardDocumentUrl: standard.documentUrl || "",
292
+ standardDocumentSha256: standard.documentSha256 || "",
293
+ interfaceContract: standard.interfaceContract || "",
294
+ schemaId: standard.schemaId || "",
295
+ };
296
+ });
297
+ }
298
+
84
299
  function readRepositoryFromGit(cwd) {
85
300
  const remote = spawnSync("git", ["config", "--get", "remote.origin.url"], {
86
301
  cwd,
@@ -273,28 +488,20 @@ function localFactFileSummary(cwd, candidates = []) {
273
488
  return undefined;
274
489
  }
275
490
 
276
- function buildBadgeEntries(facts) {
491
+ function buildBadgeEntries(facts, { badgeConfig = {} } = {}) {
277
492
  const entries = [];
278
493
  for (const kfd of facts.kfd) {
279
494
  entries.push({
280
495
  id: kfd.key,
281
496
  alt: `${kfd.label}: ${kfd.state}`,
282
- image: badgeUrl({
283
- label: kfd.label,
284
- message: `${kfd.text} ${kfd.state}`,
285
- color: STATE_COLORS[kfd.state] || STATE_COLORS.unknown,
286
- }),
497
+ image: hostedBadgeUrl({ badgeConfig, id: kfd.key, state: kfd.state }),
287
498
  link: kfd.url,
288
499
  });
289
500
  }
290
501
  entries.push({
291
- id: "release-passport",
292
- alt: `Release Passport: ${facts.releasePassport.state}`,
293
- image: badgeUrl({
294
- label: "release passport",
295
- message: facts.releasePassport.state,
296
- color: STATE_COLORS[facts.releasePassport.state] || STATE_COLORS.unknown,
297
- }),
502
+ id: "buildchain-release-passport",
503
+ alt: `Buildchain Release Passport: ${facts.releasePassport.state}`,
504
+ image: hostedBadgeUrl({ badgeConfig, id: "buildchain-release-passport", state: facts.releasePassport.state }),
298
505
  link: facts.releasePassport.url,
299
506
  });
300
507
  if (facts.package.license) {
@@ -344,13 +551,19 @@ export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
344
551
  ? `${repository.url}/blob/HEAD/${posixPath(releasePassportLocation)}`
345
552
  : releasePassportLocation;
346
553
  const releaseState = releasePassportState({ report, error, badgeConfig });
347
- const kfd = KFD_KEYS.map((entry) => {
554
+ const kfdStandards = summarizeKfdStandards(await loadKfdStandards({ cwd: resolvedCwd, badgeConfig }));
555
+ const kfd = kfdBadgeSpecs(kfdStandards).map((entry) => {
348
556
  const passed = kfdSectionPassed({ passport, report, key: entry.key });
349
557
  const state = passed ? "passed" : declaredKfdState({ badgeConfig, key: entry.key, id: entry.id });
350
558
  return {
351
559
  key: entry.key,
352
560
  label: entry.label,
353
561
  text: entry.text,
562
+ title: entry.title,
563
+ standardDocumentUrl: entry.standardDocumentUrl,
564
+ standardDocumentSha256: entry.standardDocumentSha256,
565
+ interfaceContract: entry.interfaceContract,
566
+ schemaId: entry.schemaId,
354
567
  state,
355
568
  source: passed ? "release-passport" : "declaration",
356
569
  url: releasePassportUrl,
@@ -382,6 +595,7 @@ export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
382
595
  "dist/site/kfd-claims.json",
383
596
  ".buildchain/kfd-claims.json",
384
597
  ]),
598
+ kfdStandards,
385
599
  productMechanism: localFactFileSummary(resolvedCwd, [
386
600
  "dist/site/product-mechanism.json",
387
601
  "product-mechanism.json",
@@ -390,9 +604,15 @@ export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
390
604
  kfd,
391
605
  platforms: collectPlatformFacts({ badgeConfig, passport }),
392
606
  workflows: discoverWorkflowFacts({ cwd: resolvedCwd, repository, badgeConfig }),
607
+ badgeRuntime: {
608
+ provider: "buildchain-hosted",
609
+ endpointBaseUrl: badgeEndpointBaseUrl(badgeConfig),
610
+ hostedBadgeIds: BUILDCHAIN_BADGE_IDS,
611
+ logoPolicy: BUILDCHAIN_BADGE_LOGO_PLACEHOLDER,
612
+ },
393
613
  badges: [],
394
614
  };
395
- facts.badges = buildBadgeEntries(facts);
615
+ facts.badges = buildBadgeEntries(facts, { badgeConfig });
396
616
  return facts;
397
617
  }
398
618