@kungfu-tech/buildchain 4.1.0-alpha.0 → 4.1.0-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 (295) hide show
  1. package/AGENTS.md +30 -41
  2. package/CONTRIBUTING.md +6 -7
  3. package/architecture/action-taxonomy.json +5 -21
  4. package/architecture/agent-change-map.md +74 -56
  5. package/architecture/build-orchestration.json +8 -1
  6. package/architecture/ci-lane-change-budget.json +179 -69
  7. package/architecture/floating-consumer-policy.json +7 -17
  8. package/architecture/internal-capabilities.json +58 -72
  9. package/architecture/maintainability-debt.json +138 -52
  10. package/architecture/maintainability-policy.json +6 -15
  11. package/architecture/promotion-job-topology.json +39 -20
  12. package/architecture/release-topology.json +26 -37
  13. package/architecture/runtime-entry.json +26 -0
  14. package/architecture/runtime-resume-lineage.json +23 -0
  15. package/architecture/stage-capsule-qualification.json +2 -2
  16. package/architecture/universal-workflow-bootstrap.json +10 -25
  17. package/architecture/universal-workflow-capability-policy.json +61 -0
  18. package/architecture/universal-workflow-fault-campaign.json +18 -24
  19. package/architecture/workflow-taxonomy.json +0 -11
  20. package/contracts/fixtures/{v4-runtime-ref-resume-authority-v1 → runtime-resume-lineage-v2}/scenario.json +2 -3
  21. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +2 -2
  22. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  23. package/contracts/promotion-invocation-v1.schema.json +0 -44
  24. package/contracts/promotion-request-v1.schema.json +0 -39
  25. package/contracts/runtime-resume-lineage-v2.schema.json +182 -0
  26. package/dist/site/buildchain-contract.json +472 -361
  27. package/dist/site/buildchain-site.json +99 -99
  28. package/dist/site/capability-registry.json +3 -3
  29. package/dist/site/cli-registry.json +4 -4
  30. package/dist/site/controller-registry.json +94 -78
  31. package/dist/site/kfd-claims.json +224 -343
  32. package/dist/site/kfd-upstream-aggregate.json +1 -1
  33. package/dist/site/manual-registry.json +15 -15
  34. package/dist/site/node-api-registry.json +239 -354
  35. package/dist/site/page-registry.json +80 -80
  36. package/dist/site/public-surface-audit.json +203 -254
  37. package/dist/site/publication-authority-registry.json +1 -24
  38. package/dist/site/publication-registry.json +4 -4
  39. package/dist/site/release-provenance.json +1 -1
  40. package/dist/site/site-manifest.json +19 -19
  41. package/dist/site/workflow-registry.json +197 -357
  42. package/docs/MAP.md +3 -3
  43. package/docs/auditable-demo.md +4 -4
  44. package/docs/cli-reference.md +2 -2
  45. package/docs/code-organization.md +5 -4
  46. package/docs/controller-evidence.md +2 -2
  47. package/docs/dev-alpha-candidate-patrol.md +5 -5
  48. package/docs/dev-delivery-warrant.md +14 -24
  49. package/docs/engineering-housekeeper.md +3 -3
  50. package/docs/github-artifact-attestation.md +9 -12
  51. package/docs/github-governance-authority.md +4 -4
  52. package/docs/node-api-reference.md +153 -167
  53. package/docs/publication-artifacts.md +3 -4
  54. package/docs/publication-authority.md +2 -2
  55. package/docs/release-candidate.md +21 -27
  56. package/docs/release-governance.md +10 -10
  57. package/docs/release-promotion-request.md +11 -18
  58. package/docs/release-propagation.md +5 -6
  59. package/docs/release-tail-provider-plane.md +2 -3
  60. package/docs/reusable-build-surface.md +13 -7
  61. package/docs/runtime-entry.md +122 -0
  62. package/docs/runtime-train-validation.md +30 -41
  63. package/docs/v4-publication-rehearsal.md +5 -5
  64. package/docs/v4-stage-capsule.md +9 -10
  65. package/docs/web-surface-deployments.md +16 -20
  66. package/docs/workflow-catalog.md +0 -1
  67. package/package.json +6 -6
  68. package/packages/core/adoption/commands/init-repo.mjs +2 -2
  69. package/packages/core/adoption/cross-platform-adopter-qualification.js +2 -13
  70. package/packages/core/adoption/qualification/actions.js +3 -5
  71. package/packages/core/adoption/qualification/admission.js +2 -3
  72. package/packages/core/build/artifact/attestation.js +0 -1
  73. package/packages/core/build/artifact/contracts.js +1 -4
  74. package/packages/core/build/artifact/store.js +6 -5
  75. package/packages/core/build/artifact-signing.js +4 -10
  76. package/packages/core/build/binary/actions.js +15 -9
  77. package/packages/core/build/binary-publication-dispatch.js +2 -6
  78. package/packages/core/build/demo/action-context.js +2 -11
  79. package/packages/core/build/demo/binary-action.js +8 -10
  80. package/packages/core/build/gate/controller.js +0 -2
  81. package/packages/core/build/github-attestation/action.js +2 -7
  82. package/packages/core/build/github-attestation/admission.js +0 -8
  83. package/packages/core/build/github-attestation/transaction.js +1 -2
  84. package/packages/core/build/lifecycle/stage-action.js +2 -0
  85. package/packages/core/build/lifecycle/stage.js +6 -3
  86. package/packages/core/build/macos-credential-island/dmg-assembly.js +0 -2
  87. package/packages/core/build/macos-credential-island/lib.js +2 -6
  88. package/packages/core/build/plan/action-context.js +0 -7
  89. package/packages/core/build/plan/action.js +7 -11
  90. package/packages/core/build/plan/admission.js +6 -73
  91. package/packages/core/build/plan/configuration.js +1 -1
  92. package/packages/core/build/plan/environment.js +1 -1
  93. package/packages/core/build/plan/identity.js +1 -5
  94. package/packages/core/build/plan/resolve.js +7 -1
  95. package/packages/core/build/recovery/checkpoint.js +158 -0
  96. package/packages/core/build/recovery/transaction.js +51 -0
  97. package/packages/core/build/signing/authority-actions.js +2 -11
  98. package/packages/core/build/signing/control.js +0 -5
  99. package/packages/core/build/signing/controller-transaction.js +0 -1
  100. package/packages/core/build/signing/delegation.js +2 -4
  101. package/packages/core/build/signing/dispatch.js +10 -56
  102. package/packages/core/build/signing/intake.js +0 -4
  103. package/packages/core/build/signing/prepare.js +1 -1
  104. package/packages/core/build/signing/request.js +3 -8
  105. package/packages/core/build/source/checkout-action.js +6 -10
  106. package/packages/core/build/source/controller.js +1 -4
  107. package/packages/core/build/source/lifecycle.js +3 -13
  108. package/packages/core/build/stage-capsule/actions.js +3 -13
  109. package/packages/core/build/stage-capsule/campaign/resume.js +10 -5
  110. package/packages/core/build/stage-capsule/rehearsal/verify.js +3 -4
  111. package/packages/core/build/stage-capsule-resume-planner.js +0 -1
  112. package/packages/core/build/summary/aggregation.js +5 -5
  113. package/packages/core/build/summary/finalization.js +1 -1
  114. package/packages/core/build/verification/actions.js +3 -12
  115. package/packages/core/build/verification/runner.js +22 -20
  116. package/packages/core/consumer/floating-consumer-evidence.js +3 -21
  117. package/packages/core/consumer/floating-consumer-policy.js +2 -93
  118. package/packages/core/consumer/floating-consumer-release-passport.js +1 -34
  119. package/packages/core/consumer/policy-admission.js +1 -3
  120. package/packages/core/contracts/buildchain-contract.js +12 -146
  121. package/packages/core/contracts/workflow-yaml-contract.js +11 -1
  122. package/packages/core/dev-delivery/candidate/admission.js +3 -4
  123. package/packages/core/dev-delivery/candidate/coordinates.js +0 -8
  124. package/packages/core/dev-delivery/commands/dev-delivery-request.sh +5 -13
  125. package/packages/core/dev-delivery/commands/dev-delivery-source-proof-reuse.mjs +0 -3
  126. package/packages/core/dev-delivery/dev-delivery-contract-surface.js +4 -52
  127. package/packages/core/dev-delivery/dev-delivery-execution-transfer.js +2 -15
  128. package/packages/core/dev-delivery/native/action-context.js +4 -32
  129. package/packages/core/dev-delivery/native/provider-boundary.js +0 -2
  130. package/packages/core/dev-delivery/native/transactions.js +1 -2
  131. package/packages/core/dev-delivery/queue/reconciliation-action.js +6 -10
  132. package/packages/core/dev-delivery/source-proof/predicates.js +0 -6
  133. package/packages/core/dev-delivery/source-proof/reuse.js +0 -2
  134. package/packages/core/dev-delivery/source-proof/seal.js +0 -2
  135. package/packages/core/governance/alpha-candidate/action.js +1 -10
  136. package/packages/core/governance/audit/actions.js +1 -3
  137. package/packages/core/governance/audit/collection.js +1 -3
  138. package/packages/core/governance/audit/identity.js +3 -31
  139. package/packages/core/governance/audit/transactions.js +2 -4
  140. package/packages/core/governance/buildchain-publication-authority.js +0 -1
  141. package/packages/core/governance/commands/audit-github-governance.mjs +3 -2
  142. package/packages/core/governance/github-governance-authority.js +0 -5
  143. package/packages/core/governance/receipt-admission.js +0 -15
  144. package/packages/core/index.js +1 -1
  145. package/packages/core/observability/controller/identity.js +4 -5
  146. package/packages/core/observability/controller-evidence.js +5 -10
  147. package/packages/core/paper/commands/paper.mjs +6 -6
  148. package/packages/core/paper/operations/plans.js +2 -2
  149. package/packages/core/paper/operations/provisioning-validation.js +1 -1
  150. package/packages/core/paper/operations/scaffold-workflows.js +29 -12
  151. package/packages/core/paper/operations/scaffold.js +97 -69
  152. package/packages/core/paper/paper-agent-entry.js +0 -11
  153. package/packages/core/paper/paper-runtime-channels.js +21 -66
  154. package/packages/core/paper/paper-scaffold-content.js +1 -1
  155. package/packages/core/paper/paper.js +9 -5
  156. package/packages/core/paper/publication/candidate.js +0 -9
  157. package/packages/core/paper/publication-controller.js +2 -4
  158. package/packages/core/providers/github/artifact-attestation.js +1 -1
  159. package/packages/core/publication/authority/actions.js +9 -8
  160. package/packages/core/publication/authority/admission.js +0 -11
  161. package/packages/core/publication/authority/assembly.js +2 -7
  162. package/packages/core/publication/authority/control-plane.js +2 -2
  163. package/packages/core/publication/authority/governance.js +2 -9
  164. package/packages/core/publication/authority/request.js +7 -2
  165. package/packages/core/publication/authority/verification.js +3 -18
  166. package/packages/core/publication/binary/action.js +5 -7
  167. package/packages/core/publication/candidate/qualification-action.js +1 -6
  168. package/packages/core/publication/candidate/stage-capsules.js +1 -1
  169. package/packages/core/publication/npm/preview-action.js +9 -15
  170. package/packages/core/publication/oci/preview-actions.js +1 -10
  171. package/packages/core/publication/publication-artifact-candidate.js +2 -2
  172. package/packages/core/publication/publication-authority.js +2 -2
  173. package/packages/core/publication/publication-rehearsal-projection.js +3 -3
  174. package/packages/core/publication/settlement/actions.js +2 -11
  175. package/packages/core/release/binary/coordinates-action.js +0 -13
  176. package/packages/core/release/binary/coordinates.js +2 -19
  177. package/packages/core/release/commands/promotion-channel-router.mjs +12 -56
  178. package/packages/core/release/commands/resume-from-candidate-run.mjs +2 -3
  179. package/packages/core/release/dev-alpha-active-release-train.js +0 -5
  180. package/packages/core/release/line/action.js +2 -13
  181. package/packages/core/release/next-development/actions.js +2 -5
  182. package/packages/core/release/passport/consumer-validation.js +1 -28
  183. package/packages/core/release/promote-candidate/development-source.js +7 -8
  184. package/packages/core/release/promotion/actions.js +7 -38
  185. package/packages/core/release/promotion/admission.js +3 -50
  186. package/packages/core/release/promotion/candidate.js +2 -4
  187. package/packages/core/release/promotion/channel.js +13 -101
  188. package/packages/core/release/promotion/qualification-action.js +2 -13
  189. package/packages/core/release/promotion/routing.js +16 -81
  190. package/packages/core/release/promotion/selection.js +1 -9
  191. package/packages/core/release/promotion-request.js +1 -40
  192. package/packages/core/release/qualification/actions.js +0 -8
  193. package/packages/core/release/qualification/public-build.js +1 -1
  194. package/packages/core/release/recovery/candidate.js +15 -21
  195. package/packages/core/release/recovery/capsules.js +2 -10
  196. package/packages/core/release/recovery/lineage.js +305 -0
  197. package/packages/core/release/recovery/provenance.js +1 -2
  198. package/packages/core/release/recovery/publication.js +4 -14
  199. package/packages/core/release/recovery/readback.js +19 -32
  200. package/packages/core/release/recovery/runtime.js +4 -95
  201. package/packages/core/release/release-candidate-recovery.js +4 -9
  202. package/packages/core/release/release-candidate.js +2 -5
  203. package/packages/core/release/reseal/actions.js +3 -22
  204. package/packages/core/release/reseal/admission.js +0 -2
  205. package/packages/core/release/reseal/finalize-platform.js +0 -2
  206. package/packages/core/release/tail-reseal-github.js +1 -2
  207. package/packages/core/release/tail-reseal-receipt.js +0 -1
  208. package/packages/core/release/tail-reseal.js +2 -20
  209. package/packages/core/release/version-state.js +16 -13
  210. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  211. package/packages/core/runtime/checkout-identity.js +0 -13
  212. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  213. package/packages/core/runtime/entry/actions.js +121 -0
  214. package/packages/core/runtime/entry/activation.js +86 -0
  215. package/packages/core/runtime/entry/github.js +55 -0
  216. package/packages/core/runtime/entry/recovery.js +37 -0
  217. package/packages/core/runtime/entry/selection.js +110 -0
  218. package/packages/core/web/controller.js +1 -1
  219. package/packages/core/web/publication-admission-action.js +1 -8
  220. package/packages/core/web/web-surface-publication-candidate.js +2 -5
  221. package/packages/core/workflow/admission/actions.js +6 -26
  222. package/packages/core/workflow/admission/recovery.js +3 -43
  223. package/packages/core/workflow/admission/transactions.js +8 -24
  224. package/packages/core/workflow/commands/buildchain-cli-help.mjs +1 -1
  225. package/packages/core/workflow/commands/universal-workflow-engine.mjs +2 -7
  226. package/packages/core/workflow/dogfood/actions.js +0 -17
  227. package/packages/core/workflow/engine/action.js +0 -5
  228. package/packages/core/workflow/engine/conformance.js +1 -1
  229. package/packages/core/workflow/engine/execution.js +2 -6
  230. package/packages/core/workflow/engine/identity.js +1 -6
  231. package/packages/core/workflow/engine/release-environment.js +11 -35
  232. package/packages/core/workflow/engine/release-promotion.js +2 -2
  233. package/packages/core/workflow/settlement/actions.js +0 -12
  234. package/packages/core/workflow/universal-self-dogfood.js +2 -16
  235. package/packages/core/workflow/universal-workflow-bootstrap.js +16 -241
  236. package/packages/core/workflow/workflow-taxonomy.mjs +2 -2
  237. package/scripts/check-dev-delivery-actions.mjs +17 -8
  238. package/scripts/check-floating-consumer-policy-contract.mjs +6 -6
  239. package/scripts/check-inventory.mjs +1 -10
  240. package/scripts/check-public-dogfood-contract.mjs +34 -21
  241. package/scripts/check-release-topology.mjs +2 -6
  242. package/scripts/check-runtime-entry.mjs +133 -0
  243. package/scripts/check-universal-workflow-bootstrap.mjs +45 -281
  244. package/scripts/check-workflows.sh +0 -7
  245. package/scripts/generate-bootstrap-admission.mjs +1 -1
  246. package/scripts/generate-channel-promotion-workflow.mjs +4 -4
  247. package/scripts/inventory/binary.mjs +4 -4
  248. package/scripts/inventory/promotion.mjs +5 -3
  249. package/scripts/runtime-source-boundaries.mjs +70 -0
  250. package/scripts/site-capability-metadata.mjs +2 -2
  251. package/scripts/workflow-action-graph.mjs +5 -18
  252. package/scripts/workflow-self-reference.mjs +20 -0
  253. package/templates/native-dev-delivery.yml +4 -4
  254. package/templates/universal-buildchain-bootstrap.yml +12 -2
  255. package/architecture/runtime-ref-resume-authority.json +0 -64
  256. package/architecture/universal-workflow-train-admission.json +0 -76
  257. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +0 -235
  258. package/docs/v4-runtime-ref-resume-authority.md +0 -69
  259. package/packages/core/consumer/channel-selection.js +0 -211
  260. package/packages/core/consumer/commands/buildchain-channel-router.mjs +0 -81
  261. package/packages/core/consumer/invocation-selector.js +0 -4
  262. package/packages/core/consumer/runtime-ref-resume-authority.js +0 -621
  263. package/packages/core/consumer/workflow-definition-authority.js +0 -64
  264. package/packages/core/contracts/runtime-contract-action.js +0 -48
  265. package/packages/core/release/commands/promotion-identity-resolver.mjs +0 -77
  266. package/packages/core/release/commands/verify-promotion-router-binding.sh +0 -36
  267. package/packages/core/release/promotion/identities.js +0 -56
  268. package/packages/core/release/runtime-override-authorization.js +0 -242
  269. package/packages/core/runtime/commands/runtime-ref-core.mjs +0 -171
  270. package/packages/core/runtime/provider-closure.js +0 -60
  271. package/packages/core/runtime/runtime-selection-action.js +0 -34
  272. package/packages/core/runtime/runtime-selection.js +0 -136
  273. package/packages/core/runtime/workflow-runtime.js +0 -11
  274. package/packages/core/workflow/admission/review.js +0 -188
  275. package/packages/core/workflow/commands/universal-workflow-backflow.mjs +0 -26
  276. package/packages/core/workflow/installation.js +0 -33
  277. package/packages/core/workflow/settlement/backflow.js +0 -187
  278. package/scripts/generate-bootstrap-recovery.mjs +0 -94
  279. package/templates/bootstrap-recovery/actions/build/source/admit/action.yml +0 -17
  280. package/templates/bootstrap-recovery/actions/build/source/admit/dist/index.js +0 -63
  281. package/templates/bootstrap-recovery/actions/runtime/dependencies/install/action.yml +0 -18
  282. package/templates/bootstrap-recovery/actions/runtime/dependencies/install/dist/index.js +0 -63
  283. package/templates/bootstrap-recovery/actions/runtime/environment/prepare/action.yml +0 -21
  284. package/templates/bootstrap-recovery/actions/workflow/admission/inspect/action.yml +0 -27
  285. package/templates/bootstrap-recovery/actions/workflow/admission/inspect/dist/index.js +0 -64
  286. package/templates/bootstrap-recovery/actions/workflow/admission/review/action.yml +0 -15
  287. package/templates/bootstrap-recovery/actions/workflow/admission/review/dist/index.js +0 -65
  288. package/templates/bootstrap-recovery/actions/workflow/recovery/admit/action.yml +0 -47
  289. package/templates/bootstrap-recovery/actions/workflow/recovery/execute/action.yml +0 -48
  290. package/templates/bootstrap-recovery/actions/workflow/recovery/settle/action.yml +0 -54
  291. package/templates/bootstrap-recovery/actions/workflow/settlement/seal/action.yml +0 -17
  292. package/templates/bootstrap-recovery/actions/workflow/settlement/seal/dist/index.js +0 -65
  293. package/templates/bootstrap-recovery/manifest.json +0 -68
  294. package/templates/bootstrap-recovery/package.json +0 -9
  295. package/templates/universal-buildchain-bootstrap-recovery.yml +0 -115
@@ -25,6 +25,7 @@ export async function runBuildStage({
25
25
  stage,
26
26
  sourceRoot,
27
27
  environment,
28
+ recovery,
28
29
  }) {
29
30
  const file = path.join(
30
31
  sourceRoot,
@@ -57,6 +58,7 @@ export async function runBuildStage({
57
58
  ),
58
59
  });
59
60
  try {
61
+ const restored = stage === "build" && recovery ? await recovery.restore() : null;
60
62
  if (stage === "install" && plan.anchored_material) {
61
63
  const evidence = await session.phase((env) =>
62
64
  createAnchoredVersionMaterialEvidence({
@@ -78,22 +80,23 @@ export async function runBuildStage({
78
80
  const usesCache =
79
81
  cache.provider !== "none" &&
80
82
  JSON.parse(cache.platforms_json).includes(platform.id);
81
- if (stage === "build" && usesCache)
83
+ if (stage === "build" && usesCache && !restored)
82
84
  await session.phase((env) =>
83
85
  prepareCompilerCacheEvidence({ cwd: sourceRoot, env }),
84
86
  );
85
- await session.phase((env) =>
87
+ if (!restored) await session.phase((env) =>
86
88
  runLifecycle({
87
89
  ...lifecycleOptions(plan, platform, stage, sourceRoot),
88
90
  env,
89
91
  }),
90
92
  );
91
93
  if (stage === "build") {
92
- if (usesCache)
94
+ if (usesCache && !restored)
93
95
  await session.phase((env) =>
94
96
  verifyCompilerCacheActivity({ cwd: sourceRoot, env }),
95
97
  );
96
98
  await sealSigning(plan, platform, sourceRoot);
99
+ if (recovery) await recovery.retain();
97
100
  }
98
101
  if (
99
102
  stage === "verify" &&
@@ -366,7 +366,6 @@ export function createRunBoundDmg({
366
366
  function acceptedAssemblyBinding(binding, request, expectedExecution) {
367
367
  return (
368
368
  binding?.sourceSha === request.source.sha &&
369
- binding?.runtimeSha === request.runtime.sha &&
370
369
  binding?.requestDigest === request.digest &&
371
370
  binding?.unsignedArchiveDigest === request.artifact.transport.digest &&
372
371
  binding?.runId === expectedExecution.runId &&
@@ -427,7 +426,6 @@ export function acceptedMacosCredentialEvidence(
427
426
  evidence.source?.repository === request.source.repository &&
428
427
  evidence.source?.sha === request.source.sha &&
429
428
  evidence.source?.treeSha === request.source.treeSha &&
430
- evidence.buildchain?.runtimeSha === request.runtime.sha &&
431
429
  evidence.input?.requestDigest === request.digest &&
432
430
  evidence.app?.architecture === request.artifact.arch &&
433
431
  evidence.execution?.runId === expectedExecution.runId &&
@@ -363,18 +363,14 @@ export function loadArtifactSigningInput(inputRoot, expected = {}) {
363
363
  request.source.treeSha,
364
364
  "artifact signing source tree SHA",
365
365
  );
366
- const runtimeSha = requireSha(
367
- request.runtime.sha,
368
- "artifact signing runtime SHA",
369
- );
366
+ const runtimeSha = request.runtime.sha;
370
367
  if (expected.repository && repository !== expected.repository)
371
368
  throw new Error("artifact signing request repository mismatch");
372
369
  if (expected.sourceSha && sourceSha !== expected.sourceSha)
373
370
  throw new Error("artifact signing request source SHA mismatch");
374
371
  if (expected.sourceTreeSha && sourceTreeSha !== expected.sourceTreeSha)
375
372
  throw new Error("artifact signing request source tree SHA mismatch");
376
- if (expected.runtimeSha && runtimeSha !== expected.runtimeSha)
377
- throw new Error("artifact signing request runtime SHA mismatch");
373
+
378
374
  const transport = request.artifact.transport;
379
375
  if (transport?.format !== "ditto-zip") {
380
376
  throw new Error("apple app signing request must use ditto-zip transport");
@@ -1,21 +1,14 @@
1
1
  import path from "node:path";
2
- import { execFileSync } from "node:child_process";
3
2
  import { installationRoot } from "../../runtime/installation-root.js";
4
3
  import { bindBuildPlan } from "./identity.js";
5
4
  export function readBuildActionContext(core, env) {
6
5
  const workspace = path.resolve(env.GITHUB_WORKSPACE);
7
6
  const runtimeRoot = installationRoot(import.meta.url);
8
- const runtimeSha = execFileSync("git", ["rev-parse", "HEAD"], {
9
- cwd: runtimeRoot,
10
- encoding: "utf8",
11
- stdio: ["ignore", "pipe", "pipe"],
12
- }).trim();
13
7
  const selected = core.getInput("platform");
14
8
  const context = bindBuildPlan({
15
9
  plan: JSON.parse(core.getInput("plan", { required: true })),
16
10
  platformId: selected ? JSON.parse(selected).id : "",
17
11
  workspace,
18
- runtimeSha,
19
12
  run: {
20
13
  repository: env.GITHUB_REPOSITORY,
21
14
  id: env.GITHUB_RUN_ID,
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { execFileSync } from "node:child_process";
3
2
  import { getOctokit } from "@actions/github";
4
3
  import { installationRoot } from "../../runtime/installation-root.js";
5
4
  import { reportBuildchainIssue } from "../../governance/issue-reporting.js";
@@ -10,12 +9,7 @@ export async function resolveBuildPlanAction(core, env) {
10
9
  const workspace = path.resolve(env.GITHUB_WORKSPACE || process.cwd());
11
10
  const runtimeRoot = installationRoot(import.meta.url);
12
11
  const workflowSha = core.getInput("workflow-sha", { required: true });
13
- if (
14
- execFileSync("git", ["-C", runtimeRoot, "rev-parse", "HEAD"], {
15
- encoding: "utf8",
16
- }).trim() !== workflowSha
17
- )
18
- throw new Error("Executing runtime differs from called workflow");
12
+ const selectedSource = JSON.parse(env.BUILDCHAIN_EXECUTION_SOURCE || "{}");
19
13
  const token = core.getInput("token", { required: true });
20
14
  const github = getOctokit(token);
21
15
  const coordinates = (repository) => {
@@ -27,6 +21,7 @@ export async function resolveBuildPlanAction(core, env) {
27
21
  workspace,
28
22
  sourceRoot: path.join(workspace, "source"),
29
23
  runtimeRoot,
24
+ runtime: JSON.parse(env.BUILDCHAIN_RUNTIME_SELECTION),
30
25
  configPath: core.getInput("config-path"),
31
26
  workflow: {
32
27
  ref: core.getInput("workflow-ref", { required: true }),
@@ -35,11 +30,12 @@ export async function resolveBuildPlanAction(core, env) {
35
30
  name: env.GITHUB_WORKFLOW,
36
31
  runUrl: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
37
32
  },
33
+ recovery: selectedSource.runId ? selectedSource : undefined,
38
34
  source: {
39
- sha: env.GITHUB_SHA,
40
- ref: env.GITHUB_REF,
41
- refName: env.GITHUB_REF_NAME,
42
- baseRef: env.GITHUB_BASE_REF,
35
+ sha: selectedSource.sha || env.GITHUB_SHA,
36
+ ref: selectedSource.ref || env.GITHUB_REF,
37
+ refName: selectedSource.ref?.replace(/^refs\/(?:heads|tags)\//u, "") || env.GITHUB_REF_NAME,
38
+ baseRef: selectedSource.runId ? "" : env.GITHUB_BASE_REF,
43
39
  },
44
40
  event: {
45
41
  name: env.GITHUB_EVENT_NAME,
@@ -2,21 +2,11 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { scanConsumerPolicy } from "../../consumer/policy-scan.js";
4
4
  import { validatePackageManagerContract } from "../package-manager.js";
5
- import {
6
- inspectRuntimeContract,
7
- assertRuntimeContractAccepted,
8
- } from "../../contracts/runtime-contract-inspection.js";
9
5
 
10
- export async function admitBuildSource(
11
- { plan, sourceRoot, runtimeRoot, workspace, workflow },
12
- { reportIssue, summarize, warn },
13
- ) {
6
+ export async function admitBuildSource({ plan, sourceRoot, runtimeRoot, workflow }) {
14
7
  const policy = scanConsumerPolicy({
15
8
  runtimeRoot,
16
- output: path.join(
17
- sourceRoot,
18
- ".buildchain/evidence/consumer-policy-receipt.json",
19
- ),
9
+ output: path.join(sourceRoot, ".buildchain/evidence/consumer-policy-receipt.json"),
20
10
  root: sourceRoot,
21
11
  invocationRoot: sourceRoot,
22
12
  repository: plan.run.repository,
@@ -24,70 +14,13 @@ export async function admitBuildSource(
24
14
  invokedWorkflow: plan.identity.visible_workflow,
25
15
  invocationSourcePath: workflow.callerRef,
26
16
  expectedInvocationChannel: plan.identity.channel,
27
- resolvedWorkflowSha: plan.identity.sha,
17
+ resolvedWorkflowSha: plan.entry.sha,
28
18
  resolvedRuntimeSha: plan.identity.sha,
29
19
  stableLockPath: ".buildchain/contract-lock.json",
30
20
  alphaLockPath: ".buildchain/alpha-contract-lock.json",
31
21
  });
32
- if (!policy.ok)
33
- throw new Error("Build source rejected by floating consumer policy");
22
+ if (!policy.ok) throw new Error("Build source rejected by floating consumer policy");
34
23
  validatePackageManagerContract({ cwd: sourceRoot, expectedManager: "" });
35
- const lock = inspectRuntimeContract({
36
- lockPath: path.join(sourceRoot, plan.contract.lock_path),
37
- currentContractPath: path.join(
38
- runtimeRoot,
39
- "dist/site/buildchain-contract.json",
40
- ),
41
- runtimeRoot,
42
- runtimeRef: plan.identity.ref,
43
- runtimeSha: plan.identity.sha,
44
- runtimeClass: plan.identity.channel,
45
- compatibilityPolicy: plan.build.contract.compatibility_policy,
46
- workflowShellRef: plan.identity.ref,
47
- expectedChannel: plan.identity.channel,
48
- expectedMajor: plan.identity.major,
49
- allowOpaqueRuntime: false,
50
- issueMode: plan.build.contract.drift_issue_mode,
51
- issueBodyPath: path.join(
52
- workspace,
53
- ".buildchain/contract-drift/issue-body.md",
54
- ),
55
- repository: plan.run.repository,
56
- workflow: workflow.name,
57
- runUrl: workflow.runUrl,
58
- });
59
- await summarize(lock.summary);
60
- const admission = {
61
- policy: policy.receipt,
62
- policy_root: policy.receiptRoot,
63
- contract_digest: lock.outputs["contract-digest"],
64
- lock_status: lock.outputs["contract-lock-status"],
65
- };
66
- if (lock.shouldIssue) {
67
- try {
68
- await reportIssue({
69
- targetRepository: plan.run.repository,
70
- title: `[Buildchain contract] ${plan.run.repository} drift on ${plan.identity.ref}`,
71
- summary: `Buildchain contract drift: ${admission.lock_status}`,
72
- failureCode: `buildchain-contract-${admission.lock_status}`,
73
- buildchainRef: plan.identity.ref,
74
- buildchainVersion: admission.contract_digest,
75
- consumerRepository: plan.run.repository,
76
- consumerRef: plan.source.ref,
77
- consumerSha: plan.source.sha,
78
- workflow: workflow.name,
79
- runId: plan.run.id,
80
- body: fs.readFileSync(
81
- lock.outputs["contract-lock-issue-body-file"],
82
- "utf8",
83
- ),
84
- labels: "buildchain-contract-drift",
85
- commentCooldownHours: 24,
86
- });
87
- } catch {
88
- warn("Contract drift report could not be submitted");
89
- }
90
- }
91
- assertRuntimeContractAccepted(lock);
92
- return admission;
24
+ const contract = JSON.parse(fs.readFileSync(path.join(runtimeRoot, "dist/site/buildchain-contract.json"), "utf8"));
25
+ return { policy: policy.receipt, policy_root: policy.receiptRoot, contract_digest: contract.contractDigest, lock_status: plan.runtime?.origin || "contract-lock" };
93
26
  }
@@ -32,7 +32,7 @@ export function resolveBuildConfiguration({
32
32
  ),
33
33
  ),
34
34
  }) {
35
- for (const [label, value] of Object.entries({ workflowSha, sourceSha })) {
35
+ for (const [label, value] of Object.entries({ sourceSha })) {
36
36
  if (!/^[a-f0-9]{40}$/u.test(value || ""))
37
37
  throw new Error(`${label} must be an exact SHA`);
38
38
  }
@@ -7,7 +7,7 @@ export function commonEnv(plan, platform) {
7
7
  BUILDCHAIN_SOURCE_REF: plan.source.ref,
8
8
  BUILDCHAIN_RUNTIME_REPOSITORY: plan.identity.repository,
9
9
  BUILDCHAIN_RUNTIME_SHA: plan.identity.sha,
10
- BUILDCHAIN_RUNTIME_REF: plan.identity.ref,
10
+ BUILDCHAIN_RUNTIME_REF: plan.runtime?.ref || plan.identity.ref,
11
11
  BUILDCHAIN_PLATFORM_ID: platform?.id || "",
12
12
  BUILDCHAIN_PLATFORM_NAME: platform?.name || "",
13
13
  BUILDCHAIN_ARTIFACT_NAME: plan.artifacts.name,
@@ -4,13 +4,12 @@ export function assertPlan(plan) {
4
4
  if (plan.schema !== "buildchain.build-plan/v1" || root !== rootOf(body))
5
5
  throw new Error("Build plan root mismatch");
6
6
  for (const sha of [
7
- plan.identity.sha,
8
7
  plan.source.sha,
9
8
  plan.source.tree_sha,
10
9
  ]) {
11
10
  if (!/^[a-f0-9]{40}$/u.test(sha))
12
11
  throw new Error(
13
- "Build plan requires exact source, tree and runtime identities",
12
+ "Build plan requires exact source and tree identities",
14
13
  );
15
14
  }
16
15
  if (
@@ -28,7 +27,6 @@ export function bindBuildPlan({
28
27
  run,
29
28
  platformId = "",
30
29
  workspace,
31
- runtimeSha,
32
30
  }) {
33
31
  assertPlan(plan);
34
32
  if (
@@ -37,8 +35,6 @@ export function bindBuildPlan({
37
35
  plan.run.attempt !== run.attempt
38
36
  )
39
37
  throw new Error("Build plan belongs to another run");
40
- if (runtimeSha !== plan.identity.sha)
41
- throw new Error("Executing runtime differs from the build plan");
42
38
  const platform = platformId
43
39
  ? plan.platforms.find((item) => item.id === platformId)
44
40
  : null;
@@ -39,10 +39,16 @@ export async function resolveBuildPlan(request, providers) {
39
39
  execFileSync("git", ["-C", sourceRoot, ...args], {
40
40
  encoding: "utf8",
41
41
  }).trim();
42
+ plan.entry = { ref: workflow.ref, sha: workflow.sha };
43
+ if (request.runtime) {
44
+ plan.identity.sha = request.runtime.sha;
45
+ plan.runtime = request.runtime;
46
+ }
42
47
  if (git("rev-parse", "HEAD") !== plan.source.sha)
43
48
  throw new Error("Checked out source differs from invocation");
44
49
  plan.source.tree_sha = git("rev-parse", "HEAD^{tree}");
45
50
  plan.run = { ...run };
51
+ if (request.recovery) plan.recovery = request.recovery;
46
52
  plan.admission = await admitBuildSource(
47
53
  { plan, sourceRoot, runtimeRoot, workspace, workflow },
48
54
  providers,
@@ -64,7 +70,7 @@ export async function resolveBuildPlan(request, providers) {
64
70
  inputBoundary: "strict",
65
71
  source: { repository: run.repository, sha: plan.source.sha },
66
72
  runtime: {
67
- ref: plan.identity.ref,
73
+ ref: plan.runtime?.ref || plan.identity.ref,
68
74
  sha: plan.identity.sha,
69
75
  contractDigest: plan.admission.contract_digest,
70
76
  },
@@ -0,0 +1,158 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { rootOf, readJson, writeJson } from "../plan/values.js";
5
+ import { verifyManifest } from "../artifact/contracts.js";
6
+
7
+ export function buildRecoveryIdentity(plan, platform) {
8
+ return rootOf({
9
+ source: {
10
+ repository: plan.run.repository,
11
+ sha: plan.source.sha,
12
+ tree: plan.source.tree_sha,
13
+ },
14
+ configuration: plan.configuration_root,
15
+ platform,
16
+ project: plan.project,
17
+ lifecycle: plan.lifecycle,
18
+ tools: plan.tools,
19
+ container: plan.container,
20
+ environment: plan.environment,
21
+ artifacts: plan.artifacts,
22
+ });
23
+ }
24
+ const digestFile = (file) =>
25
+ createHash("sha256").update(fs.readFileSync(file)).digest("hex");
26
+ function supportFiles(plan) {
27
+ return plan.build?.diagnostics?.sample_process_tree
28
+ ? [
29
+ ".buildchain/diagnostics/process-summary.json",
30
+ ".buildchain/diagnostics/process-samples.jsonl",
31
+ ]
32
+ : [];
33
+ }
34
+ export const checkpointName = (platform) =>
35
+ `buildchain-build-checkpoint-${platform.id}`;
36
+
37
+ export function sealBuildCheckpoint({ plan, platform, sourceRoot }) {
38
+ const manifestPath = `.buildchain/artifacts/${platform.id}/manifest-build.json`;
39
+ const file = path.join(sourceRoot, manifestPath);
40
+ if (!fs.existsSync(file)) return null;
41
+ const manifest = verifyManifest(file, sourceRoot, plan, platform);
42
+ // A command without retained outputs cannot promise that another runner can
43
+ // continue it. Its ordinary execution remains valid and will run again.
44
+ if (!manifest.files.length) return null;
45
+ const retainedFiles = [
46
+ ...manifest.files.map(({ path }) => path),
47
+ manifestPath,
48
+ ...supportFiles(plan),
49
+ ];
50
+ const body = {
51
+ schema: "buildchain.build-checkpoint/v1",
52
+ identity: buildRecoveryIdentity(plan, platform),
53
+ source: plan.source,
54
+ producer: plan.run,
55
+ runtime: plan.runtime,
56
+ platform: platform.id,
57
+ manifestPath,
58
+ files: retainedFiles.map((relative) => ({
59
+ path: relative,
60
+ mode: fs.statSync(path.join(sourceRoot, relative)).mode & 0o777,
61
+ sha256: digestFile(path.join(sourceRoot, relative)),
62
+ })),
63
+ };
64
+ const checkpoint = { ...body, root: rootOf(body) };
65
+ const checkpointPath = `.buildchain/recovery/${platform.id}/checkpoint.json`;
66
+ writeJson(path.join(sourceRoot, checkpointPath), checkpoint);
67
+ return { checkpoint, paths: [checkpointPath, ...retainedFiles] };
68
+ }
69
+
70
+ export function restoreBuildCheckpoint({
71
+ plan,
72
+ platform,
73
+ sourceRoot,
74
+ retainedRoot,
75
+ }) {
76
+ const checkpoint = readJson(
77
+ path.join(
78
+ retainedRoot,
79
+ `.buildchain/recovery/${platform.id}/checkpoint.json`,
80
+ ),
81
+ );
82
+ const { root, ...body } = checkpoint;
83
+ if (
84
+ checkpoint.schema !== "buildchain.build-checkpoint/v1" ||
85
+ rootOf(body) !== root ||
86
+ checkpoint.identity !== buildRecoveryIdentity(plan, platform) ||
87
+ checkpoint.platform !== platform.id ||
88
+ checkpoint.producer.repository !== plan.run.repository ||
89
+ checkpoint.producer.id !== plan.recovery.runId
90
+ )
91
+ throw new Error(
92
+ "Build checkpoint does not match the retained source, inputs or producer",
93
+ );
94
+ const expectedManifest = `.buildchain/artifacts/${platform.id}/manifest-build.json`;
95
+ if (checkpoint.manifestPath !== expectedManifest)
96
+ throw new Error("Unexpected build checkpoint manifest");
97
+ const manifest = verifyManifest(
98
+ path.join(retainedRoot, expectedManifest),
99
+ retainedRoot,
100
+ plan,
101
+ platform,
102
+ );
103
+ if (!manifest.files.length)
104
+ throw new Error("Build checkpoint has no retained outputs");
105
+ const files = [
106
+ ...manifest.files.map(({ path }) => path),
107
+ expectedManifest,
108
+ ...supportFiles(plan),
109
+ ];
110
+ if (
111
+ !Array.isArray(checkpoint.files) ||
112
+ JSON.stringify(checkpoint.files.map((f) => f.path)) !==
113
+ JSON.stringify(files) ||
114
+ checkpoint.files.some(
115
+ (f) => !Number.isInteger(f.mode) || f.mode < 0 || f.mode > 0o777,
116
+ )
117
+ )
118
+ throw new Error("Build checkpoint file metadata is invalid");
119
+ for (const file of checkpoint.files)
120
+ if (file.sha256 !== digestFile(path.join(retainedRoot, file.path)))
121
+ throw new Error("Build checkpoint retained file digest mismatch");
122
+ // Validate every destination before changing the fresh source checkout.
123
+ for (const relative of files) {
124
+ if (relative.split("/").some((p) => [".git", ".github"].includes(p)))
125
+ throw new Error(
126
+ "Build checkpoint cannot replace repository control files",
127
+ );
128
+ const destination = path.resolve(sourceRoot, relative);
129
+ if (!destination.startsWith(path.resolve(sourceRoot) + path.sep))
130
+ throw new Error("Build checkpoint destination escapes source");
131
+ let current = destination;
132
+ while (current !== path.resolve(sourceRoot)) {
133
+ let stat;
134
+ try {
135
+ stat = fs.lstatSync(current);
136
+ } catch (error) {
137
+ if (error.code !== "ENOENT") throw error;
138
+ }
139
+ if (stat?.isSymbolicLink())
140
+ throw new Error("Build checkpoint destination traverses a symlink");
141
+ current = path.dirname(current);
142
+ }
143
+ }
144
+ for (const relative of files) {
145
+ const destination = path.resolve(sourceRoot, relative);
146
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
147
+ fs.copyFileSync(path.join(retainedRoot, relative), destination);
148
+ fs.chmodSync(
149
+ destination,
150
+ checkpoint.files.find((f) => f.path === relative).mode,
151
+ );
152
+ }
153
+ return {
154
+ checkpointRoot: root,
155
+ producer: checkpoint.producer,
156
+ stage: "build",
157
+ };
158
+ }
@@ -0,0 +1,51 @@
1
+ import path from "node:path";
2
+ import { createBuildArtifactStore } from "../artifact/store.js";
3
+ import {
4
+ checkpointName,
5
+ sealBuildCheckpoint,
6
+ restoreBuildCheckpoint,
7
+ } from "./checkpoint.js";
8
+
9
+ export function buildStageRecovery(
10
+ context,
11
+ token,
12
+ store = createBuildArtifactStore({ token }),
13
+ ) {
14
+ const { plan, platform, sourceRoot, workspace } = context;
15
+ return {
16
+ async restore() {
17
+ if (!plan.recovery) return null;
18
+ const retainedRoot = path.join(
19
+ workspace,
20
+ `.buildchain/recovery-input/${platform.id}`,
21
+ );
22
+ const name = checkpointName(platform);
23
+ let ref;
24
+ try {
25
+ ref = await store.lookup(plan, name, { runId: plan.recovery.runId });
26
+ } catch (error) {
27
+ // Only a proven absent checkpoint permits rebuilding. Provider errors,
28
+ // duplicates and digest failures remain errors with the original signal.
29
+ if (error.code === "artifact-not-found") return null;
30
+ throw error;
31
+ }
32
+ await store.download(ref, retainedRoot);
33
+ return restoreBuildCheckpoint({
34
+ plan,
35
+ platform,
36
+ sourceRoot,
37
+ retainedRoot,
38
+ });
39
+ },
40
+ async retain() {
41
+ const sealed = sealBuildCheckpoint({ plan, platform, sourceRoot });
42
+ if (!sealed) return null;
43
+ return store.upload(
44
+ plan,
45
+ checkpointName(platform),
46
+ sealed.paths,
47
+ sourceRoot,
48
+ );
49
+ },
50
+ };
51
+ }
@@ -1,7 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { installationRoot } from "../../runtime/installation-root.js";
4
- import { verifyCheckoutIdentity } from "../../runtime/checkout-identity.js";
5
4
  import { inspectArtifactSigningRequests } from "./intake.js";
6
5
  import { signDetachedArtifactRequests } from "./detached.js";
7
6
  import { finalizeNativeArtifactSigningResult } from "./native-result.js";
@@ -14,15 +13,8 @@ const get = (core, name) => core.getInput(name, { required: true });
14
13
  function context(core, env, needsItem = false) {
15
14
  const workspace = path.resolve(env.GITHUB_WORKSPACE),
16
15
  runtimeRoot = installationRoot(import.meta.url);
17
- if (fs.realpathSync(workspace) !== fs.realpathSync(runtimeRoot))
18
- throw new Error(
19
- "Signing authority must execute its checked-out source implementation",
20
- );
21
- verifyCheckoutIdentity({
22
- directory: runtimeRoot,
23
- sha: env.GITHUB_SHA,
24
- label: "Signing authority source",
25
- });
16
+
17
+
26
18
  const request = JSON.parse(get(core, "request-json")),
27
19
  item = needsItem ? JSON.parse(get(core, "item-json")) : undefined;
28
20
  if (item && item.slug !== safeSigningId(item.id))
@@ -52,7 +44,6 @@ export function admitSigningRequestsAction(core, env) {
52
44
  const matrices = inspectArtifactSigningRequests({
53
45
  inputRoot: requestRoot,
54
46
  expectedRepository: request["source-repository"],
55
- expectedRuntimeSha: request["expected-runtime-sha"],
56
47
  expectedRequestRoot: request["expected-request-root"],
57
48
  });
58
49
  for (const [key, value] of Object.entries(matrices))
@@ -190,11 +190,6 @@ export function assertArtifactSigningControllerReceipt({
190
190
  finalDelegation.authority.runId,
191
191
  "authority run ID",
192
192
  ],
193
- [
194
- result.authority.runtimeSha,
195
- finalDelegation.authority.runtimeSha,
196
- "authority runtime SHA",
197
- ],
198
193
  [
199
194
  result.authority.resultArtifact,
200
195
  finalDelegation.authority.resultArtifact,
@@ -42,7 +42,6 @@ export async function controlSigning(context) {
42
42
  authority = (
43
43
  await dispatch({
44
44
  authorityRepository: request.authority.repository,
45
- authorityRef: request.runtime.ref,
46
45
  runtimeSha: request.runtime.sha,
47
46
  sourceRepository: plan.run.repository,
48
47
  sourceRunId: plan.run.id,
@@ -89,7 +89,7 @@ export function validateArtifactSigningDelegation(value) {
89
89
  },
90
90
  runtime: {
91
91
  repository: repository(value.runtime?.repository, "runtime.repository"),
92
- sha: exactSha(value.runtime?.sha, "runtime.sha"),
92
+ sha: value.runtime?.sha,
93
93
  },
94
94
  platform: {
95
95
  id: required(value.platform?.id, "platform.id"),
@@ -106,7 +106,7 @@ export function validateArtifactSigningDelegation(value) {
106
106
  authority: {
107
107
  runtimeSha:
108
108
  requestCount > 0
109
- ? exactSha(value.authority?.runtimeSha, "authority.runtimeSha")
109
+ ? value.authority?.runtimeSha
110
110
  : optional(value.authority?.runtimeSha, "authority.runtimeSha"),
111
111
  runId:
112
112
  requestCount > 0
@@ -269,8 +269,6 @@ export function assertArtifactSigningDelegationContext(
269
269
  [sourceRunId, value.source.runId, "source run ID"],
270
270
  [sourceRunAttempt, String(value.source.runAttempt), "source run attempt"],
271
271
  [sourceSha, value.source.sha, "source SHA"],
272
- [runtimeRepository, value.runtime.repository, "runtime repository"],
273
- [runtimeSha, value.runtime.sha, "runtime SHA"],
274
272
  [platformId, value.platform.id, "platform ID"],
275
273
  ];
276
274
  for (const [expected, actual, label] of expectations) {