@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21

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 (203) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/ci-lane-change-budget.json +187 -0
  3. package/architecture/decisions/0002-next-development-transition.md +101 -0
  4. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  5. package/architecture/internal-capabilities.json +211 -8
  6. package/architecture/maintainability-policy.json +113 -28
  7. package/architecture/release-tail-contract-inventory.json +5 -2
  8. package/architecture/v3-core-mechanism-inventory.json +91 -0
  9. package/architecture/v4-adopter-delivery-parity.json +32 -5
  10. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  11. package/architecture/v4-compatibility-facts-parity.json +77 -0
  12. package/architecture/v4-delivery-authority-parity.json +250 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  14. package/architecture/v4-floating-consumer-policy.json +6 -0
  15. package/architecture/v4-next-development-parity.json +192 -0
  16. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  17. package/architecture/v4-release-train-parity.json +1 -0
  18. package/architecture/v4-tail-reseal-parity.json +150 -0
  19. package/bin/buildchain.mjs +32 -3
  20. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  21. package/bin/internal/command-registry.mjs +3 -0
  22. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  23. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  24. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  25. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  26. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  29. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  30. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  31. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  32. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  33. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  39. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  43. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  44. package/contracts/next-development-request-v1.schema.json +66 -0
  45. package/contracts/next-development-transition-v1.schema.json +292 -0
  46. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  47. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  48. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  49. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  50. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  51. package/dist/site/agent-index.json +5 -0
  52. package/dist/site/artifact-schemas.json +10 -0
  53. package/dist/site/buildchain-contract.json +1475 -27
  54. package/dist/site/buildchain-site.json +627 -31
  55. package/dist/site/capability-registry.json +17 -14
  56. package/dist/site/cli-registry.json +205 -3
  57. package/dist/site/kfd-claims.json +490 -16
  58. package/dist/site/kfd-upstream-aggregate.json +1 -1
  59. package/dist/site/manual-registry.json +52 -6
  60. package/dist/site/node-api-registry.json +14736 -8851
  61. package/dist/site/page-registry.json +593 -21
  62. package/dist/site/public-surface-audit.json +837 -48
  63. package/dist/site/publication-authority-registry.json +44 -8
  64. package/dist/site/publication-registry.json +4 -4
  65. package/dist/site/release-provenance.json +24 -0
  66. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  67. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  68. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  69. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  70. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  71. package/dist/site/site-manifest.json +34 -10
  72. package/dist/site/workflow-registry.json +160 -10
  73. package/docs/MAP.md +8 -2
  74. package/docs/cli-reference.md +404 -13
  75. package/docs/cli.md +8 -0
  76. package/docs/dev-alpha-candidate-patrol.md +8 -0
  77. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  78. package/docs/dev-delivery-warrant.md +351 -38
  79. package/docs/next-development-transition.md +118 -0
  80. package/docs/node-api-reference.md +646 -293
  81. package/docs/release-tail-provider-plane.md +30 -0
  82. package/docs/v4-adopter-delivery.md +133 -0
  83. package/docs/v4-compatibility-facts.md +91 -0
  84. package/docs/v4-publication-rehearsal.md +130 -0
  85. package/docs/v4-tail-reseal.md +73 -0
  86. package/docs/versioning.md +4 -3
  87. package/package.json +34 -6
  88. package/packages/core/adopter-delivery-passport.js +259 -0
  89. package/packages/core/adopter-delivery-vectors.js +158 -0
  90. package/packages/core/adopter-delivery-vectors.json +77 -0
  91. package/packages/core/buildchain-agent-manuals.js +3 -0
  92. package/packages/core/buildchain-compatibility-authority.js +336 -0
  93. package/packages/core/buildchain-compatibility-fact.js +394 -0
  94. package/packages/core/buildchain-compatibility-facts.json +217 -0
  95. package/packages/core/buildchain-compatibility-proof.js +566 -0
  96. package/packages/core/buildchain-config.js +84 -0
  97. package/packages/core/buildchain-contract.js +96 -143
  98. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  99. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  100. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  101. package/packages/core/buildchain-publication-authority.js +1 -0
  102. package/packages/core/channel-candidate.js +8 -0
  103. package/packages/core/channel-promotion-baseline.js +55 -0
  104. package/packages/core/ci-lane-change-budget.js +247 -0
  105. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  106. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  107. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  108. package/packages/core/dev-delivery-authority-landing.js +461 -0
  109. package/packages/core/dev-delivery-authority-observation.js +48 -0
  110. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  111. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  112. package/packages/core/dev-delivery-authority-state.js +583 -0
  113. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  114. package/packages/core/dev-delivery-contract-surface.js +76 -0
  115. package/packages/core/dev-delivery-execution-failure.js +133 -0
  116. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  117. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  118. package/packages/core/dev-delivery-landing-readback.js +598 -0
  119. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  120. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  121. package/packages/core/dev-delivery-native-execution.js +110 -0
  122. package/packages/core/dev-delivery-native-proof.js +562 -0
  123. package/packages/core/dev-delivery-process-boundary.js +563 -0
  124. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  125. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  126. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  127. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  128. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  129. package/packages/core/dev-delivery-warrant-state.js +587 -0
  130. package/packages/core/dev-delivery-warrant.js +328 -370
  131. package/packages/core/github-governance-authority.js +1 -0
  132. package/packages/core/index.js +5 -0
  133. package/packages/core/kungfu-temporal-fact.js +557 -0
  134. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  135. package/packages/core/next-development-candidate-reservation.js +186 -0
  136. package/packages/core/next-development-controller.js +728 -0
  137. package/packages/core/next-development-projection.js +287 -0
  138. package/packages/core/next-development-transition.js +738 -0
  139. package/packages/core/paper-agent-entry.js +11 -5
  140. package/packages/core/paper.js +16 -5
  141. package/packages/core/published-delivery-authority.js +266 -0
  142. package/packages/core/release-candidate-recovery.js +70 -15
  143. package/packages/core/release-candidate.js +2 -2
  144. package/packages/core/release-passport.js +36 -9
  145. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  146. package/packages/core/v4-adopter-delivery.js +359 -0
  147. package/packages/core/v4-canonical-contracts.js +5 -0
  148. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  149. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  150. package/packages/core/v4-floating-consumer-policy.js +143 -20
  151. package/packages/core/v4-floating-consumer-release-passport.js +32 -5
  152. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  153. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  154. package/packages/core/v4-publication-rehearsal.js +481 -0
  155. package/packages/core/v4-tail-reseal-contract.js +26 -0
  156. package/packages/core/v4-tail-reseal-github.js +178 -0
  157. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  158. package/packages/core/v4-tail-reseal.js +594 -0
  159. package/packages/core/workflow-yaml-contract.js +24 -2
  160. package/scripts/buildchain-cli-help.mjs +41 -2
  161. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  162. package/scripts/check-inventory.mjs +14 -3
  163. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  164. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  165. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  166. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  167. package/scripts/dev-delivery-authority.mjs +490 -0
  168. package/scripts/dev-delivery-native-run.mjs +177 -0
  169. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  170. package/scripts/dev-delivery-proof.mjs +67 -2
  171. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  172. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  173. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  174. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  175. package/scripts/dev-delivery-two-phase.mjs +573 -0
  176. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  177. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  178. package/scripts/dev-delivery-warrant.mjs +232 -194
  179. package/scripts/dev-pr-auto-merge.mjs +1 -1
  180. package/scripts/dev-pr-delivery-warrant.mjs +11 -0
  181. package/scripts/dev-qualification-patrol.mjs +9 -9
  182. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  183. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  184. package/scripts/generate-next-development-guidance.mjs +49 -0
  185. package/scripts/generate-release-candidate-passport.mjs +152 -0
  186. package/scripts/generate-site-bundle.mjs +69 -4
  187. package/scripts/init-repo.mjs +22 -4
  188. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  189. package/scripts/next-development-self-dogfood.mjs +597 -0
  190. package/scripts/next-development-transition.mjs +47 -0
  191. package/scripts/npm-publish-transaction.mjs +2 -2
  192. package/scripts/promotion-channel-router.mjs +4 -4
  193. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  194. package/scripts/release-tail.mjs +51 -3
  195. package/scripts/release-train-self-dogfood.mjs +94 -92
  196. package/scripts/resume-from-candidate-run.mjs +557 -30
  197. package/scripts/site-capability-metadata.mjs +41 -0
  198. package/scripts/stable-candidate-qualification.mjs +9 -10
  199. package/scripts/v4-consumer-policy.mjs +5 -5
  200. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  201. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  202. package/scripts/v4-tail-reseal.mjs +416 -0
  203. package/templates/native-dev-delivery.yml +143 -0
@@ -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;
@@ -6,7 +6,11 @@ import fs from "node:fs";
6
6
  import {
7
7
  closeDevDeliveryWarrant,
8
8
  createDevDeliveryQueue,
9
+ createNativeCommandContract,
10
+ createNativeProofReuseDecision,
11
+ createNativeQualificationProof,
9
12
  createReleaseBlockerPriorityClaim,
13
+ qualifyDevDeliveryWarrant,
10
14
  selectDevDeliveryWarrant,
11
15
  submitDevDeliveryCandidate,
12
16
  } from "../packages/core/dev-delivery-warrant.js";
@@ -81,9 +85,12 @@ function branch(value, label) {
81
85
  return normalized;
82
86
  }
83
87
 
84
- function at(recordedAt, seconds) {
85
- return new Date(Date.parse(recordedAt) + seconds * 1000).toISOString();
86
- }
88
+ const at = (recordedAt, seconds) => new Date(Date.parse(recordedAt) + seconds * 1000).toISOString();
89
+
90
+ const normalizeFields = (input, fields, normalizer, prefix = "") =>
91
+ Object.fromEntries(fields.map((field) => [field, normalizer(input?.[field], `${prefix}${field}`)]));
92
+
93
+ const requireEvidence = (condition, message) => { if (!condition) throw new Error(message); };
87
94
 
88
95
  function normalizeDelivery(input, expected) {
89
96
  const delivery = {
@@ -92,59 +99,44 @@ function normalizeDelivery(input, expected) {
92
99
  "delivery.pullRequestNumber",
93
100
  ),
94
101
  baseBranch: branch(input?.baseBranch, "delivery.baseBranch"),
95
- headSha: sha(input?.headSha, "delivery.headSha"),
96
- headTreeSha: sha(input?.headTreeSha, "delivery.headTreeSha"),
97
- state: text(input?.state, "delivery.state"),
98
- reviewState: text(input?.reviewState, "delivery.reviewState"),
99
- reviewHeadSha: sha(input?.reviewHeadSha, "delivery.reviewHeadSha"),
100
- mergeSha: sha(input?.mergeSha, "delivery.mergeSha"),
101
- mergeTreeSha: sha(input?.mergeTreeSha, "delivery.mergeTreeSha"),
102
- ciHeadSha: sha(input?.ciHeadSha, "delivery.ciHeadSha"),
103
- ciConclusion: text(input?.ciConclusion, "delivery.ciConclusion"),
104
- reviewRoot: root(input?.reviewRoot, "delivery.reviewRoot"),
105
- ciRoot: root(input?.ciRoot, "delivery.ciRoot"),
106
- mergeRoot: root(input?.mergeRoot, "delivery.mergeRoot"),
107
- artifactRoot: root(input?.artifactRoot, "delivery.artifactRoot"),
108
- installedProductRoot: root(
109
- input?.installedProductRoot,
110
- "delivery.installedProductRoot",
102
+ ...normalizeFields(
103
+ input,
104
+ ["headSha", "headTreeSha", "reviewHeadSha", "mergeSha", "mergeTreeSha", "ciHeadSha"],
105
+ sha,
106
+ "delivery.",
107
+ ),
108
+ ...normalizeFields(input, ["state", "reviewState", "ciConclusion"], text, "delivery."),
109
+ ...normalizeFields(
110
+ input,
111
+ ["reviewRoot", "ciRoot", "mergeRoot", "artifactRoot", "installedProductRoot"],
112
+ root,
113
+ "delivery.",
111
114
  ),
112
115
  };
113
- if (delivery.baseBranch !== expected.sourceBranch) {
114
- throw new Error(
115
- "protected delivery base does not match the Release Cut source branch",
116
- );
117
- }
118
- if (
119
- delivery.state !== "MERGED" ||
120
- delivery.reviewState !== "APPROVED" ||
121
- delivery.ciConclusion !== "success"
122
- ) {
123
- throw new Error(
124
- "protected delivery is not merged, approved, and CI-successful",
125
- );
126
- }
127
- if (
128
- delivery.headSha !== expected.devLandingSha ||
129
- delivery.reviewHeadSha !== delivery.headSha
130
- ) {
131
- throw new Error(
132
- "protected delivery review does not bind the exact repaired head",
133
- );
134
- }
135
- if (
136
- delivery.headTreeSha !== expected.cutCandidateTreeSha ||
137
- delivery.mergeTreeSha !== delivery.headTreeSha
138
- ) {
139
- throw new Error(
140
- "protected delivery merge tree does not equal the repaired candidate tree",
141
- );
142
- }
143
- if (delivery.ciHeadSha !== delivery.mergeSha) {
144
- throw new Error(
145
- "protected delivery CI does not bind the merge-group result",
146
- );
147
- }
116
+ requireEvidence(
117
+ delivery.baseBranch === expected.sourceBranch,
118
+ "protected delivery base does not match the Release Cut source branch",
119
+ );
120
+ requireEvidence(
121
+ delivery.state === "MERGED" &&
122
+ delivery.reviewState === "APPROVED" &&
123
+ delivery.ciConclusion === "success",
124
+ "protected delivery is not merged, approved, and CI-successful",
125
+ );
126
+ requireEvidence(
127
+ delivery.headSha === expected.devLandingSha &&
128
+ delivery.reviewHeadSha === delivery.headSha,
129
+ "protected delivery review does not bind the exact repaired head",
130
+ );
131
+ requireEvidence(
132
+ delivery.headTreeSha === expected.cutCandidateTreeSha &&
133
+ delivery.mergeTreeSha === delivery.headTreeSha,
134
+ "protected delivery merge tree does not equal the repaired candidate tree",
135
+ );
136
+ requireEvidence(
137
+ delivery.ciHeadSha === delivery.mergeSha,
138
+ "protected delivery CI does not bind the merge-group result",
139
+ );
148
140
  return delivery;
149
141
  }
150
142
 
@@ -153,50 +145,32 @@ function normalizeInput(input = {}) {
153
145
  repository: repository(input.repository),
154
146
  sourceBranch: branch(input.sourceBranch, "sourceBranch"),
155
147
  targetBranch: branch(input.targetBranch, "targetBranch"),
156
- assignmentRoot: root(input.assignmentRoot, "assignmentRoot"),
157
- initiativeRoot: root(input.initiativeRoot, "initiativeRoot"),
158
- dependencyProofRoot: root(input.dependencyProofRoot, "dependencyProofRoot"),
159
- originDevSha: sha(input.originDevSha, "originDevSha"),
160
- candidateSha: sha(input.candidateSha, "candidateSha"),
161
- candidateTreeSha: sha(input.candidateTreeSha, "candidateTreeSha"),
162
- alphaBaseSha: sha(input.alphaBaseSha, "alphaBaseSha"),
163
- buildchainRuntimeSha: sha(
164
- input.buildchainRuntimeSha,
165
- "buildchainRuntimeSha",
166
- ),
167
- observedDevSha: sha(input.observedDevSha, "observedDevSha"),
168
- blockerRoot: root(input.blockerRoot, "blockerRoot"),
169
- patchRoot: root(input.patchRoot, "patchRoot"),
170
- cutCandidateSha: sha(input.cutCandidateSha, "cutCandidateSha"),
171
- cutCandidateTreeSha: sha(input.cutCandidateTreeSha, "cutCandidateTreeSha"),
172
- cutLandingEvidenceRoot: root(
173
- input.cutLandingEvidenceRoot,
174
- "cutLandingEvidenceRoot",
148
+ ...normalizeFields(
149
+ input,
150
+ ["assignmentRoot", "initiativeRoot", "dependencyProofRoot", "environmentRoot", "blockerRoot", "patchRoot", "cutLandingEvidenceRoot", "devConflictEvidenceRoot", "devLandingEvidenceRoot"],
151
+ root,
175
152
  ),
176
- devBaseSha: sha(input.devBaseSha, "devBaseSha"),
177
- devLandingSha: sha(input.devLandingSha, "devLandingSha"),
178
- devConflictEvidenceRoot: root(
179
- input.devConflictEvidenceRoot,
180
- "devConflictEvidenceRoot",
181
- ),
182
- devLandingEvidenceRoot: root(
183
- input.devLandingEvidenceRoot,
184
- "devLandingEvidenceRoot",
153
+ ...normalizeFields(
154
+ input,
155
+ ["originDevSha", "candidateSha", "candidateTreeSha", "alphaBaseSha", "buildchainRuntimeSha", "observedDevSha", "cutCandidateSha", "cutCandidateTreeSha", "devBaseSha", "devLandingSha"],
156
+ sha,
185
157
  ),
158
+ nativeCommand: text(input.nativeCommand, "nativeCommand"),
159
+ nativeExecutionReceipt: structuredClone(input.nativeExecutionReceipt || {}),
186
160
  ordinaryPullRequestNumber: positiveInteger(
187
161
  input.ordinaryPullRequestNumber,
188
162
  "ordinaryPullRequestNumber",
189
163
  ),
190
164
  recordedAt: timestamp(input.recordedAt, "recordedAt"),
191
165
  };
192
- if (normalized.observedDevSha === normalized.originDevSha) {
193
- throw new Error(
194
- "self-dogfood requires dev to advance beyond the frozen origin",
195
- );
196
- }
197
- if (normalized.cutCandidateSha === normalized.candidateSha) {
198
- throw new Error("self-dogfood requires a successor repair candidate");
199
- }
166
+ requireEvidence(
167
+ normalized.observedDevSha !== normalized.originDevSha,
168
+ "self-dogfood requires dev to advance beyond the frozen origin",
169
+ );
170
+ requireEvidence(
171
+ normalized.cutCandidateSha !== normalized.candidateSha,
172
+ "self-dogfood requires a successor repair candidate",
173
+ );
200
174
  normalized.delivery = normalizeDelivery(input.delivery, normalized);
201
175
  return normalized;
202
176
  }
@@ -214,6 +188,10 @@ function deliveryCandidate(input, deliveryClass = "release") {
214
188
  closureRoot: input.closureRoot,
215
189
  dependencyRoot: input.dependencyRoot,
216
190
  toolchainRoot: input.toolchainRoot,
191
+ ...(input.environmentRoot ? { environmentRoot: input.environmentRoot } : {}),
192
+ ...(input.nativeCommand
193
+ ? { nativeCommandContract: createNativeCommandContract(input.nativeCommand) }
194
+ : {}),
217
195
  deliveryClass,
218
196
  priority: "ordinary",
219
197
  ...(input.releaseBlockerPriority
@@ -366,7 +344,7 @@ function runQueueCampaign(value, repaired, priorityClaim) {
366
344
  dependencyRoot: value.dependencyProofRoot,
367
345
  toolchainRoot: value.dependencyProofRoot,
368
346
  },
369
- "native-proof-required",
347
+ "non-native-fast",
370
348
  );
371
349
  queue = submitDevDeliveryCandidate(queue, ordinary, {
372
350
  now: at(value.recordedAt, 6),
@@ -388,6 +366,8 @@ function runQueueCampaign(value, repaired, priorityClaim) {
388
366
  closureRoot: value.delivery.mergeRoot,
389
367
  dependencyRoot: value.dependencyProofRoot,
390
368
  toolchainRoot: value.delivery.installedProductRoot,
369
+ environmentRoot: value.environmentRoot,
370
+ nativeCommand: value.nativeCommand,
391
371
  releaseBlockerPriority: priorityClaim,
392
372
  });
393
373
  const submitted = submitDevDeliveryCandidate(queue, blocker, {
@@ -426,7 +406,29 @@ function runQueueCampaign(value, repaired, priorityClaim) {
426
406
  "eligible release blocker did not receive bounded priority",
427
407
  );
428
408
  }
429
- const closed = closeDevDeliveryWarrant(selected.queue, selected.warrant, {
409
+ const nativeCommandRoot = createNativeCommandContract(value.nativeCommand).commandRoot;
410
+ const proof = createNativeQualificationProof({
411
+ ...selected.warrant,
412
+ repository: value.repository,
413
+ protectedBase: value.sourceBranch,
414
+ qualifiedBase: value.devBaseSha,
415
+ nativeCommandRoot,
416
+ nativeExecutionReceipt: value.nativeExecutionReceipt,
417
+ affectedPaths: [], shardEvidenceRoots: [],
418
+ qualifiedAt: at(value.recordedAt, 12),
419
+ });
420
+ const current = {
421
+ ...selected.warrant,
422
+ nativeCommandRoot,
423
+ currentBase: value.devBaseSha,
424
+ graphKnown: true, attributionComplete: true,
425
+ changedPaths: [], renames: [],
426
+ };
427
+ const reuseDecision = createNativeProofReuseDecision({ proof, current });
428
+ const qualified = qualifyDevDeliveryWarrant(selected.queue, selected.warrant, {
429
+ nativeProof: proof, reuseDecision, current, now: at(value.recordedAt, 12),
430
+ });
431
+ const closed = closeDevDeliveryWarrant(qualified.queue, qualified.warrant, {
430
432
  outcome: "merged",
431
433
  evidenceRoot: value.delivery.mergeRoot,
432
434
  now: at(value.recordedAt, 13),