@kungfu-tech/buildchain 4.0.1-alpha.9 → 4.0.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 (98) hide show
  1. package/README.md +9 -6
  2. package/actions/v4-release-candidate-promote/README.md +3 -0
  3. package/architecture/agent-change-map.md +1015 -0
  4. package/architecture/ci-lane-change-budget.json +70 -1
  5. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  6. package/architecture/internal-capabilities.json +156 -11
  7. package/architecture/maintainability-debt.json +3229 -0
  8. package/architecture/maintainability-policy.json +480 -129
  9. package/architecture/v3-core-mechanism-inventory.json +4 -3
  10. package/architecture/v3-v4-capability-closure.md +59 -0
  11. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  12. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  14. package/architecture/v4-runtime-semantic-closure.json +710 -0
  15. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  16. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  17. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  18. package/dist/site/agent-index.json +1 -0
  19. package/dist/site/artifact-schemas.json +2 -0
  20. package/dist/site/buildchain-contract.json +123 -32
  21. package/dist/site/buildchain-site.json +96 -28
  22. package/dist/site/capability-registry.json +6 -6
  23. package/dist/site/cli-registry.json +5 -2
  24. package/dist/site/controller-registry.json +41 -5
  25. package/dist/site/kfd-claims.json +170 -21
  26. package/dist/site/kfd-upstream-aggregate.json +1 -1
  27. package/dist/site/manual-registry.json +6 -6
  28. package/dist/site/node-api-registry.json +1474 -215
  29. package/dist/site/page-registry.json +85 -17
  30. package/dist/site/public-surface-audit.json +85 -20
  31. package/dist/site/publication-authority-registry.json +28 -1
  32. package/dist/site/publication-registry.json +4 -4
  33. package/dist/site/release-provenance.json +5 -0
  34. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  35. package/dist/site/site-manifest.json +10 -10
  36. package/dist/site/workflow-registry.json +94 -18
  37. package/docs/MAP.md +1 -1
  38. package/docs/cli-reference.md +4 -4
  39. package/docs/dev-delivery-warrant.md +12 -9
  40. package/docs/node-api-reference.md +291 -206
  41. package/docs/publish-transaction.md +11 -4
  42. package/docs/release-candidate.md +7 -0
  43. package/docs/v4-adopter-delivery.md +23 -4
  44. package/docs/v4-declarative-release-tail-migration.md +43 -0
  45. package/package.json +8 -3
  46. package/packages/core/adopter-delivery-passport.js +36 -0
  47. package/packages/core/buildchain-contract.js +31 -1
  48. package/packages/core/buildchain-publication-authority.js +1 -0
  49. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  50. package/packages/core/dev-delivery-warrant-state.js +15 -2
  51. package/packages/core/dev-delivery-warrant.js +21 -4
  52. package/packages/core/github-governance-authority.js +4 -4
  53. package/packages/core/index.js +1 -0
  54. package/packages/core/paper-scaffold-content.js +401 -0
  55. package/packages/core/paper.js +12 -378
  56. package/packages/core/publication-control-plane-audit.js +135 -111
  57. package/packages/core/publication-rehearsal-projection.js +104 -0
  58. package/packages/core/publication-rehearsal-runtime.js +190 -0
  59. package/packages/core/release-candidate-recovery.js +6 -5
  60. package/packages/core/release-passport.js +82 -184
  61. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  62. package/packages/core/v4-floating-consumer-policy.js +3 -2
  63. package/packages/core/v4-floating-consumer-release-passport.js +134 -15
  64. package/packages/core/v4-publication-qualification.js +222 -0
  65. package/scripts/artifact-signing-controller-core.mjs +1 -1
  66. package/scripts/audit-publication-control-plane.mjs +17 -17
  67. package/scripts/buildchain-cli-help.mjs +3 -0
  68. package/scripts/check-internal-architecture.mjs +23 -3
  69. package/scripts/check-inventory.mjs +23 -5
  70. package/scripts/check-maintainability.mjs +145 -4
  71. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  72. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  73. package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
  74. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  75. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  76. package/scripts/dev-delivery-warrant.mjs +45 -5
  77. package/scripts/dev-pr-auto-merge.mjs +25 -11
  78. package/scripts/dev-pr-delivery-warrant.mjs +44 -0
  79. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  80. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  81. package/scripts/generate-agent-change-map.mjs +118 -0
  82. package/scripts/generate-release-candidate-passport.mjs +26 -1
  83. package/scripts/generate-site-bundle.mjs +6 -0
  84. package/scripts/maintainability-domain-metrics.mjs +237 -0
  85. package/scripts/maintainability-governance.mjs +473 -0
  86. package/scripts/maintainability-metrics.mjs +28 -6
  87. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  88. package/scripts/promotion-routing-evidence.mjs +35 -6
  89. package/scripts/release-candidate-resolver.mjs +44 -0
  90. package/scripts/release-line-policy.mjs +25 -15
  91. package/scripts/release-tail.mjs +19 -2
  92. package/scripts/resume-from-candidate-run.mjs +106 -103
  93. package/scripts/run-lifecycle-core.mjs +341 -376
  94. package/scripts/site-capability-metadata.mjs +4 -0
  95. package/scripts/stable-candidate-qualification.mjs +9 -10
  96. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  97. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  98. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
@@ -4,9 +4,10 @@ import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { execFileSync } from "node:child_process";
7
- import { pathToFileURL } from "node:url";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
8
 
9
9
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
10
+ import { compareSemver } from "./publication-registry-hydrate.mjs";
10
11
  import { normalizeAnchorProvenance, normalizeCandidateRun, recoverCandidateProvenance } from "./release-candidate-anchor-provenance.mjs";
11
12
  export { resolveAnchorRecoveryRequest } from "./release-candidate-anchor-provenance.mjs";
12
13
  import {
@@ -40,10 +41,7 @@ import {
40
41
  verifyV4RuntimeAuthorizationReceipt,
41
42
  } from "../packages/core/v4-runtime-ref-resume-authority.js";
42
43
 
43
- function env(name, fallback = "") {
44
- return process.env[name] || fallback;
45
- }
46
-
44
+ function env(name, fallback = "") { return process.env[name] || fallback; }
47
45
  function requiredEnv(name) {
48
46
  const value = env(name).trim();
49
47
  if (!value) throw new Error(`${name} is required for candidate recovery`);
@@ -56,9 +54,7 @@ function splitRepository(repository) {
56
54
  return { owner: match[1], repo: match[2], fullName: `${match[1]}/${match[2]}` };
57
55
  }
58
56
 
59
- function splitPatterns(value = "") {
60
- return String(value || "").split(/\r?\n|,/).map((entry) => entry.trim()).filter(Boolean);
61
- }
57
+ function splitPatterns(value = "") { return String(value || "").split(/\r?\n|,/).map((entry) => entry.trim()).filter(Boolean); }
62
58
 
63
59
  function patternMatcher(pattern) {
64
60
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*");
@@ -111,15 +107,17 @@ function readOnlyJson(files, label) {
111
107
  return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
112
108
  }
113
109
 
114
- function trackedRuntimePersistenceScan() {
110
+ export function trackedRuntimePersistenceScan({
111
+ runtimeRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."),
112
+ } = {}) {
115
113
  const paths = execFileSync(
116
114
  "git",
117
- ["ls-files", ".github/workflows", ".github/actions", ".buildchain"],
115
+ ["-C", runtimeRoot, "ls-files", ".github/workflows", ".github/actions", ".buildchain"],
118
116
  { encoding: "utf8" },
119
117
  )
120
118
  .split(/\r?\n/)
121
119
  .filter((entry) => /\.(?:json|toml|ya?ml)$/u.test(entry));
122
- return scanV4RuntimeSelectorPersistence({ root: process.cwd(), paths });
120
+ return scanV4RuntimeSelectorPersistence({ root: runtimeRoot, paths });
123
121
  }
124
122
 
125
123
  export function verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads }) {
@@ -201,23 +199,36 @@ export function verifyReleaseCandidateStageCapsules({ sidecar, passport, downloa
201
199
  export function validateV4RuntimeResumePublicReadback({
202
200
  targetRef,
203
201
  targetSha,
202
+ targetVersion,
204
203
  alphaSha,
205
204
  exactTagSha,
206
205
  tagLineage,
206
+ runtimeLineage,
207
+ floatingTargetLineage,
207
208
  runtimeSha,
208
209
  version,
209
210
  transaction,
210
211
  main,
211
212
  npm,
212
213
  }) {
214
+ const channelVersion = npm["dist-tags"]?.alpha;
215
+ let channelDidNotRegress = false;
216
+ try {
217
+ channelDidNotRegress = compareSemver(channelVersion, version) >= 0;
218
+ } catch { channelDidNotRegress = false; }
213
219
  if (
214
220
  transaction?.target_ref !== targetRef ||
215
221
  transaction?.version !== version ||
216
222
  exactTagSha !== transaction?.source_sha ||
217
223
  !["ahead", "identical"].includes(tagLineage?.status) ||
218
- alphaSha !== runtimeSha ||
224
+ !["ahead", "identical"].includes(runtimeLineage?.status) ||
225
+ !["ahead", "identical"].includes(floatingTargetLineage?.status) ||
226
+ !alphaSha ||
219
227
  !targetSha ||
220
- npm["dist-tags"]?.alpha !== version ||
228
+ !runtimeSha ||
229
+ channelVersion !== targetVersion ||
230
+ !channelDidNotRegress ||
231
+ !npm.versions?.[channelVersion]?.dist?.integrity ||
221
232
  npm.versions?.[version]?.dist?.integrity !== main.digest
222
233
  ) {
223
234
  throw new Error(
@@ -236,21 +247,12 @@ export async function readPublicResumeState({
236
247
  apiUrl,
237
248
  fetchImpl,
238
249
  }) {
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 || "";
250
+ const readRef = async (ref) => (await githubJson({ apiUrl, token, fetchImpl,
251
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/ref/${ref}` })).object?.sha || "";
246
252
  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)}`;
253
+ if (!main || main.ref !== version || !main.digest)
254
+ throw new Error("cross-runtime recovery requires exact durable npm publication evidence");
255
+ const packageUrl = `https://registry.npmjs.org/${encodeURIComponent(main.name)}`;
254
256
  const exactTag = transaction.exact_tag || `v${version}`;
255
257
  const [targetSha, alphaSha, exactTagSha, npmResponse] = await Promise.all([
256
258
  readRef(`heads/${targetRef}`),
@@ -262,24 +264,18 @@ export async function readPublicResumeState({
262
264
  throw new Error(`npm public readback failed with HTTP ${npmResponse.status}`);
263
265
  }
264
266
  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
- });
267
+ const [tagLineage, runtimeLineage, floatingTargetLineage, targetPackageFile] = await Promise.all([
268
+ githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${exactTagSha}...${targetSha}` }),
269
+ githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${runtimeSha}...${alphaSha}` }),
270
+ githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${alphaSha}...${targetSha}` }),
271
+ githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${encodeURIComponent(targetSha)}` }),
272
+ ]);
273
+ if (targetPackageFile?.type !== "file" || targetPackageFile.encoding !== "base64" || !targetPackageFile.content)
274
+ throw new Error("cross-runtime recovery requires protected target version state");
275
+ const targetVersion = JSON.parse(Buffer.from(String(targetPackageFile.content).replace(/\s/g, ""), "base64").toString("utf8")).version;
276
+ validateV4RuntimeResumePublicReadback({ targetRef, targetSha, targetVersion, alphaSha,
277
+ exactTagSha, tagLineage, runtimeLineage, floatingTargetLineage, runtimeSha,
278
+ version, transaction, main, npm });
283
279
  const body = {
284
280
  schemaVersion: 1,
285
281
  contract: "kungfu-buildchain-v4-runtime-resume-public-readback/v1",
@@ -296,11 +292,16 @@ export async function readPublicResumeState({
296
292
  version,
297
293
  distTag: "alpha",
298
294
  integrity: npm.versions[version].dist.integrity,
295
+ targetVersion, distTagVersion: npm["dist-tags"].alpha,
296
+ distTagIntegrity: npm.versions[npm["dist-tags"].alpha].dist.integrity,
299
297
  },
300
298
  };
301
299
  return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
302
300
  }
303
301
 
302
+ export const resolveV4RuntimeResumePublicRuntimeSha = (material) =>
303
+ material?.buildAttempt?.runtimeSha || "";
304
+
304
305
  function prepareRuntimeResumeEvidence({
305
306
  repoInfo,
306
307
  targetRef,
@@ -312,15 +313,13 @@ function prepareRuntimeResumeEvidence({
312
313
  recovery,
313
314
  outputDir,
314
315
  }) {
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");
316
+ const authorizationPath = path.resolve(".buildchain/release-candidate/v4-runtime-authorization.json");
317
+ const delegatedRaw = env("BUILDCHAIN_RUNTIME_AUTHORIZATION_JSON").trim();
318
+ const delegatedRoot = env("BUILDCHAIN_RUNTIME_AUTHORIZATION_ROOT").trim();
319
+ const authorizationFileExists = fs.existsSync(authorizationPath);
320
+ if (!authorizationFileExists && (!delegatedRaw || !delegatedRoot)) throw new Error("cross-runtime recovery requires a fresh runtime authorization receipt");
321
+ const delegated = authorizationFileExists ? readOnlyJson([{ absolutePath: authorizationPath }], "runtime authorization") : JSON.parse(delegatedRaw);
322
+ if (!authorizationFileExists && delegated.receiptRoot !== delegatedRoot) throw new Error("fresh recovery runtime authorization handoff root mismatch");
324
323
  const delegatedVerification = verifyV4RuntimeAuthorizationReceipt({
325
324
  receipt: delegated.receipt,
326
325
  receiptRoot: delegated.receiptRoot,
@@ -437,7 +436,7 @@ export async function finalizeRuntimeResumeEvidence({
437
436
  const readback = await readPublicResumeState({
438
437
  repoInfo,
439
438
  targetRef,
440
- runtimeSha: material.runtimeSha,
439
+ runtimeSha: resolveV4RuntimeResumePublicRuntimeSha(material),
441
440
  version,
442
441
  transaction,
443
442
  token,
@@ -456,7 +455,7 @@ export async function finalizeRuntimeResumeEvidence({
456
455
  resumePlanRoot: material.resumePlanRoot,
457
456
  finalPublicReadbackRoot: readback.root,
458
457
  floatingRefBefore: material.floatingRefBefore,
459
- floatingRefAfter: { ref: "v4-alpha", sha: material.runtimeSha },
458
+ floatingRefAfter: { ref: "v4-alpha", sha: readback.refs.floating.sha },
460
459
  });
461
460
  const evidence = {
462
461
  authorization: material.authorization,
@@ -696,14 +695,26 @@ export function createRecoveredPublicationCandidate({
696
695
  };
697
696
  return { ...payload, candidateDigest: publicationArtifactCandidateDigest(payload) };
698
697
  }
699
-
700
- export function createRecoveredPublication({ downloads, bundleRoot, repository, passport, candidateRuntimeSha, publishArtifactKind, publishPackageMain, releasePatterns, platformManifests }) {
701
- const allFiles = downloads.flatMap((download) => download.files.map((file) => ({
702
- path: path.relative(bundleRoot, file.absolutePath).split(path.sep).join("/"),
703
- size: file.size,
704
- sha256: file.sha256.replace(/^sha256:/, ""),
705
- absolutePath: file.absolutePath,
706
- }))).sort((left, right) => left.path.localeCompare(right.path));
698
+ export function resolveRecoveredPublicationVersion({ artifactVersion, channel, rematerializeOnResume = false } = {}) {
699
+ const version = String(artifactVersion || "").trim(), match = version.match(/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u);
700
+ if (!match) throw new Error(`recovered npm artifact has invalid publication version: ${version || "<empty>"}`);
701
+ return channel === "release" && rematerializeOnResume ? match[1] : version;
702
+ }
703
+ export function resolveRecoveredCandidateVersion({ artifactVersion, publicationVersion, channel, rematerializeOnResume = false, targetRef = "", candidateRef = "" } = {}) {
704
+ const version = String(artifactVersion || "").trim(), target = String(targetRef || "").replace(/^refs\/heads\//u, ""), candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
705
+ if (channel !== "release" || !rematerializeOnResume) return version;
706
+ const prefix = `publish-gate/${target}/`;
707
+ if (!target || !candidate.startsWith(prefix)) throw new Error(`stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`);
708
+ const candidateVersion = candidate.slice(prefix.length);
709
+ if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion)) throw new Error(`stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`);
710
+ if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion) throw new Error(`stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`);
711
+ if ((version.match(/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u)?.[1] || "") !== publicationVersion) throw new Error(`recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`);
712
+ return candidateVersion;
713
+ }
714
+ export function createRecoveredPublication({ downloads, bundleRoot, repository, passport, candidateRuntimeSha, publishArtifactKind, publishPackageMain, releasePatterns, platformManifests, channel, targetRef = "", candidateRef = "", rematerializeOnResume = false }) {
715
+ const allFiles = downloads.flatMap((download) => download.files.map((file) => ({ path: path.relative(bundleRoot, file.absolutePath).split(path.sep).join("/"),
716
+ size: file.size, sha256: file.sha256.replace(/^sha256:/, ""), absolutePath: file.absolutePath })))
717
+ .sort((left, right) => left.path.localeCompare(right.path));
707
718
  const kind = String(publishArtifactKind || "npm");
708
719
  const releaseMatchers = splitPatterns(releasePatterns).map(patternMatcher);
709
720
  const releaseAssets = allFiles.filter((file) => releaseMatchers.some((matcher) => matcher.test(path.basename(file.path))));
@@ -711,12 +722,8 @@ export function createRecoveredPublication({ downloads, bundleRoot, repository,
711
722
  createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
712
723
  const version = String(passport.target?.version || "").trim();
713
724
  if (!version) throw new Error("candidate recovery requires a passport publication version");
714
- return {
715
- manifest: undefined,
716
- npmArtifacts: [],
717
- allFiles,
718
- releaseAssets,
719
- version,
725
+ return { manifest: undefined, npmArtifacts: [], allFiles, releaseAssets,
726
+ version, candidateVersion: version,
720
727
  publishRequiredArtifacts: generatePublishRequiredArtifacts({ manifests: platformManifests, version, kind }),
721
728
  };
722
729
  }
@@ -728,12 +735,9 @@ export function createRecoveredPublication({ downloads, bundleRoot, repository,
728
735
  const npmReleaseAssets = allFiles.filter((file) => releaseMatchers.length
729
736
  ? releaseMatchers.some((matcher) => matcher.test(path.basename(file.path)))
730
737
  : file.path.toLowerCase().endsWith(".tgz"));
731
- const candidate = createRecoveredPublicationCandidate({
732
- allFiles,
733
- repository,
734
- passport,
735
- candidateRuntimeSha,
736
- });
738
+ const publicationVersion = resolveRecoveredPublicationVersion({ artifactVersion: main.metadata.ref, channel, rematerializeOnResume });
739
+ const candidateVersion = resolveRecoveredCandidateVersion({ artifactVersion: main.metadata.ref, publicationVersion, channel, rematerializeOnResume, targetRef, candidateRef });
740
+ const candidate = createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
737
741
  const manifest = createPublicationSealedBundle({
738
742
  candidate,
739
743
  packageName: main.metadata.name,
@@ -747,12 +751,12 @@ export function createRecoveredPublication({ downloads, bundleRoot, repository,
747
751
  npmArtifacts,
748
752
  allFiles,
749
753
  releaseAssets: npmReleaseAssets,
750
- version: manifest.npm.version,
754
+ version: publicationVersion, candidateVersion,
751
755
  publishRequiredArtifacts: generatePublishRequiredArtifacts({
752
756
  kind: "npm",
753
757
  tarballPaths: npmArtifacts.map((entry) => entry.file.absolutePath),
754
758
  mainPackage: publishPackageMain,
755
- }),
759
+ }).map((artifact) => rematerializeOnResume ? { ...artifact, ref: publicationVersion } : artifact),
756
760
  };
757
761
  }
758
762
 
@@ -810,7 +814,7 @@ async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionI
810
814
  return { status: comparison.status, mergeIsAncestor: ["ahead", "identical"].includes(comparison.status) };
811
815
  }
812
816
 
813
- async function resolveRecoveryTransaction({
817
+ export async function resolveRecoveryTransaction({
814
818
  repoInfo,
815
819
  apiUrl,
816
820
  token,
@@ -818,22 +822,21 @@ async function resolveRecoveryTransaction({
818
822
  transactionId,
819
823
  publicationVersion,
820
824
  }) {
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 };
825
+ let transaction = publicationVersion
826
+ ? await readExistingTransaction({
827
+ repoInfo,
828
+ apiUrl,
829
+ token,
830
+ fetchImpl,
831
+ version: publicationVersion,
832
+ })
833
+ : undefined;
834
+ if (transactionId && transaction?.id !== transactionId) {
835
+ transaction = transactionId
836
+ ? await readExistingTransactionById({ repoInfo, apiUrl, token, fetchImpl, transactionId })
837
+ : undefined;
838
+ }
839
+ return { version: transaction?.version || publicationVersion, transaction };
837
840
  }
838
841
 
839
842
  function resolveRecoveredStageCapsules({
@@ -862,7 +865,7 @@ export async function resumeFromCandidateRun({
862
865
  channel, targetRef, targetSha,
863
866
  expectedSourceTree = "", expectedCandidateRoot = "",
864
867
  candidateRuntimeSha, runtimeSha,
865
- transactionId = "", artifactName = "", artifactPatterns = "", releasePatterns = "",
868
+ transactionId = "", artifactName = "", artifactPatterns = "", releasePatterns = "", rematerializeOnResume = false,
866
869
  requiredArtifactCount = 0,
867
870
  publishArtifactKind = "npm", publishPackageMain = "",
868
871
  outputDir = ".buildchain/release-candidate-recovery",
@@ -905,7 +908,7 @@ export async function resumeFromCandidateRun({
905
908
  publishArtifactKind,
906
909
  publishPackageMain,
907
910
  releasePatterns,
908
- platformManifests: platformManifestEvidence.manifests,
911
+ platformManifests: platformManifestEvidence.manifests, channel, targetRef, candidateRef: pullRequest.head?.ref || "", rematerializeOnResume,
909
912
  });
910
913
  const stageCapsules = resolveRecoveredStageCapsules({
911
914
  candidateRuntimeSha,
@@ -915,7 +918,7 @@ export async function resumeFromCandidateRun({
915
918
  downloads,
916
919
  });
917
920
  const {
918
- version: candidateVersion,
921
+ version: publicationVersion,
919
922
  transaction: existingTransaction,
920
923
  } = await resolveRecoveryTransaction({
921
924
  repoInfo,
@@ -973,7 +976,7 @@ export async function resumeFromCandidateRun({
973
976
  platformManifestEvidence: platformManifestEvidence.evidence,
974
977
  productPayloadManifests,
975
978
  artifacts: downloads.map((download) => download.record),
976
- publicationVersion: candidateVersion,
979
+ publicationVersion,
977
980
  currentToolingSha: runtimeSha,
978
981
  recoveryRunId,
979
982
  });
@@ -989,7 +992,7 @@ export async function resumeFromCandidateRun({
989
992
  repoInfo,
990
993
  targetRef,
991
994
  runtimeSha,
992
- version: candidateVersion,
995
+ version: publicationVersion,
993
996
  passport,
994
997
  sidecar: stageCapsuleSidecar,
995
998
  stageCapsules,
@@ -1004,7 +1007,7 @@ export async function resumeFromCandidateRun({
1004
1007
  repository: repoInfo.fullName,
1005
1008
  run: { id: runId, url: run.html_url || "", name: run.name },
1006
1009
  artifacts: { passport: selected.passport.name, summary: selected.summary.name, payloads: chosen.map((artifact) => artifact.name), sourceSha: selected.sourceSha },
1007
- version: candidateVersion,
1010
+ version: publicationVersion, candidateVersion: publication.candidateVersion,
1008
1011
  candidateRoot: recovery.receipt.recovered.candidateRoot,
1009
1012
  artifactRoot: recovery.receipt.recovered.artifactRoot,
1010
1013
  receipt: recovery.receipt,
@@ -1045,7 +1048,7 @@ export async function resumeFromCandidateRunCli() {
1045
1048
  expectedCandidateRoot: env("BUILDCHAIN_RESUME_EXPECTED_CANDIDATE_ROOT"),
1046
1049
  candidateRuntimeSha: requiredEnv("BUILDCHAIN_RESUME_EXPECTED_CANDIDATE_RUNTIME_SHA"),
1047
1050
  runtimeSha: requiredEnv("BUILDCHAIN_RESUME_RUNTIME_SHA"),
1048
- transactionId: env("BUILDCHAIN_RESUME_TRANSACTION_ID"),
1051
+ transactionId: env("BUILDCHAIN_RESUME_TRANSACTION_ID"), rematerializeOnResume: env("BUILDCHAIN_PUBLISH_REMATERIALIZE_ON_RESUME") === "true",
1049
1052
  artifactName: env("BUILDCHAIN_ARTIFACT_NAME"),
1050
1053
  artifactPatterns: env("BUILDCHAIN_ARTIFACT_PATTERNS"),
1051
1054
  releasePatterns: env("BUILDCHAIN_GITHUB_RELEASE_PAYLOAD_PATTERNS"),
@@ -1059,7 +1062,7 @@ export async function resumeFromCandidateRunCli() {
1059
1062
  "release-candidate-action": result.action,
1060
1063
  "release-candidate-passport-path": result.paths.passport,
1061
1064
  "release-candidate-build-summary-path": result.paths.buildSummary,
1062
- "release-candidate-version": result.version,
1065
+ "release-candidate-version": result.candidateVersion, "release-candidate-publication-version": result.version,
1063
1066
  "release-candidate-source-sha": result.artifacts.sourceSha,
1064
1067
  "release-candidate-artifact": result.artifacts.passport,
1065
1068
  "release-candidate-build-summary-artifact": result.artifacts.summary,