@kungfu-tech/buildchain 2.10.8 → 2.10.9
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/bin/buildchain.mjs +77 -2
- package/dist/site/agent-index.json +1 -0
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +19 -7
- package/dist/site/capability-registry.json +10 -1
- package/dist/site/cli-registry.json +48 -0
- package/dist/site/kfd-claims.json +66 -7
- package/dist/site/kfd-upstream-aggregate.json +120 -0
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +5 -5
- package/dist/site/page-registry.json +12 -2
- package/dist/site/public-surface-audit.json +74 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +6 -5
- package/docs/kfd-support.md +141 -0
- package/package.json +2 -1
- package/packages/core/buildchain-kfd-claims.js +1 -0
- package/packages/core/kfd.js +566 -2
- package/packages/core/public-surface-audit.js +1 -0
- package/scripts/generate-site-bundle.mjs +14 -1
package/packages/core/kfd.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import crypto from "node:crypto";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
import {
|
|
@@ -41,13 +42,71 @@ import {
|
|
|
41
42
|
registerKfd3Surfaces,
|
|
42
43
|
writeKfd3SurfaceRegistry,
|
|
43
44
|
} from "./kfd3-surface-register.js";
|
|
45
|
+
import { loadBuildchainConfig } from "./buildchain-config.js";
|
|
44
46
|
|
|
45
47
|
const require = createRequire(import.meta.url);
|
|
48
|
+
const KFD_UPSTREAM_AGGREGATE_CONTRACT = "kungfu-buildchain-kfd-upstream-aggregate";
|
|
49
|
+
const KFD_AGGREGATE_CONTRACT = "kungfu-buildchain-kfd-aggregate";
|
|
50
|
+
const KFD_UPSTREAM_CHECK_CONTRACT = "kungfu-buildchain-kfd-upstream-check";
|
|
51
|
+
const KFD_UPSTREAM_ROLES = Object.freeze({
|
|
52
|
+
STANDARD_AND_SCHEMA_PROVIDER: "standard-and-schema-provider",
|
|
53
|
+
RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER: "release-passport-and-kfd-gate-provider",
|
|
54
|
+
KFD_AWARE_PRODUCT_COMPONENT: "kfd-aware-product-component",
|
|
55
|
+
SITE_CONSUMPTION_PROVIDER: "site-consumption-provider",
|
|
56
|
+
UNKNOWN_KFD_UPSTREAM: "unknown-kfd-upstream",
|
|
57
|
+
});
|
|
58
|
+
const KFD_UPSTREAM_ROLE_SET = new Set(Object.values(KFD_UPSTREAM_ROLES));
|
|
59
|
+
const KFD_UPSTREAM_KNOWN_PACKAGE_ROLES = Object.freeze({
|
|
60
|
+
"@kungfu-tech/kfd": KFD_UPSTREAM_ROLES.STANDARD_AND_SCHEMA_PROVIDER,
|
|
61
|
+
"@kungfu-tech/buildchain": KFD_UPSTREAM_ROLES.RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER,
|
|
62
|
+
});
|
|
63
|
+
const KFD_UPSTREAM_ROLE_DEFINITIONS = Object.freeze([
|
|
64
|
+
{
|
|
65
|
+
role: KFD_UPSTREAM_ROLES.STANDARD_AND_SCHEMA_PROVIDER,
|
|
66
|
+
description: "Provides KFD standards, schemas, taxonomy, or standard-owned witness and claim facts.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
role: KFD_UPSTREAM_ROLES.RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER,
|
|
70
|
+
description: "Provides release passport, KFD gate, release claim, or release governance machinery consumed by the product.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
role: KFD_UPSTREAM_ROLES.KFD_AWARE_PRODUCT_COMPONENT,
|
|
74
|
+
description: "A product component that exposes KFD witness, claim, collaboration-interface, or package evidence without being core KFD infrastructure.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
role: KFD_UPSTREAM_ROLES.SITE_CONSUMPTION_PROVIDER,
|
|
78
|
+
description: "Provides site-consumption facts such as site manifests, site bundles, or downstream page-content contracts.",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
role: KFD_UPSTREAM_ROLES.UNKNOWN_KFD_UPSTREAM,
|
|
82
|
+
description: "Fallback for a declared upstream that has not matched a Buildchain-known package or role-specific evidence.",
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
const KFD_UPSTREAM_DEFAULT_EVIDENCE = Object.freeze([
|
|
86
|
+
{ kind: "package", path: "buildchain.release.json", required: false },
|
|
87
|
+
{ kind: "package", path: "kfd.release.json", required: false },
|
|
88
|
+
{ kind: "package", path: "libnode.release.json", required: false },
|
|
89
|
+
{ kind: "package", path: "standards.json", required: false },
|
|
90
|
+
{ kind: "package", path: ".buildchain/kfd-1/contract-world.witness.json", required: false },
|
|
91
|
+
{ kind: "package", path: ".buildchain/kfd-2/public-release-trust.claim.json", required: false },
|
|
92
|
+
{ kind: "package", path: ".buildchain/kfd-2/kfd-foundation.trust-claims.json", required: false },
|
|
93
|
+
{ kind: "package", path: ".buildchain/kfd-2/kfd-foundation.trust-assessment.json", required: false },
|
|
94
|
+
{ kind: "package", path: ".buildchain/kfd-3/collaboration-interface.json", required: false },
|
|
95
|
+
{ kind: "package", path: "dist/site/kfd-claims.json", required: false },
|
|
96
|
+
]);
|
|
46
97
|
|
|
47
98
|
function readJsonPackageExport(exportPath) {
|
|
48
99
|
return JSON.parse(fs.readFileSync(require.resolve(exportPath), "utf8"));
|
|
49
100
|
}
|
|
50
101
|
|
|
102
|
+
function sha256Buffer(buffer) {
|
|
103
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function posixPath(value) {
|
|
107
|
+
return String(value || "").split(path.sep).join("/");
|
|
108
|
+
}
|
|
109
|
+
|
|
51
110
|
function readJsonFile(filePath) {
|
|
52
111
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
53
112
|
}
|
|
@@ -72,6 +131,304 @@ function issue(level, code, message, detail = {}) {
|
|
|
72
131
|
return { level, code, message, ...detail };
|
|
73
132
|
}
|
|
74
133
|
|
|
134
|
+
function fileDigest(filePath) {
|
|
135
|
+
const stat = fs.statSync(filePath);
|
|
136
|
+
if (stat.isDirectory()) {
|
|
137
|
+
const files = [];
|
|
138
|
+
const walk = (dir) => {
|
|
139
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
140
|
+
const child = path.join(dir, entry.name);
|
|
141
|
+
if (entry.isDirectory()) walk(child);
|
|
142
|
+
else if (entry.isFile()) files.push(child);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
walk(filePath);
|
|
146
|
+
const relativeFiles = files
|
|
147
|
+
.map((file) => ({ file, relativePath: posixPath(path.relative(filePath, file)) }))
|
|
148
|
+
.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
149
|
+
const hash = crypto.createHash("sha256");
|
|
150
|
+
for (const { file, relativePath } of relativeFiles) {
|
|
151
|
+
hash.update(relativePath);
|
|
152
|
+
hash.update("\0");
|
|
153
|
+
hash.update(fs.readFileSync(file));
|
|
154
|
+
hash.update("\0");
|
|
155
|
+
}
|
|
156
|
+
return hash.digest("hex");
|
|
157
|
+
}
|
|
158
|
+
return sha256Buffer(fs.readFileSync(filePath));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readJsonIfFile(filePath) {
|
|
162
|
+
if (!filePath.endsWith(".json")) return undefined;
|
|
163
|
+
try {
|
|
164
|
+
return readJsonFile(filePath);
|
|
165
|
+
} catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function packageRequire(cwd) {
|
|
171
|
+
const resolvedCwd = path.resolve(cwd || process.cwd());
|
|
172
|
+
const packagePath = path.join(resolvedCwd, "package.json");
|
|
173
|
+
return createRequire(fs.existsSync(packagePath) ? packagePath : path.join(resolvedCwd, "noop.js"));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function readLocalPackage(cwd) {
|
|
177
|
+
const packagePath = path.join(path.resolve(cwd), "package.json");
|
|
178
|
+
return fs.existsSync(packagePath) ? readJsonFile(packagePath) : {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolvePackage({ packageName, cwd }) {
|
|
182
|
+
const req = packageRequire(cwd);
|
|
183
|
+
const packageJsonPath = req.resolve(`${packageName}/package.json`);
|
|
184
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
185
|
+
return {
|
|
186
|
+
name: packageJson.name || packageName,
|
|
187
|
+
version: packageJson.version || "",
|
|
188
|
+
repository: typeof packageJson.repository === "string" ? packageJson.repository : packageJson.repository?.url || "",
|
|
189
|
+
packageJsonPath,
|
|
190
|
+
packageRoot: path.dirname(packageJsonPath),
|
|
191
|
+
packageJson,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseEvidenceSpec(spec) {
|
|
196
|
+
if (typeof spec === "string") {
|
|
197
|
+
const [kind, ...rest] = spec.includes(":") ? spec.split(":") : ["package", spec];
|
|
198
|
+
return {
|
|
199
|
+
kind: kind || "package",
|
|
200
|
+
path: rest.join(":"),
|
|
201
|
+
required: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (spec && typeof spec === "object" && !Array.isArray(spec)) {
|
|
205
|
+
const kind = String(spec.kind || spec.type || "package");
|
|
206
|
+
const rawPath = String(spec.path || spec.asset || spec.location || "");
|
|
207
|
+
return {
|
|
208
|
+
kind,
|
|
209
|
+
path: rawPath.startsWith(`${kind}:`) ? rawPath.slice(kind.length + 1) : rawPath,
|
|
210
|
+
required: spec.required === undefined ? true : Boolean(spec.required),
|
|
211
|
+
contract: spec.contract === undefined ? "" : String(spec.contract),
|
|
212
|
+
role: spec.role === undefined ? "" : String(spec.role),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return { kind: "package", path: "", required: false };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function assetFromSpec({ spec, resolvedPackage, cwd }) {
|
|
219
|
+
const parsed = parseEvidenceSpec(spec);
|
|
220
|
+
if (!parsed.path) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
const basePath = parsed.kind === "package"
|
|
224
|
+
? resolvedPackage.packageRoot
|
|
225
|
+
: path.resolve(cwd);
|
|
226
|
+
const filePath = path.resolve(basePath, parsed.path);
|
|
227
|
+
if (!fs.existsSync(filePath)) {
|
|
228
|
+
if (parsed.required) {
|
|
229
|
+
return {
|
|
230
|
+
path: parsed.kind === "package" ? parsed.path : posixPath(path.relative(cwd, filePath)),
|
|
231
|
+
kind: parsed.kind,
|
|
232
|
+
missing: true,
|
|
233
|
+
required: true,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const stat = fs.statSync(filePath);
|
|
239
|
+
const parsedJson = stat.isFile() ? readJsonIfFile(filePath) : undefined;
|
|
240
|
+
return {
|
|
241
|
+
path: parsed.kind === "package" ? parsed.path : posixPath(path.relative(cwd, filePath)),
|
|
242
|
+
kind: parsed.kind,
|
|
243
|
+
sha256: `sha256:${fileDigest(filePath)}`,
|
|
244
|
+
contract: parsed.contract || (parsedJson && typeof parsedJson === "object" ? parsedJson.contract || parsedJson.schema || "" : ""),
|
|
245
|
+
role: parsed.role || "",
|
|
246
|
+
directory: stat.isDirectory(),
|
|
247
|
+
parsed: parsedJson,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function normalizeKfdSupport(component = {}) {
|
|
252
|
+
const support = component.kfd && typeof component.kfd === "object" && !Array.isArray(component.kfd)
|
|
253
|
+
? { ...component.kfd }
|
|
254
|
+
: {};
|
|
255
|
+
for (const [key, aliases] of Object.entries({
|
|
256
|
+
kfd1: ["kfd1", "kfd_1", "kfd-1"],
|
|
257
|
+
kfd2: ["kfd2", "kfd_2", "kfd-2"],
|
|
258
|
+
kfd3: ["kfd3", "kfd_3", "kfd-3"],
|
|
259
|
+
kfd4: ["kfd4", "kfd_4", "kfd-4"],
|
|
260
|
+
})) {
|
|
261
|
+
const found = aliases.map((alias) => component[alias] ?? support[alias]).find((value) => value !== undefined);
|
|
262
|
+
support[key] = found === undefined ? support[key] || "declared" : String(found);
|
|
263
|
+
}
|
|
264
|
+
return support;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function normalizeKfdUpstreamComponent(component = {}, index = 0) {
|
|
268
|
+
const packageName = component.package || component.packageName || component.npm || component.name;
|
|
269
|
+
const explicitRole = component.role === undefined ? "" : String(component.role || "").trim();
|
|
270
|
+
return {
|
|
271
|
+
id: String(component.id || packageName || `upstream-${index + 1}`).replace(/^@/, "").replace(/[^a-zA-Z0-9_.-]+/g, "-"),
|
|
272
|
+
explicitRole,
|
|
273
|
+
packageName: packageName ? String(packageName) : "",
|
|
274
|
+
repository: String(component.repository || ""),
|
|
275
|
+
evidence: Array.isArray(component.evidence) ? component.evidence : [],
|
|
276
|
+
releasePassport: String(component.release_passport || component.releasePassport || ""),
|
|
277
|
+
kfd: normalizeKfdSupport(component),
|
|
278
|
+
residualRisk: Array.isArray(component.residualRisk)
|
|
279
|
+
? component.residualRisk.map(String)
|
|
280
|
+
: Array.isArray(component.residual_risk) ? component.residual_risk.map(String) : [],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function configuredKfdUpstreamComponents(cwd) {
|
|
285
|
+
const loaded = loadBuildchainConfig(cwd);
|
|
286
|
+
const upstream = loaded?.config?.kfd?.upstream;
|
|
287
|
+
if (!upstream || typeof upstream !== "object" || Array.isArray(upstream)) {
|
|
288
|
+
return {
|
|
289
|
+
configPath: loaded?.path || "",
|
|
290
|
+
autoDiscover: false,
|
|
291
|
+
components: [],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
configPath: loaded?.path || "",
|
|
296
|
+
autoDiscover: upstream.auto_discover === undefined ? Boolean(upstream.autoDiscover) : Boolean(upstream.auto_discover),
|
|
297
|
+
components: Array.isArray(upstream.components) ? upstream.components.map(normalizeKfdUpstreamComponent) : [],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function dependencyMap(pkg) {
|
|
302
|
+
return {
|
|
303
|
+
...(pkg.dependencies && typeof pkg.dependencies === "object" && !Array.isArray(pkg.dependencies) ? pkg.dependencies : {}),
|
|
304
|
+
...(pkg.devDependencies && typeof pkg.devDependencies === "object" && !Array.isArray(pkg.devDependencies) ? pkg.devDependencies : {}),
|
|
305
|
+
...(pkg.optionalDependencies && typeof pkg.optionalDependencies === "object" && !Array.isArray(pkg.optionalDependencies) ? pkg.optionalDependencies : {}),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function autoDiscoveredKfdUpstreams(cwd) {
|
|
310
|
+
const pkg = readLocalPackage(cwd);
|
|
311
|
+
return Object.keys(dependencyMap(pkg))
|
|
312
|
+
.filter((name) => name.startsWith("@kungfu-tech/"))
|
|
313
|
+
.filter((name) => name !== pkg.name)
|
|
314
|
+
.filter((name) => ["@kungfu-tech/kfd", "@kungfu-tech/buildchain", "@kungfu-tech/libnode"].includes(name))
|
|
315
|
+
.map((name, index) => normalizeKfdUpstreamComponent({
|
|
316
|
+
id: name.split("/").pop(),
|
|
317
|
+
package: name,
|
|
318
|
+
evidence: KFD_UPSTREAM_DEFAULT_EVIDENCE,
|
|
319
|
+
}, index));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function inferKfdUpstreamRole({ component = {}, resolvedPackage = {}, assets = [] } = {}) {
|
|
323
|
+
const explicitRole = String(component.explicitRole || "").trim();
|
|
324
|
+
if (explicitRole) {
|
|
325
|
+
return {
|
|
326
|
+
role: explicitRole,
|
|
327
|
+
roleSource: "explicit",
|
|
328
|
+
roleReason: KFD_UPSTREAM_ROLE_SET.has(explicitRole)
|
|
329
|
+
? "Configured role is one of the Buildchain-managed KFD upstream roles."
|
|
330
|
+
: `Configured role is not a Buildchain-managed KFD upstream role: ${explicitRole}`,
|
|
331
|
+
roleValid: KFD_UPSTREAM_ROLE_SET.has(explicitRole),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const packageName = resolvedPackage.name || component.packageName || "";
|
|
336
|
+
const knownPackageRole = KFD_UPSTREAM_KNOWN_PACKAGE_ROLES[packageName];
|
|
337
|
+
if (knownPackageRole) {
|
|
338
|
+
return {
|
|
339
|
+
role: knownPackageRole,
|
|
340
|
+
roleSource: "known-package",
|
|
341
|
+
roleReason: `${packageName} is a Buildchain-known KFD upstream package.`,
|
|
342
|
+
roleValid: true,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const assetList = Array.isArray(assets) ? assets : [];
|
|
347
|
+
const contracts = new Set(assetList.map((asset) => String(asset.contract || "")).filter(Boolean));
|
|
348
|
+
const paths = assetList.map((asset) => String(asset.path || ""));
|
|
349
|
+
|
|
350
|
+
if (contracts.has("kfd-standards-metadata") || paths.includes("standards.json")) {
|
|
351
|
+
return {
|
|
352
|
+
role: KFD_UPSTREAM_ROLES.STANDARD_AND_SCHEMA_PROVIDER,
|
|
353
|
+
roleSource: "evidence",
|
|
354
|
+
roleReason: "Package evidence exposes KFD standards metadata.",
|
|
355
|
+
roleValid: true,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
contracts.has("kungfu-buildchain-release-passport") ||
|
|
361
|
+
contracts.has("kungfu-buildchain-kfd-claim-registry") ||
|
|
362
|
+
paths.some((assetPath) => assetPath === "buildchain.release.json" || assetPath === "dist/site/kfd-claims.json")
|
|
363
|
+
) {
|
|
364
|
+
return {
|
|
365
|
+
role: KFD_UPSTREAM_ROLES.RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER,
|
|
366
|
+
roleSource: "evidence",
|
|
367
|
+
roleReason: "Package evidence exposes Buildchain release passport or KFD gate facts.",
|
|
368
|
+
roleValid: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (
|
|
373
|
+
contracts.has("kungfu-buildchain-site-bundle") ||
|
|
374
|
+
contracts.has("kungfu-buildchain-site-manifest") ||
|
|
375
|
+
paths.some((assetPath) => assetPath === "dist/site/site-manifest.json" || assetPath === "dist/site/buildchain-site.json")
|
|
376
|
+
) {
|
|
377
|
+
return {
|
|
378
|
+
role: KFD_UPSTREAM_ROLES.SITE_CONSUMPTION_PROVIDER,
|
|
379
|
+
roleSource: "evidence",
|
|
380
|
+
roleReason: "Package evidence exposes site-consumption facts.",
|
|
381
|
+
roleValid: true,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
contracts.has("kfd-3-collaboration-interface") ||
|
|
387
|
+
contracts.has("kfd-2-trust-claims") ||
|
|
388
|
+
contracts.has("kfd-2-trust-assessment") ||
|
|
389
|
+
paths.some((assetPath) => assetPath.includes("/kfd-") || assetPath.startsWith(".buildchain/kfd-"))
|
|
390
|
+
) {
|
|
391
|
+
return {
|
|
392
|
+
role: KFD_UPSTREAM_ROLES.KFD_AWARE_PRODUCT_COMPONENT,
|
|
393
|
+
roleSource: "evidence",
|
|
394
|
+
roleReason: "Package evidence exposes KFD witness, claim, or collaboration-interface facts.",
|
|
395
|
+
roleValid: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
role: KFD_UPSTREAM_ROLES.UNKNOWN_KFD_UPSTREAM,
|
|
401
|
+
roleSource: "default",
|
|
402
|
+
roleReason: "No Buildchain-known package or role-specific evidence matched; keeping the upstream visible without asking the consumer to invent a role.",
|
|
403
|
+
roleValid: true,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function releaseAnchorFromAssets(assets = []) {
|
|
408
|
+
return assets.find((asset) => asset?.parsed && (
|
|
409
|
+
String(asset.path || "").endsWith("release.json") ||
|
|
410
|
+
String(asset.contract || "").includes("release")
|
|
411
|
+
))?.parsed || null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function kfd3SurfaceCountFromAssets(assets = []) {
|
|
415
|
+
const collaboration = assets.find((asset) => asset?.parsed && (
|
|
416
|
+
asset.parsed.contract === "kfd-3-collaboration-interface" ||
|
|
417
|
+
String(asset.path || "").includes("collaboration-interface")
|
|
418
|
+
))?.parsed;
|
|
419
|
+
return Array.isArray(collaboration?.surfaces) ? collaboration.surfaces.length : 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function summarizeProduct({ cwd }) {
|
|
423
|
+
const pkg = readLocalPackage(cwd);
|
|
424
|
+
return {
|
|
425
|
+
id: String(pkg.name || path.basename(path.resolve(cwd))).replace(/^@/, "").replace(/[^a-zA-Z0-9_.-]+/g, "-"),
|
|
426
|
+
name: pkg.name || path.basename(path.resolve(cwd)),
|
|
427
|
+
version: pkg.version || "",
|
|
428
|
+
repository: typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url || "",
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
75
432
|
function validateTaxonomyEntryToIssues(entry, { kind, label }) {
|
|
76
433
|
try {
|
|
77
434
|
validateKfd2TrustTaxonomyEntry(entry, { kind, label });
|
|
@@ -310,6 +667,213 @@ export const kfd4 = Object.freeze({
|
|
|
310
667
|
status: "schema-only",
|
|
311
668
|
});
|
|
312
669
|
|
|
670
|
+
export function collectKfdUpstreamFacts({ cwd = process.cwd(), components = undefined, includeOwn = true } = {}) {
|
|
671
|
+
const resolvedCwd = path.resolve(cwd);
|
|
672
|
+
const configured = configuredKfdUpstreamComponents(resolvedCwd);
|
|
673
|
+
const explicitComponents = Array.isArray(components)
|
|
674
|
+
? components.map(normalizeKfdUpstreamComponent)
|
|
675
|
+
: configured.components;
|
|
676
|
+
const discoveredComponents = configured.autoDiscover ? autoDiscoveredKfdUpstreams(resolvedCwd) : [];
|
|
677
|
+
const byId = new Map();
|
|
678
|
+
for (const component of [...discoveredComponents, ...explicitComponents]) {
|
|
679
|
+
byId.set(component.id, { ...(byId.get(component.id) || {}), ...component });
|
|
680
|
+
}
|
|
681
|
+
const upstreams = [...byId.values()].map((component, index) => {
|
|
682
|
+
if (!component.packageName) {
|
|
683
|
+
const roleFacts = inferKfdUpstreamRole({ component });
|
|
684
|
+
return {
|
|
685
|
+
id: component.id || `upstream-${index + 1}`,
|
|
686
|
+
role: roleFacts.role,
|
|
687
|
+
roleSource: roleFacts.roleSource,
|
|
688
|
+
roleReason: roleFacts.roleReason,
|
|
689
|
+
roleValid: roleFacts.roleValid,
|
|
690
|
+
package: { name: "", version: "" },
|
|
691
|
+
repository: component.repository || "",
|
|
692
|
+
kfd: component.kfd || {},
|
|
693
|
+
releasePassport: component.releasePassport || "",
|
|
694
|
+
assets: [],
|
|
695
|
+
residualRisk: component.residualRisk || [],
|
|
696
|
+
status: "unresolved",
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const resolvedPackage = resolvePackage({ packageName: component.packageName, cwd: resolvedCwd });
|
|
700
|
+
const evidence = component.evidence.length > 0 ? component.evidence : KFD_UPSTREAM_DEFAULT_EVIDENCE;
|
|
701
|
+
const assets = evidence
|
|
702
|
+
.map((spec) => assetFromSpec({ spec, resolvedPackage, cwd: resolvedCwd }))
|
|
703
|
+
.filter(Boolean)
|
|
704
|
+
.map((asset) => {
|
|
705
|
+
const { parsed, ...summary } = asset;
|
|
706
|
+
return summary;
|
|
707
|
+
});
|
|
708
|
+
const assetsWithParsed = evidence
|
|
709
|
+
.map((spec) => assetFromSpec({ spec, resolvedPackage, cwd: resolvedCwd }))
|
|
710
|
+
.filter(Boolean);
|
|
711
|
+
const roleFacts = inferKfdUpstreamRole({ component, resolvedPackage, assets });
|
|
712
|
+
return {
|
|
713
|
+
id: component.id || resolvedPackage.name,
|
|
714
|
+
role: roleFacts.role,
|
|
715
|
+
roleSource: roleFacts.roleSource,
|
|
716
|
+
roleReason: roleFacts.roleReason,
|
|
717
|
+
roleValid: roleFacts.roleValid,
|
|
718
|
+
package: {
|
|
719
|
+
name: resolvedPackage.name,
|
|
720
|
+
version: resolvedPackage.version,
|
|
721
|
+
},
|
|
722
|
+
repository: component.repository || resolvedPackage.repository,
|
|
723
|
+
kfd: component.kfd,
|
|
724
|
+
releasePassport: component.releasePassport || "",
|
|
725
|
+
releaseAnchor: releaseAnchorFromAssets(assetsWithParsed),
|
|
726
|
+
kfd3SurfaceCount: kfd3SurfaceCountFromAssets(assetsWithParsed),
|
|
727
|
+
assets,
|
|
728
|
+
residualRisk: component.residualRisk,
|
|
729
|
+
status: assets.some((asset) => asset.missing && asset.required) ? "incomplete" : "collected",
|
|
730
|
+
};
|
|
731
|
+
});
|
|
732
|
+
return {
|
|
733
|
+
schemaVersion: 1,
|
|
734
|
+
contract: KFD_UPSTREAM_AGGREGATE_CONTRACT,
|
|
735
|
+
product: summarizeProduct({ cwd: resolvedCwd }),
|
|
736
|
+
source: {
|
|
737
|
+
generator: "@kungfu-tech/buildchain/kfd.collectKfdUpstreamFacts",
|
|
738
|
+
configPath: configured.configPath,
|
|
739
|
+
autoDiscover: configured.autoDiscover,
|
|
740
|
+
},
|
|
741
|
+
upstreams,
|
|
742
|
+
...(includeOwn ? { ownKfd: collectKfdStatus({ cwd: resolvedCwd }) } : {}),
|
|
743
|
+
summary: {
|
|
744
|
+
upstreamCount: upstreams.length,
|
|
745
|
+
kfdAwareUpstreams: upstreams.map((entry) => entry.id),
|
|
746
|
+
packageVersions: Object.fromEntries(upstreams.map((entry) => [entry.id, entry.package?.version || ""])),
|
|
747
|
+
status: upstreams.some((entry) => entry.status === "incomplete" || entry.status === "unresolved") ? "incomplete" : "collected",
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function listKfdUpstreamRoles() {
|
|
753
|
+
return {
|
|
754
|
+
schemaVersion: 1,
|
|
755
|
+
contract: "kungfu-buildchain-kfd-upstream-role-registry",
|
|
756
|
+
roles: KFD_UPSTREAM_ROLE_DEFINITIONS.map((entry) => ({ ...entry })),
|
|
757
|
+
policy: {
|
|
758
|
+
consumerConfiguration: "role is optional; Buildchain infers it from package identity and evidence whenever possible",
|
|
759
|
+
explicitRole: "explicit role values must be one of this registry",
|
|
760
|
+
unknownExplicitRole: "fail-closed",
|
|
761
|
+
fallbackRole: KFD_UPSTREAM_ROLES.UNKNOWN_KFD_UPSTREAM,
|
|
762
|
+
},
|
|
763
|
+
inference: {
|
|
764
|
+
knownPackages: Object.fromEntries(Object.entries(KFD_UPSTREAM_KNOWN_PACKAGE_ROLES)),
|
|
765
|
+
evidenceContracts: {
|
|
766
|
+
"kfd-standards-metadata": KFD_UPSTREAM_ROLES.STANDARD_AND_SCHEMA_PROVIDER,
|
|
767
|
+
"kungfu-buildchain-release-passport": KFD_UPSTREAM_ROLES.RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER,
|
|
768
|
+
"kungfu-buildchain-kfd-claim-registry": KFD_UPSTREAM_ROLES.RELEASE_PASSPORT_AND_KFD_GATE_PROVIDER,
|
|
769
|
+
"kungfu-buildchain-site-bundle": KFD_UPSTREAM_ROLES.SITE_CONSUMPTION_PROVIDER,
|
|
770
|
+
"kungfu-buildchain-site-manifest": KFD_UPSTREAM_ROLES.SITE_CONSUMPTION_PROVIDER,
|
|
771
|
+
"kfd-3-collaboration-interface": KFD_UPSTREAM_ROLES.KFD_AWARE_PRODUCT_COMPONENT,
|
|
772
|
+
"kfd-2-trust-claims": KFD_UPSTREAM_ROLES.KFD_AWARE_PRODUCT_COMPONENT,
|
|
773
|
+
"kfd-2-trust-assessment": KFD_UPSTREAM_ROLES.KFD_AWARE_PRODUCT_COMPONENT,
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function checkKfdUpstreamFacts(aggregate = {}) {
|
|
780
|
+
const issues = [];
|
|
781
|
+
if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) {
|
|
782
|
+
issues.push(issue("error", "kfd.upstream.object", "KFD upstream aggregate must be a JSON object"));
|
|
783
|
+
}
|
|
784
|
+
if (aggregate.contract !== KFD_UPSTREAM_AGGREGATE_CONTRACT) {
|
|
785
|
+
issues.push(issue("error", "kfd.upstream.contract", `KFD upstream aggregate contract must be ${KFD_UPSTREAM_AGGREGATE_CONTRACT}`));
|
|
786
|
+
}
|
|
787
|
+
const upstreams = Array.isArray(aggregate.upstreams) ? aggregate.upstreams : [];
|
|
788
|
+
if (upstreams.length === 0) {
|
|
789
|
+
issues.push(issue("warning", "kfd.upstream.empty", "KFD upstream aggregate has no upstream components"));
|
|
790
|
+
}
|
|
791
|
+
for (const [index, upstream] of upstreams.entries()) {
|
|
792
|
+
const label = `kfd.upstream.upstreams[${index}]`;
|
|
793
|
+
if (!upstream?.id) {
|
|
794
|
+
issues.push(issue("error", `${label}.id`, "KFD upstream component must include id"));
|
|
795
|
+
}
|
|
796
|
+
if (!upstream?.role) {
|
|
797
|
+
issues.push(issue("error", `${label}.role`, "KFD upstream component must include role"));
|
|
798
|
+
} else if (!KFD_UPSTREAM_ROLE_SET.has(upstream.role)) {
|
|
799
|
+
issues.push(issue("error", `${label}.role`, `KFD upstream role must be one of: ${[...KFD_UPSTREAM_ROLE_SET].join(", ")}`, {
|
|
800
|
+
id: upstream?.id || "",
|
|
801
|
+
role: upstream.role,
|
|
802
|
+
roleSource: upstream.roleSource || "",
|
|
803
|
+
}));
|
|
804
|
+
}
|
|
805
|
+
if (!upstream?.package?.name || !upstream?.package?.version) {
|
|
806
|
+
issues.push(issue("error", `${label}.package`, "KFD upstream component must include package name and version"));
|
|
807
|
+
}
|
|
808
|
+
const assets = Array.isArray(upstream?.assets) ? upstream.assets : [];
|
|
809
|
+
if (assets.length === 0 && !upstream?.releasePassport) {
|
|
810
|
+
issues.push(issue("warning", `${label}.evidence`, "KFD upstream component has no package assets or release passport evidence", {
|
|
811
|
+
id: upstream?.id || "",
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
for (const [assetIndex, asset] of assets.entries()) {
|
|
815
|
+
if (asset.missing && asset.required) {
|
|
816
|
+
issues.push(issue("error", `${label}.assets[${assetIndex}].missing`, "required KFD upstream evidence asset is missing", {
|
|
817
|
+
id: upstream?.id || "",
|
|
818
|
+
path: asset.path || "",
|
|
819
|
+
}));
|
|
820
|
+
} else if (!asset.sha256) {
|
|
821
|
+
issues.push(issue("error", `${label}.assets[${assetIndex}].sha256`, "KFD upstream evidence asset must include sha256", {
|
|
822
|
+
id: upstream?.id || "",
|
|
823
|
+
path: asset.path || "",
|
|
824
|
+
}));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const kfdStates = upstream?.kfd && typeof upstream.kfd === "object" && !Array.isArray(upstream.kfd)
|
|
828
|
+
? Object.entries(upstream.kfd)
|
|
829
|
+
: [];
|
|
830
|
+
for (const [standard, state] of kfdStates) {
|
|
831
|
+
if (String(state).toLowerCase() === "passed" && !upstream.releasePassport) {
|
|
832
|
+
issues.push(issue("error", `${label}.kfd.${standard}`, "upstream KFD passed state requires a release passport; use declared/aligned/exported-* for package-local evidence", {
|
|
833
|
+
id: upstream?.id || "",
|
|
834
|
+
}));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const ok = issues.filter((entry) => entry.level === "error").length === 0;
|
|
839
|
+
return {
|
|
840
|
+
schemaVersion: 1,
|
|
841
|
+
contract: KFD_UPSTREAM_CHECK_CONTRACT,
|
|
842
|
+
ok,
|
|
843
|
+
status: ok ? "passed" : "failed",
|
|
844
|
+
upstreamCount: upstreams.length,
|
|
845
|
+
issues,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export function collectKfdAggregate({ cwd = process.cwd() } = {}) {
|
|
850
|
+
const upstream = collectKfdUpstreamFacts({ cwd });
|
|
851
|
+
const upstreamCheck = checkKfdUpstreamFacts(upstream);
|
|
852
|
+
return {
|
|
853
|
+
schemaVersion: 1,
|
|
854
|
+
contract: KFD_AGGREGATE_CONTRACT,
|
|
855
|
+
product: upstream.product,
|
|
856
|
+
own: {
|
|
857
|
+
status: collectKfdStatus({ cwd }),
|
|
858
|
+
},
|
|
859
|
+
upstream,
|
|
860
|
+
upstreamCheck,
|
|
861
|
+
kfd: {
|
|
862
|
+
kfd1: "own-status-plus-upstream-contract-world-facts",
|
|
863
|
+
kfd2: "own-release-passport-plus-upstream-trust-surface",
|
|
864
|
+
kfd3: "own-collaboration-interface-plus-upstream-capability-facts",
|
|
865
|
+
kfd4: "schema-only-unless-product-declares-more",
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export const upstream = Object.freeze({
|
|
871
|
+
collect: collectKfdUpstreamFacts,
|
|
872
|
+
check: checkKfdUpstreamFacts,
|
|
873
|
+
aggregate: collectKfdAggregate,
|
|
874
|
+
roles: listKfdUpstreamRoles,
|
|
875
|
+
});
|
|
876
|
+
|
|
313
877
|
export const buildchainKfdClaims = Object.freeze({
|
|
314
878
|
createClaimRegistry: createBuildchainKfdClaimRegistry,
|
|
315
879
|
createSurfaceRegistry: createBuildchainKfdSurfaceRegistry,
|
|
@@ -354,8 +918,8 @@ export function collectKfdStatus({ cwd = process.cwd() } = {}) {
|
|
|
354
918
|
}, {}),
|
|
355
919
|
support: {
|
|
356
920
|
"kfd-1": ["schema", "witness", "gate", "verify"],
|
|
357
|
-
"kfd-2": ["schema", "taxonomy", "claims", "trust-claims", "trust-assessment"],
|
|
358
|
-
"kfd-3": ["schema", "detect", "register", "audit", "witness", "query"],
|
|
921
|
+
"kfd-2": ["schema", "taxonomy", "claims", "trust-claims", "trust-assessment", "upstream"],
|
|
922
|
+
"kfd-3": ["schema", "detect", "register", "audit", "witness", "query", "aggregate"],
|
|
359
923
|
"kfd-4": ["schema"],
|
|
360
924
|
},
|
|
361
925
|
paths: pathStatus,
|
|
@@ -64,6 +64,7 @@ function commandId(first = "", second = "", third = "") {
|
|
|
64
64
|
if (head === "kfd") {
|
|
65
65
|
if (!sub || sub === "...") return "kfd";
|
|
66
66
|
if (sub === "schema") return normalizedLeaf ? `kfd-schema-${normalizedLeaf}` : "kfd-schema";
|
|
67
|
+
if (sub === "upstream") return normalizedLeaf ? `kfd-upstream-${normalizedLeaf}` : "kfd-upstream";
|
|
67
68
|
if (/^[1-9][0-9]*$/.test(sub)) return normalizedLeaf ? `kfd-${sub}-${normalizedLeaf}` : `kfd-${sub}`;
|
|
68
69
|
return `kfd-${sub}`;
|
|
69
70
|
}
|
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
BUILDCHAIN_AGENT_MANUALS,
|
|
10
10
|
createBuildchainKfdClaimRegistry,
|
|
11
11
|
} from "../packages/core/buildchain-kfd-claims.js";
|
|
12
|
+
import {
|
|
13
|
+
collectKfdUpstreamFacts,
|
|
14
|
+
} from "../packages/core/kfd.js";
|
|
12
15
|
import {
|
|
13
16
|
createReadmeBadgeEndpointRegistry,
|
|
14
17
|
} from "../packages/core/readme-badges.js";
|
|
@@ -412,6 +415,11 @@ function cliCommandMeta(id) {
|
|
|
412
415
|
"kfd-3-register": { group: "kfd-trust", purpose: "Declare detected KFD-3 public surfaces into a product-owned surface registry." },
|
|
413
416
|
"kfd-3-witness": { group: "kfd-trust", purpose: "Generate release-passport-compatible KFD-3 surface witnesses from a product registry." },
|
|
414
417
|
"kfd-4-schema": { group: "kfd-trust", purpose: "Print the default KFD-4 schema exposed by the KFD package standards metadata." },
|
|
418
|
+
"kfd-aggregate": { group: "kfd-trust", purpose: "Return a product KFD view that combines own KFD status with upstream KFD aggregate facts." },
|
|
419
|
+
"kfd-upstream": { group: "kfd-trust", purpose: "Inspect KFD upstream aggregate command families." },
|
|
420
|
+
"kfd-upstream-check": { group: "kfd-trust", purpose: "Validate a KFD upstream aggregate document and fail closed on missing evidence." },
|
|
421
|
+
"kfd-upstream-collect": { group: "kfd-trust", purpose: "Collect declared KFD-aware upstream package evidence and hashes from Buildchain config." },
|
|
422
|
+
"kfd-upstream-roles": { group: "kfd-trust", purpose: "List Buildchain-managed KFD upstream role values and inference policy." },
|
|
415
423
|
lifecycle: { group: "reusable-build", purpose: "Run configured lifecycle commands and write deterministic artifact manifests." },
|
|
416
424
|
log: { group: "observability-diagnostics", purpose: "Inspect Buildchain logging command families." },
|
|
417
425
|
logging: { group: "observability-diagnostics", purpose: "Emit timestamped build events, summarize logs, and enforce required phases." },
|
|
@@ -462,7 +470,7 @@ function nodeApiMeta(exportName) {
|
|
|
462
470
|
"./surface-manifest": { group: "site-and-propagation", summary: "Surface manifest timestamp and reproducibility policy APIs." },
|
|
463
471
|
"./issue-reporting": { group: "observability-diagnostics", summary: "Buildchain-owned issue reporting API for workflow friction feedback." },
|
|
464
472
|
"./buildchain-layout": { group: "kfd-trust", summary: "Canonical .buildchain repository layout constants, path resolution, and migration APIs." },
|
|
465
|
-
"./kfd": { group: "kfd-trust", summary: "Unified KFD standards, schema discovery, KFD-1/KFD-2/KFD-3 grouped APIs, KFD-4 schema discovery, and Buildchain KFD claim helpers." },
|
|
473
|
+
"./kfd": { group: "kfd-trust", summary: "Unified KFD standards, schema discovery, KFD-1/KFD-2/KFD-3 grouped APIs, KFD-4 schema discovery, upstream KFD aggregate facts, and Buildchain KFD claim helpers." },
|
|
466
474
|
"./public-surface-audit": { group: "kfd-trust", summary: "Reverse audit APIs for CLI, workflow, action, site page, and documentation command surfaces." },
|
|
467
475
|
"./kfd-gate": { group: "kfd-trust", summary: "KFD-1/KFD-2/KFD-3 release gate evidence and validation APIs." },
|
|
468
476
|
"./buildchain-kfd-claims": { group: "kfd-trust", summary: "Buildchain self KFD claim registry, witnesses, and public claim APIs." },
|
|
@@ -510,6 +518,7 @@ function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nod
|
|
|
510
518
|
"cli-registry.json",
|
|
511
519
|
"node-api-registry.json",
|
|
512
520
|
"workflow-registry.json",
|
|
521
|
+
"kfd-upstream-aggregate.json",
|
|
513
522
|
"kfd-claims.json",
|
|
514
523
|
],
|
|
515
524
|
})),
|
|
@@ -922,6 +931,7 @@ function buildSiteBundle() {
|
|
|
922
931
|
"badge-endpoint-registry.json",
|
|
923
932
|
"product-mechanism.json",
|
|
924
933
|
"release-provenance.json",
|
|
934
|
+
"kfd-upstream-aggregate.json",
|
|
925
935
|
"kfd-claims.json",
|
|
926
936
|
"agent-index.json",
|
|
927
937
|
],
|
|
@@ -943,6 +953,7 @@ function buildSiteBundle() {
|
|
|
943
953
|
"public-surface-audit.json",
|
|
944
954
|
"artifact-schemas.json",
|
|
945
955
|
"buildchain-contract.json",
|
|
956
|
+
"kfd-upstream-aggregate.json",
|
|
946
957
|
"kfd-claims.json",
|
|
947
958
|
"badge-endpoint-registry.json",
|
|
948
959
|
],
|
|
@@ -1077,6 +1088,7 @@ function buildSiteBundle() {
|
|
|
1077
1088
|
"public surface reverse audit",
|
|
1078
1089
|
"manual and Node API registries",
|
|
1079
1090
|
"KFD claim registry",
|
|
1091
|
+
"KFD upstream aggregate registry",
|
|
1080
1092
|
"release-passport evidence vocabulary",
|
|
1081
1093
|
],
|
|
1082
1094
|
ownedBySite: [
|
|
@@ -1106,6 +1118,7 @@ function buildSiteBundle() {
|
|
|
1106
1118
|
"artifact-schemas.json": artifactSchemas,
|
|
1107
1119
|
"badge-endpoint-registry.json": badgeEndpointRegistry,
|
|
1108
1120
|
"buildchain-contract.json": createBuildchainContractWorld({ root }),
|
|
1121
|
+
"kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, includeOwn: false }),
|
|
1109
1122
|
"kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
|
|
1110
1123
|
"product-mechanism.json": productMechanism,
|
|
1111
1124
|
"release-provenance.json": releaseProvenance,
|