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

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 (228) hide show
  1. package/AGENTS.md +13 -5
  2. package/actions/promote-buildchain-ref/README.md +11 -6
  3. package/actions/release-tail/README.md +8 -0
  4. package/architecture/ci-lane-change-budget.json +187 -0
  5. package/architecture/decisions/0002-next-development-transition.md +101 -0
  6. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  7. package/architecture/internal-capabilities.json +219 -7
  8. package/architecture/maintainability-policy.json +246 -29
  9. package/architecture/release-tail-contract-inventory.json +5 -2
  10. package/architecture/v3-core-mechanism-inventory.json +91 -0
  11. package/architecture/v4-adopter-delivery-parity.json +32 -5
  12. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  13. package/architecture/v4-compatibility-facts-parity.json +77 -0
  14. package/architecture/v4-delivery-authority-parity.json +250 -0
  15. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  16. package/architecture/v4-floating-consumer-policy.json +81 -0
  17. package/architecture/v4-floating-consumer-policy.md +32 -0
  18. package/architecture/v4-next-development-parity.json +192 -0
  19. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  20. package/architecture/v4-release-train-parity.json +1 -0
  21. package/architecture/v4-runtime-ref-resume-authority.json +64 -0
  22. package/architecture/v4-stage-capsule-qualification.json +2 -2
  23. package/architecture/v4-tail-reseal-parity.json +150 -0
  24. package/bin/buildchain.mjs +32 -3
  25. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  26. package/bin/internal/command-registry.mjs +3 -0
  27. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  28. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  29. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  30. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  31. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  32. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  33. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  34. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  35. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  36. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  37. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  38. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  39. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +59 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  43. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  44. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  45. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  46. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  47. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  48. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  49. package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
  50. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  51. package/contracts/next-development-request-v1.schema.json +66 -0
  52. package/contracts/next-development-transition-v1.schema.json +292 -0
  53. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  54. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  55. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +107 -0
  56. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  57. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
  58. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  59. package/dist/site/agent-index.json +5 -0
  60. package/dist/site/artifact-schemas.json +10 -0
  61. package/dist/site/buildchain-contract.json +1562 -44
  62. package/dist/site/buildchain-site.json +684 -42
  63. package/dist/site/capability-registry.json +17 -14
  64. package/dist/site/cli-registry.json +205 -3
  65. package/dist/site/controller-registry.json +62 -6
  66. package/dist/site/kfd-claims.json +551 -22
  67. package/dist/site/kfd-upstream-aggregate.json +1 -1
  68. package/dist/site/manual-registry.json +55 -9
  69. package/dist/site/node-api-registry.json +15247 -8690
  70. package/dist/site/page-registry.json +647 -29
  71. package/dist/site/public-surface-audit.json +876 -55
  72. package/dist/site/publication-authority-registry.json +44 -8
  73. package/dist/site/publication-registry.json +4 -4
  74. package/dist/site/release-provenance.json +26 -0
  75. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  76. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  77. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  78. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  79. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  80. package/dist/site/site-manifest.json +37 -13
  81. package/dist/site/workflow-registry.json +200 -22
  82. package/docs/MAP.md +10 -2
  83. package/docs/cli-reference.md +404 -13
  84. package/docs/cli.md +8 -0
  85. package/docs/dev-alpha-candidate-patrol.md +8 -0
  86. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  87. package/docs/dev-delivery-warrant.md +361 -30
  88. package/docs/lifecycle-protocol.md +41 -0
  89. package/docs/next-development-transition.md +118 -0
  90. package/docs/node-api-reference.md +748 -322
  91. package/docs/release-tail-provider-plane.md +30 -0
  92. package/docs/reusable-build-surface.md +41 -0
  93. package/docs/runtime-train-validation.md +10 -0
  94. package/docs/v4-adopter-delivery.md +133 -0
  95. package/docs/v4-compatibility-facts.md +91 -0
  96. package/docs/v4-publication-rehearsal.md +130 -0
  97. package/docs/v4-runtime-ref-resume-authority.md +69 -0
  98. package/docs/v4-tail-reseal.md +73 -0
  99. package/docs/versioning.md +4 -2
  100. package/package.json +36 -6
  101. package/packages/core/adopter-delivery-passport.js +259 -0
  102. package/packages/core/adopter-delivery-vectors.js +158 -0
  103. package/packages/core/adopter-delivery-vectors.json +77 -0
  104. package/packages/core/artifact-signing.js +61 -0
  105. package/packages/core/buildchain-agent-manuals.js +3 -0
  106. package/packages/core/buildchain-compatibility-authority.js +336 -0
  107. package/packages/core/buildchain-compatibility-fact.js +394 -0
  108. package/packages/core/buildchain-compatibility-facts.json +217 -0
  109. package/packages/core/buildchain-compatibility-proof.js +566 -0
  110. package/packages/core/buildchain-config.js +150 -1
  111. package/packages/core/buildchain-contract.js +96 -143
  112. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  113. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  114. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  115. package/packages/core/buildchain-publication-authority.js +1 -0
  116. package/packages/core/channel-candidate.js +8 -0
  117. package/packages/core/channel-promotion-baseline.js +55 -0
  118. package/packages/core/ci-lane-change-budget.js +247 -0
  119. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  120. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  121. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  122. package/packages/core/dev-delivery-authority-landing.js +461 -0
  123. package/packages/core/dev-delivery-authority-observation.js +48 -0
  124. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  125. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  126. package/packages/core/dev-delivery-authority-state.js +583 -0
  127. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  128. package/packages/core/dev-delivery-contract-surface.js +76 -0
  129. package/packages/core/dev-delivery-execution-failure.js +133 -0
  130. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  131. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  132. package/packages/core/dev-delivery-landing-readback.js +598 -0
  133. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  134. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  135. package/packages/core/dev-delivery-native-execution.js +110 -0
  136. package/packages/core/dev-delivery-native-proof.js +562 -0
  137. package/packages/core/dev-delivery-process-boundary.js +563 -0
  138. package/packages/core/dev-delivery-proof.js +37 -3
  139. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  140. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  141. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  142. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  143. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  144. package/packages/core/dev-delivery-warrant-state.js +587 -0
  145. package/packages/core/dev-delivery-warrant.js +328 -370
  146. package/packages/core/github-governance-authority.js +1 -0
  147. package/packages/core/index.js +7 -0
  148. package/packages/core/kungfu-temporal-fact.js +557 -0
  149. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  150. package/packages/core/next-development-candidate-reservation.js +186 -0
  151. package/packages/core/next-development-controller.js +728 -0
  152. package/packages/core/next-development-projection.js +287 -0
  153. package/packages/core/next-development-transition.js +738 -0
  154. package/packages/core/paper-agent-entry.js +11 -5
  155. package/packages/core/paper.js +16 -5
  156. package/packages/core/publication-authority.js +5 -5
  157. package/packages/core/published-delivery-authority.js +266 -0
  158. package/packages/core/release-candidate-recovery.js +66 -11
  159. package/packages/core/release-candidate.js +79 -19
  160. package/packages/core/release-passport.js +271 -38
  161. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  162. package/packages/core/v4-adopter-delivery.js +359 -0
  163. package/packages/core/v4-canonical-contracts.js +5 -0
  164. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  165. package/packages/core/v4-floating-consumer-evidence.js +338 -0
  166. package/packages/core/v4-floating-consumer-policy.js +569 -0
  167. package/packages/core/v4-floating-consumer-release-passport.js +153 -0
  168. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  169. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  170. package/packages/core/v4-publication-rehearsal.js +481 -0
  171. package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
  172. package/packages/core/v4-runtime-selector-persistence.js +228 -0
  173. package/packages/core/v4-tail-reseal-contract.js +26 -0
  174. package/packages/core/v4-tail-reseal-github.js +178 -0
  175. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  176. package/packages/core/v4-tail-reseal.js +594 -0
  177. package/packages/core/workflow-yaml-contract.js +72 -2
  178. package/scripts/audit-publication-control-plane.mjs +31 -31
  179. package/scripts/buildchain-cli-help.mjs +41 -2
  180. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  181. package/scripts/check-inventory.mjs +14 -3
  182. package/scripts/check-v4-floating-consumer-policy-contract.mjs +217 -0
  183. package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
  184. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  185. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  186. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  187. package/scripts/dev-delivery-authority.mjs +490 -0
  188. package/scripts/dev-delivery-native-run.mjs +177 -0
  189. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  190. package/scripts/dev-delivery-proof.mjs +67 -2
  191. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  192. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  193. package/scripts/dev-delivery-source-proof-reuse.mjs +547 -0
  194. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  195. package/scripts/dev-delivery-two-phase.mjs +573 -0
  196. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  197. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  198. package/scripts/dev-delivery-warrant.mjs +232 -194
  199. package/scripts/dev-pr-auto-merge.mjs +37 -48
  200. package/scripts/dev-pr-delivery-warrant.mjs +216 -2
  201. package/scripts/dev-pr-prequeue-guard.mjs +399 -0
  202. package/scripts/dev-qualification-patrol.mjs +9 -9
  203. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  204. package/scripts/ensure-github-release.mjs +3 -3
  205. package/scripts/generate-channel-build-workflow.mjs +3 -0
  206. package/scripts/generate-channel-promotion-workflow.mjs +114 -14
  207. package/scripts/generate-next-development-guidance.mjs +49 -0
  208. package/scripts/generate-release-candidate-passport.mjs +192 -32
  209. package/scripts/generate-site-bundle.mjs +69 -4
  210. package/scripts/init-repo.mjs +26 -4
  211. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  212. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  213. package/scripts/next-development-self-dogfood.mjs +597 -0
  214. package/scripts/next-development-transition.mjs +47 -0
  215. package/scripts/npm-publish-transaction.mjs +103 -91
  216. package/scripts/promotion-channel-router.mjs +4 -4
  217. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  218. package/scripts/release-tail.mjs +51 -3
  219. package/scripts/release-train-self-dogfood.mjs +94 -92
  220. package/scripts/resume-from-candidate-run.mjs +565 -41
  221. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  222. package/scripts/site-capability-metadata.mjs +43 -0
  223. package/scripts/stable-candidate-qualification.mjs +9 -10
  224. package/scripts/v4-consumer-policy.mjs +231 -0
  225. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  226. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  227. package/scripts/v4-tail-reseal.mjs +416 -0
  228. package/templates/native-dev-delivery.yml +143 -0
@@ -3,17 +3,19 @@ import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { execFileSync } from "node:child_process";
6
7
  import { pathToFileURL } from "node:url";
7
8
 
8
9
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
10
+ import { normalizeAnchorProvenance, normalizeCandidateRun, recoverCandidateProvenance } from "./release-candidate-anchor-provenance.mjs";
11
+ export { resolveAnchorRecoveryRequest } from "./release-candidate-anchor-provenance.mjs";
9
12
  import {
10
13
  generatePublishRequiredArtifacts,
11
- readNpmPackageArtifact,
12
- selectReleaseCandidateArtifacts,
13
- } from "./release-candidate-resolver.mjs";
14
- import {
15
14
  githubDownload,
16
15
  githubJson,
16
+ readNpmPackageArtifact,
17
+ selectPayloadArtifacts,
18
+ selectReleaseCandidateArtifacts,
17
19
  unzip,
18
20
  verifyArtifactArchive,
19
21
  } from "./release-candidate-resolver.mjs";
@@ -28,6 +30,15 @@ import {
28
30
  } from "../packages/core/publication-artifact-candidate.js";
29
31
  import { createPublicationSealedBundle } from "../packages/core/publication-sealed-bundle.js";
30
32
  import { releaseTransactionStateRef } from "../packages/core/publish-transaction.js";
33
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
34
+ import { validateV4StageCapsule } from "../packages/core/v4-stage-capsule.js";
35
+ import {
36
+ authorizeV4RuntimeSelection,
37
+ createV4RuntimeResumeLineage,
38
+ scanV4RuntimeSelectorPersistence,
39
+ v4RuntimeResumeDocumentRoot,
40
+ verifyV4RuntimeAuthorizationReceipt,
41
+ } from "../packages/core/v4-runtime-ref-resume-authority.js";
31
42
 
32
43
  function env(name, fallback = "") {
33
44
  return process.env[name] || fallback;
@@ -100,8 +111,373 @@ function readOnlyJson(files, label) {
100
111
  return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
101
112
  }
102
113
 
103
- async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
104
- const stateRef = releaseTransactionStateRef(version);
114
+ function trackedRuntimePersistenceScan() {
115
+ const paths = execFileSync(
116
+ "git",
117
+ ["ls-files", ".github/workflows", ".github/actions", ".buildchain"],
118
+ { encoding: "utf8" },
119
+ )
120
+ .split(/\r?\n/)
121
+ .filter((entry) => /\.(?:json|toml|ya?ml)$/u.test(entry));
122
+ return scanV4RuntimeSelectorPersistence({ root: process.cwd(), paths });
123
+ }
124
+
125
+ export function verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads }) {
126
+ if (
127
+ !sidecar ||
128
+ sidecar.contract !==
129
+ "kungfu-buildchain-v4-release-candidate-stage-capsules/v1" ||
130
+ sidecar.status !== "sealed"
131
+ ) {
132
+ throw new Error(
133
+ "cross-runtime recovery requires original sealed release-candidate Stage Capsules",
134
+ );
135
+ }
136
+ const payload = { ...sidecar };
137
+ delete payload.root;
138
+ const expectedAttempt =
139
+ `github-run:${passport.workflow.runId}:attempt:${passport.workflow.runAttempt}`;
140
+ if (
141
+ sidecar.root !== v4RuntimeResumeDocumentRoot(payload) ||
142
+ sidecar.repository !== passport.repository ||
143
+ sidecar.source?.sha !== passport.source.headSha ||
144
+ sidecar.source?.treeSha !== passport.source.treeHash ||
145
+ sidecar.buildAttempt?.id !== expectedAttempt ||
146
+ sidecar.buildAttempt?.runtimeSha !== passport.buildchain.sha ||
147
+ sidecar.consumerPolicyReceiptRoot !== passport.consumerPolicy?.receiptRoot
148
+ ) {
149
+ throw new Error("release-candidate Stage Capsule sidecar identity mismatch");
150
+ }
151
+ const artifactByName = new Map(downloads.map((entry) => [entry.artifact.name, entry]));
152
+ const required = [...passport.platformMatrix.map((entry) => entry.platformId)].sort();
153
+ const entries = [...(sidecar.capsules || [])].sort((left, right) =>
154
+ left.platform.localeCompare(right.platform),
155
+ );
156
+ if (
157
+ entries.length !== required.length ||
158
+ entries.some((entry, index) => entry.platform !== required[index])
159
+ ) {
160
+ throw new Error("release-candidate Stage Capsule platform set is incomplete");
161
+ }
162
+ return entries.map((entry) => {
163
+ validateV4StageCapsule(entry.capsule);
164
+ const download = artifactByName.get(entry.artifactName);
165
+ const artifact = entry.artifact;
166
+ if (
167
+ !download ||
168
+ artifact?.platformId !== entry.platform ||
169
+ artifact?.id !== String(download.artifact.id) ||
170
+ artifact?.name !== download.artifact.name ||
171
+ artifact?.digest !== entry.artifactDigest ||
172
+ artifact?.digest !== download.artifact.digest ||
173
+ artifact?.digest !== download.record.digest ||
174
+ artifact?.digest !== download.record.downloadedDigest ||
175
+ artifact?.expiresAt !==
176
+ new Date(download.artifact.expires_at).toISOString() ||
177
+ entry.capsule.identity.policyRoot !== sidecar.consumerPolicyReceiptRoot ||
178
+ entry.capsule.identity.sourceRoot !==
179
+ v4ContentRoot("candidate-identity", passport.source) ||
180
+ entry.capsule.identity.runtimeRoot !==
181
+ v4ContentRoot("candidate-identity", { sha: passport.buildchain.sha })
182
+ ) {
183
+ throw new Error(
184
+ `Stage Capsule ${entry.platform} does not bind the verified provider artifact`,
185
+ );
186
+ }
187
+ return {
188
+ platform: entry.platform,
189
+ capsuleRoot: entry.capsule.capsuleRoot,
190
+ identityRoot: entry.capsule.identityRoot,
191
+ artifactDigest: entry.artifactDigest,
192
+ sourceSha: sidecar.source.sha,
193
+ sourceTreeSha: sidecar.source.treeSha,
194
+ policyRoot: sidecar.consumerPolicyReceiptRoot,
195
+ buildRuntimeSha: sidecar.buildAttempt.runtimeSha,
196
+ sealed: true,
197
+ };
198
+ });
199
+ }
200
+
201
+ export function validateV4RuntimeResumePublicReadback({
202
+ targetRef,
203
+ targetSha,
204
+ alphaSha,
205
+ exactTagSha,
206
+ tagLineage,
207
+ runtimeSha,
208
+ version,
209
+ transaction,
210
+ main,
211
+ npm,
212
+ }) {
213
+ if (
214
+ transaction?.target_ref !== targetRef ||
215
+ transaction?.version !== version ||
216
+ exactTagSha !== transaction?.source_sha ||
217
+ !["ahead", "identical"].includes(tagLineage?.status) ||
218
+ alphaSha !== runtimeSha ||
219
+ !targetSha ||
220
+ npm["dist-tags"]?.alpha !== version ||
221
+ npm.versions?.[version]?.dist?.integrity !== main.digest
222
+ ) {
223
+ throw new Error(
224
+ "cross-runtime final public readback does not match durable publication bytes and runtime",
225
+ );
226
+ }
227
+ }
228
+
229
+ export async function readPublicResumeState({
230
+ repoInfo,
231
+ targetRef,
232
+ runtimeSha,
233
+ version,
234
+ transaction,
235
+ token,
236
+ apiUrl,
237
+ fetchImpl,
238
+ }) {
239
+ const readRef = async (ref) =>
240
+ (await githubJson({
241
+ apiUrl,
242
+ token,
243
+ fetchImpl,
244
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/ref/${ref}`,
245
+ })).object?.sha || "";
246
+ const main = transaction?.artifacts?.find((entry) => entry.kind === "npm" && entry.required !== false);
247
+ if (!main || main.ref !== version || !main.digest) {
248
+ throw new Error(
249
+ "cross-runtime recovery requires exact durable npm publication evidence",
250
+ );
251
+ }
252
+ const packageUrl =
253
+ `https://registry.npmjs.org/${encodeURIComponent(main.name)}`;
254
+ const exactTag = transaction.exact_tag || `v${version}`;
255
+ const [targetSha, alphaSha, exactTagSha, npmResponse] = await Promise.all([
256
+ readRef(`heads/${targetRef}`),
257
+ readRef("tags/v4-alpha"),
258
+ readRef(`tags/${exactTag}`),
259
+ fetchImpl(packageUrl),
260
+ ]);
261
+ if (!npmResponse.ok) {
262
+ throw new Error(`npm public readback failed with HTTP ${npmResponse.status}`);
263
+ }
264
+ const npm = await npmResponse.json();
265
+ const tagLineage = await githubJson({
266
+ apiUrl,
267
+ token,
268
+ fetchImpl,
269
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${exactTagSha}...${targetSha}`,
270
+ });
271
+ validateV4RuntimeResumePublicReadback({
272
+ targetRef,
273
+ targetSha,
274
+ alphaSha,
275
+ exactTagSha,
276
+ tagLineage,
277
+ runtimeSha,
278
+ version,
279
+ transaction,
280
+ main,
281
+ npm,
282
+ });
283
+ const body = {
284
+ schemaVersion: 1,
285
+ contract: "kungfu-buildchain-v4-runtime-resume-public-readback/v1",
286
+ observedAt: new Date().toISOString(),
287
+ repository: repoInfo.fullName,
288
+ version,
289
+ refs: {
290
+ target: { ref: targetRef, sha: targetSha },
291
+ floating: { ref: "v4-alpha", sha: alphaSha },
292
+ exactTag: { ref: exactTag, sha: exactTagSha },
293
+ },
294
+ npm: {
295
+ package: main.name,
296
+ version,
297
+ distTag: "alpha",
298
+ integrity: npm.versions[version].dist.integrity,
299
+ },
300
+ };
301
+ return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
302
+ }
303
+
304
+ function prepareRuntimeResumeEvidence({
305
+ repoInfo,
306
+ targetRef,
307
+ runtimeSha,
308
+ version,
309
+ passport,
310
+ sidecar,
311
+ stageCapsules,
312
+ recovery,
313
+ outputDir,
314
+ }) {
315
+ const authorizationPath = path.resolve(
316
+ ".buildchain/release-candidate/v4-runtime-authorization.json",
317
+ );
318
+ if (!fs.existsSync(authorizationPath)) {
319
+ throw new Error(
320
+ "cross-runtime recovery requires a fresh runtime authorization receipt",
321
+ );
322
+ }
323
+ const delegated = readOnlyJson([{ absolutePath: authorizationPath }], "runtime authorization");
324
+ const delegatedVerification = verifyV4RuntimeAuthorizationReceipt({
325
+ receipt: delegated.receipt,
326
+ receiptRoot: delegated.receiptRoot,
327
+ repository: repoInfo.fullName,
328
+ runtimeSha,
329
+ });
330
+ if (!delegatedVerification.ok) {
331
+ throw new Error(
332
+ `fresh recovery runtime authorization rejected: ${delegatedVerification.failures.join(", ")}`,
333
+ );
334
+ }
335
+ const policy = passport.consumerPolicy.receipt;
336
+ const authorization = authorizeV4RuntimeSelection({
337
+ repository: repoInfo.fullName,
338
+ eventName: "workflow_dispatch",
339
+ mode: "resume",
340
+ actor: delegated.receipt.actor.login,
341
+ actorPermission: delegated.receipt.actor.permission,
342
+ reason:
343
+ `resume sealed candidate run ${passport.workflow.runId} from a tree-equivalent protected source`,
344
+ authorizedAt: new Date().toISOString(),
345
+ sourceSha: sidecar.source.sha,
346
+ sourceTreeSha: sidecar.source.treeSha,
347
+ requestedRef: delegated.receipt.request.ref,
348
+ resolvedRuntimeSha: runtimeSha,
349
+ approvedRefReadbacks: delegated.receipt.runtime.reachableFrom,
350
+ stableContractLockRoot: policy.contractLocks.stable.root,
351
+ alphaContractLockRoot: policy.contractLocks.alpha.root,
352
+ consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
353
+ persistenceScan: trackedRuntimePersistenceScan(),
354
+ });
355
+ const planBody = {
356
+ schemaVersion: 1,
357
+ contract: "kungfu-buildchain-v4-runtime-resume-plan/v1",
358
+ originalRunId: String(passport.workflow.runId),
359
+ resumeRunId: String(env("GITHUB_RUN_ID")),
360
+ recoveryReceiptRoot: recovery.receipt.root,
361
+ requiredPlatforms: stageCapsules.map((entry) => entry.platform),
362
+ reusedCapsuleRoots: stageCapsules.map((entry) => entry.capsuleRoot),
363
+ rebuildPlatforms: [],
364
+ skippedBuildStages: recovery.receipt.skippedBuildStages,
365
+ };
366
+ const plan = { ...planBody, root: v4RuntimeResumeDocumentRoot(planBody) };
367
+ const materialBody = {
368
+ schemaVersion: 1,
369
+ contract: "kungfu-buildchain-v4-runtime-resume-material/v1",
370
+ repository: repoInfo.fullName,
371
+ targetRef,
372
+ runtimeSha,
373
+ version,
374
+ authorization: authorization.receipt,
375
+ authorizationRoot: authorization.receiptRoot,
376
+ buildAttempt: sidecar.buildAttempt,
377
+ resumeAttempt: {
378
+ id: `github-run:${env("GITHUB_RUN_ID")}:attempt:${env("GITHUB_RUN_ATTEMPT", "1")}`,
379
+ runtimeSha,
380
+ },
381
+ source: sidecar.source,
382
+ consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
383
+ requiredPlatforms: stageCapsules.map((entry) => entry.platform),
384
+ stageCapsules,
385
+ resumePlanRoot: plan.root,
386
+ floatingRefBefore: {
387
+ ref: "v4-alpha",
388
+ sha: sidecar.buildAttempt.runtimeSha,
389
+ },
390
+ };
391
+ const material = {
392
+ ...materialBody,
393
+ root: v4RuntimeResumeDocumentRoot(materialBody),
394
+ };
395
+ for (const [name, value] of [
396
+ ["v4-runtime-resume-plan.json", plan],
397
+ ["v4-runtime-resume-material.json", material],
398
+ ]) {
399
+ fs.writeFileSync(
400
+ path.join(outputDir, name),
401
+ `${JSON.stringify(value, null, 2)}\n`,
402
+ );
403
+ }
404
+ return path.join(outputDir, "v4-runtime-resume-evidence.json");
405
+ }
406
+
407
+ export async function finalizeRuntimeResumeEvidence({
408
+ materialPath,
409
+ transaction,
410
+ token = env("GITHUB_TOKEN", env("INPUT_TOKEN")),
411
+ apiUrl = env("GITHUB_API_URL", "https://api.github.com"),
412
+ fetchImpl = globalThis.fetch,
413
+ outputDir = path.dirname(materialPath),
414
+ }) {
415
+ const material = JSON.parse(fs.readFileSync(materialPath, "utf8"));
416
+ const body = { ...material };
417
+ delete body.root;
418
+ if (
419
+ material.contract !== "kungfu-buildchain-v4-runtime-resume-material/v1" ||
420
+ material.root !== v4RuntimeResumeDocumentRoot(body)
421
+ ) {
422
+ throw new Error("cross-runtime resume material root mismatch");
423
+ }
424
+ const repoInfo = splitRepository(material.repository);
425
+ const version = env("BUILDCHAIN_RELEASE_VERSION", material.version);
426
+ const targetRef = env("BUILDCHAIN_RELEASE_TARGET_REF", material.targetRef);
427
+ if (
428
+ version !== material.version ||
429
+ targetRef !== material.targetRef ||
430
+ transaction?.version !== version ||
431
+ transaction?.state !== "complete"
432
+ ) {
433
+ throw new Error(
434
+ "cross-runtime resume finalization does not match the completed publication transaction",
435
+ );
436
+ }
437
+ const readback = await readPublicResumeState({
438
+ repoInfo,
439
+ targetRef,
440
+ runtimeSha: material.runtimeSha,
441
+ version,
442
+ transaction,
443
+ token,
444
+ apiUrl,
445
+ fetchImpl,
446
+ });
447
+ const resumed = createV4RuntimeResumeLineage({
448
+ authorization: material.authorization,
449
+ authorizationRoot: material.authorizationRoot,
450
+ buildAttempt: material.buildAttempt,
451
+ resumeAttempt: material.resumeAttempt,
452
+ source: material.source,
453
+ consumerPolicyReceiptRoot: material.consumerPolicyReceiptRoot,
454
+ requiredPlatforms: material.requiredPlatforms,
455
+ stageCapsules: material.stageCapsules,
456
+ resumePlanRoot: material.resumePlanRoot,
457
+ finalPublicReadbackRoot: readback.root,
458
+ floatingRefBefore: material.floatingRefBefore,
459
+ floatingRefAfter: { ref: "v4-alpha", sha: material.runtimeSha },
460
+ });
461
+ const evidence = {
462
+ authorization: material.authorization,
463
+ authorizationRoot: material.authorizationRoot,
464
+ lineage: resumed.lineage,
465
+ lineageRoot: resumed.lineageRoot,
466
+ };
467
+ const evidencePath = path.join(outputDir, "v4-runtime-resume-evidence.json");
468
+ for (const [name, value] of [
469
+ ["v4-runtime-resume-public-readback.json", readback],
470
+ [path.basename(evidencePath), evidence],
471
+ ]) {
472
+ fs.writeFileSync(
473
+ path.join(outputDir, name),
474
+ `${JSON.stringify(value, null, 2)}\n`,
475
+ );
476
+ }
477
+ return { evidence, path: evidencePath, readback };
478
+ }
479
+
480
+ async function readTransactionAtRef({ repoInfo, apiUrl, token, fetchImpl, stateRef }) {
105
481
  const response = await githubJson({
106
482
  apiUrl,
107
483
  token,
@@ -110,10 +486,65 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
110
486
  path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
111
487
  });
112
488
  if (!response) return undefined;
113
- if (response.type !== "file" || response.encoding !== "base64" || !response.content) {
114
- throw new Error(`durable transaction ${stateRef} did not expose a base64 state.json file`);
489
+ if (
490
+ response.type !== "file" ||
491
+ response.encoding !== "base64" ||
492
+ !response.content
493
+ ) {
494
+ throw new Error(
495
+ `durable transaction ${stateRef} did not expose a base64 state.json file`,
496
+ );
115
497
  }
116
- return JSON.parse(Buffer.from(String(response.content).replace(/\s/g, ""), "base64").toString("utf8"));
498
+ return JSON.parse(
499
+ Buffer.from(
500
+ String(response.content).replace(/\s/g, ""),
501
+ "base64",
502
+ ).toString("utf8"),
503
+ );
504
+ }
505
+
506
+ async function readExistingTransaction({
507
+ repoInfo,
508
+ apiUrl,
509
+ token,
510
+ fetchImpl,
511
+ version,
512
+ }) {
513
+ return readTransactionAtRef({
514
+ repoInfo,
515
+ apiUrl,
516
+ token,
517
+ fetchImpl,
518
+ stateRef: releaseTransactionStateRef(version),
519
+ });
520
+ }
521
+
522
+ async function readExistingTransactionById({
523
+ repoInfo,
524
+ apiUrl,
525
+ token,
526
+ fetchImpl,
527
+ transactionId,
528
+ }) {
529
+ if (!transactionId) return undefined;
530
+ const statePrefix = "buildchain/release-state/";
531
+ const refs = await githubJson({
532
+ apiUrl,
533
+ token,
534
+ fetchImpl,
535
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/matching-refs/heads/${statePrefix}`,
536
+ });
537
+ for (const ref of refs) {
538
+ const transaction = await readTransactionAtRef({
539
+ repoInfo,
540
+ apiUrl,
541
+ token,
542
+ fetchImpl,
543
+ stateRef: String(ref.ref).replace(/^refs\/heads\//, ""),
544
+ });
545
+ if (transaction?.id === transactionId) return transaction;
546
+ }
547
+ throw new Error(`durable publication transaction ${transactionId} was not found`);
117
548
  }
118
549
 
119
550
  function outputPath(filePath) {
@@ -156,7 +587,7 @@ async function downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir,
156
587
  };
157
588
  }
158
589
 
159
- function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
590
+ export function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
160
591
  const names = new Set([selected.passport.name, selected.summary.name]);
161
592
  for (const reference of passport.controllerReceipts || []) {
162
593
  if (!reference.artifact) throw new Error(`Passport controller receipt ${reference.controllerId} has no artifact identity`);
@@ -171,11 +602,9 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
171
602
  for (const artifact of artifacts) {
172
603
  if (manifestPattern.test(String(artifact.name || ""))) names.add(artifact.name);
173
604
  }
174
- const matchers = splitPatterns(artifactPatterns).map(patternMatcher);
175
- for (const artifact of artifacts) {
176
- if (matchers.some((matcher) => matcher.test(String(artifact.name || "")))) names.add(artifact.name);
177
- }
178
- return names;
605
+ const publicationNames = new Set(selectPayloadArtifacts({ artifacts, artifactName: selected.prefix, sourceSha: selected.sourceSha, patterns: artifactPatterns }).map((artifact) => artifact.name));
606
+ for (const name of publicationNames) names.add(name);
607
+ return { names, publicationNames };
179
608
  }
180
609
 
181
610
  export function normalizePlatformManifests(downloads, passport) {
@@ -352,7 +781,15 @@ async function recoverCandidateEvidence({
352
781
  for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
353
782
  const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
354
783
  const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
355
- const requiredNames = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
784
+ const sidecarFiles = initialDownloads[0].files.filter(
785
+ (file) =>
786
+ path.basename(file.path) === "release-candidate-stage-capsules.json",
787
+ );
788
+ const stageCapsuleSidecar =
789
+ sidecarFiles.length === 1
790
+ ? readOnlyJson(sidecarFiles, "release-candidate-stage-capsules.json")
791
+ : undefined;
792
+ const { names: requiredNames, publicationNames } = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
356
793
  const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
357
794
  if (chosen.length !== requiredNames.size) {
358
795
  const found = new Set(chosen.map((artifact) => artifact.name));
@@ -363,7 +800,7 @@ async function recoverCandidateEvidence({
363
800
  for (const artifact of chosen.filter((entry) => ![selected.passport.id, selected.summary.id].includes(entry.id))) downloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
364
801
  return {
365
802
  run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
366
- passport, buildSummary, chosen, downloads,
803
+ passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
367
804
  };
368
805
  }
369
806
 
@@ -373,6 +810,52 @@ async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionI
373
810
  return { status: comparison.status, mergeIsAncestor: ["ahead", "identical"].includes(comparison.status) };
374
811
  }
375
812
 
813
+ async function resolveRecoveryTransaction({
814
+ repoInfo,
815
+ apiUrl,
816
+ token,
817
+ fetchImpl,
818
+ transactionId,
819
+ publicationVersion,
820
+ }) {
821
+ const byId = await readExistingTransactionById({
822
+ repoInfo,
823
+ apiUrl,
824
+ token,
825
+ fetchImpl,
826
+ transactionId,
827
+ });
828
+ const version = byId?.version || publicationVersion;
829
+ const transaction = byId || await readExistingTransaction({
830
+ repoInfo,
831
+ apiUrl,
832
+ token,
833
+ fetchImpl,
834
+ version,
835
+ });
836
+ return { version, transaction };
837
+ }
838
+
839
+ function resolveRecoveredStageCapsules({
840
+ candidateRuntimeSha,
841
+ runtimeSha,
842
+ sidecar,
843
+ passport,
844
+ downloads,
845
+ }) {
846
+ if (candidateRuntimeSha === runtimeSha) return [];
847
+ return verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads });
848
+ }
849
+
850
+ function validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot) {
851
+ const runId = String(candidateRunId || "").trim();
852
+ if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
853
+ if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
854
+ throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
855
+ }
856
+ return runId;
857
+ }
858
+
376
859
  export async function resumeFromCandidateRun({
377
860
  repository, targetRepository = repository, candidateRunId,
378
861
  expectedWorkflowFile, expectedWorkflowName,
@@ -387,21 +870,23 @@ export async function resumeFromCandidateRun({
387
870
  recoveryRunId = env("GITHUB_RUN_ID"), fetchImpl = globalThis.fetch,
388
871
  } = {}) {
389
872
  const repoInfo = splitRepository(repository);
390
- const runId = String(candidateRunId || "").trim();
391
- if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
392
- if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
393
- throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
394
- }
873
+ const runId = validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot);
395
874
  const archiveDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-resume-"));
396
875
  try {
397
876
  const {
398
877
  run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
399
- passport, buildSummary, chosen, downloads,
878
+ passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
400
879
  } = await recoverCandidateEvidence({
401
880
  repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
402
881
  outputDir, apiUrl, token, fetchImpl, archiveDir,
403
882
  });
404
- const prNumber = Number(passport.pullRequest?.number || 0);
883
+ const { anchorProvenance, provenancePassport } = await recoverCandidateProvenance({
884
+ passport, buildSummary, transactionId, repoInfo, artifactName,
885
+ expectedWorkflowFile, expectedWorkflowName, channel, apiUrl, token,
886
+ fetchImpl, archiveDir, bundleRoot, githubJson, selectReleaseCandidateArtifacts,
887
+ downloadArtifact, readOnlyJson,
888
+ });
889
+ const prNumber = Number(provenancePassport.pullRequest?.number || 0);
405
890
  if (!prNumber) throw new Error("Release Candidate Passport has no PR identity");
406
891
  const pullRequest = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/pulls/${prNumber}` });
407
892
  const targetCommit = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/commits/${targetSha}` });
@@ -412,7 +897,7 @@ export async function resumeFromCandidateRun({
412
897
  const controllerReceipts = normalizeControllerReceipts(downloads, passport);
413
898
  const productPayloadManifests = normalizeProductPayloadManifests(downloads);
414
899
  const publication = createRecoveredPublication({
415
- downloads,
900
+ downloads: downloads.filter(({ artifact }) => publicationNames.has(artifact.name)),
416
901
  bundleRoot,
417
902
  repository: repoInfo.fullName,
418
903
  passport,
@@ -422,8 +907,24 @@ export async function resumeFromCandidateRun({
422
907
  releasePatterns,
423
908
  platformManifests: platformManifestEvidence.manifests,
424
909
  });
425
- const candidateVersion = publication.version;
426
- const existingTransaction = await readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version: candidateVersion });
910
+ const stageCapsules = resolveRecoveredStageCapsules({
911
+ candidateRuntimeSha,
912
+ runtimeSha,
913
+ sidecar: stageCapsuleSidecar,
914
+ passport,
915
+ downloads,
916
+ });
917
+ const {
918
+ version: candidateVersion,
919
+ transaction: existingTransaction,
920
+ } = await resolveRecoveryTransaction({
921
+ repoInfo,
922
+ apiUrl,
923
+ token,
924
+ fetchImpl,
925
+ transactionId,
926
+ publicationVersion: publication.version,
927
+ });
427
928
  const targetAdvance = await resolveTargetAdvance({
428
929
  observedTargetSha, targetSha, transactionId, existingTransaction,
429
930
  repoInfo, apiUrl, token, fetchImpl,
@@ -451,20 +952,9 @@ export async function resumeFromCandidateRun({
451
952
  expectedRuntimeSha: candidateRuntimeSha,
452
953
  expectedTransactionId: transactionId,
453
954
  existingTransaction,
454
- run: {
455
- id: String(run.id),
456
- repository: repoInfo.fullName,
457
- headRepository: run.head_repository?.full_name || "",
458
- status: run.status,
459
- conclusion: run.conclusion,
460
- event: run.event,
461
- path: run.path,
462
- name: run.name,
463
- headSha: run.head_sha || "",
464
- headBranch: run.head_branch || "",
465
- pullRequestNumbers: (run.pull_requests || []).map((entry) => Number(entry.number)),
466
- },
955
+ run: normalizeCandidateRun(run, repoInfo.fullName),
467
956
  workflow: { path: workflow.path, name: workflow.name, state: workflow.state },
957
+ anchorProvenance: normalizeAnchorProvenance(anchorProvenance, repoInfo.fullName),
468
958
  pullRequest: {
469
959
  number: pullRequest.number,
470
960
  merged: pullRequest.merged === true,
@@ -494,6 +984,19 @@ export async function resumeFromCandidateRun({
494
984
  if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
495
985
  const publishRequiredArtifacts = publication.publishRequiredArtifacts;
496
986
  fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(publishRequiredArtifacts, null, 2)}\n`);
987
+ const runtimeResumeEvidencePath = stageCapsules.length
988
+ ? prepareRuntimeResumeEvidence({
989
+ repoInfo,
990
+ targetRef,
991
+ runtimeSha,
992
+ version: candidateVersion,
993
+ passport,
994
+ sidecar: stageCapsuleSidecar,
995
+ stageCapsules,
996
+ recovery,
997
+ outputDir: resolvedOutput,
998
+ })
999
+ : "";
497
1000
  const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
498
1001
  return {
499
1002
  enabled: true,
@@ -517,6 +1020,9 @@ export async function resumeFromCandidateRun({
517
1020
  sealedBundleRoot: publication.manifest ? outputPath(bundleRoot) : "",
518
1021
  sealedBundleManifest: publication.manifest ? outputPath(sealedManifestPath) : "",
519
1022
  recoveryReceipt: outputPath(recoveryReceiptPath),
1023
+ runtimeResumeEvidence: runtimeResumeEvidencePath
1024
+ ? outputPath(runtimeResumeEvidencePath)
1025
+ : "",
520
1026
  },
521
1027
  };
522
1028
  } finally {
@@ -568,6 +1074,10 @@ export async function resumeFromCandidateRunCli() {
568
1074
  "release-candidate-run-url": result.run.url,
569
1075
  "release-candidate-recovery-receipt-path": result.paths.recoveryReceipt,
570
1076
  "release-candidate-recovery-root": result.receipt.root,
1077
+ "v4-runtime-resume-evidence-path": result.paths.runtimeResumeEvidence,
1078
+ "v4-runtime-resume-finalize-command": result.paths.runtimeResumeEvidence
1079
+ ? "node .buildchain/runtime/promotion-shell/scripts/resume-from-candidate-run.mjs finalize"
1080
+ : "",
571
1081
  "release-candidate-root": result.candidateRoot,
572
1082
  "release-candidate-artifact-root": result.artifactRoot,
573
1083
  "publish-sealed-bundle-root": result.paths.sealedBundleRoot,
@@ -589,7 +1099,21 @@ export async function resumeFromCandidateRunCli() {
589
1099
  }
590
1100
 
591
1101
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
592
- resumeFromCandidateRunCli().catch((error) => {
1102
+ const operation = process.argv[2] === "finalize"
1103
+ ? finalizeRuntimeResumeEvidence({
1104
+ materialPath: requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL"),
1105
+ transaction: JSON.parse(
1106
+ requiredEnv("BUILDCHAIN_RELEASE_TRANSACTION_JSON"),
1107
+ ),
1108
+ outputDir: env(
1109
+ "BUILDCHAIN_RELEASE_PASSPORT_OUTPUT_DIR",
1110
+ path.dirname(requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL")),
1111
+ ),
1112
+ }).then((result) =>
1113
+ console.log(JSON.stringify({ files: [outputPath(result.path)] })),
1114
+ )
1115
+ : resumeFromCandidateRunCli();
1116
+ operation.catch((error) => {
593
1117
  const failure = error.recoveryFailure || recoveryFailure(error);
594
1118
  console.error(`candidate recovery rejected [${failure.code}]: ${failure.reason}`);
595
1119
  console.error(`next action: ${failure.nextAction}`);