@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
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
+ import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { pathToFileURL } from "node:url";
6
7
  import { spawnSyncCommand } from "../packages/core/spawn-command.js";
@@ -105,6 +106,20 @@ function sealedPackResult() {
105
106
  };
106
107
  }
107
108
 
109
+ function materializedPackResult({ cwd, registry }) {
110
+ const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-npm-pack-"));
111
+ try {
112
+ const pack = parsePackResult(runNpm({
113
+ cwd,
114
+ args: ["pack", "--json", "--pack-destination", temporaryRoot, `--registry=${registry}`],
115
+ }).stdout);
116
+ return { ...pack, tarballPath: path.join(temporaryRoot, pack.filename), temporaryRoot };
117
+ } catch (error) {
118
+ fs.rmSync(temporaryRoot, { recursive: true, force: true });
119
+ throw error;
120
+ }
121
+ }
122
+
108
123
  function parseNpmView(stdout) {
109
124
  const raw = String(stdout || "").trim();
110
125
  if (!raw) {
@@ -113,8 +128,8 @@ function parseNpmView(stdout) {
113
128
  const parsed = JSON.parse(raw);
114
129
  const dist = parsed?.dist || parsed;
115
130
  return {
116
- integrity: dist?.integrity || "",
117
- shasum: dist?.shasum || "",
131
+ integrity: dist?.integrity || parsed?.["dist.integrity"] || "",
132
+ shasum: dist?.shasum || parsed?.["dist.shasum"] || "",
118
133
  };
119
134
  }
120
135
 
@@ -188,46 +203,11 @@ export function npmPublishTransaction({
188
203
  const exactTag = assertPackageVersion({ pkg, expectedVersion });
189
204
  const distTag = readEnv("BUILDCHAIN_NPM_DIST_TAG", pkg.version.includes("-") ? "alpha" : "latest");
190
205
 
191
- const pack =
192
- sealedPackResult() ||
193
- parsePackResult(
194
- runNpm({
195
- cwd: resolvedCwd,
196
- args: ["pack", "--dry-run", "--json", `--registry=${registry}`],
197
- }).stdout,
198
- );
206
+ const pack = sealedPackResult() || materializedPackResult({ cwd: resolvedCwd, registry });
199
207
  const digest = artifactDigest(pack);
200
208
 
201
- const existingDigest = skipRegistryLookup
202
- ? undefined
203
- : publishedDigest({
204
- cwd: resolvedCwd,
205
- name: pkg.name,
206
- version: pkg.version,
207
- registry,
208
- });
209
- let publishAction = "already-published";
210
- if (existingDigest) {
211
- if (existingDigest !== digest) {
212
- throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
213
- }
214
- } else if (dryRunPublish) {
215
- publishAction = "dry-run";
216
- } else {
217
- runNpm({
218
- cwd: resolvedCwd,
219
- args: [
220
- "publish",
221
- ...(pack.tarballPath ? [pack.tarballPath] : []),
222
- "--access",
223
- access,
224
- "--tag",
225
- distTag,
226
- `--registry=${registry}`,
227
- ],
228
- });
229
- publishAction = "published";
230
- const registryDigest = skipRegistryLookup
209
+ try {
210
+ const existingDigest = skipRegistryLookup
231
211
  ? undefined
232
212
  : publishedDigest({
233
213
  cwd: resolvedCwd,
@@ -235,60 +215,92 @@ export function npmPublishTransaction({
235
215
  version: pkg.version,
236
216
  registry,
237
217
  });
238
- if (registryDigest && registryDigest !== digest) {
239
- throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
218
+ let publishAction = "already-published";
219
+ if (existingDigest) {
220
+ if (existingDigest !== digest) {
221
+ throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
222
+ }
223
+ } else if (dryRunPublish) {
224
+ publishAction = "dry-run";
225
+ } else {
226
+ runNpm({
227
+ cwd: resolvedCwd,
228
+ args: [
229
+ "publish",
230
+ ...(pack.tarballPath ? [pack.tarballPath] : []),
231
+ "--access",
232
+ access,
233
+ "--tag",
234
+ distTag,
235
+ `--registry=${registry}`,
236
+ ],
237
+ });
238
+ publishAction = "published";
239
+ const registryDigest = skipRegistryLookup
240
+ ? undefined
241
+ : publishedDigest({
242
+ cwd: resolvedCwd,
243
+ name: pkg.name,
244
+ version: pkg.version,
245
+ registry,
246
+ });
247
+ if (registryDigest && registryDigest !== digest) {
248
+ throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
249
+ }
240
250
  }
241
- }
242
251
 
243
- const evidencePath = readEnv("BUILDCHAIN_PUBLISH_EVIDENCE");
244
- if (!evidencePath) {
245
- throw new Error("BUILDCHAIN_PUBLISH_EVIDENCE is required");
246
- }
247
- const evidence = {
248
- schema: 1,
249
- version: expectedVersion || pkg.version,
250
- channel: readEnv("BUILDCHAIN_CHANNEL"),
251
- source_sha: readEnv("BUILDCHAIN_SOURCE_SHA"),
252
- release_sha: readEnv("BUILDCHAIN_RELEASE_SHA"),
253
- target_ref: readEnv("BUILDCHAIN_TARGET_REF"),
254
- release_material_sha: readEnv("BUILDCHAIN_RELEASE_MATERIAL_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
255
- publish_tooling_sha: readEnv("BUILDCHAIN_PUBLISH_TOOLING_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
256
- artifacts: [{
257
- group: "node",
258
- kind: "npm",
259
- name: pkg.name,
260
- ref: pkg.version,
261
- digest,
262
- }],
263
- };
264
- const resolvedEvidencePath = writeEvidence({
265
- cwd: resolvedCwd,
266
- evidencePath,
267
- evidence,
268
- });
269
- writeGitHubOutputs({
270
- version: pkg.version,
271
- "exact-tag": exactTag,
272
- "dist-tag": distTag,
273
- "artifact-digest": digest,
274
- "publish-action": publishAction,
275
- "publish-evidence": resolvedEvidencePath,
276
- });
277
- return {
278
- schemaVersion: 1,
279
- package: {
280
- name: pkg.name,
252
+ const evidencePath = readEnv("BUILDCHAIN_PUBLISH_EVIDENCE");
253
+ if (!evidencePath) {
254
+ throw new Error("BUILDCHAIN_PUBLISH_EVIDENCE is required");
255
+ }
256
+ const evidence = {
257
+ schema: 1,
258
+ version: expectedVersion || pkg.version,
259
+ channel: readEnv("BUILDCHAIN_CHANNEL"),
260
+ source_sha: readEnv("BUILDCHAIN_SOURCE_SHA"),
261
+ release_sha: readEnv("BUILDCHAIN_RELEASE_SHA"),
262
+ target_ref: readEnv("BUILDCHAIN_TARGET_REF"),
263
+ release_material_sha: readEnv("BUILDCHAIN_RELEASE_MATERIAL_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
264
+ publish_tooling_sha: readEnv("BUILDCHAIN_PUBLISH_TOOLING_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
265
+ artifacts: [{
266
+ group: "node",
267
+ kind: "npm",
268
+ name: pkg.name,
269
+ ref: pkg.version,
270
+ digest,
271
+ }],
272
+ };
273
+ const resolvedEvidencePath = writeEvidence({
274
+ cwd: resolvedCwd,
275
+ evidencePath,
276
+ evidence,
277
+ });
278
+ writeGitHubOutputs({
281
279
  version: pkg.version,
282
- },
283
- exactTag,
284
- distTag,
285
- registry,
286
- publishAction,
287
- pack,
288
- sealedBundleRoot: readEnv("BUILDCHAIN_SEALED_BUNDLE_ROOT"),
289
- evidencePath: resolvedEvidencePath,
290
- evidence,
291
- };
280
+ "exact-tag": exactTag,
281
+ "dist-tag": distTag,
282
+ "artifact-digest": digest,
283
+ "publish-action": publishAction,
284
+ "publish-evidence": resolvedEvidencePath,
285
+ });
286
+ return {
287
+ schemaVersion: 1,
288
+ package: {
289
+ name: pkg.name,
290
+ version: pkg.version,
291
+ },
292
+ exactTag,
293
+ distTag,
294
+ registry,
295
+ publishAction,
296
+ pack: { ...pack, temporaryRoot: undefined },
297
+ sealedBundleRoot: readEnv("BUILDCHAIN_SEALED_BUNDLE_ROOT"),
298
+ evidencePath: resolvedEvidencePath,
299
+ evidence,
300
+ };
301
+ } finally {
302
+ if (pack.temporaryRoot) fs.rmSync(pack.temporaryRoot, { recursive: true, force: true });
303
+ }
292
304
  }
293
305
 
294
306
  function usage() {
@@ -66,8 +66,8 @@ export function resolvePromotionChannel({
66
66
  }
67
67
  const shellRef = intent.shellChannel === "alpha" ? `v${selected.major}-alpha` : `v${selected.major}`;
68
68
  return {
69
- targetRef: intent.targetRef,
70
- publicationChannel: intent.publicationChannel,
69
+ targetRef: intent.targetRef, publicationChannel: intent.publicationChannel,
70
+ routerRef: normalized(routerRef), routerSha: normalized(routerSha).toLowerCase(),
71
71
  channel: intent.shellChannel,
72
72
  major: selected.major,
73
73
  shellRef,
@@ -99,8 +99,8 @@ function readPackageVersion(cwd) {
99
99
 
100
100
  function writeOutputs(file, result) {
101
101
  const outputs = {
102
- "target-ref": result.targetRef,
103
- "publication-channel": result.publicationChannel,
102
+ "target-ref": result.targetRef, "publication-channel": result.publicationChannel,
103
+ "router-ref": result.routerRef, "router-sha": result.routerSha,
104
104
  channel: result.channel,
105
105
  major: String(result.major),
106
106
  "shell-ref": result.shellRef,
@@ -0,0 +1,99 @@
1
+ import path from "node:path";
2
+
3
+ export function resolveAnchorRecoveryRequest({ passport, buildSummary, transactionId = "" } = {}) {
4
+ if (Number(passport?.pullRequest?.number || 0)) return undefined;
5
+ if (passport?.target?.channel !== "anchor" || passport?.target?.ref !== "publish-gate/anchor") {
6
+ throw new Error("Release Candidate Passport has no PR identity");
7
+ }
8
+ let releaseManifest;
9
+ try {
10
+ releaseManifest = JSON.parse(buildSummary?.publishSource?.releaseManifest || "");
11
+ } catch {
12
+ throw new Error("anchored candidate release manifest is invalid");
13
+ }
14
+ const request = releaseManifest?.anchorRequest;
15
+ if (
16
+ releaseManifest?.sourceRef !== "publish-gate/anchor" ||
17
+ releaseManifest?.sourceSha !== passport.source?.headSha ||
18
+ request?.schema !== 1 ||
19
+ request?.contract !== "kungfu-buildchain-explicit-publish-anchor-request/v1" ||
20
+ !request.assignmentId ||
21
+ !/^native:[0-9a-f-]+$/u.test(String(request.attemptId || "")) ||
22
+ request.transactionId !== transactionId ||
23
+ request.source?.sha !== passport.source?.headSha ||
24
+ request.source?.tree !== passport.source?.treeHash ||
25
+ !/^\d+$/u.test(String(request.supersededCandidate?.workflowRunId || "")) ||
26
+ !/^[0-9a-f]{40}$/u.test(String(request.supersededCandidate?.sha || "")) ||
27
+ !/^sha256:[0-9a-f]{64}$/u.test(String(request.supersededCandidate?.root || "")) ||
28
+ request.runtime?.sha !== passport.buildchain?.sha
29
+ ) {
30
+ throw new Error("anchored candidate request does not bind the sealed recovery identity");
31
+ }
32
+ return request;
33
+ }
34
+
35
+ function normalizeRun(run, repository) {
36
+ return {
37
+ id: String(run.id), repository,
38
+ headRepository: run.head_repository?.full_name || "",
39
+ status: run.status, conclusion: run.conclusion,
40
+ event: run.event, path: run.path, name: run.name,
41
+ headSha: run.head_sha || "", headBranch: run.head_branch || "",
42
+ pullRequestNumbers: (run.pull_requests || []).map((entry) => Number(entry.number)),
43
+ };
44
+ }
45
+
46
+ export function normalizeCandidateRun(run, repository) {
47
+ return normalizeRun(run, repository);
48
+ }
49
+
50
+ export function normalizeAnchorProvenance(anchorProvenance, repository) {
51
+ if (!anchorProvenance) return undefined;
52
+ return {
53
+ request: anchorProvenance.request,
54
+ run: normalizeRun(anchorProvenance.run, repository),
55
+ workflow: {
56
+ path: anchorProvenance.workflow.path,
57
+ name: anchorProvenance.workflow.name,
58
+ state: anchorProvenance.workflow.state,
59
+ },
60
+ passport: anchorProvenance.passport,
61
+ buildSummary: anchorProvenance.buildSummary,
62
+ };
63
+ }
64
+
65
+ export async function recoverCandidateProvenance({
66
+ passport, buildSummary, transactionId, repoInfo, artifactName,
67
+ expectedWorkflowFile, expectedWorkflowName, channel, apiUrl, token,
68
+ fetchImpl, archiveDir, bundleRoot, githubJson, selectReleaseCandidateArtifacts,
69
+ downloadArtifact, readOnlyJson,
70
+ }) {
71
+ const request = resolveAnchorRecoveryRequest({ passport, buildSummary, transactionId });
72
+ if (!request) {
73
+ return { anchorProvenance: undefined, provenancePassport: passport };
74
+ }
75
+ const runId = String(request.supersededCandidate.workflowRunId);
76
+ const run = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/runs/${runId}` });
77
+ const workflow = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/workflows/${run.workflow_id}` });
78
+ const artifactResponse = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/runs/${runId}/artifacts?per_page=100` });
79
+ const artifacts = Array.isArray(artifactResponse.artifacts) ? artifactResponse.artifacts : [];
80
+ if (Number(artifactResponse.total_count || artifacts.length) !== artifacts.length) throw new Error("superseded candidate run has more than 100 artifacts; complete pagination is required before recovery");
81
+ const selected = selectReleaseCandidateArtifacts({ artifacts, artifactName });
82
+ const provenanceRoot = path.join(bundleRoot, "anchored-provenance");
83
+ const passportDownload = await downloadArtifact({ artifact: selected.passport, repoInfo, apiUrl, token, archiveDir, bundleRoot: provenanceRoot, fetchImpl });
84
+ const summaryDownload = await downloadArtifact({ artifact: selected.summary, repoInfo, apiUrl, token, archiveDir, bundleRoot: provenanceRoot, fetchImpl });
85
+ const recoveredPassport = readOnlyJson(passportDownload.files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "superseded release-candidate-passport.json");
86
+ const recoveredBuildSummary = readOnlyJson(summaryDownload.files.filter((file) => path.basename(file.path) === "build-summary.json"), "superseded build-summary.json");
87
+ if (
88
+ String(run.id) !== runId ||
89
+ recoveredPassport.workflow?.runId !== runId ||
90
+ recoveredPassport.workflow?.name !== expectedWorkflowName ||
91
+ String(workflow.path || run.path || "").split("@")[0].replace(/^\.github\/workflows\//u, "") !== expectedWorkflowFile.replace(/^\.github\/workflows\//u, "") ||
92
+ recoveredPassport.target?.channel !== channel ||
93
+ recoveredPassport.source?.headSha !== request.supersededCandidate.sha ||
94
+ recoveredPassport.source?.treeHash !== request.source.tree ||
95
+ `sha256:${recoveredPassport.candidateHash}` !== request.supersededCandidate.root
96
+ ) throw new Error("superseded candidate provenance does not match the anchored request");
97
+ const anchorProvenance = { request, run, workflow, passport: recoveredPassport, buildSummary: recoveredBuildSummary };
98
+ return { anchorProvenance, provenancePassport: recoveredPassport };
99
+ }
@@ -11,6 +11,10 @@ import {
11
11
  writeReleaseTailTransaction,
12
12
  } from "../packages/core/release-tail-provider-plane.js";
13
13
  import { diagnoseLegacyReleaseTailHooks } from "../packages/core/release-tail-compatibility.js";
14
+ import {
15
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
16
+ executeV4PublicationRehearsal,
17
+ } from "../packages/core/v4-publication-rehearsal.js";
14
18
 
15
19
  function flag(args, name, fallback = "") {
16
20
  const index = args.indexOf(`--${name}`);
@@ -34,8 +38,52 @@ function output(value, filePath = "") {
34
38
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
35
39
  }
36
40
 
37
- export function runReleaseTailCli(args = process.argv.slice(2)) {
41
+ export async function runReleaseTailCli(args = process.argv.slice(2)) {
38
42
  const [mode = "status", ...options] = args;
43
+ if (mode === "rehearse") {
44
+ const capsulePath = flag(options, "capsule");
45
+ const candidateRoot = flag(options, "candidate-root");
46
+ const statePath = flag(options, "state");
47
+ const evidencePath = flag(options, "evidence");
48
+ for (const [name, value] of Object.entries({
49
+ "--capsule": capsulePath,
50
+ "--candidate-root": candidateRoot,
51
+ "--state": statePath,
52
+ "--evidence": evidencePath,
53
+ }))
54
+ if (!value || !path.isAbsolute(value))
55
+ throw new Error(`${name} must be an explicit absolute path`);
56
+ const rehearsalMode = flag(options, "mode", "simulate");
57
+ if (!new Set(["simulate", "replay"]).has(rehearsalMode))
58
+ throw new Error(
59
+ "local rehearsal --mode must be simulate or replay; authorized provider rehearsal uses the reusable workflow",
60
+ );
61
+ try {
62
+ const result = await executeV4PublicationRehearsal({
63
+ capsule: readJson(capsulePath, "--capsule"),
64
+ candidateRoot,
65
+ mode: rehearsalMode,
66
+ checkpoint: (transaction) =>
67
+ writeReleaseTailTransaction(statePath, transaction),
68
+ });
69
+ writeReleaseTailTransaction(statePath, result.transaction);
70
+ output(result.evidence, evidencePath);
71
+ return result;
72
+ } catch (error) {
73
+ output(
74
+ {
75
+ schema: V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
76
+ status: "rejected",
77
+ code: error.code || "publication-rehearsal-runtime-error",
78
+ message: error.message,
79
+ productionAuthority: false,
80
+ releasePassport: null,
81
+ },
82
+ evidencePath,
83
+ );
84
+ throw error;
85
+ }
86
+ }
39
87
  if (mode === "plan") {
40
88
  const plan = compileReleaseTailDeclaration(
41
89
  readJson(flag(options, "declaration"), "--declaration"),
@@ -95,7 +143,7 @@ export function runReleaseTailCli(args = process.argv.slice(2)) {
95
143
  return report;
96
144
  }
97
145
  throw new Error(
98
- "usage: buildchain release-tail <plan|init|status|verify|compat> [--declaration <json-or-path>] [--state <path>] [--hooks-json <json-or-path>] [--output <path>]",
146
+ "usage: buildchain release-tail <plan|init|status|verify|compat|rehearse> [--declaration <json-or-path>] [--capsule <absolute-path>] [--candidate-root <absolute-path>] [--mode <simulate|replay>] [--state <absolute-path>] [--evidence <absolute-path>] [--hooks-json <json-or-path>] [--output <path>]",
99
147
  );
100
148
  }
101
149
 
@@ -105,7 +153,7 @@ if (
105
153
  import.meta.url === pathToFileURL(process.argv[1]).href
106
154
  ) {
107
155
  try {
108
- runReleaseTailCli();
156
+ await runReleaseTailCli();
109
157
  } catch (error) {
110
158
  console.error(`release-tail: ${error.message}`);
111
159
  process.exitCode = 1;