@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.
- package/dist/site/buildchain-contract.json +189 -9
- package/dist/site/buildchain-site.json +9 -9
- package/dist/site/kfd-claims.json +3 -3
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +166 -22
- package/dist/site/page-registry.json +4 -4
- package/dist/site/public-surface-audit.json +2 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +4 -4
- package/docs/dev-delivery-warrant.md +26 -6
- package/docs/node-api-reference.md +30 -25
- package/package.json +1 -1
- package/packages/core/buildchain-compatibility-proof.js +66 -0
- package/packages/core/dev-delivery-native-proof.js +142 -5
- package/packages/core/dev-delivery-warrant.js +1 -1
- package/scripts/dev-delivery-native-run.mjs +33 -6
- package/scripts/dev-delivery-proof.mjs +3 -0
- package/scripts/dev-delivery-two-phase-resume.mjs +80 -1
- package/scripts/dev-delivery-two-phase.mjs +41 -72
- package/scripts/dev-delivery-warrant.mjs +3 -0
- package/templates/native-dev-delivery.yml +1 -1
|
@@ -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
|
|
103
|
-
environmentRoot
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
schema: "kungfu.buildchain.native-heartbeat-run-receipt/v1",
|
|
142
|
+
return createNativeExecutionReceipt({
|
|
143
143
|
outcome: "succeeded",
|
|
144
144
|
commandRoot: devDeliveryContentRoot({ command }),
|
|
145
|
-
|
|
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({
|
|
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(
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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: "
|
|
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"
|