@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,573 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { devDeliveryContentRoot } from "../packages/core/dev-delivery-warrant.js";
7
+ import { runNativeWithHeartbeat } from "./dev-delivery-native-run.mjs";
8
+ import {
9
+ attributedGitHubBaseDelta,
10
+ classifyNativeProofAgainstCurrent,
11
+ GitHubTwoPhaseClient,
12
+ LocalTwoPhaseClient,
13
+ replayQualifiedNativeWarrant,
14
+ runNativeQualificationAttempt,
15
+ } from "./dev-delivery-two-phase-resume.mjs";
16
+ import { runDevDeliveryCommand } from "./dev-delivery-warrant.mjs";
17
+
18
+ export { GitHubTwoPhaseClient };
19
+
20
+ function flag(args, name, fallback = "") {
21
+ const index = args.indexOf(`--${name}`);
22
+ return index === -1 ? fallback : args[index + 1] || "";
23
+ }
24
+
25
+ function positiveInteger(value, label, fallback = 0) {
26
+ const parsed = Number(value || fallback);
27
+ if (!Number.isInteger(parsed) || parsed < 1) {
28
+ throw new Error(`${label} must be a positive integer`);
29
+ }
30
+ return parsed;
31
+ }
32
+
33
+ function jsonList(value, label) {
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(value || "[]");
37
+ } catch (cause) {
38
+ throw new Error(`${label} must be a JSON array: ${cause.message}`);
39
+ }
40
+ if (!Array.isArray(parsed)) throw new Error(`${label} must be a JSON array`);
41
+ return parsed;
42
+ }
43
+
44
+ function exactSha(value, label) {
45
+ const normalized = String(value || "")
46
+ .trim()
47
+ .toLowerCase();
48
+ if (!/^[0-9a-f]{40}$/u.test(normalized)) {
49
+ throw new Error(`${label} must be an exact lowercase Git SHA`);
50
+ }
51
+ return normalized;
52
+ }
53
+
54
+ function exactRoot(value, label) {
55
+ const normalized = String(value || "")
56
+ .trim()
57
+ .toLowerCase();
58
+ if (!/^sha256:[0-9a-f]{64}$/u.test(normalized)) {
59
+ throw new Error(`${label} must be a sha256 content root`);
60
+ }
61
+ return normalized;
62
+ }
63
+
64
+ function readJson(file, label) {
65
+ if (!file) throw new Error(`${label} is required`);
66
+ return JSON.parse(fs.readFileSync(file, "utf8"));
67
+ }
68
+
69
+ function writeJson(file, value) {
70
+ fs.mkdirSync(path.dirname(file), { recursive: true });
71
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
72
+ }
73
+
74
+ export function composeCandidate(candidateDirectory, expectedHead, baseSha) {
75
+ try {
76
+ execFileSync("git", ["-C", candidateDirectory, "merge", "--abort"], {
77
+ stdio: "ignore",
78
+ });
79
+ } catch {
80
+ // No merge was active.
81
+ }
82
+ execFileSync(
83
+ "git",
84
+ ["-C", candidateDirectory, "reset", "--hard", expectedHead],
85
+ {
86
+ stdio: "ignore",
87
+ },
88
+ );
89
+ execFileSync(
90
+ "git",
91
+ ["-C", candidateDirectory, "merge", "--no-commit", "--no-ff", baseSha],
92
+ {
93
+ stdio: "pipe",
94
+ env: {
95
+ ...process.env,
96
+ GIT_COMMITTER_NAME: "Buildchain Delivery Warrant",
97
+ GIT_COMMITTER_EMAIL:
98
+ "buildchain-delivery-warrant@users.noreply.github.com",
99
+ },
100
+ },
101
+ );
102
+ }
103
+
104
+ async function releaseFailedAttempt({
105
+ error,
106
+ options,
107
+ warrant,
108
+ warrantStateRoot,
109
+ nativeAttempts,
110
+ evidenceDirectory,
111
+ runCommand,
112
+ client,
113
+ }) {
114
+ const failure = {
115
+ schema: "kungfu.buildchain.two-phase-delivery-failure/v1",
116
+ pullRequestNumber: options.pullRequestNumber,
117
+ expectedHead: options.expectedHead,
118
+ fencingToken: warrant.fencingToken,
119
+ leaseGeneration: warrant.generation,
120
+ nativeAttempts,
121
+ reason: error.message,
122
+ workerTerminationProven: error.workerTerminationProven !== false,
123
+ };
124
+ const evidenceRoot = devDeliveryContentRoot(failure);
125
+ writeJson(path.join(evidenceDirectory, "failure.json"), {
126
+ ...failure,
127
+ evidenceRoot,
128
+ });
129
+ if (options.nativeOnly) {
130
+ writeJson(
131
+ path.join(evidenceDirectory, "failure-provider-settlement.json"),
132
+ {
133
+ schema: "kungfu.buildchain.two-phase-provider-settlement-required/v1",
134
+ evidenceRoot,
135
+ stateRoot: warrantStateRoot,
136
+ candidateId: warrant.candidateId,
137
+ fencingToken: warrant.fencingToken,
138
+ leaseGeneration: warrant.generation,
139
+ pullRequestNumber: options.pullRequestNumber,
140
+ sourceHead: options.expectedHead,
141
+ workerTerminationProven: error.workerTerminationProven !== false,
142
+ nextAction:
143
+ "Run the separate credentialed post-native finalizer; candidate code has exited and cannot ancestor provider authority.",
144
+ },
145
+ );
146
+ return;
147
+ }
148
+ if (error.workerTerminationProven === false) {
149
+ writeJson(path.join(evidenceDirectory, "failure-warrant-retained.json"), {
150
+ schema: "kungfu.buildchain.two-phase-delivery-retained-warrant/v1",
151
+ reason: error.message,
152
+ fencingToken: warrant.fencingToken,
153
+ leaseGeneration: warrant.generation,
154
+ workerTerminationProven: false,
155
+ nextAction:
156
+ "Prove the fenced native worker stopped before closing or releasing this provisional Warrant.",
157
+ });
158
+ return;
159
+ }
160
+ try {
161
+ const closed = await runCommand({
162
+ command: "close",
163
+ repository: options.repository,
164
+ branch: options.branch,
165
+ fencingToken: warrant.fencingToken,
166
+ leaseGeneration: warrant.generation,
167
+ outcome: "terminal-failure",
168
+ evidenceRoot,
169
+ reason: error.message,
170
+ execute: true,
171
+ token: options.token,
172
+ apiUrl: options.apiUrl,
173
+ });
174
+ writeJson(path.join(evidenceDirectory, "failure-close.json"), closed);
175
+ const nextCandidate = closed.receipt?.successorWake || null;
176
+ if (nextCandidate && options.wakeEventType) {
177
+ try {
178
+ await client.wake(options.wakeEventType, nextCandidate);
179
+ writeJson(path.join(evidenceDirectory, "wake-next.json"), {
180
+ schema: "kungfu.buildchain.dev-delivery-wake-receipt/v1",
181
+ eventType: options.wakeEventType,
182
+ candidateId: nextCandidate.candidateId,
183
+ pullRequestNumber: nextCandidate.pullRequestNumber,
184
+ sourceHead: nextCandidate.sourceHead,
185
+ action: "repository-dispatch-sent",
186
+ });
187
+ } catch (wakeError) {
188
+ writeJson(path.join(evidenceDirectory, "wake-next-error.json"), {
189
+ schema: "kungfu.buildchain.dev-delivery-wake-error/v1",
190
+ reason: wakeError.message,
191
+ candidateId: nextCandidate.candidateId,
192
+ nextAction:
193
+ "A later candidate submission or lease recovery will retry deterministic selection.",
194
+ });
195
+ }
196
+ }
197
+ } catch (closeError) {
198
+ writeJson(path.join(evidenceDirectory, "failure-close-error.json"), {
199
+ schema: "kungfu.buildchain.two-phase-delivery-close-error/v1",
200
+ reason: closeError.message,
201
+ nextAction:
202
+ "Recover the expired lease or close the current fenced generation; no merge admission was attempted.",
203
+ });
204
+ }
205
+ }
206
+
207
+ function retainedWarrantStateRoot(warrantResult) {
208
+ return warrantResult.observation?.stateRoot || warrantResult.after?.stateRoot;
209
+ }
210
+
211
+ export async function runTwoPhaseDelivery(options, dependencies = {}) {
212
+ options.environmentRoot = exactRoot(
213
+ options.environmentRoot,
214
+ "environmentRoot",
215
+ );
216
+ const client =
217
+ dependencies.client ||
218
+ (options.nativeOnly
219
+ ? new LocalTwoPhaseClient(options)
220
+ : new GitHubTwoPhaseClient(options));
221
+ const runNative = dependencies.runNative || runNativeWithHeartbeat;
222
+ const warrantResult = readJson(options.warrantResultPath, "Warrant result");
223
+ const warrant =
224
+ warrantResult.observation?.activeWarrant || warrantResult.warrant;
225
+ if (!warrant || !["provisional", "qualified"].includes(warrant.phase)) {
226
+ throw new Error(
227
+ "two-phase delivery requires an active provisional or qualified Warrant",
228
+ );
229
+ }
230
+ if (
231
+ Number(warrant.pullRequestNumber) !== options.pullRequestNumber ||
232
+ warrant.sourceHead !== options.expectedHead
233
+ ) {
234
+ throw new Error("provisional Warrant does not match the exact PR head");
235
+ }
236
+ const runCommand =
237
+ dependencies.runCommand ||
238
+ (options.nativeOnly
239
+ ? async ({ command }) => {
240
+ if (command !== "heartbeat")
241
+ throw new Error(
242
+ "credentialless native phase cannot mutate provider state",
243
+ );
244
+ return {
245
+ action: "immutable-admission-binding",
246
+ fencingToken: warrant.fencingToken,
247
+ leaseGeneration: warrant.generation,
248
+ };
249
+ }
250
+ : runDevDeliveryCommand);
251
+ for (const field of [
252
+ "sourceIdentityRoot",
253
+ "sourcePatchRoot",
254
+ "planRoot",
255
+ "closureRoot",
256
+ "dependencyRoot",
257
+ "toolchainRoot",
258
+ "environmentRoot",
259
+ ]) {
260
+ if (warrant[field] !== options[field]) {
261
+ throw new Error(`provisional Warrant ${field} does not match execution`);
262
+ }
263
+ }
264
+ if (
265
+ JSON.stringify([...(warrant.affectedPaths || [])].sort()) !==
266
+ JSON.stringify([...options.affectedPaths].sort()) ||
267
+ JSON.stringify([...(warrant.shardEvidenceRoots || [])].sort()) !==
268
+ JSON.stringify([...options.shardEvidenceRoots].sort())
269
+ ) {
270
+ throw new Error(
271
+ "provisional Warrant candidate evidence roots do not match execution",
272
+ );
273
+ }
274
+ if (
275
+ !warrant.nativeCommandContract ||
276
+ warrant.nativeCommandContract.command !== options.nativeCommand
277
+ ) {
278
+ throw new Error(
279
+ "provisional Warrant native command contract does not match execution",
280
+ );
281
+ }
282
+ options.nativeCommandRoot = warrant.nativeCommandContract.commandRoot;
283
+ const evidenceDirectory = path.resolve(options.evidenceDirectory);
284
+ fs.mkdirSync(evidenceDirectory, { recursive: true });
285
+ const qualifiedReplay = await replayQualifiedNativeWarrant({
286
+ warrant,
287
+ pullRequestNumber: options.pullRequestNumber,
288
+ expectedHead: options.expectedHead,
289
+ exactPullRequestHead: (...args) => client.exactPullRequestHead(...args),
290
+ });
291
+ if (qualifiedReplay) return qualifiedReplay;
292
+ let proof = options.nativeProofPath
293
+ ? readJson(options.nativeProofPath, "native proof")
294
+ : null;
295
+ let classified = proof
296
+ ? await classifyNativeProofAgainstCurrent(proof, options, client)
297
+ : null;
298
+ let nativeAttempts = 0;
299
+ try {
300
+ while (true) {
301
+ if (classified?.decision.reusable) {
302
+ await client.exactPullRequestHead(
303
+ options.pullRequestNumber,
304
+ options.expectedHead,
305
+ );
306
+ classified = await classifyNativeProofAgainstCurrent(
307
+ proof,
308
+ options,
309
+ client,
310
+ );
311
+ if (classified.decision.reusable) break;
312
+ }
313
+ if (options.finalizeOnly) break;
314
+ if (nativeAttempts >= 2) break;
315
+ if (!String(options.nativeCommand || "").trim()) {
316
+ throw new Error(
317
+ `native proof cannot be reused (${classified?.decision.reason || "not-supplied"}) and native-command is empty`,
318
+ );
319
+ }
320
+ nativeAttempts += 1;
321
+ proof = await runNativeQualificationAttempt({
322
+ options,
323
+ warrant,
324
+ attempt: nativeAttempts,
325
+ client,
326
+ runCommand,
327
+ runNative,
328
+ composeCandidate: dependencies.composeCandidate || composeCandidate,
329
+ writeEvidence: (name, value) =>
330
+ writeJson(path.join(evidenceDirectory, name), value),
331
+ });
332
+ classified = await classifyNativeProofAgainstCurrent(
333
+ proof,
334
+ options,
335
+ client,
336
+ );
337
+ }
338
+
339
+ if (!classified?.decision.reusable) {
340
+ throw new Error(
341
+ `native proof remains non-reusable after ${nativeAttempts} attempt(s): ${classified?.decision.reason || "unknown"}`,
342
+ );
343
+ }
344
+ writeJson(path.join(evidenceDirectory, "native-proof.json"), proof);
345
+ writeJson(
346
+ path.join(evidenceDirectory, "native-reuse-decision.json"),
347
+ classified.decision,
348
+ );
349
+ if (options.nativeOnly) {
350
+ return {
351
+ schema: "kungfu.buildchain.two-phase-delivery-result/v1",
352
+ ok: true,
353
+ outcome: "native-proof-ready",
354
+ nativeAttempts,
355
+ nativeProofRoot: proof.proofRoot,
356
+ nativeReuseDecisionRoot: classified.decision.decisionRoot,
357
+ qualificationReceiptRoot: null,
358
+ landingAuthority: false,
359
+ qualifiedWarrant: warrant,
360
+ };
361
+ }
362
+ const qualified = await runCommand({
363
+ command: "qualify",
364
+ repository: options.repository,
365
+ branch: options.branch,
366
+ fencingToken: warrant.fencingToken,
367
+ leaseGeneration: warrant.generation,
368
+ nativeProofPath: path.join(evidenceDirectory, "native-proof.json"),
369
+ nativeReuseDecisionPath: path.join(
370
+ evidenceDirectory,
371
+ "native-reuse-decision.json",
372
+ ),
373
+ currentBase: classified.current.currentBase,
374
+ graphKnown: classified.current.graphKnown,
375
+ attributionComplete: classified.current.attributionComplete,
376
+ changedPaths: JSON.stringify(classified.current.changedPaths),
377
+ renames: JSON.stringify(classified.current.renames),
378
+ execute: true,
379
+ token: options.token,
380
+ apiUrl: options.apiUrl,
381
+ });
382
+ writeJson(
383
+ path.join(evidenceDirectory, "qualified-warrant.json"),
384
+ qualified,
385
+ );
386
+ return {
387
+ schema: "kungfu.buildchain.two-phase-delivery-result/v1",
388
+ ok: true,
389
+ outcome: "qualified-warrant",
390
+ nativeAttempts,
391
+ nativeProofRoot: proof.proofRoot,
392
+ nativeReuseDecisionRoot: classified.decision.decisionRoot,
393
+ qualificationReceiptRoot: qualified.receiptRoot,
394
+ landingAuthority: false,
395
+ qualifiedWarrant: qualified.observation.activeWarrant,
396
+ };
397
+ } catch (error) {
398
+ await releaseFailedAttempt({
399
+ error,
400
+ options,
401
+ warrant,
402
+ warrantStateRoot: retainedWarrantStateRoot(warrantResult),
403
+ nativeAttempts,
404
+ evidenceDirectory,
405
+ runCommand,
406
+ client,
407
+ });
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ function cliOptions(args, environment = process.env) {
413
+ const leaseSeconds = positiveInteger(
414
+ flag(
415
+ args,
416
+ "lease-seconds",
417
+ environment.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS,
418
+ ),
419
+ "leaseSeconds",
420
+ 3600,
421
+ );
422
+ const heartbeatSeconds = positiveInteger(
423
+ flag(
424
+ args,
425
+ "heartbeat-seconds",
426
+ environment.BUILDCHAIN_DEV_DELIVERY_HEARTBEAT_SECONDS,
427
+ ),
428
+ "heartbeatSeconds",
429
+ Math.max(15, Math.floor(leaseSeconds / 3)),
430
+ );
431
+ if (heartbeatSeconds >= leaseSeconds) {
432
+ throw new Error("heartbeatSeconds must be less than leaseSeconds");
433
+ }
434
+ const nativeOnly = args.includes("--native-only");
435
+ const finalizeOnly = args.includes("--finalize-only");
436
+ if (nativeOnly && finalizeOnly)
437
+ throw new Error("native-only and finalize-only are mutually exclusive");
438
+ return {
439
+ repository: flag(args, "repository", environment.GITHUB_REPOSITORY),
440
+ branch: flag(
441
+ args,
442
+ "branch",
443
+ environment.BUILDCHAIN_DEV_DELIVERY_BRANCH || environment.GITHUB_BASE_REF,
444
+ ),
445
+ pullRequestNumber: positiveInteger(
446
+ flag(args, "pull-request", environment.BUILDCHAIN_DEV_DELIVERY_PR_NUMBER),
447
+ "pullRequestNumber",
448
+ ),
449
+ expectedHead: exactSha(
450
+ flag(
451
+ args,
452
+ "expected-head",
453
+ environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD,
454
+ ),
455
+ "expectedHead",
456
+ ),
457
+ sourceIdentityRoot: flag(
458
+ args,
459
+ "source-identity-root",
460
+ environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_IDENTITY_ROOT,
461
+ ),
462
+ sourcePatchRoot: flag(
463
+ args,
464
+ "source-patch-root",
465
+ environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PATCH_ROOT,
466
+ ),
467
+ planRoot: flag(
468
+ args,
469
+ "plan-root",
470
+ environment.BUILDCHAIN_DEV_DELIVERY_PLAN_ROOT,
471
+ ),
472
+ closureRoot: flag(
473
+ args,
474
+ "closure-root",
475
+ environment.BUILDCHAIN_DEV_DELIVERY_CLOSURE_ROOT,
476
+ ),
477
+ dependencyRoot: flag(
478
+ args,
479
+ "dependency-root",
480
+ environment.BUILDCHAIN_DEV_DELIVERY_DEPENDENCY_ROOT,
481
+ ),
482
+ toolchainRoot: flag(
483
+ args,
484
+ "toolchain-root",
485
+ environment.BUILDCHAIN_DEV_DELIVERY_TOOLCHAIN_ROOT,
486
+ ),
487
+ environmentRoot: flag(
488
+ args,
489
+ "environment-root",
490
+ environment.BUILDCHAIN_DEV_DELIVERY_ENVIRONMENT_ROOT,
491
+ ),
492
+ affectedPaths: jsonList(
493
+ flag(
494
+ args,
495
+ "affected-paths-json",
496
+ environment.BUILDCHAIN_DEV_DELIVERY_AFFECTED_PATHS,
497
+ ),
498
+ "affectedPaths",
499
+ ),
500
+ shardEvidenceRoots: jsonList(
501
+ flag(
502
+ args,
503
+ "shard-evidence-roots-json",
504
+ environment.BUILDCHAIN_DEV_DELIVERY_SHARD_EVIDENCE_ROOTS,
505
+ ),
506
+ "shardEvidenceRoots",
507
+ ),
508
+ nativeCommand: flag(
509
+ args,
510
+ "native-command",
511
+ environment.BUILDCHAIN_DEV_DELIVERY_NATIVE_COMMAND,
512
+ ),
513
+ nativeProofPath: flag(
514
+ args,
515
+ "native-proof",
516
+ environment.BUILDCHAIN_DEV_DELIVERY_NATIVE_PROOF,
517
+ ),
518
+ warrantResultPath: flag(
519
+ args,
520
+ "warrant-result",
521
+ environment.BUILDCHAIN_DEV_DELIVERY_WARRANT_RESULT,
522
+ ),
523
+ candidateDirectory: path.resolve(
524
+ flag(args, "candidate-directory", ".buildchain/candidate"),
525
+ ),
526
+ evidenceDirectory: path.resolve(
527
+ flag(args, "evidence-directory", ".buildchain/dev-delivery"),
528
+ ),
529
+ leaseSeconds,
530
+ heartbeatSeconds,
531
+ token: environment.GITHUB_TOKEN,
532
+ apiUrl: environment.GITHUB_API_URL || "https://api.github.com",
533
+ wakeEventType: flag(
534
+ args,
535
+ "wake-event-type",
536
+ environment.BUILDCHAIN_DEV_DELIVERY_WAKE_EVENT_TYPE ||
537
+ "buildchain-dev-delivery-wake",
538
+ ),
539
+ nativeOnly,
540
+ finalizeOnly,
541
+ };
542
+ }
543
+
544
+ async function main() {
545
+ const args = process.argv.slice(2);
546
+ if (args.length === 0 || args.includes("--help")) {
547
+ process.stdout.write(
548
+ "Usage: buildchain dev two-phase --repository owner/repo --branch dev/vN/vN.M --pull-request N --expected-head SHA --warrant-result FILE [--native-only|--finalize-only] [--native-proof FILE] [--native-command COMMAND]\n",
549
+ );
550
+ return;
551
+ }
552
+ const options = cliOptions(args);
553
+ const result = await runTwoPhaseDelivery(options);
554
+ writeJson(
555
+ path.join(
556
+ options.evidenceDirectory,
557
+ options.nativeOnly
558
+ ? "two-phase-native-result.json"
559
+ : "two-phase-result.json",
560
+ ),
561
+ result,
562
+ );
563
+ process.stdout.write(
564
+ `Two-phase Delivery Warrant: ${result.qualificationReceiptRoot}\n`,
565
+ );
566
+ }
567
+
568
+ if (import.meta.url === `file://${process.argv[1]}`) {
569
+ main().catch((error) => {
570
+ console.error(`buildchain dev two-phase: ${error.message}`);
571
+ process.exit(1);
572
+ });
573
+ }