@kungfu-tech/buildchain 3.0.9-alpha.3 → 3.0.9-alpha.5
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/bin/buildchain.mjs +5 -1
- package/dist/site/buildchain-contract.json +9 -9
- package/dist/site/buildchain-site.json +58 -28
- package/dist/site/cli-registry.json +18 -2
- package/dist/site/kfd-claims.json +8 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +290 -49
- package/dist/site/page-registry.json +48 -18
- package/dist/site/public-surface-audit.json +122 -6
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +12 -6
- package/docs/aws-us-elastic-runner-burst-plane.md +14 -0
- package/docs/cli-reference.md +79 -13
- package/docs/dev-delivery-warrant.md +53 -19
- package/docs/kfd-support.md +7 -2
- package/docs/node-api-reference.md +59 -51
- package/docs/release-governance.md +5 -4
- package/docs/release-passport.md +8 -2
- package/docs/versioning.md +1 -0
- package/package.json +3 -2
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/dev-delivery-native-proof.js +333 -0
- package/packages/core/dev-delivery-warrant-settlement.js +5 -1
- package/packages/core/dev-delivery-warrant.js +40 -6
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/scripts/aws-macos-jit-controller-core.mjs +106 -2
- package/scripts/aws-macos-jit-controller.mjs +119 -1
- package/scripts/aws-macos-jit-source-rebind.mjs +194 -14
- package/scripts/buildchain-cli-help.mjs +6 -2
- package/scripts/check-internal-architecture.mjs +1 -0
- package/scripts/dev-delivery-native-run.mjs +187 -0
- package/scripts/dev-delivery-proof.mjs +54 -2
- package/scripts/dev-delivery-two-phase.mjs +598 -0
- package/scripts/dev-delivery-warrant.mjs +38 -3
- package/scripts/dev-pr-delivery-warrant.mjs +6 -1
- package/scripts/generate-channel-build-workflow.mjs +4 -0
- package/scripts/init-repo.mjs +16 -0
- package/scripts/release-candidate-tail-reseal.mjs +426 -0
- package/templates/native-dev-delivery.yml +82 -0
|
@@ -28,7 +28,137 @@ function priorOwnershipPlan(plan) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
function assertRunMatchesPolicy(policy, run, jobs, jobCount, artifactCount) {
|
|
32
|
+
if (policy === "unused") {
|
|
33
|
+
if (jobCount !== 0 || artifactCount !== 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`macOS campaign run ${run.id} already has jobs or artifacts`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (policy === "preflight-failure") {
|
|
41
|
+
const jobList = jobs.jobs || [];
|
|
42
|
+
const failedJobs = jobList.filter((job) => job.conclusion === "failure");
|
|
43
|
+
const nonFailedJobs = jobList.filter((job) => job.conclusion !== "failure");
|
|
44
|
+
const hasJitLabel = jobList.some((job) =>
|
|
45
|
+
(job.labels || []).some((label) =>
|
|
46
|
+
String(label.name || label).startsWith(MACOS_JIT_RUNNER_LABEL_PREFIX),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
if (
|
|
50
|
+
run.status !== "completed" ||
|
|
51
|
+
run.conclusion !== "failure" ||
|
|
52
|
+
jobCount === 0 ||
|
|
53
|
+
artifactCount === 0 ||
|
|
54
|
+
jobList.some((job) => job.status !== "completed") ||
|
|
55
|
+
failedJobs.length !== 1 ||
|
|
56
|
+
!String(failedJobs[0].name || "").endsWith(" / Trust gate") ||
|
|
57
|
+
nonFailedJobs.some((job) => job.conclusion !== "skipped") ||
|
|
58
|
+
hasJitLabel
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`macOS campaign run ${run.id} is not an exact preflight Trust gate failure`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
run.status !== "completed" ||
|
|
68
|
+
run.conclusion !== "failure" ||
|
|
69
|
+
jobCount === 0 ||
|
|
70
|
+
artifactCount === 0 ||
|
|
71
|
+
(jobs.jobs || []).some((job) => job.status !== "completed")
|
|
72
|
+
) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`macOS campaign run ${run.id} is not a terminal failed run with retained evidence`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function assertExactStartupFailure(run, jobCount, artifactCount) {
|
|
80
|
+
if (
|
|
81
|
+
run.status !== "completed" ||
|
|
82
|
+
run.conclusion !== "startup_failure" ||
|
|
83
|
+
jobCount !== 0 ||
|
|
84
|
+
artifactCount !== 0
|
|
85
|
+
) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`macOS campaign run ${run.id} is not an exact startup failure`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function classifyCampaignRun(plan, run) {
|
|
93
|
+
const startupFailure = plan.github.startupFailureRuns.find(
|
|
94
|
+
({ runId }) => runId === String(run.id),
|
|
95
|
+
);
|
|
96
|
+
const historicalTerminalFailure =
|
|
97
|
+
plan.github.historicalTerminalFailureRuns.find(
|
|
98
|
+
({ runId }) => runId === String(run.id),
|
|
99
|
+
);
|
|
100
|
+
const preflightFailure = plan.github.preflightFailureRunIds.includes(
|
|
101
|
+
String(run.id),
|
|
102
|
+
);
|
|
103
|
+
return {
|
|
104
|
+
startupFailure,
|
|
105
|
+
historicalTerminalFailure,
|
|
106
|
+
expectedSourceSha:
|
|
107
|
+
startupFailure?.sourceSha ||
|
|
108
|
+
historicalTerminalFailure?.sourceSha ||
|
|
109
|
+
plan.previousSource.sha,
|
|
110
|
+
classification: startupFailure
|
|
111
|
+
? "startup-failure"
|
|
112
|
+
: historicalTerminalFailure
|
|
113
|
+
? "historical-terminal-failure"
|
|
114
|
+
: preflightFailure
|
|
115
|
+
? "preflight-failure"
|
|
116
|
+
: plan.github.priorRunPolicy,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function exactReceiptInventory(receipts, classification, withSource = false) {
|
|
121
|
+
return receipts
|
|
122
|
+
.filter((receipt) => receipt.classification === classification)
|
|
123
|
+
.map((receipt) =>
|
|
124
|
+
withSource
|
|
125
|
+
? { runId: receipt.runId, sourceSha: receipt.sourceSha }
|
|
126
|
+
: receipt.runId,
|
|
127
|
+
)
|
|
128
|
+
.sort(
|
|
129
|
+
(left, right) =>
|
|
130
|
+
Number(withSource ? left.runId : left) -
|
|
131
|
+
Number(withSource ? right.runId : right),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function assertExactReceiptInventories(plan, receipts) {
|
|
136
|
+
if (plan.github.priorRunPolicy === "unused") return;
|
|
137
|
+
const inventories = [
|
|
138
|
+
["terminal-failure", plan.github.terminalFailureRunIds, false],
|
|
139
|
+
["preflight-failure", plan.github.preflightFailureRunIds, false],
|
|
140
|
+
["startup-failure", plan.github.startupFailureRuns, true],
|
|
141
|
+
[
|
|
142
|
+
"historical-terminal-failure",
|
|
143
|
+
plan.github.historicalTerminalFailureRuns,
|
|
144
|
+
true,
|
|
145
|
+
],
|
|
146
|
+
];
|
|
147
|
+
for (const [classification, expected, withSource] of inventories) {
|
|
148
|
+
const observed = exactReceiptInventory(
|
|
149
|
+
receipts,
|
|
150
|
+
classification,
|
|
151
|
+
withSource,
|
|
152
|
+
);
|
|
153
|
+
if (JSON.stringify(observed) !== JSON.stringify(expected)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`macOS campaign ${classification} inventory does not match the confirmed runs`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function campaignRunReceipts(plan) {
|
|
32
162
|
const branch = sourceRefName(plan.source.ref);
|
|
33
163
|
const response = ghJson(
|
|
34
164
|
[
|
|
@@ -46,7 +176,8 @@ function campaignRunsWithNoWork(plan) {
|
|
|
46
176
|
}
|
|
47
177
|
const receipts = [];
|
|
48
178
|
for (const run of matching) {
|
|
49
|
-
|
|
179
|
+
const classification = classifyCampaignRun(plan, run);
|
|
180
|
+
if (run.head_sha !== classification.expectedSourceSha) {
|
|
50
181
|
throw new Error(
|
|
51
182
|
`macOS campaign run ${run.id} is bound to an unexpected source`,
|
|
52
183
|
);
|
|
@@ -68,19 +199,37 @@ function campaignRunsWithNoWork(plan) {
|
|
|
68
199
|
const artifactCount = Number(
|
|
69
200
|
artifacts.total_count || (artifacts.artifacts || []).length || 0,
|
|
70
201
|
);
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
202
|
+
if (classification.startupFailure) {
|
|
203
|
+
assertExactStartupFailure(run, jobCount, artifactCount);
|
|
204
|
+
} else {
|
|
205
|
+
assertRunMatchesPolicy(
|
|
206
|
+
classification.historicalTerminalFailure
|
|
207
|
+
? "terminal-failure"
|
|
208
|
+
: plan.github.priorRunPolicy,
|
|
209
|
+
run,
|
|
210
|
+
jobs,
|
|
211
|
+
jobCount,
|
|
212
|
+
artifactCount,
|
|
74
213
|
);
|
|
75
214
|
}
|
|
76
215
|
receipts.push({
|
|
77
216
|
runId: String(run.id),
|
|
217
|
+
sourceSha: String(run.head_sha || ""),
|
|
218
|
+
classification: classification.classification,
|
|
78
219
|
status: String(run.status || "unknown"),
|
|
79
220
|
conclusion: String(run.conclusion || ""),
|
|
80
221
|
jobCount,
|
|
81
222
|
artifactCount,
|
|
223
|
+
artifacts: (artifacts.artifacts || []).map((artifact) => ({
|
|
224
|
+
id: String(artifact.id),
|
|
225
|
+
name: String(artifact.name || ""),
|
|
226
|
+
sizeInBytes: Number(artifact.size_in_bytes || 0),
|
|
227
|
+
digest: String(artifact.digest || ""),
|
|
228
|
+
expired: artifact.expired === true,
|
|
229
|
+
})),
|
|
82
230
|
});
|
|
83
231
|
}
|
|
232
|
+
assertExactReceiptInventories(plan, receipts);
|
|
84
233
|
return receipts;
|
|
85
234
|
}
|
|
86
235
|
|
|
@@ -286,8 +435,17 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
286
435
|
if (matchingRunners.length !== 0) {
|
|
287
436
|
throw new Error("macOS campaign already has a registered JIT runner");
|
|
288
437
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
438
|
+
const evidenceObjects = {};
|
|
439
|
+
const historicalTerminalSources =
|
|
440
|
+
plan.github.historicalTerminalFailureRuns.map(({ sourceSha }) => sourceSha);
|
|
441
|
+
const evidenceSources = [
|
|
442
|
+
...new Set([
|
|
443
|
+
plan.previousSource.sha,
|
|
444
|
+
...historicalTerminalSources,
|
|
445
|
+
plan.source.sha,
|
|
446
|
+
]),
|
|
447
|
+
];
|
|
448
|
+
for (const sourceSha of evidenceSources) {
|
|
291
449
|
const evidence = awsJson(
|
|
292
450
|
plan,
|
|
293
451
|
profile,
|
|
@@ -299,27 +457,44 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
299
457
|
"--prefix",
|
|
300
458
|
`macos/${sourceSha}/`,
|
|
301
459
|
"--max-keys",
|
|
302
|
-
"
|
|
460
|
+
"1000",
|
|
303
461
|
"--output",
|
|
304
462
|
"json",
|
|
305
463
|
],
|
|
306
464
|
`macOS campaign evidence inventory for ${sourceSha}`,
|
|
307
465
|
);
|
|
466
|
+
if (evidence.IsTruncated === true) {
|
|
467
|
+
throw new Error(
|
|
468
|
+
`macOS campaign source ${sourceSha} evidence inventory is incomplete`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const objects = (evidence.Contents || []).map((object) => ({
|
|
472
|
+
key: String(object.Key || ""),
|
|
473
|
+
size: Number(object.Size || 0),
|
|
474
|
+
etag: String(object.ETag || ""),
|
|
475
|
+
}));
|
|
308
476
|
const count = Number(
|
|
309
477
|
evidence.KeyCount || (evidence.Contents || []).length || 0,
|
|
310
478
|
);
|
|
311
|
-
|
|
479
|
+
const terminalEvidenceSource =
|
|
480
|
+
(sourceSha === plan.previousSource.sha &&
|
|
481
|
+
plan.github.priorRunPolicy === "terminal-failure") ||
|
|
482
|
+
historicalTerminalSources.includes(sourceSha);
|
|
483
|
+
if (
|
|
484
|
+
(!terminalEvidenceSource && count !== 0) ||
|
|
485
|
+
(terminalEvidenceSource && count === 0)
|
|
486
|
+
) {
|
|
312
487
|
throw new Error(
|
|
313
|
-
`macOS campaign source ${sourceSha}
|
|
488
|
+
`macOS campaign source ${sourceSha} bootstrap evidence does not match the rebind policy`,
|
|
314
489
|
);
|
|
315
490
|
}
|
|
316
|
-
|
|
491
|
+
evidenceObjects[sourceSha] = objects;
|
|
317
492
|
}
|
|
318
493
|
return {
|
|
319
|
-
priorRuns:
|
|
494
|
+
priorRuns: campaignRunReceipts(plan),
|
|
320
495
|
jitParameterCount: parameters.length,
|
|
321
496
|
runnerCount: matchingRunners.length,
|
|
322
|
-
|
|
497
|
+
evidenceObjects,
|
|
323
498
|
};
|
|
324
499
|
}
|
|
325
500
|
|
|
@@ -480,7 +655,12 @@ export function executeMacosJitSourceRebind(plan, { profile = "" } = {}) {
|
|
|
480
655
|
schemaVersion: 1,
|
|
481
656
|
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
482
657
|
kind: "campaign-source-rebind-result",
|
|
483
|
-
status:
|
|
658
|
+
status:
|
|
659
|
+
plan.github.priorRunPolicy === "terminal-failure"
|
|
660
|
+
? "rebound-after-terminal-failure-zero-allocation"
|
|
661
|
+
: plan.github.priorRunPolicy === "preflight-failure"
|
|
662
|
+
? "rebound-after-preflight-failure-zero-allocation"
|
|
663
|
+
: "rebound-zero-allocation",
|
|
484
664
|
repository: plan.repository,
|
|
485
665
|
campaign: plan.campaign,
|
|
486
666
|
previousSource: plan.previousSource,
|
|
@@ -129,11 +129,15 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
129
129
|
[--from-config | --workflow <required-workflow.yml>...] [--cwd <dir>]
|
|
130
130
|
[--check-response-timeout-minutes <n>]
|
|
131
131
|
[--max-entries-to-build <n>] [--apply]
|
|
132
|
-
buildchain dev warrant <submit|select|heartbeat|recover|close|cancel-queued|observe>
|
|
132
|
+
buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|cancel-queued|observe>
|
|
133
133
|
--repository <owner/repo> --branch <dev/vN/vN.M>
|
|
134
134
|
[--execute] [--output <file>] [--json]
|
|
135
|
-
buildchain dev proof <source|verify-source|classify|replay|integration|verify-integration>
|
|
135
|
+
buildchain dev proof <source|verify-source|classify|native|verify-native|classify-native|verify-native-reuse|replay|integration|verify-integration>
|
|
136
136
|
[--output <file>] [--json]
|
|
137
|
+
buildchain dev two-phase --repository <owner/repo> --branch <dev/vN/vN.M>
|
|
138
|
+
--pull-request <n> --expected-head <sha>
|
|
139
|
+
--warrant-result <file> [--native-proof <file>]
|
|
140
|
+
[--native-command <command>]
|
|
137
141
|
buildchain log <info|warn|error> --event <name> [--phase <phase>]
|
|
138
142
|
[--component <name>] [--source <name>] [--attribute key=value]...
|
|
139
143
|
[--path <jsonl>] [--json]
|
|
@@ -131,6 +131,7 @@ function repositoryJavaScriptFiles(root) {
|
|
|
131
131
|
.filter((file) => [".js", ".mjs", ".cjs"].includes(path.extname(file)))
|
|
132
132
|
.filter((file) => !file.startsWith("tests/"))
|
|
133
133
|
.filter((file) => !/(?:^|\/)tests?\//u.test(file))
|
|
134
|
+
.filter((file) => !file.startsWith(".buildchain/"))
|
|
134
135
|
.filter((file) => !/^actions\/[^/]+\/dist\//u.test(file))
|
|
135
136
|
.sort();
|
|
136
137
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { devDeliveryContentRoot } from "../packages/core/dev-delivery-warrant.js";
|
|
7
|
+
import { runDevDeliveryCommand } from "./dev-delivery-warrant.mjs";
|
|
8
|
+
|
|
9
|
+
function flag(args, name, fallback = "") {
|
|
10
|
+
const index = args.indexOf(`--${name}`);
|
|
11
|
+
return index === -1 ? fallback : args[index + 1] || "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function positiveInteger(value, label, fallback) {
|
|
15
|
+
const parsed = Number(value || fallback);
|
|
16
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
17
|
+
throw new Error(`${label} must be a positive integer`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function exactRoot(value, label) {
|
|
23
|
+
const normalized = String(value || "")
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase();
|
|
26
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(normalized)) {
|
|
27
|
+
throw new Error(`${label} must be a sha256 content root`);
|
|
28
|
+
}
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function runNativeWithHeartbeat({
|
|
33
|
+
command,
|
|
34
|
+
cwd = process.cwd(),
|
|
35
|
+
heartbeat,
|
|
36
|
+
executionBinding = {},
|
|
37
|
+
intervalMs = 30_000,
|
|
38
|
+
spawnImpl = spawn,
|
|
39
|
+
now = () => new Date().toISOString(),
|
|
40
|
+
} = {}) {
|
|
41
|
+
if (!String(command || "").trim()) {
|
|
42
|
+
throw new Error("native command is required");
|
|
43
|
+
}
|
|
44
|
+
if (typeof heartbeat !== "function") {
|
|
45
|
+
throw new Error("heartbeat callback is required");
|
|
46
|
+
}
|
|
47
|
+
const startedAt = now();
|
|
48
|
+
try {
|
|
49
|
+
await heartbeat();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`native heartbeat failed: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
let heartbeatCount = 1;
|
|
54
|
+
let heartbeatError = null;
|
|
55
|
+
let heartbeatRunning = false;
|
|
56
|
+
const child = spawnImpl("bash", ["-lc", command], {
|
|
57
|
+
cwd,
|
|
58
|
+
env: process.env,
|
|
59
|
+
stdio: "inherit",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const beat = async () => {
|
|
63
|
+
if (heartbeatRunning || heartbeatError) return;
|
|
64
|
+
heartbeatRunning = true;
|
|
65
|
+
try {
|
|
66
|
+
await heartbeat();
|
|
67
|
+
heartbeatCount += 1;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
heartbeatError = error;
|
|
70
|
+
child.kill("SIGTERM");
|
|
71
|
+
} finally {
|
|
72
|
+
heartbeatRunning = false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const timer = setInterval(beat, intervalMs);
|
|
76
|
+
const exit = await new Promise((resolve, reject) => {
|
|
77
|
+
child.once("error", reject);
|
|
78
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
79
|
+
}).finally(() => clearInterval(timer));
|
|
80
|
+
while (heartbeatRunning) {
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
82
|
+
}
|
|
83
|
+
if (heartbeatError) {
|
|
84
|
+
throw new Error(`native heartbeat failed: ${heartbeatError.message}`);
|
|
85
|
+
}
|
|
86
|
+
if (exit.code !== 0) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`native command failed with ${exit.signal || `exit ${exit.code}`}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
await beat();
|
|
92
|
+
const completedAt = now();
|
|
93
|
+
const receipt = {
|
|
94
|
+
schema: "kungfu.buildchain.native-heartbeat-run-receipt/v1",
|
|
95
|
+
outcome: "succeeded",
|
|
96
|
+
commandRoot: devDeliveryContentRoot({ command }),
|
|
97
|
+
executionBindingRoot: devDeliveryContentRoot(executionBinding),
|
|
98
|
+
startedAt,
|
|
99
|
+
completedAt,
|
|
100
|
+
heartbeatCount,
|
|
101
|
+
};
|
|
102
|
+
return { ...receipt, receiptRoot: devDeliveryContentRoot(receipt) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main() {
|
|
106
|
+
const args = process.argv.slice(2);
|
|
107
|
+
const repository = flag(args, "repository", process.env.GITHUB_REPOSITORY);
|
|
108
|
+
const branch = flag(
|
|
109
|
+
args,
|
|
110
|
+
"branch",
|
|
111
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_BRANCH || process.env.GITHUB_BASE_REF,
|
|
112
|
+
);
|
|
113
|
+
const command = flag(
|
|
114
|
+
args,
|
|
115
|
+
"command",
|
|
116
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_NATIVE_COMMAND,
|
|
117
|
+
);
|
|
118
|
+
const cwd = path.resolve(
|
|
119
|
+
flag(args, "working-directory", process.env.GITHUB_WORKSPACE),
|
|
120
|
+
);
|
|
121
|
+
const fencingToken = exactRoot(
|
|
122
|
+
flag(
|
|
123
|
+
args,
|
|
124
|
+
"fencing-token",
|
|
125
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_FENCING_TOKEN,
|
|
126
|
+
),
|
|
127
|
+
"fencingToken",
|
|
128
|
+
);
|
|
129
|
+
const leaseGeneration = positiveInteger(
|
|
130
|
+
flag(
|
|
131
|
+
args,
|
|
132
|
+
"lease-generation",
|
|
133
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_LEASE_GENERATION,
|
|
134
|
+
),
|
|
135
|
+
"leaseGeneration",
|
|
136
|
+
);
|
|
137
|
+
const leaseSeconds = positiveInteger(
|
|
138
|
+
flag(
|
|
139
|
+
args,
|
|
140
|
+
"lease-seconds",
|
|
141
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS,
|
|
142
|
+
),
|
|
143
|
+
"leaseSeconds",
|
|
144
|
+
3600,
|
|
145
|
+
);
|
|
146
|
+
const heartbeatSeconds = positiveInteger(
|
|
147
|
+
flag(
|
|
148
|
+
args,
|
|
149
|
+
"heartbeat-seconds",
|
|
150
|
+
process.env.BUILDCHAIN_DEV_DELIVERY_HEARTBEAT_SECONDS,
|
|
151
|
+
),
|
|
152
|
+
"heartbeatSeconds",
|
|
153
|
+
Math.max(15, Math.floor(leaseSeconds / 3)),
|
|
154
|
+
);
|
|
155
|
+
if (heartbeatSeconds >= leaseSeconds) {
|
|
156
|
+
throw new Error("heartbeatSeconds must be less than leaseSeconds");
|
|
157
|
+
}
|
|
158
|
+
const output = path.resolve(
|
|
159
|
+
flag(args, "output", ".buildchain/dev-delivery/native-heartbeat-run.json"),
|
|
160
|
+
);
|
|
161
|
+
const result = await runNativeWithHeartbeat({
|
|
162
|
+
command,
|
|
163
|
+
cwd,
|
|
164
|
+
intervalMs: heartbeatSeconds * 1000,
|
|
165
|
+
heartbeat: async () => {
|
|
166
|
+
await runDevDeliveryCommand({
|
|
167
|
+
command: "heartbeat",
|
|
168
|
+
repository,
|
|
169
|
+
branch,
|
|
170
|
+
fencingToken,
|
|
171
|
+
leaseGeneration,
|
|
172
|
+
leaseSeconds,
|
|
173
|
+
execute: true,
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
178
|
+
fs.writeFileSync(output, `${JSON.stringify(result, null, 2)}\n`);
|
|
179
|
+
process.stdout.write(`Native heartbeat run: ${result.receiptRoot}\n`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
183
|
+
main().catch((error) => {
|
|
184
|
+
console.error(`buildchain dev native: ${error.message}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -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}`);
|
|
@@ -52,6 +52,7 @@ export function devDeliveryProofCliOptions(args = [], environment = process.env)
|
|
|
52
52
|
affectedPaths: flag(rest, "affected-paths-json", environment.BUILDCHAIN_DEV_DELIVERY_AFFECTED_PATHS || "[]"),
|
|
53
53
|
shardEvidenceRoots: flag(rest, "shard-evidence-roots-json", environment.BUILDCHAIN_DEV_DELIVERY_SHARD_EVIDENCE_ROOTS || "[]"),
|
|
54
54
|
qualifiedAt: flag(rest, "qualified-at", environment.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_AT),
|
|
55
|
+
qualifiedBase: flag(rest, "qualified-base", environment.BUILDCHAIN_DEV_DELIVERY_QUALIFIED_BASE),
|
|
55
56
|
sourceProofPath: flag(rest, "source-proof", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF),
|
|
56
57
|
sourceProofRoot: flag(rest, "source-proof-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF_ROOT),
|
|
57
58
|
warrantResultPath: flag(rest, "warrant-result", environment.BUILDCHAIN_DEV_DELIVERY_WARRANT_RESULT),
|
|
@@ -117,6 +118,57 @@ export function runDevDeliveryProofCommand(options) {
|
|
|
117
118
|
},
|
|
118
119
|
});
|
|
119
120
|
}
|
|
121
|
+
if (options.command === "native") {
|
|
122
|
+
return createNativeQualificationProof({
|
|
123
|
+
repository: options.repository,
|
|
124
|
+
protectedBase: options.protectedBase,
|
|
125
|
+
sourceIdentityRoot: options.sourceIdentityRoot,
|
|
126
|
+
sourcePatchRoot: options.sourcePatchRoot,
|
|
127
|
+
planRoot: options.planRoot,
|
|
128
|
+
closureRoot: options.closureRoot,
|
|
129
|
+
dependencyRoot: options.dependencyRoot,
|
|
130
|
+
toolchainRoot: options.toolchainRoot,
|
|
131
|
+
qualifiedBase: options.qualifiedBase,
|
|
132
|
+
affectedPaths: jsonList(options.affectedPaths, "affected paths"),
|
|
133
|
+
shardEvidenceRoots: jsonList(options.shardEvidenceRoots, "shard evidence roots", { required: true }),
|
|
134
|
+
qualifiedAt: options.qualifiedAt,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (options.command === "verify-native") {
|
|
138
|
+
return verifyNativeQualificationProof(jsonFile(options.sourceProofPath, "native proof"));
|
|
139
|
+
}
|
|
140
|
+
if (options.command === "classify-native") {
|
|
141
|
+
return createNativeProofReuseDecision({
|
|
142
|
+
proof: jsonFile(options.sourceProofPath, "native proof"),
|
|
143
|
+
current: {
|
|
144
|
+
sourceIdentityRoot: options.sourceIdentityRoot,
|
|
145
|
+
sourcePatchRoot: options.sourcePatchRoot,
|
|
146
|
+
planRoot: options.planRoot,
|
|
147
|
+
closureRoot: options.closureRoot,
|
|
148
|
+
dependencyRoot: options.dependencyRoot,
|
|
149
|
+
toolchainRoot: options.toolchainRoot,
|
|
150
|
+
currentBase: options.currentBase,
|
|
151
|
+
graphKnown: options.graphKnown,
|
|
152
|
+
changedPaths: jsonList(options.changedPaths, "changed paths"),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (options.command === "verify-native-reuse") {
|
|
157
|
+
return verifyNativeProofReuseDecision(jsonFile(options.qualificationReceiptPath, "native reuse decision"), {
|
|
158
|
+
proof: jsonFile(options.sourceProofPath, "native proof"),
|
|
159
|
+
current: {
|
|
160
|
+
sourceIdentityRoot: options.sourceIdentityRoot,
|
|
161
|
+
sourcePatchRoot: options.sourcePatchRoot,
|
|
162
|
+
planRoot: options.planRoot,
|
|
163
|
+
closureRoot: options.closureRoot,
|
|
164
|
+
dependencyRoot: options.dependencyRoot,
|
|
165
|
+
toolchainRoot: options.toolchainRoot,
|
|
166
|
+
currentBase: options.currentBase,
|
|
167
|
+
graphKnown: options.graphKnown,
|
|
168
|
+
changedPaths: jsonList(options.changedPaths, "changed paths"),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
120
172
|
if (options.command === "replay") {
|
|
121
173
|
return createProjectCutReplayPlan({
|
|
122
174
|
repository: options.repository,
|
|
@@ -168,7 +220,7 @@ export function runDevDeliveryProofCommand(options) {
|
|
|
168
220
|
}
|
|
169
221
|
|
|
170
222
|
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";
|
|
223
|
+
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
224
|
}
|
|
173
225
|
|
|
174
226
|
async function main() {
|