@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.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. 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
  }