@kungfu-tech/buildchain 3.0.8 → 3.0.9-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/actions/promote-buildchain-ref/README.md +8 -7
- package/bin/buildchain.mjs +34 -33
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/dist/site/agent-index.json +0 -1
- package/dist/site/artifact-schemas.json +0 -2
- package/dist/site/badge-endpoint-registry.json +9 -9
- package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-8/declared.json +1 -1
- package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-8/draft.json +1 -1
- package/dist/site/badges/v1/kfd-8/failed.json +1 -1
- package/dist/site/badges/v1/kfd-8/missing.json +1 -1
- package/dist/site/badges/v1/kfd-8/passed.json +1 -1
- package/dist/site/badges/v1/kfd-8/planned.json +1 -1
- package/dist/site/buildchain-contract.json +27 -22
- package/dist/site/buildchain-site.json +38 -28
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +8 -7
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +34 -7
- package/dist/site/kfd-upstream-aggregate.json +10 -10
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +754 -228
- package/dist/site/page-registry.json +28 -18
- package/dist/site/public-surface-audit.json +19 -9
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +33 -5
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +19 -9
- package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
- package/docs/cli-reference.md +6 -6
- package/docs/cli.md +2 -3
- package/docs/dev-alpha-candidate-patrol.md +45 -20
- package/docs/kfd-support.md +7 -7
- package/docs/node-api-reference.md +179 -140
- package/docs/release-passport.md +18 -12
- package/docs/release-train.md +8 -0
- package/package.json +4 -3
- package/packages/core/artifact-verification-envelope.js +114 -0
- package/packages/core/dev-alpha-active-release-train.js +460 -0
- package/packages/core/dev-alpha-candidate-selection.js +181 -0
- package/packages/core/index.js +4 -5
- package/packages/core/kfd-adopter-manifest.js +390 -0
- package/packages/core/kfd-product-gates.js +0 -300
- package/packages/core/release-passport-contract.js +219 -6
- package/packages/core/release-passport.js +139 -242
- package/scripts/aws-macos-jit-controller-core.mjs +76 -0
- package/scripts/aws-macos-jit-controller.mjs +29 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
- package/scripts/buildchain-cli-help.mjs +4 -2
- package/scripts/check-inventory.mjs +2 -2
- package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
- package/scripts/generate-site-bundle.mjs +1 -8
- package/scripts/site-capability-metadata.mjs +3 -1
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +0 -106
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
|
|
5
|
+
function text(value = "") {
|
|
6
|
+
return String(value ?? "").trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function canonical(value) {
|
|
10
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
11
|
+
if (value && typeof value === "object") {
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries(value)
|
|
14
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
15
|
+
.map(([key, item]) => [key, canonical(item)]),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function evidenceRoot(value) {
|
|
22
|
+
return `sha256:${crypto
|
|
23
|
+
.createHash("sha256")
|
|
24
|
+
.update(JSON.stringify(canonical(value)))
|
|
25
|
+
.digest("hex")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function latestWorkflowEvidence(runs, workflowPath, sourceSha) {
|
|
29
|
+
const matching = runs
|
|
30
|
+
.filter(
|
|
31
|
+
(run) =>
|
|
32
|
+
run.conclusion !== "cancelled" &&
|
|
33
|
+
run.path === workflowPath &&
|
|
34
|
+
run.head_sha === sourceSha,
|
|
35
|
+
)
|
|
36
|
+
.sort((left, right) => Number(right.id) - Number(left.id));
|
|
37
|
+
if (matching.length === 0) {
|
|
38
|
+
throw new Error(`missing completed same-SHA workflow run: ${workflowPath}`);
|
|
39
|
+
}
|
|
40
|
+
const run = matching[0];
|
|
41
|
+
return {
|
|
42
|
+
workflowPath,
|
|
43
|
+
workflowName: run.name,
|
|
44
|
+
runId: run.id,
|
|
45
|
+
runAttempt: run.run_attempt,
|
|
46
|
+
headSha: run.head_sha,
|
|
47
|
+
status: run.status,
|
|
48
|
+
conclusion: run.conclusion,
|
|
49
|
+
completedAt: run.updated_at,
|
|
50
|
+
url: run.html_url,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function workflowEvidenceIsFreshAndSuccessful(run, { now, maxAgeSeconds }) {
|
|
55
|
+
const completedAt = Date.parse(run.updated_at);
|
|
56
|
+
const ageSeconds = (Date.parse(now) - completedAt) / 1000;
|
|
57
|
+
return (
|
|
58
|
+
run.status === "completed" &&
|
|
59
|
+
run.conclusion === "success" &&
|
|
60
|
+
Number.isFinite(ageSeconds) &&
|
|
61
|
+
ageSeconds >= 0 &&
|
|
62
|
+
ageSeconds <= maxAgeSeconds
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function latestRunsBySha(runs, workflowPath) {
|
|
67
|
+
const latest = new Map();
|
|
68
|
+
for (const run of runs.filter(
|
|
69
|
+
(row) => row.path === workflowPath && row.conclusion !== "cancelled",
|
|
70
|
+
)) {
|
|
71
|
+
const current = latest.get(run.head_sha);
|
|
72
|
+
if (!current || Number(run.id) > Number(current.id)) {
|
|
73
|
+
latest.set(run.head_sha, run);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return latest;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function selectLatestQualifiedSource({
|
|
80
|
+
sourceHistory,
|
|
81
|
+
workflowRunsByPath,
|
|
82
|
+
requiredWorkflowPaths,
|
|
83
|
+
now,
|
|
84
|
+
maxAgeSeconds,
|
|
85
|
+
}) {
|
|
86
|
+
const latestByPath = new Map(
|
|
87
|
+
requiredWorkflowPaths.map((workflow) => [
|
|
88
|
+
workflow,
|
|
89
|
+
latestRunsBySha(workflowRunsByPath.get(workflow) || [], workflow),
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
93
|
+
const sourceSha = sourceHistory[index];
|
|
94
|
+
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
95
|
+
latestByPath.get(workflow).get(sourceSha),
|
|
96
|
+
);
|
|
97
|
+
if (
|
|
98
|
+
rows.every((run) =>
|
|
99
|
+
workflowEvidenceIsFreshAndSuccessful(run || {}, {
|
|
100
|
+
now,
|
|
101
|
+
maxAgeSeconds,
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
) {
|
|
105
|
+
return {
|
|
106
|
+
sourceSha,
|
|
107
|
+
skippedNewerCommitCount: index,
|
|
108
|
+
workflowEvidence: requiredWorkflowPaths.map((workflow) =>
|
|
109
|
+
latestWorkflowEvidence(
|
|
110
|
+
workflowRunsByPath.get(workflow) || [],
|
|
111
|
+
workflow,
|
|
112
|
+
sourceSha,
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const staleSuccessfulPair = sourceHistory.some((sourceSha) => {
|
|
119
|
+
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
120
|
+
latestByPath.get(workflow).get(sourceSha),
|
|
121
|
+
);
|
|
122
|
+
return (
|
|
123
|
+
rows.every(
|
|
124
|
+
(run) =>
|
|
125
|
+
run?.status === "completed" &&
|
|
126
|
+
run?.conclusion === "success" &&
|
|
127
|
+
run?.head_sha === sourceSha,
|
|
128
|
+
) &&
|
|
129
|
+
rows.some(
|
|
130
|
+
(run) =>
|
|
131
|
+
!workflowEvidenceIsFreshAndSuccessful(run, { now, maxAgeSeconds }),
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
if (staleSuccessfulPair) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"same-SHA workflow evidence is stale for every qualified source commit",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
throw new Error(
|
|
141
|
+
"no source commit ahead of target has fresh completed successful same-SHA workflow evidence",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function blockedCandidateDecision({
|
|
146
|
+
options,
|
|
147
|
+
sourceSha,
|
|
148
|
+
targetSha,
|
|
149
|
+
comparison,
|
|
150
|
+
reason,
|
|
151
|
+
}) {
|
|
152
|
+
const body = {
|
|
153
|
+
schema: "kungfu-buildchain-channel-candidate-decision/v1",
|
|
154
|
+
eligible: false,
|
|
155
|
+
reason: "qualification-evidence-blocked",
|
|
156
|
+
repository: options.repository,
|
|
157
|
+
source: { branch: options.sourceBranch, sha: sourceSha },
|
|
158
|
+
target: { branch: options.targetBranch, sha: targetSha },
|
|
159
|
+
comparison: {
|
|
160
|
+
status: text(comparison.status || "unknown"),
|
|
161
|
+
aheadBy: Number(comparison.ahead_by || 0),
|
|
162
|
+
},
|
|
163
|
+
blockReason: text(reason),
|
|
164
|
+
decidedAt: options.now,
|
|
165
|
+
};
|
|
166
|
+
return { ...body, decisionRoot: evidenceRoot(body) };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function candidateFromDecision(decision) {
|
|
170
|
+
if (!decision.eligible) return null;
|
|
171
|
+
return {
|
|
172
|
+
sourceSha: decision.source.sha,
|
|
173
|
+
sourceLockRef: decision.sourceLockRef,
|
|
174
|
+
decisionRoot: decision.decisionRoot,
|
|
175
|
+
workflowEvidence: decision.workflowEvidence,
|
|
176
|
+
qualificationRoot: evidenceRoot({
|
|
177
|
+
sourceSha: decision.source.sha,
|
|
178
|
+
workflowEvidence: decision.workflowEvidence,
|
|
179
|
+
}),
|
|
180
|
+
};
|
|
181
|
+
}
|
package/packages/core/index.js
CHANGED
|
@@ -305,10 +305,14 @@ export {
|
|
|
305
305
|
export {
|
|
306
306
|
ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT,
|
|
307
307
|
ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT,
|
|
308
|
+
KFD_ADOPTER_RELEASE_BINDING_CONTRACT,
|
|
308
309
|
KFX_ADMISSION_INPUTS_CONTRACT,
|
|
309
310
|
artifactVerificationEnvelopeDigest,
|
|
311
|
+
createKfdAdopterReleaseBinding,
|
|
312
|
+
installedKfdPackageArtifactRoot,
|
|
310
313
|
projectArtifactVerificationEnvelopeToKfx,
|
|
311
314
|
sealArtifactVerificationReport,
|
|
315
|
+
validateKfdAdopterReleaseBinding,
|
|
312
316
|
verifyArtifactVerificationEnvelope,
|
|
313
317
|
} from "./artifact-verification-envelope.js";
|
|
314
318
|
|
|
@@ -488,15 +492,10 @@ export {
|
|
|
488
492
|
KFD_PRODUCT_GATE_INPUT_CONTRACT,
|
|
489
493
|
KFD_PRODUCT_GATE_INPUT_SCHEMA,
|
|
490
494
|
KFD_PRODUCT_GATE_INPUT_SCHEMA_ID,
|
|
491
|
-
KFD_SUPPORT_PROJECTION_CONTRACT,
|
|
492
|
-
KFD_SUPPORT_PROJECTION_SCHEMA,
|
|
493
|
-
KFD_SUPPORT_PROJECTION_SCHEMA_ID,
|
|
494
|
-
createKfdSupportProjection,
|
|
495
495
|
evaluateKfdProductGate,
|
|
496
496
|
kfdProductGateDigest,
|
|
497
497
|
kfdProductGates,
|
|
498
498
|
validateKfdProductGateResult,
|
|
499
|
-
validateKfdSupportProjection,
|
|
500
499
|
verifyKfdRecord,
|
|
501
500
|
} from "./kfd-product-gates.js";
|
|
502
501
|
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
|
|
5
|
+
import kfdPackageJson from "@kungfu-tech/kfd/package.json" with { type: "json" };
|
|
6
|
+
import kfdStandards from "@kungfu-tech/kfd/standards.json" with { type: "json" };
|
|
7
|
+
import {
|
|
8
|
+
bundleAdopterManifest,
|
|
9
|
+
verifyAdopterManifestFromPackage,
|
|
10
|
+
} from "@kungfu-tech/kfd/adopter-conformance/toolchain";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
KFD_PRODUCT_GATE_CONTRACT,
|
|
14
|
+
kfdProductGateDigest,
|
|
15
|
+
validateKfdProductGateResult,
|
|
16
|
+
} from "./kfd-product-gates.js";
|
|
17
|
+
|
|
18
|
+
export const KFD_ADOPTER_MANIFEST_GATE_CONTRACT =
|
|
19
|
+
"kungfu-buildchain-kfd-adopter-manifest-gate";
|
|
20
|
+
export const KFD_LEGACY_SUPPORT_MATRIX_CONTRACT = "kungfu-kfd-support-matrix";
|
|
21
|
+
|
|
22
|
+
const BUILDCHAIN_ADOPTER_ID = "kungfu-systems/buildchain";
|
|
23
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
24
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
25
|
+
const REQUIRED_DECISIONS = Object.freeze(["KFD-1", "KFD-2", "KFD-3", "KFD-4", "KFD-5", "KFD-7"]);
|
|
26
|
+
const PRODUCT_GATE_STANDARDS = Object.freeze(["kfd-4", "kfd-5", "kfd-7"]);
|
|
27
|
+
const PRODUCT_GATE_SET = new Set(PRODUCT_GATE_STANDARDS);
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
|
|
30
|
+
function issue(code, path, message, detail = {}) {
|
|
31
|
+
return { level: "error", code, path, message, ...detail };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function text(value) {
|
|
35
|
+
return value === undefined || value === null ? "" : String(value).trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function standardsFileDigest() {
|
|
39
|
+
const bytes = fs.readFileSync(require.resolve("@kungfu-tech/kfd/standards.json"));
|
|
40
|
+
return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function standardMetadata(standard) {
|
|
44
|
+
return kfdStandards?.standards?.[standard] || {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function decisionWitnessRow(row) {
|
|
48
|
+
const roots = (entries) => (entries || []).map((entry) => entry?.root || "");
|
|
49
|
+
return {
|
|
50
|
+
id: row.id,
|
|
51
|
+
state: row.state,
|
|
52
|
+
usage: row.usage,
|
|
53
|
+
implementationRoots: roots(row.implementationEvidence),
|
|
54
|
+
verificationRoots: roots(row.verificationEvidence),
|
|
55
|
+
negativeRoots: roots(row.negativeEvidence),
|
|
56
|
+
reviewRoots: roots(row.reviews),
|
|
57
|
+
witnessRoots: (row.witnessBindings || []).map((entry) => entry?.witnessRoot || ""),
|
|
58
|
+
releaseBindingIds: [...(row.releaseBindingIds || [])],
|
|
59
|
+
claims: [...(row.claims || [])],
|
|
60
|
+
gaps: [...(row.gaps || [])],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function verifyPublishedManifest(manifest, packageOptions, issues) {
|
|
65
|
+
try {
|
|
66
|
+
const report = verifyAdopterManifestFromPackage(manifest, packageOptions);
|
|
67
|
+
if (!report.valid) {
|
|
68
|
+
issues.push(issue("adopter-manifest-invalid", "manifest", "published KFD adopter verifier rejected the manifest", {
|
|
69
|
+
manifestIssues: report.issues,
|
|
70
|
+
}));
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return bundleAdopterManifest(manifest, packageOptions);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
issues.push(issue("adopter-manifest-invalid", "manifest", error.message));
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function manifestSourceSha(manifest) {
|
|
81
|
+
const match = /^kungfu-systems\/buildchain@([0-9a-f]{40})$/.exec(text(manifest?.adopter?.artifact?.coordinate));
|
|
82
|
+
return match?.[1] || "";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateManifestIdentity(manifest, packageArtifactRoot, expectedSourceSha, issues) {
|
|
86
|
+
if (manifest?.adopter?.id !== BUILDCHAIN_ADOPTER_ID) {
|
|
87
|
+
issues.push(issue("adopter-identity", "manifest.adopter.id", `manifest adopter must be ${BUILDCHAIN_ADOPTER_ID}`));
|
|
88
|
+
}
|
|
89
|
+
const pinned = manifest?.kfdCut?.package;
|
|
90
|
+
if (pinned?.name !== kfdPackageJson.name
|
|
91
|
+
|| pinned?.version !== kfdPackageJson.version
|
|
92
|
+
|| pinned?.artifactRoot !== packageArtifactRoot) {
|
|
93
|
+
issues.push(issue("adopter-package-cut", "manifest.kfdCut.package", "manifest must bind the exact installed KFD package cut"));
|
|
94
|
+
}
|
|
95
|
+
const sourceSha = manifestSourceSha(manifest);
|
|
96
|
+
if (manifest?.adopter?.artifact?.kind !== "git-commit" || !sourceSha
|
|
97
|
+
|| !ROOT_PATTERN.test(text(manifest?.adopter?.artifact?.root))) {
|
|
98
|
+
issues.push(issue("adopter-source", "manifest.adopter.artifact", "Buildchain adopter authority must bind one exact git commit and artifact root"));
|
|
99
|
+
} else if (expectedSourceSha && sourceSha !== expectedSourceSha) {
|
|
100
|
+
issues.push(issue("adopter-source", "manifest.adopter.artifact.coordinate", "manifest source does not match the requested release source"));
|
|
101
|
+
}
|
|
102
|
+
return sourceSha;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateRequiredDecisions(decisions, issues) {
|
|
106
|
+
for (const id of REQUIRED_DECISIONS) {
|
|
107
|
+
const row = decisions.get(id);
|
|
108
|
+
if (!row || !["candidate", "adopted"].includes(row.state) || row.usage === "unused") {
|
|
109
|
+
issues.push(issue("adopter-required-decision", `manifest.decisions.${id}`, `${id} must remain an explicit used candidate or adopted declaration`));
|
|
110
|
+
} else if ((row.implementationEvidence || []).length === 0 || (row.verificationEvidence || []).length === 0) {
|
|
111
|
+
issues.push(issue("adopter-required-evidence", `manifest.decisions.${id}`, `${id} must bind implementation and verification evidence`));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const kfd6 = decisions.get("KFD-6");
|
|
115
|
+
if (kfd6?.state !== "unsupported" || kfd6?.usage !== "unused") {
|
|
116
|
+
issues.push(issue("adopter-kfd6-barrier", "manifest.decisions.KFD-6", "KFD-6 must remain explicitly unsupported and unused"));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateWarrantWitness(decisions, issues) {
|
|
121
|
+
const row = decisions.get("KFD-10");
|
|
122
|
+
const witnesses = (row?.witnessBindings || []).filter((entry) =>
|
|
123
|
+
entry?.decisionId === "KFD-10" && entry?.profileId === "kfd-warrant-evidence");
|
|
124
|
+
if (row?.state !== "draft-evidence" || row?.usage !== "evaluating" || witnesses.length !== 1) {
|
|
125
|
+
issues.push(issue("adopter-kfd10-witness", "manifest.decisions.KFD-10", "KFD-10 must retain exactly one draft Warrant-evidence witness binding"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function collectProductGates(gateResults, decisions, verificationCut, issues) {
|
|
130
|
+
const gates = new Map();
|
|
131
|
+
for (const [index, gate] of gateResults.entries()) {
|
|
132
|
+
const validation = validateKfdProductGateResult(gate, verificationCut);
|
|
133
|
+
if (!validation.valid) {
|
|
134
|
+
issues.push(issue("adopter-gate-invalid", `gateResults[${index}]`, "Buildchain product gate is invalid", {
|
|
135
|
+
gateIssues: validation.issues,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
if (!PRODUCT_GATE_SET.has(gate?.standard)) {
|
|
139
|
+
issues.push(issue("adopter-gate-standard", `gateResults[${index}].standard`, "only the exact KFD-4/5/7 product-gate set is allowed"));
|
|
140
|
+
}
|
|
141
|
+
if (gates.has(gate?.standard)) {
|
|
142
|
+
issues.push(issue("adopter-gate-duplicate", `gateResults[${index}].standard`, `${gate.standard} gate is duplicated`));
|
|
143
|
+
}
|
|
144
|
+
gates.set(gate?.standard, gate);
|
|
145
|
+
}
|
|
146
|
+
for (const standard of PRODUCT_GATE_STANDARDS) {
|
|
147
|
+
const gate = gates.get(standard);
|
|
148
|
+
const id = standard.toUpperCase();
|
|
149
|
+
if (!gate) {
|
|
150
|
+
issues.push(issue("adopter-gate-missing", `gateResults.${standard}`, `${id} requires its existing Buildchain product gate`));
|
|
151
|
+
} else if (!(decisions.get(id)?.verificationEvidence || []).some((entry) => entry?.root === gate.gateRoot)) {
|
|
152
|
+
issues.push(issue("adopter-gate-unbound", `manifest.decisions.${id}.verificationEvidence`, `${id} must bind the exact Buildchain gate root`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return gates;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function projectedProductGates(gates) {
|
|
159
|
+
return PRODUCT_GATE_STANDARDS.map((standard) => {
|
|
160
|
+
const gate = gates.get(standard);
|
|
161
|
+
return gate
|
|
162
|
+
? { standard, sourceSha: gate.source.sha, gateRoot: gate.gateRoot, status: gate.status }
|
|
163
|
+
: { standard, sourceSha: "", gateRoot: "", status: "missing" };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, sourceSha, checkedAt, maxAgeSeconds, issues }) {
|
|
168
|
+
const gate = {
|
|
169
|
+
schemaVersion: 1,
|
|
170
|
+
contract: KFD_ADOPTER_MANIFEST_GATE_CONTRACT,
|
|
171
|
+
checkedAt,
|
|
172
|
+
authority: {
|
|
173
|
+
path: authorityPath,
|
|
174
|
+
contract: text(manifest?.contract),
|
|
175
|
+
manifestRoot: bundle?.roots?.manifestRoot || "",
|
|
176
|
+
},
|
|
177
|
+
source: {
|
|
178
|
+
sha: sourceSha,
|
|
179
|
+
artifactRoot: text(manifest?.adopter?.artifact?.root),
|
|
180
|
+
},
|
|
181
|
+
verificationCut: { checkedAt, maxAgeSeconds },
|
|
182
|
+
standardPackage: {
|
|
183
|
+
name: kfdPackageJson.name,
|
|
184
|
+
version: kfdPackageJson.version,
|
|
185
|
+
artifactRoot: text(manifest?.kfdCut?.package?.artifactRoot || packageArtifactRoot),
|
|
186
|
+
registryRoot: text(manifest?.kfdCut?.registry?.root),
|
|
187
|
+
verifierSetRoot: text(manifest?.kfdCut?.verifierSetRoot),
|
|
188
|
+
},
|
|
189
|
+
decisionWitness: {
|
|
190
|
+
rootAlgorithm: "sha256-buildchain-stable-json-v1",
|
|
191
|
+
root: kfdProductGateDigest((manifest?.decisions || []).map(decisionWitnessRow)),
|
|
192
|
+
},
|
|
193
|
+
gateResults: projectedProductGates(gates),
|
|
194
|
+
manifestVerificationReportRoot: bundle?.roots?.verificationReportRoot || "",
|
|
195
|
+
manifestBundleRoot: bundle?.bundleRoot || "",
|
|
196
|
+
status: bundle && issues.length === 0 ? "passed" : "failed",
|
|
197
|
+
qualifying: false,
|
|
198
|
+
selfCertified: false,
|
|
199
|
+
nonClaims: [
|
|
200
|
+
"The standard adopter manifest is the sole declaration authority; legacy support matrices are projections only.",
|
|
201
|
+
"A passing manifest gate does not authorize release, runtime action, activation, or independent certification.",
|
|
202
|
+
],
|
|
203
|
+
issues,
|
|
204
|
+
};
|
|
205
|
+
gate.gateRoot = kfdProductGateDigest(gate);
|
|
206
|
+
return gate;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function createKfdAdopterManifestGate({
|
|
210
|
+
manifest,
|
|
211
|
+
packageArtifactRoot = "",
|
|
212
|
+
gateResults = [],
|
|
213
|
+
authorityPath = ".buildchain/kfd/adopter-manifest.json",
|
|
214
|
+
expectedSourceSha = "",
|
|
215
|
+
checkedAt = new Date().toISOString(),
|
|
216
|
+
maxAgeSeconds = 86400,
|
|
217
|
+
} = {}) {
|
|
218
|
+
const issues = [];
|
|
219
|
+
const bundle = verifyPublishedManifest(manifest, {
|
|
220
|
+
packageArtifactRoot,
|
|
221
|
+
verifiedAt: checkedAt,
|
|
222
|
+
maxAgeSeconds,
|
|
223
|
+
}, issues);
|
|
224
|
+
const decisions = new Map((manifest?.decisions || []).map((row) => [row.id, row]));
|
|
225
|
+
let sourceSha = manifestSourceSha(manifest);
|
|
226
|
+
if (bundle) {
|
|
227
|
+
sourceSha = validateManifestIdentity(manifest, packageArtifactRoot, expectedSourceSha, issues);
|
|
228
|
+
validateRequiredDecisions(decisions, issues);
|
|
229
|
+
validateWarrantWitness(decisions, issues);
|
|
230
|
+
}
|
|
231
|
+
const gates = collectProductGates(gateResults, decisions, { expectedSourceSha: sourceSha || expectedSourceSha, checkedAt }, issues);
|
|
232
|
+
return createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, sourceSha, checkedAt, maxAgeSeconds, issues });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function validateGateDocument(gate, issues) {
|
|
236
|
+
const copy = structuredClone(gate);
|
|
237
|
+
const root = copy.gateRoot;
|
|
238
|
+
delete copy.gateRoot;
|
|
239
|
+
if (root !== kfdProductGateDigest(copy)) issues.push(issue("adopter-gate-root", "gateRoot", "adopter manifest gate root does not match its content"));
|
|
240
|
+
if (gate.status !== "passed" || gate.qualifying !== false || gate.selfCertified !== false || (gate.issues || []).length !== 0) {
|
|
241
|
+
issues.push(issue("adopter-gate-status", "status", "release consumption requires a passing non-qualifying, non-self-certifying manifest gate"));
|
|
242
|
+
}
|
|
243
|
+
if (gate?.authority?.contract !== "kfd.adopter-conformance-manifest/v1"
|
|
244
|
+
|| !ROOT_PATTERN.test(text(gate?.authority?.manifestRoot))) {
|
|
245
|
+
issues.push(issue("adopter-gate-authority", "authority", "adopter gate must bind the standard manifest contract and root"));
|
|
246
|
+
}
|
|
247
|
+
if (gate?.standardPackage?.name !== kfdPackageJson.name || gate?.standardPackage?.version !== kfdPackageJson.version
|
|
248
|
+
|| ![gate?.standardPackage?.artifactRoot, gate?.standardPackage?.registryRoot, gate?.standardPackage?.verifierSetRoot].every((value) => ROOT_PATTERN.test(text(value)))) {
|
|
249
|
+
issues.push(issue("adopter-gate-package", "standardPackage", "adopter gate uses stale KFD package metadata"));
|
|
250
|
+
}
|
|
251
|
+
if (!SHA_PATTERN.test(text(gate?.source?.sha)) || !ROOT_PATTERN.test(text(gate?.source?.artifactRoot))) {
|
|
252
|
+
issues.push(issue("adopter-gate-source", "source", "adopter gate must bind one exact Buildchain source commit and artifact root"));
|
|
253
|
+
}
|
|
254
|
+
if (gate?.decisionWitness?.rootAlgorithm !== "sha256-buildchain-stable-json-v1"
|
|
255
|
+
|| !ROOT_PATTERN.test(text(gate?.decisionWitness?.root))
|
|
256
|
+
|| !ROOT_PATTERN.test(text(gate?.manifestVerificationReportRoot))
|
|
257
|
+
|| !ROOT_PATTERN.test(text(gate?.manifestBundleRoot))) {
|
|
258
|
+
issues.push(issue("adopter-gate-evidence-root", "", "decision, report, and bundle roots are required"));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function validateProjectedGates(gate, expectedSourceSha, issues) {
|
|
263
|
+
const seenStandards = new Set();
|
|
264
|
+
for (const [index, productGate] of (gate.gateResults || []).entries()) {
|
|
265
|
+
if (!PRODUCT_GATE_SET.has(productGate?.standard) || productGate?.status !== "passed" || !ROOT_PATTERN.test(text(productGate?.gateRoot))) {
|
|
266
|
+
issues.push(issue("adopter-gate-result", `gateResults[${index}]`, "KFD-4/5/7 gate projections must be passing and rooted"));
|
|
267
|
+
}
|
|
268
|
+
if (seenStandards.has(productGate?.standard)) issues.push(issue("adopter-gate-result-set", `gateResults[${index}]`, "product gate standards must be unique"));
|
|
269
|
+
seenStandards.add(productGate?.standard);
|
|
270
|
+
if (productGate?.sourceSha !== gate?.source?.sha || (expectedSourceSha && productGate?.sourceSha !== expectedSourceSha)) {
|
|
271
|
+
issues.push(issue("adopter-gate-source", `gateResults[${index}].sourceSha`, "product gate source must match the release source"));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (seenStandards.size !== PRODUCT_GATE_STANDARDS.length || PRODUCT_GATE_STANDARDS.some((standard) => !seenStandards.has(standard))) {
|
|
275
|
+
issues.push(issue("adopter-gate-result-set", "gateResults", "adopter gate must contain exactly one KFD-4, KFD-5, and KFD-7 product gate"));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function validateKfdAdopterManifestGate(gate, {
|
|
280
|
+
expectedSourceSha = "",
|
|
281
|
+
checkedAt = gate?.checkedAt || new Date().toISOString(),
|
|
282
|
+
} = {}) {
|
|
283
|
+
const issues = [];
|
|
284
|
+
if (!gate || gate.schemaVersion !== 1 || gate.contract !== KFD_ADOPTER_MANIFEST_GATE_CONTRACT) {
|
|
285
|
+
return { valid: false, issues: [issue("adopter-gate-contract", "", `gate must use ${KFD_ADOPTER_MANIFEST_GATE_CONTRACT} v1`)] };
|
|
286
|
+
}
|
|
287
|
+
validateGateDocument(gate, issues);
|
|
288
|
+
validateProjectedGates(gate, expectedSourceSha, issues);
|
|
289
|
+
if (gate.checkedAt !== checkedAt || gate?.verificationCut?.checkedAt !== checkedAt || !Number.isFinite(Date.parse(checkedAt))
|
|
290
|
+
|| !Number.isSafeInteger(gate?.verificationCut?.maxAgeSeconds) || gate.verificationCut.maxAgeSeconds < 0) {
|
|
291
|
+
issues.push(issue("adopter-gate-time", "verificationCut", "adopter gate verification cut does not match the requested cut"));
|
|
292
|
+
}
|
|
293
|
+
return { valid: issues.length === 0, issues };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function legacyStatus(row) {
|
|
297
|
+
if (row.state === "adopted") return "source-supported-release-bound";
|
|
298
|
+
if (row.state === "candidate") return "candidate";
|
|
299
|
+
if (row.state === "draft-evidence") return "draft-adopter-evidence";
|
|
300
|
+
return row.state;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function legacyRow(row, gates) {
|
|
304
|
+
const key = row.id.toLowerCase();
|
|
305
|
+
const productGate = gates.get(key);
|
|
306
|
+
return {
|
|
307
|
+
id: row.id,
|
|
308
|
+
key,
|
|
309
|
+
title: standardMetadata(key).title,
|
|
310
|
+
supportStatus: legacyStatus(row),
|
|
311
|
+
normative: { status: row.registryStatus, revision: standardMetadata(key).revision },
|
|
312
|
+
implementation: { status: row.implementationEvidence.length > 0 ? "implemented" : "not-declared" },
|
|
313
|
+
verification: { status: row.verificationEvidence.length > 0 || row.witnessBindings.length > 0 ? "passed" : "not-declared" },
|
|
314
|
+
buildchain: {
|
|
315
|
+
protocol: productGate ? `${KFD_PRODUCT_GATE_CONTRACT}/v1` : `${KFD_ADOPTER_MANIFEST_GATE_CONTRACT}/v1`,
|
|
316
|
+
gateStatus: productGate?.status || "manifest-verified",
|
|
317
|
+
},
|
|
318
|
+
releaseQualification: { shippedSupport: false },
|
|
319
|
+
claimClass: "standard-adopter-manifest-projection",
|
|
320
|
+
knownLimitations: [...row.gaps],
|
|
321
|
+
owner: BUILDCHAIN_ADOPTER_ID,
|
|
322
|
+
nextGate: "Release Passport artifact binding and independent release decision",
|
|
323
|
+
declaration: { state: row.state, usage: row.usage, root: kfdProductGateDigest(decisionWitnessRow(row)) },
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate } = {}) {
|
|
328
|
+
const validation = validateKfdAdopterManifestGate(manifestGate, {
|
|
329
|
+
expectedSourceSha: manifestGate?.source?.sha,
|
|
330
|
+
checkedAt: manifestGate?.checkedAt,
|
|
331
|
+
});
|
|
332
|
+
if (!validation.valid || manifest?.contract !== "kfd.adopter-conformance-manifest/v1") {
|
|
333
|
+
throw new Error("legacy support projection requires the exact passing standard adopter manifest authority");
|
|
334
|
+
}
|
|
335
|
+
const authorityIssues = [];
|
|
336
|
+
const bundle = verifyPublishedManifest(manifest, {
|
|
337
|
+
packageArtifactRoot: manifestGate.standardPackage.artifactRoot,
|
|
338
|
+
verifiedAt: manifestGate.verificationCut.checkedAt,
|
|
339
|
+
maxAgeSeconds: manifestGate.verificationCut.maxAgeSeconds,
|
|
340
|
+
}, authorityIssues);
|
|
341
|
+
const sourceSha = validateManifestIdentity(manifest, manifestGate.standardPackage.artifactRoot, manifestGate.source.sha, authorityIssues);
|
|
342
|
+
const decisionRoot = kfdProductGateDigest(manifest.decisions.map(decisionWitnessRow));
|
|
343
|
+
if (!bundle || sourceSha !== manifestGate.source.sha || manifest.adopter.artifact.root !== manifestGate.source.artifactRoot
|
|
344
|
+
|| bundle.roots.manifestRoot !== manifestGate.authority.manifestRoot
|
|
345
|
+
|| bundle.roots.verificationReportRoot !== manifestGate.manifestVerificationReportRoot
|
|
346
|
+
|| bundle.bundleRoot !== manifestGate.manifestBundleRoot
|
|
347
|
+
|| decisionRoot !== manifestGate.decisionWitness.root
|
|
348
|
+
|| manifest.kfdCut.registry.root !== manifestGate.standardPackage.registryRoot
|
|
349
|
+
|| manifest.kfdCut.verifierSetRoot !== manifestGate.standardPackage.verifierSetRoot) {
|
|
350
|
+
authorityIssues.push(issue("legacy-projection-authority", "manifest", "manifest does not match the exact gate authority closure"));
|
|
351
|
+
}
|
|
352
|
+
const gates = new Map(manifestGate.gateResults.map((entry) => [entry.standard, entry]));
|
|
353
|
+
for (const [standard, gate] of gates) {
|
|
354
|
+
const row = manifest.decisions.find((entry) => entry.id === standard.toUpperCase());
|
|
355
|
+
if (!(row?.verificationEvidence || []).some((entry) => entry?.root === gate.gateRoot)) {
|
|
356
|
+
authorityIssues.push(issue("legacy-projection-gate-binding", `manifest.decisions.${standard.toUpperCase()}`, "manifest does not bind the projected product gate root"));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (authorityIssues.length > 0) throw new Error(`legacy support projection authority failed: ${JSON.stringify(authorityIssues)}`);
|
|
360
|
+
return {
|
|
361
|
+
schemaVersion: 1,
|
|
362
|
+
contract: KFD_LEGACY_SUPPORT_MATRIX_CONTRACT,
|
|
363
|
+
authority: {
|
|
364
|
+
path: manifestGate.authority.path,
|
|
365
|
+
contract: manifest.contract,
|
|
366
|
+
root: manifestGate.authority.manifestRoot,
|
|
367
|
+
gateRoot: manifestGate.gateRoot,
|
|
368
|
+
},
|
|
369
|
+
upstream: {
|
|
370
|
+
package: manifestGate.standardPackage.name,
|
|
371
|
+
version: manifestGate.standardPackage.version,
|
|
372
|
+
artifactRoot: manifestGate.standardPackage.artifactRoot,
|
|
373
|
+
registryRoot: manifestGate.standardPackage.registryRoot,
|
|
374
|
+
verifierSetRoot: manifestGate.standardPackage.verifierSetRoot,
|
|
375
|
+
standardsSha256: standardsFileDigest(),
|
|
376
|
+
},
|
|
377
|
+
rows: manifest.decisions.map((row) => legacyRow(row, gates)),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function validateKfdLegacySupportMatrixProjection(matrix, { manifest, manifestGate } = {}) {
|
|
382
|
+
try {
|
|
383
|
+
const expected = createKfdLegacySupportMatrixProjection({ manifest, manifestGate });
|
|
384
|
+
return kfdProductGateDigest(matrix) === kfdProductGateDigest(expected)
|
|
385
|
+
? { valid: true, issues: [] }
|
|
386
|
+
: { valid: false, issues: [issue("legacy-projection-drift", "", "legacy support matrix differs from the sole standard adopter manifest projection")] };
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return { valid: false, issues: [issue("legacy-projection-authority", "", error.message)] };
|
|
389
|
+
}
|
|
390
|
+
}
|