@kungfu-tech/kfd 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (44) hide show
  1. package/.buildchain/kfd-1/contract-world.witness.json +753 -0
  2. package/.buildchain/kfd-2/kfd-foundation.trust-assessment.json +238 -0
  3. package/.buildchain/kfd-2/kfd-foundation.trust-claims.json +225 -0
  4. package/.buildchain/kfd-2/public-release-trust.claim.json +131 -0
  5. package/.buildchain/kfd-3/collaboration-interface.artifact.json +774 -0
  6. package/.buildchain/kfd-3/collaboration-interface.json +421 -0
  7. package/.buildchain/kfd-3/collaboration-interface.prebuild.json +1141 -0
  8. package/README.md +149 -29
  9. package/TRADEMARKS.md +60 -0
  10. package/buildchain.contract-lock.json +86 -0
  11. package/buildchain.release-propagation.json +32 -0
  12. package/decisions/{kfd-1.md → KFD-1.md} +77 -35
  13. package/decisions/{kfd-2.md → KFD-2.md} +63 -11
  14. package/decisions/{kfd-3.md → KFD-3.md} +54 -16
  15. package/decisions/KFD-4.md +182 -0
  16. package/docs/KFD-1-usage.md +37 -0
  17. package/docs/KFD-2-usage.md +123 -0
  18. package/docs/KFD-3-usage.md +98 -0
  19. package/docs/KFD-4-usage.md +31 -0
  20. package/docs/MAP.md +20 -3
  21. package/docs/release-governance.md +28 -0
  22. package/kfd.release.json +13 -0
  23. package/package.json +28 -2
  24. package/registry.json +15 -5
  25. package/release-impact.json +113 -0
  26. package/schemas/kfd-1/contract-world.schema.json +67 -4
  27. package/schemas/kfd-1/witness.schema.json +113 -0
  28. package/schemas/kfd-2/release-claims.schema.json +331 -0
  29. package/schemas/kfd-2/release-trust-passport.schema.json +276 -0
  30. package/schemas/kfd-2/trust-assessment.schema.json +313 -0
  31. package/schemas/kfd-2/trust-claims.schema.json +334 -0
  32. package/schemas/kfd-2/trust-taxonomy.schema.json +219 -0
  33. package/schemas/kfd-3/collaboration-interface.schema.json +542 -0
  34. package/schemas/kfd-3/witness.schema.json +167 -0
  35. package/schemas/kfd-4/observer-perspective.schema.json +272 -0
  36. package/schemas/kfd-standards.schema.json +163 -0
  37. package/scripts/check.mjs +930 -0
  38. package/scripts/npm-publish-transaction.mjs +220 -0
  39. package/scripts/update-kfd-1-witness.mjs +35 -0
  40. package/scripts/update-kfd-2-claim.mjs +304 -0
  41. package/scripts/update-kfd-3-witness.mjs +261 -0
  42. package/scripts/update-site-bundle.mjs +353 -0
  43. package/site/kfd-site.json +251 -12
  44. package/standards.json +775 -15
@@ -0,0 +1,930 @@
1
+ // Registry conformance check: the registry, decision documents, and release
2
+ // impact ledger must agree, so a release cannot ship evidence that lies about
3
+ // its contents or versioning surface.
4
+ import { readFileSync, existsSync } from "node:fs";
5
+ import crypto from "node:crypto";
6
+ import { generatedSiteBundle } from "./update-site-bundle.mjs";
7
+
8
+ const fail = (msg) => { console.error(`check: ${msg}`); process.exitCode = 1; };
9
+ const sha256File = (filePath) => crypto.createHash("sha256").update(readFileSync(filePath)).digest("hex");
10
+ const requireFields = (value, required, label) => {
11
+ for (const field of required ?? []) {
12
+ if (value?.[field] === undefined) fail(`${label} missing required field ${field}`);
13
+ }
14
+ };
15
+ const asSet = (items) => new Set(items ?? []);
16
+ const requireSameEnum = (actual, expected, label) => {
17
+ const actualSet = asSet(actual);
18
+ const expectedSet = asSet(expected);
19
+ for (const value of expectedSet) {
20
+ if (!actualSet.has(value)) fail(`${label} missing value ${value}`);
21
+ }
22
+ for (const value of actualSet) {
23
+ if (!expectedSet.has(value)) fail(`${label} has unregistered value ${value}`);
24
+ }
25
+ };
26
+ const registry = JSON.parse(readFileSync("registry.json", "utf8"));
27
+ const standardsMetadata = JSON.parse(readFileSync("standards.json", "utf8"));
28
+ const standardsSchema = JSON.parse(readFileSync("schemas/kfd-standards.schema.json", "utf8"));
29
+ const releaseImpact = JSON.parse(readFileSync("release-impact.json", "utf8"));
30
+ const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
31
+ const siteBundle = JSON.parse(readFileSync("site/kfd-site.json", "utf8"));
32
+ const expectedSiteBundle = generatedSiteBundle();
33
+ const kfd1WitnessPath = ".buildchain/kfd-1/contract-world.witness.json";
34
+ const kfd1Witness = existsSync(kfd1WitnessPath) ? JSON.parse(readFileSync(kfd1WitnessPath, "utf8")) : undefined;
35
+ const kfd2ClaimPath = ".buildchain/kfd-2/public-release-trust.claim.json";
36
+ const kfd2Claim = existsSync(kfd2ClaimPath) ? JSON.parse(readFileSync(kfd2ClaimPath, "utf8")) : undefined;
37
+ const kfd2TrustClaimsPath = ".buildchain/kfd-2/kfd-foundation.trust-claims.json";
38
+ const kfd2TrustClaims = existsSync(kfd2TrustClaimsPath) ? JSON.parse(readFileSync(kfd2TrustClaimsPath, "utf8")) : undefined;
39
+ const kfd2TrustAssessmentPath = ".buildchain/kfd-2/kfd-foundation.trust-assessment.json";
40
+ const kfd2TrustAssessment = existsSync(kfd2TrustAssessmentPath) ? JSON.parse(readFileSync(kfd2TrustAssessmentPath, "utf8")) : undefined;
41
+ const kfd3InterfacePath = ".buildchain/kfd-3/collaboration-interface.json";
42
+ const kfd3PrebuildWitnessPath = ".buildchain/kfd-3/collaboration-interface.prebuild.json";
43
+ const kfd3ArtifactWitnessPath = ".buildchain/kfd-3/collaboration-interface.artifact.json";
44
+ const kfd3Interface = existsSync(kfd3InterfacePath) ? JSON.parse(readFileSync(kfd3InterfacePath, "utf8")) : undefined;
45
+ const kfd3PrebuildWitness = existsSync(kfd3PrebuildWitnessPath) ? JSON.parse(readFileSync(kfd3PrebuildWitnessPath, "utf8")) : undefined;
46
+ const kfd3ArtifactWitness = existsSync(kfd3ArtifactWitnessPath) ? JSON.parse(readFileSync(kfd3ArtifactWitnessPath, "utf8")) : undefined;
47
+ const hashablePath = (filePath) => String(filePath || "").split("#", 1)[0];
48
+
49
+ if (registry.schemaVersion !== 1) fail(`unsupported schemaVersion ${registry.schemaVersion}`);
50
+ if (registry.contract !== "kfd-registry") fail(`unexpected contract ${registry.contract}`);
51
+ if (standardsMetadata.schemaVersion !== 1) fail(`unsupported standards schemaVersion ${standardsMetadata.schemaVersion}`);
52
+ if (standardsMetadata.contract !== "kfd-standards-metadata") fail(`unexpected standards contract ${standardsMetadata.contract}`);
53
+ if (standardsMetadata.metadataSchema?.id !== "https://kfd.libkungfu.dev/schemas/kfd-standards.schema.json") {
54
+ fail("standards metadataSchema.id must be the canonical KFD standards schema URI");
55
+ }
56
+ if (standardsMetadata.metadataSchema?.path !== "schemas/kfd-standards.schema.json") {
57
+ fail("standards metadataSchema.path must be schemas/kfd-standards.schema.json");
58
+ }
59
+ if (standardsSchema.$id !== standardsMetadata.metadataSchema?.id) {
60
+ fail("standards schema $id must match standards metadataSchema.id");
61
+ }
62
+ if (standardsSchema.properties?.contract?.const !== "kfd-standards-metadata") {
63
+ fail("standards schema must describe the kfd-standards-metadata contract");
64
+ }
65
+ requireFields(standardsMetadata, standardsSchema.required, "standards metadata");
66
+ requireFields(standardsMetadata.metadataSchema, standardsSchema.properties?.metadataSchema?.required, "standards metadataSchema");
67
+ requireFields(standardsMetadata.source, standardsSchema.properties?.source?.required, "standards source");
68
+ if (siteBundle.schemaVersion !== 1) fail(`unsupported site bundle schemaVersion ${siteBundle.schemaVersion}`);
69
+ if (siteBundle.contract !== "kfd-site-bundle") fail(`unexpected site bundle contract ${siteBundle.contract}`);
70
+ if (JSON.stringify(siteBundle) !== JSON.stringify(expectedSiteBundle)) {
71
+ fail("site/kfd-site.json must match the generated README.md homepage bundle; run npm run update:site-bundle");
72
+ }
73
+ if (siteBundle.source?.homepageTextSource !== "README.md") fail("site bundle homepageTextSource must be README.md");
74
+ if (siteBundle.source?.registry !== "registry.json") fail("site bundle registry source must be registry.json");
75
+ if (siteBundle.source?.decisionsDir !== "decisions") fail("site bundle decisionsDir must be decisions");
76
+ if (siteBundle.homepage?.title !== "KFD — Kung Fu Decisions") fail("site bundle homepage title must match README H1 text");
77
+ if (!Array.isArray(siteBundle.homepage?.sections) || siteBundle.homepage.sections.length === 0) {
78
+ fail("site bundle homepage.sections must expose generated README sections");
79
+ }
80
+ if (!siteBundle.homepage?.displayPlan?.firstScreen?.include?.includes("foundation-triad")) {
81
+ fail("site bundle homepage displayPlan firstScreen must include foundation-triad");
82
+ }
83
+ for (const requiredSection of ["foundation-triad", "foundation-model", "adoption-boundary", "practice-guidelines", "product-proof-path", "agent-quickstart", "decision-metadata"]) {
84
+ if (!siteBundle.homepage.sections.some((entry) => entry.id === requiredSection && entry.sourcePath === "README.md" && entry.markdown)) {
85
+ fail(`site bundle homepage.sections must include README projection ${requiredSection}`);
86
+ }
87
+ }
88
+ const adoptionBoundary = siteBundle.homepage.sections.find((entry) => entry.id === "adoption-boundary");
89
+ if (adoptionBoundary?.includeInFirstScreen !== false || adoptionBoundary?.renderRole !== "primary") {
90
+ fail("site bundle adoption-boundary must be primary content outside the first screen");
91
+ }
92
+ if (siteBundle.homepage.sections.some((entry) => entry.id === "homepage-content-contract")) {
93
+ fail("site bundle homepage.sections must not render the renderer contract as homepage content");
94
+ }
95
+ if (
96
+ siteBundle.homepage?.rendererContract?.id !== "homepage-content-contract" ||
97
+ siteBundle.homepage?.rendererContract?.renderAsHomepageContent !== false ||
98
+ !siteBundle.homepage?.rendererContract?.markdown
99
+ ) {
100
+ fail("site bundle homepage.rendererContract must expose the README renderer contract outside homepage.sections");
101
+ }
102
+ if (siteBundle.homepage?.currentDecisions?.source !== "registry.json") fail("site bundle currentDecisions source must be registry.json");
103
+ if (siteBundle.decisionPages?.source !== "registry.json") fail("site bundle decisionPages source must be registry.json");
104
+ if (siteBundle.decisionPages?.bodySource !== "registry.entries[].path") fail("site bundle decision page body source must be registry.entries[].path");
105
+ if (siteBundle.routes?.decisionUsagePattern !== "/{number}/usage") fail("site bundle routes.decisionUsagePattern must be /{number}/usage");
106
+ if (siteBundle.decisionPages?.usagePages?.relationship !== "usage-child-of-decision") {
107
+ fail("site bundle decisionPages.usagePages.relationship must be usage-child-of-decision");
108
+ }
109
+ if (siteBundle.decisionPages?.usagePages?.bodySource !== "docs/KFD-{number}-usage.md") {
110
+ fail("site bundle decisionPages.usagePages.bodySource must be docs/KFD-{number}-usage.md");
111
+ }
112
+ if (siteBundle.decisionPages?.usagePages?.stableUrlPattern !== "/{number}/usage") {
113
+ fail("site bundle decisionPages.usagePages.stableUrlPattern must be /{number}/usage");
114
+ }
115
+ const usagePagesByDecision = new Map((siteBundle.decisionPages?.usagePages?.pages ?? []).map((entry) => [entry.decisionId, entry]));
116
+ for (const e of registry.entries) {
117
+ const usagePage = usagePagesByDecision.get(e.id);
118
+ const expectedPath = `docs/KFD-${e.number}-usage.md`;
119
+ const expectedUrl = `${e.url}/usage`;
120
+ if (!usagePage) fail(`site bundle decisionPages.usagePages missing ${e.id}`);
121
+ else {
122
+ if (usagePage.parentPath !== e.path) fail(`site bundle ${e.id} usage parentPath must be ${e.path}`);
123
+ if (usagePage.parentUrl !== e.url) fail(`site bundle ${e.id} usage parentUrl must be ${e.url}`);
124
+ if (usagePage.path !== expectedPath) fail(`site bundle ${e.id} usage path must be ${expectedPath}`);
125
+ if (usagePage.sourcePath !== expectedPath) fail(`site bundle ${e.id} usage sourcePath must be ${expectedPath}`);
126
+ if (usagePage.url !== expectedUrl) fail(`site bundle ${e.id} usage url must be ${expectedUrl}`);
127
+ if (usagePage.sourceExists !== true) fail(`site bundle ${e.id} usage sourceExists must be true`);
128
+ }
129
+ if (!existsSync(expectedPath)) fail(`missing usage document ${expectedPath} for ${e.id}`);
130
+ }
131
+ for (const decisionId of usagePagesByDecision.keys()) {
132
+ if (!registry.entries.some((entry) => entry.id === decisionId)) {
133
+ fail(`site bundle decisionPages.usagePages has unknown decision ${decisionId}`);
134
+ }
135
+ }
136
+ if (siteBundle.decisionPages?.metadata?.licenseBoundary?.license !== "Apache-2.0") {
137
+ fail("site bundle decision metadata licenseBoundary.license must be Apache-2.0");
138
+ }
139
+ if (siteBundle.decisionPages?.metadata?.licenseBoundary?.licenseFile !== "LICENSE") {
140
+ fail("site bundle decision metadata licenseBoundary.licenseFile must be LICENSE");
141
+ }
142
+ if (siteBundle.decisionPages?.metadata?.licenseBoundary?.officialStatusAndTrademarks !== "TRADEMARKS.md") {
143
+ fail("site bundle decision metadata licenseBoundary.officialStatusAndTrademarks must be TRADEMARKS.md");
144
+ }
145
+ const sitePublicFactSource = siteBundle.decisionPages?.metadata?.publicFactSource;
146
+ if (sitePublicFactSource?.kind !== "git-repository") fail("site bundle decision metadata publicFactSource.kind must be git-repository");
147
+ if (sitePublicFactSource?.host !== "github") fail("site bundle decision metadata publicFactSource.host must be github");
148
+ if (sitePublicFactSource?.repository !== "kungfu-systems/kfd") fail("site bundle decision metadata publicFactSource.repository must be kungfu-systems/kfd");
149
+ if (sitePublicFactSource?.url !== "https://github.com/kungfu-systems/kfd") fail("site bundle decision metadata publicFactSource.url must be the KFD GitHub repository");
150
+ if (sitePublicFactSource?.loadBearingCoordinate !== "commit-addressed repository contents") {
151
+ fail("site bundle decision metadata publicFactSource.loadBearingCoordinate must be commit-addressed repository contents");
152
+ }
153
+ if (sitePublicFactSource?.stableRenderedIndex !== "https://kfd.libkungfu.dev") {
154
+ fail("site bundle decision metadata publicFactSource.stableRenderedIndex must be https://kfd.libkungfu.dev");
155
+ }
156
+ for (const requiredPath of ["decisions/KFD-N.md", "registry.json", "standards.json"]) {
157
+ if (!sitePublicFactSource?.canonicalPaths?.includes(requiredPath)) {
158
+ fail(`site bundle decision metadata publicFactSource.canonicalPaths must include ${requiredPath}`);
159
+ }
160
+ }
161
+ for (const requiredFile of ["README.md", "TRADEMARKS.md", "decisions", "registry.json", "standards.json", "kfd.release.json", "schemas", "site", "buildchain.contract-lock.json", "buildchain.release-propagation.json", "release-impact.json", ".buildchain/kfd-1/contract-world.witness.json", ".buildchain/kfd-2", ".buildchain/kfd-3", "docs"]) {
162
+ if (!Array.isArray(packageJson.files) || !packageJson.files.includes(requiredFile)) {
163
+ fail(`package.json files[] must include ${requiredFile}`);
164
+ }
165
+ }
166
+ for (const requiredExport of ["./TRADEMARKS.md", "./registry.json", "./standards.json", "./kfd.release.json", "./site/kfd-site.json", "./buildchain.contract-lock.json", "./buildchain.release-propagation.json", "./release-impact.json", "./buildchain/kfd-1/contract-world.witness.json", "./buildchain/kfd-2/public-release-trust.claim.json", "./buildchain/kfd-2/kfd-foundation.trust-claims.json", "./buildchain/kfd-2/kfd-foundation.trust-assessment.json", "./buildchain/kfd-3/collaboration-interface.json", "./buildchain/kfd-3/collaboration-interface.prebuild.json", "./buildchain/kfd-3/collaboration-interface.artifact.json", "./schemas/*.json", "./schemas/*/*.json"]) {
167
+ if (!packageJson.exports || !packageJson.exports[requiredExport]) {
168
+ fail(`package.json exports must include ${requiredExport}`);
169
+ }
170
+ }
171
+
172
+ const seen = new Set();
173
+ const statuses = new Set(["draft", "active", "superseded"]);
174
+ const kinds = new Set(["principle", "procedure"]);
175
+ const superseded = new Map();
176
+ for (const e of registry.entries) {
177
+ if (!Number.isInteger(e.number) || e.number < 1) fail(`bad number ${e.number}`);
178
+ if (seen.has(e.number)) fail(`duplicate number ${e.number}`);
179
+ seen.add(e.number);
180
+ if (e.id !== `KFD-${e.number}`) fail(`id ${e.id} does not match number ${e.number}`);
181
+ if (e.slug !== `kfd-${e.number}`) fail(`${e.id} slug must be kfd-${e.number}, not ${e.slug}`);
182
+ if (e.path !== `decisions/KFD-${e.number}.md`) {
183
+ fail(`${e.id} path must be decisions/KFD-${e.number}.md, not ${e.path}`);
184
+ }
185
+ if (!kinds.has(e.kind)) fail(`bad kind ${e.kind} on ${e.id}`);
186
+ if (!statuses.has(e.status)) fail(`bad status ${e.status} on ${e.id}`);
187
+ if (e.status === "superseded") {
188
+ if (!Array.isArray(e.supersededBy) || e.supersededBy.length === 0) {
189
+ fail(`${e.id} is superseded but does not declare supersededBy`);
190
+ } else {
191
+ superseded.set(e.id, e.supersededBy);
192
+ }
193
+ }
194
+ if (!existsSync(e.path)) fail(`missing document ${e.path} for ${e.id}`);
195
+ else {
196
+ const doc = readFileSync(e.path, "utf8");
197
+ if (!doc.startsWith(`# ${e.id}:`)) fail(`${e.path} heading does not open with "# ${e.id}:"`);
198
+ if (!doc.includes(`Status: ${e.status}`)) fail(`${e.path} status line does not say ${e.status}`);
199
+ if (!doc.includes(`Kind: ${e.kind}`)) fail(`${e.path} kind line does not say ${e.kind}`);
200
+ if (e.status === "superseded") {
201
+ for (const successor of superseded.get(e.id) ?? []) {
202
+ if (!doc.includes(successor)) fail(`${e.path} does not cite successor ${successor}`);
203
+ }
204
+ }
205
+ }
206
+ const standard = standardsMetadata.standards?.[e.slug];
207
+ if (!standard) fail(`standards metadata missing ${e.slug}`);
208
+ else {
209
+ requireFields(standard, standardsSchema.$defs?.standard?.required, `${e.id} standards metadata`);
210
+ if (standard.key !== e.slug) fail(`${e.id} standard key must be ${e.slug}`);
211
+ if (standard.id !== e.id) fail(`${e.id} standard id mismatch in standards metadata`);
212
+ if (standard.number !== e.number) fail(`${e.id} standard number mismatch in standards metadata`);
213
+ if (standard.label !== e.id) fail(`${e.id} standard label must be ${e.id}`);
214
+ if (standard.title !== e.title) fail(`${e.id} standard title must match registry title`);
215
+ if (standard.kind !== e.kind) fail(`${e.id} standard kind must match registry kind`);
216
+ if (standard.status !== e.status) fail(`${e.id} standard status must match registry status`);
217
+ if (standard.document?.path !== e.path) fail(`${e.id} standard document path must match registry path`);
218
+ if (standard.document?.url !== e.url) fail(`${e.id} standard document url must match registry url`);
219
+ if (!/^[0-9a-f]{64}$/.test(standard.document?.sha256 || "")) {
220
+ fail(`${e.id} standard document sha256 is required`);
221
+ } else if (existsSync(e.path) && standard.document.sha256 !== sha256File(e.path)) {
222
+ fail(`${e.id} standard document sha256 does not match ${e.path}`);
223
+ }
224
+ if (standard.metadataSchemaVersion !== standardsMetadata.metadataSchema?.version) {
225
+ fail(`${e.id} standard metadataSchemaVersion must match metadata schema version`);
226
+ }
227
+ if (standard.schemaIds?.metadata !== standardsMetadata.metadataSchema?.id) {
228
+ fail(`${e.id} standard schemaIds.metadata must match metadata schema id`);
229
+ }
230
+ if (standard.schemaPaths?.metadata !== standardsMetadata.metadataSchema?.path) {
231
+ fail(`${e.id} standard schemaPaths.metadata must match metadata schema path`);
232
+ }
233
+ for (const [name, schemaPath] of Object.entries(standard.schemaPaths ?? {})) {
234
+ if (!existsSync(schemaPath)) fail(`${e.id} schemaPaths.${name} points to missing ${schemaPath}`);
235
+ else if (name !== "metadata") {
236
+ const schemaDoc = JSON.parse(readFileSync(schemaPath, "utf8"));
237
+ if (standard.schemaIds?.[name] && schemaDoc.$id !== standard.schemaIds[name]) {
238
+ fail(`${e.id} schemaPaths.${name} $id must match schemaIds.${name}`);
239
+ }
240
+ }
241
+ }
242
+ for (const [name, iface] of Object.entries(standard.interfaces ?? {})) {
243
+ requireFields(
244
+ iface,
245
+ standardsSchema.$defs?.standard?.properties?.interfaces?.additionalProperties?.required,
246
+ `${e.id} interfaces.${name}`
247
+ );
248
+ if (iface.schemaId !== standard.schemaIds?.[name]) fail(`${e.id} interfaces.${name}.schemaId must match schemaIds.${name}`);
249
+ if (iface.schemaPath !== standard.schemaPaths?.[name]) fail(`${e.id} interfaces.${name}.schemaPath must match schemaPaths.${name}`);
250
+ if (!Number.isInteger(iface.schemaVersion) || iface.schemaVersion < 1) {
251
+ fail(`${e.id} interfaces.${name}.schemaVersion must be a positive integer`);
252
+ }
253
+ if (!iface.compatibilityRule) fail(`${e.id} interfaces.${name}.compatibilityRule is required`);
254
+ if (existsSync(iface.schemaPath)) {
255
+ const schemaDoc = JSON.parse(readFileSync(iface.schemaPath, "utf8"));
256
+ if (schemaDoc.properties?.schemaVersion?.const !== iface.schemaVersion) {
257
+ fail(`${e.id} interfaces.${name}.schemaVersion must match schema properties.schemaVersion.const`);
258
+ }
259
+ if (schemaDoc.properties?.contract?.const !== iface.contract) {
260
+ fail(`${e.id} interfaces.${name}.contract must match schema properties.contract.const`);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ for (const key of Object.keys(standardsMetadata.standards ?? {})) {
267
+ if (!registry.entries.some((e) => e.slug === key)) fail(`standards metadata contains unknown ${key}`);
268
+ }
269
+ const kfd1 = standardsMetadata.standards?.["kfd-1"];
270
+ if (kfd1?.schemaIds?.contractWorld !== "https://kfd.libkungfu.dev/schemas/kfd-1/contract-world.schema.json") {
271
+ fail("KFD-1 standards metadata must expose the canonical contractWorld schema URI");
272
+ }
273
+ if (kfd1?.schemaIds?.witness !== "https://kfd.libkungfu.dev/schemas/kfd-1/witness.schema.json") {
274
+ fail("KFD-1 standards metadata must expose the canonical witness schema URI");
275
+ }
276
+ for (const concept of ["factSource", "contractWorld", "weldedSurfaceRegister", "witness", "surfaceClass", "compatibilityImpact", "impactProjection"]) {
277
+ if (!kfd1?.concepts?.[concept]) fail(`KFD-1 standards metadata missing concept ${concept}`);
278
+ }
279
+ for (const iface of ["contractWorld", "witness"]) {
280
+ if (!kfd1?.interfaces?.[iface]) fail(`KFD-1 standards metadata missing interface ${iface}`);
281
+ }
282
+ const expectedKfd1SurfaceClasses = ["integration-time", "cross-time"];
283
+ const expectedKfd1ImpactClasses = ["breaking", "additive", "none", "unclassifiable"];
284
+ const kfd1ContractWorldSchema = JSON.parse(readFileSync("schemas/kfd-1/contract-world.schema.json", "utf8"));
285
+ const kfd1WitnessSchema = JSON.parse(readFileSync("schemas/kfd-1/witness.schema.json", "utf8"));
286
+ for (const [schemaName, schemaDoc] of [["contractWorld", kfd1ContractWorldSchema], ["witness", kfd1WitnessSchema]]) {
287
+ requireSameEnum(schemaDoc.$defs?.surfaceClass?.enum, expectedKfd1SurfaceClasses, `KFD-1 ${schemaName} surfaceClass`);
288
+ requireSameEnum(schemaDoc.$defs?.compatibilityImpact?.enum, expectedKfd1ImpactClasses, `KFD-1 ${schemaName} compatibilityImpact`);
289
+ for (const impact of expectedKfd1ImpactClasses) {
290
+ if (!schemaDoc.$defs?.impactProjection?.required?.includes(impact)) {
291
+ fail(`KFD-1 ${schemaName} impactProjection must require ${impact}`);
292
+ }
293
+ }
294
+ }
295
+ const kfd1SurfaceRegister = kfd1?.surfaceRegister;
296
+ if (kfd1SurfaceRegister?.factSource !== "standards.json#/standards/kfd-1/surfaceRegister") {
297
+ fail("KFD-1 surfaceRegister must declare standards.json as its fact source");
298
+ }
299
+ requireSameEnum(kfd1SurfaceRegister?.surfaceClasses, expectedKfd1SurfaceClasses, "KFD-1 surfaceRegister surfaceClasses");
300
+ requireSameEnum(kfd1SurfaceRegister?.compatibilityImpactClasses, expectedKfd1ImpactClasses, "KFD-1 surfaceRegister compatibilityImpactClasses");
301
+ if (!Array.isArray(kfd1SurfaceRegister?.surfaces) || kfd1SurfaceRegister.surfaces.length === 0) {
302
+ fail("KFD-1 surfaceRegister.surfaces[] is required");
303
+ } else {
304
+ const surfaceIds = new Set();
305
+ for (const [index, surface] of kfd1SurfaceRegister.surfaces.entries()) {
306
+ if (!surface.id) fail(`KFD-1 surfaceRegister.surfaces[${index}].id is required`);
307
+ else if (surfaceIds.has(surface.id)) fail(`KFD-1 surfaceRegister duplicate surface ${surface.id}`);
308
+ else surfaceIds.add(surface.id);
309
+ if (!expectedKfd1SurfaceClasses.includes(surface.class)) fail(`KFD-1 surfaceRegister ${surface.id} has invalid class`);
310
+ if (!Array.isArray(surface.classes) || !surface.classes.includes(surface.class)) {
311
+ fail(`KFD-1 surfaceRegister ${surface.id} classes[] must include class`);
312
+ }
313
+ if (!surface.description) fail(`KFD-1 surfaceRegister ${surface.id} description is required`);
314
+ if (!surface.sourcePath || !existsSync(surface.sourcePath)) fail(`KFD-1 surfaceRegister ${surface.id} sourcePath is missing`);
315
+ for (const impact of expectedKfd1ImpactClasses) {
316
+ if (!surface.impactProjection?.[impact]) fail(`KFD-1 surfaceRegister ${surface.id} missing impactProjection.${impact}`);
317
+ }
318
+ }
319
+ }
320
+ const kfd2 = standardsMetadata.standards?.["kfd-2"];
321
+ if (kfd2?.schemaIds?.trustTaxonomy !== "https://kfd.libkungfu.dev/schemas/kfd-2/trust-taxonomy.schema.json") {
322
+ fail("KFD-2 standards metadata must expose the canonical trustTaxonomy schema URI");
323
+ }
324
+ if (kfd2?.schemaIds?.trustClaims !== "https://kfd.libkungfu.dev/schemas/kfd-2/trust-claims.schema.json") {
325
+ fail("KFD-2 standards metadata must expose the canonical trustClaims schema URI");
326
+ }
327
+ if (kfd2?.schemaIds?.trustAssessment !== "https://kfd.libkungfu.dev/schemas/kfd-2/trust-assessment.schema.json") {
328
+ fail("KFD-2 standards metadata must expose the canonical trustAssessment schema URI");
329
+ }
330
+ if (kfd2?.schemaIds?.releaseClaims !== "https://kfd.libkungfu.dev/schemas/kfd-2/release-claims.schema.json") {
331
+ fail("KFD-2 standards metadata must expose the canonical releaseClaims schema URI");
332
+ }
333
+ if (kfd2?.schemaIds?.releaseTrustPassport !== "https://kfd.libkungfu.dev/schemas/kfd-2/release-trust-passport.schema.json") {
334
+ fail("KFD-2 standards metadata must expose the canonical releaseTrustPassport schema URI");
335
+ }
336
+ if (kfd2?.schemaPaths?.releaseClaims !== "schemas/kfd-2/release-claims.schema.json") {
337
+ fail("KFD-2 standards metadata must expose the releaseClaims schema path");
338
+ }
339
+ if (kfd2?.schemaPaths?.trustTaxonomy !== "schemas/kfd-2/trust-taxonomy.schema.json") {
340
+ fail("KFD-2 standards metadata must expose the trustTaxonomy schema path");
341
+ }
342
+ if (kfd2?.schemaPaths?.trustClaims !== "schemas/kfd-2/trust-claims.schema.json") {
343
+ fail("KFD-2 standards metadata must expose the trustClaims schema path");
344
+ }
345
+ if (kfd2?.schemaPaths?.trustAssessment !== "schemas/kfd-2/trust-assessment.schema.json") {
346
+ fail("KFD-2 standards metadata must expose the trustAssessment schema path");
347
+ }
348
+ if (kfd2?.schemaPaths?.releaseTrustPassport !== "schemas/kfd-2/release-trust-passport.schema.json") {
349
+ fail("KFD-2 standards metadata must expose the releaseTrustPassport schema path");
350
+ }
351
+ const kfd2TrustTaxonomySchema = JSON.parse(readFileSync("schemas/kfd-2/trust-taxonomy.schema.json", "utf8"));
352
+ const kfd2TrustClaimsSchema = JSON.parse(readFileSync("schemas/kfd-2/trust-claims.schema.json", "utf8"));
353
+ const kfd2TrustAssessmentSchema = JSON.parse(readFileSync("schemas/kfd-2/trust-assessment.schema.json", "utf8"));
354
+ const kfd2ClaimsSchema = JSON.parse(readFileSync("schemas/kfd-2/release-claims.schema.json", "utf8"));
355
+ const kfd2TrustPassportSchema = JSON.parse(readFileSync("schemas/kfd-2/release-trust-passport.schema.json", "utf8"));
356
+ if (kfd2TrustTaxonomySchema.properties?.contract?.const !== "kfd-2-trust-taxonomy") {
357
+ fail("KFD-2 trustTaxonomy schema must describe the kfd-2-trust-taxonomy contract");
358
+ }
359
+ if (kfd2TrustClaimsSchema.properties?.contract?.const !== "kfd-2-trust-claims") {
360
+ fail("KFD-2 trustClaims schema must describe the kfd-2-trust-claims contract");
361
+ }
362
+ if (kfd2TrustAssessmentSchema.properties?.contract?.const !== "kfd-2-trust-assessment") {
363
+ fail("KFD-2 trustAssessment schema must describe the kfd-2-trust-assessment contract");
364
+ }
365
+ if (kfd2ClaimsSchema.properties?.contract?.const !== "kfd-2-release-claims") {
366
+ fail("KFD-2 releaseClaims schema must describe the kfd-2-release-claims contract");
367
+ }
368
+ if (kfd2TrustPassportSchema.properties?.contract?.const !== "kfd-2-release-trust-passport") {
369
+ fail("KFD-2 releaseTrustPassport schema must describe the kfd-2-release-trust-passport contract");
370
+ }
371
+ const taxonomyMeta = kfd2TrustTaxonomySchema["x-kfd"];
372
+ if (taxonomyMeta?.extensionPolicy?.unknownValuePolicy !== "schema-validation-fail") {
373
+ fail("KFD-2 trust taxonomy must fail unknown values");
374
+ }
375
+ if (taxonomyMeta?.extensionPolicy?.standardAction !== "open-kfd-extension-issue") {
376
+ fail("KFD-2 trust taxonomy must tell agents to open a KFD extension issue for missing values");
377
+ }
378
+ if (taxonomyMeta?.extensionPolicy?.requestPath?.repository !== "https://github.com/kungfu-systems/kfd") {
379
+ fail("KFD-2 trust taxonomy extension path must target the KFD GitHub repository");
380
+ }
381
+ if (taxonomyMeta?.extensionPolicy?.requestPath?.kind !== "github-issue") {
382
+ fail("KFD-2 trust taxonomy extension path must be a GitHub issue");
383
+ }
384
+ for (const enumName of ["riskType", "trustImpact", "machineProvability", "agentAction"]) {
385
+ requireSameEnum(
386
+ kfd2TrustTaxonomySchema.$defs?.[enumName]?.enum,
387
+ taxonomyMeta?.allowedValues?.[enumName],
388
+ `KFD-2 trust taxonomy ${enumName}`
389
+ );
390
+ }
391
+ const residualRiskRef = "https://kfd.libkungfu.dev/schemas/kfd-2/trust-taxonomy.schema.json#/$defs/residualRisk";
392
+ const downgradeReasonRef = "https://kfd.libkungfu.dev/schemas/kfd-2/trust-taxonomy.schema.json#/$defs/downgradeReason";
393
+ const checkResidualRisk = (risk, label) => {
394
+ if (risk?.definedBy !== residualRiskRef) fail(`${label}.definedBy must reference the KFD-2 trust taxonomy residualRisk definition`);
395
+ for (const [field, enumName] of [
396
+ ["riskType", "riskType"],
397
+ ["trustImpact", "trustImpact"],
398
+ ["machineProvability", "machineProvability"],
399
+ ["agentAction", "agentAction"],
400
+ ]) {
401
+ if (!kfd2TrustTaxonomySchema.$defs?.[enumName]?.enum?.includes(risk?.[field])) {
402
+ fail(`${label}.${field} must be a KFD-2 trust taxonomy value`);
403
+ }
404
+ }
405
+ if (!risk?.reason) fail(`${label}.reason is required`);
406
+ if (!risk?.owner) fail(`${label}.owner is required`);
407
+ };
408
+ if (kfd2ClaimsSchema.$defs?.claim?.properties?.residualRisk?.items?.$ref !== residualRiskRef) {
409
+ fail("KFD-2 releaseClaims residualRisk must reference the KFD-2 trust taxonomy residualRisk definition");
410
+ }
411
+ if (kfd2TrustClaimsSchema.$defs?.claim?.properties?.residualRisk?.items?.$ref !== residualRiskRef) {
412
+ fail("KFD-2 trustClaims residualRisk must reference the KFD-2 trust taxonomy residualRisk definition");
413
+ }
414
+ if (kfd2TrustAssessmentSchema.$defs?.claimAssessment?.properties?.residualRisk?.items?.$ref !== residualRiskRef) {
415
+ fail("KFD-2 trustAssessment residualRisk must reference the KFD-2 trust taxonomy residualRisk definition");
416
+ }
417
+ if (kfd2TrustAssessmentSchema.properties?.downgradeReasons?.items?.$ref !== downgradeReasonRef) {
418
+ fail("KFD-2 trustAssessment downgradeReasons must reference the KFD-2 trust taxonomy downgradeReason definition");
419
+ }
420
+ if (kfd2TrustPassportSchema.$defs?.claimResult?.properties?.residualRisk?.items?.$ref !== residualRiskRef) {
421
+ fail("KFD-2 releaseTrustPassport claim residualRisk must reference the KFD-2 trust taxonomy residualRisk definition");
422
+ }
423
+ if (kfd2TrustPassportSchema.properties?.downgradeReasons?.items?.$ref !== downgradeReasonRef) {
424
+ fail("KFD-2 releaseTrustPassport downgradeReasons must reference the KFD-2 trust taxonomy downgradeReason definition");
425
+ }
426
+ for (const concept of ["facts", "trustClaim", "trustClaims", "trustAssessment", "claimSubject", "claimSubjectKind", "projection", "releaseClaim", "releaseClaims", "evidenceBinding", "auditBoundary", "residualRisk", "riskType", "trustImpact", "machineProvability", "agentAction", "extensionRequest", "releaseTrustPassport", "responsibilityState", "trust"]) {
427
+ if (!kfd2?.concepts?.[concept]) fail(`KFD-2 standards metadata missing concept ${concept}`);
428
+ }
429
+ for (const iface of ["trustTaxonomy", "trustClaims", "trustAssessment", "releaseClaims", "releaseTrustPassport"]) {
430
+ if (!kfd2?.interfaces?.[iface]) fail(`KFD-2 standards metadata missing interface ${iface}`);
431
+ }
432
+ const kfd3 = standardsMetadata.standards?.["kfd-3"];
433
+ if (kfd3?.schemaIds?.collaborationInterface !== "https://kfd.libkungfu.dev/schemas/kfd-3/collaboration-interface.schema.json") {
434
+ fail("KFD-3 standards metadata must expose the canonical collaborationInterface schema URI");
435
+ }
436
+ if (kfd3?.schemaIds?.witness !== "https://kfd.libkungfu.dev/schemas/kfd-3/witness.schema.json") {
437
+ fail("KFD-3 standards metadata must expose the canonical witness schema URI");
438
+ }
439
+ if (kfd3?.schemaPaths?.collaborationInterface !== "schemas/kfd-3/collaboration-interface.schema.json") {
440
+ fail("KFD-3 standards metadata must expose the collaborationInterface schema path");
441
+ }
442
+ if (kfd3?.schemaPaths?.witness !== "schemas/kfd-3/witness.schema.json") {
443
+ fail("KFD-3 standards metadata must expose the witness schema path");
444
+ }
445
+ const kfd3CollaborationSchema = JSON.parse(readFileSync("schemas/kfd-3/collaboration-interface.schema.json", "utf8"));
446
+ const kfd3WitnessSchema = JSON.parse(readFileSync("schemas/kfd-3/witness.schema.json", "utf8"));
447
+ if (kfd3CollaborationSchema.properties?.contract?.const !== "kfd-3-collaboration-interface") {
448
+ fail("KFD-3 collaborationInterface schema must describe the kfd-3-collaboration-interface contract");
449
+ }
450
+ if (kfd3WitnessSchema.properties?.contract?.const !== "kfd-3-witness") {
451
+ fail("KFD-3 witness schema must describe the kfd-3-witness contract");
452
+ }
453
+ if (kfd3WitnessSchema.properties?.residualRisk?.items?.$ref !== residualRiskRef) {
454
+ fail("KFD-3 witness residualRisk must reference the KFD-2 trust taxonomy residualRisk definition");
455
+ }
456
+ if (!kfd3CollaborationSchema.properties?.extensionRequests) {
457
+ fail("KFD-3 collaborationInterface schema must expose extensionRequests");
458
+ }
459
+ if (!kfd3CollaborationSchema.properties?.factSources) {
460
+ fail("KFD-3 collaborationInterface schema must expose factSources");
461
+ }
462
+ if (!kfd3CollaborationSchema.properties?.valueEvidence) {
463
+ fail("KFD-3 collaborationInterface schema must expose valueEvidence");
464
+ }
465
+ if (!kfd3CollaborationSchema.required?.includes("valueEvidence")) {
466
+ fail("KFD-3 collaborationInterface schema must require valueEvidence");
467
+ }
468
+ if (!kfd3CollaborationSchema.$defs?.valueEvidence?.properties?.trustAssessment) {
469
+ fail("KFD-3 collaborationInterface valueEvidence must support trustAssessment");
470
+ }
471
+ if (!kfd3WitnessSchema.properties?.evidence?.required?.includes("valueEvidence")) {
472
+ fail("KFD-3 witness evidence must require valueEvidence");
473
+ }
474
+ if (!kfd3WitnessSchema.properties?.evidence?.properties?.valueEvidence) {
475
+ fail("KFD-3 witness evidence must expose valueEvidence");
476
+ }
477
+ if (!kfd3CollaborationSchema.$defs?.extensionRequest?.properties?.requestPath?.properties?.kind?.enum?.includes("github-issue")) {
478
+ fail("KFD-3 extensionRequest.requestPath.kind must support github-issue");
479
+ }
480
+ for (const concept of ["trustedValueClaim", "valueEvidence", "trustAssessmentLink", "participant", "collaborationInterface", "minimalEntrypoint", "closure", "choicePath", "extensionRequest", "extensionPath"]) {
481
+ if (!kfd3?.concepts?.[concept]) fail(`KFD-3 standards metadata missing concept ${concept}`);
482
+ }
483
+ for (const iface of ["collaborationInterface", "witness"]) {
484
+ if (!kfd3?.interfaces?.[iface]) fail(`KFD-3 standards metadata missing interface ${iface}`);
485
+ }
486
+ const kfd4 = standardsMetadata.standards?.["kfd-4"];
487
+ if (kfd4?.schemaIds?.observerPerspective !== "https://kfd.libkungfu.dev/schemas/kfd-4/observer-perspective.schema.json") {
488
+ fail("KFD-4 standards metadata must expose the canonical observerPerspective schema URI");
489
+ }
490
+ if (kfd4?.schemaPaths?.observerPerspective !== "schemas/kfd-4/observer-perspective.schema.json") {
491
+ fail("KFD-4 standards metadata must expose the observerPerspective schema path");
492
+ }
493
+ const kfd4ObserverPerspectiveSchema = JSON.parse(readFileSync("schemas/kfd-4/observer-perspective.schema.json", "utf8"));
494
+ if (kfd4ObserverPerspectiveSchema.properties?.contract?.const !== "kfd-4-observer-perspective") {
495
+ fail("KFD-4 observerPerspective schema must describe the kfd-4-observer-perspective contract");
496
+ }
497
+ for (const concept of ["observer", "declaredPerspective", "observerPerspective", "acceptedFacts", "projectionPolicy", "causalDominance", "degradedEvidence", "timeline"]) {
498
+ if (!kfd4?.concepts?.[concept]) fail(`KFD-4 standards metadata missing concept ${concept}`);
499
+ }
500
+ if (!kfd4?.interfaces?.observerPerspective) fail("KFD-4 standards metadata missing interface observerPerspective");
501
+ for (const [id, successors] of superseded) {
502
+ for (const successor of successors) {
503
+ if (!registry.entries.some((e) => e.id === successor)) fail(`${id} cites missing successor ${successor}`);
504
+ }
505
+ }
506
+ const siteCommitments = new Map((siteBundle.homepage?.foundationTriad?.commitments ?? []).map((item) => [item.id, item]));
507
+ for (const id of ["KFD-1", "KFD-2", "KFD-3"]) {
508
+ if (!siteCommitments.has(id)) fail(`site bundle foundationTriad missing ${id}`);
509
+ }
510
+ for (const id of siteCommitments.keys()) {
511
+ if (!["KFD-1", "KFD-2", "KFD-3"].includes(id)) fail(`site bundle foundationTriad must not include practice guideline ${id}`);
512
+ }
513
+ const siteLayers = new Map((siteBundle.homepage?.foundationModel?.layers ?? []).map((item) => [item.decision, item]));
514
+ for (const id of ["KFD-1", "KFD-2", "KFD-3"]) {
515
+ if (!siteLayers.has(id)) fail(`site bundle foundationModel missing ${id}`);
516
+ }
517
+ for (const id of siteLayers.keys()) {
518
+ if (!["KFD-1", "KFD-2", "KFD-3"].includes(id)) fail(`site bundle foundationModel must not include practice guideline ${id}`);
519
+ }
520
+ const practiceGuidelines = new Map((siteBundle.homepage?.practiceGuidelines?.guidelines ?? []).map((item) => [item.decision, item]));
521
+ for (const e of registry.entries.filter((entry) => !["KFD-1", "KFD-2", "KFD-3"].includes(entry.id))) {
522
+ if (!practiceGuidelines.has(e.id)) fail(`site bundle practiceGuidelines missing ${e.id}`);
523
+ }
524
+ const boundary = siteBundle.renderingBoundary ?? {};
525
+ if (!Array.isArray(boundary.ownedByKfd) || !boundary.ownedByKfd.includes("homepage title and text")) {
526
+ fail("site bundle renderingBoundary.ownedByKfd must include homepage title and text");
527
+ }
528
+ if (!Array.isArray(boundary.ownedByKfd) || !boundary.ownedByKfd.includes("homepage section projection from README.md")) {
529
+ fail("site bundle renderingBoundary.ownedByKfd must include homepage section projection from README.md");
530
+ }
531
+ if (!Array.isArray(boundary.ownedBySite) || !boundary.ownedBySite.includes("CSS")) {
532
+ fail("site bundle renderingBoundary.ownedBySite must include CSS");
533
+ }
534
+ if (!kfd1Witness) {
535
+ fail(`missing KFD-1 release witness ${kfd1WitnessPath}`);
536
+ } else {
537
+ if (kfd1Witness.standard !== "kfd-1") fail("KFD-1 release witness standard must be kfd-1");
538
+ if (kfd1Witness.contractWorld?.schemaId !== "https://kfd.libkungfu.dev/schemas/kfd-1/contract-world.schema.json") {
539
+ fail("KFD-1 release witness contractWorld.schemaId must be canonical");
540
+ }
541
+ for (const field of ["canonicalPolicy", "registry"]) {
542
+ const entry = kfd1Witness[field];
543
+ if (!entry?.path || !entry?.sha256) fail(`KFD-1 release witness ${field} must include path and sha256`);
544
+ else if (!existsSync(entry.path)) fail(`KFD-1 release witness ${field} points to missing ${entry.path}`);
545
+ else if (entry.sha256 !== sha256File(entry.path)) fail(`KFD-1 release witness ${field}.sha256 does not match ${entry.path}`);
546
+ }
547
+ if (!Array.isArray(kfd1Witness.surfaces) || kfd1Witness.surfaces.length === 0) {
548
+ fail("KFD-1 release witness surfaces[] is required");
549
+ } else {
550
+ requireSameEnum(kfd1Witness.compatibilityImpactClasses, expectedKfd1ImpactClasses, "KFD-1 witness compatibilityImpactClasses");
551
+ const registeredSurfaceById = new Map((kfd1SurfaceRegister?.surfaces ?? []).map((surface) => [surface.id, surface]));
552
+ const witnessedSurfaceNames = new Set(kfd1Witness.surfaces.map((surface) => surface.name));
553
+ for (const requiredSurface of [
554
+ "readme",
555
+ "kfd-2-trust-taxonomy-schema",
556
+ "kfd-2-trust-claims-schema",
557
+ "kfd-2-trust-assessment-schema",
558
+ "kfd-site-bundle",
559
+ "kfd-doc-map",
560
+ "kfd-1-usage-doc",
561
+ "kfd-2-usage-doc",
562
+ "kfd-3-usage-doc",
563
+ "kfd-4-usage-doc",
564
+ "kfd-2-foundation-trust-claims",
565
+ "kfd-2-foundation-trust-assessment",
566
+ "release-impact-ledger",
567
+ "kfd-check-gate"
568
+ ]) {
569
+ if (!witnessedSurfaceNames.has(requiredSurface)) {
570
+ fail(`KFD-1 release witness must include self-proof surface ${requiredSurface}`);
571
+ }
572
+ }
573
+ for (const [index, surface] of kfd1Witness.surfaces.entries()) {
574
+ if (!surface.name) fail(`KFD-1 release witness surfaces[${index}].name is required`);
575
+ const registered = registeredSurfaceById.get(surface.name);
576
+ if (!registered) fail(`KFD-1 release witness surface ${surface.name || index} is not in standards surfaceRegister`);
577
+ else {
578
+ if (surface.class !== registered.class) fail(`KFD-1 release witness surface ${surface.name} class must match surfaceRegister`);
579
+ requireSameEnum(surface.classes, registered.classes, `KFD-1 witness ${surface.name} classes`);
580
+ if (surface.description !== registered.description) fail(`KFD-1 release witness surface ${surface.name} description must match surfaceRegister`);
581
+ if (surface.weldRationale !== registered.weldRationale) fail(`KFD-1 release witness surface ${surface.name} weldRationale must match surfaceRegister`);
582
+ for (const impact of expectedKfd1ImpactClasses) {
583
+ if (surface.impactProjection?.[impact] !== registered.impactProjection?.[impact]) {
584
+ fail(`KFD-1 release witness surface ${surface.name} impactProjection.${impact} must match surfaceRegister`);
585
+ }
586
+ }
587
+ }
588
+ if (!surface.artifactPath) fail(`KFD-1 release witness surfaces[${index}].artifactPath is required`);
589
+ else if (!existsSync(surface.artifactPath)) fail(`KFD-1 release witness surface ${surface.name || index} points to missing ${surface.artifactPath}`);
590
+ else {
591
+ const actual = sha256File(surface.artifactPath);
592
+ if (surface.expectedSha256 !== actual) {
593
+ fail(`KFD-1 release witness surface ${surface.name || index} expectedSha256 does not match ${surface.artifactPath}`);
594
+ }
595
+ if (surface.sourceSha256 && surface.sourceSha256 !== actual) {
596
+ fail(`KFD-1 release witness surface ${surface.name || index} sourceSha256 does not match ${surface.artifactPath}`);
597
+ }
598
+ }
599
+ }
600
+ }
601
+ }
602
+
603
+ const checkKfd2Pointer = (entry, label) => {
604
+ if (!entry?.path) {
605
+ fail(`${label} must include path`);
606
+ return;
607
+ }
608
+ if (!entry?.sha256) {
609
+ fail(`${label} must include sha256`);
610
+ return;
611
+ }
612
+ const filePath = hashablePath(entry.path);
613
+ if (!existsSync(filePath)) {
614
+ fail(`${label} points to missing ${filePath}`);
615
+ } else if (entry.sha256 !== sha256File(filePath)) {
616
+ fail(`${label}.sha256 does not match ${filePath}`);
617
+ }
618
+ };
619
+
620
+ if (!kfd2Claim) {
621
+ fail(`missing KFD-2 public release trust claim ${kfd2ClaimPath}`);
622
+ } else {
623
+ if (kfd2Claim.id !== "kfd-public-release-trust") fail("KFD-2 release trust claim id must be kfd-public-release-trust");
624
+ if (kfd2Claim.public !== true) fail("KFD-2 release trust claim must be public");
625
+ if (!kfd2Claim.claim) fail("KFD-2 release trust claim must include claim text");
626
+ for (const field of ["sourceBindings", "machineEvidence", "artifacts", "residualRisk"]) {
627
+ if (!Array.isArray(kfd2Claim[field])) fail(`KFD-2 release trust claim ${field} must be an array`);
628
+ }
629
+ if (!Array.isArray(kfd2Claim.sourceBindings) || kfd2Claim.sourceBindings.length === 0) {
630
+ fail("KFD-2 release trust claim sourceBindings[] is required");
631
+ } else {
632
+ kfd2Claim.sourceBindings.forEach((entry, index) => checkKfd2Pointer(entry, `KFD-2 release trust claim sourceBindings[${index}]`));
633
+ }
634
+ if (!Array.isArray(kfd2Claim.machineEvidence) || kfd2Claim.machineEvidence.length === 0) {
635
+ fail("KFD-2 release trust claim machineEvidence[] is required");
636
+ } else {
637
+ kfd2Claim.machineEvidence.forEach((entry, index) => checkKfd2Pointer(entry, `KFD-2 release trust claim machineEvidence[${index}]`));
638
+ }
639
+ if (!kfd2Claim.hashes || Object.keys(kfd2Claim.hashes).length === 0) fail("KFD-2 release trust claim hashes must be non-empty");
640
+ for (const entry of [...(kfd2Claim.sourceBindings ?? []), ...(kfd2Claim.machineEvidence ?? [])]) {
641
+ if (entry?.id && kfd2Claim.hashes?.[entry.id] !== entry.sha256) {
642
+ fail(`KFD-2 release trust claim hashes.${entry.id} must match its bound sha256`);
643
+ }
644
+ }
645
+ if (!Array.isArray(kfd2Claim.artifacts) || kfd2Claim.artifacts.length === 0) {
646
+ fail("KFD-2 release trust claim artifacts[] is required");
647
+ } else {
648
+ kfd2Claim.artifacts.forEach((entry, index) => checkKfd2Pointer(entry, `KFD-2 release trust claim artifacts[${index}]`));
649
+ }
650
+ if (kfd2Claim.verification?.result !== "passed") fail("KFD-2 release trust claim verification.result must be passed");
651
+ if (kfd2Claim.verification?.command !== "node scripts/check.mjs") fail("KFD-2 release trust claim verification.command must be node scripts/check.mjs");
652
+ if (!kfd2Claim.auditBoundary?.scope) fail("KFD-2 release trust claim auditBoundary.scope is required");
653
+ if (kfd2Claim.auditBoundary?.enumerability !== "closed-world") fail("KFD-2 release trust claim auditBoundary.enumerability must be closed-world");
654
+ if (!kfd2Claim.responsibility?.owner) fail("KFD-2 release trust claim responsibility.owner is required");
655
+ if (!Array.isArray(kfd2Claim.residualRisk)) fail("KFD-2 release trust claim residualRisk must be an array");
656
+ }
657
+
658
+ const checkKfd2AssessmentPointer = (entry, label) => {
659
+ if (!entry?.path) fail(`${label}.path is required`);
660
+ else {
661
+ const filePath = hashablePath(entry.path);
662
+ if (!existsSync(filePath)) fail(`${label} points to missing ${filePath}`);
663
+ else if (entry.sha256 && entry.sha256 !== sha256File(filePath)) fail(`${label}.sha256 does not match ${filePath}`);
664
+ }
665
+ };
666
+
667
+ if (!kfd2TrustClaims) {
668
+ fail(`missing KFD-2 generic trust claims ${kfd2TrustClaimsPath}`);
669
+ } else {
670
+ if (kfd2TrustClaims.contract !== "kfd-2-trust-claims") fail("KFD-2 generic trust claims contract must be kfd-2-trust-claims");
671
+ if (kfd2TrustClaims.standard !== "kfd-2") fail("KFD-2 generic trust claims standard must be kfd-2");
672
+ if (kfd2TrustClaims.projection?.kind !== "generic") fail("KFD-2 generic trust claims projection.kind must be generic");
673
+ const claimsById = new Map((kfd2TrustClaims.claims ?? []).map((claim) => [claim.id, claim]));
674
+ for (const requiredClaim of ["kfd-1-contract-world-trust", "kfd-3-collaboration-interface-trust", "kfd-4-observer-perspective-trust"]) {
675
+ if (!claimsById.has(requiredClaim)) fail(`KFD-2 generic trust claims missing ${requiredClaim}`);
676
+ }
677
+ const expectedSubjectKinds = new Map([
678
+ ["kfd-1-contract-world-trust", "contract-world"],
679
+ ["kfd-3-collaboration-interface-trust", "collaboration-interface"],
680
+ ["kfd-4-observer-perspective-trust", "observer-perspective"],
681
+ ]);
682
+ for (const [claimId, expectedKind] of expectedSubjectKinds.entries()) {
683
+ const claim = claimsById.get(claimId);
684
+ if (claim?.subject?.kind !== expectedKind) fail(`KFD-2 generic trust claim ${claimId} subject.kind must be ${expectedKind}`);
685
+ if (!Array.isArray(claim?.facts) || claim.facts.length === 0) fail(`KFD-2 generic trust claim ${claimId} facts[] is required`);
686
+ else claim.facts.forEach((entry, index) => checkKfd2AssessmentPointer(entry, `KFD-2 generic trust claim ${claimId}.facts[${index}]`));
687
+ if (!Array.isArray(claim?.evidence) || claim.evidence.length === 0) fail(`KFD-2 generic trust claim ${claimId} evidence[] is required`);
688
+ for (const [index, entry] of (claim?.evidence ?? []).entries()) {
689
+ if (!kfd2TrustTaxonomySchema.$defs?.machineProvability?.enum?.includes(entry.machineProvability)) {
690
+ fail(`KFD-2 generic trust claim ${claimId}.evidence[${index}].machineProvability must be a KFD-2 value`);
691
+ }
692
+ if (entry.pointer) checkKfd2AssessmentPointer(entry.pointer, `KFD-2 generic trust claim ${claimId}.evidence[${index}].pointer`);
693
+ }
694
+ for (const [index, risk] of (claim?.residualRisk ?? []).entries()) {
695
+ checkResidualRisk(risk, `KFD-2 generic trust claim ${claimId}.residualRisk[${index}]`);
696
+ }
697
+ }
698
+ }
699
+
700
+ if (!kfd2TrustAssessment) {
701
+ fail(`missing KFD-2 generic trust assessment ${kfd2TrustAssessmentPath}`);
702
+ } else {
703
+ if (kfd2TrustAssessment.contract !== "kfd-2-trust-assessment") fail("KFD-2 generic trust assessment contract must be kfd-2-trust-assessment");
704
+ if (kfd2TrustAssessment.standard !== "kfd-2") fail("KFD-2 generic trust assessment standard must be kfd-2");
705
+ if (kfd2TrustAssessment.assessedClaims?.schemaId !== "https://kfd.libkungfu.dev/schemas/kfd-2/trust-claims.schema.json") {
706
+ fail("KFD-2 generic trust assessment assessedClaims.schemaId must be canonical");
707
+ }
708
+ if (kfd2TrustAssessment.assessedClaims?.path !== kfd2TrustClaimsPath) {
709
+ fail("KFD-2 generic trust assessment assessedClaims.path must point to generic trust claims");
710
+ }
711
+ if (kfd2TrustAssessment.assessedClaims?.digest !== `sha256:${sha256File(kfd2TrustClaimsPath)}`) {
712
+ fail("KFD-2 generic trust assessment assessedClaims.digest must match generic trust claims");
713
+ }
714
+ if (kfd2TrustAssessment.result !== "warning") fail("KFD-2 generic trust assessment result must be warning because KFD-3 declares semantic residual risk");
715
+ const assessmentsByClaim = new Map((kfd2TrustAssessment.assessments ?? []).map((entry) => [entry.claimId, entry]));
716
+ for (const requiredClaim of ["kfd-1-contract-world-trust", "kfd-3-collaboration-interface-trust", "kfd-4-observer-perspective-trust"]) {
717
+ if (!assessmentsByClaim.has(requiredClaim)) fail(`KFD-2 generic trust assessment missing claim ${requiredClaim}`);
718
+ }
719
+ if (assessmentsByClaim.get("kfd-3-collaboration-interface-trust")?.result !== "warning") {
720
+ fail("KFD-2 generic trust assessment must downgrade KFD-3 to warning");
721
+ }
722
+ for (const [index, reason] of (kfd2TrustAssessment.downgradeReasons ?? []).entries()) {
723
+ if (!kfd2TrustTaxonomySchema.$defs?.riskType?.enum?.includes(reason.riskType)) fail(`KFD-2 generic trust assessment downgradeReasons[${index}].riskType must be a KFD-2 value`);
724
+ if (!kfd2TrustTaxonomySchema.$defs?.trustImpact?.enum?.includes(reason.trustImpact)) fail(`KFD-2 generic trust assessment downgradeReasons[${index}].trustImpact must be a KFD-2 value`);
725
+ if (reason.agentAction && !kfd2TrustTaxonomySchema.$defs?.agentAction?.enum?.includes(reason.agentAction)) fail(`KFD-2 generic trust assessment downgradeReasons[${index}].agentAction must be a KFD-2 value`);
726
+ }
727
+ for (const [index, entry] of (kfd2TrustAssessment.assessments ?? []).entries()) {
728
+ if (!["pass", "warning", "fail", "unverifiable"].includes(entry.result)) fail(`KFD-2 generic trust assessment assessments[${index}].result is invalid`);
729
+ for (const [riskIndex, risk] of (entry.residualRisk ?? []).entries()) {
730
+ checkResidualRisk(risk, `KFD-2 generic trust assessment assessments[${index}].residualRisk[${riskIndex}]`);
731
+ }
732
+ }
733
+ }
734
+
735
+ const kfd3SurfaceGroups = ["docs", "schemas", "standardsMetadata", "packageExports", "siteConsumptionContracts"];
736
+ const kfd3SurfaceIds = (witness) => {
737
+ const ids = new Set();
738
+ for (const surface of witness?.surfaces ?? []) {
739
+ if (surface?.id) ids.add(surface.id);
740
+ }
741
+ for (const group of kfd3SurfaceGroups) {
742
+ for (const surface of witness?.[group] ?? []) {
743
+ if (surface?.id) ids.add(surface.id);
744
+ }
745
+ }
746
+ return ids;
747
+ };
748
+ const checkPointer = (entry, label, field = "path") => {
749
+ if (!entry?.[field]) {
750
+ fail(`${label} must include ${field}`);
751
+ return;
752
+ }
753
+ const filePath = hashablePath(entry[field]);
754
+ if (filePath.includes("*")) {
755
+ return;
756
+ }
757
+ if (!existsSync(filePath)) {
758
+ fail(`${label} points to missing ${filePath}`);
759
+ } else if (entry.sha256 && entry.sha256 !== sha256File(filePath)) {
760
+ fail(`${label}.sha256 does not match ${filePath}`);
761
+ }
762
+ };
763
+ const checkGroupedSurfaces = (witness, label) => {
764
+ for (const group of kfd3SurfaceGroups) {
765
+ if (!Array.isArray(witness?.[group]) || witness[group].length === 0) {
766
+ fail(`${label}.${group} must include at least one surface`);
767
+ continue;
768
+ }
769
+ for (const [index, surface] of witness[group].entries()) {
770
+ if (!surface?.id) fail(`${label}.${group}[${index}].id is required`);
771
+ checkPointer(surface, `${label}.${group}[${index}]`, "sourcePath");
772
+ }
773
+ }
774
+ };
775
+
776
+ if (!kfd3Interface) {
777
+ fail(`missing KFD-3 collaboration interface ${kfd3InterfacePath}`);
778
+ } else {
779
+ if (kfd3Interface.contract !== "kfd-3-collaboration-interface") fail("KFD-3 collaboration interface contract must be kfd-3-collaboration-interface");
780
+ if (kfd3Interface.standard !== "kfd-3") fail("KFD-3 collaboration interface standard must be kfd-3");
781
+ if (!Array.isArray(kfd3Interface.factSources) || kfd3Interface.factSources.length === 0) fail("KFD-3 collaboration interface factSources[] is required");
782
+ if (!kfd3Interface.factSources.some((entry) =>
783
+ entry.id === "public-kfd-fact-source" &&
784
+ entry.kind === "git-repository" &&
785
+ entry.host === "github" &&
786
+ entry.repository === "kungfu-systems/kfd" &&
787
+ entry.url === "https://github.com/kungfu-systems/kfd" &&
788
+ entry.loadBearingCoordinate === "commit-addressed repository contents" &&
789
+ entry.stableRenderedIndex === "https://kfd.libkungfu.dev" &&
790
+ entry.canonicalPaths?.includes("decisions/KFD-N.md") &&
791
+ entry.canonicalPaths?.includes("registry.json") &&
792
+ entry.canonicalPaths?.includes("standards.json")
793
+ )) {
794
+ fail("KFD-3 collaboration interface must declare the public KFD GitHub fact source");
795
+ }
796
+ if (!Array.isArray(kfd3Interface.participants) || kfd3Interface.participants.length === 0) fail("KFD-3 collaboration interface participants[] is required");
797
+ if (!Array.isArray(kfd3Interface.minimalEntrypoints) || kfd3Interface.minimalEntrypoints.length === 0) fail("KFD-3 collaboration interface minimalEntrypoints[] is required");
798
+ if (!kfd3Interface.minimalEntrypoints.some((entry) => entry.id === "official-status-and-trademarks" && entry.surface === "TRADEMARKS.md")) {
799
+ fail("KFD-3 collaboration interface must expose TRADEMARKS.md as an official-status-and-trademarks entrypoint");
800
+ }
801
+ if (!Array.isArray(kfd3Interface.surfaces) || kfd3Interface.surfaces.length === 0) fail("KFD-3 collaboration interface surfaces[] is required");
802
+ if (!kfd3Interface.surfaces.some((entry) => entry.id === "official-status-and-trademarks" && entry.discoverability?.path === "TRADEMARKS.md")) {
803
+ fail("KFD-3 collaboration interface must expose TRADEMARKS.md as a participant-facing surface");
804
+ }
805
+ if (!Array.isArray(kfd3Interface.valueEvidence) || kfd3Interface.valueEvidence.length === 0) {
806
+ fail("KFD-3 collaboration interface valueEvidence[] is required");
807
+ } else {
808
+ for (const [index, entry] of kfd3Interface.valueEvidence.entries()) {
809
+ if (!entry.id) fail(`KFD-3 collaboration interface valueEvidence[${index}].id is required`);
810
+ if (!entry.claim) fail(`KFD-3 collaboration interface valueEvidence[${index}].claim is required`);
811
+ if (!Array.isArray(entry.participants) || entry.participants.length === 0) fail(`KFD-3 collaboration interface valueEvidence[${index}].participants[] is required`);
812
+ if (!Array.isArray(entry.facts) || entry.facts.length === 0) fail(`KFD-3 collaboration interface valueEvidence[${index}].facts[] is required`);
813
+ if (!Array.isArray(entry.evidence) || entry.evidence.length === 0) fail(`KFD-3 collaboration interface valueEvidence[${index}].evidence[] is required`);
814
+ for (const [factIndex, fact] of (entry.facts ?? []).entries()) {
815
+ checkPointer(fact, `KFD-3 collaboration interface valueEvidence[${index}].facts[${factIndex}]`);
816
+ }
817
+ for (const [evidenceIndex, evidenceEntry] of (entry.evidence ?? []).entries()) {
818
+ checkPointer(evidenceEntry, `KFD-3 collaboration interface valueEvidence[${index}].evidence[${evidenceIndex}]`);
819
+ }
820
+ if (entry.trustAssessment) {
821
+ checkPointer(entry.trustAssessment, `KFD-3 collaboration interface valueEvidence[${index}].trustAssessment`);
822
+ }
823
+ for (const [riskIndex, risk] of (entry.residualRisk ?? []).entries()) {
824
+ checkResidualRisk(risk, `KFD-3 collaboration interface valueEvidence[${index}].residualRisk[${riskIndex}]`);
825
+ }
826
+ }
827
+ if (!kfd3Interface.valueEvidence.some((entry) => entry.trustAssessment?.path === kfd2TrustAssessmentPath)) {
828
+ fail("KFD-3 collaboration interface valueEvidence must link to the KFD-2 generic trust assessment");
829
+ }
830
+ }
831
+ if (!Array.isArray(kfd3Interface.extensionRequests) || kfd3Interface.extensionRequests.length === 0) fail("KFD-3 collaboration interface extensionRequests[] is required");
832
+ if (!kfd3Interface.extensionRequests.some((entry) => entry.id === "kfd-2-trust-taxonomy-extension" && entry.requestPath?.kind === "github-issue" && String(entry.requestPath?.target || "").startsWith("https://github.com/kungfu-systems/kfd/issues/new"))) {
833
+ fail("KFD-3 collaboration interface must declare the KFD-2 trust taxonomy GitHub issue extension path");
834
+ }
835
+ if (kfd3Interface.closure?.classificationMode !== "closed-world") fail("KFD-3 collaboration interface closure must be closed-world");
836
+ if (kfd3Interface.closure?.unclassifiedEntrypointsPolicy !== "fail") fail("KFD-3 collaboration interface unclassified entrypoint policy must fail");
837
+ }
838
+
839
+ const kfd3InterfaceDigest = existsSync(kfd3InterfacePath) ? `sha256:${sha256File(kfd3InterfacePath)}` : "";
840
+ if (!kfd3PrebuildWitness) {
841
+ fail(`missing KFD-3 pre-build witness ${kfd3PrebuildWitnessPath}`);
842
+ } else {
843
+ if (kfd3PrebuildWitness.contract !== "kungfu-buildchain-kfd-3-collaboration-interface-prebuild-witness") {
844
+ fail("KFD-3 pre-build witness contract must match Buildchain KFD-3 prebuild witness");
845
+ }
846
+ if (kfd3PrebuildWitness.standard !== "kfd-3") fail("KFD-3 pre-build witness standard must be kfd-3");
847
+ if (kfd3PrebuildWitness.sourceRegistry?.path !== kfd3InterfacePath) fail("KFD-3 pre-build witness sourceRegistry.path must point to the collaboration interface");
848
+ if (kfd3PrebuildWitness.sourceRegistry?.sha256 !== sha256File(kfd3InterfacePath)) fail("KFD-3 pre-build witness sourceRegistry.sha256 does not match the collaboration interface");
849
+ if (kfd3PrebuildWitness.collaborationInterfaceDigest !== kfd3InterfaceDigest) fail("KFD-3 pre-build witness collaborationInterfaceDigest does not match the collaboration interface");
850
+ for (const [index, risk] of (kfd3PrebuildWitness.residualRisk ?? []).entries()) {
851
+ checkResidualRisk(risk, `KFD-3 pre-build witness residualRisk[${index}]`);
852
+ }
853
+ for (const [index, risk] of (kfd3PrebuildWitness.auditBoundary?.nonExhaustivelyEnumerableSurfaces ?? []).entries()) {
854
+ checkResidualRisk(risk, `KFD-3 pre-build witness auditBoundary.nonExhaustivelyEnumerableSurfaces[${index}]`);
855
+ }
856
+ checkGroupedSurfaces(kfd3PrebuildWitness, "KFD-3 pre-build witness");
857
+ }
858
+ if (!kfd3ArtifactWitness) {
859
+ fail(`missing KFD-3 artifact witness ${kfd3ArtifactWitnessPath}`);
860
+ } else {
861
+ if (kfd3ArtifactWitness.contract !== "kfd-3-witness") fail("KFD-3 artifact witness must also satisfy the KFD-3 witness contract");
862
+ if (kfd3ArtifactWitness.standard !== "kfd-3") fail("KFD-3 artifact witness standard must be kfd-3");
863
+ if (kfd3ArtifactWitness.collaborationInterface?.schemaId !== "https://kfd.libkungfu.dev/schemas/kfd-3/collaboration-interface.schema.json") {
864
+ fail("KFD-3 artifact witness collaborationInterface.schemaId must be canonical");
865
+ }
866
+ if (kfd3ArtifactWitness.collaborationInterface?.digest !== kfd3InterfaceDigest) {
867
+ fail("KFD-3 artifact witness collaborationInterface.digest does not match the collaboration interface");
868
+ }
869
+ if (kfd3ArtifactWitness.sourceRegistry?.path !== kfd3InterfacePath) fail("KFD-3 artifact witness sourceRegistry.path must point to the collaboration interface");
870
+ if (kfd3ArtifactWitness.sourceRegistry?.sha256 !== sha256File(kfd3InterfacePath)) fail("KFD-3 artifact witness sourceRegistry.sha256 does not match the collaboration interface");
871
+ for (const [index, risk] of (kfd3ArtifactWitness.residualRisk ?? []).entries()) {
872
+ checkResidualRisk(risk, `KFD-3 artifact witness residualRisk[${index}]`);
873
+ }
874
+ if (!Array.isArray(kfd3ArtifactWitness.evidence?.valueEvidence) || kfd3ArtifactWitness.evidence.valueEvidence.length === 0) {
875
+ fail("KFD-3 artifact witness evidence.valueEvidence[] is required");
876
+ }
877
+ if (kfd3ArtifactWitness.closure?.classificationMode !== "closed-world") fail("KFD-3 artifact witness closure must be closed-world");
878
+ if (!Array.isArray(kfd3ArtifactWitness.closure?.unclassifiedEntrypoints) || kfd3ArtifactWitness.closure.unclassifiedEntrypoints.length !== 0) {
879
+ fail("KFD-3 artifact witness must have zero unclassifiedEntrypoints");
880
+ }
881
+ for (const [section, entries] of Object.entries(kfd3ArtifactWitness.evidence ?? {})) {
882
+ if (Array.isArray(entries)) {
883
+ for (const [index, entry] of entries.entries()) {
884
+ checkPointer(entry, `KFD-3 artifact witness evidence.${section}[${index}]`);
885
+ }
886
+ }
887
+ }
888
+ checkGroupedSurfaces(kfd3ArtifactWitness, "KFD-3 artifact witness");
889
+ }
890
+ if (kfd3PrebuildWitness && kfd3ArtifactWitness) {
891
+ const declared = kfd3SurfaceIds(kfd3PrebuildWitness);
892
+ const exposed = kfd3SurfaceIds(kfd3ArtifactWitness);
893
+ for (const id of declared) {
894
+ if (!exposed.has(id)) fail(`KFD-3 artifact witness missing declared surface ${id}`);
895
+ }
896
+ for (const id of exposed) {
897
+ if (!declared.has(id)) fail(`KFD-3 artifact witness exposes undeclared surface ${id}`);
898
+ }
899
+ }
900
+
901
+ const impactLevels = new Set(["patch", "minor", "major"]);
902
+ const requiredSurfaces = new Set(["kfd-content", "kfd-registry-schema", "kfd-standards-metadata", "kfd-package-structure", "kfd-2-public-release-trust-claim", "kfd-3-trusted-value-evidence", "kfd-site-decision-usage-pages"]);
903
+
904
+ if (releaseImpact.schemaVersion !== 1) fail(`unsupported release-impact schemaVersion ${releaseImpact.schemaVersion}`);
905
+ if (releaseImpact.contract !== "kungfu-buildchain-impact") fail(`unexpected release-impact contract ${releaseImpact.contract}`);
906
+ if (!releaseImpact.versionImpact || !impactLevels.has(releaseImpact.versionImpact.final)) {
907
+ fail("release-impact versionImpact.final must be patch, minor, or major");
908
+ }
909
+ if (!releaseImpact.versionImpact?.rationale) {
910
+ fail("release-impact versionImpact.rationale is required");
911
+ }
912
+ if (!Array.isArray(releaseImpact.surfaceImpacts) || releaseImpact.surfaceImpacts.length === 0) {
913
+ fail("release-impact surfaceImpacts[] is required");
914
+ } else {
915
+ const seenSurfaces = new Set();
916
+ for (const [index, surface] of releaseImpact.surfaceImpacts.entries()) {
917
+ if (!surface.id) fail(`release-impact surfaceImpacts[${index}].id is required`);
918
+ else seenSurfaces.add(surface.id);
919
+ if (!impactLevels.has(surface.impact)) {
920
+ fail(`release-impact surfaceImpacts[${index}].impact must be patch, minor, or major`);
921
+ }
922
+ if (!surface.rationale) fail(`release-impact surfaceImpacts[${index}].rationale is required`);
923
+ }
924
+ for (const surfaceId of requiredSurfaces) {
925
+ if (!seenSurfaces.has(surfaceId)) fail(`release-impact missing surface ${surfaceId}`);
926
+ }
927
+ }
928
+
929
+ if (process.exitCode) process.exit(process.exitCode);
930
+ console.log(`check: ${registry.entries.length} entries ok; release impact ok`);