@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,260 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ createNativeExecutionTransfer,
7
+ createNativeExecutionSealBinding,
8
+ createProviderFinalizerBoundary,
9
+ stageNativeExecutionTransfer,
10
+ verifyNativeExecutionTransfer,
11
+ verifyProviderFailureSettlementBinding,
12
+ } from "../packages/core/dev-delivery-process-boundary.js";
13
+
14
+ function flag(args, name, fallback = "") {
15
+ const index = args.indexOf(`--${name}`);
16
+ return index === -1 ? fallback : args[index + 1] || "";
17
+ }
18
+
19
+ function readJson(file, label) {
20
+ if (!file) throw new Error(`${label} is required`);
21
+ return JSON.parse(fs.readFileSync(file, "utf8"));
22
+ }
23
+
24
+ function writeJson(file, value) {
25
+ fs.mkdirSync(path.dirname(file), { recursive: true });
26
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
27
+ }
28
+
29
+ function warrantBinding(warrantResult) {
30
+ const warrant =
31
+ warrantResult.observation?.activeWarrant || warrantResult.warrant;
32
+ if (!warrant) throw new Error("active Warrant is missing from transfer");
33
+ return {
34
+ repository: warrant.repository || warrantResult.observation?.repository,
35
+ protectedBase:
36
+ warrant.protectedBase || warrantResult.observation?.protectedBase,
37
+ stateRoot:
38
+ warrantResult.observation?.stateRoot || warrantResult.after?.stateRoot,
39
+ candidateId: warrant.candidateId,
40
+ fencingToken: warrant.fencingToken,
41
+ generation: warrant.generation,
42
+ pullRequestNumber: warrant.pullRequestNumber,
43
+ sourceHead: warrant.sourceHead,
44
+ };
45
+ }
46
+
47
+ function nativeJobContext(args, environment) {
48
+ const outcome = flag(args, "outcome");
49
+ if (!["succeeded", "failed"].includes(outcome)) {
50
+ throw new Error("native job outcome must be succeeded or failed");
51
+ }
52
+ return {
53
+ schema: "kungfu.buildchain.native-job-context/v1",
54
+ workflowRunId: Number(environment.GITHUB_RUN_ID),
55
+ workflowRunAttempt: Number(environment.GITHUB_RUN_ATTEMPT),
56
+ job: flag(args, "job", "native-execution"),
57
+ runnerEnvironment: environment.BUILDCHAIN_RUNNER_ENVIRONMENT,
58
+ runnerName: environment.RUNNER_NAME,
59
+ runnerOs: environment.RUNNER_OS,
60
+ runnerArch: environment.RUNNER_ARCH,
61
+ outcome,
62
+ evidenceCompletedAt: new Date().toISOString(),
63
+ };
64
+ }
65
+
66
+ function recordNative(args, environment) {
67
+ const context = nativeJobContext(args, environment);
68
+ writeJson(flag(args, "output"), context);
69
+ return context;
70
+ }
71
+
72
+ function seal(args, environment) {
73
+ const directory = path.resolve(flag(args, "directory"));
74
+ const stagingDirectory = path.resolve(
75
+ flag(args, "staging-directory", `${directory}-transfer`),
76
+ );
77
+ const warrantResult = readJson(
78
+ path.join(directory, flag(args, "warrant", "warrant.json")),
79
+ "Warrant result",
80
+ );
81
+ const resultPath = flag(args, "result", "two-phase-native-result.json");
82
+ const failurePath = flag(args, "failure", "failure.json");
83
+ const succeeded = fs.existsSync(path.join(directory, resultPath));
84
+ const context = readJson(
85
+ path.join(directory, "native-job-context.json"),
86
+ "native job context",
87
+ );
88
+ const runtimeSelection = readJson(
89
+ path.join(directory, "runtime-selection.json"),
90
+ "runtime selection",
91
+ );
92
+ const isolation = createNativeExecutionSealBinding({
93
+ nativeContext: context,
94
+ sealJob: flag(args, "seal-job", "seal-native-execution"),
95
+ sealRunnerName: environment.RUNNER_NAME,
96
+ sealRunnerEnvironment: environment.BUILDCHAIN_RUNNER_ENVIRONMENT,
97
+ sealRunnerOs: environment.RUNNER_OS,
98
+ sealRunnerArch: environment.RUNNER_ARCH,
99
+ observedAt: new Date().toISOString(),
100
+ });
101
+ if (isolation.nativeOutcome !== (succeeded ? "succeeded" : "failed")) {
102
+ throw new Error("native job context outcome does not match raw evidence");
103
+ }
104
+ const files = [
105
+ "native-job-context.json",
106
+ "runtime-selection.json",
107
+ "warrant.json",
108
+ ];
109
+ let nativeProofRoot = null;
110
+ let nativeReuseDecisionRoot = null;
111
+ if (succeeded) {
112
+ const result = readJson(path.join(directory, resultPath), "native result");
113
+ nativeProofRoot = result.nativeProofRoot;
114
+ nativeReuseDecisionRoot = result.nativeReuseDecisionRoot;
115
+ files.push(resultPath, "native-proof.json", "native-reuse-decision.json");
116
+ } else {
117
+ if (!fs.existsSync(path.join(directory, failurePath))) {
118
+ throw new Error("failed native execution has no failure evidence");
119
+ }
120
+ if (
121
+ !fs.existsSync(path.join(directory, "failure-provider-settlement.json"))
122
+ ) {
123
+ throw new Error(
124
+ "failed native execution has no provider settlement binding",
125
+ );
126
+ }
127
+ files.push(failurePath, "failure-provider-settlement.json");
128
+ }
129
+ stageNativeExecutionTransfer({
130
+ sourceDirectory: directory,
131
+ stagingDirectory,
132
+ files,
133
+ });
134
+ const transfer = createNativeExecutionTransfer({
135
+ directory: stagingDirectory,
136
+ files,
137
+ outcome: succeeded ? "succeeded" : "failed",
138
+ producer: {
139
+ ...isolation.producer,
140
+ },
141
+ sealer: isolation.sealer,
142
+ runtime: {
143
+ repository: runtimeSelection.repository,
144
+ selector: runtimeSelection.selector,
145
+ resolvedSha: runtimeSelection.resolvedSha,
146
+ selectionRoot: flag(args, "runtime-selection-root"),
147
+ },
148
+ warrant: warrantBinding(warrantResult),
149
+ nativeProofRoot,
150
+ nativeReuseDecisionRoot,
151
+ completedAt: isolation.nativeCompletedAt,
152
+ sealedAt: isolation.sealedAt,
153
+ });
154
+ const output = flag(args, "output", "execution-transfer.json");
155
+ if (output !== "execution-transfer.json") {
156
+ throw new Error("native transfer manifest must be execution-transfer.json");
157
+ }
158
+ writeJson(path.join(stagingDirectory, output), transfer);
159
+ return verifyNativeExecutionTransfer(transfer, {
160
+ directory: stagingDirectory,
161
+ });
162
+ }
163
+
164
+ function verify(args, environment) {
165
+ const directory = path.resolve(flag(args, "directory"));
166
+ const transfer = verifyNativeExecutionTransfer(
167
+ readJson(
168
+ path.join(directory, flag(args, "transfer", "execution-transfer.json")),
169
+ "execution transfer",
170
+ ),
171
+ {
172
+ directory,
173
+ expected: {
174
+ "producer.workflowRunId": Number(environment.GITHUB_RUN_ID),
175
+ "producer.workflowRunAttempt": Number(environment.GITHUB_RUN_ATTEMPT),
176
+ "producer.job": flag(args, "native-job", "native-execution"),
177
+ "runtime.resolvedSha": flag(args, "runtime-sha"),
178
+ "runtime.selectionRoot": flag(args, "runtime-selection-root"),
179
+ "warrant.pullRequestNumber": Number(flag(args, "pull-request")),
180
+ "warrant.sourceHead": flag(args, "source-head"),
181
+ },
182
+ },
183
+ );
184
+ const jobs = readJson(flag(args, "jobs-readback"), "provider jobs readback");
185
+ const pullRequestReadback = readJson(
186
+ flag(args, "pull-request-readback"),
187
+ "pull request readback",
188
+ );
189
+ const baseRefReadback = readJson(
190
+ flag(args, "base-ref-readback"),
191
+ "protected base ref readback",
192
+ );
193
+ const boundary = createProviderFinalizerBoundary({
194
+ jobs,
195
+ executionTransfer: transfer,
196
+ workflowRunId: environment.GITHUB_RUN_ID,
197
+ workflowRunAttempt: environment.GITHUB_RUN_ATTEMPT,
198
+ nativeJobName: flag(
199
+ args,
200
+ "native-job-name",
201
+ "Credentialless native execution",
202
+ ),
203
+ sealJobName: flag(
204
+ args,
205
+ "seal-job-name",
206
+ "Credentialless native evidence seal",
207
+ ),
208
+ finalizerJobName: flag(
209
+ args,
210
+ "finalizer-job-name",
211
+ "Credentialed provider finalizer",
212
+ ),
213
+ finalizerRunnerName: environment.RUNNER_NAME,
214
+ finalizerRunnerEnvironment: environment.BUILDCHAIN_RUNNER_ENVIRONMENT,
215
+ pullRequestReadback,
216
+ baseRefReadback,
217
+ observedAt: new Date().toISOString(),
218
+ });
219
+ const boundaryOutput = flag(args, "output");
220
+ writeJson(boundaryOutput, boundary);
221
+ if (transfer.outcome === "failed") {
222
+ const settlement = verifyProviderFailureSettlementBinding(
223
+ boundary,
224
+ transfer,
225
+ );
226
+ writeJson(
227
+ flag(
228
+ args,
229
+ "failure-settlement-output",
230
+ path.join(
231
+ path.dirname(boundaryOutput),
232
+ "provider-failure-settlement.json",
233
+ ),
234
+ ),
235
+ settlement,
236
+ );
237
+ }
238
+ return boundary;
239
+ }
240
+
241
+ export function runDevDeliveryProcessBoundary(args, environment = process.env) {
242
+ const command = args[0];
243
+ if (command === "record-native")
244
+ return recordNative(args.slice(1), environment);
245
+ if (command === "seal") return seal(args.slice(1), environment);
246
+ if (command === "verify") return verify(args.slice(1), environment);
247
+ throw new Error("expected record-native, seal, or verify");
248
+ }
249
+
250
+ if (import.meta.url === `file://${process.argv[1]}`) {
251
+ try {
252
+ const result = runDevDeliveryProcessBoundary(process.argv.slice(2));
253
+ process.stdout.write(
254
+ `Dev delivery process boundary: ${result.transferRoot || result.boundaryRoot}\n`,
255
+ );
256
+ } catch (error) {
257
+ console.error(`buildchain dev boundary: ${error.message}`);
258
+ process.exit(1);
259
+ }
260
+ }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { classifyDevDeliveryDelta, createIntegrationDeliveryProof, createProjectCutReplayPlan, createProjectCutReplayProof, createSourceQualificationProof, verifyIntegrationDeliveryProof, verifyProjectCutReplayProof, verifySourceQualificationProof } from "../packages/core/dev-delivery-warrant.js";
4
+ import { classifyDevDeliveryDelta, createIntegrationDeliveryProof, createNativeProofReuseDecision, createNativeQualificationProof, createProjectCutReplayPlan, createProjectCutReplayProof, createSourceQualificationProof, verifyIntegrationDeliveryProof, verifyNativeProofReuseDecision, verifyNativeQualificationProof, verifyProjectCutReplayProof, verifySourceQualificationProof } from "../packages/core/dev-delivery-warrant.js";
5
5
 
6
6
  function flag(args, name, fallback = "") {
7
7
  const index = args.indexOf(`--${name}`);
@@ -49,9 +49,12 @@ export function devDeliveryProofCliOptions(args = [], environment = process.env)
49
49
  closureRoot: flag(rest, "closure-root", environment.BUILDCHAIN_DEV_DELIVERY_CLOSURE_ROOT),
50
50
  dependencyRoot: flag(rest, "dependency-root", environment.BUILDCHAIN_DEV_DELIVERY_DEPENDENCY_ROOT),
51
51
  toolchainRoot: flag(rest, "toolchain-root", environment.BUILDCHAIN_DEV_DELIVERY_TOOLCHAIN_ROOT),
52
+ environmentRoot: flag(rest, "environment-root", environment.BUILDCHAIN_DEV_DELIVERY_ENVIRONMENT_ROOT),
52
53
  affectedPaths: flag(rest, "affected-paths-json", environment.BUILDCHAIN_DEV_DELIVERY_AFFECTED_PATHS || "[]"),
53
54
  shardEvidenceRoots: flag(rest, "shard-evidence-roots-json", environment.BUILDCHAIN_DEV_DELIVERY_SHARD_EVIDENCE_ROOTS || "[]"),
54
55
  qualifiedAt: flag(rest, "qualified-at", environment.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_AT),
56
+ qualifiedBase: flag(rest, "qualified-base", environment.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_BASE),
57
+ nativeExecutionReceiptPath: flag(rest, "native-execution-receipt", environment.BUILDCHAIN_DEV_DELIVERY_NATIVE_EXECUTION_RECEIPT),
55
58
  sourceProofPath: flag(rest, "source-proof", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF),
56
59
  sourceProofRoot: flag(rest, "source-proof-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF_ROOT),
57
60
  warrantResultPath: flag(rest, "warrant-result", environment.BUILDCHAIN_DEV_DELIVERY_WARRANT_RESULT),
@@ -66,6 +69,8 @@ export function devDeliveryProofCliOptions(args = [], environment = process.env)
66
69
  pullRequestNumber: flag(rest, "pull-request", environment.BUILDCHAIN_DEV_DELIVERY_PR_NUMBER),
67
70
  changedPaths: flag(rest, "changed-paths-json", environment.BUILDCHAIN_DEV_DELIVERY_CHANGED_PATHS || "[]"),
68
71
  graphKnown: boolean(flag(rest, "graph-known", environment.BUILDCHAIN_DEV_DELIVERY_GRAPH_KNOWN), false),
72
+ attributionComplete: boolean(flag(rest, "attribution-complete", environment.BUILDCHAIN_DEV_DELIVERY_ATTRIBUTION_COMPLETE), false),
73
+ renames: flag(rest, "renames-json", environment.BUILDCHAIN_DEV_DELIVERY_RENAMES || "[]"),
69
74
  outputPath: flag(rest, "output", environment.BUILDCHAIN_DEV_DELIVERY_PROOF_OUTPUT || ".buildchain/dev-delivery/proof.json"),
70
75
  json: hasFlag(rest, "json"),
71
76
  };
@@ -117,6 +122,66 @@ export function runDevDeliveryProofCommand(options) {
117
122
  },
118
123
  });
119
124
  }
125
+ if (options.command === "native") {
126
+ return createNativeQualificationProof({
127
+ repository: options.repository,
128
+ protectedBase: options.protectedBase,
129
+ sourceIdentityRoot: options.sourceIdentityRoot,
130
+ sourcePatchRoot: options.sourcePatchRoot,
131
+ planRoot: options.planRoot,
132
+ closureRoot: options.closureRoot,
133
+ dependencyRoot: options.dependencyRoot,
134
+ toolchainRoot: options.toolchainRoot,
135
+ environmentRoot: options.environmentRoot,
136
+ sourceHead: options.sourceHead,
137
+ qualifiedBase: options.qualifiedBase,
138
+ nativeExecutionReceipt: jsonFile(options.nativeExecutionReceiptPath, "native execution receipt"),
139
+ affectedPaths: jsonList(options.affectedPaths, "affected paths"),
140
+ shardEvidenceRoots: jsonList(options.shardEvidenceRoots, "shard evidence roots", { required: true }),
141
+ qualifiedAt: options.qualifiedAt,
142
+ });
143
+ }
144
+ if (options.command === "verify-native") {
145
+ return verifyNativeQualificationProof(jsonFile(options.sourceProofPath, "native proof"));
146
+ }
147
+ if (options.command === "classify-native") {
148
+ return createNativeProofReuseDecision({
149
+ proof: jsonFile(options.sourceProofPath, "native proof"),
150
+ current: {
151
+ sourceIdentityRoot: options.sourceIdentityRoot,
152
+ sourcePatchRoot: options.sourcePatchRoot,
153
+ planRoot: options.planRoot,
154
+ closureRoot: options.closureRoot,
155
+ dependencyRoot: options.dependencyRoot,
156
+ toolchainRoot: options.toolchainRoot,
157
+ environmentRoot: options.environmentRoot,
158
+ currentBase: options.currentBase,
159
+ graphKnown: options.graphKnown,
160
+ attributionComplete: options.attributionComplete,
161
+ changedPaths: jsonList(options.changedPaths, "changed paths"),
162
+ renames: jsonList(options.renames, "renames"),
163
+ },
164
+ });
165
+ }
166
+ if (options.command === "verify-native-reuse") {
167
+ return verifyNativeProofReuseDecision(jsonFile(options.qualificationReceiptPath, "native reuse decision"), {
168
+ proof: jsonFile(options.sourceProofPath, "native proof"),
169
+ current: {
170
+ sourceIdentityRoot: options.sourceIdentityRoot,
171
+ sourcePatchRoot: options.sourcePatchRoot,
172
+ planRoot: options.planRoot,
173
+ closureRoot: options.closureRoot,
174
+ dependencyRoot: options.dependencyRoot,
175
+ toolchainRoot: options.toolchainRoot,
176
+ environmentRoot: options.environmentRoot,
177
+ currentBase: options.currentBase,
178
+ graphKnown: options.graphKnown,
179
+ attributionComplete: options.attributionComplete,
180
+ changedPaths: jsonList(options.changedPaths, "changed paths"),
181
+ renames: jsonList(options.renames, "renames"),
182
+ },
183
+ });
184
+ }
120
185
  if (options.command === "replay") {
121
186
  return createProjectCutReplayPlan({
122
187
  repository: options.repository,
@@ -168,7 +233,7 @@ export function runDevDeliveryProofCommand(options) {
168
233
  }
169
234
 
170
235
  function usage() {
171
- return "Usage:\n buildchain dev proof <source|verify-source|classify|replay|replay-proof|verify-replay|integration|verify-integration> [options] [--output FILE] [--json]\n";
236
+ return "Usage:\n buildchain dev proof <source|verify-source|classify|native|verify-native|classify-native|verify-native-reuse|replay|replay-proof|verify-replay|integration|verify-integration> [options] [--output FILE] [--json]\n";
172
237
  }
173
238
 
174
239
  async function main() {
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ runDevDeliveryProviderHeartbeat,
7
+ verifyDevDeliveryProviderHeartbeat,
8
+ } from "../packages/core/dev-delivery-provider-heartbeat.js";
9
+ import { runDevDeliveryCommand } from "./dev-delivery-warrant.mjs";
10
+
11
+ function flag(args, name, fallback = "") {
12
+ const index = args.indexOf(`--${name}`);
13
+ return index === -1 ? fallback : args[index + 1] || "";
14
+ }
15
+
16
+ function readJson(file, label) {
17
+ if (!file) throw new Error(`${label} is required`);
18
+ return JSON.parse(fs.readFileSync(path.resolve(file), "utf8"));
19
+ }
20
+
21
+ function writeJson(file, value) {
22
+ fs.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
23
+ fs.writeFileSync(path.resolve(file), `${JSON.stringify(value, null, 2)}\n`);
24
+ }
25
+
26
+ async function githubJson(apiUrl, token, requestPath, method = "GET") {
27
+ if (!token) throw new Error("provider heartbeat requires GitHub credentials");
28
+ const response = await fetch(
29
+ `${String(apiUrl).replace(/\/$/u, "")}${requestPath}`,
30
+ {
31
+ method,
32
+ headers: {
33
+ accept: "application/vnd.github+json",
34
+ authorization: `Bearer ${token}`,
35
+ "x-github-api-version": "2022-11-28",
36
+ },
37
+ },
38
+ );
39
+ const bytes = await response.text();
40
+ const body = bytes ? JSON.parse(bytes) : {};
41
+ if (!response.ok) {
42
+ throw new Error(
43
+ `GitHub provider ${requestPath} failed with ${response.status}: ${body.message || "unknown error"}`,
44
+ );
45
+ }
46
+ return body;
47
+ }
48
+
49
+ function repositoryParts(value) {
50
+ const match = String(value || "").match(/^([^/\s]+)\/([^/\s]+)$/u);
51
+ if (!match) throw new Error("repository must be owner/repo");
52
+ return { owner: match[1], repo: match[2] };
53
+ }
54
+
55
+ export async function coordinateExactProviderAttemptAfterHeartbeatLoss(
56
+ { repository, workflowRunId, workflowRunAttempt },
57
+ { readRun },
58
+ ) {
59
+ const run = await readRun(workflowRunId);
60
+ if (
61
+ Number(run.id) !== workflowRunId ||
62
+ Number(run.run_attempt) !== workflowRunAttempt ||
63
+ run.repository?.full_name !== repository ||
64
+ run.status !== "in_progress"
65
+ ) {
66
+ throw new Error(
67
+ "heartbeat loss cannot coordinate a different provider attempt",
68
+ );
69
+ }
70
+ return {
71
+ action: "exact-attempt-heartbeat-loss-observed",
72
+ workflowRunId,
73
+ workflowRunAttempt,
74
+ cancellation: "withheld-run-scoped-api",
75
+ nextAction:
76
+ "Wait for terminal readback of this exact attempt; never cancel the run-scoped coordinate.",
77
+ };
78
+ }
79
+
80
+ export async function runProviderHeartbeatCommand(options) {
81
+ const { owner, repo } = repositoryParts(options.repository);
82
+ return runDevDeliveryProviderHeartbeat(
83
+ {
84
+ admission: options.admission,
85
+ workflowRunId: options.workflowRunId,
86
+ workflowRunAttempt: options.workflowRunAttempt,
87
+ leaseSeconds: options.leaseSeconds,
88
+ heartbeatSeconds: options.heartbeatSeconds,
89
+ },
90
+ {
91
+ heartbeat: ({
92
+ expectedOldStateRoot,
93
+ fencingToken,
94
+ leaseGeneration,
95
+ leaseSeconds,
96
+ }) =>
97
+ runDevDeliveryCommand({
98
+ command: "heartbeat",
99
+ repository: options.repository,
100
+ branch: options.branch,
101
+ expectedOldStateRoot,
102
+ fencingToken,
103
+ leaseGeneration,
104
+ leaseSeconds,
105
+ execute: true,
106
+ token: options.token,
107
+ apiUrl: options.apiUrl,
108
+ }),
109
+ readJobs: () =>
110
+ githubJson(
111
+ options.apiUrl,
112
+ options.token,
113
+ `/repos/${owner}/${repo}/actions/runs/${options.workflowRunId}/attempts/${options.workflowRunAttempt}/jobs?per_page=100`,
114
+ ),
115
+ onHeartbeatLoss:
116
+ options.onHeartbeatLoss ||
117
+ (async ({ workflowRunId, workflowRunAttempt }) => {
118
+ await coordinateExactProviderAttemptAfterHeartbeatLoss(
119
+ {
120
+ repository: options.repository,
121
+ workflowRunId,
122
+ workflowRunAttempt,
123
+ },
124
+ {
125
+ readRun: (runId) =>
126
+ githubJson(
127
+ options.apiUrl,
128
+ options.token,
129
+ `/repos/${owner}/${repo}/actions/runs/${runId}`,
130
+ ),
131
+ },
132
+ );
133
+ }),
134
+ },
135
+ );
136
+ }
137
+
138
+ export async function verifyProviderHeartbeatCommand(options) {
139
+ const observed = await runDevDeliveryCommand({
140
+ command: "observe",
141
+ repository: options.repository,
142
+ branch: options.branch,
143
+ token: options.token,
144
+ apiUrl: options.apiUrl,
145
+ });
146
+ return verifyDevDeliveryProviderHeartbeat(options.receipt, {
147
+ admission: options.admission,
148
+ jobsReadback: options.jobsReadback,
149
+ liveObservation: observed.observation,
150
+ workflowRunId: options.workflowRunId,
151
+ workflowRunAttempt: options.workflowRunAttempt,
152
+ admissionJobName: options.admissionJobName,
153
+ heartbeatJobName: options.heartbeatJobName,
154
+ finalizerJobName: options.finalizerJobName,
155
+ observedAt: options.now,
156
+ });
157
+ }
158
+
159
+ async function main() {
160
+ const args = process.argv.slice(2);
161
+ const command = args[0] || "";
162
+ if (!new Set(["run", "verify"]).has(command)) {
163
+ process.stdout.write(
164
+ "Usage: dev-delivery-provider-heartbeat.mjs <run|verify> --admission FILE --repository owner/repo --branch dev/vN/vN.M --workflow-run-id N --workflow-run-attempt N --output FILE\n",
165
+ );
166
+ return;
167
+ }
168
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
169
+ const common = {
170
+ repository: flag(args, "repository", process.env.GITHUB_REPOSITORY),
171
+ branch: flag(args, "branch", process.env.GITHUB_BASE_REF),
172
+ admission: readJson(flag(args, "admission"), "admission"),
173
+ workflowRunId: Number(process.env.GITHUB_RUN_ID),
174
+ workflowRunAttempt: Number(process.env.GITHUB_RUN_ATTEMPT),
175
+ token,
176
+ apiUrl: process.env.GITHUB_API_URL || "https://api.github.com",
177
+ now: new Date().toISOString(),
178
+ admissionJobName: flag(
179
+ args,
180
+ "admission-job-name",
181
+ "Reserve exact delivery candidate",
182
+ ),
183
+ heartbeatJobName: flag(
184
+ args,
185
+ "heartbeat-job-name",
186
+ "Credentialed independent Warrant heartbeat",
187
+ ),
188
+ finalizerJobName: flag(
189
+ args,
190
+ "finalizer-job-name",
191
+ "Credentialed provider finalizer",
192
+ ),
193
+ };
194
+ const result =
195
+ command === "run"
196
+ ? await runProviderHeartbeatCommand({
197
+ ...common,
198
+ leaseSeconds: Number(flag(args, "lease-seconds", "3600")),
199
+ heartbeatSeconds: Number(flag(args, "heartbeat-seconds", "30")),
200
+ })
201
+ : await verifyProviderHeartbeatCommand({
202
+ ...common,
203
+ receipt: readJson(flag(args, "receipt"), "heartbeat receipt"),
204
+ jobsReadback: readJson(flag(args, "jobs-readback"), "jobs readback"),
205
+ });
206
+ writeJson(flag(args, "output"), result);
207
+ process.stdout.write(`${JSON.stringify(result)}\n`);
208
+ }
209
+
210
+ if (import.meta.url === `file://${process.argv[1]}`) {
211
+ main().catch((error) => {
212
+ console.error(`dev delivery provider heartbeat: ${error.message}`);
213
+ process.exit(1);
214
+ });
215
+ }