@kungfu-tech/buildchain 4.0.0 → 4.0.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/AGENTS.md +13 -5
  2. package/actions/promote-buildchain-ref/README.md +11 -6
  3. package/architecture/internal-capabilities.json +11 -2
  4. package/architecture/maintainability-policy.json +149 -17
  5. package/architecture/v4-floating-consumer-policy.json +75 -0
  6. package/architecture/v4-floating-consumer-policy.md +32 -0
  7. package/architecture/v4-runtime-ref-resume-authority.json +64 -0
  8. package/architecture/v4-stage-capsule-qualification.json +2 -2
  9. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  10. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +53 -0
  11. package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
  12. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +105 -0
  13. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
  14. package/dist/site/buildchain-contract.json +101 -31
  15. package/dist/site/buildchain-site.json +73 -27
  16. package/dist/site/capability-registry.json +3 -3
  17. package/dist/site/controller-registry.json +62 -6
  18. package/dist/site/kfd-claims.json +68 -13
  19. package/dist/site/kfd-upstream-aggregate.json +1 -1
  20. package/dist/site/manual-registry.json +6 -6
  21. package/dist/site/node-api-registry.json +799 -127
  22. package/dist/site/page-registry.json +63 -17
  23. package/dist/site/public-surface-audit.json +49 -17
  24. package/dist/site/publication-registry.json +4 -4
  25. package/dist/site/release-provenance.json +2 -0
  26. package/dist/site/site-manifest.json +10 -10
  27. package/dist/site/workflow-registry.json +46 -18
  28. package/docs/MAP.md +2 -0
  29. package/docs/dev-delivery-warrant.md +19 -1
  30. package/docs/lifecycle-protocol.md +41 -0
  31. package/docs/node-api-reference.md +207 -134
  32. package/docs/reusable-build-surface.md +41 -0
  33. package/docs/runtime-train-validation.md +10 -0
  34. package/docs/v4-runtime-ref-resume-authority.md +69 -0
  35. package/docs/versioning.md +1 -0
  36. package/package.json +6 -4
  37. package/packages/core/artifact-signing.js +61 -0
  38. package/packages/core/buildchain-config.js +66 -1
  39. package/packages/core/dev-delivery-proof.js +37 -3
  40. package/packages/core/dev-delivery-warrant.js +3 -3
  41. package/packages/core/index.js +2 -0
  42. package/packages/core/publication-authority.js +5 -5
  43. package/packages/core/release-candidate.js +79 -19
  44. package/packages/core/release-passport.js +239 -33
  45. package/packages/core/v4-floating-consumer-evidence.js +324 -0
  46. package/packages/core/v4-floating-consumer-policy.js +446 -0
  47. package/packages/core/v4-floating-consumer-release-passport.js +126 -0
  48. package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
  49. package/packages/core/v4-runtime-selector-persistence.js +228 -0
  50. package/packages/core/workflow-yaml-contract.js +48 -0
  51. package/scripts/audit-publication-control-plane.mjs +31 -31
  52. package/scripts/check-inventory.mjs +1 -1
  53. package/scripts/check-v4-floating-consumer-policy-contract.mjs +198 -0
  54. package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
  55. package/scripts/dev-delivery-source-proof-reuse.mjs +631 -0
  56. package/scripts/dev-pr-auto-merge.mjs +37 -48
  57. package/scripts/dev-pr-delivery-warrant.mjs +205 -2
  58. package/scripts/dev-pr-prequeue-guard.mjs +399 -0
  59. package/scripts/ensure-github-release.mjs +3 -3
  60. package/scripts/generate-channel-build-workflow.mjs +3 -0
  61. package/scripts/generate-channel-promotion-workflow.mjs +112 -12
  62. package/scripts/generate-release-candidate-passport.mjs +41 -33
  63. package/scripts/init-repo.mjs +5 -1
  64. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  65. package/scripts/npm-publish-transaction.mjs +101 -89
  66. package/scripts/resume-from-candidate-run.mjs +11 -14
  67. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  68. package/scripts/site-capability-metadata.mjs +2 -0
  69. package/scripts/v4-consumer-policy.mjs +231 -0
@@ -1,4 +1,5 @@
1
1
  import crypto from "node:crypto";
2
+ import { verifyV4FloatingConsumerPolicyReceipt } from "./v4-floating-consumer-policy.js";
2
3
  import { normalizeControllerReceiptReferences, validateControllerReceiptReference } from "./controller-evidence.js";
3
4
 
4
5
  export const RELEASE_CANDIDATE_PASSPORT_CONTRACT = "kungfu-buildchain-release-candidate-passport";
@@ -171,6 +172,64 @@ function normalizeGateProfileEvidence(gateAggregate = undefined) {
171
172
  };
172
173
  }
173
174
 
175
+ function isV4BuildchainRuntime(buildchain = {}) {
176
+ return [buildchain.ref, buildchain.workflowShellRef]
177
+ .some((value) => /^v4(?:-alpha)?$/u.test(String(value || "")))
178
+ || /^4\./u.test(String(buildchain.version || ""));
179
+ }
180
+
181
+ function normalizeConsumerPolicyEvidence(value, expected = {}) {
182
+ if (value === undefined || value === null || value === "") return undefined;
183
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
184
+ throw new Error("consumerPolicyReceipt must be an object");
185
+ }
186
+ const receipt = value.receipt || value;
187
+ const verification = verifyV4FloatingConsumerPolicyReceipt({
188
+ receipt,
189
+ receiptRoot: value.receiptRoot || "",
190
+ repository: expected.repository,
191
+ sourceSha: expected.sourceSha,
192
+ resolvedRuntimeSha: expected.runtimeSha,
193
+ });
194
+ if (!verification.ok) {
195
+ throw new Error(`consumer policy receipt invalid: ${verification.failures.map((failure) => failure.code).join(", ")}`);
196
+ }
197
+ return { receiptRoot: verification.receiptRoot, receipt };
198
+ }
199
+
200
+ function validateConsumerPolicyEvidence(passport, check) {
201
+ if (isV4BuildchainRuntime(passport.buildchain)) {
202
+ check(Boolean(passport.consumerPolicy), "Buildchain v4 release candidate requires consumer policy evidence");
203
+ }
204
+ if (!passport.consumerPolicy) return;
205
+ const verification = verifyV4FloatingConsumerPolicyReceipt({
206
+ receipt: passport.consumerPolicy.receipt,
207
+ receiptRoot: passport.consumerPolicy.receiptRoot,
208
+ repository: passport.repository,
209
+ sourceSha: passport.source?.headSha,
210
+ resolvedRuntimeSha: passport.buildchain?.sha,
211
+ });
212
+ for (const failure of verification.failures) {
213
+ check(false, `consumer policy evidence invalid: ${failure.code}`);
214
+ }
215
+ }
216
+
217
+ function validateEvidenceBoundCandidateHash(passport, check) {
218
+ if (!passport.familyEvidence && !passport.consumerPolicy) return;
219
+ const expectedCandidateHash = sha256Json({
220
+ repository: passport.repository,
221
+ target: passport.target,
222
+ source: passport.source,
223
+ platformMatrix: passport.platformMatrix,
224
+ buildchain: passport.buildchain,
225
+ ...(passport.gateProfileEvidence ? { gateProfileEvidence: passport.gateProfileEvidence } : {}),
226
+ ...(passport.familyEvidence ? { familyEvidence: passport.familyEvidence } : {}),
227
+ ...(passport.consumerPolicy ? { consumerPolicy: passport.consumerPolicy } : {}),
228
+ ...(passport.controllerReceipts ? { controllerReceipts: passport.controllerReceipts } : {}),
229
+ });
230
+ check(passport.candidateHash === expectedCandidateHash, "candidate hash mismatch");
231
+ }
232
+
174
233
  function normalizeFamilyReleaseEvidence(value = undefined) {
175
234
  if (value === undefined || value === null || value === "") return undefined;
176
235
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -320,6 +379,7 @@ export function createReleaseCandidatePassport({
320
379
  buildchain = {},
321
380
  gateAggregate = undefined,
322
381
  familyEvidence = undefined,
382
+ consumerPolicyReceipt = undefined,
323
383
  controllerReceipts = [],
324
384
  controllerReceiptReferences = [],
325
385
  workflow = {},
@@ -335,6 +395,20 @@ export function createReleaseCandidatePassport({
335
395
  const resolvedVersion = version || normalizedSummary.publishSource?.consumerVersion || inferVersionFromReleaseManifest(normalizedSummary);
336
396
  const gateProfileEvidence = normalizeGateProfileEvidence(gateAggregate);
337
397
  const normalizedFamilyEvidence = normalizeFamilyReleaseEvidence(familyEvidence);
398
+ const resolvedBuildchain = {
399
+ ref: optionalString(buildchain.ref || normalizedSummary.runtime?.ref),
400
+ sha: optionalString(buildchain.sha || normalizedSummary.runtime?.sha),
401
+ version: optionalString(buildchain.version),
402
+ workflowShellRef: optionalString(buildchain.workflowShellRef || normalizedSummary.runtime?.workflowShellRef),
403
+ };
404
+ const normalizedConsumerPolicy = normalizeConsumerPolicyEvidence(consumerPolicyReceipt, {
405
+ repository: repository || normalizedSummary.git?.repository || "",
406
+ sourceSha,
407
+ runtimeSha: resolvedBuildchain.sha,
408
+ });
409
+ if (isV4BuildchainRuntime(resolvedBuildchain) && !normalizedConsumerPolicy) {
410
+ throw new Error("Buildchain v4 release candidate passport requires a valid floating consumer policy receipt");
411
+ }
338
412
  const controllerReceiptEvidence = normalizeControllerReceiptReferences({
339
413
  receipts: controllerReceipts,
340
414
  references: controllerReceiptReferences,
@@ -366,12 +440,7 @@ export function createReleaseCandidatePassport({
366
440
  builtSourceSha: nonEmptyString(mergeRefSha || sourceSha, "builtSourceSha"),
367
441
  builtSourceTreeSha: resolvedTreeHash,
368
442
  },
369
- buildchain: {
370
- ref: optionalString(buildchain.ref || normalizedSummary.runtime?.ref),
371
- sha: optionalString(buildchain.sha || normalizedSummary.runtime?.sha),
372
- version: optionalString(buildchain.version),
373
- workflowShellRef: optionalString(buildchain.workflowShellRef || normalizedSummary.runtime?.workflowShellRef),
374
- },
443
+ buildchain: resolvedBuildchain,
375
444
  workflow: {
376
445
  name: optionalString(workflow.name),
377
446
  runId: optionalString(workflow.runId || normalizedSummary.git?.runId),
@@ -386,6 +455,7 @@ export function createReleaseCandidatePassport({
386
455
  },
387
456
  ...(gateProfileEvidence ? { gateProfileEvidence } : {}),
388
457
  ...(normalizedFamilyEvidence ? { familyEvidence: normalizedFamilyEvidence } : {}),
458
+ ...(normalizedConsumerPolicy ? { consumerPolicy: normalizedConsumerPolicy } : {}),
389
459
  ...(controllerReceiptEvidence.length > 0 ? { controllerReceipts: controllerReceiptEvidence } : {}),
390
460
  };
391
461
  candidate.candidateHash = sha256Json({
@@ -396,6 +466,7 @@ export function createReleaseCandidatePassport({
396
466
  buildchain: candidate.buildchain,
397
467
  ...(candidate.gateProfileEvidence ? { gateProfileEvidence: candidate.gateProfileEvidence } : {}),
398
468
  ...(candidate.familyEvidence ? { familyEvidence: candidate.familyEvidence } : {}),
469
+ ...(candidate.consumerPolicy ? { consumerPolicy: candidate.consumerPolicy } : {}),
399
470
  ...(candidate.controllerReceipts ? { controllerReceipts: candidate.controllerReceipts } : {}),
400
471
  });
401
472
  return candidate;
@@ -463,6 +534,7 @@ export function validateReleaseCandidatePassport({
463
534
  check(Boolean(passport.gateProfileEvidence.digest), "gate profile evidence digest is required");
464
535
  check(Boolean(passport.gateProfileEvidence.matrixDigest), "gate profile matrix digest is required");
465
536
  }
537
+ validateConsumerPolicyEvidence(passport, check);
466
538
  if (requireFamilyEvidence || familyEvidenceRoot || familyInitiativeId || familyAssignmentId) {
467
539
  check(Boolean(passport.familyEvidence), "family evidence is required");
468
540
  }
@@ -514,18 +586,6 @@ export function validateReleaseCandidatePassport({
514
586
  controllerIds.add(reference.controllerId);
515
587
  }
516
588
  }
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
- }
589
+ validateEvidenceBoundCandidateHash(passport, check);
530
590
  return { ok: errors.length === 0, errors };
531
591
  }
@@ -33,6 +33,16 @@ import { normalizeControllerReceiptReferences } from "./controller-evidence.js";
33
33
  import {
34
34
  normalizeGitHubArtifactAttestationPolicy,
35
35
  } from "./github-artifact-attestation.js";
36
+ import { verifyV4FloatingConsumerPolicyCertification } from "./v4-floating-consumer-evidence.js";
37
+ import {
38
+ isV4PromotionRouting,
39
+ releasePassportCertificationVerificationOptions,
40
+ requireV4ConsumerPolicyCertification,
41
+ } from "./v4-floating-consumer-release-passport.js";
42
+ import {
43
+ verifyV4RuntimeAuthorizationReceipt,
44
+ verifyV4RuntimeResumeLineage,
45
+ } from "./v4-runtime-ref-resume-authority.js";
36
46
 
37
47
  export { RELEASE_CHECK_REPORT_CONTRACT, RELEASE_PASSPORT_CONTRACT };
38
48
  export const ARTIFACT_EVIDENCE_CONTRACT = "kungfu-buildchain-artifact-evidence";
@@ -390,6 +400,13 @@ function parseJsonInputWithMeta(value, fallback = undefined, { cwd = process.cwd
390
400
  };
391
401
  }
392
402
 
403
+ function parseV4ConsumerPolicyCertification(input, cwd) {
404
+ return parseJsonInputWithMeta(input, undefined, {
405
+ cwd,
406
+ label: "v4ConsumerPolicyCertificationJson",
407
+ });
408
+ }
409
+
393
410
  function mergeAuthoritativePassportBase(passport, basePassport = undefined, { requireKfd = false } = {}) {
394
411
  if (!basePassport) {
395
412
  if (requireKfd) {
@@ -1085,6 +1102,123 @@ function normalizePromotionRouting(value = undefined) {
1085
1102
  return JSON.parse(JSON.stringify(value));
1086
1103
  }
1087
1104
 
1105
+ function normalizePromotionEvidence({
1106
+ release = {},
1107
+ v4ConsumerPolicyCertification: certification,
1108
+ v4ConsumerPolicyCertificationRoot: certificationRoot,
1109
+ repository,
1110
+ sourceSha,
1111
+ cwd,
1112
+ }) {
1113
+ const routing = normalizePromotionRouting(release.promotionRouting);
1114
+ return {
1115
+ routing,
1116
+ consumerPolicy: requireV4ConsumerPolicyCertification({
1117
+ value: certification,
1118
+ repository,
1119
+ sourceSha,
1120
+ routing,
1121
+ cwd,
1122
+ certificationRoot,
1123
+ }),
1124
+ };
1125
+ }
1126
+
1127
+ function normalizeV4RuntimeResumeEvidence(value, expected = {}) {
1128
+ if (value === undefined || value === null || value === "") return undefined;
1129
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1130
+ throw new Error("v4RuntimeResumeEvidence must be a JSON object");
1131
+ }
1132
+ const authorizationVerification = verifyV4RuntimeAuthorizationReceipt({
1133
+ receipt: value.authorization,
1134
+ receiptRoot: value.authorizationRoot,
1135
+ repository: expected.repository,
1136
+ sourceSha: expected.sourceSha,
1137
+ runtimeSha: expected.resumeRuntimeSha,
1138
+ consumerPolicyReceiptRoot: expected.consumerPolicyReceiptRoot,
1139
+ });
1140
+ if (!authorizationVerification.ok) {
1141
+ throw new Error(`v4 runtime authorization invalid: ${authorizationVerification.failures.join(", ")}`);
1142
+ }
1143
+ const lineageVerification = verifyV4RuntimeResumeLineage({
1144
+ lineage: value.lineage,
1145
+ lineageRoot: value.lineageRoot,
1146
+ repository: expected.repository,
1147
+ sourceSha: expected.sourceSha,
1148
+ resumeRuntimeSha: expected.resumeRuntimeSha,
1149
+ consumerPolicyReceiptRoot: expected.consumerPolicyReceiptRoot,
1150
+ });
1151
+ if (!lineageVerification.ok) {
1152
+ throw new Error(`v4 runtime resume lineage invalid: ${lineageVerification.failures.join(", ")}`);
1153
+ }
1154
+ if (value.lineage?.authorizationRoot !== value.authorizationRoot) {
1155
+ throw new Error("v4 runtime resume lineage authorization root mismatch");
1156
+ }
1157
+ return {
1158
+ authorizationRoot: value.authorizationRoot,
1159
+ authorization: structuredClone(value.authorization),
1160
+ lineageRoot: value.lineageRoot,
1161
+ lineage: structuredClone(value.lineage),
1162
+ };
1163
+ }
1164
+
1165
+ function prepareBuildEvidence({
1166
+ buildSummary,
1167
+ buildFacts,
1168
+ platformArtifactManifests,
1169
+ distTagPromotionEvidence,
1170
+ }) {
1171
+ return {
1172
+ buildSummary: buildSummary
1173
+ ? normalizeEvidenceDocument(buildSummary, "buildSummary")
1174
+ : undefined,
1175
+ buildFacts: (buildFacts || [])
1176
+ .map((value, index) =>
1177
+ normalizeEvidenceDocument(value, `buildFacts[${index}]`),
1178
+ )
1179
+ .filter(Boolean),
1180
+ platformArtifactManifests: (platformArtifactManifests || [])
1181
+ .map((manifest, index) =>
1182
+ normalizePlatformArtifactManifest(manifest, index),
1183
+ )
1184
+ .filter(Boolean),
1185
+ distTagPromotionEvidence: distTagPromotionEvidence
1186
+ ? normalizeEvidenceDocument(
1187
+ distTagPromotionEvidence,
1188
+ "distTagPromotionEvidence",
1189
+ )
1190
+ : undefined,
1191
+ };
1192
+ }
1193
+
1194
+ function preparePromotionBuild(options) {
1195
+ return {
1196
+ promotion: normalizePromotionEvidence(options),
1197
+ buildEvidence: prepareBuildEvidence(options),
1198
+ };
1199
+ }
1200
+
1201
+ function normalizeReleaseSourceFields(release) {
1202
+ return {
1203
+ builtSourceSha: releaseField(release, "builtSourceSha", "built_source_sha"),
1204
+ builtSourceTreeSha: releaseField(
1205
+ release,
1206
+ "builtSourceTreeSha",
1207
+ "built_source_tree_sha",
1208
+ ),
1209
+ promotionChannelSha: releaseField(
1210
+ release,
1211
+ "promotionChannelSha",
1212
+ "promotion_channel_sha",
1213
+ ),
1214
+ promotionChannelTreeSha: releaseField(
1215
+ release,
1216
+ "promotionChannelTreeSha",
1217
+ "promotion_channel_tree_sha",
1218
+ ),
1219
+ };
1220
+ }
1221
+
1088
1222
  export function createArtifactEvidence({ assets = [], repository = "", tag = "", sourceSha = "", workflow = {}, kfdAdopter = undefined } = {}) {
1089
1223
  const normalizedAssets = assets.map((asset, index) => normalizeAsset(asset, index));
1090
1224
  return buildReleaseArtifactEvidence({ normalizedAssets, repository, tag, sourceSha, workflow, kfdAdopter });
@@ -1120,7 +1254,8 @@ function prepareReleaseKfdAdopterArtifacts({ kfdAdopter, assets, repository, tag
1120
1254
  };
1121
1255
  }
1122
1256
 
1123
- export function createReleasePassport({
1257
+ export function createReleasePassport(options = {}) {
1258
+ const {
1124
1259
  cwd = process.cwd(),
1125
1260
  repository = "",
1126
1261
  tag = "",
@@ -1161,13 +1296,16 @@ export function createReleasePassport({
1161
1296
  kfdSupportEvidencePath = "",
1162
1297
  invariantPassports = undefined,
1163
1298
  releaseEvidence = [],
1299
+ v4ConsumerPolicyCertification = undefined,
1300
+ v4ConsumerPolicyCertificationRoot = "",
1301
+ v4RuntimeResumeEvidence = undefined,
1164
1302
  kfdAgentHubEvidence = undefined,
1165
1303
  kfdAgentHubEvidencePath = "",
1166
1304
  controllerReceipts = [],
1167
1305
  controllerReceiptReferences = [],
1168
1306
  githubArtifactAttestations = [],
1169
1307
  checkedAt = "",
1170
- } = {}) {
1308
+ } = options;
1171
1309
  const normalizedTag = nonEmptyString(tag, "tag");
1172
1310
  const { normalized: normalizedKfdAdopter, artifactEvidence } = prepareReleaseKfdAdopterArtifacts({
1173
1311
  kfdAdopter, assets, repository, tag: normalizedTag, sourceSha, workflow,
@@ -1176,17 +1314,13 @@ export function createReleasePassport({
1176
1314
  const normalizedPackageSet = normalizePackageSet(packageSet, { packageName, packageVersion, publish });
1177
1315
  const normalizedTrustedPublishing = normalizeTrustedPublishing(trustedPublishing, { workflow, publish });
1178
1316
  const normalizedTransaction = normalizeTransaction(transaction);
1179
- const normalizedPromotionRouting = normalizePromotionRouting(release.promotionRouting);
1180
- const normalizedBuildSummary = buildSummary ? normalizeEvidenceDocument(buildSummary, "buildSummary") : undefined;
1181
- const normalizedBuildFacts = (buildFacts || [])
1182
- .map((fact, index) => normalizeEvidenceDocument(fact, `buildFacts[${index}]`))
1183
- .filter(Boolean);
1184
- const normalizedPlatformArtifactManifests = (platformArtifactManifests || [])
1185
- .map((manifest, index) => normalizePlatformArtifactManifest(manifest, index))
1186
- .filter(Boolean);
1187
- const normalizedDistTagPromotionEvidence = distTagPromotionEvidence
1188
- ? normalizeEvidenceDocument(distTagPromotionEvidence, "distTagPromotionEvidence")
1189
- : undefined;
1317
+ const { promotion, buildEvidence } = preparePromotionBuild(options);
1318
+ const normalizedV4RuntimeResume = normalizeV4RuntimeResumeEvidence(v4RuntimeResumeEvidence, {
1319
+ repository,
1320
+ sourceSha,
1321
+ resumeRuntimeSha: promotion.routing?.runtime?.resolvedSha || "",
1322
+ consumerPolicyReceiptRoot: promotion.consumerPolicy?.certification?.receiptRoot || "",
1323
+ });
1190
1324
  const normalizedImpact = normalizeImpactLedger(impact, { tag: normalizedTag, line });
1191
1325
  const generatedAt = optionalString(checkedAt) || nowIso();
1192
1326
  const kfd1Metadata = resolveKfd1Metadata();
@@ -1205,16 +1339,13 @@ export function createReleasePassport({
1205
1339
  support: kfdSupportEvidencePath,
1206
1340
  },
1207
1341
  });
1208
- const builtSourceSha = releaseField(release, "builtSourceSha", "built_source_sha");
1209
- const builtSourceTreeSha = releaseField(release, "builtSourceTreeSha", "built_source_tree_sha");
1210
- const promotionChannelSha = releaseField(release, "promotionChannelSha", "promotion_channel_sha");
1211
- const promotionChannelTreeSha = releaseField(release, "promotionChannelTreeSha", "promotion_channel_tree_sha");
1342
+ const sourceFields = normalizeReleaseSourceFields(release);
1212
1343
  const treeEquivalent = release.treeEquivalent === true;
1213
1344
  const recoveryTreeEquivalent = Boolean(
1214
1345
  treeEquivalent &&
1215
- builtSourceTreeSha &&
1216
- promotionChannelTreeSha &&
1217
- builtSourceTreeSha === promotionChannelTreeSha,
1346
+ sourceFields.builtSourceTreeSha &&
1347
+ sourceFields.promotionChannelTreeSha &&
1348
+ sourceFields.builtSourceTreeSha === sourceFields.promotionChannelTreeSha,
1218
1349
  );
1219
1350
  const normalizedControllerReceipts = normalizeControllerReceiptReferences({
1220
1351
  receipts: controllerReceipts,
@@ -1222,8 +1353,8 @@ export function createReleasePassport({
1222
1353
  expectedSourceSha: sourceSha,
1223
1354
  acceptedSourceShas: acceptedControllerSourceShas({
1224
1355
  treeEquivalent,
1225
- builtSourceSha,
1226
- promotionChannelSha,
1356
+ builtSourceSha: sourceFields.builtSourceSha,
1357
+ promotionChannelSha: sourceFields.promotionChannelSha,
1227
1358
  sourceSha,
1228
1359
  recoveryTreeEquivalent,
1229
1360
  }),
@@ -1285,9 +1416,9 @@ export function createReleasePassport({
1285
1416
  releaseSha,
1286
1417
  releaseMaterialSha,
1287
1418
  builtSourceSha: optionalString(release.builtSourceSha || release.built_source_sha),
1288
- builtSourceTreeSha,
1419
+ builtSourceTreeSha: sourceFields.builtSourceTreeSha,
1289
1420
  promotionChannelSha: optionalString(release.promotionChannelSha || release.promotion_channel_sha),
1290
- promotionChannelTreeSha,
1421
+ promotionChannelTreeSha: sourceFields.promotionChannelTreeSha,
1291
1422
  treeEquivalent: release.treeEquivalent === undefined ? undefined : Boolean(release.treeEquivalent),
1292
1423
  publishToolingSha: optionalString(
1293
1424
  release.publishToolingSha ||
@@ -1325,15 +1456,17 @@ export function createReleasePassport({
1325
1456
  ["versionMaterial", versionMaterial],
1326
1457
  ["trustedPublishing", normalizedTrustedPublishing],
1327
1458
  ["transaction", normalizedTransaction],
1328
- ["promotionRouting", normalizedPromotionRouting],
1329
- ["buildSummary", normalizedBuildSummary],
1330
- ["buildFacts", normalizedBuildFacts],
1331
- ["platformArtifactManifests", normalizedPlatformArtifactManifests],
1332
- ["distTagPromotion", normalizedDistTagPromotionEvidence],
1459
+ ["promotionRouting", promotion.routing],
1460
+ ["buildSummary", buildEvidence.buildSummary],
1461
+ ["buildFacts", buildEvidence.buildFacts],
1462
+ ["platformArtifactManifests", buildEvidence.platformArtifactManifests],
1463
+ ["distTagPromotion", buildEvidence.distTagPromotionEvidence],
1333
1464
  ...kfdParts.sectionEntries,
1334
1465
  ["kfdAgentHub", normalizedKfdAgentHub],
1335
1466
  ["invariantPassports", invariantPassports],
1336
1467
  ["releaseEvidence", releaseEvidence],
1468
+ ["v4ConsumerPolicy", promotion.consumerPolicy],
1469
+ ["v4RuntimeResume", normalizedV4RuntimeResume],
1337
1470
  ["controllerReceipts", normalizedControllerReceipts],
1338
1471
  ["githubArtifactAttestations", normalizedGitHubArtifactAttestations],
1339
1472
  ]),
@@ -1360,21 +1493,21 @@ export function createReleasePassport({
1360
1493
  artifactEvidence: artifactEvidencePath,
1361
1494
  publishEvidence: publishEvidencePath,
1362
1495
  transactionState: transactionStatePath,
1363
- buildSummary: normalizedBuildSummary?.path || "",
1364
- buildFacts: normalizedBuildFacts.map((fact) => ({
1496
+ buildSummary: buildEvidence.buildSummary?.path || "",
1497
+ buildFacts: buildEvidence.buildFacts.map((fact) => ({
1365
1498
  path: fact.path || "",
1366
1499
  sha256: fact.sha256 || "",
1367
1500
  contract: fact.fields?.contract || "",
1368
1501
  id: fact.fields?.id || "",
1369
1502
  digest: fact.fields?.digest || "",
1370
1503
  })),
1371
- platformArtifactManifests: normalizedPlatformArtifactManifests.map((manifest) => ({
1504
+ platformArtifactManifests: buildEvidence.platformArtifactManifests.map((manifest) => ({
1372
1505
  path: manifest.path,
1373
1506
  sha256: manifest.sha256,
1374
1507
  platform: manifest.platform,
1375
1508
  artifactName: manifest.artifactName,
1376
1509
  })),
1377
- distTagPromotionEvidence: normalizedDistTagPromotionEvidence?.path || "",
1510
+ distTagPromotionEvidence: buildEvidence.distTagPromotionEvidence?.path || "",
1378
1511
  ...kfdParts.evidence,
1379
1512
  kfdAgentHub: normalizedKfdAgentHub ? optionalString(kfdAgentHubEvidencePath || "kfd-agent-hub-evidence.json") : "",
1380
1513
  invariantPassports: invariantPassports ? "invariantPassports" : "",
@@ -1456,6 +1589,9 @@ export function collectGitHubReleasePassport({
1456
1589
  invariantPassportJsons = [],
1457
1590
  invariantPassportCommand = "",
1458
1591
  releaseEvidenceJsons = [],
1592
+ v4ConsumerPolicyCertificationJson = "",
1593
+ v4ConsumerPolicyCertificationRoot = "",
1594
+ v4RuntimeResumeEvidenceJson = "",
1459
1595
  kfdAgentHubEvidenceJson = "",
1460
1596
  controllerReceiptReferences = [],
1461
1597
  githubArtifactAttestationPolicyJsons = [],
@@ -1531,6 +1667,12 @@ export function collectGitHubReleasePassport({
1531
1667
  }),
1532
1668
  )
1533
1669
  .filter((meta) => meta.value);
1670
+ const v4ConsumerPolicyCertificationMeta =
1671
+ parseV4ConsumerPolicyCertification(v4ConsumerPolicyCertificationJson, cwd);
1672
+ const v4RuntimeResumeEvidence = parseJsonInput(v4RuntimeResumeEvidenceJson, undefined, {
1673
+ cwd,
1674
+ label: "v4RuntimeResumeEvidenceJson",
1675
+ });
1534
1676
  const kfdAgentHubEvidenceMeta = parseJsonInputWithMeta(
1535
1677
  kfdAgentHubEvidenceJson,
1536
1678
  undefined,
@@ -1656,6 +1798,9 @@ export function collectGitHubReleasePassport({
1656
1798
  kfdSupportEvidencePath: kfdSupport ? "kfd-support.json" : "",
1657
1799
  invariantPassports,
1658
1800
  releaseEvidence: releaseEvidenceAttachments.map(({ reference }) => reference),
1801
+ v4ConsumerPolicyCertification: v4ConsumerPolicyCertificationMeta.value,
1802
+ v4ConsumerPolicyCertificationRoot,
1803
+ v4RuntimeResumeEvidence,
1659
1804
  kfdAgentHubEvidence: kfdAgentHubEvidenceMeta.value
1660
1805
  ? { ...kfdAgentHubEvidenceMeta, path: "kfd-agent-hub-evidence.json" }
1661
1806
  : undefined,
@@ -2125,6 +2270,65 @@ function validateReleaseEvidenceAttachments({
2125
2270
  return { evidenceArtifacts, releaseEvidence };
2126
2271
  }
2127
2272
 
2273
+ function validateV4ConsumerPolicyPassportSection({ passport, issues }) {
2274
+ const routing = passport?.promotionRouting;
2275
+ const evidence = passport?.v4ConsumerPolicy;
2276
+ if (isV4PromotionRouting(routing) && !evidence) {
2277
+ issues.push(issue(
2278
+ "error",
2279
+ "v4ConsumerPolicy.missing",
2280
+ "Buildchain v4 Release Passport requires an external floating consumer policy certification",
2281
+ ));
2282
+ return;
2283
+ }
2284
+ if (!evidence) return;
2285
+ const verification = verifyV4FloatingConsumerPolicyCertification(
2286
+ releasePassportCertificationVerificationOptions({
2287
+ evidence,
2288
+ passport,
2289
+ routing,
2290
+ }),
2291
+ );
2292
+ for (const failure of verification.failures) {
2293
+ issues.push(issue("error", `v4ConsumerPolicy.${failure.code}`, failure.message));
2294
+ }
2295
+ }
2296
+
2297
+ function validateV4RuntimeResumePassportSection({ passport, issues }) {
2298
+ const evidence = passport?.v4RuntimeResume;
2299
+ if (!evidence) return;
2300
+ const consumerPolicyReceiptRoot = passport?.v4ConsumerPolicy?.certification?.receiptRoot || "";
2301
+ const authorization = verifyV4RuntimeAuthorizationReceipt({
2302
+ receipt: evidence.authorization,
2303
+ receiptRoot: evidence.authorizationRoot,
2304
+ repository: passport?.product?.repository || "",
2305
+ sourceSha: passport?.release?.sourceSha || "",
2306
+ runtimeSha: passport?.promotionRouting?.runtime?.resolvedSha || "",
2307
+ consumerPolicyReceiptRoot,
2308
+ });
2309
+ for (const failure of authorization.failures) {
2310
+ issues.push(issue("error", `v4RuntimeResume.authorization.${failure}`, failure));
2311
+ }
2312
+ const lineage = verifyV4RuntimeResumeLineage({
2313
+ lineage: evidence.lineage,
2314
+ lineageRoot: evidence.lineageRoot,
2315
+ repository: passport?.product?.repository || "",
2316
+ sourceSha: passport?.release?.sourceSha || "",
2317
+ resumeRuntimeSha: passport?.promotionRouting?.runtime?.resolvedSha || "",
2318
+ consumerPolicyReceiptRoot,
2319
+ });
2320
+ for (const failure of lineage.failures) {
2321
+ issues.push(issue("error", `v4RuntimeResume.lineage.${failure}`, failure));
2322
+ }
2323
+ if (evidence.lineage?.authorizationRoot !== evidence.authorizationRoot) {
2324
+ issues.push(issue(
2325
+ "error",
2326
+ "v4RuntimeResume.authorization-root-mismatch",
2327
+ "runtime resume lineage must bind the embedded authorization receipt root",
2328
+ ));
2329
+ }
2330
+ }
2331
+
2128
2332
  function validatePublishEvidenceSection({ passport, publishEvidence, normalizedPublishEvidence, issues }) {
2129
2333
  const publishEvidenceSupplied =
2130
2334
  Boolean(passport?.evidence?.publishEvidence) ||
@@ -2597,6 +2801,8 @@ export function createReleaseCheckReport({
2597
2801
  releaseEvidenceDocuments,
2598
2802
  issues,
2599
2803
  });
2804
+ validateV4ConsumerPolicyPassportSection({ passport, issues });
2805
+ validateV4RuntimeResumePassportSection({ passport, issues });
2600
2806
  validatePublishEvidenceSection({ passport, publishEvidence, normalizedPublishEvidence, issues });
2601
2807
  const evidenceIndex = indexEvidenceArtifacts(evidenceArtifacts);
2602
2808
  validateReleaseArtifacts({ artifacts, evidenceIndex, issues });