@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
@@ -266,12 +266,12 @@ function normalizeKfdSupport(component = {}) {
266
266
  const support = component.kfd && typeof component.kfd === "object" && !Array.isArray(component.kfd)
267
267
  ? { ...component.kfd }
268
268
  : {};
269
- for (const [key, aliases] of Object.entries({
270
- kfd1: ["kfd1", "kfd_1", "kfd-1"],
271
- kfd2: ["kfd2", "kfd_2", "kfd-2"],
272
- kfd3: ["kfd3", "kfd_3", "kfd-3"],
273
- kfd4: ["kfd4", "kfd_4", "kfd-4"],
274
- })) {
269
+ for (const [key, aliases] of Object.entries(Object.fromEntries(
270
+ Array.from({ length: 13 }, (_, index) => {
271
+ const number = index + 1;
272
+ return [`kfd${number}`, [`kfd${number}`, `kfd_${number}`, `kfd-${number}`]];
273
+ }),
274
+ ))) {
275
275
  const found = aliases.map((alias) => component[alias] ?? support[alias]).find((value) => value !== undefined);
276
276
  support[key] = found === undefined ? support[key] || "declared" : String(found);
277
277
  }
@@ -940,7 +940,16 @@ export function collectKfdStatus({ cwd = process.cwd() } = {}) {
940
940
  "kfd-1": ["schema", "witness", "gate", "verify"],
941
941
  "kfd-2": ["schema", "taxonomy", "claims", "product-claims", "trust-claims", "trust-assessment", "upstream"],
942
942
  "kfd-3": ["schema", "detect", "register", "audit", "witness", "query", "aggregate"],
943
- "kfd-4": ["schema"],
943
+ "kfd-4": ["schema", "product-gate", "verify", "support-projection"],
944
+ "kfd-5": ["schema", "product-gate", "verify", "support-projection"],
945
+ "kfd-6": ["schema", "unsupported-claim-barrier"],
946
+ "kfd-7": ["schema", "product-gate", "verify", "support-projection"],
947
+ "kfd-8": ["schema", "draft-claim-barrier"],
948
+ "kfd-9": ["schema", "draft-claim-barrier"],
949
+ "kfd-10": ["schema", "draft-claim-barrier"],
950
+ "kfd-11": ["schema", "draft-claim-barrier"],
951
+ "kfd-12": ["schema", "draft-claim-barrier"],
952
+ "kfd-13": ["schema", "draft-claim-barrier"],
944
953
  },
945
954
  paths: pathStatus,
946
955
  };
@@ -20,6 +20,9 @@ const KIND_ALIASES = Object.freeze({
20
20
  command: "cli",
21
21
  binary: "binary",
22
22
  "standalone-binary": "binary",
23
+ app: "binary",
24
+ appimage: "binary",
25
+ installer: "binary",
23
26
  docs: "documentation",
24
27
  documentation: "documentation",
25
28
  site: "site-bundle",
@@ -602,6 +605,100 @@ export function auditKfd3Surfaces({
602
605
  };
603
606
  }
604
607
 
608
+ function auditProductDeclaredKfd3Surfaces({ cwd, registry }) {
609
+ const issues = [];
610
+ const ids = new Set();
611
+ for (const [index, entry] of registry.surfaces.entries()) {
612
+ const label = `surfaces[${index}]`;
613
+ if (!entry.id || ids.has(entry.id)) {
614
+ issues.push({
615
+ level: "error",
616
+ code: "declared-surface-id",
617
+ surfaceId: entry.id || "",
618
+ message: `${label}.id must be non-empty and unique`,
619
+ });
620
+ }
621
+ ids.add(entry.id);
622
+ for (const field of ["kind", "name", "sourcePath"]) {
623
+ if (!String(entry[field] || "").trim()) {
624
+ issues.push({
625
+ level: "error",
626
+ code: "declared-surface-field",
627
+ surfaceId: entry.id || "",
628
+ message: `${label}.${field} is required`,
629
+ });
630
+ }
631
+ }
632
+ const evidencePath = String(entry.evidencePath || entry.artifactPath || "").trim();
633
+ if (!evidencePath) {
634
+ issues.push({
635
+ level: "error",
636
+ code: "declared-surface-evidence",
637
+ surfaceId: entry.id || "",
638
+ message: `${label} must bind evidencePath or artifactPath`,
639
+ });
640
+ }
641
+ for (const [field, relativePath] of [
642
+ ["sourcePath", entry.sourcePath],
643
+ ["evidencePath", evidencePath],
644
+ ]) {
645
+ const normalized = String(relativePath || "").replaceAll("\\", "/");
646
+ const filePath = path.resolve(cwd, normalized);
647
+ const relative = path.relative(path.resolve(cwd), filePath);
648
+ if (
649
+ !normalized ||
650
+ path.isAbsolute(normalized) ||
651
+ relative.startsWith("..") ||
652
+ path.isAbsolute(relative) ||
653
+ !fs.existsSync(filePath)
654
+ ) {
655
+ issues.push({
656
+ level: "error",
657
+ code: "declared-surface-evidence-missing",
658
+ surfaceId: entry.id || "",
659
+ message: `${label}.${field} must resolve inside the product repository`,
660
+ });
661
+ }
662
+ }
663
+ }
664
+ const ok = issues.length === 0;
665
+ return {
666
+ schemaVersion: 1,
667
+ contract: KFD3_SURFACE_AUDIT_CONTRACT,
668
+ ok,
669
+ status: ok ? "passed" : "failed",
670
+ registryPath: registry.registryPath,
671
+ detection: {
672
+ schemaVersion: 1,
673
+ contract: KFD3_SURFACE_DETECTION_CONTRACT,
674
+ cwd: path.resolve(cwd),
675
+ artifactPath: "",
676
+ detectedAt: new Date().toISOString(),
677
+ kinds: [...new Set(registry.surfaces.map((entry) => entry.kind))].sort(),
678
+ summary: {
679
+ surfaceCount: registry.surfaces.length,
680
+ byKind: Object.fromEntries(
681
+ [...new Set(registry.surfaces.map((entry) => entry.kind))]
682
+ .sort()
683
+ .map((kind) => [kind, registry.surfaces.filter((entry) => entry.kind === kind).length]),
684
+ ),
685
+ },
686
+ surfaces: registry.surfaces,
687
+ },
688
+ registry,
689
+ summary: {
690
+ detected: registry.surfaces.length,
691
+ declared: registry.surfaces.length,
692
+ enforced: registry.surfaces.filter((entry) => entry.state === "enforced" || entry.enforcement === "enforced").length,
693
+ detectedButUnregistered: 0,
694
+ declaredButMissing: issues.length,
695
+ },
696
+ issues,
697
+ verificationMode: "product-declared-registry",
698
+ verifier: String(registry.policy?.customSurfaceDetection || ""),
699
+ };
700
+ }
701
+
605
702
  export function createKfd3SurfaceWitness({
606
703
  cwd = process.cwd(),
607
704
  registryPath = "",
@@ -766,7 +863,13 @@ export async function queryKfd3Capabilities({
766
863
  const effectiveRegistryPath = resolveRegistryPath(cwd, registryPath);
767
864
  const registry = readKfd3SurfaceRegistry({ cwd, registryPath: effectiveRegistryPath });
768
865
  const hasRegistry = fs.existsSync(path.resolve(cwd, effectiveRegistryPath));
769
- const audit = hasRegistry ? auditKfd3Surfaces({ cwd, registryPath: effectiveRegistryPath, artifactPath }) : null;
866
+ const audit = hasRegistry
867
+ ? (
868
+ registry.policy?.customSurfaceDetection
869
+ ? auditProductDeclaredKfd3Surfaces({ cwd, registry })
870
+ : auditKfd3Surfaces({ cwd, registryPath: effectiveRegistryPath, artifactPath })
871
+ )
872
+ : null;
770
873
  if (hasRegistry) {
771
874
  return {
772
875
  schemaVersion: 1,
@@ -774,6 +877,7 @@ export async function queryKfd3Capabilities({
774
877
  product: product || registry.product?.name || registry.product?.id || "local-product",
775
878
  source: { type: "surface-registry", path: effectiveRegistryPath },
776
879
  status: audit.status,
880
+ verificationMode: audit.verificationMode || "buildchain-detected-surface-audit",
777
881
  summary: audit.summary,
778
882
  capabilities: capabilitiesFromRegistry({ registry, audit }),
779
883
  kfd: {
@@ -85,6 +85,11 @@ function commandId(first = "", second = "", third = "") {
85
85
 
86
86
  export function enumerateCliCommandsFromBin({ root = process.cwd(), binPath = "bin/buildchain.mjs" } = {}) {
87
87
  const source = readText(root, binPath);
88
+ const dispatchSource = [
89
+ source,
90
+ ...listFiles(root, "bin/internal", (name) => name.endsWith(".mjs"))
91
+ .map((relPath) => readText(root, relPath)),
92
+ ].join("\n");
88
93
  const usageMatch = source.match(/return `Usage:\n([\s\S]*?)`;\n}/);
89
94
  const usage = usageMatch?.[1] || "";
90
95
  const usageCommands = [];
@@ -96,7 +101,7 @@ export function enumerateCliCommandsFromBin({ root = process.cwd(), binPath = "b
96
101
  usage: line.trim().replace(/\s+/g, " "),
97
102
  });
98
103
  }
99
- const dispatchCommands = [...source.matchAll(/if\s*\(\s*command\s*===\s*"([^"]+)"/g)]
104
+ const dispatchCommands = [...dispatchSource.matchAll(/if\s*\(\s*command\s*===\s*"([^"]+)"/g)]
100
105
  .map((match) => commandId(match[1]));
101
106
  return uniqueSorted([
102
107
  ...usageCommands.map((entry) => entry.id),
@@ -99,6 +99,7 @@ export const RELEASE_PASSPORT_SCHEMA = {
99
99
  "kfd-1": OBJECT,
100
100
  "kfd-2": OBJECT,
101
101
  "kfd-3": OBJECT,
102
+ kfdSupport: OBJECT,
102
103
  },
103
104
  additionalProperties: true,
104
105
  };
@@ -121,6 +122,7 @@ const BUILDCHAIN_AGGREGATION_FIELDS = [
121
122
  "platformArtifactManifests",
122
123
  "distTagPromotion",
123
124
  "controllerReceipts",
125
+ "kfdSupport",
124
126
  "artifacts",
125
127
  "evidence",
126
128
  "recovery",
@@ -158,6 +160,13 @@ export function createReleasePassportCheckManifest({ standards = kfdStandards }
158
160
  nodeApi: "@kungfu-tech/buildchain/release-passport#verifyReleasePassport",
159
161
  result: "check-report.json",
160
162
  },
163
+ kfdSupportProjection: {
164
+ inputSchema: "schemas/kfd-product-gate-input-v1.schema.json",
165
+ projectionSchema: "schemas/kfd-support-projection-v1.schema.json",
166
+ evidence: "kfd-support.json",
167
+ rule:
168
+ "The passport mirrors one product-owned support matrix and validated product-gate results without widening any claim state.",
169
+ },
161
170
  },
162
171
  ownership: {
163
172
  envelopeOwner: "Buildchain",
@@ -184,6 +193,10 @@ export function createReleasePassportCheckManifest({ standards = kfdStandards }
184
193
  when: "evidence.transactionState is present",
185
194
  pointer: "evidence.transactionState",
186
195
  },
196
+ {
197
+ when: "kfdSupport is present",
198
+ pointer: "evidence.kfdSupport",
199
+ },
187
200
  ],
188
201
  rule:
189
202
  "The normative checker resolves every declared sibling relative to the passport and fails closed on missing required evidence, digest drift, or semantic mismatch.",
@@ -14,6 +14,10 @@ import {
14
14
  validateKfd1ReleaseGateEvidence,
15
15
  validateKfd3CollaborationInterfaceReleaseGateEvidence,
16
16
  } from "./kfd-gate.js";
17
+ import {
18
+ createKfdSupportProjection,
19
+ validateKfdSupportProjection,
20
+ } from "./kfd-product-gates.js";
17
21
  import { createSurfaceTimestampPolicy } from "./surface-manifest.js";
18
22
  import { validatePublishEvidence as validateTransactionPublishEvidence } from "./publish-transaction.js";
19
23
  import { normalizeControllerReceiptReferences } from "./controller-evidence.js";
@@ -1155,6 +1159,8 @@ export function createReleasePassport({
1155
1159
  kfd1 = undefined,
1156
1160
  kfd2Claims = [],
1157
1161
  kfd3 = undefined,
1162
+ kfdSupport = undefined,
1163
+ kfdSupportEvidencePath = "",
1158
1164
  invariantPassports = undefined,
1159
1165
  kfdAgentHubEvidence = undefined,
1160
1166
  kfdAgentHubEvidencePath = "",
@@ -1183,6 +1189,16 @@ export function createReleasePassport({
1183
1189
  const kfd1Metadata = resolveKfd1Metadata();
1184
1190
  const normalizedKfd1 = kfd1?.passportSection ? kfd1 : undefined;
1185
1191
  const normalizedKfd3 = kfd3?.passportSection ? kfd3 : undefined;
1192
+ const normalizedKfdSupport = kfdSupport ? structuredClone(kfdSupport) : undefined;
1193
+ if (normalizedKfdSupport) {
1194
+ const validation = validateKfdSupportProjection(normalizedKfdSupport, {
1195
+ expectedSourceSha: optionalString(sourceSha),
1196
+ checkedAt: generatedAt,
1197
+ });
1198
+ if (!validation.valid) {
1199
+ throw new Error(`KFD support projection is invalid: ${validation.issues.map((entry) => `${entry.path}: ${entry.message}`).join("; ")}`);
1200
+ }
1201
+ }
1186
1202
  const normalizedKfdAgentHub = normalizeKfdAgentHubEvidence(kfdAgentHubEvidence);
1187
1203
  const normalizedKfd2 = createKfd2ReleaseTrustPassportAudit({
1188
1204
  explicitClaims: kfd2Claims,
@@ -1190,13 +1206,27 @@ export function createReleasePassport({
1190
1206
  kfd3Section: normalizedKfd3?.passportSection,
1191
1207
  });
1192
1208
  const builtSourceSha = optionalString(release.builtSourceSha || release.built_source_sha);
1209
+ const builtSourceTreeSha = optionalString(
1210
+ release.builtSourceTreeSha || release.built_source_tree_sha,
1211
+ );
1193
1212
  const promotionChannelSha = optionalString(release.promotionChannelSha || release.promotion_channel_sha);
1213
+ const promotionChannelTreeSha = optionalString(
1214
+ release.promotionChannelTreeSha || release.promotion_channel_tree_sha,
1215
+ );
1194
1216
  const treeEquivalent = release.treeEquivalent === true;
1217
+ const recoveryTreeEquivalent = Boolean(
1218
+ treeEquivalent &&
1219
+ builtSourceTreeSha &&
1220
+ promotionChannelTreeSha &&
1221
+ builtSourceTreeSha === promotionChannelTreeSha,
1222
+ );
1195
1223
  const normalizedControllerReceipts = normalizeControllerReceiptReferences({
1196
1224
  receipts: controllerReceipts,
1197
1225
  references: controllerReceiptReferences,
1198
1226
  expectedSourceSha: sourceSha,
1199
- acceptedSourceShas: treeEquivalent && promotionChannelSha === sourceSha && builtSourceSha
1227
+ acceptedSourceShas: treeEquivalent && builtSourceSha && (
1228
+ promotionChannelSha === sourceSha || recoveryTreeEquivalent
1229
+ )
1200
1230
  ? [builtSourceSha]
1201
1231
  : [],
1202
1232
  requirePassed: true,
@@ -1279,9 +1309,9 @@ export function createReleasePassport({
1279
1309
  releaseSha,
1280
1310
  releaseMaterialSha,
1281
1311
  builtSourceSha: optionalString(release.builtSourceSha || release.built_source_sha),
1282
- builtSourceTreeSha: optionalString(release.builtSourceTreeSha || release.built_source_tree_sha),
1312
+ builtSourceTreeSha,
1283
1313
  promotionChannelSha: optionalString(release.promotionChannelSha || release.promotion_channel_sha),
1284
- promotionChannelTreeSha: optionalString(release.promotionChannelTreeSha || release.promotion_channel_tree_sha),
1314
+ promotionChannelTreeSha,
1285
1315
  treeEquivalent: release.treeEquivalent === undefined ? undefined : Boolean(release.treeEquivalent),
1286
1316
  publishToolingSha: optionalString(
1287
1317
  release.publishToolingSha ||
@@ -1326,6 +1356,7 @@ export function createReleasePassport({
1326
1356
  ...(normalizedKfd1 ? { [normalizedKfd1.key || kfd1Metadata.key]: normalizedKfd1.passportSection } : {}),
1327
1357
  ...(normalizedKfd2 ? { "kfd-2": normalizedKfd2 } : {}),
1328
1358
  ...(normalizedKfd3 ? { [normalizedKfd3.key || "kfd-3"]: normalizedKfd3.passportSection } : {}),
1359
+ ...(normalizedKfdSupport ? { kfdSupport: normalizedKfdSupport } : {}),
1329
1360
  ...(normalizedKfdAgentHub ? { kfdAgentHub: normalizedKfdAgentHub } : {}),
1330
1361
  ...(invariantPassports ? { invariantPassports } : {}),
1331
1362
  ...(normalizedControllerReceipts.length > 0 ? { controllerReceipts: normalizedControllerReceipts } : {}),
@@ -1370,6 +1401,7 @@ export function createReleasePassport({
1370
1401
  kfd1: normalizedKfd1 ? `${normalizedKfd1.key || kfd1Metadata.key}` : "",
1371
1402
  kfd2: normalizedKfd2 ? "kfd-2" : "",
1372
1403
  kfd3: normalizedKfd3 ? `${normalizedKfd3.key || "kfd-3"}` : "",
1404
+ kfdSupport: normalizedKfdSupport ? optionalString(kfdSupportEvidencePath || "kfd-support.json") : "",
1373
1405
  kfdAgentHub: normalizedKfdAgentHub ? optionalString(kfdAgentHubEvidencePath || "kfd-agent-hub-evidence.json") : "",
1374
1406
  invariantPassports: invariantPassports ? "invariantPassports" : "",
1375
1407
  impact: impactPath,
@@ -1412,6 +1444,8 @@ export function collectGitHubReleasePassport({
1412
1444
  kfd3PrebuildWitnessJsons = [],
1413
1445
  kfd3ArtifactWitnessJsons = [],
1414
1446
  kfd3ArtifactVerifyCommand = "",
1447
+ kfdSupportMatrixJson = "",
1448
+ kfdProductGateJsons = [],
1415
1449
  invariantPassportJsons = [],
1416
1450
  invariantPassportCommand = "",
1417
1451
  kfdAgentHubEvidenceJson = "",
@@ -1461,6 +1495,15 @@ export function collectGitHubReleasePassport({
1461
1495
  .filter(Boolean)
1462
1496
  .map((witnessJson) => parseJsonInputWithMeta(witnessJson, undefined, { cwd, label: "kfd3ArtifactWitnessJsons entry" }))
1463
1497
  .filter((meta) => meta.value);
1498
+ const kfdSupportMatrixMeta = parseJsonInputWithMeta(
1499
+ kfdSupportMatrixJson,
1500
+ undefined,
1501
+ { cwd, label: "kfdSupportMatrixJson" },
1502
+ );
1503
+ const kfdProductGateMetas = (kfdProductGateJsons || [])
1504
+ .filter(Boolean)
1505
+ .map((gateJson) => parseJsonInputWithMeta(gateJson, undefined, { cwd, label: "kfdProductGateJsons entry" }))
1506
+ .filter((meta) => meta.value);
1464
1507
  const basePassportMeta = parseJsonInputWithMeta(basePassportJson, undefined, { cwd, label: "basePassportJson" });
1465
1508
  const kfd3ArtifactCommandMeta = parseJsonCommandOutput({
1466
1509
  command: kfd3ArtifactVerifyCommand,
@@ -1487,6 +1530,9 @@ export function collectGitHubReleasePassport({
1487
1530
  ...kfd3ArtifactWitnessMetas.map((meta) => meta.value),
1488
1531
  ...(kfd3ArtifactCommandMeta.value ? [kfd3ArtifactCommandMeta.value] : []),
1489
1532
  ];
1533
+ if (!kfdSupportMatrixMeta.value && kfdProductGateMetas.length > 0) {
1534
+ throw new Error("KFD product gate inputs require --kfd-support-matrix-json");
1535
+ }
1490
1536
  const publish = parseJsonInput(publishJson, {}, { cwd, label: "publishJson" });
1491
1537
  const assets = [
1492
1538
  ...(Array.isArray(release.assets) ? release.assets : []),
@@ -1516,6 +1562,15 @@ export function collectGitHubReleasePassport({
1516
1562
  artifactWitnessMetas: kfd3ArtifactWitnessMetas,
1517
1563
  artifactCommandMeta: kfd3ArtifactCommandMeta.value ? kfd3ArtifactCommandMeta : undefined,
1518
1564
  });
1565
+ const kfdSupport = kfdSupportMatrixMeta.value
1566
+ ? createKfdSupportProjection({
1567
+ matrix: kfdSupportMatrixMeta.value,
1568
+ matrixRoot: kfdSupportMatrixMeta.sha256 ? `sha256:${kfdSupportMatrixMeta.sha256}` : "",
1569
+ gateResults: kfdProductGateMetas.map((meta) => meta.value),
1570
+ expectedSourceSha: sourceSha,
1571
+ checkedAt: nowIso(),
1572
+ })
1573
+ : undefined;
1519
1574
  const passport = mergeAuthoritativePassportBase(createReleasePassport({
1520
1575
  cwd,
1521
1576
  repository,
@@ -1560,6 +1615,8 @@ export function collectGitHubReleasePassport({
1560
1615
  kfd1,
1561
1616
  kfd2Claims: kfd2ClaimMetas.map((meta) => meta.value),
1562
1617
  kfd3,
1618
+ kfdSupport,
1619
+ kfdSupportEvidencePath: kfdSupport ? "kfd-support.json" : "",
1563
1620
  invariantPassports,
1564
1621
  kfdAgentHubEvidence: kfdAgentHubEvidenceMeta.value
1565
1622
  ? { ...kfdAgentHubEvidenceMeta, path: "kfd-agent-hub-evidence.json" }
@@ -1581,6 +1638,7 @@ export function collectGitHubReleasePassport({
1581
1638
  agentIndex,
1582
1639
  productMechanism,
1583
1640
  kfdAgentHubEvidence: kfdAgentHubEvidenceMeta.value,
1641
+ kfdSupportEvidence: kfdSupport,
1584
1642
  checkedAt: nowIso(),
1585
1643
  });
1586
1644
  const files = {
@@ -1590,6 +1648,7 @@ export function collectGitHubReleasePassport({
1590
1648
  "impact.json": impact,
1591
1649
  "agent-index.json": agentIndex,
1592
1650
  ...(kfdAgentHubEvidenceMeta.value ? { "kfd-agent-hub-evidence.json": kfdAgentHubEvidenceMeta.value } : {}),
1651
+ ...(kfdSupport ? { "kfd-support.json": kfdSupport } : {}),
1593
1652
  "buildchain.release.json": passport,
1594
1653
  "check-report.json": checkReport,
1595
1654
  };
@@ -1884,6 +1943,7 @@ export function createReleaseCheckReport({
1884
1943
  agentIndex,
1885
1944
  productMechanism,
1886
1945
  kfdAgentHubEvidence,
1946
+ kfdSupportEvidence,
1887
1947
  checkedAt = nowIso(),
1888
1948
  } = {}) {
1889
1949
  const issues = [];
@@ -1893,6 +1953,24 @@ export function createReleaseCheckReport({
1893
1953
  validateContract(agentIndex, AGENT_INDEX_CONTRACT, "agentIndex", issues);
1894
1954
  validateContract(productMechanism, PRODUCT_MECHANISM_CONTRACT, "productMechanism", issues);
1895
1955
  validateKfdAgentHubReleaseEvidence(passport?.kfdAgentHub, kfdAgentHubEvidence, issues);
1956
+ if (passport?.kfdSupport) {
1957
+ const validation = validateKfdSupportProjection(passport.kfdSupport, {
1958
+ expectedSourceSha: optionalString(passport?.release?.sourceSha),
1959
+ checkedAt,
1960
+ });
1961
+ for (const entry of validation.issues) {
1962
+ issues.push(issue("error", `kfdSupport.${entry.path || entry.code}`, entry.message, entry));
1963
+ }
1964
+ if (!kfdSupportEvidence || stableJson(kfdSupportEvidence) !== stableJson(passport.kfdSupport)) {
1965
+ issues.push(issue(
1966
+ "error",
1967
+ "kfdSupport.evidence",
1968
+ "release passport KFD support projection must exactly match its sibling evidence",
1969
+ ));
1970
+ }
1971
+ } else if (kfdSupportEvidence) {
1972
+ issues.push(issue("error", "kfdSupport.section", "KFD support evidence is present without a release-passport projection"));
1973
+ }
1896
1974
 
1897
1975
  const tag = passport?.release?.tag || "";
1898
1976
  if (!tag) {
@@ -2343,6 +2421,7 @@ export async function verifyReleasePassport({
2343
2421
  agentIndexLocation = "",
2344
2422
  productMechanismLocation = "",
2345
2423
  kfdAgentHubEvidenceLocation = "",
2424
+ kfdSupportEvidenceLocation = "",
2346
2425
  } = {}) {
2347
2426
  const passport = await readJsonFromLocation(passportLocation);
2348
2427
  const basePath = /^https?:\/\//.test(passportLocation) ? passportLocation : path.resolve(passportLocation);
@@ -2370,6 +2449,10 @@ export async function verifyReleasePassport({
2370
2449
  kfdAgentHubEvidenceLocation
2371
2450
  ? await readJsonFromLocation(kfdAgentHubEvidenceLocation)
2372
2451
  : await resolveSiblingJson(basePath, passport.evidence?.kfdAgentHub) || undefined;
2452
+ const kfdSupportEvidence =
2453
+ kfdSupportEvidenceLocation
2454
+ ? await readJsonFromLocation(kfdSupportEvidenceLocation)
2455
+ : await resolveSiblingJson(basePath, passport.evidence?.kfdSupport) || undefined;
2373
2456
  return createReleaseCheckReport({
2374
2457
  passport,
2375
2458
  artifactEvidence,
@@ -2378,6 +2461,7 @@ export async function verifyReleasePassport({
2378
2461
  agentIndex,
2379
2462
  productMechanism,
2380
2463
  kfdAgentHubEvidence,
2464
+ kfdSupportEvidence,
2381
2465
  });
2382
2466
  }
2383
2467
 
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ const implementationExtensions = new Set([".js", ".mjs", ".cjs", ".sh"]);
8
+ const importPattern =
9
+ /(?:\bimport\s*(?:\([^)]*?\)|[^"'\n]*?\s+from\s+)?|\bexport\s+[^"'\n]*?\s+from\s+)(["'])([^"'\n]+)\1/g;
10
+
11
+ function normalizeRelative(root, value) {
12
+ return path.relative(root, path.resolve(root, value)).split(path.sep).join("/");
13
+ }
14
+
15
+ function isInside(relativePath, prefix) {
16
+ return relativePath === prefix || relativePath.startsWith(`${prefix}/`);
17
+ }
18
+
19
+ function collectImplementationFiles(root, entry) {
20
+ const absolute = path.resolve(root, entry);
21
+ if (!fs.existsSync(absolute)) {
22
+ throw new Error(`internal architecture path is missing: ${entry}`);
23
+ }
24
+ if (fs.statSync(absolute).isFile()) {
25
+ return implementationExtensions.has(path.extname(absolute)) ? [absolute] : [];
26
+ }
27
+ return fs.readdirSync(absolute, { withFileTypes: true }).flatMap((item) =>
28
+ collectImplementationFiles(root, path.join(entry, item.name))
29
+ );
30
+ }
31
+
32
+ function relativeImports(source) {
33
+ const imports = [];
34
+ for (const match of source.matchAll(importPattern)) {
35
+ if (match[2].startsWith(".")) imports.push(match[2]);
36
+ }
37
+ return imports;
38
+ }
39
+
40
+ function resolveImportTarget(root, sourcePath, specifier) {
41
+ const absolute = path.resolve(path.dirname(path.resolve(root, sourcePath)), specifier);
42
+ return normalizeRelative(root, absolute);
43
+ }
44
+
45
+ function assertIndexShape(index) {
46
+ if (index?.schemaVersion !== 1) {
47
+ throw new Error("internal architecture index schemaVersion must be 1");
48
+ }
49
+ for (const field of ["coverageRoots", "dependencyRules", "capabilities"]) {
50
+ if (!Array.isArray(index[field]) || index[field].length === 0) {
51
+ throw new Error(`internal architecture index requires non-empty ${field}`);
52
+ }
53
+ }
54
+ }
55
+
56
+ function checkInternalArchitecture({
57
+ root = process.cwd(),
58
+ index = JSON.parse(
59
+ fs.readFileSync(
60
+ path.join(root, "architecture", "internal-capabilities.json"),
61
+ "utf8",
62
+ ),
63
+ ),
64
+ sourceOverrides = new Map(),
65
+ } = {}) {
66
+ assertIndexShape(index);
67
+ const issues = [];
68
+ const capabilityIds = new Set();
69
+ const coveredImplementation = new Map();
70
+
71
+ for (const capability of index.capabilities) {
72
+ if (!capability?.id || capabilityIds.has(capability.id)) {
73
+ issues.push(`capability id must be present and unique: ${capability?.id || "<empty>"}`);
74
+ continue;
75
+ }
76
+ capabilityIds.add(capability.id);
77
+ if (!Array.isArray(capability.implementation) || capability.implementation.length === 0) {
78
+ issues.push(`${capability.id}: implementation mapping is empty`);
79
+ }
80
+ if (!Array.isArray(capability.tests) || capability.tests.length === 0) {
81
+ issues.push(`${capability.id}: test mapping is empty`);
82
+ }
83
+ for (const field of ["implementation", "tests", "contracts"]) {
84
+ for (const relativePath of capability[field] || []) {
85
+ const absolutePath = path.resolve(root, relativePath);
86
+ if (!fs.existsSync(absolutePath)) {
87
+ issues.push(`${capability.id}: ${field} path is missing: ${relativePath}`);
88
+ }
89
+ if (field === "implementation") {
90
+ const owner = coveredImplementation.get(relativePath);
91
+ if (owner) {
92
+ issues.push(
93
+ `${relativePath}: implementation is mapped by both ${owner} and ${capability.id}`,
94
+ );
95
+ } else {
96
+ coveredImplementation.set(relativePath, capability.id);
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ const expectedImplementation = new Set(
104
+ index.coverageRoots.flatMap((entry) =>
105
+ collectImplementationFiles(root, entry).map((absolutePath) =>
106
+ normalizeRelative(root, absolutePath)
107
+ )
108
+ ),
109
+ );
110
+ for (const relativePath of expectedImplementation) {
111
+ if (!coveredImplementation.has(relativePath)) {
112
+ issues.push(`capability-to-test mapping is missing implementation: ${relativePath}`);
113
+ }
114
+ }
115
+ for (const relativePath of coveredImplementation.keys()) {
116
+ if (!expectedImplementation.has(relativePath)) {
117
+ issues.push(`capability implementation is outside coverageRoots: ${relativePath}`);
118
+ }
119
+ }
120
+
121
+ for (const rule of index.dependencyRules) {
122
+ const allowed = rule.allowedRelativeTargets || [];
123
+ for (const sourceEntry of rule.sources || []) {
124
+ for (const absoluteSource of collectImplementationFiles(root, sourceEntry)) {
125
+ if (![".js", ".mjs", ".cjs"].includes(path.extname(absoluteSource))) continue;
126
+ const sourcePath = normalizeRelative(root, absoluteSource);
127
+ const source = sourceOverrides.has(sourcePath)
128
+ ? sourceOverrides.get(sourcePath)
129
+ : fs.readFileSync(absoluteSource, "utf8");
130
+ for (const specifier of relativeImports(source)) {
131
+ const target = resolveImportTarget(root, sourcePath, specifier);
132
+ if (!allowed.some((prefix) => isInside(target, prefix))) {
133
+ issues.push(
134
+ `${rule.id}: ${sourcePath} imports ${target}, outside allowed dependency direction`,
135
+ );
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ if (issues.length > 0) {
143
+ throw new Error(
144
+ `internal architecture check failed:\n- ${issues.join("\n- ")}`,
145
+ );
146
+ }
147
+ return {
148
+ schemaVersion: index.schemaVersion,
149
+ capabilities: index.capabilities.length,
150
+ implementations: expectedImplementation.size,
151
+ dependencyRules: index.dependencyRules.length,
152
+ };
153
+ }
154
+
155
+ if (
156
+ process.argv[1] &&
157
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
158
+ ) {
159
+ try {
160
+ const report = checkInternalArchitecture();
161
+ console.log(
162
+ `internal architecture check passed: ${report.capabilities} capabilities, ` +
163
+ `${report.implementations} implementations, ${report.dependencyRules} dependency rules`,
164
+ );
165
+ } catch (error) {
166
+ console.error(error instanceof Error ? error.message : String(error));
167
+ process.exitCode = 1;
168
+ }
169
+ }
170
+
171
+ export { checkInternalArchitecture, relativeImports };
@@ -924,6 +924,10 @@ for (const requiredSnippet of [
924
924
  "release-passport-kfd-3-artifact-witness-jsons:",
925
925
  "release-passport-kfd-3-artifact-witness-jsons: ${{ inputs.release-passport-kfd-3-artifact-witness-jsons }}",
926
926
  "release-passport-kfd-3-artifact-verify-command:",
927
+ "release-passport-kfd-support-matrix-json:",
928
+ "release-passport-kfd-support-matrix-json: ${{ inputs.release-passport-kfd-support-matrix-json }}",
929
+ "release-passport-kfd-product-gate-jsons:",
930
+ "release-passport-kfd-product-gate-jsons: ${{ inputs.release-passport-kfd-product-gate-jsons }}",
927
931
  "release-passport-invariant-passport-jsons:",
928
932
  "release-passport-invariant-passport-jsons: ${{ inputs.release-passport-invariant-passport-jsons }}",
929
933
  "release-passport-invariant-passport-command:",