@kungfu-tech/buildchain 2.7.1-alpha.0 → 2.8.0-alpha.0
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/actions/promote-buildchain-ref/README.md +5 -0
- package/bin/buildchain.mjs +2 -0
- package/dist/site/release-provenance.json +1 -0
- package/docs/MAP.md +3 -0
- package/docs/cli.md +10 -0
- package/docs/release-passport.md +50 -0
- package/docs/versioning.md +1 -0
- package/package.json +3 -1
- package/packages/core/index.js +11 -0
- package/packages/core/kfd-gate.js +413 -0
- package/packages/core/release-passport.js +25 -1
- package/scripts/check-inventory.mjs +39 -0
|
@@ -209,6 +209,11 @@ After a publish transaction reaches `complete`, the action generates the unified
|
|
|
209
209
|
persists those files under `release-passport/` in the durable release-state ref.
|
|
210
210
|
Set `release-passport-product-name` to record the consumer product name, for
|
|
211
211
|
example `Libnode`, instead of the default `Buildchain`.
|
|
212
|
+
Set `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1
|
|
213
|
+
contract-world witness JSON paths when released artifacts must prove
|
|
214
|
+
byte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from
|
|
215
|
+
`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the
|
|
216
|
+
result under the KFD-provided `kfd-1` passport section.
|
|
212
217
|
When present, the passport includes the aggregate build summary, platform
|
|
213
218
|
artifact manifests, npm publish evidence, dist-tag promotion evidence, the
|
|
214
219
|
release-state ref, trusted publishing metadata, and the Buildchain transaction
|
package/bin/buildchain.mjs
CHANGED
|
@@ -76,6 +76,7 @@ function usage() {
|
|
|
76
76
|
[--build-summary-json <json-or-path>]
|
|
77
77
|
[--platform-manifest-json <json-or-path>]...
|
|
78
78
|
[--dist-tag-evidence-json <json-or-path>]
|
|
79
|
+
[--kfd-1-witness-json <json-or-path>]...
|
|
79
80
|
[--release-extra-json <json-or-path>]
|
|
80
81
|
[--publish-json <json-or-path>] [--output-dir <dir>] [--json]
|
|
81
82
|
buildchain verify release-passport <file-or-url> [--json]
|
|
@@ -734,6 +735,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
734
735
|
buildSummaryJson: readFlag(collectArgs, "build-summary-json", ""),
|
|
735
736
|
platformManifestJsons: readRepeatedFlag(collectArgs, "platform-manifest-json"),
|
|
736
737
|
distTagEvidenceJson: readFlag(collectArgs, "dist-tag-evidence-json", ""),
|
|
738
|
+
kfd1WitnessJsons: readRepeatedFlag(collectArgs, "kfd-1-witness-json"),
|
|
737
739
|
releaseJsonExtra: readFlag(collectArgs, "release-extra-json", ""),
|
|
738
740
|
publishJson: readFlag(collectArgs, "publish-json", ""),
|
|
739
741
|
workflow,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./diagnostics": "./packages/core/diagnostics.js",
|
|
15
15
|
"./issue-reporting": "./packages/core/issue-reporting.js",
|
|
16
16
|
"./logging": "./packages/core/logging.js",
|
|
17
|
+
"./kfd-gate": "./packages/core/kfd-gate.js",
|
|
17
18
|
"./release-candidate": "./packages/core/release-candidate.js",
|
|
18
19
|
"./release-passport": "./packages/core/release-passport.js",
|
|
19
20
|
"./release-propagation": "./packages/core/release-propagation.js",
|
package/docs/MAP.md
CHANGED
|
@@ -33,6 +33,7 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
33
33
|
| How do I declare version files and custom lifecycle commands? | [`lifecycle-protocol.md`](lifecycle-protocol.md) | use | stable |
|
|
34
34
|
| How does publish evidence, recovery, and finalization work? | [`publish-transaction.md`](publish-transaction.md) | verify | stable |
|
|
35
35
|
| How do I publish or verify release passport artifacts? | [`release-passport.md`](release-passport.md) | use | stable |
|
|
36
|
+
| How do I gate release artifacts with KFD-1 contract-world witnesses? | [`release-passport.md`](release-passport.md#kfd-1-contract-world-release-gate) | verify/use | stable |
|
|
36
37
|
| How do I propagate finalized upstream releases to downstream package/site PRs? | [`release-propagation.md`](release-propagation.md) | use | preview |
|
|
37
38
|
| How do I prove a PR-stage reusable build is the artifact source promoted later? | [`release-candidate.md`](release-candidate.md) + [`reusable-build-surface.md`](reusable-build-surface.md) | verify | stable |
|
|
38
39
|
| Why are binary release assets archived by platform, and where is the single bundle? | [`binary-distribution.md`](binary-distribution.md) | verify | stable |
|
|
@@ -90,6 +91,8 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
90
91
|
[`cli.md`](cli.md) and [`publish-transaction.md`](publish-transaction.md).
|
|
91
92
|
- **release chains / upstream package as source of truth / site synchronization** ->
|
|
92
93
|
[`release-propagation.md`](release-propagation.md).
|
|
94
|
+
- **KFD-1 contract worlds / byte-for-byte release gates** ->
|
|
95
|
+
[`release-passport.md`](release-passport.md#kfd-1-contract-world-release-gate).
|
|
93
96
|
- **GitHub Release passport / binary assets / artifact evidence / agent release checks** ->
|
|
94
97
|
[`release-passport.md`](release-passport.md),
|
|
95
98
|
[`binary-distribution.md`](binary-distribution.md), and [`cli.md`](cli.md).
|
package/docs/cli.md
CHANGED
|
@@ -260,6 +260,7 @@ buildchain collect github-release \
|
|
|
260
260
|
--platform-manifest-json .buildchain/artifacts/darwin-arm64/manifest.json \
|
|
261
261
|
--platform-manifest-json .buildchain/artifacts/win32-x64/manifest.json \
|
|
262
262
|
--dist-tag-evidence-json .buildchain/release-evidence/v2.3.2/dist-tag-evidence.json \
|
|
263
|
+
--kfd-1-witness-json .buildchain/kfd-1/contract-world.witness.json \
|
|
263
264
|
--release-extra-json '{"channel":"release","targetRef":"release/v2/v2.3"}' \
|
|
264
265
|
--output-dir .buildchain/release-passport
|
|
265
266
|
```
|
|
@@ -276,6 +277,15 @@ platform packages with version, dist-tag, and digest evidence. Verification
|
|
|
276
277
|
fails closed if supplied sections are internally incomplete or point to
|
|
277
278
|
artifacts without matching evidence.
|
|
278
279
|
|
|
280
|
+
`--kfd-1-witness-json` attaches a KFD-1 contract-world release gate. The witness
|
|
281
|
+
is structured JSON: consumers declare the contract world, canonical JSON policy,
|
|
282
|
+
artifact paths, and expected SHA-256 digests. Buildchain imports KFD-owned
|
|
283
|
+
metadata from `@kungfu-tech/kfd`, freezes the witness before build publication,
|
|
284
|
+
then verifies the resulting artifact bytes itself and writes the evidence under
|
|
285
|
+
the KFD-provided top-level key currently named `kfd-1`. Consumers should not
|
|
286
|
+
duplicate this by running repository-specific scripts or invoking the Kungfu
|
|
287
|
+
SDK from their release workflow.
|
|
288
|
+
|
|
279
289
|
`--impact-json` supplies the surface-aware impact ledger. Production release
|
|
280
290
|
passports (`release/*`) and major publish-gate passports require
|
|
281
291
|
`surfaceImpacts[]`; alpha, local, and legacy passport contexts keep it
|
package/docs/release-passport.md
CHANGED
|
@@ -97,6 +97,7 @@ buildchain collect github-release \
|
|
|
97
97
|
--platform-manifest-json .buildchain/artifacts/darwin-arm64/manifest.json \
|
|
98
98
|
--platform-manifest-json .buildchain/artifacts/win32-x64/manifest.json \
|
|
99
99
|
--dist-tag-evidence-json .buildchain/release-evidence/v2.2.0/dist-tag-evidence.json \
|
|
100
|
+
--kfd-1-witness-json .buildchain/kfd-1/contract-world.witness.json \
|
|
100
101
|
--output-dir .buildchain/release-passport
|
|
101
102
|
```
|
|
102
103
|
|
|
@@ -106,6 +107,55 @@ dist-tag, registry, role, platform, and digest, so agents do not need to stitch
|
|
|
106
107
|
npm facts back together from the lower-level evidence files.
|
|
107
108
|
`buildSummary`, `platformArtifactManifests`, and `distTagPromotion` preserve the
|
|
108
109
|
build and npm dist-tag evidence chain in the same passport.
|
|
110
|
+
|
|
111
|
+
### KFD-1 contract-world release gate
|
|
112
|
+
|
|
113
|
+
Buildchain can gate release artifacts with KFD-1 contract-world witnesses. This
|
|
114
|
+
is a structured evidence protocol, not a request for consumers to shell out to
|
|
115
|
+
the Kungfu SDK. The authority chain is:
|
|
116
|
+
|
|
117
|
+
1. KFD owns the standard metadata and schema ids in `@kungfu-tech/kfd`.
|
|
118
|
+
2. Buildchain imports that metadata, owns the JSON formatting policy, freezes
|
|
119
|
+
the pre-build witness, and independently verifies post-build artifact bytes.
|
|
120
|
+
3. Consumers only pass declarative witness JSON plus the artifact payloads their
|
|
121
|
+
build already produced.
|
|
122
|
+
|
|
123
|
+
The witness JSON names the contract world, the canonical serialization policy,
|
|
124
|
+
and the release surfaces that must be byte-for-byte verified:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"id": "kungfu-config",
|
|
129
|
+
"standard": "kfd-1",
|
|
130
|
+
"source": "kfd",
|
|
131
|
+
"contractWorld": {
|
|
132
|
+
"id": "kungfu-config",
|
|
133
|
+
"kind": "schema",
|
|
134
|
+
"name": "Kungfu config schema"
|
|
135
|
+
},
|
|
136
|
+
"canonicalPolicy": {
|
|
137
|
+
"format": "json",
|
|
138
|
+
"encoding": "utf-8",
|
|
139
|
+
"indent": 2,
|
|
140
|
+
"trailingNewline": true
|
|
141
|
+
},
|
|
142
|
+
"surfaces": [
|
|
143
|
+
{
|
|
144
|
+
"id": "kungfu-config-schema",
|
|
145
|
+
"artifactPath": "Contents/Resources/core/config.schema.json",
|
|
146
|
+
"expectedSha256": "..."
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`collect github-release` writes the result under the KFD-provided top-level key
|
|
153
|
+
currently named `kfd-1`. Each contract world records the frozen witness digest,
|
|
154
|
+
the KFD package version, KFD schema ids, the Buildchain formatting policy, and
|
|
155
|
+
the actual artifact digest observed after the build. Verification fails closed
|
|
156
|
+
when the witness is missing required facts, an artifact cannot be found, or a
|
|
157
|
+
post-build digest does not match the frozen witness.
|
|
158
|
+
|
|
109
159
|
`impact.json` can be supplied with `--impact-json`. Production release
|
|
110
160
|
passports (`release/*`) and major publish-gate passports require
|
|
111
161
|
`surfaceImpacts[]`; alpha, local, and legacy passport contexts keep the field
|
package/docs/versioning.md
CHANGED
|
@@ -61,6 +61,7 @@ repository changes are patch".
|
|
|
61
61
|
|
|
62
62
|
| Date | Action | Line | Faces | Class | Rationale | PR |
|
|
63
63
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
64
|
+
| 2026-07-06 | open-minor | `v2.8` | kfd-1-contract-world-release-gate, required-check-protection | additive | KFD-1 contract-world release gates add a public witness API, Buildchain-owned passport evidence, and fail-closed byte verification, while protected channel required checks are repaired to bind GitHub Actions check runs instead of legacy commit status contexts. | |
|
|
64
65
|
| 2026-07-04 | open-minor | `v2.5` | scheduled-integration-governance | additive | Scheduled integration governance adds scheduled feature-branch discovery, conflict-free integration, reporting, and agent-visible governance automation for dev-line maintenance. | |
|
|
65
66
|
| 2026-07-03 | open-minor | `v2.4` | infra-contract-lifecycle | additive | Infra contract lifecycle adds the provider-neutral `infra-contract` CLI command family, project type, adapter capability contract, lifecycle evidence bundle, propagation evidence, CI evidence mode, and consumer-facing contract artifacts. | |
|
|
66
67
|
| 2026-07-02 | open-minor | `v2.3` | web-surface-host-mapping | additive | Web surface host mapping adds first-class multi-host surface bindings, reusable workflow URL outputs, per-surface deployment overrides, and an agent-readable fixture contract. | |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0-alpha.0",
|
|
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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"./diagnostics": "./packages/core/diagnostics.js",
|
|
18
18
|
"./issue-reporting": "./packages/core/issue-reporting.js",
|
|
19
19
|
"./logging": "./packages/core/logging.js",
|
|
20
|
+
"./kfd-gate": "./packages/core/kfd-gate.js",
|
|
20
21
|
"./release-candidate": "./packages/core/release-candidate.js",
|
|
21
22
|
"./release-passport": "./packages/core/release-passport.js",
|
|
22
23
|
"./release-propagation": "./packages/core/release-propagation.js",
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
"vitest": "^4.1.9"
|
|
92
93
|
},
|
|
93
94
|
"dependencies": {
|
|
95
|
+
"@kungfu-tech/kfd": "1.0.0-alpha.2",
|
|
94
96
|
"smol-toml": "1.7.0"
|
|
95
97
|
}
|
|
96
98
|
}
|
package/packages/core/index.js
CHANGED
|
@@ -104,6 +104,17 @@ export {
|
|
|
104
104
|
verifyArtifactPassport,
|
|
105
105
|
} from "./artifact-passport.js";
|
|
106
106
|
|
|
107
|
+
export {
|
|
108
|
+
BUILDCHAIN_JSON_FORMATTING_POLICY,
|
|
109
|
+
KFD1_RELEASE_GATE_CONTRACT,
|
|
110
|
+
KFD1_WITNESS_SET_CONTRACT,
|
|
111
|
+
createKfd1ReleaseGateEvidence,
|
|
112
|
+
normalizeKfd1ContractWorldWitness,
|
|
113
|
+
resolveKfd1Metadata,
|
|
114
|
+
sha256Json as sha256KfdJson,
|
|
115
|
+
validateKfd1ReleaseGateEvidence,
|
|
116
|
+
} from "./kfd-gate.js";
|
|
117
|
+
|
|
107
118
|
export {
|
|
108
119
|
AGENT_INDEX_CONTRACT,
|
|
109
120
|
ARTIFACT_EVIDENCE_CONTRACT,
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
export const KFD1_RELEASE_GATE_CONTRACT = "kungfu-buildchain-kfd-1-release-gate";
|
|
9
|
+
export const KFD1_WITNESS_SET_CONTRACT = "kungfu-buildchain-kfd-1-witness-set";
|
|
10
|
+
export const BUILDCHAIN_JSON_FORMATTING_POLICY = Object.freeze({
|
|
11
|
+
name: "buildchain-release-evidence-json-v1",
|
|
12
|
+
indentation: 2,
|
|
13
|
+
trailingNewline: true,
|
|
14
|
+
canonicalDigest: "stable-json-sha256-v1",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function optionalString(value) {
|
|
18
|
+
return value === undefined || value === null ? "" : String(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function nonEmptyString(value, label) {
|
|
22
|
+
const normalized = optionalString(value).trim();
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stableJson(value) {
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
32
|
+
}
|
|
33
|
+
if (value && typeof value === "object") {
|
|
34
|
+
return `{${Object.keys(value)
|
|
35
|
+
.sort()
|
|
36
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
37
|
+
.join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sha256Bytes(value) {
|
|
43
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sha256Json(value) {
|
|
47
|
+
return sha256Bytes(stableJson(value));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function sha256File(filePath) {
|
|
51
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readJsonPackageExport(exportPath) {
|
|
55
|
+
return JSON.parse(fs.readFileSync(require.resolve(exportPath), "utf8"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadKfdPackageMetadata() {
|
|
59
|
+
const packageJson = readJsonPackageExport("@kungfu-tech/kfd/package.json");
|
|
60
|
+
const standards = readJsonPackageExport("@kungfu-tech/kfd/standards.json");
|
|
61
|
+
return { packageJson, standards };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolveKfd1Metadata() {
|
|
65
|
+
const { packageJson, standards } = loadKfdPackageMetadata();
|
|
66
|
+
const entries = Object.entries(standards.standards || {});
|
|
67
|
+
const entry = entries.find(([key, value]) => {
|
|
68
|
+
const names = [key, value?.key, value?.id, value?.label]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.map((name) => String(name).toLowerCase());
|
|
71
|
+
return (
|
|
72
|
+
names.includes("kfd-1") &&
|
|
73
|
+
value?.schemaIds?.contractWorld &&
|
|
74
|
+
value?.schemaIds?.witness &&
|
|
75
|
+
value?.schemaPaths?.contractWorld &&
|
|
76
|
+
value?.schemaPaths?.witness
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
if (!entry) {
|
|
80
|
+
throw new Error("KFD metadata package does not expose the KFD-1 contract-world witness standard");
|
|
81
|
+
}
|
|
82
|
+
const [key, standard] = entry;
|
|
83
|
+
return {
|
|
84
|
+
key: standard.key || key,
|
|
85
|
+
id: standard.id || standard.label || key,
|
|
86
|
+
label: standard.label || standard.id || key,
|
|
87
|
+
title: standard.title || "",
|
|
88
|
+
status: standard.status || "",
|
|
89
|
+
revision: standard.revision || 0,
|
|
90
|
+
package: {
|
|
91
|
+
name: packageJson.name,
|
|
92
|
+
version: packageJson.version,
|
|
93
|
+
repository: packageJson.repository?.url || "",
|
|
94
|
+
},
|
|
95
|
+
schemaIds: { ...standard.schemaIds },
|
|
96
|
+
schemaPaths: { ...standard.schemaPaths },
|
|
97
|
+
concepts: { ...(standard.concepts || {}) },
|
|
98
|
+
metadataSchema: standards.metadataSchema || {},
|
|
99
|
+
source: standards.source || {},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeHash(value, label, { required = false } = {}) {
|
|
104
|
+
const text = optionalString(value).replace(/^sha256:/, "").trim();
|
|
105
|
+
if (!text && !required) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
if (!/^[0-9a-f]{64}$/i.test(text)) {
|
|
109
|
+
throw new Error(`${label} must be a sha256 hex digest`);
|
|
110
|
+
}
|
|
111
|
+
return text.toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizePath(value, label) {
|
|
115
|
+
const text = nonEmptyString(value, label).replace(/\\/g, "/");
|
|
116
|
+
if (path.isAbsolute(text)) {
|
|
117
|
+
throw new Error(`${label} must be repository-relative`);
|
|
118
|
+
}
|
|
119
|
+
if (text.split("/").some((part) => part === "..")) {
|
|
120
|
+
throw new Error(`${label} must not contain ..`);
|
|
121
|
+
}
|
|
122
|
+
return text;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeEvidenceFile(value, label) {
|
|
126
|
+
if (!value) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (typeof value === "string") {
|
|
130
|
+
return { path: normalizePath(value, `${label}.path`) };
|
|
131
|
+
}
|
|
132
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
133
|
+
throw new Error(`${label} must be an object`);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
...value,
|
|
137
|
+
path: value.path ? normalizePath(value.path, `${label}.path`) : "",
|
|
138
|
+
sha256: value.sha256 ? normalizeHash(value.sha256, `${label}.sha256`) : "",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeSurface(surface, index) {
|
|
143
|
+
if (!surface || typeof surface !== "object" || Array.isArray(surface)) {
|
|
144
|
+
throw new Error(`surfaces[${index}] must be an object`);
|
|
145
|
+
}
|
|
146
|
+
const artifactPath = normalizePath(surface.artifactPath || surface.artifact_path, `surfaces[${index}].artifactPath`);
|
|
147
|
+
return {
|
|
148
|
+
name: nonEmptyString(surface.name || surface.id, `surfaces[${index}].name`),
|
|
149
|
+
sourcePath: surface.sourcePath || surface.source_path
|
|
150
|
+
? normalizePath(surface.sourcePath || surface.source_path, `surfaces[${index}].sourcePath`)
|
|
151
|
+
: "",
|
|
152
|
+
sourceSha256: normalizeHash(surface.sourceSha256 || surface.source_sha256, `surfaces[${index}].sourceSha256`),
|
|
153
|
+
artifactPath,
|
|
154
|
+
expectedSha256: normalizeHash(surface.expectedSha256 || surface.expected_sha256, `surfaces[${index}].expectedSha256`, { required: true }),
|
|
155
|
+
byteForByte: surface.byteForByte === undefined && surface.byte_for_byte === undefined
|
|
156
|
+
? true
|
|
157
|
+
: Boolean(surface.byteForByte ?? surface.byte_for_byte),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function normalizeKfd1ContractWorldWitness(witness, { metadata = resolveKfd1Metadata() } = {}) {
|
|
162
|
+
if (!witness || typeof witness !== "object" || Array.isArray(witness)) {
|
|
163
|
+
throw new Error("KFD-1 witness must be a JSON object");
|
|
164
|
+
}
|
|
165
|
+
const surfaces = Array.isArray(witness.surfaces)
|
|
166
|
+
? witness.surfaces.map((surface, index) => normalizeSurface(surface, index))
|
|
167
|
+
: [];
|
|
168
|
+
if (surfaces.length === 0) {
|
|
169
|
+
throw new Error("KFD-1 witness surfaces[] must include at least one artifact surface");
|
|
170
|
+
}
|
|
171
|
+
const standard = optionalString(witness.standard || metadata.key);
|
|
172
|
+
if (standard && standard !== metadata.key && standard !== metadata.id && standard !== metadata.label) {
|
|
173
|
+
throw new Error(`KFD-1 witness standard must match ${metadata.key}`);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
schemaVersion: 1,
|
|
177
|
+
contract: KFD1_WITNESS_SET_CONTRACT,
|
|
178
|
+
id: nonEmptyString(witness.id, "KFD-1 witness id"),
|
|
179
|
+
standard: metadata.key,
|
|
180
|
+
standardLabel: metadata.label,
|
|
181
|
+
source: witness.source && typeof witness.source === "object" && !Array.isArray(witness.source)
|
|
182
|
+
? { ...witness.source }
|
|
183
|
+
: {},
|
|
184
|
+
contractWorld: witness.contractWorld && typeof witness.contractWorld === "object" && !Array.isArray(witness.contractWorld)
|
|
185
|
+
? {
|
|
186
|
+
...witness.contractWorld,
|
|
187
|
+
schemaId: witness.contractWorld.schemaId || metadata.schemaIds.contractWorld,
|
|
188
|
+
digest: optionalString(witness.contractWorld.digest || witness.contractWorld.sha256),
|
|
189
|
+
}
|
|
190
|
+
: {
|
|
191
|
+
schemaId: metadata.schemaIds.contractWorld,
|
|
192
|
+
digest: "",
|
|
193
|
+
},
|
|
194
|
+
canonicalPolicy: normalizeEvidenceFile(witness.canonicalPolicy || witness.canonical_policy, "canonicalPolicy"),
|
|
195
|
+
registry: normalizeEvidenceFile(witness.registry, "registry"),
|
|
196
|
+
surfaces,
|
|
197
|
+
metadata: {
|
|
198
|
+
kfdPackage: metadata.package,
|
|
199
|
+
schemaIds: metadata.schemaIds,
|
|
200
|
+
schemaPaths: metadata.schemaPaths,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveArtifactFile({ cwd, artifactRoot, artifacts = [], artifactPath }) {
|
|
206
|
+
const candidates = [
|
|
207
|
+
path.resolve(cwd, artifactPath),
|
|
208
|
+
artifactRoot ? path.resolve(artifactRoot, artifactPath) : "",
|
|
209
|
+
].filter(Boolean);
|
|
210
|
+
for (const artifact of artifacts) {
|
|
211
|
+
const sourcePath = artifact.sourcePath || artifact.path || "";
|
|
212
|
+
if (!sourcePath) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const normalizedSource = sourcePath.replace(/\\/g, "/");
|
|
216
|
+
if (normalizedSource === artifactPath || normalizedSource.endsWith(`/${artifactPath}`)) {
|
|
217
|
+
candidates.push(path.resolve(sourcePath));
|
|
218
|
+
}
|
|
219
|
+
const name = artifact.name || artifact.filename || "";
|
|
220
|
+
if (name && name === artifactPath) {
|
|
221
|
+
candidates.push(path.resolve(sourcePath));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return candidates.find((candidate) => candidate && fs.existsSync(candidate) && fs.statSync(candidate).isFile()) || "";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function createKfd1ReleaseGateEvidence({
|
|
228
|
+
cwd = process.cwd(),
|
|
229
|
+
artifactRoot = "",
|
|
230
|
+
artifacts = [],
|
|
231
|
+
witnesses = [],
|
|
232
|
+
verifiedAt = new Date().toISOString(),
|
|
233
|
+
metadata = resolveKfd1Metadata(),
|
|
234
|
+
} = {}) {
|
|
235
|
+
const normalizedWitnesses = witnesses.map((witness) => normalizeKfd1ContractWorldWitness(witness, { metadata }));
|
|
236
|
+
if (normalizedWitnesses.length === 0) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const worlds = normalizedWitnesses.map((witness) => {
|
|
240
|
+
const preBuildWitnessSha256 = sha256Json(witness);
|
|
241
|
+
const surfaceResults = witness.surfaces.map((surface) => {
|
|
242
|
+
const filePath = resolveArtifactFile({
|
|
243
|
+
cwd,
|
|
244
|
+
artifactRoot,
|
|
245
|
+
artifacts,
|
|
246
|
+
artifactPath: surface.artifactPath,
|
|
247
|
+
});
|
|
248
|
+
if (!filePath) {
|
|
249
|
+
return {
|
|
250
|
+
name: surface.name,
|
|
251
|
+
artifactPath: surface.artifactPath,
|
|
252
|
+
expectedSha256: surface.expectedSha256,
|
|
253
|
+
actualSha256: "",
|
|
254
|
+
byteForByte: surface.byteForByte,
|
|
255
|
+
status: "failed",
|
|
256
|
+
reason: "artifact-missing",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const actualSha256 = sha256File(filePath);
|
|
260
|
+
const passed = actualSha256 === surface.expectedSha256;
|
|
261
|
+
return {
|
|
262
|
+
name: surface.name,
|
|
263
|
+
artifactPath: surface.artifactPath,
|
|
264
|
+
expectedSha256: surface.expectedSha256,
|
|
265
|
+
actualSha256,
|
|
266
|
+
byteForByte: surface.byteForByte,
|
|
267
|
+
status: passed ? "passed" : "failed",
|
|
268
|
+
reason: passed ? "" : "digest-mismatch",
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
const status = surfaceResults.every((surface) => surface.status === "passed") ? "passed" : "failed";
|
|
272
|
+
return {
|
|
273
|
+
id: witness.id,
|
|
274
|
+
standard: metadata.label,
|
|
275
|
+
standardKey: metadata.key,
|
|
276
|
+
preBuildWitnessSha256,
|
|
277
|
+
witness,
|
|
278
|
+
artifactVerification: {
|
|
279
|
+
status,
|
|
280
|
+
verifiedAt,
|
|
281
|
+
surfaces: surfaceResults,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
const status = worlds.every((world) => world.artifactVerification.status === "passed") ? "passed" : "failed";
|
|
286
|
+
return {
|
|
287
|
+
key: metadata.key,
|
|
288
|
+
passportSection: {
|
|
289
|
+
schemaVersion: 1,
|
|
290
|
+
contract: KFD1_RELEASE_GATE_CONTRACT,
|
|
291
|
+
status,
|
|
292
|
+
metadata: {
|
|
293
|
+
standard: {
|
|
294
|
+
key: metadata.key,
|
|
295
|
+
id: metadata.id,
|
|
296
|
+
label: metadata.label,
|
|
297
|
+
title: metadata.title,
|
|
298
|
+
revision: metadata.revision,
|
|
299
|
+
status: metadata.status,
|
|
300
|
+
},
|
|
301
|
+
schemas: {
|
|
302
|
+
ids: metadata.schemaIds,
|
|
303
|
+
paths: metadata.schemaPaths,
|
|
304
|
+
metadata: metadata.metadataSchema,
|
|
305
|
+
},
|
|
306
|
+
package: metadata.package,
|
|
307
|
+
},
|
|
308
|
+
formatting: BUILDCHAIN_JSON_FORMATTING_POLICY,
|
|
309
|
+
contractWorlds: worlds,
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function validateKfd1ReleaseGateEvidence(section, { metadata = resolveKfd1Metadata() } = {}) {
|
|
315
|
+
const issues = [];
|
|
316
|
+
if (!section) {
|
|
317
|
+
return issues;
|
|
318
|
+
}
|
|
319
|
+
if (typeof section !== "object" || Array.isArray(section)) {
|
|
320
|
+
issues.push({
|
|
321
|
+
level: "error",
|
|
322
|
+
code: `${metadata.key}.object`,
|
|
323
|
+
message: `${metadata.key} release gate evidence must be an object`,
|
|
324
|
+
details: {},
|
|
325
|
+
});
|
|
326
|
+
return issues;
|
|
327
|
+
}
|
|
328
|
+
if (section.contract !== KFD1_RELEASE_GATE_CONTRACT) {
|
|
329
|
+
issues.push({
|
|
330
|
+
level: "error",
|
|
331
|
+
code: `${metadata.key}.contract`,
|
|
332
|
+
message: `${metadata.key}.contract must be ${KFD1_RELEASE_GATE_CONTRACT}`,
|
|
333
|
+
details: {},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (section.metadata?.standard?.key !== metadata.key) {
|
|
337
|
+
issues.push({
|
|
338
|
+
level: "error",
|
|
339
|
+
code: `${metadata.key}.metadata.standard.key`,
|
|
340
|
+
message: `${metadata.key} metadata key must come from the KFD metadata package`,
|
|
341
|
+
details: { expected: metadata.key, actual: section.metadata?.standard?.key || "" },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const worlds = Array.isArray(section.contractWorlds) ? section.contractWorlds : [];
|
|
345
|
+
if (worlds.length === 0) {
|
|
346
|
+
issues.push({
|
|
347
|
+
level: "error",
|
|
348
|
+
code: `${metadata.key}.contractWorlds.empty`,
|
|
349
|
+
message: `${metadata.key}.contractWorlds must include at least one witness`,
|
|
350
|
+
details: {},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
for (const [worldIndex, world] of worlds.entries()) {
|
|
354
|
+
if (!world.preBuildWitnessSha256) {
|
|
355
|
+
issues.push({
|
|
356
|
+
level: "error",
|
|
357
|
+
code: `${metadata.key}.contractWorlds[${worldIndex}].preBuildWitnessSha256`,
|
|
358
|
+
message: "KFD-1 contract world must record the frozen pre-build witness digest",
|
|
359
|
+
details: {},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (world.standardKey !== metadata.key) {
|
|
363
|
+
issues.push({
|
|
364
|
+
level: "error",
|
|
365
|
+
code: `${metadata.key}.contractWorlds[${worldIndex}].standardKey`,
|
|
366
|
+
message: "KFD-1 contract world standardKey must match KFD metadata",
|
|
367
|
+
details: { expected: metadata.key, actual: world.standardKey || "" },
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (world.artifactVerification?.status !== "passed") {
|
|
371
|
+
issues.push({
|
|
372
|
+
level: "error",
|
|
373
|
+
code: `${metadata.key}.contractWorlds[${worldIndex}].artifactVerification`,
|
|
374
|
+
message: "KFD-1 artifact verification must pass before release passport finalization",
|
|
375
|
+
details: { id: world.id || "", status: world.artifactVerification?.status || "" },
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const surfaces = Array.isArray(world.artifactVerification?.surfaces) ? world.artifactVerification.surfaces : [];
|
|
379
|
+
if (surfaces.length === 0) {
|
|
380
|
+
issues.push({
|
|
381
|
+
level: "error",
|
|
382
|
+
code: `${metadata.key}.contractWorlds[${worldIndex}].surfaces.empty`,
|
|
383
|
+
message: "KFD-1 artifact verification must list checked surfaces",
|
|
384
|
+
details: { id: world.id || "" },
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
for (const [surfaceIndex, surface] of surfaces.entries()) {
|
|
388
|
+
if (surface.status !== "passed" || !surface.expectedSha256 || !surface.actualSha256 || surface.expectedSha256 !== surface.actualSha256) {
|
|
389
|
+
issues.push({
|
|
390
|
+
level: "error",
|
|
391
|
+
code: `${metadata.key}.contractWorlds[${worldIndex}].surfaces[${surfaceIndex}]`,
|
|
392
|
+
message: "KFD-1 verified surface digest mismatch",
|
|
393
|
+
details: {
|
|
394
|
+
id: world.id || "",
|
|
395
|
+
name: surface.name || "",
|
|
396
|
+
artifactPath: surface.artifactPath || "",
|
|
397
|
+
status: surface.status || "",
|
|
398
|
+
reason: surface.reason || "",
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (section.status !== "passed") {
|
|
405
|
+
issues.push({
|
|
406
|
+
level: "error",
|
|
407
|
+
code: `${metadata.key}.status`,
|
|
408
|
+
message: "KFD-1 release gate status must be passed",
|
|
409
|
+
details: { status: section.status || "" },
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return issues;
|
|
413
|
+
}
|
|
@@ -4,6 +4,11 @@ import http from "node:http";
|
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
createKfd1ReleaseGateEvidence,
|
|
9
|
+
resolveKfd1Metadata,
|
|
10
|
+
validateKfd1ReleaseGateEvidence,
|
|
11
|
+
} from "./kfd-gate.js";
|
|
7
12
|
|
|
8
13
|
export const RELEASE_PASSPORT_CONTRACT = "kungfu-buildchain-release-passport";
|
|
9
14
|
export const ARTIFACT_EVIDENCE_CONTRACT = "kungfu-buildchain-artifact-evidence";
|
|
@@ -621,6 +626,7 @@ export function createReleasePassport({
|
|
|
621
626
|
publish = {},
|
|
622
627
|
impact = undefined,
|
|
623
628
|
workflow = {},
|
|
629
|
+
kfd1 = undefined,
|
|
624
630
|
} = {}) {
|
|
625
631
|
const normalizedTag = nonEmptyString(tag, "tag");
|
|
626
632
|
const artifactEvidence = createArtifactEvidence({ assets, repository, tag: normalizedTag, sourceSha, workflow });
|
|
@@ -636,6 +642,8 @@ export function createReleasePassport({
|
|
|
636
642
|
? normalizeEvidenceDocument(distTagPromotionEvidence, "distTagPromotionEvidence")
|
|
637
643
|
: undefined;
|
|
638
644
|
const normalizedImpact = normalizeImpactLedger(impact, { tag: normalizedTag, line });
|
|
645
|
+
const kfd1Metadata = resolveKfd1Metadata();
|
|
646
|
+
const normalizedKfd1 = kfd1?.passportSection ? kfd1 : undefined;
|
|
639
647
|
const publishArtifacts = normalizedPublishEvidence?.artifacts || [];
|
|
640
648
|
const normalizedPublishSummary = normalizePublishSummary({
|
|
641
649
|
packageSet: normalizedPackageSet,
|
|
@@ -736,6 +744,7 @@ export function createReleasePassport({
|
|
|
736
744
|
...(normalizedBuildSummary ? { buildSummary: normalizedBuildSummary } : {}),
|
|
737
745
|
...(normalizedPlatformArtifactManifests.length > 0 ? { platformArtifactManifests: normalizedPlatformArtifactManifests } : {}),
|
|
738
746
|
...(normalizedDistTagPromotionEvidence ? { distTagPromotion: normalizedDistTagPromotionEvidence } : {}),
|
|
747
|
+
...(normalizedKfd1 ? { [normalizedKfd1.key || kfd1Metadata.key]: normalizedKfd1.passportSection } : {}),
|
|
739
748
|
versionImpact: normalizedImpact.versionImpact,
|
|
740
749
|
surfaceImpacts: normalizedImpact.surfaceImpacts,
|
|
741
750
|
artifacts: [
|
|
@@ -771,6 +780,7 @@ export function createReleasePassport({
|
|
|
771
780
|
artifactName: manifest.artifactName,
|
|
772
781
|
})),
|
|
773
782
|
distTagPromotionEvidence: normalizedDistTagPromotionEvidence?.path || "",
|
|
783
|
+
kfd1: normalizedKfd1 ? `${normalizedKfd1.key || kfd1Metadata.key}` : "",
|
|
774
784
|
impact: impactPath,
|
|
775
785
|
checkReport: checkReportPath,
|
|
776
786
|
agentIndex: agentIndexPath,
|
|
@@ -804,6 +814,7 @@ export function collectGitHubReleasePassport({
|
|
|
804
814
|
buildSummaryJson = "",
|
|
805
815
|
platformManifestJsons = [],
|
|
806
816
|
distTagEvidenceJson = "",
|
|
817
|
+
kfd1WitnessJsons = [],
|
|
807
818
|
releaseJsonExtra = "",
|
|
808
819
|
publishJson = "",
|
|
809
820
|
workflow = {},
|
|
@@ -822,11 +833,15 @@ export function collectGitHubReleasePassport({
|
|
|
822
833
|
.filter(Boolean)
|
|
823
834
|
.map((manifestJson) => parseJsonInputWithMeta(manifestJson, undefined));
|
|
824
835
|
const distTagEvidenceMeta = parseJsonInputWithMeta(distTagEvidenceJson, undefined);
|
|
836
|
+
const kfd1WitnessMetas = (kfd1WitnessJsons || [])
|
|
837
|
+
.filter(Boolean)
|
|
838
|
+
.map((witnessJson) => parseJsonInputWithMeta(witnessJson, undefined))
|
|
839
|
+
.filter((meta) => meta.value);
|
|
825
840
|
const publish = parseJsonInput(publishJson, {});
|
|
826
841
|
const assets = [
|
|
827
842
|
...(Array.isArray(release.assets) ? release.assets : []),
|
|
828
843
|
...(Array.isArray(assetsFromJson) ? assetsFromJson : []),
|
|
829
|
-
...discoverAssetsFromDir(assetsDir),
|
|
844
|
+
...discoverAssetsFromDir(assetsDir ? path.resolve(cwd, assetsDir) : ""),
|
|
830
845
|
];
|
|
831
846
|
const resolvedTag = tag || release.tag_name || release.name || "";
|
|
832
847
|
const resolvedOutputDir = path.resolve(cwd, outputDir);
|
|
@@ -834,6 +849,12 @@ export function collectGitHubReleasePassport({
|
|
|
834
849
|
const artifactEvidence = createArtifactEvidence({ assets, repository, tag: resolvedTag, sourceSha, workflow });
|
|
835
850
|
const impact = normalizeImpactLedger(impactMeta.value, { tag: resolvedTag, line, decision: "unknown" });
|
|
836
851
|
const agentIndex = defaultAgentIndex({ tag: resolvedTag });
|
|
852
|
+
const kfd1 = createKfd1ReleaseGateEvidence({
|
|
853
|
+
cwd,
|
|
854
|
+
artifactRoot: assetsDir ? path.resolve(cwd, assetsDir) : "",
|
|
855
|
+
artifacts: assets,
|
|
856
|
+
witnesses: kfd1WitnessMetas.map((meta) => meta.value),
|
|
857
|
+
});
|
|
837
858
|
const passport = createReleasePassport({
|
|
838
859
|
cwd,
|
|
839
860
|
repository,
|
|
@@ -870,6 +891,7 @@ export function collectGitHubReleasePassport({
|
|
|
870
891
|
release: releaseExtra,
|
|
871
892
|
publish,
|
|
872
893
|
impact,
|
|
894
|
+
kfd1,
|
|
873
895
|
publishEvidencePath: publishEvidenceMeta.path ? path.relative(resolvedOutputDir, publishEvidenceMeta.path).split(path.sep).join("/") : "",
|
|
874
896
|
transactionStatePath: transactionMeta.path ? path.relative(resolvedOutputDir, transactionMeta.path).split(path.sep).join("/") : "",
|
|
875
897
|
workflow,
|
|
@@ -1170,6 +1192,8 @@ export function createReleaseCheckReport({
|
|
|
1170
1192
|
const declaredFinalImpact = normalizeImpactLevel(impact?.versionImpact?.final || impact?.classification);
|
|
1171
1193
|
const computedFinalImpact = highestImpactLevel(surfaceImpacts.map((entry) => entry.impact));
|
|
1172
1194
|
const requiredSurfaceImpacts = surfaceImpactRequirement({ passport, impact });
|
|
1195
|
+
const kfd1Metadata = resolveKfd1Metadata();
|
|
1196
|
+
issues.push(...validateKfd1ReleaseGateEvidence(passport?.[kfd1Metadata.key], { metadata: kfd1Metadata }));
|
|
1173
1197
|
if (requiredSurfaceImpacts.required && surfaceImpacts.length === 0) {
|
|
1174
1198
|
issues.push(issue("error", "impact.surfaceImpacts.required", "surfaceImpacts[] is required for this release passport type", requiredSurfaceImpacts));
|
|
1175
1199
|
}
|
|
@@ -93,6 +93,9 @@ if (rootPackage.exports?.["./issue-reporting"] !== "./packages/core/issue-report
|
|
|
93
93
|
if (rootPackage.exports?.["./logging"] !== "./packages/core/logging.js") {
|
|
94
94
|
throw new Error("root package must export @kungfu-tech/buildchain/logging");
|
|
95
95
|
}
|
|
96
|
+
if (rootPackage.exports?.["./kfd-gate"] !== "./packages/core/kfd-gate.js") {
|
|
97
|
+
throw new Error("root package must export @kungfu-tech/buildchain/kfd-gate");
|
|
98
|
+
}
|
|
96
99
|
if (rootPackage.exports?.["./release-passport"] !== "./packages/core/release-passport.js") {
|
|
97
100
|
throw new Error("root package must export @kungfu-tech/buildchain/release-passport");
|
|
98
101
|
}
|
|
@@ -138,10 +141,22 @@ if (!coreIndexSource.includes("collectBuildchainDiagnostics")) {
|
|
|
138
141
|
if (!coreIndexSource.includes("reportBuildchainIssue")) {
|
|
139
142
|
throw new Error("packages/core/index.js must export reportBuildchainIssue");
|
|
140
143
|
}
|
|
144
|
+
for (const requiredSnippet of [
|
|
145
|
+
"createKfd1ReleaseGateEvidence",
|
|
146
|
+
"resolveKfd1Metadata",
|
|
147
|
+
"validateKfd1ReleaseGateEvidence",
|
|
148
|
+
]) {
|
|
149
|
+
if (!coreIndexSource.includes(requiredSnippet)) {
|
|
150
|
+
throw new Error(`packages/core/index.js must export KFD-1 gate API: ${requiredSnippet}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
141
153
|
for (const requiredSnippet of [
|
|
142
154
|
"surfaceImpacts[]",
|
|
143
155
|
"versionImpact.final",
|
|
144
156
|
"kfd-registry-schema",
|
|
157
|
+
"`v2.8`",
|
|
158
|
+
"kfd-1-contract-world-release-gate",
|
|
159
|
+
"required-check-protection",
|
|
145
160
|
"`v2.2`",
|
|
146
161
|
"GitHub-hosted runners for production",
|
|
147
162
|
"Self-hosted runners remain compatibility fixtures",
|
|
@@ -150,6 +165,18 @@ for (const requiredSnippet of [
|
|
|
150
165
|
throw new Error(`versioning doc missing required snippet: ${requiredSnippet}`);
|
|
151
166
|
}
|
|
152
167
|
}
|
|
168
|
+
const releasePassportDoc = fs.readFileSync(path.join(root, "docs/release-passport.md"), "utf8");
|
|
169
|
+
for (const requiredSnippet of [
|
|
170
|
+
"--kfd-1-witness-json",
|
|
171
|
+
"@kungfu-tech/kfd",
|
|
172
|
+
"currently named `kfd-1`",
|
|
173
|
+
"Buildchain formatting policy",
|
|
174
|
+
"Verification fails closed",
|
|
175
|
+
]) {
|
|
176
|
+
if (!releasePassportDoc.includes(requiredSnippet)) {
|
|
177
|
+
throw new Error(`release passport doc missing KFD-1 gate snippet: ${requiredSnippet}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
153
180
|
for (const [docName, docSource] of Object.entries({ "docs/cli.md": cliDoc, "docs/install.md": installDoc })) {
|
|
154
181
|
for (const requiredSnippet of [
|
|
155
182
|
"minimumReleaseAgeExclude",
|
|
@@ -227,12 +254,24 @@ for (const requiredSnippet of [
|
|
|
227
254
|
"target-sha: ${{ github.event.workflow_run.head_sha || inputs.sha || github.sha }}",
|
|
228
255
|
"publish-required-artifacts-json: \"[]\"",
|
|
229
256
|
"release-passport-impact-json: >-",
|
|
257
|
+
"Buildchain v2.8 promotes KFD-1 contract-world release gates",
|
|
258
|
+
"kfd-1-contract-world-release-gate",
|
|
259
|
+
"required-check-protection",
|
|
230
260
|
"\"surfaceImpacts\":[",
|
|
231
261
|
]) {
|
|
232
262
|
if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
|
|
233
263
|
throw new Error(`buildchain ref promotion workflow missing npm transaction snippet: ${requiredSnippet}`);
|
|
234
264
|
}
|
|
235
265
|
}
|
|
266
|
+
const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/release-candidate-promote.yml"), "utf8");
|
|
267
|
+
for (const requiredSnippet of [
|
|
268
|
+
"release-passport-kfd-1-witness-jsons:",
|
|
269
|
+
"release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons }}",
|
|
270
|
+
]) {
|
|
271
|
+
if (!releaseCandidatePromoteWorkflow.includes(requiredSnippet)) {
|
|
272
|
+
throw new Error(`release candidate promote workflow missing KFD-1 gate pass-through: ${requiredSnippet}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
236
275
|
for (const forbiddenSnippet of [
|
|
237
276
|
"run: node scripts/release-candidate-resolver.mjs",
|
|
238
277
|
"uses: ./actions/promote-buildchain-ref",
|