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

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 (78) hide show
  1. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  2. package/architecture/internal-capabilities.json +94 -0
  3. package/architecture/maintainability-policy.json +75 -8
  4. package/architecture/v3-core-mechanism-inventory.json +2 -0
  5. package/architecture/v4-delivery-authority-parity.json +250 -0
  6. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  7. package/bin/buildchain.mjs +9 -1
  8. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  9. package/dist/site/agent-index.json +1 -0
  10. package/dist/site/artifact-schemas.json +2 -0
  11. package/dist/site/buildchain-contract.json +91 -8
  12. package/dist/site/buildchain-site.json +169 -18
  13. package/dist/site/capability-registry.json +2 -2
  14. package/dist/site/cli-registry.json +46 -2
  15. package/dist/site/kfd-claims.json +43 -9
  16. package/dist/site/kfd-upstream-aggregate.json +1 -1
  17. package/dist/site/manual-registry.json +3 -3
  18. package/dist/site/node-api-registry.json +1694 -165
  19. package/dist/site/page-registry.json +162 -11
  20. package/dist/site/public-surface-audit.json +367 -7
  21. package/dist/site/publication-registry.json +4 -4
  22. package/dist/site/release-provenance.json +2 -0
  23. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  24. package/dist/site/site-manifest.json +7 -7
  25. package/dist/site/workflow-registry.json +22 -6
  26. package/docs/MAP.md +1 -0
  27. package/docs/cli-reference.md +211 -13
  28. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  29. package/docs/dev-delivery-warrant.md +330 -38
  30. package/docs/node-api-reference.md +113 -53
  31. package/package.json +5 -1
  32. package/packages/core/buildchain-contract.js +3 -2
  33. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  34. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  35. package/packages/core/dev-delivery-authority-landing.js +461 -0
  36. package/packages/core/dev-delivery-authority-observation.js +48 -0
  37. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  38. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  39. package/packages/core/dev-delivery-authority-state.js +583 -0
  40. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  41. package/packages/core/dev-delivery-contract-surface.js +76 -0
  42. package/packages/core/dev-delivery-execution-failure.js +133 -0
  43. package/packages/core/dev-delivery-execution-transfer.js +572 -0
  44. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  45. package/packages/core/dev-delivery-landing-readback.js +598 -0
  46. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  47. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  48. package/packages/core/dev-delivery-native-execution.js +110 -0
  49. package/packages/core/dev-delivery-native-proof.js +546 -0
  50. package/packages/core/dev-delivery-process-boundary.js +550 -0
  51. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  52. package/packages/core/dev-delivery-provider-heartbeat.js +370 -0
  53. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  54. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  55. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  56. package/packages/core/dev-delivery-warrant-state.js +564 -0
  57. package/packages/core/dev-delivery-warrant.js +321 -368
  58. package/scripts/buildchain-cli-help.mjs +11 -2
  59. package/scripts/check-v4-floating-consumer-policy-contract.mjs +1 -1
  60. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  61. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  62. package/scripts/dev-delivery-authority.mjs +490 -0
  63. package/scripts/dev-delivery-native-run.mjs +177 -0
  64. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  65. package/scripts/dev-delivery-proof.mjs +67 -2
  66. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  67. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  68. package/scripts/dev-delivery-two-phase.mjs +573 -0
  69. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  70. package/scripts/dev-delivery-warrant-store.mjs +227 -0
  71. package/scripts/dev-delivery-warrant.mjs +227 -193
  72. package/scripts/dev-pr-auto-merge.mjs +1 -1
  73. package/scripts/dev-pr-delivery-warrant.mjs +10 -0
  74. package/scripts/generate-site-bundle.mjs +26 -4
  75. package/scripts/npm-publish-transaction.mjs +2 -2
  76. package/scripts/release-train-self-dogfood.mjs +94 -92
  77. package/scripts/site-capability-metadata.mjs +12 -0
  78. package/templates/native-dev-delivery.yml +143 -0
@@ -1,16 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { cancelQueuedDevDeliveryCandidate, closeDevDeliveryWarrant, createDevDeliveryQueue, heartbeatDevDeliveryWarrant, observeDevDeliveryQueue, recoverExpiredDevDeliveryWarrant, selectDevDeliveryWarrant, settleDevDeliveryTerminalEvent, submitDevDeliveryCandidate } from "../packages/core/dev-delivery-warrant.js";
4
+ import {
5
+ cancelQueuedDevDeliveryCandidate,
6
+ closeDevDeliveryWarrant,
7
+ createDevDeliveryQueue,
8
+ createNativeCommandContract,
9
+ heartbeatDevDeliveryWarrant,
10
+ observeDevDeliveryQueue,
11
+ qualifyDevDeliveryWarrant,
12
+ recoverExpiredDevDeliveryWarrant,
13
+ selectDevDeliveryWarrant,
14
+ settleDevDeliveryTerminalEvent,
15
+ submitDevDeliveryCandidate,
16
+ } from "../packages/core/dev-delivery-warrant.js";
5
17
  import { runV4DeliveryWarrantReadCandidate } from "../packages/core/v4-delivery-warrant-read-candidate.js";
6
18
 
7
- const STATE_PATH = "queue.json";
19
+ import { GitHubDevDeliveryStore } from "./dev-delivery-warrant-store.mjs";
20
+ import { devDeliveryCliOptions } from "./dev-delivery-warrant-options.mjs";
21
+ export { GitHubDevDeliveryStore, devDeliveryCliOptions };
22
+
8
23
  const STATE_REF_PREFIX = "buildchain/dev-delivery-warrant/";
9
24
 
10
25
  function text(value = "") {
11
26
  return String(value ?? "").trim();
12
27
  }
13
-
14
28
  function bool(value, fallback = false) {
15
29
  if (value === undefined || value === null || value === "") return fallback;
16
30
  if (typeof value === "boolean") return value;
@@ -19,33 +33,41 @@ function bool(value, fallback = false) {
19
33
 
20
34
  function positiveInteger(value, label, fallback = 0) {
21
35
  const parsed = Number(value ?? fallback);
22
- if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${label} must be a positive integer`);
36
+ if (!Number.isInteger(parsed) || parsed < 1)
37
+ throw new Error(`${label} must be a positive integer`);
23
38
  return parsed;
24
39
  }
25
40
 
26
41
  function exactRoot(value, label) {
27
42
  const normalized = text(value).toLowerCase();
28
- if (!/^sha256:[0-9a-f]{64}$/.test(normalized)) throw new Error(`${label} must be a sha256 content root`);
43
+ if (!/^sha256:[0-9a-f]{64}$/.test(normalized))
44
+ throw new Error(`${label} must be a sha256 content root`);
29
45
  return normalized;
30
46
  }
31
47
 
32
48
  function exactSha(value, label) {
33
49
  const normalized = text(value).toLowerCase();
34
- if (!/^[0-9a-f]{40}$/.test(normalized)) throw new Error(`${label} must be a 40-character Git SHA`);
50
+ if (!/^[0-9a-f]{40}$/.test(normalized))
51
+ throw new Error(`${label} must be a 40-character Git SHA`);
35
52
  return normalized;
36
53
  }
37
54
 
38
55
  function normalizeRepository(value) {
39
56
  const normalized = text(value);
40
57
  const match = normalized.match(/^([^/\s]+)\/([^/\s]+)$/);
41
- if (!match) throw new Error(`repository must be owner/repo, got ${normalized || "<empty>"}`);
58
+ if (!match)
59
+ throw new Error(
60
+ `repository must be owner/repo, got ${normalized || "<empty>"}`,
61
+ );
42
62
  return { owner: match[1], repo: match[2], fullName: normalized };
43
63
  }
44
64
 
45
65
  function normalizeBranch(value) {
46
66
  const normalized = text(value).replace(/^refs\/heads\//, "");
47
67
  if (!/^dev\/v\d+\/v\d+\.\d+$/.test(normalized)) {
48
- throw new Error(`branch must be dev/vN/vN.M, got ${normalized || "<empty>"}`);
68
+ throw new Error(
69
+ `branch must be dev/vN/vN.M, got ${normalized || "<empty>"}`,
70
+ );
49
71
  }
50
72
  return normalized;
51
73
  }
@@ -56,142 +78,122 @@ export function defaultDevDeliveryStateRef(branch) {
56
78
  }
57
79
 
58
80
  function normalizeStateRef(value, branch) {
59
- const normalized = text(value || defaultDevDeliveryStateRef(branch)).replace(/^refs\/heads\//, "");
60
- if (!normalized.startsWith(STATE_REF_PREFIX) || normalized.includes("..") || normalized.endsWith("/")) {
81
+ const normalized = text(value || defaultDevDeliveryStateRef(branch)).replace(
82
+ /^refs\/heads\//,
83
+ "",
84
+ );
85
+ if (
86
+ !normalized.startsWith(STATE_REF_PREFIX) ||
87
+ normalized.includes("..") ||
88
+ normalized.endsWith("/")
89
+ ) {
61
90
  throw new Error(`state ref must remain under ${STATE_REF_PREFIX}`);
62
91
  }
63
92
  return normalized;
64
93
  }
65
94
 
66
- function encodeRef(ref) {
67
- return ref.split("/").map(encodeURIComponent).join("/");
95
+ function warrantIdentity(queue, options) {
96
+ const active = queue.activeWarrant;
97
+ if (!active) throw new Error("no active Delivery Warrant");
98
+ const fencingToken = exactRoot(options.fencingToken, "fencingToken");
99
+ const generation = positiveInteger(
100
+ options.leaseGeneration,
101
+ "leaseGeneration",
102
+ );
103
+ return { candidateId: active.candidateId, fencingToken, generation };
68
104
  }
69
105
 
70
- function decodeBlob(blob) {
71
- if (blob.encoding !== "base64") throw new Error(`unsupported Git blob encoding ${blob.encoding || "<empty>"}`);
72
- return Buffer.from(String(blob.content || "").replace(/\s+/g, ""), "base64").toString("utf8");
106
+ function jsonFile(file, label) {
107
+ if (!file) throw new Error(`${label} is required`);
108
+ return JSON.parse(fs.readFileSync(file, "utf8"));
73
109
  }
74
110
 
75
- export class GitHubDevDeliveryStore {
76
- constructor({ repository, token, apiUrl = "https://api.github.com", fetchImpl = globalThis.fetch } = {}) {
77
- this.repository = normalizeRepository(repository);
78
- if (!fetchImpl) throw new Error("fetch is required");
79
- if (!token) throw new Error("GITHUB_TOKEN is required for the GitHub dev delivery store");
80
- this.token = token;
81
- this.apiUrl = apiUrl.replace(/\/+$/, "");
82
- this.fetch = fetchImpl;
83
- }
84
-
85
- async request(method, requestPath, body) {
86
- const response = await this.fetch(`${this.apiUrl}${requestPath}`, {
87
- method,
88
- headers: {
89
- accept: "application/vnd.github+json",
90
- authorization: `Bearer ${this.token}`,
91
- "content-type": "application/json",
92
- "x-github-api-version": "2022-11-28",
93
- },
94
- body: body === undefined ? undefined : JSON.stringify(body),
95
- });
96
- const raw = await response.text();
97
- const data = raw ? JSON.parse(raw) : null;
98
- if (!response.ok) {
99
- const error = new Error(data?.message || raw || `${method} ${requestPath} failed`);
100
- error.status = response.status;
101
- error.data = data;
102
- throw error;
103
- }
104
- return data;
105
- }
106
-
107
- async read({ stateRef, protectedBase, now }) {
108
- let ref;
109
- try {
110
- ref = await this.request("GET", `/repos/${this.repository.fullName}/git/ref/heads/${encodeRef(stateRef)}`);
111
- } catch (error) {
112
- if (error.status !== 404) throw error;
113
- return {
114
- exists: false,
115
- commitSha: "",
116
- queue: createDevDeliveryQueue({
117
- repository: this.repository.fullName,
118
- protectedBase,
119
- now,
120
- }),
121
- };
122
- }
123
- const commitSha = exactSha(ref?.object?.sha, "state ref commit");
124
- const readback = await this.readCommit(commitSha);
125
- return { exists: true, ...readback };
111
+ function jsonList(value, label) {
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(value || "[]");
115
+ } catch (cause) {
116
+ throw new Error(`${label} must be a JSON array: ${cause.message}`);
126
117
  }
118
+ if (!Array.isArray(parsed)) throw new Error(`${label} must be a JSON array`);
119
+ return parsed;
120
+ }
127
121
 
128
- async readCommit(commitShaInput) {
129
- const commitSha = exactSha(commitShaInput, "state commit");
130
- const commit = await this.request("GET", `/repos/${this.repository.fullName}/git/commits/${commitSha}`);
131
- const tree = await this.request("GET", `/repos/${this.repository.fullName}/git/trees/${commit.tree?.sha}`);
132
- const entry = (tree.tree || []).find((item) => item.path === STATE_PATH && item.type === "blob");
133
- if (!entry?.sha) throw new Error(`${commitSha} does not contain ${STATE_PATH}`);
134
- const blob = await this.request("GET", `/repos/${this.repository.fullName}/git/blobs/${entry.sha}`);
135
- const queue = JSON.parse(decodeBlob(blob));
136
- return { commitSha, queue };
122
+ function jsonObject(value, label) {
123
+ if (!value) return null;
124
+ let parsed;
125
+ try {
126
+ parsed = typeof value === "string" ? JSON.parse(value) : value;
127
+ } catch (cause) {
128
+ throw new Error(`${label} must be a JSON object: ${cause.message}`);
137
129
  }
138
-
139
- async write({ stateRef, queue, expectedCommitSha, expectedStateRoot, receiptRoot }) {
140
- if (queue.stateRoot === expectedStateRoot) throw new Error("state transition did not advance the queue root");
141
- const blob = await this.request("POST", `/repos/${this.repository.fullName}/git/blobs`, {
142
- content: `${JSON.stringify(queue, null, 2)}\n`,
143
- encoding: "utf-8",
144
- });
145
- const tree = await this.request("POST", `/repos/${this.repository.fullName}/git/trees`, {
146
- tree: [{ path: STATE_PATH, mode: "100644", type: "blob", sha: blob.sha }],
147
- });
148
- const commit = await this.request("POST", `/repos/${this.repository.fullName}/git/commits`, {
149
- message: `chore(dev-delivery): advance Warrant queue ${receiptRoot.slice(0, 20)}`,
150
- tree: tree.sha,
151
- parents: expectedCommitSha ? [expectedCommitSha] : [],
152
- });
153
- if (expectedCommitSha) {
154
- await this.request("PATCH", `/repos/${this.repository.fullName}/git/refs/heads/${encodeRef(stateRef)}`, { sha: commit.sha, force: false });
155
- } else {
156
- await this.request("POST", `/repos/${this.repository.fullName}/git/refs`, {
157
- ref: `refs/heads/${stateRef}`,
158
- sha: commit.sha,
159
- });
160
- }
161
- const readback = await this.readCommit(commit.sha);
162
- if (readback.commitSha !== commit.sha || readback.queue.stateRoot !== queue.stateRoot) {
163
- throw new Error("dev delivery state commit readback mismatch after expected-old update");
164
- }
165
- return { commitSha: commit.sha, stateRoot: readback.queue.stateRoot };
130
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
131
+ throw new Error(`${label} must be a JSON object`);
166
132
  }
167
- }
168
-
169
- function warrantIdentity(queue, options) {
170
- const active = queue.activeWarrant;
171
- if (!active) throw new Error("no active Delivery Warrant");
172
- const fencingToken = exactRoot(options.fencingToken, "fencingToken");
173
- const generation = positiveInteger(options.leaseGeneration, "leaseGeneration");
174
- return { candidateId: active.candidateId, fencingToken, generation };
133
+ return parsed;
175
134
  }
176
135
 
177
136
  function transitionFor(command, queue, options) {
178
137
  if (command === "submit") {
138
+ const nativeCommandContract = options.environmentRoot
139
+ ? createNativeCommandContract(options.nativeCommand)
140
+ : null;
141
+ if (
142
+ options.nativeCommandRoot &&
143
+ nativeCommandContract?.commandRoot !==
144
+ exactRoot(options.nativeCommandRoot, "nativeCommandRoot")
145
+ ) {
146
+ throw new Error(
147
+ "native command contract root does not match native-command",
148
+ );
149
+ }
179
150
  return submitDevDeliveryCandidate(
180
151
  queue,
181
152
  {
182
- pullRequestNumber: positiveInteger(options.pullRequestNumber, "pullRequestNumber"),
153
+ pullRequestNumber: positiveInteger(
154
+ options.pullRequestNumber,
155
+ "pullRequestNumber",
156
+ ),
183
157
  sourceHead: exactSha(options.sourceHead, "sourceHead"),
184
158
  assignmentRoot: exactRoot(options.assignmentRoot, "assignmentRoot"),
185
159
  initiativeRoot: exactRoot(options.initiativeRoot, "initiativeRoot"),
186
- sourceIdentityRoot: exactRoot(options.sourceIdentityRoot, "sourceIdentityRoot"),
160
+ sourceIdentityRoot: exactRoot(
161
+ options.sourceIdentityRoot,
162
+ "sourceIdentityRoot",
163
+ ),
187
164
  sourcePatchRoot: exactRoot(options.sourcePatchRoot, "sourcePatchRoot"),
188
165
  sourceProofRoot: exactRoot(options.sourceProofRoot, "sourceProofRoot"),
189
166
  planRoot: exactRoot(options.planRoot, "planRoot"),
190
167
  closureRoot: exactRoot(options.closureRoot, "closureRoot"),
191
168
  dependencyRoot: exactRoot(options.dependencyRoot, "dependencyRoot"),
192
169
  toolchainRoot: exactRoot(options.toolchainRoot, "toolchainRoot"),
170
+ ...(options.environmentRoot
171
+ ? {
172
+ environmentRoot: exactRoot(
173
+ options.environmentRoot,
174
+ "environmentRoot",
175
+ ),
176
+ }
177
+ : {}),
178
+ ...(nativeCommandContract ? { nativeCommandContract } : {}),
179
+ affectedPaths: jsonList(options.affectedPaths, "affected paths"),
180
+ shardEvidenceRoots: jsonList(
181
+ options.shardEvidenceRoots,
182
+ "shard evidence roots",
183
+ ),
184
+ sourceWorkflowRunId: options.sourceWorkflowRunId
185
+ ? positiveInteger(options.sourceWorkflowRunId, "sourceWorkflowRunId")
186
+ : 0,
193
187
  deliveryClass: options.deliveryClass,
194
188
  priority: options.priority || "ordinary",
189
+ ...(options.releaseBlockerPriority
190
+ ? {
191
+ releaseBlockerPriority: jsonObject(
192
+ options.releaseBlockerPriority,
193
+ "release blocker priority",
194
+ ),
195
+ }
196
+ : {}),
195
197
  },
196
198
  { now: options.now },
197
199
  );
@@ -208,7 +210,25 @@ function transitionFor(command, queue, options) {
208
210
  leaseSeconds: options.leaseSeconds,
209
211
  });
210
212
  }
211
- if (command === "recover") return recoverExpiredDevDeliveryWarrant(queue, { now: options.now });
213
+ if (command === "qualify") {
214
+ return qualifyDevDeliveryWarrant(queue, warrantIdentity(queue, options), {
215
+ nativeProof: jsonFile(options.nativeProofPath, "native proof"),
216
+ reuseDecision: jsonFile(
217
+ options.nativeReuseDecisionPath,
218
+ "native reuse decision",
219
+ ),
220
+ current: {
221
+ currentBase: options.currentBase,
222
+ graphKnown: options.graphKnown,
223
+ attributionComplete: options.attributionComplete,
224
+ changedPaths: jsonList(options.changedPaths, "changed paths"),
225
+ renames: jsonList(options.renames, "renames"),
226
+ },
227
+ now: options.now,
228
+ });
229
+ }
230
+ if (command === "recover")
231
+ return recoverExpiredDevDeliveryWarrant(queue, { now: options.now });
212
232
  if (command === "close") {
213
233
  return closeDevDeliveryWarrant(queue, warrantIdentity(queue, options), {
214
234
  outcome: options.outcome,
@@ -221,14 +241,24 @@ function transitionFor(command, queue, options) {
221
241
  return settleDevDeliveryTerminalEvent(
222
242
  queue,
223
243
  {
224
- pullRequestNumber: positiveInteger(options.pullRequestNumber, "pullRequestNumber"),
225
- sourceHead: exactSha(options.expectedSourceHead || options.sourceHead, "sourceHead"),
244
+ pullRequestNumber: positiveInteger(
245
+ options.pullRequestNumber,
246
+ "pullRequestNumber",
247
+ ),
248
+ sourceHead: exactSha(
249
+ options.expectedSourceHead || options.sourceHead,
250
+ "sourceHead",
251
+ ),
226
252
  fencingToken: options.fencingToken,
227
253
  leaseGeneration: options.leaseGeneration,
228
254
  outcome: options.outcome,
229
255
  eventAction: options.eventAction,
230
256
  evidenceRoot: options.evidenceRoot,
231
257
  reason: options.reason,
258
+ transferRoot: options.transferRoot,
259
+ finalizerBoundaryRoot: options.finalizerBoundaryRoot,
260
+ nativeJobId: options.nativeJobId,
261
+ sealJobId: options.sealJobId,
232
262
  },
233
263
  { now: options.now },
234
264
  );
@@ -238,9 +268,18 @@ function transitionFor(command, queue, options) {
238
268
  queue,
239
269
  {
240
270
  candidateId: exactRoot(options.candidateId, "candidateId"),
241
- pullRequestNumber: positiveInteger(options.pullRequestNumber, "pullRequestNumber"),
242
- expectedSourceHead: exactSha(options.expectedSourceHead, "expectedSourceHead"),
243
- observedSourceHead: exactSha(options.observedSourceHead, "observedSourceHead"),
271
+ pullRequestNumber: positiveInteger(
272
+ options.pullRequestNumber,
273
+ "pullRequestNumber",
274
+ ),
275
+ expectedSourceHead: exactSha(
276
+ options.expectedSourceHead,
277
+ "expectedSourceHead",
278
+ ),
279
+ observedSourceHead: exactSha(
280
+ options.observedSourceHead,
281
+ "observedSourceHead",
282
+ ),
244
283
  eventAction: options.eventAction,
245
284
  outcome: options.outcome,
246
285
  evidenceRoot: exactRoot(options.evidenceRoot, "evidenceRoot"),
@@ -254,17 +293,26 @@ function transitionFor(command, queue, options) {
254
293
 
255
294
  async function observeQueue(loaded, options) {
256
295
  const readMode = text(options.readMode || "v3").toLowerCase();
257
- if (!["v3", "v4"].includes(readMode)) throw new Error("readMode must be v3 or v4");
296
+ if (!["v3", "v4"].includes(readMode))
297
+ throw new Error("readMode must be v3 or v4");
258
298
  let observation = observeDevDeliveryQueue(loaded.queue, { now: options.now });
259
299
  let readCandidate;
260
300
  if (readMode === "v4") {
261
- const qualification = options.readQualification || JSON.parse(fs.readFileSync(options.readQualificationPath, "utf8"));
301
+ const qualification =
302
+ options.readQualification ||
303
+ JSON.parse(fs.readFileSync(options.readQualificationPath, "utf8"));
262
304
  const retain =
263
305
  options.retainReadEvidence ||
264
306
  (async (evidence) => {
265
- if (!options.readEvidenceOutput) throw new Error("readEvidenceOutput is required");
266
- fs.mkdirSync(path.dirname(options.readEvidenceOutput), { recursive: true });
267
- fs.writeFileSync(options.readEvidenceOutput, `${JSON.stringify(evidence, null, 2)}\n`);
307
+ if (!options.readEvidenceOutput)
308
+ throw new Error("readEvidenceOutput is required");
309
+ fs.mkdirSync(path.dirname(options.readEvidenceOutput), {
310
+ recursive: true,
311
+ });
312
+ fs.writeFileSync(
313
+ options.readEvidenceOutput,
314
+ `${JSON.stringify(evidence, null, 2)}\n`,
315
+ );
268
316
  return { receiptRoot: evidence.evidenceRoot };
269
317
  });
270
318
  readCandidate = await runV4DeliveryWarrantReadCandidate(loaded.queue, {
@@ -305,20 +353,35 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
305
353
  now: new Date(optionsInput.now || Date.now()).toISOString(),
306
354
  execute: bool(optionsInput.execute, false),
307
355
  };
356
+ if (
357
+ options.command === "submit" &&
358
+ options.execute &&
359
+ options.deliveryClass !== "non-native-fast"
360
+ ) {
361
+ exactRoot(options.environmentRoot, "environmentRoot");
362
+ }
308
363
  const store =
309
364
  clientInput ||
310
365
  new GitHubDevDeliveryStore({
311
366
  repository: options.repository,
312
367
  token: options.token || process.env.GITHUB_TOKEN,
313
- apiUrl: options.apiUrl || process.env.GITHUB_API_URL || "https://api.github.com",
368
+ apiUrl:
369
+ options.apiUrl ||
370
+ process.env.GITHUB_API_URL ||
371
+ "https://api.github.com",
314
372
  });
315
373
  let loaded = await store.read({
316
374
  stateRef: options.stateRef,
317
375
  protectedBase: options.branch,
318
376
  now: options.now,
319
377
  });
320
- if (options.expectedOldStateRoot && loaded.queue.stateRoot !== options.expectedOldStateRoot) {
321
- throw new Error(`expected-old state drift: ${loaded.queue.stateRoot} != ${options.expectedOldStateRoot}`);
378
+ if (
379
+ options.expectedOldStateRoot &&
380
+ loaded.queue.stateRoot !== options.expectedOldStateRoot
381
+ ) {
382
+ throw new Error(
383
+ `expected-old state drift: ${loaded.queue.stateRoot} != ${options.expectedOldStateRoot}`,
384
+ );
322
385
  }
323
386
  if (options.command === "observe") return observeQueue(loaded, options);
324
387
  const initialLoaded = loaded;
@@ -328,7 +391,9 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
328
391
  let concurrencyRecovery = null;
329
392
  if (options.execute && mutates) {
330
393
  if (changed.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
331
- throw new Error("transition receipt expected-old root does not match the loaded authority");
394
+ throw new Error(
395
+ "transition receipt expected-old root does not match the loaded authority",
396
+ );
332
397
  }
333
398
  try {
334
399
  write = await store.write({
@@ -339,15 +404,21 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
339
404
  receiptRoot: changed.receiptRoot,
340
405
  });
341
406
  } catch (error) {
342
- if (options.command !== "settle" || options.expectedOldStateRoot) throw error;
407
+ if (options.command !== "settle" || options.expectedOldStateRoot)
408
+ throw error;
343
409
  const latest = await store.read({
344
410
  stateRef: options.stateRef,
345
411
  protectedBase: options.branch,
346
412
  now: options.now,
347
413
  });
348
414
  const reconciled = transitionFor(options.command, latest.queue, options);
349
- const reconciledMutates = reconciled.queue.stateRoot !== latest.queue.stateRoot;
350
- if (reconciledMutates || reconciled.receipt.action !== "duplicate-terminal-event-noop") throw error;
415
+ const reconciledMutates =
416
+ reconciled.queue.stateRoot !== latest.queue.stateRoot;
417
+ if (
418
+ reconciledMutates ||
419
+ reconciled.receipt.action !== "duplicate-terminal-event-noop"
420
+ )
421
+ throw error;
351
422
  loaded = latest;
352
423
  changed = reconciled;
353
424
  mutates = false;
@@ -381,83 +452,46 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
381
452
  };
382
453
  }
383
454
 
384
- function flag(args, name, fallback = "") {
385
- const index = args.indexOf(`--${name}`);
386
- return index === -1 ? fallback : args[index + 1] || "";
387
- }
388
-
389
- function hasFlag(args, name) {
390
- return args.includes(`--${name}`);
391
- }
392
-
393
- export function devDeliveryCliOptions(args = [], environment = process.env) {
394
- const [command = "", ...rest] = args;
395
- return {
396
- command,
397
- repository: flag(rest, "repository", environment.GITHUB_REPOSITORY),
398
- branch: flag(rest, "branch", environment.BUILDCHAIN_DEV_DELIVERY_BRANCH || environment.GITHUB_BASE_REF),
399
- stateRef: flag(rest, "state-ref", environment.BUILDCHAIN_DEV_DELIVERY_STATE_REF),
400
- expectedOldStateRoot: flag(rest, "expected-old", environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_OLD),
401
- pullRequestNumber: flag(rest, "pull-request", environment.BUILDCHAIN_DEV_DELIVERY_PR_NUMBER),
402
- candidateId: flag(rest, "candidate-id", environment.BUILDCHAIN_DEV_DELIVERY_CANDIDATE_ID),
403
- sourceHead: flag(rest, "source-head", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD),
404
- expectedSourceHead: flag(rest, "expected-source-head", environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_SOURCE_HEAD),
405
- observedSourceHead: flag(rest, "observed-source-head", environment.BUILDCHAIN_DEV_DELIVERY_OBSERVED_SOURCE_HEAD),
406
- assignmentRoot: flag(rest, "assignment-root", environment.BUILDCHAIN_DEV_DELIVERY_ASSIGNMENT_ROOT),
407
- initiativeRoot: flag(rest, "initiative-root", environment.BUILDCHAIN_DEV_DELIVERY_INITIATIVE_ROOT),
408
- sourceIdentityRoot: flag(rest, "source-identity-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_IDENTITY_ROOT),
409
- sourcePatchRoot: flag(rest, "source-patch-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PATCH_ROOT),
410
- sourceProofRoot: flag(rest, "source-proof-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF_ROOT),
411
- planRoot: flag(rest, "plan-root", environment.BUILDCHAIN_DEV_DELIVERY_PLAN_ROOT),
412
- closureRoot: flag(rest, "closure-root", environment.BUILDCHAIN_DEV_DELIVERY_CLOSURE_ROOT),
413
- dependencyRoot: flag(rest, "dependency-root", environment.BUILDCHAIN_DEV_DELIVERY_DEPENDENCY_ROOT),
414
- toolchainRoot: flag(rest, "toolchain-root", environment.BUILDCHAIN_DEV_DELIVERY_TOOLCHAIN_ROOT),
415
- deliveryClass: flag(rest, "delivery-class", environment.BUILDCHAIN_DEV_DELIVERY_CLASS),
416
- priority: flag(rest, "priority", environment.BUILDCHAIN_DEV_DELIVERY_PRIORITY || "ordinary"),
417
- fencingToken: flag(rest, "fencing-token", environment.BUILDCHAIN_DEV_DELIVERY_FENCING_TOKEN),
418
- leaseGeneration: flag(rest, "lease-generation", environment.BUILDCHAIN_DEV_DELIVERY_LEASE_GENERATION),
419
- leaseSeconds: flag(rest, "lease-seconds", environment.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS),
420
- outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
421
- eventAction: flag(rest, "event-action", environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION),
422
- evidenceRoot: flag(rest, "evidence-root", environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT),
423
- reason: flag(rest, "reason", environment.BUILDCHAIN_DEV_DELIVERY_REASON),
424
- readMode: flag(rest, "read-mode", environment.BUILDCHAIN_V4_WARRANT_READ_MODE || "v3"),
425
- readQualificationPath: flag(rest, "read-qualification", environment.BUILDCHAIN_V4_WARRANT_READ_QUALIFICATION),
426
- readQualificationRoot: flag(rest, "read-qualification-root", environment.BUILDCHAIN_V4_WARRANT_READ_QUALIFICATION_ROOT),
427
- readTypescriptRevision: flag(rest, "read-typescript-revision", environment.BUILDCHAIN_V4_WARRANT_TYPESCRIPT_REVISION),
428
- readRustRevision: flag(rest, "read-rust-revision", environment.BUILDCHAIN_V4_WARRANT_RUST_REVISION),
429
- readValidatorVersion: flag(rest, "read-validator-version", environment.BUILDCHAIN_V4_WARRANT_VALIDATOR_VERSION),
430
- readTimeoutMs: flag(rest, "read-timeout-ms", environment.BUILDCHAIN_V4_WARRANT_READ_TIMEOUT_MS || "5000"),
431
- readEvidenceOutput: flag(rest, "read-evidence-output", environment.BUILDCHAIN_V4_WARRANT_READ_EVIDENCE || ".buildchain/dev-delivery/v4-read-evidence.json"),
432
- now: flag(rest, "now", environment.BUILDCHAIN_DEV_DELIVERY_NOW),
433
- outputPath: flag(rest, "output", environment.BUILDCHAIN_DEV_DELIVERY_OUTPUT || ".buildchain/dev-delivery/result.json"),
434
- execute: hasFlag(rest, "execute"),
435
- json: hasFlag(rest, "json"),
436
- };
437
- }
438
-
439
455
  function usage() {
440
- return "Usage:\n buildchain dev warrant <submit|select|heartbeat|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
456
+ return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
441
457
  }
442
458
 
443
459
  async function main() {
444
460
  const args = process.argv.slice(2);
445
- if (args.length === 0 || hasFlag(args, "help")) {
461
+ if (args.length === 0 || args.includes("--help")) {
446
462
  process.stdout.write(usage());
447
463
  return;
448
464
  }
449
465
  const options = devDeliveryCliOptions(args);
450
- if (!["submit", "select", "heartbeat", "recover", "close", "settle", "cancel-queued", "observe"].includes(options.command)) {
466
+ if (
467
+ ![
468
+ "submit",
469
+ "select",
470
+ "heartbeat",
471
+ "qualify",
472
+ "recover",
473
+ "close",
474
+ "settle",
475
+ "cancel-queued",
476
+ "observe",
477
+ ].includes(options.command)
478
+ ) {
451
479
  throw new Error(usage().trim());
452
480
  }
453
481
  const result = await runDevDeliveryCommand(options);
454
482
  fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
455
483
  fs.writeFileSync(options.outputPath, `${JSON.stringify(result, null, 2)}\n`);
456
- if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
484
+ if (options.json)
485
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
457
486
  else {
458
- process.stdout.write(`Buildchain dev delivery ${options.command}: ${result.receipt?.reason || result.mode}\n`);
459
- process.stdout.write(`State root: ${result.after?.stateRoot || result.observation.stateRoot}\n`);
460
- if (result.receiptRoot) process.stdout.write(`Receipt root: ${result.receiptRoot}\n`);
487
+ process.stdout.write(
488
+ `Buildchain dev delivery ${options.command}: ${result.receipt?.reason || result.mode}\n`,
489
+ );
490
+ process.stdout.write(
491
+ `State root: ${result.after?.stateRoot || result.observation.stateRoot}\n`,
492
+ );
493
+ if (result.receiptRoot)
494
+ process.stdout.write(`Receipt root: ${result.receiptRoot}\n`);
461
495
  process.stdout.write(`Result: ${options.outputPath}\n`);
462
496
  }
463
497
  }
@@ -70,7 +70,7 @@ function normalizeOptions(options = {}) {
70
70
  allowedHeadPrefixes: splitList(options.allowedHeadPrefixes, DEFAULT_ALLOWED_HEAD_PREFIXES),
71
71
  requiredChecks: splitList(options.requiredChecks, DEFAULT_REQUIRED_CHECKS),
72
72
  queueAdmissionContext: String(options.queueAdmissionContext || "").trim(),
73
- activeLeaseContext: String(options.activeLeaseContext || "").trim(),
73
+ activeLeaseContext: String(options.activeLeaseContext || (choiceOption(options.warrantMode, VALID_WARRANT_MODES, "off", "delivery Warrant mode") === "required" ? "Queue family lease/exact" : "")).trim(),
74
74
  requireApproval: boolOption(options.requireApproval, true),
75
75
  sameRepositoryOnly: boolOption(options.sameRepositoryOnly, true),
76
76
  maxMerges: intOption(options.maxMerges, 1),
@@ -52,6 +52,11 @@ function exactWarrantBinding({ result, warrant, candidate, options, pullRequest
52
52
  requireMatch(Number.isInteger(Number(warrant.generation)) && Number(warrant.generation) >= 1, "delivery-warrant-generation-invalid");
53
53
  requireMatch(Number.isFinite(Date.parse(warrant.expiresAt)) && Date.parse(warrant.expiresAt) > Date.now(), "delivery-warrant-expired");
54
54
  requireMatch(ROOT_PATTERN.test(String(result.receiptRoot || "")), "delivery-warrant-receipt-root-missing");
55
+ requireMatch((warrant.phase || "qualified") === "qualified", "delivery-warrant-not-qualified");
56
+ if (warrant.phase === "qualified") {
57
+ requireMatch(ROOT_PATTERN.test(String(warrant.nativeProofRoot || "")), "delivery-warrant-native-proof-missing");
58
+ requireMatch(ROOT_PATTERN.test(String(warrant.nativeProofReuseRoot || "")), "delivery-warrant-native-reuse-missing");
59
+ }
55
60
  }
56
61
 
57
62
  export function createDevPrAdmissionReceipt({ options, pr = {}, state, reason, readiness, decision = {}, queue = null, warrant = null, labels = [], nextAction }) {
@@ -120,6 +125,8 @@ export function readDeliveryWarrantResult(options, pullRequest) {
120
125
  pullRequestNumber: warrant.pullRequestNumber,
121
126
  sourceHead: warrant.sourceHead,
122
127
  sourceProofRoot: warrant.sourceProofRoot,
128
+ nativeProofRoot: warrant.nativeProofRoot || "",
129
+ nativeProofReuseRoot: warrant.nativeProofReuseRoot || "",
123
130
  };
124
131
  }
125
132
 
@@ -162,6 +169,9 @@ export async function verifyCurrentDeliveryWarrant(client, options, pullRequest,
162
169
  requireMatch(Number(active?.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-current-pr-mismatch");
163
170
  requireMatch(String(active?.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-current-head-mismatch");
164
171
  requireMatch(active?.sourceProofRoot === warrant.sourceProofRoot, "delivery-warrant-current-source-proof-mismatch");
172
+ requireMatch((active?.phase || "qualified") === "qualified", "delivery-warrant-current-not-qualified");
173
+ requireMatch((active?.nativeProofRoot || "") === (warrant.nativeProofRoot || ""), "delivery-warrant-current-native-proof-mismatch");
174
+ requireMatch((active?.nativeProofReuseRoot || "") === (warrant.nativeProofReuseRoot || ""), "delivery-warrant-current-native-reuse-mismatch");
165
175
  requireMatch(Number.isFinite(Date.parse(active?.expiresAt)) && Date.parse(active.expiresAt) > Date.now(), "delivery-warrant-current-expired");
166
176
  requireMatch(["selected", "proving", "waiting", "blocked", "qualified"].includes(candidate?.status), "delivery-warrant-current-candidate-not-selected");
167
177
  requireMatch(candidate?.sourceHead === active.sourceHead, "delivery-warrant-current-candidate-head-mismatch");