@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.1
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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +146 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- package/scripts/web-surface-production-decision.mjs +19 -3
|
@@ -31,6 +31,7 @@ export const IMPACT_LEDGER_CONTRACT = "kungfu-buildchain-impact";
|
|
|
31
31
|
export const AGENT_INDEX_CONTRACT = "kungfu-buildchain-agent-index";
|
|
32
32
|
export const PRODUCT_MECHANISM_CONTRACT = "kungfu-buildchain-product-mechanism";
|
|
33
33
|
export const RELEASE_CHECK_REPORT_CONTRACT = "kungfu-buildchain-release-check-report";
|
|
34
|
+
export const RELEASE_EVIDENCE_ATTACHMENT_CONTRACT = "kungfu-buildchain-release-evidence-attachment";
|
|
34
35
|
export const KFD2_RELEASE_TRUST_PASSPORT_CONTRACT = "kungfu-buildchain-kfd-2-release-trust-passport-audit";
|
|
35
36
|
export const KFD2_TRUST_PROOF_CONTRACT = "kungfu-buildchain-kfd-2-trust-proof";
|
|
36
37
|
export const INVARIANT_PASSPORT_GATE_CONTRACT = "buildchain.invariant-passport-gate/v1";
|
|
@@ -42,6 +43,7 @@ const CONTRACTS = new Set([
|
|
|
42
43
|
IMPACT_LEDGER_CONTRACT,
|
|
43
44
|
AGENT_INDEX_CONTRACT,
|
|
44
45
|
PRODUCT_MECHANISM_CONTRACT,
|
|
46
|
+
RELEASE_EVIDENCE_ATTACHMENT_CONTRACT,
|
|
45
47
|
]);
|
|
46
48
|
|
|
47
49
|
function nowIso() {
|
|
@@ -249,6 +251,65 @@ function writeTextFile(filePath, value) {
|
|
|
249
251
|
fs.writeFileSync(filePath, value.endsWith("\n") ? value : `${value}\n`);
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
function normalizeReleaseEvidenceAttachment(meta, {
|
|
255
|
+
cwd,
|
|
256
|
+
expectedSourceSha,
|
|
257
|
+
expectedTag,
|
|
258
|
+
expectedChannel,
|
|
259
|
+
index,
|
|
260
|
+
} = {}) {
|
|
261
|
+
const document = meta?.value;
|
|
262
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
263
|
+
throw new Error(`releaseEvidenceJsons[${index}] must be a JSON object`);
|
|
264
|
+
}
|
|
265
|
+
const inputPath = resolveJsonInputPath(meta.path, { cwd });
|
|
266
|
+
if (!inputPath) {
|
|
267
|
+
throw new Error(`releaseEvidenceJsons[${index}] must be an existing JSON file path`);
|
|
268
|
+
}
|
|
269
|
+
if (Number(document.schemaVersion) !== 1) {
|
|
270
|
+
throw new Error(`releaseEvidenceJsons[${index}].schemaVersion must be 1`);
|
|
271
|
+
}
|
|
272
|
+
const id = nonEmptyString(document.id, `releaseEvidenceJsons[${index}].id`);
|
|
273
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id)) {
|
|
274
|
+
throw new Error(`releaseEvidenceJsons[${index}].id must use only letters, digits, dot, underscore, or hyphen`);
|
|
275
|
+
}
|
|
276
|
+
const contract = nonEmptyString(document.contract, `releaseEvidenceJsons[${index}].contract`);
|
|
277
|
+
const release = document.release;
|
|
278
|
+
if (!release || typeof release !== "object" || Array.isArray(release)) {
|
|
279
|
+
throw new Error(`releaseEvidenceJsons[${index}].release must be a JSON object`);
|
|
280
|
+
}
|
|
281
|
+
const sourceSha = nonEmptyString(release.sourceSha, `releaseEvidenceJsons[${index}].release.sourceSha`);
|
|
282
|
+
const tag = nonEmptyString(release.tag, `releaseEvidenceJsons[${index}].release.tag`);
|
|
283
|
+
const channel = nonEmptyString(release.channel, `releaseEvidenceJsons[${index}].release.channel`);
|
|
284
|
+
if (!/^[0-9a-f]{40}$/i.test(sourceSha)) {
|
|
285
|
+
throw new Error(`releaseEvidenceJsons[${index}].release.sourceSha must be a full 40-character Git SHA`);
|
|
286
|
+
}
|
|
287
|
+
if (!expectedSourceSha || sourceSha !== expectedSourceSha) {
|
|
288
|
+
throw new Error(`releaseEvidenceJsons[${index}].release.sourceSha must match the release passport source SHA`);
|
|
289
|
+
}
|
|
290
|
+
if (!expectedTag || tag !== expectedTag) {
|
|
291
|
+
throw new Error(`releaseEvidenceJsons[${index}].release.tag must match the release passport tag`);
|
|
292
|
+
}
|
|
293
|
+
if (!expectedChannel || channel !== expectedChannel) {
|
|
294
|
+
throw new Error(`releaseEvidenceJsons[${index}].release.channel must match the release passport channel`);
|
|
295
|
+
}
|
|
296
|
+
const relativePath = `release-evidence-${id}.json`;
|
|
297
|
+
return {
|
|
298
|
+
document,
|
|
299
|
+
inputPath,
|
|
300
|
+
reference: {
|
|
301
|
+
schemaVersion: 1,
|
|
302
|
+
contract: RELEASE_EVIDENCE_ATTACHMENT_CONTRACT,
|
|
303
|
+
id,
|
|
304
|
+
kind: optionalString(document.kind || "product-release-evidence"),
|
|
305
|
+
documentContract: contract,
|
|
306
|
+
path: relativePath,
|
|
307
|
+
sha256: sha256Text(stableJson(document)),
|
|
308
|
+
release: { sourceSha, tag, channel },
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
252
313
|
function inferPlatformFromName(name) {
|
|
253
314
|
const lower = String(name || "").toLowerCase();
|
|
254
315
|
if (lower.includes("apple-darwin") || lower.includes("darwin") || lower.includes("macos")) {
|
|
@@ -1165,6 +1226,7 @@ export function createReleasePassport({
|
|
|
1165
1226
|
kfdSupport = undefined,
|
|
1166
1227
|
kfdSupportEvidencePath = "",
|
|
1167
1228
|
invariantPassports = undefined,
|
|
1229
|
+
releaseEvidence = [],
|
|
1168
1230
|
kfdAgentHubEvidence = undefined,
|
|
1169
1231
|
kfdAgentHubEvidencePath = "",
|
|
1170
1232
|
controllerReceipts = [],
|
|
@@ -1366,6 +1428,7 @@ export function createReleasePassport({
|
|
|
1366
1428
|
...(normalizedKfdSupport ? { kfdSupport: normalizedKfdSupport } : {}),
|
|
1367
1429
|
...(normalizedKfdAgentHub ? { kfdAgentHub: normalizedKfdAgentHub } : {}),
|
|
1368
1430
|
...(invariantPassports ? { invariantPassports } : {}),
|
|
1431
|
+
...(releaseEvidence.length > 0 ? { releaseEvidence } : {}),
|
|
1369
1432
|
...(normalizedControllerReceipts.length > 0 ? { controllerReceipts: normalizedControllerReceipts } : {}),
|
|
1370
1433
|
...(normalizedGitHubArtifactAttestations.length > 0
|
|
1371
1434
|
? { githubArtifactAttestations: normalizedGitHubArtifactAttestations }
|
|
@@ -1414,6 +1477,7 @@ export function createReleasePassport({
|
|
|
1414
1477
|
kfdSupport: normalizedKfdSupport ? optionalString(kfdSupportEvidencePath || "kfd-support.json") : "",
|
|
1415
1478
|
kfdAgentHub: normalizedKfdAgentHub ? optionalString(kfdAgentHubEvidencePath || "kfd-agent-hub-evidence.json") : "",
|
|
1416
1479
|
invariantPassports: invariantPassports ? "invariantPassports" : "",
|
|
1480
|
+
releaseEvidence: releaseEvidence.map((entry) => entry.path),
|
|
1417
1481
|
impact: impactPath,
|
|
1418
1482
|
checkReport: checkReportPath,
|
|
1419
1483
|
agentIndex: agentIndexPath,
|
|
@@ -1458,6 +1522,7 @@ export function collectGitHubReleasePassport({
|
|
|
1458
1522
|
kfdProductGateJsons = [],
|
|
1459
1523
|
invariantPassportJsons = [],
|
|
1460
1524
|
invariantPassportCommand = "",
|
|
1525
|
+
releaseEvidenceJsons = [],
|
|
1461
1526
|
kfdAgentHubEvidenceJson = "",
|
|
1462
1527
|
controllerReceiptReferences = [],
|
|
1463
1528
|
githubArtifactAttestationPolicyJsons = [],
|
|
@@ -1533,6 +1598,15 @@ export function collectGitHubReleasePassport({
|
|
|
1533
1598
|
});
|
|
1534
1599
|
if (invariantPassportCommandMeta.value) invariantPassportMetas.push(invariantPassportCommandMeta);
|
|
1535
1600
|
const invariantPassports = createInvariantPassportGate(invariantPassportMetas);
|
|
1601
|
+
const releaseEvidenceMetas = (releaseEvidenceJsons || [])
|
|
1602
|
+
.filter(Boolean)
|
|
1603
|
+
.map((evidenceJson) =>
|
|
1604
|
+
parseJsonInputWithMeta(evidenceJson, undefined, {
|
|
1605
|
+
cwd,
|
|
1606
|
+
label: "releaseEvidenceJsons entry",
|
|
1607
|
+
}),
|
|
1608
|
+
)
|
|
1609
|
+
.filter((meta) => meta.value);
|
|
1536
1610
|
const kfdAgentHubEvidenceMeta = parseJsonInputWithMeta(
|
|
1537
1611
|
kfdAgentHubEvidenceJson,
|
|
1538
1612
|
undefined,
|
|
@@ -1560,6 +1634,28 @@ export function collectGitHubReleasePassport({
|
|
|
1560
1634
|
];
|
|
1561
1635
|
const resolvedTag = tag || release.tag_name || release.name || "";
|
|
1562
1636
|
const resolvedOutputDir = path.resolve(cwd, outputDir);
|
|
1637
|
+
const resolvedChannel = optionalString(releaseExtra.channel || publish.channel);
|
|
1638
|
+
const releaseEvidenceAttachments = releaseEvidenceMetas.map((meta, index) =>
|
|
1639
|
+
normalizeReleaseEvidenceAttachment(meta, {
|
|
1640
|
+
cwd,
|
|
1641
|
+
expectedSourceSha: sourceSha,
|
|
1642
|
+
expectedTag: resolvedTag,
|
|
1643
|
+
expectedChannel: resolvedChannel,
|
|
1644
|
+
index,
|
|
1645
|
+
}),
|
|
1646
|
+
);
|
|
1647
|
+
const releaseEvidenceIds = new Set();
|
|
1648
|
+
for (const { reference } of releaseEvidenceAttachments) {
|
|
1649
|
+
if (releaseEvidenceIds.has(reference.id)) {
|
|
1650
|
+
throw new Error(`release evidence attachment id must be unique: ${reference.id}`);
|
|
1651
|
+
}
|
|
1652
|
+
releaseEvidenceIds.add(reference.id);
|
|
1653
|
+
}
|
|
1654
|
+
for (const { inputPath, reference } of releaseEvidenceAttachments) {
|
|
1655
|
+
const destinationPath = path.join(resolvedOutputDir, reference.path);
|
|
1656
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
1657
|
+
fs.copyFileSync(inputPath, destinationPath);
|
|
1658
|
+
}
|
|
1563
1659
|
const resolvedCheckedAt = optionalString(checkedAt) || nowIso();
|
|
1564
1660
|
const productMechanism = defaultProductMechanism({ repository, productName });
|
|
1565
1661
|
const artifactEvidence = createArtifactEvidence({ assets, repository, tag: resolvedTag, sourceSha, workflow });
|
|
@@ -1638,6 +1734,7 @@ export function collectGitHubReleasePassport({
|
|
|
1638
1734
|
kfdSupport,
|
|
1639
1735
|
kfdSupportEvidencePath: kfdSupport ? "kfd-support.json" : "",
|
|
1640
1736
|
invariantPassports,
|
|
1737
|
+
releaseEvidence: releaseEvidenceAttachments.map(({ reference }) => reference),
|
|
1641
1738
|
kfdAgentHubEvidence: kfdAgentHubEvidenceMeta.value
|
|
1642
1739
|
? { ...kfdAgentHubEvidenceMeta, path: "kfd-agent-hub-evidence.json" }
|
|
1643
1740
|
: undefined,
|
|
@@ -1661,6 +1758,7 @@ export function collectGitHubReleasePassport({
|
|
|
1661
1758
|
productMechanism,
|
|
1662
1759
|
kfdAgentHubEvidence: kfdAgentHubEvidenceMeta.value,
|
|
1663
1760
|
kfdSupportEvidence: kfdSupport,
|
|
1761
|
+
releaseEvidenceDocuments: releaseEvidenceAttachments,
|
|
1664
1762
|
checkedAt: resolvedCheckedAt,
|
|
1665
1763
|
});
|
|
1666
1764
|
const files = {
|
|
@@ -1682,7 +1780,9 @@ export function collectGitHubReleasePassport({
|
|
|
1682
1780
|
schemaVersion: 1,
|
|
1683
1781
|
contract: "kungfu-buildchain-release-passport-collection",
|
|
1684
1782
|
outputDir: resolvedOutputDir,
|
|
1685
|
-
files: Object.keys(files)
|
|
1783
|
+
files: Object.keys(files)
|
|
1784
|
+
.concat(releaseEvidenceAttachments.map(({ reference }) => reference.path))
|
|
1785
|
+
.concat("llms.txt"),
|
|
1686
1786
|
passport,
|
|
1687
1787
|
artifactEvidence,
|
|
1688
1788
|
checkReport,
|
|
@@ -1957,18 +2057,17 @@ function findEvidenceForArtifact(artifact, evidenceIndex) {
|
|
|
1957
2057
|
return undefined;
|
|
1958
2058
|
}
|
|
1959
2059
|
|
|
1960
|
-
|
|
2060
|
+
function validateReleaseEvidenceContracts({
|
|
1961
2061
|
passport,
|
|
1962
2062
|
artifactEvidence,
|
|
1963
|
-
publishEvidence,
|
|
1964
2063
|
impact,
|
|
1965
2064
|
agentIndex,
|
|
1966
2065
|
productMechanism,
|
|
1967
2066
|
kfdAgentHubEvidence,
|
|
1968
2067
|
kfdSupportEvidence,
|
|
1969
|
-
checkedAt
|
|
1970
|
-
|
|
1971
|
-
|
|
2068
|
+
checkedAt,
|
|
2069
|
+
issues,
|
|
2070
|
+
}) {
|
|
1972
2071
|
validateContract(passport, RELEASE_PASSPORT_CONTRACT, "passport", issues);
|
|
1973
2072
|
validateContract(artifactEvidence, ARTIFACT_EVIDENCE_CONTRACT, "artifactEvidence", issues);
|
|
1974
2073
|
validateContract(impact, IMPACT_LEDGER_CONTRACT, "impact", issues);
|
|
@@ -2026,79 +2125,142 @@ export function createReleaseCheckReport({
|
|
|
2026
2125
|
));
|
|
2027
2126
|
}
|
|
2028
2127
|
}
|
|
2128
|
+
}
|
|
2029
2129
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
const artifacts = Array.isArray(passport?.artifacts) ? passport.artifacts : [];
|
|
2038
|
-
const normalizedPublishEvidence = normalizePublishEvidence(publishEvidence);
|
|
2039
|
-
const publishEvidenceSupplied =
|
|
2040
|
-
Boolean(passport?.evidence?.publishEvidence) ||
|
|
2041
|
-
(publishEvidence && typeof publishEvidence === "object" && Object.keys(publishEvidence).length > 0);
|
|
2130
|
+
function validateReleaseEvidenceAttachments({
|
|
2131
|
+
passport,
|
|
2132
|
+
artifactEvidence,
|
|
2133
|
+
normalizedPublishEvidence,
|
|
2134
|
+
releaseEvidenceDocuments,
|
|
2135
|
+
issues,
|
|
2136
|
+
}) {
|
|
2042
2137
|
const evidenceArtifacts = [
|
|
2043
2138
|
...(Array.isArray(artifactEvidence?.artifacts) ? artifactEvidence.artifacts : []),
|
|
2044
2139
|
...(normalizedPublishEvidence?.artifacts || []),
|
|
2045
2140
|
];
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2141
|
+
const releaseEvidence = Array.isArray(passport?.releaseEvidence) ? passport.releaseEvidence : [];
|
|
2142
|
+
const releaseEvidenceByCoordinate = new Map(
|
|
2143
|
+
releaseEvidenceDocuments.map((entry) => [
|
|
2144
|
+
`${entry?.reference?.id || ""}\0${entry?.reference?.path || ""}`,
|
|
2145
|
+
entry,
|
|
2146
|
+
]),
|
|
2147
|
+
);
|
|
2148
|
+
const releaseEvidenceIds = new Set();
|
|
2149
|
+
const releaseEvidencePaths = new Set();
|
|
2150
|
+
for (const [index, reference] of releaseEvidence.entries()) {
|
|
2151
|
+
const label = `releaseEvidence[${index}]`;
|
|
2152
|
+
if (!reference || typeof reference !== "object" || Array.isArray(reference)) {
|
|
2153
|
+
issues.push(issue("error", `${label}.object`, `${label} must be a JSON object`));
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
if (reference.contract !== RELEASE_EVIDENCE_ATTACHMENT_CONTRACT) {
|
|
2157
|
+
issues.push(issue("error", `${label}.contract`, `${label}.contract must be ${RELEASE_EVIDENCE_ATTACHMENT_CONTRACT}`));
|
|
2158
|
+
}
|
|
2159
|
+
if (!reference.id || !reference.path || !reference.sha256 || !reference.documentContract) {
|
|
2160
|
+
issues.push(issue("error", `${label}.identity`, `${label} must include id, path, sha256, and documentContract`));
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(reference.id)) {
|
|
2164
|
+
issues.push(issue("error", `${label}.id`, `${label}.id must use only letters, digits, dot, underscore, or hyphen`));
|
|
2165
|
+
}
|
|
2166
|
+
const expectedPath = `release-evidence-${reference.id}.json`;
|
|
2167
|
+
if (reference.path !== expectedPath) {
|
|
2168
|
+
issues.push(issue("error", `${label}.path`, `${label}.path must be ${expectedPath}`));
|
|
2169
|
+
}
|
|
2170
|
+
if (releaseEvidenceIds.has(reference.id)) {
|
|
2171
|
+
issues.push(issue("error", `${label}.id`, `${label}.id must be unique`));
|
|
2172
|
+
}
|
|
2173
|
+
if (releaseEvidencePaths.has(reference.path)) {
|
|
2174
|
+
issues.push(issue("error", `${label}.path`, `${label}.path must be unique`));
|
|
2175
|
+
}
|
|
2176
|
+
releaseEvidenceIds.add(reference.id);
|
|
2177
|
+
releaseEvidencePaths.add(reference.path);
|
|
2178
|
+
const loaded = releaseEvidenceByCoordinate.get(`${reference.id}\0${reference.path}`);
|
|
2179
|
+
if (!loaded?.document) {
|
|
2180
|
+
issues.push(issue("error", `${label}.missing`, `${label} document is not independently retrievable`, {
|
|
2181
|
+
id: reference.id,
|
|
2182
|
+
path: reference.path,
|
|
2183
|
+
}));
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
const document = loaded.document;
|
|
2187
|
+
if (document.contract !== reference.documentContract) {
|
|
2188
|
+
issues.push(issue("error", `${label}.documentContract`, `${label} document contract differs from its passport reference`));
|
|
2189
|
+
}
|
|
2190
|
+
const digest = sha256Text(stableJson(document));
|
|
2191
|
+
if (digest !== reference.sha256) {
|
|
2192
|
+
issues.push(issue("error", `${label}.sha256`, `${label} document digest differs from its passport reference`));
|
|
2193
|
+
}
|
|
2194
|
+
for (const field of ["sourceSha", "tag", "channel"]) {
|
|
2195
|
+
const expected = passport?.release?.[field] || "";
|
|
2196
|
+
const declared = document?.release?.[field] || "";
|
|
2197
|
+
if (!declared || declared !== expected || reference?.release?.[field] !== expected) {
|
|
2198
|
+
issues.push(issue("error", `${label}.release.${field}`, `${label} ${field} must match the release passport`));
|
|
2086
2199
|
}
|
|
2087
2200
|
}
|
|
2088
|
-
|
|
2089
|
-
|
|
2201
|
+
}
|
|
2202
|
+
return { evidenceArtifacts, releaseEvidence };
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
function validatePublishEvidenceSection({ passport, publishEvidence, normalizedPublishEvidence, issues }) {
|
|
2206
|
+
const publishEvidenceSupplied =
|
|
2207
|
+
Boolean(passport?.evidence?.publishEvidence) ||
|
|
2208
|
+
(publishEvidence && typeof publishEvidence === "object" && Object.keys(publishEvidence).length > 0);
|
|
2209
|
+
if (!publishEvidenceSupplied && !passport?.packageSet) {
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
const checkPublishField = (value, label) => {
|
|
2213
|
+
if (!value) {
|
|
2214
|
+
issues.push(issue("error", `publishEvidence.${label}`, `publishEvidence.${label} is required`));
|
|
2090
2215
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2216
|
+
};
|
|
2217
|
+
if (Number(normalizedPublishEvidence?.schema) !== 1) {
|
|
2218
|
+
issues.push(issue("error", "publishEvidence.schema", "publishEvidence.schema must be 1"));
|
|
2219
|
+
}
|
|
2220
|
+
for (const field of ["version", "channel", "sourceSha", "releaseSha", "targetRef", "releaseMaterialSha", "publishToolingSha"]) {
|
|
2221
|
+
checkPublishField(normalizedPublishEvidence?.[field], field);
|
|
2222
|
+
}
|
|
2223
|
+
if (!Array.isArray(normalizedPublishEvidence?.artifacts) || normalizedPublishEvidence.artifacts.length === 0) {
|
|
2224
|
+
issues.push(issue("error", "publishEvidence.artifacts", "publishEvidence.artifacts must include published artifacts"));
|
|
2225
|
+
}
|
|
2226
|
+
if ((normalizedPublishEvidence?.artifacts || []).some((artifact) => artifact.action)) {
|
|
2227
|
+
try {
|
|
2228
|
+
const validation = validateTransactionPublishEvidence({
|
|
2229
|
+
evidence: publishEvidence,
|
|
2230
|
+
version: normalizedPublishEvidence.version,
|
|
2231
|
+
channel: normalizedPublishEvidence.channel,
|
|
2232
|
+
sourceSha: normalizedPublishEvidence.sourceSha,
|
|
2233
|
+
releaseSha: normalizedPublishEvidence.releaseSha,
|
|
2234
|
+
targetRef: normalizedPublishEvidence.targetRef,
|
|
2235
|
+
releaseMaterialSha: normalizedPublishEvidence.releaseMaterialSha,
|
|
2236
|
+
publishToolingSha: normalizedPublishEvidence.publishToolingSha,
|
|
2237
|
+
});
|
|
2238
|
+
for (const error of validation.errors) {
|
|
2239
|
+
issues.push(issue("error", "publishEvidence.artifactProvenance", error));
|
|
2240
|
+
}
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
issues.push(issue(
|
|
2243
|
+
"error",
|
|
2244
|
+
"publishEvidence.artifactProvenance",
|
|
2245
|
+
`publish artifact provenance is invalid: ${error.message}`,
|
|
2246
|
+
));
|
|
2093
2247
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2248
|
+
}
|
|
2249
|
+
for (const field of ["sourceSha", "releaseSha", "targetRef"]) {
|
|
2250
|
+
if (passport?.release?.[field] && normalizedPublishEvidence?.[field] && passport.release[field] !== normalizedPublishEvidence[field]) {
|
|
2251
|
+
issues.push(issue(
|
|
2252
|
+
"error",
|
|
2253
|
+
`publishEvidence.${field}.mismatch`,
|
|
2254
|
+
`publishEvidence.${field} must match passport.release.${field}`,
|
|
2255
|
+
));
|
|
2096
2256
|
}
|
|
2097
2257
|
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function validateReleaseArtifacts({ artifacts, evidenceIndex, issues }) {
|
|
2098
2261
|
if (artifacts.length === 0) {
|
|
2099
2262
|
issues.push(issue("error", "artifacts.empty", "release passport must list at least one artifact"));
|
|
2100
2263
|
}
|
|
2101
|
-
const evidenceIndex = indexEvidenceArtifacts(evidenceArtifacts);
|
|
2102
2264
|
for (const artifact of artifacts) {
|
|
2103
2265
|
if (!artifact.name) {
|
|
2104
2266
|
issues.push(issue("error", "artifact.name", "artifact name is required"));
|
|
@@ -2119,15 +2281,7 @@ export function createReleaseCheckReport({
|
|
|
2119
2281
|
if (artifact.digest && evidenceDigest && artifact.digest !== evidenceDigest && artifact.digest !== `sha256:${evidenceDigest}`) {
|
|
2120
2282
|
issues.push(issue("error", "artifact.digest.mismatch", `artifact ${artifact.name} digest differs between passport and evidence`));
|
|
2121
2283
|
}
|
|
2122
|
-
for (const field of [
|
|
2123
|
-
"action",
|
|
2124
|
-
"platform",
|
|
2125
|
-
"contract_major",
|
|
2126
|
-
"parent_digest",
|
|
2127
|
-
"content",
|
|
2128
|
-
"release",
|
|
2129
|
-
"verification",
|
|
2130
|
-
]) {
|
|
2284
|
+
for (const field of ["action", "platform", "contract_major", "parent_digest", "content", "release", "verification"]) {
|
|
2131
2285
|
if (
|
|
2132
2286
|
Object.prototype.hasOwnProperty.call(evidence, field) &&
|
|
2133
2287
|
stableJson(artifact[field]) !== stableJson(evidence[field])
|
|
@@ -2140,47 +2294,54 @@ export function createReleaseCheckReport({
|
|
|
2140
2294
|
}
|
|
2141
2295
|
}
|
|
2142
2296
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
const artifact = findEvidenceForArtifact({
|
|
2157
|
-
group: "node",
|
|
2158
|
-
kind: "npm",
|
|
2159
|
-
name: entry.name,
|
|
2160
|
-
ref: entry.version,
|
|
2161
|
-
digest: entry.digest,
|
|
2162
|
-
}, evidenceIndex);
|
|
2163
|
-
if (!artifact) {
|
|
2164
|
-
issues.push(issue("error", `${label}.artifact`, `${label} must have matching npm artifact evidence`, {
|
|
2165
|
-
role,
|
|
2166
|
-
name: entry.name,
|
|
2167
|
-
version: entry.version,
|
|
2168
|
-
}));
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
};
|
|
2172
|
-
checkPackageEntry(main, "packageSet.main", "main");
|
|
2173
|
-
const platforms = Array.isArray(passport.packageSet.platforms) ? passport.packageSet.platforms : [];
|
|
2174
|
-
if (platforms.length < 3) {
|
|
2175
|
-
issues.push(issue("error", "packageSet.platforms", "packageSet must include at least three platform packages"));
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function validatePackageSet({ passport, evidenceIndex, issues }) {
|
|
2300
|
+
if (!passport?.packageSet) {
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const main = passport.packageSet.main || {};
|
|
2304
|
+
const checkPackageEntry = (entry, label, role) => {
|
|
2305
|
+
if (!entry.name || !entry.version) {
|
|
2306
|
+
issues.push(issue("error", `${label}.identity`, `${label} must include name and version`));
|
|
2307
|
+
}
|
|
2308
|
+
if (!entry.distTag) {
|
|
2309
|
+
issues.push(issue("error", `${label}.distTag`, `${label} must include distTag`));
|
|
2176
2310
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2311
|
+
if (!entry.digest) {
|
|
2312
|
+
issues.push(issue("error", `${label}.digest`, `${label} must include digest`));
|
|
2179
2313
|
}
|
|
2180
|
-
if (
|
|
2181
|
-
|
|
2314
|
+
if (entry.name && entry.version) {
|
|
2315
|
+
const artifact = findEvidenceForArtifact({
|
|
2316
|
+
group: "node",
|
|
2317
|
+
kind: "npm",
|
|
2318
|
+
name: entry.name,
|
|
2319
|
+
ref: entry.version,
|
|
2320
|
+
digest: entry.digest,
|
|
2321
|
+
}, evidenceIndex);
|
|
2322
|
+
if (!artifact) {
|
|
2323
|
+
issues.push(issue("error", `${label}.artifact`, `${label} must have matching npm artifact evidence`, {
|
|
2324
|
+
role,
|
|
2325
|
+
name: entry.name,
|
|
2326
|
+
version: entry.version,
|
|
2327
|
+
}));
|
|
2328
|
+
}
|
|
2182
2329
|
}
|
|
2330
|
+
};
|
|
2331
|
+
checkPackageEntry(main, "packageSet.main", "main");
|
|
2332
|
+
const platforms = Array.isArray(passport.packageSet.platforms) ? passport.packageSet.platforms : [];
|
|
2333
|
+
if (platforms.length < 3) {
|
|
2334
|
+
issues.push(issue("error", "packageSet.platforms", "packageSet must include at least three platform packages"));
|
|
2335
|
+
}
|
|
2336
|
+
for (const [index, entry] of platforms.entries()) {
|
|
2337
|
+
checkPackageEntry(entry, `packageSet.platforms[${index}]`, "platform");
|
|
2183
2338
|
}
|
|
2339
|
+
if (!Array.isArray(passport?.publish?.packages) || passport.publish.packages.length !== 1 + platforms.length) {
|
|
2340
|
+
issues.push(issue("error", "publish.packages", "publish.packages must summarize main and platform packages"));
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
function validateReleaseState({ passport, issues }) {
|
|
2184
2345
|
if (passport?.trustedPublishing) {
|
|
2185
2346
|
if (!passport.trustedPublishing.provider) {
|
|
2186
2347
|
issues.push(issue("error", "trustedPublishing.provider", "trustedPublishing.provider is required"));
|
|
@@ -2198,17 +2359,10 @@ export function createReleaseCheckReport({
|
|
|
2198
2359
|
} else if (passport.transaction.state !== "complete") {
|
|
2199
2360
|
issues.push(issue("error", "transaction.state", "release passport transaction state must be complete"));
|
|
2200
2361
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
issues.push(issue("error", "transaction.releaseSha", "transaction.releaseSha is required"));
|
|
2206
|
-
}
|
|
2207
|
-
if (!passport.transaction.releaseMaterialSha) {
|
|
2208
|
-
issues.push(issue("error", "transaction.releaseMaterialSha", "transaction.releaseMaterialSha is required"));
|
|
2209
|
-
}
|
|
2210
|
-
if (!passport.transaction.stateRef) {
|
|
2211
|
-
issues.push(issue("error", "transaction.stateRef", "transaction.stateRef is required"));
|
|
2362
|
+
for (const field of ["exactTag", "releaseSha", "releaseMaterialSha", "stateRef"]) {
|
|
2363
|
+
if (!passport.transaction[field]) {
|
|
2364
|
+
issues.push(issue("error", `transaction.${field}`, `transaction.${field} is required`));
|
|
2365
|
+
}
|
|
2212
2366
|
}
|
|
2213
2367
|
}
|
|
2214
2368
|
if (passport?.anchorManifest) {
|
|
@@ -2219,77 +2373,92 @@ export function createReleaseCheckReport({
|
|
|
2219
2373
|
issues.push(issue("error", "anchorManifest.fields", "anchorManifest.fields must be an object"));
|
|
2220
2374
|
}
|
|
2221
2375
|
}
|
|
2222
|
-
if (passport?.
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2376
|
+
if (!passport?.runnerPolicy?.productionDefault) {
|
|
2377
|
+
issues.push(issue("warning", "runnerPolicy.productionDefault", "runner policy should record the production default"));
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function validateVersionMaterial({ passport, issues }) {
|
|
2382
|
+
if (!passport?.versionMaterial) {
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
if (passport.versionMaterial.contract !== "kungfu-buildchain-anchored-version-material/v1") {
|
|
2386
|
+
issues.push(issue(
|
|
2387
|
+
"error",
|
|
2388
|
+
"versionMaterial.contract",
|
|
2389
|
+
"versionMaterial contract must be kungfu-buildchain-anchored-version-material/v1",
|
|
2390
|
+
));
|
|
2391
|
+
}
|
|
2392
|
+
if (!passport.versionMaterial.alpha?.tree || !passport.versionMaterial.release?.tree) {
|
|
2393
|
+
issues.push(issue(
|
|
2394
|
+
"error",
|
|
2395
|
+
"versionMaterial.tree",
|
|
2396
|
+
"versionMaterial must record alpha and release tree identities",
|
|
2397
|
+
));
|
|
2398
|
+
}
|
|
2399
|
+
const allowedPaths = Array.isArray(passport.versionMaterial.allowedPaths)
|
|
2400
|
+
? passport.versionMaterial.allowedPaths
|
|
2401
|
+
: [];
|
|
2402
|
+
const derivedFiles = Array.isArray(passport.versionMaterial.derivedFiles)
|
|
2403
|
+
? passport.versionMaterial.derivedFiles
|
|
2404
|
+
: [];
|
|
2405
|
+
for (const [index, file] of derivedFiles.entries()) {
|
|
2406
|
+
if (!file?.path || !file?.sha256 || !allowedPaths.includes(file.path)) {
|
|
2231
2407
|
issues.push(issue(
|
|
2232
2408
|
"error",
|
|
2233
|
-
|
|
2234
|
-
"
|
|
2409
|
+
`versionMaterial.derivedFiles[${index}]`,
|
|
2410
|
+
"derived version material must have a path, digest, and matching allowed path",
|
|
2235
2411
|
));
|
|
2236
2412
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
? passport.versionMaterial.derivedFiles
|
|
2413
|
+
}
|
|
2414
|
+
for (const side of ["alpha", "release"]) {
|
|
2415
|
+
const material = Array.isArray(passport.versionMaterial[side]?.material)
|
|
2416
|
+
? passport.versionMaterial[side].material
|
|
2242
2417
|
: [];
|
|
2243
|
-
for (const [index, file] of
|
|
2244
|
-
if (
|
|
2418
|
+
for (const [index, file] of material.entries()) {
|
|
2419
|
+
if (
|
|
2420
|
+
!file?.path ||
|
|
2421
|
+
!allowedPaths.includes(file.path) ||
|
|
2422
|
+
file.present !== true ||
|
|
2423
|
+
!/^sha256:[0-9a-f]{64}$/.test(file.sha256 || "")
|
|
2424
|
+
) {
|
|
2245
2425
|
issues.push(issue(
|
|
2246
2426
|
"error",
|
|
2247
|
-
`versionMaterial.
|
|
2248
|
-
"
|
|
2427
|
+
`versionMaterial.${side}.material[${index}]`,
|
|
2428
|
+
"version material must have an allowed path, present bytes, and sha256 digest",
|
|
2249
2429
|
));
|
|
2250
2430
|
}
|
|
2251
2431
|
}
|
|
2252
|
-
for (const side of ["alpha", "release"]) {
|
|
2253
|
-
const material = Array.isArray(passport.versionMaterial[side]?.material)
|
|
2254
|
-
? passport.versionMaterial[side].material
|
|
2255
|
-
: [];
|
|
2256
|
-
for (const [index, file] of material.entries()) {
|
|
2257
|
-
if (
|
|
2258
|
-
!file?.path ||
|
|
2259
|
-
!allowedPaths.includes(file.path) ||
|
|
2260
|
-
file.present !== true ||
|
|
2261
|
-
!/^sha256:[0-9a-f]{64}$/.test(file.sha256 || "")
|
|
2262
|
-
) {
|
|
2263
|
-
issues.push(issue(
|
|
2264
|
-
"error",
|
|
2265
|
-
`versionMaterial.${side}.material[${index}]`,
|
|
2266
|
-
"version material must have an allowed path, present bytes, and sha256 digest",
|
|
2267
|
-
));
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
if (!passport?.runnerPolicy?.productionDefault) {
|
|
2273
|
-
issues.push(issue("warning", "runnerPolicy.productionDefault", "runner policy should record the production default"));
|
|
2274
2432
|
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function createReleaseImpactContext({ passport, impact }) {
|
|
2275
2436
|
const surfaceImpacts = Array.isArray(impact?.surfaceImpacts) ? impact.surfaceImpacts : [];
|
|
2276
2437
|
const passportSurfaceImpacts = Array.isArray(passport?.surfaceImpacts) ? passport.surfaceImpacts : [];
|
|
2277
2438
|
const impactRelease = impact?.release && typeof impact.release === "object" && !Array.isArray(impact.release)
|
|
2278
2439
|
? impact.release
|
|
2279
2440
|
: {};
|
|
2280
|
-
const impactVersion = optionalString(impactRelease.version);
|
|
2281
|
-
const impactLine = optionalString(impactRelease.line);
|
|
2282
2441
|
const passportVersion = optionalString(passport?.release?.publishedVersion || passport?.release?.package?.version);
|
|
2283
2442
|
const passportTargetRef = optionalString(passport?.release?.targetRef || passport?.release?.target_ref);
|
|
2284
2443
|
const passportTargetLineMatch = passportTargetRef.match(/^(?:alpha|release)\/v(\d+)\/v\1\.(\d+)$/);
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2444
|
+
return {
|
|
2445
|
+
surfaceImpacts,
|
|
2446
|
+
passportSurfaceImpacts,
|
|
2447
|
+
impactVersion: optionalString(impactRelease.version),
|
|
2448
|
+
impactLine: optionalString(impactRelease.line),
|
|
2449
|
+
passportVersion,
|
|
2450
|
+
passportLine: optionalString(
|
|
2451
|
+
passport?.release?.line ||
|
|
2452
|
+
(passportTargetLineMatch ? `v${passportTargetLineMatch[1]}.${passportTargetLineMatch[2]}` : ""),
|
|
2453
|
+
),
|
|
2454
|
+
impactClassification: normalizeImpactLevel(impact?.classification),
|
|
2455
|
+
declaredFinalImpact: normalizeImpactLevel(impact?.versionImpact?.final || impact?.classification),
|
|
2456
|
+
computedFinalImpact: highestImpactLevel(surfaceImpacts.map((entry) => entry.impact)),
|
|
2457
|
+
requiredSurfaceImpacts: surfaceImpactRequirement({ passport, impact }),
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
function validatePassportTrustSections({ passport, issues }) {
|
|
2293
2462
|
const kfd1Metadata = resolveKfd1Metadata();
|
|
2294
2463
|
issues.push(...validateKfd1ReleaseGateEvidence(passport?.[kfd1Metadata.key], { metadata: kfd1Metadata }));
|
|
2295
2464
|
validateKfd2ReleaseTrustPassportAudit(passport?.["kfd-2"], issues);
|
|
@@ -2305,35 +2474,51 @@ export function createReleaseCheckReport({
|
|
|
2305
2474
|
issues.push(issue("error", "kfd-3.metadata", error.message));
|
|
2306
2475
|
}
|
|
2307
2476
|
}
|
|
2308
|
-
if (passport?.invariantPassports) {
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (!Array.isArray(entry.platforms) || entry.platforms.length === 0) issues.push(issue("error", `${prefix}.platforms`, `${prefix}.platforms must be non-empty`));
|
|
2334
|
-
if (!Array.isArray(entry.residualRisk)) issues.push(issue("error", `${prefix}.residualRisk`, `${prefix}.residualRisk must be an array`));
|
|
2477
|
+
if (!passport?.invariantPassports) {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const section = passport.invariantPassports;
|
|
2481
|
+
if (section.contract !== INVARIANT_PASSPORT_GATE_CONTRACT) {
|
|
2482
|
+
issues.push(issue("error", "invariantPassports.contract", `invariantPassports.contract must be ${INVARIANT_PASSPORT_GATE_CONTRACT}`));
|
|
2483
|
+
}
|
|
2484
|
+
if (section.result !== "passed") {
|
|
2485
|
+
issues.push(issue("error", "invariantPassports.result", "invariantPassports.result must be passed"));
|
|
2486
|
+
}
|
|
2487
|
+
if (!Array.isArray(section.passports) || section.passports.length === 0) {
|
|
2488
|
+
issues.push(issue("error", "invariantPassports.empty", "invariantPassports must contain at least one verified passport"));
|
|
2489
|
+
}
|
|
2490
|
+
const acceptedSourceShas = new Set([
|
|
2491
|
+
passport?.release?.sourceSha,
|
|
2492
|
+
passport?.release?.builtSourceSha,
|
|
2493
|
+
passport?.release?.promotionChannelSha,
|
|
2494
|
+
].filter(Boolean));
|
|
2495
|
+
for (const [index, entry] of (section.passports || []).entries()) {
|
|
2496
|
+
const prefix = `invariantPassports.passports[${index}]`;
|
|
2497
|
+
if (entry.verdict !== "verified") issues.push(issue("error", `${prefix}.verdict`, `${prefix}.verdict must be verified`));
|
|
2498
|
+
if (entry.coverage?.complete !== true) issues.push(issue("error", `${prefix}.coverage`, `${prefix}.coverage.complete must be true`));
|
|
2499
|
+
if (entry.source?.dirty !== false) issues.push(issue("error", `${prefix}.source.dirty`, `${prefix}.source.dirty must be false`));
|
|
2500
|
+
if (acceptedSourceShas.size > 0 && !acceptedSourceShas.has(entry.source?.revision)) {
|
|
2501
|
+
issues.push(issue("error", `${prefix}.source.revision`, `${prefix}.source.revision must match a release source identity`));
|
|
2335
2502
|
}
|
|
2503
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(optionalString(entry.passportRoot))) issues.push(issue("error", `${prefix}.passportRoot`, `${prefix}.passportRoot is invalid`));
|
|
2504
|
+
if (!Array.isArray(entry.platforms) || entry.platforms.length === 0) issues.push(issue("error", `${prefix}.platforms`, `${prefix}.platforms must be non-empty`));
|
|
2505
|
+
if (!Array.isArray(entry.residualRisk)) issues.push(issue("error", `${prefix}.residualRisk`, `${prefix}.residualRisk must be an array`));
|
|
2336
2506
|
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
function validateReleaseImpact({ passport, impact, context, issues }) {
|
|
2510
|
+
const {
|
|
2511
|
+
surfaceImpacts,
|
|
2512
|
+
passportSurfaceImpacts,
|
|
2513
|
+
impactVersion,
|
|
2514
|
+
impactLine,
|
|
2515
|
+
passportVersion,
|
|
2516
|
+
passportLine,
|
|
2517
|
+
impactClassification,
|
|
2518
|
+
declaredFinalImpact,
|
|
2519
|
+
computedFinalImpact,
|
|
2520
|
+
requiredSurfaceImpacts,
|
|
2521
|
+
} = context;
|
|
2337
2522
|
if (impactVersion) {
|
|
2338
2523
|
if (!impactLine) {
|
|
2339
2524
|
issues.push(issue("error", "impact.release.line", "version-bound impact requires release.line"));
|
|
@@ -2397,6 +2582,21 @@ export function createReleaseCheckReport({
|
|
|
2397
2582
|
if (passport?.versionImpact?.final && impact?.versionImpact?.final && passport.versionImpact.final !== impact.versionImpact.final) {
|
|
2398
2583
|
issues.push(issue("error", "passport.versionImpact.final", "passport.versionImpact.final must match impact.versionImpact.final"));
|
|
2399
2584
|
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
function buildReleaseCheckReport({
|
|
2588
|
+
checkedAt,
|
|
2589
|
+
issues,
|
|
2590
|
+
requiredSurfaceImpacts,
|
|
2591
|
+
artifacts,
|
|
2592
|
+
evidenceArtifacts,
|
|
2593
|
+
passport,
|
|
2594
|
+
impact,
|
|
2595
|
+
agentIndex,
|
|
2596
|
+
productMechanism,
|
|
2597
|
+
surfaceImpacts,
|
|
2598
|
+
releaseEvidence,
|
|
2599
|
+
}) {
|
|
2400
2600
|
const ok = issues.every((entry) => entry.level !== "error");
|
|
2401
2601
|
return {
|
|
2402
2602
|
schemaVersion: 1,
|
|
@@ -2423,46 +2623,122 @@ export function createReleaseCheckReport({
|
|
|
2423
2623
|
surfaceImpactsRequired: requiredSurfaceImpacts.required,
|
|
2424
2624
|
surfaceImpactCount: surfaceImpacts.length,
|
|
2425
2625
|
versionImpact: impact?.versionImpact?.final || "",
|
|
2626
|
+
releaseEvidenceCount: releaseEvidence.length,
|
|
2426
2627
|
},
|
|
2427
2628
|
issues,
|
|
2428
2629
|
};
|
|
2429
2630
|
}
|
|
2430
2631
|
|
|
2431
|
-
export
|
|
2632
|
+
export function createReleaseCheckReport({
|
|
2633
|
+
passport,
|
|
2634
|
+
artifactEvidence,
|
|
2635
|
+
publishEvidence,
|
|
2636
|
+
impact,
|
|
2637
|
+
agentIndex,
|
|
2638
|
+
productMechanism,
|
|
2639
|
+
kfdAgentHubEvidence,
|
|
2640
|
+
kfdSupportEvidence,
|
|
2641
|
+
releaseEvidenceDocuments = [],
|
|
2642
|
+
checkedAt = nowIso(),
|
|
2643
|
+
} = {}) {
|
|
2644
|
+
const issues = [];
|
|
2645
|
+
validateReleaseEvidenceContracts({
|
|
2646
|
+
passport,
|
|
2647
|
+
artifactEvidence,
|
|
2648
|
+
impact,
|
|
2649
|
+
agentIndex,
|
|
2650
|
+
productMechanism,
|
|
2651
|
+
kfdAgentHubEvidence,
|
|
2652
|
+
kfdSupportEvidence,
|
|
2653
|
+
checkedAt,
|
|
2654
|
+
issues,
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
const tag = passport?.release?.tag || "";
|
|
2658
|
+
if (!tag) {
|
|
2659
|
+
issues.push(issue("error", "release.tag", "release.tag is required"));
|
|
2660
|
+
}
|
|
2661
|
+
if (!passport?.release?.sourceSha) {
|
|
2662
|
+
issues.push(issue("warning", "release.sourceSha", "release.sourceSha is recommended"));
|
|
2663
|
+
}
|
|
2664
|
+
const artifacts = Array.isArray(passport?.artifacts) ? passport.artifacts : [];
|
|
2665
|
+
const normalizedPublishEvidence = normalizePublishEvidence(publishEvidence);
|
|
2666
|
+
const { evidenceArtifacts, releaseEvidence } = validateReleaseEvidenceAttachments({
|
|
2667
|
+
passport,
|
|
2668
|
+
artifactEvidence,
|
|
2669
|
+
normalizedPublishEvidence,
|
|
2670
|
+
releaseEvidenceDocuments,
|
|
2671
|
+
issues,
|
|
2672
|
+
});
|
|
2673
|
+
validatePublishEvidenceSection({ passport, publishEvidence, normalizedPublishEvidence, issues });
|
|
2674
|
+
const evidenceIndex = indexEvidenceArtifacts(evidenceArtifacts);
|
|
2675
|
+
validateReleaseArtifacts({ artifacts, evidenceIndex, issues });
|
|
2676
|
+
validatePackageSet({ passport, evidenceIndex, issues });
|
|
2677
|
+
validateReleaseState({ passport, issues });
|
|
2678
|
+
validateVersionMaterial({ passport, issues });
|
|
2679
|
+
const impactContext = createReleaseImpactContext({ passport, impact });
|
|
2680
|
+
const { requiredSurfaceImpacts, surfaceImpacts } = impactContext;
|
|
2681
|
+
validatePassportTrustSections({ passport, issues });
|
|
2682
|
+
validateReleaseImpact({ passport, impact, context: impactContext, issues });
|
|
2683
|
+
return buildReleaseCheckReport({
|
|
2684
|
+
checkedAt,
|
|
2685
|
+
issues,
|
|
2686
|
+
requiredSurfaceImpacts,
|
|
2687
|
+
artifacts,
|
|
2688
|
+
evidenceArtifacts,
|
|
2689
|
+
passport,
|
|
2690
|
+
impact,
|
|
2691
|
+
agentIndex,
|
|
2692
|
+
productMechanism,
|
|
2693
|
+
surfaceImpacts,
|
|
2694
|
+
releaseEvidence,
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
export async function readJsonFromLocation(
|
|
2699
|
+
location,
|
|
2700
|
+
redirectCount = 0,
|
|
2701
|
+
{ timeoutMs = 15_000 } = {},
|
|
2702
|
+
) {
|
|
2432
2703
|
const input = nonEmptyString(location, "location");
|
|
2704
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
2705
|
+
throw new Error("timeoutMs must be a positive integer");
|
|
2706
|
+
}
|
|
2433
2707
|
if (redirectCount > 5) {
|
|
2434
2708
|
throw new Error(`too many redirects while reading ${input}`);
|
|
2435
2709
|
}
|
|
2436
2710
|
if (/^https?:\/\//.test(input)) {
|
|
2437
2711
|
const client = input.startsWith("https:") ? https : http;
|
|
2438
2712
|
return new Promise((resolve, reject) => {
|
|
2439
|
-
client
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2713
|
+
const request = client.get(input, (response) => {
|
|
2714
|
+
if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
|
|
2715
|
+
const nextLocation = new URL(response.headers.location, input).toString();
|
|
2716
|
+
response.resume();
|
|
2717
|
+
readJsonFromLocation(nextLocation, redirectCount + 1, { timeoutMs }).then(resolve, reject);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
2721
|
+
reject(new Error(`HTTP ${response.statusCode} while reading ${input}`));
|
|
2722
|
+
response.resume();
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
let body = "";
|
|
2726
|
+
response.setEncoding("utf8");
|
|
2727
|
+
response.on("data", (chunk) => {
|
|
2728
|
+
body += chunk;
|
|
2729
|
+
});
|
|
2730
|
+
response.on("end", () => {
|
|
2731
|
+
try {
|
|
2732
|
+
resolve(JSON.parse(body));
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
reject(error);
|
|
2451
2735
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
try {
|
|
2459
|
-
resolve(JSON.parse(body));
|
|
2460
|
-
} catch (error) {
|
|
2461
|
-
reject(error);
|
|
2462
|
-
}
|
|
2463
|
-
});
|
|
2464
|
-
})
|
|
2465
|
-
.on("error", reject);
|
|
2736
|
+
});
|
|
2737
|
+
});
|
|
2738
|
+
request.setTimeout(timeoutMs, () => {
|
|
2739
|
+
request.destroy(new Error(`timed out after ${timeoutMs}ms while reading ${input}`));
|
|
2740
|
+
});
|
|
2741
|
+
request.on("error", reject);
|
|
2466
2742
|
});
|
|
2467
2743
|
}
|
|
2468
2744
|
return readJsonFile(input);
|
|
@@ -2509,6 +2785,20 @@ export async function verifyReleasePassport({
|
|
|
2509
2785
|
kfdSupportEvidenceLocation
|
|
2510
2786
|
? await readJsonFromLocation(kfdSupportEvidenceLocation)
|
|
2511
2787
|
: await resolveSiblingJson(basePath, passport.evidence?.kfdSupport) || undefined;
|
|
2788
|
+
const releaseEvidenceDocuments = [];
|
|
2789
|
+
for (const reference of Array.isArray(passport.releaseEvidence) ? passport.releaseEvidence : []) {
|
|
2790
|
+
const id = String(reference?.id || "");
|
|
2791
|
+
const expectedPath = `release-evidence-${id}.json`;
|
|
2792
|
+
const safePath =
|
|
2793
|
+
/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id) &&
|
|
2794
|
+
reference?.path === expectedPath;
|
|
2795
|
+
releaseEvidenceDocuments.push({
|
|
2796
|
+
reference,
|
|
2797
|
+
document: safePath
|
|
2798
|
+
? await resolveSiblingJson(basePath, reference.path)
|
|
2799
|
+
: undefined,
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2512
2802
|
return createReleaseCheckReport({
|
|
2513
2803
|
passport,
|
|
2514
2804
|
artifactEvidence,
|
|
@@ -2518,6 +2808,7 @@ export async function verifyReleasePassport({
|
|
|
2518
2808
|
productMechanism,
|
|
2519
2809
|
kfdAgentHubEvidence,
|
|
2520
2810
|
kfdSupportEvidence,
|
|
2811
|
+
releaseEvidenceDocuments,
|
|
2521
2812
|
checkedAt,
|
|
2522
2813
|
});
|
|
2523
2814
|
}
|