@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.
- package/README.md +4 -4
- package/dist/site/agent-index.json +2 -1
- package/dist/site/artifact-schemas.json +1 -0
- package/dist/site/badge-endpoint-registry.json +659 -0
- package/dist/site/badges/v1/buildchain-release-passport/aligned.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/declared.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/downgraded.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/draft.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/failed.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/missing.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/passed.json +14 -0
- package/dist/site/badges/v1/buildchain-release-passport/planned.json +14 -0
- package/dist/site/badges/v1/kfd-1/aligned.json +14 -0
- package/dist/site/badges/v1/kfd-1/declared.json +14 -0
- package/dist/site/badges/v1/kfd-1/downgraded.json +14 -0
- package/dist/site/badges/v1/kfd-1/draft.json +14 -0
- package/dist/site/badges/v1/kfd-1/failed.json +14 -0
- package/dist/site/badges/v1/kfd-1/missing.json +14 -0
- package/dist/site/badges/v1/kfd-1/passed.json +14 -0
- package/dist/site/badges/v1/kfd-1/planned.json +14 -0
- package/dist/site/badges/v1/kfd-2/aligned.json +14 -0
- package/dist/site/badges/v1/kfd-2/declared.json +14 -0
- package/dist/site/badges/v1/kfd-2/downgraded.json +14 -0
- package/dist/site/badges/v1/kfd-2/draft.json +14 -0
- package/dist/site/badges/v1/kfd-2/failed.json +14 -0
- package/dist/site/badges/v1/kfd-2/missing.json +14 -0
- package/dist/site/badges/v1/kfd-2/passed.json +14 -0
- package/dist/site/badges/v1/kfd-2/planned.json +14 -0
- package/dist/site/badges/v1/kfd-3/aligned.json +14 -0
- package/dist/site/badges/v1/kfd-3/declared.json +14 -0
- package/dist/site/badges/v1/kfd-3/downgraded.json +14 -0
- package/dist/site/badges/v1/kfd-3/draft.json +14 -0
- package/dist/site/badges/v1/kfd-3/failed.json +14 -0
- package/dist/site/badges/v1/kfd-3/missing.json +14 -0
- package/dist/site/badges/v1/kfd-3/passed.json +14 -0
- package/dist/site/badges/v1/kfd-3/planned.json +14 -0
- package/dist/site/buildchain-contract.json +6 -6
- package/dist/site/buildchain-site.json +25 -19
- package/dist/site/manual-registry.json +4 -4
- package/dist/site/node-api-registry.json +5 -5
- package/dist/site/page-registry.json +15 -10
- package/dist/site/site-manifest.json +9 -8
- package/docs/MAP.md +3 -0
- package/docs/readme-badges.md +48 -7
- package/docs/reusable-build-surface.md +49 -0
- package/docs/web-surface-deployments.md +7 -5
- package/package.json +1 -1
- package/packages/core/diagnostics.js +16 -0
- package/packages/core/index.js +3 -0
- package/packages/core/readme-badges.js +235 -15
- package/scripts/check-inventory.mjs +22 -1
- package/scripts/generate-site-bundle.mjs +17 -0
- package/scripts/locked-source-checkout.mjs +386 -0
- package/scripts/run-lifecycle-core.mjs +12 -0
- 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-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
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": "
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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).
|
package/docs/readme-badges.md
CHANGED
|
@@ -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,
|
|
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,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
461
|
-
recorded in each surface binding. Nested smoke URLs are derived from
|
|
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.
|
|
465
|
-
|
|
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
|
@@ -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 || {},
|
package/packages/core/index.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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
|
|