@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.
- package/README.md +5 -3
- package/actions/promote-buildchain-ref/README.md +7 -0
- package/bin/buildchain.mjs +173 -513
- package/bin/internal/cli-options.mjs +79 -0
- package/bin/internal/trust-release-cli.mjs +469 -0
- package/dist/site/agent-index.json +2 -0
- package/dist/site/artifact-schemas.json +5 -0
- package/dist/site/badge-endpoint-registry.json +18 -18
- package/dist/site/badges/v1/kfd-12/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-12/declared.json +1 -1
- package/dist/site/badges/v1/kfd-12/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-12/draft.json +1 -1
- package/dist/site/badges/v1/kfd-12/failed.json +1 -1
- package/dist/site/badges/v1/kfd-12/missing.json +1 -1
- package/dist/site/badges/v1/kfd-12/passed.json +1 -1
- package/dist/site/badges/v1/kfd-12/planned.json +1 -1
- package/dist/site/badges/v1/kfd-13/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-13/declared.json +1 -1
- package/dist/site/badges/v1/kfd-13/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-13/draft.json +1 -1
- package/dist/site/badges/v1/kfd-13/failed.json +1 -1
- package/dist/site/badges/v1/kfd-13/missing.json +1 -1
- package/dist/site/badges/v1/kfd-13/passed.json +1 -1
- package/dist/site/badges/v1/kfd-13/planned.json +1 -1
- package/dist/site/buildchain-contract.json +32 -22
- package/dist/site/buildchain-site.json +28 -18
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/cli-registry.json +108 -0
- package/dist/site/controller-registry.json +10 -2
- package/dist/site/kfd-claims.json +136 -10
- package/dist/site/kfd-upstream-aggregate.json +20 -11
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +21 -8
- package/dist/site/page-registry.json +20 -10
- package/dist/site/public-surface-audit.json +114 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +11 -0
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/schemas/kfd-product-gate-input-v1.schema.json +164 -0
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +9 -3
- package/docs/cli.md +9 -2
- package/docs/kfd-support.md +43 -7
- package/docs/release-passport.md +15 -0
- package/package.json +6 -3
- package/packages/core/index.js +17 -0
- package/packages/core/kfd-product-gates.js +865 -0
- package/packages/core/kfd.js +16 -7
- package/packages/core/kfd3-surface-register.js +105 -1
- package/packages/core/public-surface-audit.js +6 -1
- package/packages/core/release-passport-contract.js +13 -0
- package/packages/core/release-passport.js +87 -3
- package/scripts/check-internal-architecture.mjs +171 -0
- package/scripts/check-inventory.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +23 -0
- package/scripts/promotion-routing-evidence.mjs +73 -0
package/packages/core/kfd.js
CHANGED
|
@@ -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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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 = [...
|
|
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 &&
|
|
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
|
|
1312
|
+
builtSourceTreeSha,
|
|
1283
1313
|
promotionChannelSha: optionalString(release.promotionChannelSha || release.promotion_channel_sha),
|
|
1284
|
-
promotionChannelTreeSha
|
|
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:",
|