@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
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import { normalizeControllerReceiptReferences, validateControllerReceiptReference } from "./controller-evidence.js";
|
|
3
3
|
|
|
4
4
|
export const RELEASE_CANDIDATE_PASSPORT_CONTRACT = "kungfu-buildchain-release-candidate-passport";
|
|
5
|
+
export const FAMILY_RELEASE_EVIDENCE_CONTRACT = "kungfu-buildchain-initiative-family-release-evidence/v1";
|
|
5
6
|
|
|
6
7
|
function nowIso() {
|
|
7
8
|
return new Date().toISOString();
|
|
@@ -19,6 +20,45 @@ function nonEmptyString(value, label) {
|
|
|
19
20
|
return normalized;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function sha256Root(value, label) {
|
|
24
|
+
const normalized = nonEmptyString(value, label).toLowerCase();
|
|
25
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(normalized)) {
|
|
26
|
+
throw new Error(`${label} must be a sha256 content root`);
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function gitSha(value, label) {
|
|
32
|
+
const normalized = nonEmptyString(value, label).toLowerCase();
|
|
33
|
+
if (!/^[0-9a-f]{40}$/.test(normalized)) {
|
|
34
|
+
throw new Error(`${label} must be a 40-character Git SHA`);
|
|
35
|
+
}
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sortedUniqueRoots(value, label, { allowEmpty = false } = {}) {
|
|
40
|
+
if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) {
|
|
41
|
+
throw new Error(`${label} must be ${allowEmpty ? "an" : "a non-empty"} array`);
|
|
42
|
+
}
|
|
43
|
+
const roots = value.map((root, index) => sha256Root(root, `${label}[${index}]`));
|
|
44
|
+
const expected = [...new Set(roots)].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
45
|
+
if (roots.length !== expected.length || roots.some((root, index) => root !== expected[index])) {
|
|
46
|
+
throw new Error(`${label} must be sorted and duplicate-free`);
|
|
47
|
+
}
|
|
48
|
+
return roots;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function requireExactKeys(value, keys, label) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
53
|
+
throw new Error(`${label} must be an object`);
|
|
54
|
+
}
|
|
55
|
+
const actual = Object.keys(value).sort();
|
|
56
|
+
const expected = [...keys].sort();
|
|
57
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
58
|
+
throw new Error(`${label} has an invalid field set`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
22
62
|
function normalizeTargetChannel(value) {
|
|
23
63
|
const normalized = optionalString(value).trim();
|
|
24
64
|
if (!normalized || normalized === "none") {
|
|
@@ -131,6 +171,142 @@ function normalizeGateProfileEvidence(gateAggregate = undefined) {
|
|
|
131
171
|
};
|
|
132
172
|
}
|
|
133
173
|
|
|
174
|
+
function normalizeFamilyReleaseEvidence(value = undefined) {
|
|
175
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
176
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
177
|
+
throw new Error("familyEvidence must be an object");
|
|
178
|
+
}
|
|
179
|
+
if (value.contract !== FAMILY_RELEASE_EVIDENCE_CONTRACT) {
|
|
180
|
+
throw new Error(`familyEvidence.contract must be ${FAMILY_RELEASE_EVIDENCE_CONTRACT}`);
|
|
181
|
+
}
|
|
182
|
+
requireExactKeys(
|
|
183
|
+
value,
|
|
184
|
+
["contract", "initiative", "child", "source", "qualification", "terminal", "continuation", "artifact", "release", "invalidation", "evidenceRoot"],
|
|
185
|
+
"familyEvidence",
|
|
186
|
+
);
|
|
187
|
+
requireExactKeys(value.initiative, ["initiativeId", "versionRoot", "familyStateRoot"], "familyEvidence.initiative");
|
|
188
|
+
requireExactKeys(value.child, ["assignmentId", "workDefinitionRoot", "deliveryClass"], "familyEvidence.child");
|
|
189
|
+
requireExactKeys(value.source, ["commitSha", "treeSha", "sourceRoot"], "familyEvidence.source");
|
|
190
|
+
requireExactKeys(value.qualification, ["status", "proofRoot", "qualificationRoot"], "familyEvidence.qualification");
|
|
191
|
+
requireExactKeys(value.terminal, ["state", "terminalRoot", "evidenceRoots"], "familyEvidence.terminal");
|
|
192
|
+
requireExactKeys(value.artifact, ["artifactRoot"], "familyEvidence.artifact");
|
|
193
|
+
requireExactKeys(value.release, ["releaseRoot"], "familyEvidence.release");
|
|
194
|
+
requireExactKeys(value.invalidation, ["status", "roots"], "familyEvidence.invalidation");
|
|
195
|
+
const terminalState = nonEmptyString(value.terminal?.state, "familyEvidence.terminal.state");
|
|
196
|
+
if (!["merged", "continued"].includes(terminalState)) {
|
|
197
|
+
throw new Error("familyEvidence.terminal.state must be merged or continued");
|
|
198
|
+
}
|
|
199
|
+
const deliveryClass = nonEmptyString(value.child?.deliveryClass, "familyEvidence.child.deliveryClass");
|
|
200
|
+
if (deliveryClass !== "release") {
|
|
201
|
+
throw new Error("familyEvidence.child.deliveryClass must be release");
|
|
202
|
+
}
|
|
203
|
+
const continuation = value.continuation === null || value.continuation === undefined
|
|
204
|
+
? null
|
|
205
|
+
: {
|
|
206
|
+
successorAssignmentId: nonEmptyString(
|
|
207
|
+
value.continuation.successorAssignmentId,
|
|
208
|
+
"familyEvidence.continuation.successorAssignmentId",
|
|
209
|
+
),
|
|
210
|
+
requestRoot: sha256Root(value.continuation.requestRoot, "familyEvidence.continuation.requestRoot"),
|
|
211
|
+
completedEvidenceRoots: sortedUniqueRoots(
|
|
212
|
+
value.continuation.completedEvidenceRoots,
|
|
213
|
+
"familyEvidence.continuation.completedEvidenceRoots",
|
|
214
|
+
),
|
|
215
|
+
residualResponsibilityRoot: sha256Root(
|
|
216
|
+
value.continuation.residualResponsibilityRoot,
|
|
217
|
+
"familyEvidence.continuation.residualResponsibilityRoot",
|
|
218
|
+
),
|
|
219
|
+
};
|
|
220
|
+
if (value.continuation !== null) {
|
|
221
|
+
requireExactKeys(
|
|
222
|
+
value.continuation,
|
|
223
|
+
["successorAssignmentId", "requestRoot", "completedEvidenceRoots", "residualResponsibilityRoot"],
|
|
224
|
+
"familyEvidence.continuation",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (terminalState === "continued" && !continuation) {
|
|
228
|
+
throw new Error("continued family evidence requires an exact residual successor");
|
|
229
|
+
}
|
|
230
|
+
if (terminalState === "merged" && continuation) {
|
|
231
|
+
throw new Error("merged family evidence must not declare a residual successor");
|
|
232
|
+
}
|
|
233
|
+
const normalized = {
|
|
234
|
+
contract: FAMILY_RELEASE_EVIDENCE_CONTRACT,
|
|
235
|
+
initiative: {
|
|
236
|
+
initiativeId: nonEmptyString(value.initiative?.initiativeId, "familyEvidence.initiative.initiativeId"),
|
|
237
|
+
versionRoot: sha256Root(value.initiative?.versionRoot, "familyEvidence.initiative.versionRoot"),
|
|
238
|
+
familyStateRoot: sha256Root(value.initiative?.familyStateRoot, "familyEvidence.initiative.familyStateRoot"),
|
|
239
|
+
},
|
|
240
|
+
child: {
|
|
241
|
+
assignmentId: nonEmptyString(value.child?.assignmentId, "familyEvidence.child.assignmentId"),
|
|
242
|
+
workDefinitionRoot: sha256Root(value.child?.workDefinitionRoot, "familyEvidence.child.workDefinitionRoot"),
|
|
243
|
+
deliveryClass,
|
|
244
|
+
},
|
|
245
|
+
source: {
|
|
246
|
+
commitSha: gitSha(value.source?.commitSha, "familyEvidence.source.commitSha"),
|
|
247
|
+
treeSha: gitSha(value.source?.treeSha, "familyEvidence.source.treeSha"),
|
|
248
|
+
sourceRoot: sha256Root(value.source?.sourceRoot, "familyEvidence.source.sourceRoot"),
|
|
249
|
+
},
|
|
250
|
+
qualification: {
|
|
251
|
+
status: nonEmptyString(value.qualification?.status, "familyEvidence.qualification.status"),
|
|
252
|
+
proofRoot: sha256Root(value.qualification?.proofRoot, "familyEvidence.qualification.proofRoot"),
|
|
253
|
+
qualificationRoot: sha256Root(
|
|
254
|
+
value.qualification?.qualificationRoot,
|
|
255
|
+
"familyEvidence.qualification.qualificationRoot",
|
|
256
|
+
),
|
|
257
|
+
},
|
|
258
|
+
terminal: {
|
|
259
|
+
state: terminalState,
|
|
260
|
+
terminalRoot: sha256Root(value.terminal?.terminalRoot, "familyEvidence.terminal.terminalRoot"),
|
|
261
|
+
evidenceRoots: sortedUniqueRoots(
|
|
262
|
+
value.terminal?.evidenceRoots,
|
|
263
|
+
"familyEvidence.terminal.evidenceRoots",
|
|
264
|
+
),
|
|
265
|
+
},
|
|
266
|
+
continuation,
|
|
267
|
+
artifact: {
|
|
268
|
+
artifactRoot: sha256Root(value.artifact?.artifactRoot, "familyEvidence.artifact.artifactRoot"),
|
|
269
|
+
},
|
|
270
|
+
release: {
|
|
271
|
+
releaseRoot: sha256Root(value.release?.releaseRoot, "familyEvidence.release.releaseRoot"),
|
|
272
|
+
},
|
|
273
|
+
invalidation: {
|
|
274
|
+
status: nonEmptyString(value.invalidation?.status, "familyEvidence.invalidation.status"),
|
|
275
|
+
roots: sortedUniqueRoots(value.invalidation?.roots, "familyEvidence.invalidation.roots", { allowEmpty: true }),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
if (normalized.qualification.status !== "qualified") {
|
|
279
|
+
throw new Error("familyEvidence.qualification.status must be qualified");
|
|
280
|
+
}
|
|
281
|
+
if (normalized.invalidation.status !== "clear" || normalized.invalidation.roots.length > 0) {
|
|
282
|
+
throw new Error("familyEvidence must not contain invalidated evidence");
|
|
283
|
+
}
|
|
284
|
+
const requiredTerminalRoots = [
|
|
285
|
+
normalized.source.sourceRoot,
|
|
286
|
+
normalized.qualification.proofRoot,
|
|
287
|
+
normalized.qualification.qualificationRoot,
|
|
288
|
+
normalized.artifact.artifactRoot,
|
|
289
|
+
normalized.release.releaseRoot,
|
|
290
|
+
];
|
|
291
|
+
for (const root of requiredTerminalRoots) {
|
|
292
|
+
if (!normalized.terminal.evidenceRoots.includes(root)) {
|
|
293
|
+
throw new Error(`familyEvidence.terminal.evidenceRoots is missing ${root}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (continuation) {
|
|
297
|
+
for (const root of [normalized.artifact.artifactRoot, normalized.release.releaseRoot]) {
|
|
298
|
+
if (!continuation.completedEvidenceRoots.includes(root)) {
|
|
299
|
+
throw new Error(`familyEvidence.continuation.completedEvidenceRoots is missing ${root}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const evidenceRoot = `sha256:${sha256Json(normalized)}`;
|
|
304
|
+
if (sha256Root(value.evidenceRoot, "familyEvidence.evidenceRoot") !== evidenceRoot) {
|
|
305
|
+
throw new Error("familyEvidence.evidenceRoot does not match its content");
|
|
306
|
+
}
|
|
307
|
+
return { ...normalized, evidenceRoot };
|
|
308
|
+
}
|
|
309
|
+
|
|
134
310
|
export function createReleaseCandidatePassport({
|
|
135
311
|
repository = "",
|
|
136
312
|
pullRequest = {},
|
|
@@ -143,6 +319,7 @@ export function createReleaseCandidatePassport({
|
|
|
143
319
|
buildSummary = {},
|
|
144
320
|
buildchain = {},
|
|
145
321
|
gateAggregate = undefined,
|
|
322
|
+
familyEvidence = undefined,
|
|
146
323
|
controllerReceipts = [],
|
|
147
324
|
controllerReceiptReferences = [],
|
|
148
325
|
workflow = {},
|
|
@@ -157,6 +334,7 @@ export function createReleaseCandidatePassport({
|
|
|
157
334
|
|| normalizeTargetChannel(pullRequest.baseRef);
|
|
158
335
|
const resolvedVersion = version || normalizedSummary.publishSource?.consumerVersion || inferVersionFromReleaseManifest(normalizedSummary);
|
|
159
336
|
const gateProfileEvidence = normalizeGateProfileEvidence(gateAggregate);
|
|
337
|
+
const normalizedFamilyEvidence = normalizeFamilyReleaseEvidence(familyEvidence);
|
|
160
338
|
const controllerReceiptEvidence = normalizeControllerReceiptReferences({
|
|
161
339
|
receipts: controllerReceipts,
|
|
162
340
|
references: controllerReceiptReferences,
|
|
@@ -207,6 +385,7 @@ export function createReleaseCandidatePassport({
|
|
|
207
385
|
buildSummaryHash: sha256Json(normalizedSummary),
|
|
208
386
|
},
|
|
209
387
|
...(gateProfileEvidence ? { gateProfileEvidence } : {}),
|
|
388
|
+
...(normalizedFamilyEvidence ? { familyEvidence: normalizedFamilyEvidence } : {}),
|
|
210
389
|
...(controllerReceiptEvidence.length > 0 ? { controllerReceipts: controllerReceiptEvidence } : {}),
|
|
211
390
|
};
|
|
212
391
|
candidate.candidateHash = sha256Json({
|
|
@@ -216,6 +395,7 @@ export function createReleaseCandidatePassport({
|
|
|
216
395
|
platformMatrix: candidate.platformMatrix,
|
|
217
396
|
buildchain: candidate.buildchain,
|
|
218
397
|
...(candidate.gateProfileEvidence ? { gateProfileEvidence: candidate.gateProfileEvidence } : {}),
|
|
398
|
+
...(candidate.familyEvidence ? { familyEvidence: candidate.familyEvidence } : {}),
|
|
219
399
|
...(candidate.controllerReceipts ? { controllerReceipts: candidate.controllerReceipts } : {}),
|
|
220
400
|
});
|
|
221
401
|
return candidate;
|
|
@@ -229,6 +409,10 @@ export function validateReleaseCandidatePassport({
|
|
|
229
409
|
sourceHeadSha = "",
|
|
230
410
|
buildSummary = undefined,
|
|
231
411
|
requirePlatforms = true,
|
|
412
|
+
requireFamilyEvidence = false,
|
|
413
|
+
familyEvidenceRoot = "",
|
|
414
|
+
familyInitiativeId = "",
|
|
415
|
+
familyAssignmentId = "",
|
|
232
416
|
} = {}) {
|
|
233
417
|
const errors = [];
|
|
234
418
|
const check = (condition, message) => {
|
|
@@ -279,6 +463,43 @@ export function validateReleaseCandidatePassport({
|
|
|
279
463
|
check(Boolean(passport.gateProfileEvidence.digest), "gate profile evidence digest is required");
|
|
280
464
|
check(Boolean(passport.gateProfileEvidence.matrixDigest), "gate profile matrix digest is required");
|
|
281
465
|
}
|
|
466
|
+
if (requireFamilyEvidence || familyEvidenceRoot || familyInitiativeId || familyAssignmentId) {
|
|
467
|
+
check(Boolean(passport.familyEvidence), "family evidence is required");
|
|
468
|
+
}
|
|
469
|
+
if (passport.familyEvidence) {
|
|
470
|
+
try {
|
|
471
|
+
const normalized = normalizeFamilyReleaseEvidence(passport.familyEvidence);
|
|
472
|
+
check(
|
|
473
|
+
normalized.source.commitSha === passport.source?.headSha
|
|
474
|
+
|| normalized.source.commitSha === passport.source?.mergeRefSha,
|
|
475
|
+
"family evidence source commit does not match the release candidate",
|
|
476
|
+
);
|
|
477
|
+
check(
|
|
478
|
+
normalized.source.treeSha === passport.source?.treeHash,
|
|
479
|
+
"family evidence source tree does not match the release candidate",
|
|
480
|
+
);
|
|
481
|
+
if (familyEvidenceRoot) {
|
|
482
|
+
check(
|
|
483
|
+
normalized.evidenceRoot === familyEvidenceRoot,
|
|
484
|
+
`family evidence root mismatch: expected ${familyEvidenceRoot}, got ${normalized.evidenceRoot}`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (familyInitiativeId) {
|
|
488
|
+
check(
|
|
489
|
+
normalized.initiative.initiativeId === familyInitiativeId,
|
|
490
|
+
`family initiative mismatch: expected ${familyInitiativeId}, got ${normalized.initiative.initiativeId}`,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
if (familyAssignmentId) {
|
|
494
|
+
check(
|
|
495
|
+
normalized.child.assignmentId === familyAssignmentId,
|
|
496
|
+
`family assignment mismatch: expected ${familyAssignmentId}, got ${normalized.child.assignmentId}`,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
check(false, `family evidence invalid: ${error.message || error}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
282
503
|
if (passport.controllerReceipts !== undefined) {
|
|
283
504
|
check(Array.isArray(passport.controllerReceipts), "controllerReceipts must be an array");
|
|
284
505
|
const controllerIds = new Set();
|
|
@@ -293,5 +514,18 @@ export function validateReleaseCandidatePassport({
|
|
|
293
514
|
controllerIds.add(reference.controllerId);
|
|
294
515
|
}
|
|
295
516
|
}
|
|
517
|
+
if (passport.familyEvidence) {
|
|
518
|
+
const expectedCandidateHash = sha256Json({
|
|
519
|
+
repository: passport.repository,
|
|
520
|
+
target: passport.target,
|
|
521
|
+
source: passport.source,
|
|
522
|
+
platformMatrix: passport.platformMatrix,
|
|
523
|
+
buildchain: passport.buildchain,
|
|
524
|
+
...(passport.gateProfileEvidence ? { gateProfileEvidence: passport.gateProfileEvidence } : {}),
|
|
525
|
+
familyEvidence: passport.familyEvidence,
|
|
526
|
+
...(passport.controllerReceipts ? { controllerReceipts: passport.controllerReceipts } : {}),
|
|
527
|
+
});
|
|
528
|
+
check(passport.candidateHash === expectedCandidateHash, "candidate hash mismatch");
|
|
529
|
+
}
|
|
296
530
|
return { ok: errors.length === 0, errors };
|
|
297
531
|
}
|