@kungfu-tech/buildchain 4.0.2 → 4.0.3

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 (233) hide show
  1. package/AGENTS.md +4 -4
  2. package/CONTRIBUTING.md +34 -0
  3. package/README.md +1 -1
  4. package/architecture/agent-change-map.md +154 -147
  5. package/architecture/{v4-architecture-constitution.md → architecture-constitution.md} +11 -11
  6. package/architecture/{v3-core-mechanism-inventory.md → baseline-core-mechanism-inventory.md} +1 -1
  7. package/architecture/{v4-bootstrap-authority.json → bootstrap-authority.json} +3 -3
  8. package/architecture/{v3-v4-capability-closure.md → capability-parity-closure.md} +3 -3
  9. package/architecture/{v3-v4-live-capability-inventory.json → capability-parity-live-capability-inventory.json} +4 -4
  10. package/architecture/{v4-capability-state-machine-manifest.json → capability-state-machine-manifest.json} +2 -2
  11. package/architecture/{v4-capability-state-machine-manifest.schema.json → capability-state-machine-manifest.schema.json} +2 -2
  12. package/architecture/ci-lane-change-budget.json +18 -0
  13. package/architecture/{v4-cross-platform-adopter-qualification.json → cross-platform-adopter-qualification.json} +3 -3
  14. package/architecture/{v4-delivery-authority-parity.json → delivery-authority-parity.json} +16 -16
  15. package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.json → delivery-warrant-shadow-bootstrap-plan.json} +3 -3
  16. package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.md → delivery-warrant-shadow-bootstrap-plan.md} +7 -7
  17. package/architecture/{v4-floating-consumer-policy.md → floating-consumer-policy.md} +1 -1
  18. package/architecture/implementation-naming.json +1273 -0
  19. package/architecture/internal-capabilities.json +171 -169
  20. package/architecture/maintainability-debt.json +87 -82
  21. package/architecture/maintainability-policy.json +15 -15
  22. package/architecture/{v4-next-development-parity.json → next-development-parity.json} +8 -8
  23. package/architecture/{v4-partial-mutation-recovery-qualification.json → partial-mutation-recovery-qualification.json} +8 -8
  24. package/architecture/{v4-platform-stage-checkpoints.json → platform-stage-checkpoints.json} +2 -2
  25. package/architecture/{v4-provider-operation-journal-contract.json → provider-operation-journal-contract.json} +9 -9
  26. package/architecture/{v4-publication-rehearsal-parity.json → publication-rehearsal-parity.json} +4 -4
  27. package/architecture/{v4-release-activation-shadow-domain.json → release-activation-shadow-domain.json} +8 -8
  28. package/architecture/{v4-release-topology.json → release-topology.json} +112 -56
  29. package/architecture/{v4-release-train-parity.json → release-train-parity.json} +2 -2
  30. package/architecture/{v4-runtime-ref-resume-authority.json → runtime-ref-resume-authority.json} +1 -1
  31. package/architecture/{v4-runtime-semantic-closure.json → runtime-semantic-closure.json} +2 -2
  32. package/architecture/{v4-rust-libnode-bridge-evaluation.json → rust-libnode-bridge-evaluation.json} +3 -3
  33. package/architecture/{v4-rust-libnode-bridge-spike.md → rust-libnode-bridge-spike.md} +6 -6
  34. package/architecture/{v4-rust-wasm-production-authority.json → rust-wasm-production-authority.json} +11 -11
  35. package/architecture/{v4-stable-publication-fence.json → stable-publication-fence.json} +6 -6
  36. package/architecture/{v4-stage-capsule-contract.json → stage-capsule-contract.json} +1 -1
  37. package/architecture/{v4-stage-capsule-qualification.json → stage-capsule-qualification.json} +6 -6
  38. package/architecture/{v4-stage-capsule-resume-planner.json → stage-capsule-resume-planner.json} +2 -2
  39. package/architecture/{v4-stage-capsule-store-contract.json → stage-capsule-store-contract.json} +4 -4
  40. package/architecture/{v4-tail-reseal-parity.json → tail-reseal-parity.json} +28 -28
  41. package/architecture/{v4-universal-workflow-bootstrap.json → universal-workflow-bootstrap.json} +9 -4
  42. package/architecture/{v4-universal-workflow-fault-campaign.json → universal-workflow-fault-campaign.json} +8 -8
  43. package/architecture/{v4-universal-workflow-train-admission.json → universal-workflow-train-admission.json} +8 -8
  44. package/architecture/workflow-taxonomy.json +11 -0
  45. package/bin/buildchain.mjs +3 -3
  46. package/bin/internal/adopter-delivery-cli.mjs +9 -9
  47. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  48. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.lock +1 -1
  49. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.toml +1 -1
  50. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/src/main.rs +3 -3
  51. package/dist/site/buildchain-contract.json +18 -18
  52. package/dist/site/buildchain-site.json +167 -71
  53. package/dist/site/capability-registry.json +5 -5
  54. package/dist/site/kfd-claims.json +226 -90
  55. package/dist/site/kfd-upstream-aggregate.json +1 -1
  56. package/dist/site/manual-registry.json +7 -7
  57. package/dist/site/node-api-registry.json +12929 -10491
  58. package/dist/site/page-registry.json +155 -59
  59. package/dist/site/public-surface-audit.json +65 -46
  60. package/dist/site/publication-authority-registry.json +27 -17
  61. package/dist/site/publication-registry.json +4 -4
  62. package/dist/site/release-provenance.json +18 -9
  63. package/dist/site/site-manifest.json +11 -11
  64. package/dist/site/workflow-registry.json +86 -52
  65. package/docs/MAP.md +4 -3
  66. package/docs/next-development-transition.md +24 -1
  67. package/docs/node-api-reference.md +435 -175
  68. package/docs/oci-publication.md +173 -0
  69. package/docs/publish-transaction.md +36 -3
  70. package/docs/release-flow.md +1 -1
  71. package/docs/reusable-build-surface.md +1 -1
  72. package/docs/v4-adopter-delivery.md +2 -2
  73. package/docs/v4-canonical-contracts.md +7 -7
  74. package/docs/v4-delivery-warrant-read-candidate.md +3 -3
  75. package/docs/v4-delivery-warrant-semantic-diff.md +3 -3
  76. package/docs/v4-production-release.md +8 -2
  77. package/docs/v4-publication-rehearsal.md +3 -3
  78. package/docs/v4-runtime-ref-resume-authority.md +2 -2
  79. package/docs/v4-rust-wasm-production-authority.md +6 -6
  80. package/docs/v4-stage-capsule.md +7 -7
  81. package/docs/v4-tail-reseal.md +1 -1
  82. package/docs/workflow-catalog.md +1 -0
  83. package/package.json +37 -26
  84. package/packages/compatibility/adopter-delivery.js +16 -0
  85. package/packages/compatibility/cross-platform-adopter-qualification.js +12 -0
  86. package/packages/compatibility/floating-consumer-policy.js +14 -0
  87. package/packages/compatibility/publication-qualification.js +11 -0
  88. package/packages/compatibility/publication-rehearsal.js +18 -0
  89. package/packages/compatibility/runtime-ref-resume-authority.js +13 -0
  90. package/packages/compatibility/tail-reseal-receipt.js +7 -0
  91. package/packages/compatibility/tail-reseal.js +11 -0
  92. package/packages/core/{v4-adopter-delivery-parity.js → adopter-delivery-parity.js} +12 -12
  93. package/packages/core/{v4-adopter-delivery.js → adopter-delivery.js} +24 -24
  94. package/packages/core/buildchain-contract.js +5 -5
  95. package/packages/core/buildchain-domain.wasm +0 -0
  96. package/packages/core/buildchain-publication-authority.js +1 -0
  97. package/packages/core/{v4-canonical-contracts.js → canonical-contracts.js} +32 -31
  98. package/packages/core/{v4-cross-platform-adopter-qualification.js → cross-platform-adopter-qualification.js} +19 -19
  99. package/packages/core/{v4-delivery-warrant-fixture-runner.js → delivery-warrant-fixture-runner.js} +36 -32
  100. package/packages/core/{v4-delivery-warrant-read-candidate.js → delivery-warrant-read-candidate.js} +40 -40
  101. package/packages/core/{v4-delivery-warrant-semantic-diff-gate.js → delivery-warrant-semantic-diff-gate.js} +58 -59
  102. package/packages/core/{v4-delivery-warrant-shadow-adapter.js → delivery-warrant-shadow-adapter.js} +15 -15
  103. package/packages/core/dev-delivery-execution-transfer.js +6 -6
  104. package/packages/core/dev-delivery-native-proof.js +12 -8
  105. package/packages/core/dev-delivery-warrant-legacy-recovery.js +1 -1
  106. package/packages/core/dev-delivery-warrant-native-compatibility.js +4 -4
  107. package/packages/core/dev-delivery-warrant-state.js +11 -11
  108. package/packages/core/dev-delivery-warrant.js +3 -3
  109. package/packages/core/domain-wasm-artifact.js +7 -0
  110. package/packages/core/{v4-domain-wasm.js → domain-wasm.js} +31 -31
  111. package/packages/core/{v4-floating-consumer-evidence.js → floating-consumer-evidence.js} +13 -13
  112. package/packages/core/{v4-floating-consumer-policy.js → floating-consumer-policy.js} +27 -29
  113. package/packages/core/{v4-floating-consumer-release-passport.js → floating-consumer-release-passport.js} +25 -25
  114. package/packages/core/index.js +4 -8
  115. package/packages/core/next-development-projection.js +24 -1
  116. package/packages/core/next-development-transition.js +5 -6
  117. package/packages/core/oci-compose-qualification.js +143 -0
  118. package/packages/core/oci-publication-bundle.js +281 -0
  119. package/packages/core/oci-publication-graph.js +206 -0
  120. package/packages/core/paper-runtime-channels.js +2 -2
  121. package/packages/core/paper.js +3 -3
  122. package/packages/core/partial-mutation-recovery-qualification.js +18 -0
  123. package/packages/core/{v4-platform-stage-checkpoints.js → platform-stage-checkpoints.js} +37 -37
  124. package/packages/core/{v4-product-publication.js → product-publication.js} +11 -9
  125. package/packages/core/{v4-protected-publication-source.js → protected-publication-source.js} +3 -3
  126. package/packages/core/provider-operation-journal.js +62 -0
  127. package/packages/core/{v4-provider-readback-idempotency.js → provider-readback-idempotency.js} +33 -33
  128. package/packages/core/public-export-compatibility.js +33 -0
  129. package/packages/core/publication-development.js +72 -0
  130. package/packages/core/{v4-publication-qualification.js → publication-qualification.js} +17 -17
  131. package/packages/core/{v4-publication-rehearsal-capsule.js → publication-rehearsal-capsule.js} +23 -23
  132. package/packages/core/publication-rehearsal-projection.js +2 -2
  133. package/packages/core/{v4-publication-rehearsal-provider-bindings.js → publication-rehearsal-provider-bindings.js} +4 -4
  134. package/packages/core/publication-rehearsal-runtime.js +21 -21
  135. package/packages/core/{v4-publication-rehearsal.js → publication-rehearsal.js} +33 -33
  136. package/packages/core/publication-sealed-bundle.js +78 -13
  137. package/packages/core/release-activation-shadow.js +32 -0
  138. package/packages/core/release-candidate.js +6 -6
  139. package/packages/core/{v4-release-invocation.js → release-invocation.js} +15 -15
  140. package/packages/core/release-passport.js +36 -36
  141. package/packages/core/release-tail-product-capabilities.js +9 -0
  142. package/packages/core/release-tail-provider-plane.js +10 -10
  143. package/packages/core/{v4-runtime-ref-resume-authority.js → runtime-ref-resume-authority.js} +36 -40
  144. package/packages/core/{v4-runtime-selector-persistence.js → runtime-selector-persistence.js} +7 -7
  145. package/packages/core/stable-publication-fence.js +28 -0
  146. package/packages/core/{v4-stage-capsule-local-store.js → stage-capsule-local-store.js} +56 -54
  147. package/packages/core/{v4-stage-capsule-qualification-campaign.js → stage-capsule-qualification-campaign.js} +37 -38
  148. package/packages/core/{v4-stage-capsule-qualification.js → stage-capsule-qualification.js} +28 -29
  149. package/packages/core/{v4-stage-capsule-resume-planner.js → stage-capsule-resume-planner.js} +29 -29
  150. package/packages/core/{v4-stage-capsule-store.js → stage-capsule-store.js} +59 -56
  151. package/packages/core/{v4-stage-capsule.js → stage-capsule.js} +45 -45
  152. package/packages/core/{v4-tail-reseal-contract.js → tail-reseal-contract.js} +6 -6
  153. package/packages/core/{v4-tail-reseal-github.js → tail-reseal-github.js} +12 -12
  154. package/packages/core/{v4-tail-reseal-receipt.js → tail-reseal-receipt.js} +22 -22
  155. package/packages/core/{v4-tail-reseal.js → tail-reseal.js} +29 -29
  156. package/packages/core/{v4-universal-workflow-bootstrap.js → universal-workflow-bootstrap.js} +23 -23
  157. package/scripts/{v4-architecture.mjs → architecture.mjs} +6 -6
  158. package/scripts/binary-publication-evidence.mjs +1 -1
  159. package/scripts/{v4-bridge-bootstrap.mjs → bridge-bootstrap.mjs} +14 -18
  160. package/scripts/{v4-bridge-evidence.mjs → bridge-evidence.mjs} +8 -8
  161. package/scripts/{build-v4-domain-wasm.mjs → build-domain-wasm.mjs} +6 -18
  162. package/scripts/{v3-v4-capability-catalog.mjs → capability-parity-catalog.mjs} +19 -19
  163. package/scripts/check-action-bundles.mjs +2 -2
  164. package/scripts/{check-v3-v4-capability-inventory.mjs → check-capability-parity-inventory.mjs} +42 -42
  165. package/scripts/check-core-mechanism-inventory.mjs +4 -1
  166. package/scripts/{check-v4-floating-consumer-policy-contract.mjs → check-floating-consumer-policy-contract.mjs} +8 -8
  167. package/scripts/check-implementation-naming.mjs +135 -0
  168. package/scripts/check-inventory.mjs +6 -5
  169. package/scripts/check-maintainability.mjs +9 -2
  170. package/scripts/{check-v4-public-dogfood-contract.mjs → check-public-dogfood-contract.mjs} +15 -15
  171. package/scripts/{check-v4-release-topology.mjs → check-release-topology.mjs} +53 -31
  172. package/scripts/{check-v4-runtime-semantic-closure.mjs → check-runtime-semantic-closure.mjs} +19 -16
  173. package/scripts/{check-v4-universal-workflow-bootstrap.mjs → check-universal-workflow-bootstrap.mjs} +14 -14
  174. package/scripts/{v4-consumer-policy.mjs → consumer-policy.mjs} +15 -15
  175. package/scripts/{copy-v4-domain-wasm.mjs → copy-domain-wasm.mjs} +3 -3
  176. package/scripts/{v4-cross-platform-adopter-qualification.mjs → cross-platform-adopter-qualification.mjs} +9 -9
  177. package/scripts/{v4-declarative-promotion-admission.mjs → declarative-promotion-admission.mjs} +4 -4
  178. package/scripts/dev-delivery-warrant-options.mjs +1 -1
  179. package/scripts/dev-delivery-warrant-store.mjs +29 -10
  180. package/scripts/dev-delivery-warrant.mjs +4 -4
  181. package/scripts/dev-pr-delivery-warrant.mjs +1 -1
  182. package/scripts/generate-channel-build-workflow.mjs +2 -2
  183. package/scripts/generate-channel-promotion-workflow.mjs +14 -31
  184. package/scripts/generate-release-candidate-passport.mjs +23 -23
  185. package/scripts/generate-site-bundle.mjs +17 -17
  186. package/scripts/{generate-v4-universal-workflow-facades.mjs → generate-universal-workflow-facades.mjs} +61 -21
  187. package/scripts/{v4-host-adapter.mjs → host-adapter.mjs} +1 -1
  188. package/scripts/implementation-references.mjs +25 -0
  189. package/scripts/init-repo.mjs +1 -1
  190. package/scripts/next-development-review.mjs +17 -11
  191. package/scripts/oci-compose-preview.mjs +261 -0
  192. package/scripts/{v4-platform-stage-checkpoint-rehearsal.mjs → platform-stage-checkpoint-rehearsal.mjs} +17 -17
  193. package/scripts/{v4-product-publication-intent.mjs → product-publication-intent.mjs} +16 -8
  194. package/scripts/publication-candidate-kind.mjs +93 -0
  195. package/scripts/publication-candidate-sealer.mjs +16 -1
  196. package/scripts/{v4-publication-rehearsal-fixture.mjs → publication-rehearsal-fixture.mjs} +5 -5
  197. package/scripts/{v4-publication-settlement.mjs → publication-settlement.mjs} +8 -8
  198. package/scripts/{v4-release-candidate-adapter.mjs → release-candidate-adapter.mjs} +4 -4
  199. package/scripts/release-candidate-resolver.mjs +15 -26
  200. package/scripts/release-tail.mjs +5 -5
  201. package/scripts/resume-from-candidate-run.mjs +35 -42
  202. package/scripts/site-capability-metadata.mjs +9 -0
  203. package/scripts/source-verification-evidence.mjs +7 -7
  204. package/scripts/{v4-stage-capsule-qualification.mjs → stage-capsule-qualification.mjs} +62 -62
  205. package/scripts/{v4-stage-capsule-resume-rehearsal.mjs → stage-capsule-resume-rehearsal.mjs} +6 -6
  206. package/scripts/{v4-tail-reseal-macos-rehearsal.mjs → tail-reseal-macos-rehearsal.mjs} +9 -9
  207. package/scripts/{v4-tail-reseal.mjs → tail-reseal.mjs} +19 -19
  208. package/scripts/universal-facade-maintainability.mjs +12 -3
  209. package/scripts/{v4-universal-workflow-backflow.mjs → universal-workflow-backflow.mjs} +10 -10
  210. package/scripts/{v4-universal-workflow-engine.mjs → universal-workflow-engine.mjs} +40 -38
  211. package/scripts/{v4-universal-workflow-self-dogfood.mjs → universal-workflow-self-dogfood.mjs} +12 -12
  212. package/scripts/verify-version-state-delta.mjs +4 -2
  213. package/scripts/{v4-warrant-shadow-plan.mjs → warrant-shadow-plan.mjs} +6 -6
  214. package/templates/universal-buildchain-bootstrap-recovery.yml +4 -4
  215. package/packages/core/buildchain-v4-domain.wasm +0 -0
  216. package/packages/core/v4-domain-wasm-artifact.js +0 -7
  217. package/packages/core/v4-partial-mutation-recovery-qualification.js +0 -18
  218. package/packages/core/v4-provider-operation-journal.js +0 -62
  219. package/packages/core/v4-release-activation-shadow.js +0 -32
  220. package/packages/core/v4-stable-publication-fence.js +0 -28
  221. /package/actions/{v4-release-candidate-promote → release-candidate-promote}/README.md +0 -0
  222. /package/architecture/{v4-adopter-delivery-parity.json → adopter-delivery-parity.json} +0 -0
  223. /package/architecture/{v3-core-mechanism-inventory.json → baseline-core-mechanism-inventory.json} +0 -0
  224. /package/architecture/{v4-canonical-contract-fixtures.json → canonical-contract-fixtures.json} +0 -0
  225. /package/architecture/{v4-compatibility-facts-parity.json → compatibility-facts-parity.json} +0 -0
  226. /package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.schema.json → delivery-warrant-shadow-bootstrap-plan.schema.json} +0 -0
  227. /package/architecture/{v4-delivery-warrant-shadow-fixtures.json → delivery-warrant-shadow-fixtures.json} +0 -0
  228. /package/architecture/{v4-delivery-warrant-shadow-fixtures.schema.json → delivery-warrant-shadow-fixtures.schema.json} +0 -0
  229. /package/architecture/{v4-exception-ledger.json → exception-ledger.json} +0 -0
  230. /package/architecture/{v4-exception-ledger.schema.json → exception-ledger.schema.json} +0 -0
  231. /package/architecture/{v4-floating-consumer-policy.json → floating-consumer-policy.json} +0 -0
  232. /package/architecture/{v4-publication-facade-patches.json → publication-facade-patches.json} +0 -0
  233. /package/architecture/{v4-release-invocation-fixtures.json → release-invocation-fixtures.json} +0 -0
@@ -366,7 +366,7 @@ jobs:
366
366
  }
367
367
 
368
368
  function workflowYaml({ type, runnerPreset, artifactName }) {
369
- return `${nextDevelopmentWorkflowHeader()}# Buildchain v4 lifecycle checkpoints are governed by architecture/v4-platform-stage-checkpoints.json.
369
+ return `${nextDevelopmentWorkflowHeader()}# Buildchain v4 lifecycle checkpoints are governed by architecture/platform-stage-checkpoints.json.
370
370
  # Do not add undeclared runner-only inputs, outputs, environment, or provider effects.
371
371
  # Commit both .buildchain/contract-lock.json and .buildchain/alpha-contract-lock.json.
372
372
  # Persist only @v4 or @v4-alpha; pass temporary train/SHA runtimes through workflow_dispatch.
@@ -1,3 +1,4 @@
1
+ import { nextPatchDevelopmentVersion } from "../packages/core/publication-development.js";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { execFileSync } from "node:child_process";
@@ -5,14 +6,14 @@ import { pathToFileURL } from "node:url";
5
6
  import { verifyVersionStateDelta } from "./verify-version-state-delta.mjs";
6
7
  import { readBinaryPublicationEvidence } from "./binary-publication-evidence.mjs";
7
8
  import { releaseAssetClient } from "./release-asset-client.mjs";
8
- import { enqueueNextDevelopmentPullRequest } from "../actions/v4-release-candidate-promote/next-development-queue.js";
9
+ import { enqueueNextDevelopmentPullRequest } from "../actions/release-candidate-promote/next-development-queue.js";
9
10
 
10
11
  const REPOSITORY = "kungfu-systems/buildchain";
11
12
  const WORKFLOW = ".github/workflows/self-build-verify.yml";
12
13
  const REVIEWER = "kungfu-origin";
13
14
  const SHA = /^[a-f0-9]{40}$/u;
14
15
  const BRANCH =
15
- /^chore\/next-development\/(4\.\d+\.\d+-alpha\.\d+)-[a-f0-9]{16}$/u;
16
+ /^chore\/next-development\/((?:0|[1-9]\d*)\.\d+\.\d+-alpha\.\d+)-[a-f0-9]{16}$/u;
16
17
 
17
18
  export function assertReviewRun(run, { repository, runId, headSha } = {}) {
18
19
  if (
@@ -43,7 +44,7 @@ export function assertReviewPull(
43
44
  pull.base?.sha !== baseSha ||
44
45
  pull.head?.ref !== branch ||
45
46
  !BRANCH.test(branch || "") ||
46
- !/^dev\/v4\/v4\.\d+$/u.test(pull.base?.ref || "") ||
47
+ pull.base?.ref !== `dev/v${BRANCH.exec(branch)?.[1].split(".")[0]}/v${BRANCH.exec(branch)?.[1].split(".").slice(0, 2).join(".")}` ||
47
48
  pull.state !== "open" ||
48
49
  pull.draft ||
49
50
  pull.merged_at ||
@@ -114,18 +115,23 @@ export async function verifyNextDevelopmentReview({
114
115
  const parents = git("show", "-s", "--format=%P", run.head_sha).split(" ");
115
116
  if (parents.length !== 1 || parents[0] !== baseSha)
116
117
  throw new Error("next-development must have one exact protected parent");
117
- const projection = verifyDelta({ baseSha, headSha: run.head_sha });
118
- if (BRANCH.exec(run.head_branch)[1] !== projection.version)
119
- throw new Error(
120
- "next-development branch version differs from regenerated version",
121
- );
122
118
  const version = JSON.parse(git("show", `${baseSha}:package.json`)).version;
123
- const tag = `v${version}`;
119
+ const targetVersion = BRANCH.exec(run.head_branch)[1];
120
+ const stable = targetVersion.endsWith("-alpha.0");
121
+ const completedStableVersion = version.split("-alpha.")[0];
122
+ if (stable && nextPatchDevelopmentVersion(completedStableVersion) !== targetVersion)
123
+ throw new Error("development target is not the completed stable patch successor");
124
+ const tag = `v${stable ? completedStableVersion : version}`;
124
125
  const sourceSha = client.json(`repos/${repository}/commits/${tag}`).sha;
125
126
  const settlement = await publication({ repository, tag, sourceSha });
126
127
  const publishedTree = settlement.documents.passport.source.treeHash;
127
- if (publishedTree !== git("rev-parse", `${baseSha}^{tree}`))
128
+ if (!stable && publishedTree !== git("rev-parse", `${baseSha}^{tree}`))
128
129
  throw new Error("development base is not the completed alpha source tree");
130
+ if (stable && (settlement.documents.passport.release?.version !== completedStableVersion || settlement.documents.invocation?.target?.channel !== "stable"))
131
+ throw new Error("stable publication evidence differs from the completed version");
132
+ const projection = verifyDelta({ baseSha, headSha: run.head_sha, ...(stable ? { completedStableVersion } : {}) });
133
+ if (targetVersion !== projection.version)
134
+ throw new Error("next-development branch version differs from regenerated version");
129
135
  observe(client, repository, runId, {
130
136
  headSha: run.head_sha,
131
137
  baseSha,
@@ -175,7 +181,7 @@ export async function approveNextDevelopment({ client, reviewer, plan }) {
175
181
  await reviewer.post(endpoint, {
176
182
  event: "APPROVE",
177
183
  commit_id: plan.headSha,
178
- body: `Verified the exact version-only transition by regenerating all tracked bytes from protected base ${plan.baseSha}. Completed alpha receipt: ${plan.publicationReceiptRoot}. Verification run: https://github.com/${plan.repository}/actions/runs/${plan.runId}.`,
184
+ body: `Verified the exact version-only transition by regenerating all tracked bytes from protected base ${plan.baseSha}. Completed publication receipt: ${plan.publicationReceiptRoot}. Verification run: https://github.com/${plan.repository}/actions/runs/${plan.runId}.`,
179
185
  });
180
186
  }
181
187
  observe(client, plan.repository, plan.runId, plan);
@@ -0,0 +1,261 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { pathToFileURL } from "node:url";
6
+ import { verifyPublicationSettlement } from "./publication-settlement.mjs";
7
+ import {
8
+ verifyComposePublication,
9
+ verifyComposeQualification,
10
+ } from "../packages/core/oci-compose-qualification.js";
11
+ import { releaseTailRoot } from "../packages/core/release-tail-provider-plane.js";
12
+ import { createRegistryClient } from "../actions/release-candidate-promote/oci-registry-client.js";
13
+ import { ociPublicationTag } from "../packages/core/oci-publication-graph.js";
14
+
15
+ const digest = (bytes) =>
16
+ `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
17
+ function check(condition, message) {
18
+ if (!condition) throw new Error(`Compose preview: ${message}`);
19
+ }
20
+ function gh(args) {
21
+ return execFileSync("gh", args, {
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "pipe"],
24
+ });
25
+ }
26
+ const api = (route) => JSON.parse(gh(["api", route]));
27
+ const read = (file) => JSON.parse(fs.readFileSync(file));
28
+ export function createComposePreviewRegistry(environment, fetchImpl = fetch) {
29
+ const token = environment.BUILDCHAIN_REGISTRY_TOKEN;
30
+ const actor = environment.GITHUB_ACTOR;
31
+ check(
32
+ typeof token === "string" &&
33
+ token.length > 0 &&
34
+ typeof actor === "string" &&
35
+ actor.length > 0,
36
+ "explicit scoped registry identity required",
37
+ );
38
+ return createRegistryClient(token, fetchImpl, actor);
39
+ }
40
+ function output(name, value) {
41
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
42
+ }
43
+
44
+ export async function promoteComposePreview({
45
+ context,
46
+ registry,
47
+ receipt,
48
+ fetchManifest,
49
+ }) {
50
+ const { application, policy } = context;
51
+ const exact = await fetchManifest(
52
+ ociPublicationTag(application, context.tag.slice(1)),
53
+ );
54
+ check(
55
+ exact.digest === application.digest,
56
+ "immutable application digest changed",
57
+ );
58
+ const current = await fetchManifest(policy.alias, true);
59
+ check(
60
+ current.digest === policy.previousDigest ||
61
+ current.digest === application.digest,
62
+ "preview changed since qualification",
63
+ );
64
+ const plan = {
65
+ schema: "kungfu-buildchain-compose-preview-plan/v1",
66
+ ...context,
67
+ qualificationRoot: releaseTailRoot(receipt),
68
+ expectedOld: policy.previousDigest,
69
+ targetDigest: application.digest,
70
+ operation: "digest-preserving-preview-promotion",
71
+ };
72
+ if (current.digest !== application.digest) {
73
+ const result = await registry(application, `manifests/${policy.alias}`, {
74
+ write: true,
75
+ method: "PUT",
76
+ body: exact.bytes,
77
+ headers: {
78
+ "content-type": exact.mediaType,
79
+ "content-length": String(exact.bytes.length),
80
+ },
81
+ });
82
+ check(
83
+ result.status === 201,
84
+ `preview write uncertain (HTTP ${result.status}); retain the original qualification for readback`,
85
+ );
86
+ }
87
+ const observed = await fetchManifest(policy.alias);
88
+ check(observed.digest === application.digest, "preview readback mismatch");
89
+ const body = {
90
+ schema: "kungfu-buildchain-compose-preview-receipt/v1",
91
+ repository: context.repository,
92
+ tag: context.tag,
93
+ sourceSha: context.sourceSha,
94
+ publicationReceiptRoot: context.publicationReceiptRoot,
95
+ planRoot: releaseTailRoot(plan),
96
+ qualificationRoot: plan.qualificationRoot,
97
+ previousDigest: policy.previousDigest,
98
+ application: `${application.repository}@${application.digest}`,
99
+ alias: `${application.repository}:${policy.alias}`,
100
+ observedDigest: observed.digest,
101
+ outcome: "complete",
102
+ };
103
+ return { ...body, receiptRoot: releaseTailRoot(body) };
104
+ }
105
+
106
+ async function run(mode) {
107
+ const repository = process.env.GITHUB_REPOSITORY,
108
+ event = read(process.env.GITHUB_EVENT_PATH);
109
+ check(
110
+ process.env.GITHUB_EVENT_NAME === "workflow_run" && event.workflow_run?.id,
111
+ "requires a completed qualification workflow event",
112
+ );
113
+ check(
114
+ /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/u.test(repository),
115
+ "invalid repository",
116
+ );
117
+ const id = String(event.workflow_run.id);
118
+ check(/^\d+$/u.test(id), "invalid qualification run");
119
+ const run = api(`repos/${repository}/actions/runs/${id}`),
120
+ tag = run.head_branch;
121
+ check(
122
+ /^v\d+\.\d+\.\d+-alpha\.\d+$/u.test(tag),
123
+ "qualification must run on an exact alpha tag",
124
+ );
125
+ const release = api(`repos/${repository}/releases/tags/${tag}`);
126
+ check(release.prerelease && !release.draft, "public alpha release required");
127
+ const root = path.resolve(".buildchain/compose-preview");
128
+ fs.mkdirSync(path.join(root, "public"), { recursive: true });
129
+ const names = [
130
+ "oci-family.json",
131
+ "oci-publication-readback.json",
132
+ "buildchain-publication-settlement.json",
133
+ "buildchain.release.json",
134
+ ];
135
+ for (const name of names) {
136
+ check(
137
+ release.assets.filter((asset) => asset.name === name).length === 1,
138
+ `missing exact public asset ${name}`,
139
+ );
140
+ gh([
141
+ "release",
142
+ "download",
143
+ tag,
144
+ "--repo",
145
+ repository,
146
+ "--pattern",
147
+ name,
148
+ "--dir",
149
+ path.join(root, "public"),
150
+ "--clobber",
151
+ ]);
152
+ }
153
+ const [family, readback, settlement, publicPassport] = names.map((name) =>
154
+ read(path.join(root, "public", name)),
155
+ );
156
+ const documents = settlement.documents;
157
+ const tagged = api(`repos/${repository}/commits/${tag}`).sha;
158
+ verifyPublicationSettlement(documents, {
159
+ repository,
160
+ tag,
161
+ sourceSha: tagged,
162
+ publicPassport,
163
+ });
164
+ const context = verifyComposePublication({
165
+ family,
166
+ readback,
167
+ documents,
168
+ repository,
169
+ tag,
170
+ });
171
+ verifyComposeQualification(context, run);
172
+ const artifactName = `oci-compose-qualification-${id}-${run.run_attempt}`;
173
+ const artifacts = api(
174
+ `repos/${repository}/actions/runs/${id}/artifacts?per_page=100`,
175
+ ).artifacts.filter(
176
+ (artifact) => artifact.name === artifactName && !artifact.expired,
177
+ );
178
+ check(
179
+ artifacts.length === 1,
180
+ "qualification artifact is missing or ambiguous",
181
+ );
182
+ if (mode === "prepare") {
183
+ output("artifact-id", artifacts[0].id);
184
+ output("run-id", id);
185
+ output("source-sha", tagged);
186
+ return;
187
+ }
188
+ check(mode === "apply", "unsupported mode");
189
+ const evidenceRoot = path.join(root, "qualification");
190
+ const receipt = read(path.join(evidenceRoot, "qualification.json"));
191
+ verifyComposeQualification(context, run, receipt);
192
+ for (const item of receipt.evidence) {
193
+ const file = path.join(evidenceRoot, item.path);
194
+ check(
195
+ !fs.lstatSync(file).isSymbolicLink() &&
196
+ fs.statSync(file).size < 2_000_000 &&
197
+ digest(fs.readFileSync(file)) === item.sha256,
198
+ "qualification evidence bytes mismatch",
199
+ );
200
+ }
201
+ const { registry } = createComposePreviewRegistry(process.env);
202
+ async function fetchManifest(ref, allowAbsent = false) {
203
+ const response = await registry(context.application, `manifests/${ref}`, {
204
+ headers: {
205
+ accept:
206
+ "application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json",
207
+ },
208
+ });
209
+ if (allowAbsent && response.status === 404) return { digest: "none" };
210
+ check(response.ok, "public registry readback unavailable");
211
+ const bytes = Buffer.from(await response.arrayBuffer()),
212
+ value = digest(bytes);
213
+ check(
214
+ !response.headers.get("docker-content-digest") ||
215
+ response.headers.get("docker-content-digest") === value,
216
+ "registry digest mismatch",
217
+ );
218
+ return { bytes, digest: value, mediaType: JSON.parse(bytes).mediaType };
219
+ }
220
+ const result = await promoteComposePreview({
221
+ context,
222
+ registry,
223
+ receipt,
224
+ fetchManifest,
225
+ });
226
+ const file = path.join(
227
+ root,
228
+ `buildchain-compose-preview-${id}-${run.run_attempt}.json`,
229
+ );
230
+ fs.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
231
+ const existing = release.assets.find(
232
+ (asset) => asset.name === path.basename(file),
233
+ );
234
+ if (existing) {
235
+ const previous = path.join(root, "previous");
236
+ fs.mkdirSync(previous, { recursive: true });
237
+ gh([
238
+ "release",
239
+ "download",
240
+ tag,
241
+ "--repo",
242
+ repository,
243
+ "--pattern",
244
+ path.basename(file),
245
+ "--dir",
246
+ previous,
247
+ "--clobber",
248
+ ]);
249
+ check(
250
+ fs.readFileSync(path.join(previous, path.basename(file)), "utf8") ===
251
+ fs.readFileSync(file, "utf8"),
252
+ "existing preview receipt conflict",
253
+ );
254
+ } else gh(["release", "upload", tag, file, "--repo", repository]);
255
+ process.stdout.write(`Verified ${result.alias}@${result.observedDigest}\n`);
256
+ }
257
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
258
+ run(process.argv[2]).catch((error) => {
259
+ process.stderr.write(`${error.message}\n`);
260
+ process.exitCode = 1;
261
+ });
@@ -5,14 +5,14 @@ import path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- import { v4CanonicalBytes } from "../packages/core/v4-canonical-contracts.js";
9
- import { V4StageCapsuleLocalStore } from "../packages/core/v4-stage-capsule-local-store.js";
10
- import { v4StageCapsuleBlobRoot } from "../packages/core/v4-stage-capsule-store.js";
8
+ import { domainCanonicalBytes } from "../packages/core/canonical-contracts.js";
9
+ import { StageCapsuleLocalStore } from "../packages/core/stage-capsule-local-store.js";
10
+ import { stageCapsuleBlobRoot } from "../packages/core/stage-capsule-store.js";
11
11
  import {
12
- emitV4PlatformStageCheckpoint,
13
- restoreV4PlatformStageCheckpoint,
14
- validateV4PlatformStageCheckpointDeclaration,
15
- } from "../packages/core/v4-platform-stage-checkpoints.js";
12
+ emitPlatformStageCheckpoint,
13
+ restorePlatformStageCheckpoint,
14
+ validatePlatformStageCheckpointDeclaration,
15
+ } from "../packages/core/platform-stage-checkpoints.js";
16
16
 
17
17
  const repoRoot = path.resolve(
18
18
  path.dirname(fileURLToPath(import.meta.url)),
@@ -32,10 +32,10 @@ function required(name) {
32
32
  }
33
33
 
34
34
  function readDeclaration() {
35
- return validateV4PlatformStageCheckpointDeclaration(
35
+ return validatePlatformStageCheckpointDeclaration(
36
36
  JSON.parse(
37
37
  fs.readFileSync(
38
- path.join(repoRoot, "architecture/v4-platform-stage-checkpoints.json"),
38
+ path.join(repoRoot, "architecture/platform-stage-checkpoints.json"),
39
39
  "utf8",
40
40
  ),
41
41
  ),
@@ -49,12 +49,12 @@ function stageFor(declaration, stageId) {
49
49
  }
50
50
 
51
51
  function rooted(label) {
52
- return v4StageCapsuleBlobRoot(Buffer.from(`${label}\n`, "utf8"));
52
+ return stageCapsuleBlobRoot(Buffer.from(`${label}\n`, "utf8"));
53
53
  }
54
54
 
55
55
  function writeJson(file, value) {
56
56
  fs.mkdirSync(path.dirname(file), { recursive: true });
57
- fs.writeFileSync(file, v4CanonicalBytes(value), { flag: "wx" });
57
+ fs.writeFileSync(file, domainCanonicalBytes(value), { flag: "wx" });
58
58
  }
59
59
 
60
60
  function emit({ workRoot, platformId, stageId, recordedAt }) {
@@ -87,11 +87,11 @@ function emit({ workRoot, platformId, stageId, recordedAt }) {
87
87
  const productionOutputRoots = Object.fromEntries(
88
88
  Object.entries(outputs).map(([name, bytes]) => [
89
89
  name,
90
- v4StageCapsuleBlobRoot(bytes),
90
+ stageCapsuleBlobRoot(bytes),
91
91
  ]),
92
92
  );
93
- const store = new V4StageCapsuleLocalStore(path.join(workRoot, "store"));
94
- const result = emitV4PlatformStageCheckpoint({
93
+ const store = new StageCapsuleLocalStore(path.join(workRoot, "store"));
94
+ const result = emitPlatformStageCheckpoint({
95
95
  declaration,
96
96
  platformId,
97
97
  stageId,
@@ -131,8 +131,8 @@ function restore({ workRoot, platformId, stageId, recordedAt }) {
131
131
  );
132
132
  if (emission.platform !== platformId || emission.stage !== stageId)
133
133
  throw new Error("emission identity does not match restore request");
134
- const store = new V4StageCapsuleLocalStore(path.join(workRoot, "store"));
135
- const result = restoreV4PlatformStageCheckpoint({
134
+ const store = new StageCapsuleLocalStore(path.join(workRoot, "store"));
135
+ const result = restorePlatformStageCheckpoint({
136
136
  declaration,
137
137
  expectedPlatform: platformId,
138
138
  expectedStage: stageId,
@@ -201,7 +201,7 @@ else if (action === "rehearse") {
201
201
  providerCredentialsUsed: restored.providerCredentialsUsed,
202
202
  };
203
203
  writeJson(path.join(common.workRoot, "evidence.json"), evidence);
204
- process.stdout.write(v4CanonicalBytes(evidence));
204
+ process.stdout.write(domainCanonicalBytes(evidence));
205
205
  } else {
206
206
  throw new Error("action must be emit, restore, or rehearse");
207
207
  }
@@ -5,8 +5,8 @@ import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
7
  import { spawnSyncCommand } from "../packages/core/spawn-command.js";
8
- import { selectV4ProductPublicationIntent } from "../packages/core/v4-product-publication.js";
9
- import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
8
+ import { selectProductPublicationIntent } from "../packages/core/product-publication.js";
9
+ import { domainContentRoot } from "../packages/core/canonical-contracts.js";
10
10
 
11
11
  function env(name, required = false) {
12
12
  const value = String(process.env[name] || "").trim();
@@ -59,20 +59,20 @@ function writeOutput(name, value) {
59
59
  fs.appendFileSync(output, `${name}=${String(value)}\n`);
60
60
  }
61
61
 
62
- export function resolveV4ProductPublicationIntent() {
62
+ export function resolveProductPublicationIntent() {
63
63
  const artifactKind = env("BUILDCHAIN_PUBLISH_ARTIFACT_KIND") || "npm";
64
64
  const name = packageName(artifactKind);
65
65
  const sourceSha = env("BUILDCHAIN_SOURCE_SHA", true);
66
66
  const manifestPath = env(
67
67
  "BUILDCHAIN_SEALED_BUNDLE_MANIFEST",
68
- artifactKind === "npm",
68
+ artifactKind !== "custom",
69
69
  );
70
70
  const manifest = manifestPath ? readJson(manifestPath) : null;
71
71
  const requiredArtifacts = readJson(
72
72
  env("BUILDCHAIN_REQUIRED_ARTIFACTS_PATH", true),
73
73
  );
74
74
  const channel = env("BUILDCHAIN_CHANNEL", true);
75
- const intent = selectV4ProductPublicationIntent({
75
+ const intent = selectProductPublicationIntent({
76
76
  channel,
77
77
  targetRef: env("BUILDCHAIN_TARGET_REF", true),
78
78
  sourceSha,
@@ -80,11 +80,19 @@ export function resolveV4ProductPublicationIntent() {
80
80
  repository: env("BUILDCHAIN_REPOSITORY", true),
81
81
  artifactKind,
82
82
  packageName: name,
83
+ ...(manifest?.npmPackages
84
+ ? {
85
+ npmPackages: manifest.npmPackages.map((entry) => ({
86
+ ...entry,
87
+ sha256: `sha256:${entry.sha256}`,
88
+ })),
89
+ }
90
+ : {}),
83
91
  distTag:
84
92
  env("BUILDCHAIN_PUBLISH_DIST_TAG") ||
85
93
  (channel === "alpha" ? "alpha" : "latest"),
86
94
  sealedBundleRoot: manifest?.root,
87
- requiredArtifactsRoot: v4ContentRoot(
95
+ requiredArtifactsRoot: domainContentRoot(
88
96
  "v4-product-required-artifacts",
89
97
  requiredArtifacts,
90
98
  ),
@@ -94,7 +102,7 @@ export function resolveV4ProductPublicationIntent() {
94
102
  });
95
103
  const outputPath = path.resolve(
96
104
  env("BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH") ||
97
- ".buildchain/release-candidate/v4-product-publication-intent.json",
105
+ ".buildchain/release-candidate/product-publication-intent.json",
98
106
  );
99
107
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
100
108
  fs.writeFileSync(outputPath, `${JSON.stringify(intent, null, 2)}\n`);
@@ -110,7 +118,7 @@ if (
110
118
  import.meta.url === pathToFileURL(process.argv[1]).href
111
119
  ) {
112
120
  try {
113
- const result = resolveV4ProductPublicationIntent();
121
+ const result = resolveProductPublicationIntent();
114
122
  process.stdout.write(
115
123
  `v4 product publication intent: ${result.name}@${result.intent.version} (${result.intent.mode})\n`,
116
124
  );
@@ -0,0 +1,93 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ociPublicationTag } from "../packages/core/oci-publication-graph.js";
4
+ import { verifyOciPublicationBundle } from "../packages/core/oci-publication-bundle.js";
5
+
6
+ export function resolveOciCandidate({ payloadRoot, passport }) {
7
+ const matches = [];
8
+ function visit(directory) {
9
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
10
+ if (entry.isSymbolicLink())
11
+ throw new Error("OCI candidate cannot contain symlinks");
12
+ const file = path.join(directory, entry.name);
13
+ if (entry.isDirectory()) visit(file);
14
+ else if (entry.name === "oci-family.json") matches.push(file);
15
+ }
16
+ }
17
+ visit(payloadRoot);
18
+ if (matches.length !== 1)
19
+ throw new Error("OCI candidate requires exactly one sealed image family");
20
+ const manifest = JSON.parse(fs.readFileSync(matches[0]));
21
+ const bundleRoot = path.dirname(matches[0]);
22
+ const verified = verifyOciPublicationBundle({
23
+ bundleRoot,
24
+ manifest,
25
+ repository: passport.repository,
26
+ sourceSha: passport.source.headSha,
27
+ version: passport.target.version,
28
+ });
29
+ return {
30
+ root: bundleRoot,
31
+ manifest: verified.manifest,
32
+ requiredArtifacts: manifest.images.map((image) => ({
33
+ kind: "oci",
34
+ name: image.repository,
35
+ ref: ociPublicationTag(image, manifest.version),
36
+ digest: image.digest,
37
+ platform: image.platform,
38
+ required: true,
39
+ })),
40
+ };
41
+ }
42
+
43
+ export function resolveRecoveredPublicationVersion({
44
+ artifactVersion,
45
+ channel,
46
+ rematerializeOnResume = false,
47
+ } = {}) {
48
+ const version = String(artifactVersion || "").trim(),
49
+ match = version.match(
50
+ /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
51
+ );
52
+ if (!match)
53
+ throw new Error(
54
+ `recovered npm artifact has invalid publication version: ${version || "<empty>"}`,
55
+ );
56
+ return channel === "release" && rematerializeOnResume ? match[1] : version;
57
+ }
58
+ export function resolveRecoveredCandidateVersion({
59
+ artifactVersion,
60
+ publicationVersion,
61
+ channel,
62
+ rematerializeOnResume = false,
63
+ targetRef = "",
64
+ candidateRef = "",
65
+ } = {}) {
66
+ const version = String(artifactVersion || "").trim(),
67
+ target = String(targetRef || "").replace(/^refs\/heads\//u, ""),
68
+ candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
69
+ if (channel !== "release" || !rematerializeOnResume) return version;
70
+ const prefix = `publish-gate/${target}/`;
71
+ if (!target || !candidate.startsWith(prefix))
72
+ throw new Error(
73
+ `stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`,
74
+ );
75
+ const candidateVersion = candidate.slice(prefix.length);
76
+ if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion))
77
+ throw new Error(
78
+ `stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`,
79
+ );
80
+ if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion)
81
+ throw new Error(
82
+ `stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`,
83
+ );
84
+ if (
85
+ (version.match(
86
+ /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
87
+ )?.[1] || "") !== publicationVersion
88
+ )
89
+ throw new Error(
90
+ `recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`,
91
+ );
92
+ return candidateVersion;
93
+ }
@@ -1,3 +1,4 @@
1
+ import { resolveOciCandidate } from "./publication-candidate-kind.mjs";
1
2
  import crypto from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -69,7 +70,7 @@ export function createResolvedPublicationSealedBundle({
69
70
  });
70
71
  const main = resolvedNpmArtifacts.find((entry) => entry.metadata.role === "main")
71
72
  || (resolvedNpmArtifacts.length === 1 ? resolvedNpmArtifacts[0] : undefined);
72
- if (!main) throw new Error("candidate npm payload set has no unique main package tarball");
73
+ if (!main || resolvedNpmArtifacts.filter((entry) => entry.metadata.role === "main").length > 1) throw new Error("candidate npm payload set has no unique main package tarball");
73
74
  const selectedReleaseAssets = (releaseAssetPaths.length > 0
74
75
  ? releaseAssetPaths
75
76
  : resolvedNpmArtifacts.map((entry) => entry.file.absolutePath))
@@ -88,6 +89,12 @@ export function createResolvedPublicationSealedBundle({
88
89
  releaseCandidateRoot: `sha256:${normalizeSha256(releaseCandidateRoot, "releaseCandidateRoot")}`,
89
90
  files: files.map(({ path: filePath, size, sha256 }) => ({ path: filePath, size, sha256 })),
90
91
  };
92
+ if (resolvedNpmArtifacts.length > 1) {
93
+ candidatePayload.npmPackages = resolvedNpmArtifacts.map(({file, metadata}) => ({
94
+ name: metadata.name, version: metadata.ref, role: metadata.role,
95
+ path: file.path, size: file.size, sha256: file.sha256, integrity: metadata.integrity,
96
+ }));
97
+ }
91
98
  const candidate = {
92
99
  ...candidatePayload,
93
100
  candidateDigest: publicationArtifactCandidateDigest(candidatePayload),
@@ -102,3 +109,11 @@ export function createResolvedPublicationSealedBundle({
102
109
  });
103
110
  return { root: resolvedRoot, manifest, npmArtifacts: resolvedNpmArtifacts, files };
104
111
  }
112
+
113
+ export function resolveCandidatePublicationBundle({ kind, payloadDir, passport, runtimeSha, npmArtifacts, releaseAssetPaths }) {
114
+ if (kind === "oci") return resolveOciCandidate({ payloadRoot: payloadDir, passport });
115
+ if (kind !== "npm") return undefined;
116
+ return createResolvedPublicationSealedBundle({ bundleRoot: payloadDir, repository: passport.repository,
117
+ sourceSha: passport.source?.headSha, sourceTreeSha: passport.source?.treeHash,
118
+ runtimeSha, releaseCandidateRoot: passport.candidateHash, npmArtifacts, releaseAssetPaths });
119
+ }
@@ -6,9 +6,9 @@ import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  import {
9
- createV4PublicationRehearsalCapsule,
10
- executeV4PublicationRehearsal,
11
- } from "../packages/core/v4-publication-rehearsal.js";
9
+ createDomainPublicationRehearsalCapsule,
10
+ executeDomainPublicationRehearsal,
11
+ } from "../packages/core/publication-rehearsal.js";
12
12
 
13
13
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
14
  const fixtureRoot = path.join(
@@ -61,7 +61,7 @@ async function generated() {
61
61
  path: relative,
62
62
  root: files.find((entry) => entry.path === relative).root,
63
63
  });
64
- const capsule = createV4PublicationRehearsalCapsule({
64
+ const capsule = createDomainPublicationRehearsalCapsule({
65
65
  source: {
66
66
  repository: "kungfu-systems/buildchain",
67
67
  revision: "bc12cd0ff18a7e1d918777ea093c169349952d34",
@@ -100,7 +100,7 @@ async function generated() {
100
100
  });
101
101
  const vectors = [];
102
102
  for (const platform of ["linux", "macos", "windows"]) {
103
- const result = await executeV4PublicationRehearsal({
103
+ const result = await executeDomainPublicationRehearsal({
104
104
  capsule,
105
105
  candidateRoot,
106
106
  mode: "simulate",