@kungfu-tech/buildchain 3.0.9-alpha.4 → 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 +56 -26
- 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 +46 -16
- 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/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 +82 -2
- package/scripts/aws-macos-jit-controller.mjs +81 -1
- package/scripts/aws-macos-jit-source-rebind.mjs +147 -24
- 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,8 +28,8 @@ function priorOwnershipPlan(plan) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function assertRunMatchesPolicy(
|
|
32
|
-
if (
|
|
31
|
+
function assertRunMatchesPolicy(policy, run, jobs, jobCount, artifactCount) {
|
|
32
|
+
if (policy === "unused") {
|
|
33
33
|
if (jobCount !== 0 || artifactCount !== 0) {
|
|
34
34
|
throw new Error(
|
|
35
35
|
`macOS campaign run ${run.id} already has jobs or artifacts`,
|
|
@@ -37,6 +37,32 @@ function assertRunMatchesPolicy(plan, run, jobs, jobCount, artifactCount) {
|
|
|
37
37
|
}
|
|
38
38
|
return;
|
|
39
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
|
+
}
|
|
40
66
|
if (
|
|
41
67
|
run.status !== "completed" ||
|
|
42
68
|
run.conclusion !== "failure" ||
|
|
@@ -50,6 +76,88 @@ function assertRunMatchesPolicy(plan, run, jobs, jobCount, artifactCount) {
|
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
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
|
+
|
|
53
161
|
function campaignRunReceipts(plan) {
|
|
54
162
|
const branch = sourceRefName(plan.source.ref);
|
|
55
163
|
const response = ghJson(
|
|
@@ -68,7 +176,8 @@ function campaignRunReceipts(plan) {
|
|
|
68
176
|
}
|
|
69
177
|
const receipts = [];
|
|
70
178
|
for (const run of matching) {
|
|
71
|
-
|
|
179
|
+
const classification = classifyCampaignRun(plan, run);
|
|
180
|
+
if (run.head_sha !== classification.expectedSourceSha) {
|
|
72
181
|
throw new Error(
|
|
73
182
|
`macOS campaign run ${run.id} is bound to an unexpected source`,
|
|
74
183
|
);
|
|
@@ -90,9 +199,23 @@ function campaignRunReceipts(plan) {
|
|
|
90
199
|
const artifactCount = Number(
|
|
91
200
|
artifacts.total_count || (artifacts.artifacts || []).length || 0,
|
|
92
201
|
);
|
|
93
|
-
|
|
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,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
94
215
|
receipts.push({
|
|
95
216
|
runId: String(run.id),
|
|
217
|
+
sourceSha: String(run.head_sha || ""),
|
|
218
|
+
classification: classification.classification,
|
|
96
219
|
status: String(run.status || "unknown"),
|
|
97
220
|
conclusion: String(run.conclusion || ""),
|
|
98
221
|
jobCount,
|
|
@@ -106,19 +229,7 @@ function campaignRunReceipts(plan) {
|
|
|
106
229
|
})),
|
|
107
230
|
});
|
|
108
231
|
}
|
|
109
|
-
|
|
110
|
-
const observed = receipts
|
|
111
|
-
.map((receipt) => receipt.runId)
|
|
112
|
-
.sort((left, right) => Number(left) - Number(right));
|
|
113
|
-
if (
|
|
114
|
-
JSON.stringify(observed) !==
|
|
115
|
-
JSON.stringify(plan.github.terminalFailureRunIds)
|
|
116
|
-
) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
"macOS campaign terminal failed run inventory does not match the confirmed run ids",
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
232
|
+
assertExactReceiptInventories(plan, receipts);
|
|
122
233
|
return receipts;
|
|
123
234
|
}
|
|
124
235
|
|
|
@@ -325,7 +436,16 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
325
436
|
throw new Error("macOS campaign already has a registered JIT runner");
|
|
326
437
|
}
|
|
327
438
|
const evidenceObjects = {};
|
|
328
|
-
|
|
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) {
|
|
329
449
|
const evidence = awsJson(
|
|
330
450
|
plan,
|
|
331
451
|
profile,
|
|
@@ -356,12 +476,13 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
356
476
|
const count = Number(
|
|
357
477
|
evidence.KeyCount || (evidence.Contents || []).length || 0,
|
|
358
478
|
);
|
|
359
|
-
const
|
|
360
|
-
sourceSha === plan.previousSource.sha &&
|
|
361
|
-
|
|
479
|
+
const terminalEvidenceSource =
|
|
480
|
+
(sourceSha === plan.previousSource.sha &&
|
|
481
|
+
plan.github.priorRunPolicy === "terminal-failure") ||
|
|
482
|
+
historicalTerminalSources.includes(sourceSha);
|
|
362
483
|
if (
|
|
363
|
-
(!
|
|
364
|
-
(
|
|
484
|
+
(!terminalEvidenceSource && count !== 0) ||
|
|
485
|
+
(terminalEvidenceSource && count === 0)
|
|
365
486
|
) {
|
|
366
487
|
throw new Error(
|
|
367
488
|
`macOS campaign source ${sourceSha} bootstrap evidence does not match the rebind policy`,
|
|
@@ -537,7 +658,9 @@ export function executeMacosJitSourceRebind(plan, { profile = "" } = {}) {
|
|
|
537
658
|
status:
|
|
538
659
|
plan.github.priorRunPolicy === "terminal-failure"
|
|
539
660
|
? "rebound-after-terminal-failure-zero-allocation"
|
|
540
|
-
: "
|
|
661
|
+
: plan.github.priorRunPolicy === "preflight-failure"
|
|
662
|
+
? "rebound-after-preflight-failure-zero-allocation"
|
|
663
|
+
: "rebound-zero-allocation",
|
|
541
664
|
repository: plan.repository,
|
|
542
665
|
campaign: plan.campaign,
|
|
543
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() {
|