@kungfu-tech/buildchain 3.0.1 → 3.0.2-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.
Files changed (58) hide show
  1. package/README.md +5 -3
  2. package/actions/promote-buildchain-ref/README.md +7 -0
  3. package/bin/buildchain.mjs +173 -513
  4. package/bin/internal/cli-options.mjs +79 -0
  5. package/bin/internal/trust-release-cli.mjs +469 -0
  6. package/dist/site/agent-index.json +2 -0
  7. package/dist/site/artifact-schemas.json +5 -0
  8. package/dist/site/badge-endpoint-registry.json +18 -18
  9. package/dist/site/badges/v1/kfd-12/aligned.json +1 -1
  10. package/dist/site/badges/v1/kfd-12/declared.json +1 -1
  11. package/dist/site/badges/v1/kfd-12/downgraded.json +1 -1
  12. package/dist/site/badges/v1/kfd-12/draft.json +1 -1
  13. package/dist/site/badges/v1/kfd-12/failed.json +1 -1
  14. package/dist/site/badges/v1/kfd-12/missing.json +1 -1
  15. package/dist/site/badges/v1/kfd-12/passed.json +1 -1
  16. package/dist/site/badges/v1/kfd-12/planned.json +1 -1
  17. package/dist/site/badges/v1/kfd-13/aligned.json +1 -1
  18. package/dist/site/badges/v1/kfd-13/declared.json +1 -1
  19. package/dist/site/badges/v1/kfd-13/downgraded.json +1 -1
  20. package/dist/site/badges/v1/kfd-13/draft.json +1 -1
  21. package/dist/site/badges/v1/kfd-13/failed.json +1 -1
  22. package/dist/site/badges/v1/kfd-13/missing.json +1 -1
  23. package/dist/site/badges/v1/kfd-13/passed.json +1 -1
  24. package/dist/site/badges/v1/kfd-13/planned.json +1 -1
  25. package/dist/site/buildchain-contract.json +32 -22
  26. package/dist/site/buildchain-site.json +28 -18
  27. package/dist/site/capability-registry.json +2 -2
  28. package/dist/site/cli-registry.json +108 -0
  29. package/dist/site/controller-registry.json +10 -2
  30. package/dist/site/kfd-claims.json +136 -10
  31. package/dist/site/kfd-upstream-aggregate.json +20 -11
  32. package/dist/site/manual-registry.json +3 -3
  33. package/dist/site/node-api-registry.json +21 -8
  34. package/dist/site/page-registry.json +20 -10
  35. package/dist/site/public-surface-audit.json +114 -8
  36. package/dist/site/publication-registry.json +4 -4
  37. package/dist/site/release-passport-check-manifest.json +11 -0
  38. package/dist/site/release-provenance.json +3 -0
  39. package/dist/site/schemas/kfd-product-gate-input-v1.schema.json +164 -0
  40. package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
  41. package/dist/site/schemas/release-passport-v1.schema.json +3 -0
  42. package/dist/site/site-manifest.json +7 -7
  43. package/dist/site/workflow-registry.json +9 -3
  44. package/docs/cli.md +9 -2
  45. package/docs/kfd-support.md +43 -7
  46. package/docs/release-passport.md +15 -0
  47. package/package.json +6 -3
  48. package/packages/core/index.js +17 -0
  49. package/packages/core/kfd-product-gates.js +865 -0
  50. package/packages/core/kfd.js +16 -7
  51. package/packages/core/kfd3-surface-register.js +105 -1
  52. package/packages/core/public-surface-audit.js +6 -1
  53. package/packages/core/release-passport-contract.js +13 -0
  54. package/packages/core/release-passport.js +87 -3
  55. package/scripts/check-internal-architecture.mjs +171 -0
  56. package/scripts/check-inventory.mjs +4 -0
  57. package/scripts/generate-site-bundle.mjs +23 -0
  58. package/scripts/promotion-routing-evidence.mjs +73 -0
@@ -0,0 +1,865 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+
6
+ import kfdPackageJson from "@kungfu-tech/kfd/package.json" with { type: "json" };
7
+ import kfdStandards from "@kungfu-tech/kfd/standards.json" with { type: "json" };
8
+
9
+ export const KFD_PRODUCT_GATE_INPUT_CONTRACT =
10
+ "kungfu-buildchain-kfd-product-gate-input";
11
+ export const KFD_PRODUCT_GATE_CONTRACT =
12
+ "kungfu-buildchain-kfd-product-gate";
13
+ export const KFD_SUPPORT_PROJECTION_CONTRACT =
14
+ "kungfu-buildchain-kfd-support-projection";
15
+ export const KFD_PRODUCT_GATE_INPUT_SCHEMA_ID =
16
+ "https://buildchain.libkungfu.dev/schemas/kfd-product-gate-input-v1.schema.json";
17
+ export const KFD_SUPPORT_PROJECTION_SCHEMA_ID =
18
+ "https://buildchain.libkungfu.dev/schemas/kfd-support-projection-v1.schema.json";
19
+
20
+ const SUPPORTED_GATE_STANDARDS = Object.freeze(["kfd-4", "kfd-5", "kfd-7"]);
21
+ const SUPPORTED_GATE_STANDARD_SET = new Set(SUPPORTED_GATE_STANDARDS);
22
+ const SHA256_PATTERN = "^sha256:[0-9a-f]{64}$";
23
+ const GIT_SHA_PATTERN = "^[0-9a-f]{40}(?:[0-9a-f]{24})?$";
24
+ const REQUIRED_KFD7_EVIDENCE_CATEGORIES = Object.freeze([
25
+ "semantic-component-deletion-or-fusion",
26
+ "invalid-transition",
27
+ "export-import-rebuild",
28
+ "backend-migration",
29
+ "concurrency-retry-compensation",
30
+ "warrant-decay-revocation",
31
+ "atlas-staleness-loss",
32
+ "pursuit-continuity-settlement",
33
+ "episode-replay-contraction",
34
+ "cold-start-continuation",
35
+ "session-round-trip-refinement",
36
+ "session-complexity-breakpoint",
37
+ "context-insufficiency-counterexample",
38
+ ]);
39
+ const REQUIRED_RECORDS = Object.freeze({
40
+ "kfd-4": Object.freeze({
41
+ "observer-perspective": "kfd-4-observer-perspective",
42
+ "perspective-replay": "kfd-4-perspective-replay",
43
+ }),
44
+ "kfd-5": Object.freeze({
45
+ "primitive-discovery": "kfd-5-primitive-discovery",
46
+ }),
47
+ "kfd-7": Object.freeze({
48
+ "domain-profile": "kfd-7-domain-profile",
49
+ }),
50
+ });
51
+
52
+ const STRING = { type: "string", minLength: 1 };
53
+ const SHA256 = { type: "string", pattern: SHA256_PATTERN };
54
+
55
+ export const KFD_PRODUCT_GATE_INPUT_SCHEMA = {
56
+ $schema: "https://json-schema.org/draft/2020-12/schema",
57
+ $id: KFD_PRODUCT_GATE_INPUT_SCHEMA_ID,
58
+ title: "Buildchain KFD product gate input v1",
59
+ type: "object",
60
+ additionalProperties: false,
61
+ required: [
62
+ "schemaVersion",
63
+ "contract",
64
+ "standard",
65
+ "standardRevision",
66
+ "source",
67
+ "evidenceCut",
68
+ "records",
69
+ "evidence",
70
+ "responsibility",
71
+ "nonClaims",
72
+ ],
73
+ properties: {
74
+ schemaVersion: { const: 1 },
75
+ contract: { const: KFD_PRODUCT_GATE_INPUT_CONTRACT },
76
+ standard: { enum: SUPPORTED_GATE_STANDARDS },
77
+ standardRevision: { type: "integer", minimum: 1 },
78
+ source: {
79
+ type: "object",
80
+ additionalProperties: false,
81
+ required: ["repository", "sha"],
82
+ properties: {
83
+ repository: STRING,
84
+ sha: { type: "string", pattern: GIT_SHA_PATTERN },
85
+ },
86
+ },
87
+ evidenceCut: {
88
+ type: "object",
89
+ additionalProperties: false,
90
+ required: ["generatedAt", "expiresAt"],
91
+ properties: {
92
+ generatedAt: { type: "string", format: "date-time" },
93
+ expiresAt: { type: "string", format: "date-time" },
94
+ },
95
+ },
96
+ records: {
97
+ type: "array",
98
+ minItems: 1,
99
+ items: {
100
+ type: "object",
101
+ additionalProperties: false,
102
+ required: ["role", "path", "sha256"],
103
+ properties: { role: STRING, path: STRING, sha256: SHA256 },
104
+ },
105
+ },
106
+ evidence: {
107
+ type: "array",
108
+ minItems: 1,
109
+ items: {
110
+ type: "object",
111
+ additionalProperties: false,
112
+ required: ["id", "kind", "path", "sha256"],
113
+ properties: { id: STRING, kind: STRING, path: STRING, sha256: SHA256 },
114
+ },
115
+ },
116
+ responsibility: {
117
+ type: "object",
118
+ additionalProperties: false,
119
+ required: ["owner", "evidenceOwner", "proofOwner"],
120
+ properties: { owner: STRING, evidenceOwner: STRING, proofOwner: STRING },
121
+ },
122
+ nonClaims: {
123
+ type: "array",
124
+ minItems: 1,
125
+ items: STRING,
126
+ },
127
+ },
128
+ };
129
+
130
+ export const KFD_SUPPORT_PROJECTION_SCHEMA = {
131
+ $schema: "https://json-schema.org/draft/2020-12/schema",
132
+ $id: KFD_SUPPORT_PROJECTION_SCHEMA_ID,
133
+ title: "Buildchain KFD support projection v1",
134
+ description:
135
+ "A release-bound, non-authoritative projection of one product-owned KFD support matrix.",
136
+ type: "object",
137
+ additionalProperties: false,
138
+ required: [
139
+ "schemaVersion",
140
+ "contract",
141
+ "matrix",
142
+ "standardPackage",
143
+ "gateResults",
144
+ "rows",
145
+ "status",
146
+ "projectionRoot",
147
+ "nonClaims",
148
+ ],
149
+ properties: {
150
+ schemaVersion: { const: 1 },
151
+ contract: { const: KFD_SUPPORT_PROJECTION_CONTRACT },
152
+ matrix: {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ required: ["contract", "root", "authorityPath"],
156
+ properties: { contract: STRING, root: SHA256, authorityPath: STRING },
157
+ },
158
+ standardPackage: {
159
+ type: "object",
160
+ additionalProperties: false,
161
+ required: ["name", "version", "standardsSha256"],
162
+ properties: { name: STRING, version: STRING, standardsSha256: SHA256 },
163
+ },
164
+ gateResults: { type: "array", minItems: 3, items: { type: "object" } },
165
+ rows: { type: "array", minItems: 13, maxItems: 13, items: { type: "object" } },
166
+ status: { enum: ["passed", "failed"] },
167
+ projectionRoot: SHA256,
168
+ nonClaims: { type: "array", minItems: 1, items: STRING },
169
+ },
170
+ };
171
+
172
+ const require = createRequire(import.meta.url);
173
+ let verifierPromise;
174
+
175
+ function stableJson(value) {
176
+ if (Array.isArray(value)) {
177
+ return `[${value.map(stableJson).join(",")}]`;
178
+ }
179
+ if (value && typeof value === "object") {
180
+ return `{${Object.keys(value)
181
+ .sort()
182
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
183
+ .join(",")}}`;
184
+ }
185
+ return JSON.stringify(value);
186
+ }
187
+
188
+ export function kfdProductGateDigest(value) {
189
+ return `sha256:${crypto.createHash("sha256").update(stableJson(value)).digest("hex")}`;
190
+ }
191
+
192
+ function fileDigest(filePath) {
193
+ return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`;
194
+ }
195
+
196
+ function issue(code, pathName, message, detail = {}) {
197
+ return { level: "error", code, path: pathName, message, ...detail };
198
+ }
199
+
200
+ function isObject(value) {
201
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
202
+ }
203
+
204
+ function optionalString(value) {
205
+ return value === undefined || value === null ? "" : String(value).trim();
206
+ }
207
+
208
+ function standardMetadata(standard) {
209
+ return kfdStandards?.standards?.[standard] || {};
210
+ }
211
+
212
+ function standardsFileDigest() {
213
+ return fileDigest(require.resolve("@kungfu-tech/kfd/standards.json"));
214
+ }
215
+
216
+ function normalizeSha256(value) {
217
+ const normalized = optionalString(value).toLowerCase();
218
+ if (!normalized) return "";
219
+ return normalized.startsWith("sha256:") ? normalized : `sha256:${normalized}`;
220
+ }
221
+
222
+ function parseDate(value) {
223
+ const timestamp = Date.parse(optionalString(value));
224
+ return Number.isFinite(timestamp) ? timestamp : Number.NaN;
225
+ }
226
+
227
+ function checkEvidenceCut(input, { checkedAt, expectedSourceSha }, issues) {
228
+ const sourceSha = optionalString(input?.source?.sha).toLowerCase();
229
+ if (!new RegExp(GIT_SHA_PATTERN).test(sourceSha)) {
230
+ issues.push(issue("source-sha", "source.sha", "source.sha must be a 40- or 64-character Git SHA"));
231
+ }
232
+ if (expectedSourceSha && sourceSha !== optionalString(expectedSourceSha).toLowerCase()) {
233
+ issues.push(issue("source-sha-mismatch", "source.sha", "gate source must match the expected release source", {
234
+ expected: expectedSourceSha,
235
+ actual: sourceSha,
236
+ }));
237
+ }
238
+ const checked = parseDate(checkedAt);
239
+ const generated = parseDate(input?.evidenceCut?.generatedAt);
240
+ const expires = parseDate(input?.evidenceCut?.expiresAt);
241
+ if (!Number.isFinite(generated)) {
242
+ issues.push(issue("generated-at", "evidenceCut.generatedAt", "evidenceCut.generatedAt must be an ISO date-time"));
243
+ }
244
+ if (!Number.isFinite(expires)) {
245
+ issues.push(issue("expires-at", "evidenceCut.expiresAt", "evidenceCut.expiresAt must be an ISO date-time"));
246
+ }
247
+ if (Number.isFinite(generated) && Number.isFinite(checked) && generated > checked) {
248
+ issues.push(issue("future-evidence", "evidenceCut.generatedAt", "evidence was generated after the gate check"));
249
+ }
250
+ if (Number.isFinite(expires) && Number.isFinite(checked) && expires <= checked) {
251
+ issues.push(issue("stale-evidence", "evidenceCut.expiresAt", "evidence cut has expired"));
252
+ }
253
+ if (Number.isFinite(generated) && Number.isFinite(expires) && expires <= generated) {
254
+ issues.push(issue("invalid-evidence-window", "evidenceCut", "expiresAt must be later than generatedAt"));
255
+ }
256
+ }
257
+
258
+ function resolveBoundFile(cwd, reference, label, issues) {
259
+ const relativePath = optionalString(reference?.path).replace(/\\/g, "/");
260
+ if (!relativePath || path.isAbsolute(relativePath)) {
261
+ issues.push(issue("evidence-path", `${label}.path`, `${label}.path must be a repository-relative path`));
262
+ return undefined;
263
+ }
264
+ const root = fs.realpathSync(cwd);
265
+ const candidate = path.resolve(root, relativePath);
266
+ if (!fs.existsSync(candidate)) {
267
+ issues.push(issue("evidence-missing", `${label}.path`, `${relativePath} does not exist`));
268
+ return undefined;
269
+ }
270
+ const real = fs.realpathSync(candidate);
271
+ const withinRoot = real === root || real.startsWith(`${root}${path.sep}`);
272
+ if (!withinRoot || fs.lstatSync(candidate).isSymbolicLink() || !fs.statSync(real).isFile()) {
273
+ issues.push(issue("evidence-boundary", `${label}.path`, `${relativePath} must resolve to a regular file inside the product repository`));
274
+ return undefined;
275
+ }
276
+ const actual = fileDigest(real);
277
+ const expected = normalizeSha256(reference?.sha256);
278
+ if (!new RegExp(SHA256_PATTERN).test(expected)) {
279
+ issues.push(issue("evidence-digest", `${label}.sha256`, `${label}.sha256 must be a sha256 digest`));
280
+ } else if (actual !== expected) {
281
+ issues.push(issue("evidence-drift", `${label}.sha256`, `${relativePath} digest does not match the declared evidence cut`, {
282
+ expected,
283
+ actual,
284
+ }));
285
+ }
286
+ let value;
287
+ try {
288
+ value = JSON.parse(fs.readFileSync(real, "utf8"));
289
+ } catch (error) {
290
+ issues.push(issue("evidence-json", `${label}.path`, `${relativePath} must contain valid JSON`, {
291
+ cause: error instanceof Error ? error.message : String(error),
292
+ }));
293
+ }
294
+ return { path: relativePath, realPath: real, sha256: actual, value };
295
+ }
296
+
297
+ async function instantiateVerifier() {
298
+ const wasmPath = require.resolve("@kungfu-tech/kfd/verifier/wasm");
299
+ const module = await WebAssembly.instantiate(fs.readFileSync(wasmPath), {});
300
+ return module.instance;
301
+ }
302
+
303
+ async function kfdVerifierInstance() {
304
+ verifierPromise ||= instantiateVerifier();
305
+ return verifierPromise;
306
+ }
307
+
308
+ export async function verifyKfdRecord(record) {
309
+ const instance = await kfdVerifierInstance();
310
+ const { memory, kfd_alloc: alloc, kfd_free: free, kfd_verify: verify } = instance.exports;
311
+ const bundle = {
312
+ schemaVersion: 1,
313
+ contract: "kfd.verification-bundle/v1",
314
+ kind: "kfd-record",
315
+ primary: JSON.stringify(record),
316
+ artifacts: {},
317
+ };
318
+ const input = new TextEncoder().encode(JSON.stringify(bundle));
319
+ const inputPointer = alloc(input.length);
320
+ new Uint8Array(memory.buffer, inputPointer, input.length).set(input);
321
+ let packed;
322
+ try {
323
+ packed = verify(inputPointer, input.length);
324
+ } finally {
325
+ free(inputPointer, input.length);
326
+ }
327
+ const outputPointer = Number(packed >> 32n);
328
+ const outputLength = Number(packed & 0xffffffffn);
329
+ const output = new Uint8Array(memory.buffer, outputPointer, outputLength).slice();
330
+ free(outputPointer, outputLength);
331
+ return JSON.parse(new TextDecoder().decode(output));
332
+ }
333
+
334
+ function validateGateInputEnvelope(input, issues) {
335
+ if (!isObject(input)) {
336
+ issues.push(issue("input-type", "", "gate input must be a JSON object"));
337
+ return;
338
+ }
339
+ if (input.schemaVersion !== 1) {
340
+ issues.push(issue("schema-version", "schemaVersion", "schemaVersion must be 1"));
341
+ }
342
+ if (input.contract !== KFD_PRODUCT_GATE_INPUT_CONTRACT) {
343
+ issues.push(issue("contract", "contract", `contract must be ${KFD_PRODUCT_GATE_INPUT_CONTRACT}`));
344
+ }
345
+ if (!SUPPORTED_GATE_STANDARD_SET.has(input.standard)) {
346
+ issues.push(issue("standard", "standard", "standard must be kfd-4, kfd-5, or kfd-7"));
347
+ return;
348
+ }
349
+ const metadata = standardMetadata(input.standard);
350
+ if (input.standardRevision !== metadata.revision) {
351
+ issues.push(issue("standard-revision", "standardRevision", "gate input revision must match the installed KFD standard", {
352
+ expected: metadata.revision,
353
+ actual: input.standardRevision,
354
+ }));
355
+ }
356
+ if (!optionalString(input?.source?.repository)) {
357
+ issues.push(issue("source-repository", "source.repository", "source.repository is required"));
358
+ }
359
+ if (!isObject(input.responsibility)) {
360
+ issues.push(issue("responsibility", "responsibility", "responsibility is required"));
361
+ } else {
362
+ for (const field of ["owner", "evidenceOwner", "proofOwner"]) {
363
+ if (!optionalString(input.responsibility[field])) {
364
+ issues.push(issue("responsibility-field", `responsibility.${field}`, `responsibility.${field} is required`));
365
+ }
366
+ }
367
+ }
368
+ if (!Array.isArray(input.nonClaims) || input.nonClaims.length === 0) {
369
+ issues.push(issue("non-claims", "nonClaims", "nonClaims must be non-empty"));
370
+ }
371
+ }
372
+
373
+ function validateKfd4(records, evidence, issues) {
374
+ const perspective = records.get("observer-perspective")?.value;
375
+ const replay = records.get("perspective-replay")?.value;
376
+ if (perspective?.verification?.result !== "pass") {
377
+ issues.push(issue("kfd4-perspective-verification", "records.observer-perspective.verification.result", "observer perspective verification must pass"));
378
+ }
379
+ if (replay?.mode !== "contrastive") {
380
+ issues.push(issue("kfd4-contrastive-replay", "records.perspective-replay.mode", "KFD-4 product qualification requires contrastive replay"));
381
+ }
382
+ const sourceViews = Array.isArray(replay?.sourceViews) ? replay.sourceViews : [];
383
+ const viewIds = sourceViews.map((entry) => optionalString(entry?.id)).filter(Boolean);
384
+ if (sourceViews.length < 2 || new Set(viewIds).size !== sourceViews.length) {
385
+ issues.push(issue("kfd4-view-identity", "records.perspective-replay.sourceViews", "contrastive replay requires at least two uniquely identified source views"));
386
+ }
387
+ if (!Array.isArray(replay?.reconstruction?.declaredLoss)) {
388
+ issues.push(issue("kfd4-declared-loss", "records.perspective-replay.reconstruction.declaredLoss", "reconstruction must explicitly declare transformation loss"));
389
+ }
390
+ const preserved = new Set(replay?.reconstruction?.preservedElements || []);
391
+ for (const element of ["observer", "accepted-fact-cut", "evidence-boundary"]) {
392
+ if (!preserved.has(element)) {
393
+ issues.push(issue("kfd4-preservation", "records.perspective-replay.reconstruction.preservedElements", `reconstruction must preserve ${element}`));
394
+ }
395
+ }
396
+ if (replay?.verification?.result !== "pass" || !Array.isArray(replay?.verification?.evidence) || replay.verification.evidence.length === 0) {
397
+ issues.push(issue("kfd4-replay-verification", "records.perspective-replay.verification", "perspective replay verification must pass with retained evidence"));
398
+ }
399
+ for (const kind of ["projection-fsck", "negative-fixture"]) {
400
+ if (!evidence.some((entry) => entry.kind === kind)) {
401
+ issues.push(issue("kfd4-gate-evidence", "evidence", `KFD-4 gate requires ${kind} evidence`));
402
+ }
403
+ }
404
+ }
405
+
406
+ function validateKfd5(records, evidence, issues) {
407
+ const discovery = records.get("primitive-discovery")?.value;
408
+ if (discovery?.decision?.outcome !== "accepted") {
409
+ issues.push(issue("kfd5-decision", "records.primitive-discovery.decision.outcome", "Primitive qualification requires an accepted decision"));
410
+ }
411
+ for (const testName of ["minimumClosure", "deletion", "fuse", "dogfood"]) {
412
+ const result = discovery?.tests?.[testName];
413
+ if (result?.result !== "pass" || !Array.isArray(result?.evidence) || result.evidence.length === 0) {
414
+ issues.push(issue("kfd5-test", `records.primitive-discovery.tests.${testName}`, `${testName} must pass with retained evidence`));
415
+ }
416
+ }
417
+ if (!Array.isArray(discovery?.tests?.falsifiers) || discovery.tests.falsifiers.length === 0) {
418
+ issues.push(issue("kfd5-falsifiers", "records.primitive-discovery.tests.falsifiers", "Primitive qualification requires explicit falsifiers"));
419
+ }
420
+ if (!evidence.some((entry) => entry.kind === "negative-fixture")) {
421
+ issues.push(issue("kfd5-negative-fixture", "evidence", "KFD-5 gate requires a negative fixture"));
422
+ }
423
+ }
424
+
425
+ function validateKfd7(records, evidence, issues) {
426
+ const profile = records.get("domain-profile")?.value;
427
+ if (profile?.domainProfile?.qualificationStatus !== "qualified") {
428
+ issues.push(issue("kfd7-qualification", "records.domain-profile.domainProfile.qualificationStatus", "KFD-7 Domain Profile must be qualified"));
429
+ }
430
+ if (profile?.activation?.decision !== "activate") {
431
+ issues.push(issue("kfd7-activation", "records.domain-profile.activation.decision", "KFD-7 gate requires an explicit activate decision"));
432
+ }
433
+ const independentReview = optionalString(profile?.activation?.independentReview);
434
+ if (!independentReview || /^(?:pending|none|not[- ]yet|not[- ]performed)/i.test(independentReview)) {
435
+ issues.push(issue("kfd7-independent-review", "records.domain-profile.activation.independentReview", "activation requires a retained independent review"));
436
+ }
437
+ if (!Array.isArray(profile?.activation?.productWitnesses) || profile.activation.productWitnesses.length === 0) {
438
+ issues.push(issue("kfd7-product-witness", "records.domain-profile.activation.productWitnesses", "activation requires product witnesses"));
439
+ }
440
+ const obligations = Array.isArray(profile?.evidenceObligations) ? profile.evidenceObligations : [];
441
+ const byCategory = new Map(obligations.map((entry) => [entry?.category, entry]));
442
+ const evidenceIds = new Set(evidence.map((entry) => entry.id));
443
+ for (const category of REQUIRED_KFD7_EVIDENCE_CATEGORIES) {
444
+ const obligation = byCategory.get(category);
445
+ if (!obligation || obligation.status !== "passed" || !Array.isArray(obligation.artifactRefs) || obligation.artifactRefs.length === 0) {
446
+ issues.push(issue("kfd7-evidence-obligation", "records.domain-profile.evidenceObligations", `${category} must pass with retained artifact refs`));
447
+ continue;
448
+ }
449
+ for (const artifactRef of obligation.artifactRefs) {
450
+ if (!evidenceIds.has(artifactRef)) {
451
+ issues.push(issue("kfd7-evidence-binding", "evidence", `artifact ref ${artifactRef} is not bound by the gate evidence cut`));
452
+ }
453
+ }
454
+ }
455
+ if (new Set(obligations.map((entry) => entry?.category)).size !== obligations.length) {
456
+ issues.push(issue("kfd7-evidence-duplicate", "records.domain-profile.evidenceObligations", "evidence obligation categories must be unique"));
457
+ }
458
+ for (const kind of ["independent-review", "negative-fixture"]) {
459
+ if (!evidence.some((entry) => entry.kind === kind)) {
460
+ issues.push(issue("kfd7-gate-evidence", "evidence", `KFD-7 gate requires ${kind} evidence`));
461
+ }
462
+ }
463
+ }
464
+
465
+ export async function evaluateKfdProductGate({
466
+ cwd = process.cwd(),
467
+ input,
468
+ expectedSourceSha = "",
469
+ checkedAt = new Date().toISOString(),
470
+ } = {}) {
471
+ const issues = [];
472
+ validateGateInputEnvelope(input, issues);
473
+ checkEvidenceCut(input, { checkedAt, expectedSourceSha }, issues);
474
+ const standard = optionalString(input?.standard).toLowerCase();
475
+ const requiredRecords = REQUIRED_RECORDS[standard] || {};
476
+ const recordEntries = Array.isArray(input?.records) ? input.records : [];
477
+ const evidenceEntries = Array.isArray(input?.evidence) ? input.evidence : [];
478
+ const records = new Map();
479
+ const recordResults = [];
480
+ const roleCounts = new Map();
481
+ for (const reference of recordEntries) {
482
+ const role = optionalString(reference?.role);
483
+ roleCounts.set(role, (roleCounts.get(role) || 0) + 1);
484
+ const resolved = resolveBoundFile(cwd, reference, `records.${role || "unknown"}`, issues);
485
+ if (!resolved) continue;
486
+ const verifier = await verifyKfdRecord(resolved.value);
487
+ const expectedContract = requiredRecords[role];
488
+ if (!expectedContract) {
489
+ issues.push(issue("record-role", `records.${role}`, `${role || "empty role"} is not admitted for ${standard}`));
490
+ } else if (resolved.value?.contract !== expectedContract || resolved.value?.standard !== standard) {
491
+ issues.push(issue("record-contract", `records.${role}`, `${role} must contain ${expectedContract} for ${standard}`));
492
+ }
493
+ if (!verifier.valid) {
494
+ issues.push(issue("kfd-verifier", `records.${role}`, `${role} failed the KFD offline verifier`, {
495
+ verifierIssues: verifier.issues || [],
496
+ }));
497
+ }
498
+ records.set(role, resolved);
499
+ recordResults.push({
500
+ role,
501
+ path: resolved.path,
502
+ sha256: resolved.sha256,
503
+ contract: resolved.value?.contract || "",
504
+ verifier: {
505
+ valid: verifier.valid === true,
506
+ reportRoot: kfdProductGateDigest(verifier),
507
+ qualifying: verifier.qualifying === true,
508
+ selfCertified: verifier.selfCertified === true,
509
+ },
510
+ });
511
+ }
512
+ for (const role of Object.keys(requiredRecords)) {
513
+ if ((roleCounts.get(role) || 0) !== 1) {
514
+ issues.push(issue("record-cardinality", `records.${role}`, `${standard} requires exactly one ${role} record`));
515
+ }
516
+ }
517
+ const evidence = [];
518
+ const evidenceIds = new Set();
519
+ for (const [index, reference] of evidenceEntries.entries()) {
520
+ const id = optionalString(reference?.id);
521
+ if (!id || evidenceIds.has(id)) {
522
+ issues.push(issue("evidence-id", `evidence[${index}].id`, "evidence ids must be non-empty and unique"));
523
+ }
524
+ evidenceIds.add(id);
525
+ const resolved = resolveBoundFile(cwd, reference, `evidence[${index}]`, issues);
526
+ if (resolved) {
527
+ evidence.push({
528
+ id,
529
+ kind: optionalString(reference.kind),
530
+ path: resolved.path,
531
+ sha256: resolved.sha256,
532
+ });
533
+ }
534
+ }
535
+ if (standard === "kfd-4") validateKfd4(records, evidence, issues);
536
+ if (standard === "kfd-5") validateKfd5(records, evidence, issues);
537
+ if (standard === "kfd-7") validateKfd7(records, evidence, issues);
538
+ const result = {
539
+ schemaVersion: 1,
540
+ contract: KFD_PRODUCT_GATE_CONTRACT,
541
+ standard,
542
+ standardRevision: standardMetadata(standard).revision || 0,
543
+ standardPackage: {
544
+ name: kfdPackageJson.name,
545
+ version: kfdPackageJson.version,
546
+ standardsSha256: standardsFileDigest(),
547
+ },
548
+ source: isObject(input?.source) ? { ...input.source, sha: optionalString(input.source.sha).toLowerCase() } : {},
549
+ evidenceCut: isObject(input?.evidenceCut) ? { ...input.evidenceCut } : {},
550
+ checkedAt,
551
+ status: issues.length === 0 ? "passed" : "failed",
552
+ qualifying: false,
553
+ selfCertified: false,
554
+ records: recordResults.sort((left, right) => left.role.localeCompare(right.role)),
555
+ evidence: evidence.sort((left, right) => left.id.localeCompare(right.id)),
556
+ responsibility: isObject(input?.responsibility) ? { ...input.responsibility } : {},
557
+ nonClaims: Array.isArray(input?.nonClaims) ? [...input.nonClaims] : [],
558
+ issues,
559
+ inputRoot: kfdProductGateDigest(input),
560
+ };
561
+ result.gateRoot = kfdProductGateDigest(result);
562
+ return result;
563
+ }
564
+
565
+ export function validateKfdProductGateResult(result, {
566
+ expectedSourceSha = "",
567
+ checkedAt = new Date().toISOString(),
568
+ } = {}) {
569
+ const issues = [];
570
+ if (!isObject(result) || result.schemaVersion !== 1 || result.contract !== KFD_PRODUCT_GATE_CONTRACT) {
571
+ issues.push(issue("gate-contract", "", `gate result must use ${KFD_PRODUCT_GATE_CONTRACT} v1`));
572
+ return { valid: false, issues };
573
+ }
574
+ if (!SUPPORTED_GATE_STANDARD_SET.has(result.standard)) {
575
+ issues.push(issue("gate-standard", "standard", "gate result standard must be kfd-4, kfd-5, or kfd-7"));
576
+ }
577
+ const expectedRevision = standardMetadata(result.standard).revision;
578
+ if (result.standardRevision !== expectedRevision) {
579
+ issues.push(issue("gate-standard-revision", "standardRevision", "gate result uses a stale KFD revision", {
580
+ expected: expectedRevision,
581
+ actual: result.standardRevision,
582
+ }));
583
+ }
584
+ if (
585
+ result?.standardPackage?.name !== kfdPackageJson.name ||
586
+ result?.standardPackage?.version !== kfdPackageJson.version ||
587
+ result?.standardPackage?.standardsSha256 !== standardsFileDigest()
588
+ ) {
589
+ issues.push(issue("gate-standard-package", "standardPackage", "gate result must bind the installed KFD package and standards bytes"));
590
+ }
591
+ const copy = structuredClone(result);
592
+ const root = copy.gateRoot;
593
+ delete copy.gateRoot;
594
+ if (root !== kfdProductGateDigest(copy)) {
595
+ issues.push(issue("gate-root", "gateRoot", "gate result root does not match its content"));
596
+ }
597
+ checkEvidenceCut(result, { checkedAt, expectedSourceSha }, issues);
598
+ if (!["passed", "failed"].includes(result.status)) {
599
+ issues.push(issue("gate-status", "status", "gate status must be passed or failed"));
600
+ }
601
+ if (result.qualifying !== false || result.selfCertified !== false) {
602
+ issues.push(issue("gate-non-qualification", "", "Buildchain product gates must not self-qualify or self-certify a KFD adoption"));
603
+ }
604
+ return { valid: issues.length === 0, issues };
605
+ }
606
+
607
+ function expectedKfdIds() {
608
+ return Array.from({ length: 13 }, (_, index) => `KFD-${index + 1}`);
609
+ }
610
+
611
+ function normalizeMatrixRoot(matrix, matrixRoot) {
612
+ const normalized = normalizeSha256(matrixRoot);
613
+ return normalized || kfdProductGateDigest(matrix);
614
+ }
615
+
616
+ function projectMatrixRow(row) {
617
+ return {
618
+ id: row.id,
619
+ key: row.key,
620
+ title: row.title,
621
+ supportStatus: row.supportStatus,
622
+ normative: row.normative,
623
+ implementation: row.implementation,
624
+ verification: row.verification,
625
+ buildchain: row.buildchain,
626
+ releaseQualification: row.releaseQualification,
627
+ claimClass: row.claimClass,
628
+ knownLimitations: row.knownLimitations,
629
+ owner: row.owner,
630
+ nextGate: row.nextGate,
631
+ };
632
+ }
633
+
634
+ function validateMatrixRows(matrix, gatesByStandard, issues) {
635
+ const rows = Array.isArray(matrix?.rows) ? matrix.rows : [];
636
+ const expectedIds = expectedKfdIds();
637
+ const ids = rows.map((row) => row?.id);
638
+ if (rows.length !== expectedIds.length || new Set(ids).size !== expectedIds.length) {
639
+ issues.push(issue("matrix-row-set", "rows", "support matrix must contain exactly one row for KFD-1 through KFD-13"));
640
+ }
641
+ for (const id of expectedIds) {
642
+ const row = rows.find((entry) => entry?.id === id);
643
+ if (!row) {
644
+ issues.push(issue("matrix-row-missing", "rows", `${id} is missing`));
645
+ continue;
646
+ }
647
+ const key = id.toLowerCase();
648
+ const metadata = standardMetadata(key);
649
+ if (row.key !== key || row?.normative?.status !== metadata.status || row?.normative?.revision !== metadata.revision) {
650
+ issues.push(issue("matrix-normative-drift", `rows.${id}.normative`, `${id} must match installed KFD status and revision`, {
651
+ expected: { key, status: metadata.status, revision: metadata.revision },
652
+ actual: { key: row.key, status: row?.normative?.status, revision: row?.normative?.revision },
653
+ }));
654
+ }
655
+ const shipped = row?.releaseQualification?.shippedSupport === true;
656
+ if (
657
+ shipped &&
658
+ (
659
+ row?.implementation?.status !== "implemented" ||
660
+ row?.verification?.status !== "passed" ||
661
+ row?.buildchain?.gateStatus !== "passed" ||
662
+ !String(row.supportStatus || "").includes("supported")
663
+ )
664
+ ) {
665
+ issues.push(issue("matrix-shipped-widening", `rows.${id}`, `${id} cannot be shipped without implemented, verified, and passed Buildchain evidence`));
666
+ }
667
+ }
668
+ for (const id of ["KFD-4", "KFD-5"]) {
669
+ const row = rows.find((entry) => entry?.id === id);
670
+ if (row?.supportStatus !== "candidate" || row?.releaseQualification?.shippedSupport !== false) {
671
+ issues.push(issue("matrix-candidate-widening", `rows.${id}`, `${id} must remain a non-shipped candidate`));
672
+ }
673
+ }
674
+ const kfd6 = rows.find((entry) => entry?.id === "KFD-6");
675
+ if (
676
+ kfd6?.supportStatus !== "unsupported" ||
677
+ kfd6?.releaseQualification?.shippedSupport !== false ||
678
+ kfd6?.buildchain?.gateStatus === "passed"
679
+ ) {
680
+ issues.push(issue("matrix-kfd6-barrier", "rows.KFD-6", "KFD-6 must remain explicitly unsupported and non-shipped"));
681
+ }
682
+ for (let number = 8; number <= 13; number += 1) {
683
+ const id = `KFD-${number}`;
684
+ const row = rows.find((entry) => entry?.id === id);
685
+ if (
686
+ row?.supportStatus !== "draft-adopter-evidence" ||
687
+ row?.releaseQualification?.shippedSupport !== false ||
688
+ row?.buildchain?.gateStatus === "passed"
689
+ ) {
690
+ issues.push(issue("matrix-draft-barrier", `rows.${id}`, `${id} must remain draft adopter evidence and non-shipped`));
691
+ }
692
+ }
693
+ for (const standard of SUPPORTED_GATE_STANDARDS) {
694
+ const id = standard.toUpperCase();
695
+ const row = rows.find((entry) => entry?.id === id);
696
+ const gate = gatesByStandard.get(standard);
697
+ if (!gate) {
698
+ issues.push(issue("matrix-gate-missing", `rows.${id}.buildchain`, `${id} requires a Buildchain product-gate result`));
699
+ continue;
700
+ }
701
+ const rowPassed = row?.buildchain?.gateStatus === "passed";
702
+ const gatePassed = gate.status === "passed";
703
+ if (rowPassed !== gatePassed) {
704
+ issues.push(issue("matrix-gate-disagreement", `rows.${id}.buildchain.gateStatus`, `${id} matrix and Buildchain gate disagree`, {
705
+ matrix: row?.buildchain?.gateStatus,
706
+ gate: gate.status,
707
+ }));
708
+ }
709
+ if (row?.buildchain?.protocol !== `${KFD_PRODUCT_GATE_CONTRACT}/v1`) {
710
+ issues.push(issue("matrix-gate-protocol", `rows.${id}.buildchain.protocol`, `${id} must name the versioned Buildchain product-gate protocol`));
711
+ }
712
+ }
713
+ return rows;
714
+ }
715
+
716
+ export function createKfdSupportProjection({
717
+ matrix,
718
+ matrixRoot = "",
719
+ gateResults = [],
720
+ authorityPath = ".buildchain/kfd/support-matrix.json",
721
+ expectedSourceSha = "",
722
+ checkedAt = new Date().toISOString(),
723
+ } = {}) {
724
+ const issues = [];
725
+ if (!isObject(matrix) || matrix.contract !== "kungfu-kfd-support-matrix" || matrix.schemaVersion !== 1) {
726
+ issues.push(issue("matrix-contract", "", "support matrix must use kungfu-kfd-support-matrix v1"));
727
+ }
728
+ if (
729
+ matrix?.upstream?.package !== kfdPackageJson.name ||
730
+ matrix?.upstream?.version !== kfdPackageJson.version ||
731
+ matrix?.upstream?.standardsSha256 !== standardsFileDigest()
732
+ ) {
733
+ issues.push(issue("matrix-standard-package", "upstream", "support matrix must bind the installed KFD package and standards bytes", {
734
+ expected: {
735
+ package: kfdPackageJson.name,
736
+ version: kfdPackageJson.version,
737
+ standardsSha256: standardsFileDigest(),
738
+ },
739
+ actual: matrix?.upstream,
740
+ }));
741
+ }
742
+ const gatesByStandard = new Map();
743
+ for (const [index, gate] of gateResults.entries()) {
744
+ const validation = validateKfdProductGateResult(gate, { expectedSourceSha, checkedAt });
745
+ if (!validation.valid) {
746
+ issues.push(issue("gate-result-invalid", `gateResults[${index}]`, "gate result is invalid", {
747
+ gateIssues: validation.issues,
748
+ }));
749
+ }
750
+ if (gatesByStandard.has(gate?.standard)) {
751
+ issues.push(issue("gate-result-duplicate", `gateResults[${index}].standard`, `${gate.standard} gate result is duplicated`));
752
+ }
753
+ gatesByStandard.set(gate?.standard, gate);
754
+ }
755
+ const rows = validateMatrixRows(matrix, gatesByStandard, issues);
756
+ const projection = {
757
+ schemaVersion: 1,
758
+ contract: KFD_SUPPORT_PROJECTION_CONTRACT,
759
+ matrix: {
760
+ contract: matrix?.contract || "",
761
+ root: normalizeMatrixRoot(matrix, matrixRoot),
762
+ authorityPath: optionalString(matrix?.authority?.path || authorityPath),
763
+ },
764
+ standardPackage: {
765
+ name: kfdPackageJson.name,
766
+ version: kfdPackageJson.version,
767
+ standardsSha256: standardsFileDigest(),
768
+ },
769
+ gateResults: SUPPORTED_GATE_STANDARDS.map((standard) => {
770
+ const gate = gatesByStandard.get(standard);
771
+ return gate
772
+ ? {
773
+ standard,
774
+ standardRevision: gate.standardRevision,
775
+ sourceSha: gate?.source?.sha || "",
776
+ evidenceCut: gate.evidenceCut,
777
+ status: gate.status,
778
+ gateRoot: gate.gateRoot,
779
+ issueCount: Array.isArray(gate.issues) ? gate.issues.length : 0,
780
+ }
781
+ : { standard, status: "missing", gateRoot: "", issueCount: 1 };
782
+ }),
783
+ rows: rows.map(projectMatrixRow),
784
+ status: issues.length === 0 ? "passed" : "failed",
785
+ nonClaims: [
786
+ "This projection does not replace the product-owned support matrix.",
787
+ "A passed Buildchain gate does not by itself activate, ship, or certify a KFD adoption.",
788
+ "Candidate, unsupported, draft, and non-qualifying matrix states cannot be widened by this projection.",
789
+ ],
790
+ issues,
791
+ };
792
+ projection.projectionRoot = kfdProductGateDigest(projection);
793
+ return projection;
794
+ }
795
+
796
+ export function validateKfdSupportProjection(projection, {
797
+ expectedSourceSha = "",
798
+ checkedAt = new Date().toISOString(),
799
+ } = {}) {
800
+ const issues = [];
801
+ if (!isObject(projection) || projection.schemaVersion !== 1 || projection.contract !== KFD_SUPPORT_PROJECTION_CONTRACT) {
802
+ issues.push(issue("projection-contract", "", `projection must use ${KFD_SUPPORT_PROJECTION_CONTRACT} v1`));
803
+ return { valid: false, issues };
804
+ }
805
+ const copy = structuredClone(projection);
806
+ const root = copy.projectionRoot;
807
+ delete copy.projectionRoot;
808
+ if (root !== kfdProductGateDigest(copy)) {
809
+ issues.push(issue("projection-root", "projectionRoot", "support projection root does not match its content"));
810
+ }
811
+ if (projection.status !== "passed" || (projection.issues || []).length !== 0) {
812
+ issues.push(issue("projection-status", "status", "release passport requires a passed support projection"));
813
+ }
814
+ if (
815
+ projection?.standardPackage?.name !== kfdPackageJson.name ||
816
+ projection?.standardPackage?.version !== kfdPackageJson.version ||
817
+ projection?.standardPackage?.standardsSha256 !== standardsFileDigest()
818
+ ) {
819
+ issues.push(issue("projection-standard-package", "standardPackage", "support projection uses stale KFD package metadata"));
820
+ }
821
+ if (!new RegExp(SHA256_PATTERN).test(optionalString(projection?.matrix?.root))) {
822
+ issues.push(issue("projection-matrix-root", "matrix.root", "support projection must bind the exact support matrix bytes"));
823
+ }
824
+ if (!optionalString(projection?.matrix?.authorityPath)) {
825
+ issues.push(issue("projection-matrix-authority", "matrix.authorityPath", "support projection must retain the product-owned matrix authority path"));
826
+ }
827
+ const gatesByStandard = new Map();
828
+ for (const [index, gate] of (projection.gateResults || []).entries()) {
829
+ if (!SUPPORTED_GATE_STANDARD_SET.has(gate?.standard)) {
830
+ issues.push(issue("projection-gate-standard", `gateResults[${index}].standard`, "projected gate must be kfd-4, kfd-5, or kfd-7"));
831
+ continue;
832
+ }
833
+ if (gatesByStandard.has(gate.standard)) {
834
+ issues.push(issue("projection-gate-duplicate", `gateResults[${index}].standard`, `${gate.standard} is duplicated`));
835
+ }
836
+ gatesByStandard.set(gate.standard, gate);
837
+ if (gate.standardRevision !== standardMetadata(gate.standard).revision) {
838
+ issues.push(issue("projection-gate-revision", `gateResults[${index}].standardRevision`, "projected gate revision is stale"));
839
+ }
840
+ if (!new RegExp(SHA256_PATTERN).test(optionalString(gate.gateRoot))) {
841
+ issues.push(issue("projection-gate-root", `gateResults[${index}].gateRoot`, "projected gate must retain its gate root"));
842
+ }
843
+ if (expectedSourceSha && gate.sourceSha !== expectedSourceSha) {
844
+ issues.push(issue("projection-source", `gateResults[${index}].sourceSha`, "gate source must match the release source"));
845
+ }
846
+ const checked = parseDate(checkedAt);
847
+ const expires = parseDate(gate?.evidenceCut?.expiresAt);
848
+ if (!Number.isFinite(expires) || (Number.isFinite(checked) && expires <= checked)) {
849
+ issues.push(issue("projection-stale-gate", `gateResults[${index}].evidenceCut.expiresAt`, "projected gate evidence is stale"));
850
+ }
851
+ }
852
+ validateMatrixRows({ rows: projection.rows }, gatesByStandard, issues);
853
+ if (!Array.isArray(projection.nonClaims) || projection.nonClaims.length === 0) {
854
+ issues.push(issue("projection-non-claims", "nonClaims", "support projection must preserve non-claims"));
855
+ }
856
+ return { valid: issues.length === 0, issues };
857
+ }
858
+
859
+ export const kfdProductGates = Object.freeze({
860
+ evaluate: evaluateKfdProductGate,
861
+ projectSupport: createKfdSupportProjection,
862
+ validateGateResult: validateKfdProductGateResult,
863
+ validateSupportProjection: validateKfdSupportProjection,
864
+ verifyRecord: verifyKfdRecord,
865
+ });