@kungfu-tech/buildchain 3.0.9-alpha.11 → 3.0.9-alpha.13

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.
@@ -10,9 +10,15 @@ import {
10
10
  } from "./dev-delivery-common.js";
11
11
 
12
12
  export const NATIVE_QUALIFICATION_PROOF_SCHEMA =
13
+ "kungfu.buildchain.native-qualification-proof/v3";
14
+ const LEGACY_NATIVE_QUALIFICATION_PROOF_V2_SCHEMA =
13
15
  "kungfu.buildchain.native-qualification-proof/v2";
14
16
  const LEGACY_NATIVE_QUALIFICATION_PROOF_SCHEMA =
15
17
  "kungfu.buildchain.native-qualification-proof/v1";
18
+ export const NATIVE_EXECUTION_BINDING_SCHEMA =
19
+ "kungfu.buildchain.native-execution-binding/v1";
20
+ export const NATIVE_EXECUTION_RECEIPT_SCHEMA =
21
+ "kungfu.buildchain.native-heartbeat-run-receipt/v2";
16
22
  export const NATIVE_PROOF_REUSE_DECISION_SCHEMA =
17
23
  "kungfu.buildchain.native-proof-reuse-decision/v1";
18
24
  export const DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA =
@@ -85,7 +91,94 @@ function rooted(body) {
85
91
  return { ...body, decisionRoot: devDeliveryContentRoot(body) };
86
92
  }
87
93
 
94
+ export function createNativeExecutionBinding(input = {}) {
95
+ return {
96
+ schema: NATIVE_EXECUTION_BINDING_SCHEMA,
97
+ repository: repository(input.repository),
98
+ protectedBase: protectedBase(input.protectedBase),
99
+ sourceHead: exactSha(input.sourceHead, "sourceHead"),
100
+ qualifiedBase: exactSha(input.qualifiedBase, "qualifiedBase"),
101
+ toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
102
+ environmentRoot: exactRoot(input.environmentRoot, "environmentRoot"),
103
+ };
104
+ }
105
+
106
+ export function createNativeExecutionReceipt(input = {}) {
107
+ const executionBinding = createNativeExecutionBinding(input.executionBinding);
108
+ const receipt = {
109
+ schema: NATIVE_EXECUTION_RECEIPT_SCHEMA,
110
+ outcome: input.outcome === "succeeded" ? "succeeded" : text(input.outcome),
111
+ commandRoot: exactRoot(input.commandRoot, "commandRoot"),
112
+ executionBinding,
113
+ executionBindingRoot: devDeliveryContentRoot(executionBinding),
114
+ startedAt: timestamp(input.startedAt, "startedAt"),
115
+ completedAt: timestamp(input.completedAt, "completedAt"),
116
+ heartbeatCount: Number(input.heartbeatCount),
117
+ };
118
+ if (receipt.outcome !== "succeeded") {
119
+ throw new Error("native execution receipt must record succeeded outcome");
120
+ }
121
+ if (!Number.isInteger(receipt.heartbeatCount) || receipt.heartbeatCount < 1) {
122
+ throw new Error("heartbeatCount must be a positive integer");
123
+ }
124
+ return { ...receipt, receiptRoot: devDeliveryContentRoot(receipt) };
125
+ }
126
+
127
+ export function verifyNativeExecutionReceipt(receiptInput, expected = {}) {
128
+ try {
129
+ const receipt = clone(receiptInput || {});
130
+ if (receipt.schema !== NATIVE_EXECUTION_RECEIPT_SCHEMA) {
131
+ return { ok: false, reason: "unsupported-schema" };
132
+ }
133
+ const receiptRoot = receipt.receiptRoot;
134
+ delete receipt.receiptRoot;
135
+ if (devDeliveryContentRoot(receipt) !== receiptRoot) {
136
+ return { ok: false, reason: "receipt-root-drift" };
137
+ }
138
+ const normalized = createNativeExecutionReceipt({
139
+ ...receipt,
140
+ executionBinding: receipt.executionBinding,
141
+ });
142
+ if (normalized.receiptRoot !== receiptRoot) {
143
+ return { ok: false, reason: "receipt-input-drift" };
144
+ }
145
+ for (const [field, value] of Object.entries(expected)) {
146
+ if (value !== undefined && receipt.executionBinding?.[field] !== value) {
147
+ return { ok: false, reason: `${field}-mismatch` };
148
+ }
149
+ }
150
+ return {
151
+ ok: true,
152
+ reason: "exact-native-execution-receipt",
153
+ receiptRoot,
154
+ executionBindingRoot: receipt.executionBindingRoot,
155
+ };
156
+ } catch (error) {
157
+ return { ok: false, reason: "invalid-receipt", error: error.message };
158
+ }
159
+ }
160
+
88
161
  export function createNativeQualificationProof(input = {}) {
162
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
163
+ const qualifiedBase = exactSha(input.qualifiedBase, "qualifiedBase");
164
+ const toolchainRoot = exactRoot(input.toolchainRoot, "toolchainRoot");
165
+ const environmentRoot = exactRoot(input.environmentRoot, "environmentRoot");
166
+ const executionReceipt = verifyNativeExecutionReceipt(
167
+ input.nativeExecutionReceipt,
168
+ {
169
+ repository: repository(input.repository),
170
+ protectedBase: protectedBase(input.protectedBase),
171
+ sourceHead,
172
+ qualifiedBase,
173
+ toolchainRoot,
174
+ environmentRoot,
175
+ },
176
+ );
177
+ if (!executionReceipt.ok) {
178
+ throw new Error(
179
+ `native execution receipt rejected: ${executionReceipt.reason}`,
180
+ );
181
+ }
89
182
  const body = {
90
183
  schema: NATIVE_QUALIFICATION_PROOF_SCHEMA,
91
184
  rootSemantics: NATIVE_QUALIFICATION_ROOT_SEMANTICS,
@@ -99,12 +192,15 @@ export function createNativeQualificationProof(input = {}) {
99
192
  planRoot: exactRoot(input.planRoot, "planRoot"),
100
193
  closureRoot: exactRoot(input.closureRoot, "closureRoot"),
101
194
  dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
102
- toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
103
- environmentRoot: exactRoot(input.environmentRoot, "environmentRoot"),
104
- qualifiedBase: exactSha(input.qualifiedBase, "qualifiedBase"),
195
+ toolchainRoot,
196
+ environmentRoot,
197
+ sourceHead,
198
+ qualifiedBase,
199
+ nativeExecutionBindingRoot: executionReceipt.executionBindingRoot,
200
+ nativeExecutionReceiptRoot: executionReceipt.receiptRoot,
105
201
  affectedPaths: normalizedPaths(input.affectedPaths || []),
106
202
  shardEvidenceRoots: exactRoots(
107
- input.shardEvidenceRoots,
203
+ [...(input.shardEvidenceRoots || []), executionReceipt.receiptRoot],
108
204
  "shardEvidenceRoots",
109
205
  ),
110
206
  qualifiedAt: timestamp(input.qualifiedAt, "qualifiedAt"),
@@ -122,6 +218,7 @@ export function verifyNativeQualificationProof(proofInput, expected = {}) {
122
218
  if (
123
219
  ![
124
220
  NATIVE_QUALIFICATION_PROOF_SCHEMA,
221
+ LEGACY_NATIVE_QUALIFICATION_PROOF_V2_SCHEMA,
125
222
  LEGACY_NATIVE_QUALIFICATION_PROOF_SCHEMA,
126
223
  ].includes(proof.schema)
127
224
  ) {
@@ -146,7 +243,38 @@ export function verifyNativeQualificationProof(proofInput, expected = {}) {
146
243
  }
147
244
  }
148
245
  if (proof.schema === NATIVE_QUALIFICATION_PROOF_SCHEMA) {
149
- createNativeQualificationProof(proof);
246
+ repository(proof.repository);
247
+ protectedBase(proof.protectedBase);
248
+ exactSha(proof.sourceHead, "sourceHead");
249
+ exactSha(proof.qualifiedBase, "qualifiedBase");
250
+ for (const field of [
251
+ "sourceIdentityRoot",
252
+ "sourcePatchRoot",
253
+ "planRoot",
254
+ "closureRoot",
255
+ "dependencyRoot",
256
+ "toolchainRoot",
257
+ "environmentRoot",
258
+ "nativeExecutionBindingRoot",
259
+ "nativeExecutionReceiptRoot",
260
+ ]) {
261
+ exactRoot(proof[field], field);
262
+ }
263
+ const expectedBindingRoot = devDeliveryContentRoot(
264
+ createNativeExecutionBinding(proof),
265
+ );
266
+ if (proof.nativeExecutionBindingRoot !== expectedBindingRoot) {
267
+ return { ok: false, reason: "native-execution-binding-root-drift" };
268
+ }
269
+ const shardEvidenceRoots = exactRoots(
270
+ proof.shardEvidenceRoots,
271
+ "shardEvidenceRoots",
272
+ );
273
+ if (!shardEvidenceRoots.includes(proof.nativeExecutionReceiptRoot)) {
274
+ return { ok: false, reason: "native-execution-receipt-unbound" };
275
+ }
276
+ normalizedPaths(proof.affectedPaths || []);
277
+ timestamp(proof.qualifiedAt, "qualifiedAt");
150
278
  } else {
151
279
  repository(proof.repository);
152
280
  protectedBase(proof.protectedBase);
@@ -205,8 +333,15 @@ function nativeReuseBody(proof, current) {
205
333
  requiredValidation: "full-native",
206
334
  };
207
335
  if (!verification.ok) return base;
336
+ if (proof.schema !== NATIVE_QUALIFICATION_PROOF_SCHEMA) {
337
+ return {
338
+ ...base,
339
+ reason: "native-execution-evidence-unbound",
340
+ };
341
+ }
208
342
 
209
343
  const semanticFields = [
344
+ "sourceHead",
210
345
  "sourceIdentityRoot",
211
346
  "sourcePatchRoot",
212
347
  "planRoot",
@@ -346,6 +481,7 @@ export function createDevDeliveryWarrantQualifier({
346
481
  const proofVerification = verifyNativeQualificationProof(nativeProof, {
347
482
  repository: before.repository,
348
483
  protectedBase: before.protectedBase,
484
+ sourceHead: active.sourceHead,
349
485
  sourceIdentityRoot: active.sourceIdentityRoot,
350
486
  sourcePatchRoot: active.sourcePatchRoot,
351
487
  planRoot: active.planRoot,
@@ -363,6 +499,7 @@ export function createDevDeliveryWarrantQualifier({
363
499
  proof: nativeProof,
364
500
  current: {
365
501
  ...current,
502
+ sourceHead: active.sourceHead,
366
503
  sourceIdentityRoot: active.sourceIdentityRoot,
367
504
  sourcePatchRoot: active.sourcePatchRoot,
368
505
  planRoot: active.planRoot,
@@ -7,7 +7,7 @@ import { compareReleaseBlockerPriority, normalizeReleaseBlockerPriorityClaim } f
7
7
  export { devDeliveryContentRoot } from "./dev-delivery-common.js";
8
8
  export { RELEASE_BLOCKER_PRIORITY_CLAIM_SCHEMA, createReleaseBlockerPriorityClaim } from "./release-blocker-priority.js";
9
9
  export { SOURCE_QUALIFICATION_PROOF_SCHEMA, PROJECT_CUT_REPLAY_PROOF_SCHEMA, INTEGRATION_DELIVERY_PROOF_SCHEMA, classifyDevDeliveryDelta, createIntegrationDeliveryProof, createProjectCutReplayPlan, createProjectCutReplayProof, createSourceQualificationProof, verifyIntegrationDeliveryProof, verifyProjectCutReplayProof, verifySourceQualificationProof } from "./dev-delivery-proof.js";
10
- export { DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA, NATIVE_QUALIFICATION_PROOF_SCHEMA, NATIVE_PROOF_REUSE_DECISION_SCHEMA, NATIVE_PROOF_BASE_DELTA_SCHEMA, createNativeProofBaseDelta, createNativeProofReuseDecision, createNativeQualificationProof, verifyNativeProofReuseDecision, verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
10
+ export { DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA, NATIVE_EXECUTION_BINDING_SCHEMA, NATIVE_EXECUTION_RECEIPT_SCHEMA, NATIVE_QUALIFICATION_PROOF_SCHEMA, NATIVE_PROOF_REUSE_DECISION_SCHEMA, NATIVE_PROOF_BASE_DELTA_SCHEMA, createNativeExecutionBinding, createNativeExecutionReceipt, createNativeProofBaseDelta, createNativeProofReuseDecision, createNativeQualificationProof, verifyNativeExecutionReceipt, verifyNativeProofReuseDecision, verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
11
11
  export { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA };
12
12
  export const DEV_DELIVERY_QUEUE_CONTRACT = "kungfu-buildchain-dev-delivery-warrant-queue";
13
13
  export const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
 
6
- import { devDeliveryContentRoot } from "../packages/core/dev-delivery-warrant.js";
6
+ import { createNativeExecutionBinding, createNativeExecutionReceipt, devDeliveryContentRoot } from "../packages/core/dev-delivery-warrant.js";
7
7
  import { runDevDeliveryCommand } from "./dev-delivery-warrant.mjs";
8
8
 
9
9
  function flag(args, name, fallback = "") {
@@ -46,6 +46,7 @@ export async function runNativeWithHeartbeat({
46
46
  if (typeof heartbeat !== "function") {
47
47
  throw new Error("heartbeat callback is required");
48
48
  }
49
+ const boundExecution = createNativeExecutionBinding(executionBinding);
49
50
  const startedAt = now();
50
51
  try {
51
52
  await heartbeat();
@@ -138,16 +139,14 @@ export async function runNativeWithHeartbeat({
138
139
  }
139
140
  await beat();
140
141
  const completedAt = now();
141
- const receipt = {
142
- schema: "kungfu.buildchain.native-heartbeat-run-receipt/v1",
142
+ return createNativeExecutionReceipt({
143
143
  outcome: "succeeded",
144
144
  commandRoot: devDeliveryContentRoot({ command }),
145
- executionBindingRoot: devDeliveryContentRoot(executionBinding),
145
+ executionBinding: boundExecution,
146
146
  startedAt,
147
147
  completedAt,
148
148
  heartbeatCount,
149
- };
150
- return { ...receipt, receiptRoot: devDeliveryContentRoot(receipt) };
149
+ });
151
150
  }
152
151
 
153
152
  async function main() {
@@ -163,6 +162,26 @@ async function main() {
163
162
  "command",
164
163
  process.env.BUILDCHAIN_DEV_DELIVERY_NATIVE_COMMAND,
165
164
  );
165
+ const sourceHead = flag(
166
+ args,
167
+ "source-head",
168
+ process.env.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD,
169
+ );
170
+ const qualifiedBase = flag(
171
+ args,
172
+ "qualified-base",
173
+ process.env.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_BASE,
174
+ );
175
+ const toolchainRoot = flag(
176
+ args,
177
+ "toolchain-root",
178
+ process.env.BUILDCHAIN_DEV_DELIVERY_TOOLCHAIN_ROOT,
179
+ );
180
+ const environmentRoot = flag(
181
+ args,
182
+ "environment-root",
183
+ process.env.BUILDCHAIN_DEV_DELIVERY_ENVIRONMENT_ROOT,
184
+ );
166
185
  const cwd = path.resolve(
167
186
  flag(args, "working-directory", process.env.GITHUB_WORKSPACE),
168
187
  );
@@ -210,6 +229,14 @@ async function main() {
210
229
  command,
211
230
  cwd,
212
231
  intervalMs: heartbeatSeconds * 1000,
232
+ executionBinding: {
233
+ repository,
234
+ protectedBase: branch,
235
+ sourceHead,
236
+ qualifiedBase,
237
+ toolchainRoot,
238
+ environmentRoot,
239
+ },
213
240
  heartbeat: async () => {
214
241
  await runDevDeliveryCommand({
215
242
  command: "heartbeat",
@@ -54,6 +54,7 @@ export function devDeliveryProofCliOptions(args = [], environment = process.env)
54
54
  shardEvidenceRoots: flag(rest, "shard-evidence-roots-json", environment.BUILDCHAIN_DEV_DELIVERY_SHARD_EVIDENCE_ROOTS || "[]"),
55
55
  qualifiedAt: flag(rest, "qualified-at", environment.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_AT),
56
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),
57
58
  sourceProofPath: flag(rest, "source-proof", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF),
58
59
  sourceProofRoot: flag(rest, "source-proof-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF_ROOT),
59
60
  warrantResultPath: flag(rest, "warrant-result", environment.BUILDCHAIN_DEV_DELIVERY_WARRANT_RESULT),
@@ -132,7 +133,9 @@ export function runDevDeliveryProofCommand(options) {
132
133
  dependencyRoot: options.dependencyRoot,
133
134
  toolchainRoot: options.toolchainRoot,
134
135
  environmentRoot: options.environmentRoot,
136
+ sourceHead: options.sourceHead,
135
137
  qualifiedBase: options.qualifiedBase,
138
+ nativeExecutionReceipt: jsonFile(options.nativeExecutionReceiptPath, "native execution receipt"),
136
139
  affectedPaths: jsonList(options.affectedPaths, "affected paths"),
137
140
  shardEvidenceRoots: jsonList(options.shardEvidenceRoots, "shard evidence roots", { required: true }),
138
141
  qualifiedAt: options.qualifiedAt,
@@ -1,4 +1,4 @@
1
- import { createNativeProofReuseDecision } from "../packages/core/dev-delivery-warrant.js";
1
+ import { createNativeProofReuseDecision, createNativeQualificationProof } from "../packages/core/dev-delivery-warrant.js";
2
2
 
3
3
  const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/u;
4
4
 
@@ -70,6 +70,7 @@ export async function classifyNativeProofAgainstCurrent(
70
70
  const currentBase = await client.baseSha(options.branch);
71
71
  const delta = await client.baseDelta(proof.qualifiedBase, currentBase);
72
72
  const current = {
73
+ sourceHead: options.expectedHead,
73
74
  sourceIdentityRoot: options.sourceIdentityRoot,
74
75
  sourcePatchRoot: options.sourcePatchRoot,
75
76
  planRoot: options.planRoot,
@@ -88,3 +89,81 @@ export async function classifyNativeProofAgainstCurrent(
88
89
  decision: createNativeProofReuseDecision({ proof, current }),
89
90
  };
90
91
  }
92
+
93
+ export async function runNativeQualificationAttempt({
94
+ options,
95
+ warrant,
96
+ attempt,
97
+ client,
98
+ runCommand,
99
+ runNative,
100
+ composeCandidate,
101
+ writeEvidence,
102
+ }) {
103
+ await client.exactPullRequestHead(
104
+ options.pullRequestNumber,
105
+ options.expectedHead,
106
+ );
107
+ const qualifiedBase = await client.baseSha(options.branch);
108
+ composeCandidate(
109
+ options.candidateDirectory,
110
+ options.expectedHead,
111
+ qualifiedBase,
112
+ );
113
+ const nativeExecutionReceipt = await runNative({
114
+ command: options.nativeCommand,
115
+ cwd: options.candidateDirectory,
116
+ intervalMs: options.heartbeatSeconds * 1000,
117
+ executionBinding: {
118
+ repository: options.repository,
119
+ protectedBase: options.branch,
120
+ sourceHead: options.expectedHead,
121
+ qualifiedBase,
122
+ toolchainRoot: options.toolchainRoot,
123
+ environmentRoot: options.environmentRoot,
124
+ },
125
+ heartbeat: async () => {
126
+ await runCommand({
127
+ command: "heartbeat",
128
+ repository: options.repository,
129
+ branch: options.branch,
130
+ fencingToken: warrant.fencingToken,
131
+ leaseGeneration: warrant.generation,
132
+ leaseSeconds: options.leaseSeconds,
133
+ execute: true,
134
+ token: options.token,
135
+ apiUrl: options.apiUrl,
136
+ });
137
+ },
138
+ });
139
+ writeEvidence(
140
+ `native-heartbeat-attempt-${attempt}.json`,
141
+ nativeExecutionReceipt,
142
+ );
143
+ await client.exactPullRequestHead(
144
+ options.pullRequestNumber,
145
+ options.expectedHead,
146
+ );
147
+ const proof = createNativeQualificationProof({
148
+ repository: options.repository,
149
+ protectedBase: options.branch,
150
+ sourceIdentityRoot: options.sourceIdentityRoot,
151
+ sourcePatchRoot: options.sourcePatchRoot,
152
+ planRoot: options.planRoot,
153
+ closureRoot: options.closureRoot,
154
+ dependencyRoot: options.dependencyRoot,
155
+ toolchainRoot: options.toolchainRoot,
156
+ environmentRoot: options.environmentRoot,
157
+ sourceHead: options.expectedHead,
158
+ qualifiedBase,
159
+ nativeExecutionReceipt,
160
+ affectedPaths: options.affectedPaths,
161
+ shardEvidenceRoots: [
162
+ ...options.shardEvidenceRoots,
163
+ nativeExecutionReceipt.receiptRoot,
164
+ ],
165
+ qualifiedAt: new Date().toISOString(),
166
+ });
167
+ writeEvidence("native-proof.json", proof);
168
+ return proof;
169
+ }
@@ -4,7 +4,6 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
 
6
6
  import {
7
- createNativeQualificationProof,
8
7
  devDeliveryContentRoot,
9
8
  } from "../packages/core/dev-delivery-warrant.js";
10
9
  import { runNativeWithHeartbeat } from "./dev-delivery-native-run.mjs";
@@ -12,6 +11,7 @@ import {
12
11
  attributedGitHubBaseDelta,
13
12
  classifyNativeProofAgainstCurrent,
14
13
  replayQualifiedNativeWarrant,
14
+ runNativeQualificationAttempt,
15
15
  } from "./dev-delivery-two-phase-resume.mjs";
16
16
  import { runDevDeliveryCommand } from "./dev-delivery-warrant.mjs";
17
17
 
@@ -49,6 +49,16 @@ function exactSha(value, label) {
49
49
  return normalized;
50
50
  }
51
51
 
52
+ function exactRoot(value, label) {
53
+ const normalized = String(value || "")
54
+ .trim()
55
+ .toLowerCase();
56
+ if (!/^sha256:[0-9a-f]{64}$/u.test(normalized)) {
57
+ throw new Error(`${label} must be a sha256 content root`);
58
+ }
59
+ return normalized;
60
+ }
61
+
52
62
  function readJson(file, label) {
53
63
  if (!file) throw new Error(`${label} is required`);
54
64
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -245,13 +255,13 @@ async function releaseFailedAttempt({
245
255
  }
246
256
 
247
257
  export async function runTwoPhaseDelivery(options, dependencies = {}) {
258
+ options.environmentRoot = exactRoot(
259
+ options.environmentRoot,
260
+ "environmentRoot",
261
+ );
248
262
  const client =
249
263
  dependencies.client ||
250
- new GitHubTwoPhaseClient({
251
- repository: options.repository,
252
- token: options.token,
253
- apiUrl: options.apiUrl,
254
- });
264
+ new GitHubTwoPhaseClient(options);
255
265
  const runCommand = dependencies.runCommand || runDevDeliveryCommand;
256
266
  const runNative = dependencies.runNative || runNativeWithHeartbeat;
257
267
  const warrantResult = readJson(options.warrantResultPath, "Warrant result");
@@ -270,7 +280,12 @@ export async function runTwoPhaseDelivery(options, dependencies = {}) {
270
280
  }
271
281
  const evidenceDirectory = path.resolve(options.evidenceDirectory);
272
282
  fs.mkdirSync(evidenceDirectory, { recursive: true });
273
- const qualifiedReplay = await replayQualifiedNativeWarrant({ warrant, pullRequestNumber: options.pullRequestNumber, expectedHead: options.expectedHead, exactPullRequestHead: (...args) => client.exactPullRequestHead(...args) });
283
+ const qualifiedReplay = await replayQualifiedNativeWarrant({
284
+ warrant,
285
+ pullRequestNumber: options.pullRequestNumber,
286
+ expectedHead: options.expectedHead,
287
+ exactPullRequestHead: (...args) => client.exactPullRequestHead(...args),
288
+ });
274
289
  if (qualifiedReplay) return qualifiedReplay;
275
290
  let proof = options.nativeProofPath
276
291
  ? readJson(options.nativeProofPath, "native proof")
@@ -287,7 +302,11 @@ export async function runTwoPhaseDelivery(options, dependencies = {}) {
287
302
  options.pullRequestNumber,
288
303
  options.expectedHead,
289
304
  );
290
- classified = await classifyNativeProofAgainstCurrent(proof, options, client);
305
+ classified = await classifyNativeProofAgainstCurrent(
306
+ proof,
307
+ options,
308
+ client,
309
+ );
291
310
  if (classified.decision.reusable) break;
292
311
  }
293
312
  if (nativeAttempts >= 2) break;
@@ -296,73 +315,23 @@ export async function runTwoPhaseDelivery(options, dependencies = {}) {
296
315
  `native proof cannot be reused (${classified?.decision.reason || "not-supplied"}) and native-command is empty`,
297
316
  );
298
317
  }
299
- await client.exactPullRequestHead(
300
- options.pullRequestNumber,
301
- options.expectedHead,
302
- );
303
- const qualifiedBase = await client.baseSha(options.branch);
304
- composeCandidate(
305
- options.candidateDirectory,
306
- options.expectedHead,
307
- qualifiedBase,
308
- );
309
318
  nativeAttempts += 1;
310
- const heartbeatReceipt = await runNative({
311
- command: options.nativeCommand,
312
- cwd: options.candidateDirectory,
313
- intervalMs: options.heartbeatSeconds * 1000,
314
- executionBinding: {
315
- repository: options.repository,
316
- protectedBase: options.branch,
317
- sourceHead: options.expectedHead,
318
- qualifiedBase,
319
- toolchainRoot: options.toolchainRoot,
320
- },
321
- heartbeat: async () => {
322
- await runCommand({
323
- command: "heartbeat",
324
- repository: options.repository,
325
- branch: options.branch,
326
- fencingToken: warrant.fencingToken,
327
- leaseGeneration: warrant.generation,
328
- leaseSeconds: options.leaseSeconds,
329
- execute: true,
330
- token: options.token,
331
- apiUrl: options.apiUrl,
332
- });
333
- },
319
+ proof = await runNativeQualificationAttempt({
320
+ options,
321
+ warrant,
322
+ attempt: nativeAttempts,
323
+ client,
324
+ runCommand,
325
+ runNative,
326
+ composeCandidate,
327
+ writeEvidence: (name, value) =>
328
+ writeJson(path.join(evidenceDirectory, name), value),
334
329
  });
335
- writeJson(
336
- path.join(
337
- evidenceDirectory,
338
- `native-heartbeat-attempt-${nativeAttempts}.json`,
339
- ),
340
- heartbeatReceipt,
330
+ classified = await classifyNativeProofAgainstCurrent(
331
+ proof,
332
+ options,
333
+ client,
341
334
  );
342
- await client.exactPullRequestHead(
343
- options.pullRequestNumber,
344
- options.expectedHead,
345
- );
346
- proof = createNativeQualificationProof({
347
- repository: options.repository,
348
- protectedBase: options.branch,
349
- sourceIdentityRoot: options.sourceIdentityRoot,
350
- sourcePatchRoot: options.sourcePatchRoot,
351
- planRoot: options.planRoot,
352
- closureRoot: options.closureRoot,
353
- dependencyRoot: options.dependencyRoot,
354
- toolchainRoot: options.toolchainRoot,
355
- environmentRoot: options.environmentRoot,
356
- qualifiedBase,
357
- affectedPaths: options.affectedPaths,
358
- shardEvidenceRoots: [
359
- ...options.shardEvidenceRoots,
360
- heartbeatReceipt.receiptRoot,
361
- ],
362
- qualifiedAt: new Date().toISOString(),
363
- });
364
- writeJson(path.join(evidenceDirectory, "native-proof.json"), proof);
365
- classified = await classifyNativeProofAgainstCurrent(proof, options, client);
366
335
  }
367
336
 
368
337
  if (!classified?.decision.reusable) {
@@ -295,6 +295,9 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
295
295
  now: new Date(optionsInput.now || Date.now()).toISOString(),
296
296
  execute: bool(optionsInput.execute, false),
297
297
  };
298
+ if (options.command === "submit" && options.execute && options.deliveryClass !== "non-native-fast") {
299
+ exactRoot(options.environmentRoot, "environmentRoot");
300
+ }
298
301
  const store =
299
302
  clientInput ||
300
303
  new GitHubDevDeliveryStore({
@@ -40,7 +40,7 @@ on:
40
40
  description: "Native toolchain root"
41
41
  required: true
42
42
  environment-root:
43
- description: "Native execution environment contract root"
43
+ description: "Exact native execution environment contract root bound before native spawn"
44
44
  required: true
45
45
  affected-paths-json:
46
46
  description: "JSON array representing the affected closure"