terminalhire 0.42.23 → 0.42.24
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/bin/jpi-claim.js +587 -143
- package/dist/bin/jpi-dispatch.js +683 -239
- package/dist/bin/jpi-mcp.js +603 -159
- package/dist/bin/jpi-run.js +540 -96
- package/dist/bin/jpi-statusline.js +1 -1
- package/dist/bin/proxy/egressProxy.js +77 -6
- package/dist/bin/proxy/proxyEntry.js +10 -2
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -1077,8 +1077,8 @@ function makeGitHubGovernor(fetchImpl, cfg) {
|
|
|
1077
1077
|
try {
|
|
1078
1078
|
const res = bound == null ? await fetchP : await Promise.race([
|
|
1079
1079
|
fetchP,
|
|
1080
|
-
new Promise((
|
|
1081
|
-
timer = setTimeout(() =>
|
|
1080
|
+
new Promise((resolve5) => {
|
|
1081
|
+
timer = setTimeout(() => resolve5(null), bound);
|
|
1082
1082
|
})
|
|
1083
1083
|
]);
|
|
1084
1084
|
if (!res || !res.ok) return null;
|
|
@@ -5136,8 +5136,8 @@ async function getWithTimeout(governor, url, token, timeoutMs) {
|
|
|
5136
5136
|
try {
|
|
5137
5137
|
const res = await Promise.race([
|
|
5138
5138
|
getP,
|
|
5139
|
-
new Promise((
|
|
5140
|
-
timer = setTimeout(() =>
|
|
5139
|
+
new Promise((resolve5) => {
|
|
5140
|
+
timer = setTimeout(() => resolve5(null), timeoutMs);
|
|
5141
5141
|
})
|
|
5142
5142
|
]);
|
|
5143
5143
|
if (res === null) controller.abort();
|
|
@@ -6934,7 +6934,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
6934
6934
|
});
|
|
6935
6935
|
const { prehash } = eddsaOpts;
|
|
6936
6936
|
const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
|
|
6937
|
-
const
|
|
6937
|
+
const randomBytes13 = eddsaOpts.randomBytes || randomBytes;
|
|
6938
6938
|
const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
|
6939
6939
|
const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
|
|
6940
6940
|
_abool2(phflag, "phflag");
|
|
@@ -7016,7 +7016,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
7016
7016
|
signature: 2 * _size,
|
|
7017
7017
|
seed: _size
|
|
7018
7018
|
};
|
|
7019
|
-
function randomSecretKey(seed =
|
|
7019
|
+
function randomSecretKey(seed = randomBytes13(lengths.seed)) {
|
|
7020
7020
|
return _abytes2(seed, lengths.seed, "seed");
|
|
7021
7021
|
}
|
|
7022
7022
|
function keygen(seed) {
|
|
@@ -11342,7 +11342,7 @@ async function requireStoredLogin() {
|
|
|
11342
11342
|
}
|
|
11343
11343
|
}
|
|
11344
11344
|
function sleep(ms) {
|
|
11345
|
-
return new Promise((
|
|
11345
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
11346
11346
|
}
|
|
11347
11347
|
var TERMINALHIRE_DIR4, TOKEN_FILE, ALGO, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL, BAKED_IN_CLIENT_ID, MOCK_TOKEN, MOCK_LOGIN;
|
|
11348
11348
|
var init_github_auth = __esm({
|
|
@@ -11759,16 +11759,16 @@ var sleep2;
|
|
|
11759
11759
|
var init_sleep = __esm({
|
|
11760
11760
|
"../../node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs"() {
|
|
11761
11761
|
"use strict";
|
|
11762
|
-
sleep2 = (ms, signal) => new Promise((
|
|
11762
|
+
sleep2 = (ms, signal) => new Promise((resolve5) => {
|
|
11763
11763
|
if (signal?.aborted)
|
|
11764
|
-
return
|
|
11764
|
+
return resolve5();
|
|
11765
11765
|
const onAbort = () => {
|
|
11766
11766
|
clearTimeout(timer);
|
|
11767
|
-
|
|
11767
|
+
resolve5();
|
|
11768
11768
|
};
|
|
11769
11769
|
const timer = setTimeout(() => {
|
|
11770
11770
|
signal?.removeEventListener("abort", onAbort);
|
|
11771
|
-
|
|
11771
|
+
resolve5();
|
|
11772
11772
|
}, ms);
|
|
11773
11773
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11774
11774
|
});
|
|
@@ -13897,8 +13897,8 @@ var init_api_promise = __esm({
|
|
|
13897
13897
|
init_parse();
|
|
13898
13898
|
APIPromise = class _APIPromise extends Promise {
|
|
13899
13899
|
constructor(client, responsePromise, parseResponse = defaultParseResponse) {
|
|
13900
|
-
super((
|
|
13901
|
-
|
|
13900
|
+
super((resolve5) => {
|
|
13901
|
+
resolve5(null);
|
|
13902
13902
|
});
|
|
13903
13903
|
this.responsePromise = responsePromise;
|
|
13904
13904
|
this.parseResponse = parseResponse;
|
|
@@ -16418,16 +16418,16 @@ var init_async_queue = __esm({
|
|
|
16418
16418
|
if (__classPrivateFieldGet(this, _AsyncQueue_closed, "f") || signal?.aborted) {
|
|
16419
16419
|
return Promise.resolve({ done: true, value: void 0 });
|
|
16420
16420
|
}
|
|
16421
|
-
return new Promise((
|
|
16421
|
+
return new Promise((resolve5) => {
|
|
16422
16422
|
const waiter = (r) => {
|
|
16423
16423
|
signal?.removeEventListener("abort", onAbort);
|
|
16424
|
-
|
|
16424
|
+
resolve5(r);
|
|
16425
16425
|
};
|
|
16426
16426
|
const onAbort = () => {
|
|
16427
16427
|
const idx = __classPrivateFieldGet(this, _AsyncQueue_waiters, "f").indexOf(waiter);
|
|
16428
16428
|
if (idx >= 0)
|
|
16429
16429
|
__classPrivateFieldGet(this, _AsyncQueue_waiters, "f").splice(idx, 1);
|
|
16430
|
-
|
|
16430
|
+
resolve5({ done: true, value: void 0 });
|
|
16431
16431
|
};
|
|
16432
16432
|
__classPrivateFieldGet(this, _AsyncQueue_waiters, "f").push(waiter);
|
|
16433
16433
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -17027,13 +17027,13 @@ var init_json_schema = __esm({
|
|
|
17027
17027
|
|
|
17028
17028
|
// ../../node_modules/@anthropic-ai/sdk/internal/utils/promise.mjs
|
|
17029
17029
|
function promiseWithResolvers() {
|
|
17030
|
-
let
|
|
17030
|
+
let resolve5;
|
|
17031
17031
|
let reject;
|
|
17032
17032
|
const promise = new Promise((res, rej) => {
|
|
17033
|
-
|
|
17033
|
+
resolve5 = res;
|
|
17034
17034
|
reject = rej;
|
|
17035
17035
|
});
|
|
17036
|
-
return { promise, resolve:
|
|
17036
|
+
return { promise, resolve: resolve5, reject };
|
|
17037
17037
|
}
|
|
17038
17038
|
var init_promise = __esm({
|
|
17039
17039
|
"../../node_modules/@anthropic-ai/sdk/internal/utils/promise.mjs"() {
|
|
@@ -17635,7 +17635,7 @@ function betaGrepTool(ctx) {
|
|
|
17635
17635
|
});
|
|
17636
17636
|
}
|
|
17637
17637
|
function runRipgrep(rg, pattern, searchPath, signal) {
|
|
17638
|
-
return new Promise((
|
|
17638
|
+
return new Promise((resolve5, reject) => {
|
|
17639
17639
|
const proc = cp.spawn(rg, ["-n", "--no-heading", "-e", pattern, "--", searchPath], {
|
|
17640
17640
|
...signal ? { signal } : {}
|
|
17641
17641
|
});
|
|
@@ -17657,12 +17657,12 @@ function runRipgrep(rg, pattern, searchPath, signal) {
|
|
|
17657
17657
|
if (signal?.aborted)
|
|
17658
17658
|
return reject(new ToolError("grep: aborted"));
|
|
17659
17659
|
if (truncated)
|
|
17660
|
-
return
|
|
17660
|
+
return resolve5(out + `
|
|
17661
17661
|
[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
|
|
17662
17662
|
if (code === 0)
|
|
17663
|
-
return
|
|
17663
|
+
return resolve5(out);
|
|
17664
17664
|
if (code === 1)
|
|
17665
|
-
return
|
|
17665
|
+
return resolve5("no matches");
|
|
17666
17666
|
reject(new ToolError(`grep: rg failed: ${errOut || `exit ${code}`}`));
|
|
17667
17667
|
});
|
|
17668
17668
|
proc.on("error", (e) => {
|
|
@@ -17840,8 +17840,8 @@ var init_node = __esm({
|
|
|
17840
17840
|
`;
|
|
17841
17841
|
__classPrivateFieldGet(this, _BashSession_proc, "f").stdin.write(wrapped);
|
|
17842
17842
|
if (__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel2) < 0) {
|
|
17843
|
-
const { promise: sentinelSeen, resolve:
|
|
17844
|
-
__classPrivateFieldSet(this, _BashSession_waiting, { sentinel: sentinel2, resolve:
|
|
17843
|
+
const { promise: sentinelSeen, resolve: resolve5 } = promiseWithResolvers();
|
|
17844
|
+
__classPrivateFieldSet(this, _BashSession_waiting, { sentinel: sentinel2, resolve: resolve5 }, "f");
|
|
17845
17845
|
let timer;
|
|
17846
17846
|
let onAbort;
|
|
17847
17847
|
try {
|
|
@@ -19596,12 +19596,12 @@ var init_BetaMessageStream = __esm({
|
|
|
19596
19596
|
}
|
|
19597
19597
|
return this._emit("error", new AnthropicError(String(error)));
|
|
19598
19598
|
});
|
|
19599
|
-
__classPrivateFieldSet(this, _BetaMessageStream_connectedPromise, new Promise((
|
|
19600
|
-
__classPrivateFieldSet(this, _BetaMessageStream_resolveConnectedPromise,
|
|
19599
|
+
__classPrivateFieldSet(this, _BetaMessageStream_connectedPromise, new Promise((resolve5, reject) => {
|
|
19600
|
+
__classPrivateFieldSet(this, _BetaMessageStream_resolveConnectedPromise, resolve5, "f");
|
|
19601
19601
|
__classPrivateFieldSet(this, _BetaMessageStream_rejectConnectedPromise, reject, "f");
|
|
19602
19602
|
}), "f");
|
|
19603
|
-
__classPrivateFieldSet(this, _BetaMessageStream_endPromise, new Promise((
|
|
19604
|
-
__classPrivateFieldSet(this, _BetaMessageStream_resolveEndPromise,
|
|
19603
|
+
__classPrivateFieldSet(this, _BetaMessageStream_endPromise, new Promise((resolve5, reject) => {
|
|
19604
|
+
__classPrivateFieldSet(this, _BetaMessageStream_resolveEndPromise, resolve5, "f");
|
|
19605
19605
|
__classPrivateFieldSet(this, _BetaMessageStream_rejectEndPromise, reject, "f");
|
|
19606
19606
|
}), "f");
|
|
19607
19607
|
__classPrivateFieldGet(this, _BetaMessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -19771,11 +19771,11 @@ var init_BetaMessageStream = __esm({
|
|
|
19771
19771
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
19772
19772
|
*/
|
|
19773
19773
|
emitted(event) {
|
|
19774
|
-
return new Promise((
|
|
19774
|
+
return new Promise((resolve5, reject) => {
|
|
19775
19775
|
__classPrivateFieldSet(this, _BetaMessageStream_catchingPromiseCreated, true, "f");
|
|
19776
19776
|
if (event !== "error")
|
|
19777
19777
|
this.once("error", reject);
|
|
19778
|
-
this.once(event,
|
|
19778
|
+
this.once(event, resolve5);
|
|
19779
19779
|
});
|
|
19780
19780
|
}
|
|
19781
19781
|
async done() {
|
|
@@ -20137,7 +20137,7 @@ var init_BetaMessageStream = __esm({
|
|
|
20137
20137
|
if (done) {
|
|
20138
20138
|
return { value: void 0, done: true };
|
|
20139
20139
|
}
|
|
20140
|
-
return new Promise((
|
|
20140
|
+
return new Promise((resolve5, reject) => readQueue.push({ resolve: resolve5, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
20141
20141
|
}
|
|
20142
20142
|
const chunk = pushQueue.shift();
|
|
20143
20143
|
return { value: chunk, done: false };
|
|
@@ -22375,12 +22375,12 @@ var init_MessageStream = __esm({
|
|
|
22375
22375
|
}
|
|
22376
22376
|
return this._emit("error", new AnthropicError(String(error)));
|
|
22377
22377
|
});
|
|
22378
|
-
__classPrivateFieldSet(this, _MessageStream_connectedPromise, new Promise((
|
|
22379
|
-
__classPrivateFieldSet(this, _MessageStream_resolveConnectedPromise,
|
|
22378
|
+
__classPrivateFieldSet(this, _MessageStream_connectedPromise, new Promise((resolve5, reject) => {
|
|
22379
|
+
__classPrivateFieldSet(this, _MessageStream_resolveConnectedPromise, resolve5, "f");
|
|
22380
22380
|
__classPrivateFieldSet(this, _MessageStream_rejectConnectedPromise, reject, "f");
|
|
22381
22381
|
}), "f");
|
|
22382
|
-
__classPrivateFieldSet(this, _MessageStream_endPromise, new Promise((
|
|
22383
|
-
__classPrivateFieldSet(this, _MessageStream_resolveEndPromise,
|
|
22382
|
+
__classPrivateFieldSet(this, _MessageStream_endPromise, new Promise((resolve5, reject) => {
|
|
22383
|
+
__classPrivateFieldSet(this, _MessageStream_resolveEndPromise, resolve5, "f");
|
|
22384
22384
|
__classPrivateFieldSet(this, _MessageStream_rejectEndPromise, reject, "f");
|
|
22385
22385
|
}), "f");
|
|
22386
22386
|
__classPrivateFieldGet(this, _MessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -22550,11 +22550,11 @@ var init_MessageStream = __esm({
|
|
|
22550
22550
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
22551
22551
|
*/
|
|
22552
22552
|
emitted(event) {
|
|
22553
|
-
return new Promise((
|
|
22553
|
+
return new Promise((resolve5, reject) => {
|
|
22554
22554
|
__classPrivateFieldSet(this, _MessageStream_catchingPromiseCreated, true, "f");
|
|
22555
22555
|
if (event !== "error")
|
|
22556
22556
|
this.once("error", reject);
|
|
22557
|
-
this.once(event,
|
|
22557
|
+
this.once(event, resolve5);
|
|
22558
22558
|
});
|
|
22559
22559
|
}
|
|
22560
22560
|
async done() {
|
|
@@ -22875,7 +22875,7 @@ var init_MessageStream = __esm({
|
|
|
22875
22875
|
if (done) {
|
|
22876
22876
|
return { value: void 0, done: true };
|
|
22877
22877
|
}
|
|
22878
|
-
return new Promise((
|
|
22878
|
+
return new Promise((resolve5, reject) => readQueue.push({ resolve: resolve5, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
22879
22879
|
}
|
|
22880
22880
|
const chunk = pushQueue.shift();
|
|
22881
22881
|
return { value: chunk, done: false };
|
|
@@ -25973,7 +25973,7 @@ function splitProgressChunk(chunk) {
|
|
|
25973
25973
|
}
|
|
25974
25974
|
function shStream(cmd, args, opts = {}) {
|
|
25975
25975
|
const cap = opts.maxStderrBytes ?? 64 * 1024;
|
|
25976
|
-
return new Promise((
|
|
25976
|
+
return new Promise((resolve5, reject) => {
|
|
25977
25977
|
void (async () => {
|
|
25978
25978
|
const spawn6 = opts.spawnFn ?? (await import("child_process")).spawn;
|
|
25979
25979
|
const child = spawn6(cmd, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -25991,7 +25991,7 @@ function shStream(cmd, args, opts = {}) {
|
|
|
25991
25991
|
child.on("error", (err) => reject(err));
|
|
25992
25992
|
child.on("close", (code, signal) => {
|
|
25993
25993
|
if (code === 0) {
|
|
25994
|
-
|
|
25994
|
+
resolve5({ stdout: stdout.trim(), stderr });
|
|
25995
25995
|
return;
|
|
25996
25996
|
}
|
|
25997
25997
|
const err = new Error(
|
|
@@ -26058,6 +26058,20 @@ function isCommandUnavailable(facts, countsRead) {
|
|
|
26058
26058
|
return false;
|
|
26059
26059
|
return facts.stdout.trim().length === 0 && EXEC_FAILURE.test(facts.stderr);
|
|
26060
26060
|
}
|
|
26061
|
+
function isMissingSystemDependency(facts, countsRead) {
|
|
26062
|
+
if (countsRead)
|
|
26063
|
+
return null;
|
|
26064
|
+
if (facts.exitCode === 0 || facts.exitCode === null)
|
|
26065
|
+
return null;
|
|
26066
|
+
const out = `${facts.stdout}
|
|
26067
|
+
${facts.stderr}`;
|
|
26068
|
+
for (const pattern of MISSING_SYSTEM_DEPENDENCY) {
|
|
26069
|
+
const hit = pattern.exec(out);
|
|
26070
|
+
if (hit)
|
|
26071
|
+
return hit[0];
|
|
26072
|
+
}
|
|
26073
|
+
return null;
|
|
26074
|
+
}
|
|
26061
26075
|
function isOfflineBuildGap(facts, counts) {
|
|
26062
26076
|
if (facts.runtime !== "jvm")
|
|
26063
26077
|
return null;
|
|
@@ -26098,6 +26112,14 @@ function classifyVerification(facts) {
|
|
|
26098
26112
|
reason: `the test command could not be invoked (exit ${String(facts.exitCode)}). This is an environment failure on our side \u2014 a missing interpreter, a binary outside PATH, or a dependency installed somewhere the command cannot see it. The repo has not been judged.`
|
|
26099
26113
|
};
|
|
26100
26114
|
}
|
|
26115
|
+
const missing = isMissingSystemDependency(facts, counts !== null);
|
|
26116
|
+
if (missing !== null) {
|
|
26117
|
+
return {
|
|
26118
|
+
outcome: "test-command-unavailable",
|
|
26119
|
+
counts,
|
|
26120
|
+
reason: `the build needs a system library or header our image does not carry (${missing}). This is a gap in our environment, not a failure in the repo. The repo has not been judged.`
|
|
26121
|
+
};
|
|
26122
|
+
}
|
|
26101
26123
|
const gap = isOfflineBuildGap(facts, counts);
|
|
26102
26124
|
if (gap !== null) {
|
|
26103
26125
|
return {
|
|
@@ -26158,9 +26180,9 @@ function isGreen(outcome) {
|
|
|
26158
26180
|
return outcome === "completed";
|
|
26159
26181
|
}
|
|
26160
26182
|
function isOurFault(outcome) {
|
|
26161
|
-
return outcome
|
|
26183
|
+
return OUR_FAULT[outcome];
|
|
26162
26184
|
}
|
|
26163
|
-
var VERIFICATION_OUTCOMES, int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, OFFLINE_BUILD_GAP;
|
|
26185
|
+
var VERIFICATION_OUTCOMES, int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, OUR_FAULT;
|
|
26164
26186
|
var init_classify2 = __esm({
|
|
26165
26187
|
"../../packages/envrun/dist/classify.js"() {
|
|
26166
26188
|
"use strict";
|
|
@@ -26317,6 +26339,23 @@ var init_classify2 = __esm({
|
|
|
26317
26339
|
};
|
|
26318
26340
|
}
|
|
26319
26341
|
},
|
|
26342
|
+
{
|
|
26343
|
+
// CTest's closing summary: `50% tests passed, 1 tests failed out of 2`. Measured
|
|
26344
|
+
// on cmake 3.25 in the cpp image (TERM-1123); the singular `1 test failed` is
|
|
26345
|
+
// CTest's own form for one. Anchored at line start, so a log line quoting it
|
|
26346
|
+
// is not read as the summary. Meson and a bare `make test` print no summary we
|
|
26347
|
+
// can trust and stay unparsed.
|
|
26348
|
+
runner: "ctest",
|
|
26349
|
+
read: (out) => {
|
|
26350
|
+
if (/^No tests were found!!!$/m.test(out))
|
|
26351
|
+
return { tests_passed: 0, tests_failed: 0 };
|
|
26352
|
+
const m = /^\d+% tests passed, (\d+) tests? failed out of (\d+)$/m.exec(out);
|
|
26353
|
+
if (!m)
|
|
26354
|
+
return null;
|
|
26355
|
+
const failed = Number(m[1]);
|
|
26356
|
+
return { tests_passed: Number(m[2]) - failed, tests_failed: failed };
|
|
26357
|
+
}
|
|
26358
|
+
},
|
|
26320
26359
|
{
|
|
26321
26360
|
// mocha: ` 440 passing (1s)` and ` 2 failing`
|
|
26322
26361
|
runner: "mocha",
|
|
@@ -26445,6 +26484,11 @@ var init_classify2 = __esm({
|
|
|
26445
26484
|
"\\d+\\s+examples?,\\s+(?!0\\b)\\d+\\s+failures?"
|
|
26446
26485
|
// rspec's summary line
|
|
26447
26486
|
].join("|"), "m");
|
|
26487
|
+
MISSING_SYSTEM_DEPENDENCY = [
|
|
26488
|
+
/^(?:\S*\/)?ld(?:\.\w+)?: cannot find -l[\w.+-]+/m,
|
|
26489
|
+
/^\S+:\d+(?::\d+)?: fatal error: [\w./+-]+\.(?:h|hh|hpp|hxx): No such file or directory$/m,
|
|
26490
|
+
/^\S+: error while loading shared libraries: lib[\w.+-]*: cannot open shared object file/m
|
|
26491
|
+
];
|
|
26448
26492
|
OFFLINE_BUILD_GAP = [
|
|
26449
26493
|
// Maven, `-o`: `Cannot access central (…) in offline mode and the artifact … has
|
|
26450
26494
|
// not been downloaded from it before.`
|
|
@@ -26460,6 +26504,15 @@ var init_classify2 = __esm({
|
|
|
26460
26504
|
/No matching toolchains found for requested specification/,
|
|
26461
26505
|
/Cannot find a Java installation on your machine matching/
|
|
26462
26506
|
];
|
|
26507
|
+
OUR_FAULT = {
|
|
26508
|
+
completed: false,
|
|
26509
|
+
"tests-failed": false,
|
|
26510
|
+
"no-tests-observed": false,
|
|
26511
|
+
"budget-exceeded": false,
|
|
26512
|
+
"counts-unparsed": true,
|
|
26513
|
+
"test-command-unavailable": true,
|
|
26514
|
+
"environment-exhausted": true
|
|
26515
|
+
};
|
|
26463
26516
|
}
|
|
26464
26517
|
});
|
|
26465
26518
|
|
|
@@ -26493,7 +26546,8 @@ function scrubEnv(source, opts) {
|
|
|
26493
26546
|
if (!tmpDir.startsWith("/")) {
|
|
26494
26547
|
throw new SandboxEnvError(`tmpDir must be an absolute path, got ${JSON.stringify(tmpDir)}`);
|
|
26495
26548
|
}
|
|
26496
|
-
|
|
26549
|
+
const homesToCompare = opts.jailHomeNamespace === "guest" ? [] : realHomeCandidates(source);
|
|
26550
|
+
for (const realHome of homesToCompare) {
|
|
26497
26551
|
if (jailHome === realHome || jailHome === `${realHome}/`) {
|
|
26498
26552
|
throw new SandboxEnvError("jailHome is the real HOME \u2014 the sandbox would read the user profile");
|
|
26499
26553
|
}
|
|
@@ -26582,8 +26636,8 @@ var init_reap = __esm({
|
|
|
26582
26636
|
|
|
26583
26637
|
// ../../packages/containment/dist/fence.js
|
|
26584
26638
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
26585
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync3, realpathSync, writeFileSync as writeFileSync10 } from "fs";
|
|
26586
|
-
import { dirname as dirname6, isAbsolute as isAbsolute3, join as join19, posix as posix2 } from "path";
|
|
26639
|
+
import { existsSync as existsSync9, lchownSync, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, realpathSync, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
26640
|
+
import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute3, join as join19, posix as posix2, resolve as resolve4, sep as sep5 } from "path";
|
|
26587
26641
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26588
26642
|
function canonical(path5, label) {
|
|
26589
26643
|
if (!isAbsolute3(path5)) {
|
|
@@ -26610,7 +26664,7 @@ function resolverFor(domain) {
|
|
|
26610
26664
|
function pathDomainOf(spec) {
|
|
26611
26665
|
return spec.pathDomain ?? "local";
|
|
26612
26666
|
}
|
|
26613
|
-
function jailPasswd(uid =
|
|
26667
|
+
function jailPasswd(uid = localGuestUser()?.uid ?? null, gid = localGuestUser()?.gid ?? null) {
|
|
26614
26668
|
const rows = ["root:x:0:0:root:/root:/usr/sbin/nologin"];
|
|
26615
26669
|
if (uid !== null && gid !== null && uid !== 0) {
|
|
26616
26670
|
rows.push(`${FENCE_USER}:x:${uid}:${gid}:fenced build user:${GUEST_JAIL}:/usr/sbin/nologin`);
|
|
@@ -26619,7 +26673,7 @@ function jailPasswd(uid = idOrNull("getuid"), gid = idOrNull("getgid")) {
|
|
|
26619
26673
|
return `${rows.join("\n")}
|
|
26620
26674
|
`;
|
|
26621
26675
|
}
|
|
26622
|
-
function jailGroup(gid =
|
|
26676
|
+
function jailGroup(gid = localGuestUser()?.gid ?? null) {
|
|
26623
26677
|
const rows = ["root:x:0:"];
|
|
26624
26678
|
if (gid !== null && gid !== 0)
|
|
26625
26679
|
rows.push(`${FENCE_USER}:x:${gid}:`);
|
|
@@ -26631,19 +26685,139 @@ function idOrNull(fn) {
|
|
|
26631
26685
|
const f = process[fn];
|
|
26632
26686
|
return typeof f === "function" ? f.call(process) : null;
|
|
26633
26687
|
}
|
|
26634
|
-
function
|
|
26688
|
+
function hostIds() {
|
|
26689
|
+
const uid = idOrNull("getuid");
|
|
26690
|
+
const gid = idOrNull("getgid");
|
|
26691
|
+
return uid === null || gid === null ? null : { uid, gid };
|
|
26692
|
+
}
|
|
26693
|
+
function localGuestUser(ids2 = hostIds()) {
|
|
26694
|
+
if (ids2 === null)
|
|
26695
|
+
return null;
|
|
26696
|
+
return ids2.uid === 0 ? REMAPPED_GUEST_USER : ids2;
|
|
26697
|
+
}
|
|
26698
|
+
function chownTree(dir, user) {
|
|
26699
|
+
const stack = [dir];
|
|
26700
|
+
for (let p = stack.pop(); p !== void 0; p = stack.pop()) {
|
|
26701
|
+
lchownSync(p, user.uid, user.gid);
|
|
26702
|
+
if (lstatSync3(p).isDirectory()) {
|
|
26703
|
+
for (const entry of readdirSync2(p))
|
|
26704
|
+
stack.push(join19(p, entry));
|
|
26705
|
+
}
|
|
26706
|
+
}
|
|
26707
|
+
}
|
|
26708
|
+
function canonicalPath(p) {
|
|
26709
|
+
let head = resolve4(p);
|
|
26710
|
+
const tail2 = [];
|
|
26711
|
+
for (; ; ) {
|
|
26712
|
+
try {
|
|
26713
|
+
return join19(realpathSync.native(head), ...tail2.reverse());
|
|
26714
|
+
} catch {
|
|
26715
|
+
const up = dirname6(head);
|
|
26716
|
+
if (up === head)
|
|
26717
|
+
return resolve4(p);
|
|
26718
|
+
tail2.push(basename4(head));
|
|
26719
|
+
head = up;
|
|
26720
|
+
}
|
|
26721
|
+
}
|
|
26722
|
+
}
|
|
26723
|
+
function statOrAbsent(p, fn) {
|
|
26724
|
+
try {
|
|
26725
|
+
return fn();
|
|
26726
|
+
} catch (err) {
|
|
26727
|
+
const code = err.code;
|
|
26728
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
26729
|
+
return void 0;
|
|
26730
|
+
throw new FenceError(`cannot stat ${JSON.stringify(p)}: ${err.message}`, {
|
|
26731
|
+
cause: err
|
|
26732
|
+
});
|
|
26733
|
+
}
|
|
26734
|
+
}
|
|
26735
|
+
function fileIdentity(p) {
|
|
26736
|
+
const st = statOrAbsent(p, () => statSync3(p, { bigint: true }));
|
|
26737
|
+
return st === void 0 ? null : { dev: st.dev, ino: st.ino };
|
|
26738
|
+
}
|
|
26739
|
+
function isOrContains(outer, inner) {
|
|
26740
|
+
const fold = process.platform === "win32" ? (s) => s.toLowerCase() : (s) => s;
|
|
26741
|
+
const o = fold(canonicalPath(outer));
|
|
26742
|
+
const i = fold(canonicalPath(inner));
|
|
26743
|
+
const prefix = o.endsWith(sep5) ? o : o + sep5;
|
|
26744
|
+
if (i === o || i.startsWith(prefix))
|
|
26745
|
+
return true;
|
|
26746
|
+
const target = fileIdentity(outer);
|
|
26747
|
+
if (target === null || target.ino === 0n)
|
|
26748
|
+
return false;
|
|
26749
|
+
for (const start of physicalSpellings(canonicalPath(inner))) {
|
|
26750
|
+
for (let a = start; ; a = dirname6(a)) {
|
|
26751
|
+
const id = fileIdentity(a);
|
|
26752
|
+
if (id !== null && id.ino !== 0n && id.dev === target.dev && id.ino === target.ino) {
|
|
26753
|
+
return true;
|
|
26754
|
+
}
|
|
26755
|
+
if (dirname6(a) === a)
|
|
26756
|
+
break;
|
|
26757
|
+
}
|
|
26758
|
+
}
|
|
26759
|
+
return false;
|
|
26760
|
+
}
|
|
26761
|
+
function physicalSpellings(p) {
|
|
26762
|
+
if (process.platform !== "darwin" || p.startsWith(DARWIN_DATA_VOLUME + sep5))
|
|
26763
|
+
return [p];
|
|
26764
|
+
const onData = DARWIN_DATA_VOLUME + p;
|
|
26765
|
+
const logical = fileIdentity(p);
|
|
26766
|
+
const physical = fileIdentity(onData);
|
|
26767
|
+
const same = logical !== null && physical !== null && logical.dev === physical.dev && logical.ino === physical.ino;
|
|
26768
|
+
return same ? [p, onData] : [p];
|
|
26769
|
+
}
|
|
26770
|
+
function realHomes(who) {
|
|
26771
|
+
try {
|
|
26772
|
+
return realHomeCandidates(process.env);
|
|
26773
|
+
} catch (err) {
|
|
26774
|
+
throw new FenceError(`${who}: ${err.message}`, { cause: err });
|
|
26775
|
+
}
|
|
26776
|
+
}
|
|
26777
|
+
function assertHoldsNoRealHome(path5, what, who) {
|
|
26778
|
+
for (const home of realHomes(who)) {
|
|
26779
|
+
if (isOrContains(path5, home)) {
|
|
26780
|
+
throw new FenceError(`${who}: ${what} ${JSON.stringify(path5)} is or contains the real home ${JSON.stringify(home)} \u2014 the jail would expose the developer's profile`);
|
|
26781
|
+
}
|
|
26782
|
+
}
|
|
26783
|
+
}
|
|
26784
|
+
function assertNoForeignLinks(paths) {
|
|
26785
|
+
for (const p of paths) {
|
|
26786
|
+
const st = statOrAbsent(p, () => lstatSync3(p));
|
|
26787
|
+
if (st?.isSymbolicLink() === true) {
|
|
26788
|
+
throw new FenceError(`buildJail: ${JSON.stringify(p)} is a pre-existing symlink \u2014 buildJail would write through it, and a jail directory is never a link`);
|
|
26789
|
+
}
|
|
26790
|
+
if (st?.isFile() === true && st.nlink > 1) {
|
|
26791
|
+
throw new FenceError(`buildJail: ${JSON.stringify(p)} is a pre-existing file with ${String(st.nlink)} hard links \u2014 buildJail would overwrite an inode another path shares`);
|
|
26792
|
+
}
|
|
26793
|
+
}
|
|
26794
|
+
}
|
|
26795
|
+
function buildJail(root, guestUser, io = {}) {
|
|
26796
|
+
const ids2 = io.ids === void 0 ? hostIds() : io.ids;
|
|
26797
|
+
const guest = guestUser ?? localGuestUser(ids2);
|
|
26635
26798
|
const jail = join19(root, JAIL_SEGMENT);
|
|
26636
26799
|
const tmp = join19(root, JAIL_TMP_SEGMENT);
|
|
26637
|
-
|
|
26800
|
+
const dirs = [jail, tmp, join19(jail, ".config"), join19(jail, ".cache"), join19(jail, ".npm")];
|
|
26801
|
+
const files = [".npmrc", JAIL_PASSWD_FILE, JAIL_GROUP_FILE, ".gitconfig"].map((f) => join19(jail, f));
|
|
26802
|
+
assertHoldsNoRealHome(root, "the jail root", "buildJail");
|
|
26803
|
+
assertNoForeignLinks([...dirs, ...files]);
|
|
26804
|
+
assertHoldsNoRealHome(jail, "the jail directory", "buildJail");
|
|
26805
|
+
assertHoldsNoRealHome(tmp, "the jail tmp directory", "buildJail");
|
|
26806
|
+
for (const dir of dirs) {
|
|
26638
26807
|
mkdirSync3(dir, { recursive: true });
|
|
26639
26808
|
}
|
|
26640
26809
|
writeFileSync10(join19(jail, ".npmrc"), "", "utf8");
|
|
26641
|
-
writeFileSync10(join19(jail, JAIL_PASSWD_FILE),
|
|
26642
|
-
writeFileSync10(join19(jail, JAIL_GROUP_FILE),
|
|
26810
|
+
writeFileSync10(join19(jail, JAIL_PASSWD_FILE), guest ? jailPasswd(guest.uid, guest.gid) : jailPasswd(null, null), "utf8");
|
|
26811
|
+
writeFileSync10(join19(jail, JAIL_GROUP_FILE), guest ? jailGroup(guest.gid) : jailGroup(null), "utf8");
|
|
26643
26812
|
writeFileSync10(join19(jail, ".gitconfig"), '[user]\n name = sandbox\n email = sandbox@localhost\n[safe]\n directory = *\n[url "https://github.com/"]\n insteadOf = ssh://git@github.com/\n insteadOf = git@github.com:\n', "utf8");
|
|
26813
|
+
if (ids2?.uid === 0 && guest && guest.uid !== 0) {
|
|
26814
|
+
const chown = io.chown ?? chownTree;
|
|
26815
|
+
chown(jail, guest);
|
|
26816
|
+
chown(tmp, guest);
|
|
26817
|
+
}
|
|
26644
26818
|
return { jail, tmp };
|
|
26645
26819
|
}
|
|
26646
|
-
var FenceError, ContainmentError, ContainmentRefusalError, JAIL_PASSWD_FILE, JAIL_GROUP_FILE, GUEST_JAIL, FENCE_USER, JAIL_SEGMENT, JAIL_TMP_SEGMENT;
|
|
26820
|
+
var FenceError, ContainmentError, ContainmentRefusalError, JAIL_PASSWD_FILE, JAIL_GROUP_FILE, GUEST_JAIL, FENCE_USER, REMAPPED_GUEST_USER, JAIL_SEGMENT, JAIL_TMP_SEGMENT, DARWIN_DATA_VOLUME;
|
|
26647
26821
|
var init_fence = __esm({
|
|
26648
26822
|
"../../packages/containment/dist/fence.js"() {
|
|
26649
26823
|
"use strict";
|
|
@@ -26664,8 +26838,10 @@ var init_fence = __esm({
|
|
|
26664
26838
|
JAIL_GROUP_FILE = ".fence-group";
|
|
26665
26839
|
GUEST_JAIL = "/fenced/jail";
|
|
26666
26840
|
FENCE_USER = "fenced";
|
|
26841
|
+
REMAPPED_GUEST_USER = { uid: 1e3, gid: 1e3 };
|
|
26667
26842
|
JAIL_SEGMENT = "jail";
|
|
26668
26843
|
JAIL_TMP_SEGMENT = "tmp";
|
|
26844
|
+
DARWIN_DATA_VOLUME = "/System/Volumes/Data";
|
|
26669
26845
|
}
|
|
26670
26846
|
});
|
|
26671
26847
|
|
|
@@ -26818,7 +26994,27 @@ var init_dockerClient = __esm({
|
|
|
26818
26994
|
// ../../packages/containment/dist/egressProxy.js
|
|
26819
26995
|
import net from "net";
|
|
26820
26996
|
import http from "http";
|
|
26821
|
-
|
|
26997
|
+
function parseDeniedHosts(output) {
|
|
26998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26999
|
+
for (const line of output.split(/\r?\n/)) {
|
|
27000
|
+
if (!line.startsWith(DENIAL_PREFIX))
|
|
27001
|
+
continue;
|
|
27002
|
+
let parsed;
|
|
27003
|
+
try {
|
|
27004
|
+
parsed = JSON.parse(line.slice(DENIAL_PREFIX.length));
|
|
27005
|
+
} catch {
|
|
27006
|
+
continue;
|
|
27007
|
+
}
|
|
27008
|
+
const host = parsed?.host;
|
|
27009
|
+
if (typeof host !== "string")
|
|
27010
|
+
continue;
|
|
27011
|
+
const h = host.toLowerCase().replace(/\.$/, "");
|
|
27012
|
+
if (HOSTNAME.test(h))
|
|
27013
|
+
seen.add(h);
|
|
27014
|
+
}
|
|
27015
|
+
return [...seen];
|
|
27016
|
+
}
|
|
27017
|
+
var DEFAULT_INSTALL_ALLOWLIST, DENIAL_PREFIX, HOSTNAME;
|
|
26822
27018
|
var init_egressProxy = __esm({
|
|
26823
27019
|
"../../packages/containment/dist/egressProxy.js"() {
|
|
26824
27020
|
"use strict";
|
|
@@ -26867,24 +27063,47 @@ var init_egressProxy = __esm({
|
|
|
26867
27063
|
// allowlist than breadth.
|
|
26868
27064
|
//
|
|
26869
27065
|
// What it does NOT grant: `githubusercontent.com` is a DIFFERENT domain, so
|
|
26870
|
-
//
|
|
26871
|
-
//
|
|
27066
|
+
// raw blobs and `objects.githubusercontent.com` stay denied; only
|
|
27067
|
+
// `release-assets.githubusercontent.com` is listed, above, for the Gradle
|
|
27068
|
+
// wrapper. If a real install needs more, that should arrive as a measured
|
|
27069
|
+
// egress denial, not a pre-emptive guess.
|
|
26872
27070
|
//
|
|
26873
|
-
//
|
|
26874
|
-
//
|
|
26875
|
-
//
|
|
27071
|
+
// WHO RUNS INSTALL SCRIPTS THROUGH THIS GRANT (TERM-1157). This comment used
|
|
27072
|
+
// to say install "already runs with `--ignore-scripts`". That is true of
|
|
27073
|
+
// `apps/merge-agent` (`src/sandbox/plan.ts` adds the flag on every branch)
|
|
27074
|
+
// and false of `th run`: `packages/envspec/src/manifest.ts` derives a plain
|
|
27075
|
+
// `npm ci`, so there every dependency's `preinstall`/`install`/`postinstall`
|
|
27076
|
+
// executes behind this proxy. TERM-1157 chose to keep them running.
|
|
27077
|
+
//
|
|
27078
|
+
// For `th run`, then, the grant reaches code the repo's dependencies chose,
|
|
27079
|
+
// not only the package manager. What bounds it:
|
|
27080
|
+
// - no credential of ours is in the fence: `auditEnv` (`env.ts`) refuses to
|
|
27081
|
+
// spawn when a variable's name or a GitHub-token-shaped value says
|
|
27082
|
+
// otherwise, so a script cannot act on GitHub as us;
|
|
27083
|
+
// - the fence holds the repo's tree and its dependencies, nothing else of
|
|
27084
|
+
// ours;
|
|
27085
|
+
// - every step after install runs with no network at all.
|
|
27086
|
+
// What it does NOT bound: a script can download and run any public GitHub
|
|
27087
|
+
// content, and one that brings its author's own token can push what the
|
|
27088
|
+
// fence holds to its author's account. `registry.npmjs.org` above gives the
|
|
27089
|
+
// same script the same reach (download anything, publish with its own
|
|
27090
|
+
// token), so this entry adds a second address for a capability the install
|
|
27091
|
+
// already has rather than a new one. That is the case for keeping it; it is
|
|
27092
|
+
// not a claim that GitHub is read-only from here.
|
|
26876
27093
|
"github.com"
|
|
26877
27094
|
];
|
|
27095
|
+
DENIAL_PREFIX = "proxy-denied ";
|
|
27096
|
+
HOSTNAME = /^[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/;
|
|
26878
27097
|
}
|
|
26879
27098
|
});
|
|
26880
27099
|
|
|
26881
27100
|
// ../../packages/containment/dist/container.js
|
|
26882
27101
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
26883
27102
|
import { dirname as dirname7, join as join20 } from "path";
|
|
26884
|
-
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync10, mkdtempSync, rmSync as rmSync6 } from "fs";
|
|
27103
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync10, mkdtempSync, rmSync as rmSync6, statSync as statSync4 } from "fs";
|
|
26885
27104
|
import { tmpdir } from "os";
|
|
26886
27105
|
function scrubEnvPathsFor(containmentKind, host) {
|
|
26887
|
-
return containmentKind === "container" ? { jailHome: GUEST.jail, tmpDir: GUEST.tmp } : { jailHome: host.jail, tmpDir: host.tmp };
|
|
27106
|
+
return containmentKind === "container" ? { jailHome: GUEST.jail, tmpDir: GUEST.tmp, jailHomeNamespace: "guest" } : { jailHome: host.jail, tmpDir: host.tmp, jailHomeNamespace: "host" };
|
|
26888
27107
|
}
|
|
26889
27108
|
function buildTranslation(spec) {
|
|
26890
27109
|
const roots = [
|
|
@@ -26892,11 +27111,11 @@ function buildTranslation(spec) {
|
|
|
26892
27111
|
{ raw: spec.jail, guest: GUEST.jail, label: "jail" },
|
|
26893
27112
|
{ raw: spec.tmp, guest: GUEST.tmp, label: "tmp" }
|
|
26894
27113
|
];
|
|
26895
|
-
const
|
|
27114
|
+
const resolve5 = resolverFor(pathDomainOf(spec));
|
|
26896
27115
|
const pairs = [];
|
|
26897
27116
|
const seen = /* @__PURE__ */ new Set();
|
|
26898
27117
|
for (const { raw, guest, label } of roots) {
|
|
26899
|
-
for (const host of [raw,
|
|
27118
|
+
for (const host of [raw, resolve5(raw, label)]) {
|
|
26900
27119
|
if (seen.has(host))
|
|
26901
27120
|
continue;
|
|
26902
27121
|
seen.add(host);
|
|
@@ -26979,21 +27198,36 @@ function labelArgs(labels) {
|
|
|
26979
27198
|
}
|
|
26980
27199
|
return out;
|
|
26981
27200
|
}
|
|
26982
|
-
function hostUserFlag() {
|
|
26983
|
-
const
|
|
26984
|
-
|
|
26985
|
-
if (uid === null || gid === null)
|
|
27201
|
+
function hostUserFlag(ids2 = hostIds()) {
|
|
27202
|
+
const guest = localGuestUser(ids2);
|
|
27203
|
+
if (guest === null)
|
|
26986
27204
|
return [];
|
|
26987
|
-
return [`--user=${uid}:${gid}`];
|
|
27205
|
+
return [`--user=${String(guest.uid)}:${String(guest.gid)}`];
|
|
26988
27206
|
}
|
|
26989
|
-
function guestUserFlag(spec) {
|
|
27207
|
+
function guestUserFlag(spec, ids2 = hostIds()) {
|
|
26990
27208
|
const declared = spec.guestUser;
|
|
26991
27209
|
if (declared)
|
|
26992
27210
|
return [`--user=${declared.uid}:${declared.gid}`];
|
|
26993
27211
|
if (pathDomainOf(spec) === "venue") {
|
|
26994
27212
|
throw new FenceError("a venue-domain spec must declare guestUser: the tree is owned by the account that staged it on the venue, and this process\u2019s uid is a fact about a different machine. Handing the guest the local id gives it no write access to its own clone, and that EACCES reaches the classifier as the developer\u2019s suite failing.");
|
|
26995
27213
|
}
|
|
26996
|
-
return hostUserFlag();
|
|
27214
|
+
return hostUserFlag(ids2);
|
|
27215
|
+
}
|
|
27216
|
+
function assertTreeHandedToGuest(spec, ids2 = hostIds(), ownerOf2 = (path5) => statSync4(path5).uid) {
|
|
27217
|
+
if (pathDomainOf(spec) !== "local" || ids2?.uid !== 0)
|
|
27218
|
+
return;
|
|
27219
|
+
const guest = spec.guestUser ?? localGuestUser(ids2);
|
|
27220
|
+
if (guest === null || guest.uid === 0)
|
|
27221
|
+
return;
|
|
27222
|
+
for (const [label, path5] of [
|
|
27223
|
+
["clone", spec.clone],
|
|
27224
|
+
["jail", spec.jail]
|
|
27225
|
+
]) {
|
|
27226
|
+
const owner = ownerOf2(path5);
|
|
27227
|
+
if (owner !== guest.uid) {
|
|
27228
|
+
throw new FenceError(`refusing to run: this process is root, so the guest runs as uid ${String(guest.uid)}, but the ${label} at ${path5} is owned by uid ${String(owner)}. The guest could not write its own tree and the EACCES would read as the developer\u2019s suite failing. Hand it over first (chownTree, or buildJail for the jail).`);
|
|
27229
|
+
}
|
|
27230
|
+
}
|
|
26997
27231
|
}
|
|
26998
27232
|
function validateVolumeName(name, label) {
|
|
26999
27233
|
if (!VOLUME_NAME.test(name) || name.length > VOLUME_NAME_MAX) {
|
|
@@ -27003,14 +27237,17 @@ function validateVolumeName(name, label) {
|
|
|
27003
27237
|
}
|
|
27004
27238
|
function stageMounts(spec) {
|
|
27005
27239
|
const domain = pathDomainOf(spec);
|
|
27006
|
-
const
|
|
27240
|
+
const resolve5 = resolverFor(domain);
|
|
27007
27241
|
const volumes = spec.stageVolumes;
|
|
27008
27242
|
if (domain === "venue") {
|
|
27243
|
+
if (spec.cloneVolume !== void 0) {
|
|
27244
|
+
throw new FenceError("a venue-domain spec must not declare cloneVolume: the venue names both of its volumes in stageVolumes, and a second name for the clone would leave two answers to which one is mounted.");
|
|
27245
|
+
}
|
|
27009
27246
|
if (volumes === void 0) {
|
|
27010
27247
|
throw new FenceError("a venue-domain spec must declare stageVolumes: every writable host path on the venue is mounted noexec, so a bind mount of the staged clone cannot run the binaries an install step downloads (esbuild, swc, sharp, node-gyp \u2014 EACCES). The venue that staged the tree names the volumes it populated; a spec without them would reproduce that EACCES and report it as the developer\u2019s suite failing.");
|
|
27011
27248
|
}
|
|
27012
|
-
|
|
27013
|
-
|
|
27249
|
+
resolve5(spec.clone, "clone");
|
|
27250
|
+
resolve5(spec.jail, "jail");
|
|
27014
27251
|
return [
|
|
27015
27252
|
`--volume=${validateVolumeName(volumes.clone, "the clone volume")}:${GUEST.clone}:rw`,
|
|
27016
27253
|
`--volume=${validateVolumeName(volumes.jail, "the jail volume")}:${GUEST.jail}:rw`
|
|
@@ -27019,20 +27256,24 @@ function stageMounts(spec) {
|
|
|
27019
27256
|
if (volumes !== void 0) {
|
|
27020
27257
|
throw new FenceError("a local-domain spec must not declare stageVolumes: the paths are on this machine and ARE the mount sources, and nothing on the local path populates a volume \u2014 honouring the field would mount an empty clone. Volumes exist for the venue\u2019s noexec host only.");
|
|
27021
27258
|
}
|
|
27259
|
+
const clone = resolve5(spec.clone, "clone");
|
|
27260
|
+
const cloneSource = spec.cloneVolume === void 0 ? clone : validateVolumeName(spec.cloneVolume, "the clone volume");
|
|
27261
|
+
const jail = resolve5(spec.jail, "jail");
|
|
27262
|
+
assertHoldsNoRealHome(jail, "the jail bind source", "stageMounts");
|
|
27022
27263
|
return [
|
|
27023
|
-
`--volume=${
|
|
27024
|
-
`--volume=${
|
|
27264
|
+
`--volume=${cloneSource}:${GUEST.clone}:rw`,
|
|
27265
|
+
`--volume=${jail}:${GUEST.jail}:rw`
|
|
27025
27266
|
];
|
|
27026
27267
|
}
|
|
27027
27268
|
function guestIdentityMounts(spec) {
|
|
27028
27269
|
if (guestUserFlag(spec).length === 0)
|
|
27029
27270
|
return [];
|
|
27030
27271
|
const domain = pathDomainOf(spec);
|
|
27031
|
-
const
|
|
27272
|
+
const resolve5 = resolverFor(domain);
|
|
27032
27273
|
const under = domain === "venue" ? venueJoin : join20;
|
|
27033
|
-
const jail =
|
|
27034
|
-
const passwd =
|
|
27035
|
-
const group =
|
|
27274
|
+
const jail = resolve5(spec.jail, "jail");
|
|
27275
|
+
const passwd = resolve5(under(jail, JAIL_PASSWD_FILE), "the jail passwd file");
|
|
27276
|
+
const group = resolve5(under(jail, JAIL_GROUP_FILE), "the jail group file");
|
|
27036
27277
|
return [`--volume=${passwd}:/etc/passwd:ro`, `--volume=${group}:/etc/group:ro`];
|
|
27037
27278
|
}
|
|
27038
27279
|
function containerArgs(spec, env, opts) {
|
|
@@ -27365,7 +27606,14 @@ async function startProxySidecar(d, allow, idBase, staged, labels) {
|
|
|
27365
27606
|
await waitForProxyReady(d, proxyName);
|
|
27366
27607
|
return {
|
|
27367
27608
|
net: { network: netInt, proxyHost: proxyName, proxyPort: SIDECAR_PROXY_PORT },
|
|
27368
|
-
teardown
|
|
27609
|
+
teardown,
|
|
27610
|
+
// A `docker logs` that fails yields no hosts. That loses the hint and
|
|
27611
|
+
// changes nothing else: the list only ever adds words to a reason.
|
|
27612
|
+
deniedHosts: () => {
|
|
27613
|
+
const logs = dockerSync(d, ["logs", proxyName]);
|
|
27614
|
+
return logs.ok ? parseDeniedHosts(`${logs.stdout}
|
|
27615
|
+
${logs.stderr}`) : [];
|
|
27616
|
+
}
|
|
27369
27617
|
};
|
|
27370
27618
|
} catch (err) {
|
|
27371
27619
|
teardown();
|
|
@@ -27431,6 +27679,7 @@ async function runContainedOn(d, spec, env, opts = {}) {
|
|
|
27431
27679
|
assertDomainDeclared(d, spec);
|
|
27432
27680
|
assertNotAmbientlySteered(d, spec);
|
|
27433
27681
|
assertProxyStagedForVenue(spec, opts.proxyCode);
|
|
27682
|
+
assertTreeHandedToGuest(spec);
|
|
27434
27683
|
const leaks = auditEnv(env);
|
|
27435
27684
|
if (leaks.length > 0) {
|
|
27436
27685
|
throw new FenceError(`refusing to spawn: environment carries credential material (${leaks.join(", ")})`);
|
|
@@ -27451,7 +27700,8 @@ async function runContainedOn(d, spec, env, opts = {}) {
|
|
|
27451
27700
|
net: sidecar?.net,
|
|
27452
27701
|
labels: opts.labels
|
|
27453
27702
|
});
|
|
27454
|
-
|
|
27703
|
+
const result = await spawnWorkload(d, idBase, argv, opts.timeoutMs ?? 9e5);
|
|
27704
|
+
return sidecar ? { ...result, egressDenied: sidecar.deniedHosts() } : result;
|
|
27455
27705
|
} finally {
|
|
27456
27706
|
sidecar?.teardown();
|
|
27457
27707
|
}
|
|
@@ -27532,6 +27782,9 @@ var init_dist = __esm({
|
|
|
27532
27782
|
});
|
|
27533
27783
|
|
|
27534
27784
|
// ../../packages/envrun/dist/labels.js
|
|
27785
|
+
function runLabels(runId, callerLabels) {
|
|
27786
|
+
return { ...callerLabels ?? {}, [RUN_ID_LABEL_KEY]: runId, [RUN_LABEL_KEY]: "term-350" };
|
|
27787
|
+
}
|
|
27535
27788
|
function censusTotal(c) {
|
|
27536
27789
|
return c.containers.length + c.volumes.length + c.networks.length;
|
|
27537
27790
|
}
|
|
@@ -27615,12 +27868,13 @@ function judgeLeaks(peak, after, observation) {
|
|
|
27615
27868
|
note
|
|
27616
27869
|
};
|
|
27617
27870
|
}
|
|
27618
|
-
var RUN_LABEL_KEY, LabelWatch, LEAK_STATES;
|
|
27871
|
+
var RUN_LABEL_KEY, RUN_ID_LABEL_KEY, LabelWatch, LEAK_STATES;
|
|
27619
27872
|
var init_labels = __esm({
|
|
27620
27873
|
"../../packages/envrun/dist/labels.js"() {
|
|
27621
27874
|
"use strict";
|
|
27622
27875
|
init_dist();
|
|
27623
27876
|
RUN_LABEL_KEY = "supergoal.run";
|
|
27877
|
+
RUN_ID_LABEL_KEY = "supergoal.run-id";
|
|
27624
27878
|
LabelWatch = class {
|
|
27625
27879
|
label;
|
|
27626
27880
|
docker;
|
|
@@ -27971,6 +28225,7 @@ http
|
|
|
27971
28225
|
});
|
|
27972
28226
|
|
|
27973
28227
|
// ../../packages/envrun/dist/venue.js
|
|
28228
|
+
import { randomBytes as randomBytes8 } from "crypto";
|
|
27974
28229
|
import { join as join22 } from "path";
|
|
27975
28230
|
function localJailPaths(scratchRoot) {
|
|
27976
28231
|
return {
|
|
@@ -28066,11 +28321,62 @@ function coerceToString(thrown) {
|
|
|
28066
28321
|
return UNCOERCIBLE_THROWN;
|
|
28067
28322
|
}
|
|
28068
28323
|
}
|
|
28324
|
+
function localTreeOwner(ids2 = hostIds()) {
|
|
28325
|
+
if (ids2?.uid !== 0)
|
|
28326
|
+
return void 0;
|
|
28327
|
+
return localGuestUser(ids2) ?? void 0;
|
|
28328
|
+
}
|
|
28329
|
+
function handLocalTreeToGuest(local, owner, chown = chownTree) {
|
|
28330
|
+
if (owner === void 0)
|
|
28331
|
+
return;
|
|
28332
|
+
chown(local.cloneDir, owner);
|
|
28333
|
+
chown(local.scratchRoot, owner);
|
|
28334
|
+
}
|
|
28335
|
+
function localCloneVolumeName(runId, suffix = randomBytes8(4).toString("hex")) {
|
|
28336
|
+
const tail2 = `-${suffix}`;
|
|
28337
|
+
const safe = runId.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
28338
|
+
const head = `th-clone-${safe}`.slice(0, VOLUME_NAME_MAX2 - tail2.length);
|
|
28339
|
+
return validateVolumeName(`${head}${tail2}`, "the local clone volume");
|
|
28340
|
+
}
|
|
28341
|
+
function localCloneFillArgv(from, volume, owner) {
|
|
28342
|
+
const chown = owner === null ? "" : ` && chown -R ${String(owner.uid)}:${String(owner.gid)} /dst`;
|
|
28343
|
+
return [
|
|
28344
|
+
"run",
|
|
28345
|
+
"--rm",
|
|
28346
|
+
"--network=none",
|
|
28347
|
+
`--volume=${from}:/src:ro`,
|
|
28348
|
+
`--volume=${validateVolumeName(volume, "the local clone volume")}:/dst:rw`,
|
|
28349
|
+
"--",
|
|
28350
|
+
STAGE_HELPER_IMAGE,
|
|
28351
|
+
"sh",
|
|
28352
|
+
"-c",
|
|
28353
|
+
`cp -a /src/. /dst/${chown}`
|
|
28354
|
+
];
|
|
28355
|
+
}
|
|
28069
28356
|
function acquireLocalLease(runId) {
|
|
28070
28357
|
const docker3 = localDockerClient();
|
|
28071
28358
|
const containment = selectContainment([containerContainmentOn(docker3)]);
|
|
28072
28359
|
let released = false;
|
|
28073
28360
|
const stagedProxies = [];
|
|
28361
|
+
const owner = localTreeOwner();
|
|
28362
|
+
let cloneVolume;
|
|
28363
|
+
const createdVolumes = [];
|
|
28364
|
+
const fillCloneVolume = (cloneDir) => {
|
|
28365
|
+
const name = localCloneVolumeName(runId);
|
|
28366
|
+
const created = docker3.sync(["volume", "create", `--label=${STAGE_VOLUME_LABEL_KEY}=${runId}`, "--", name], { timeoutMs: LOCAL_VOLUME_CREATE_TIMEOUT_MS });
|
|
28367
|
+
if (created.error || created.status !== 0) {
|
|
28368
|
+
throw new ContainmentRefusalError(`could not create the clone volume ${name} on the local daemon: ${(created.error?.message ?? created.stderr).trim().slice(0, 300)}`);
|
|
28369
|
+
}
|
|
28370
|
+
createdVolumes.push(name);
|
|
28371
|
+
const from = resolverFor("local")(cloneDir, "clone");
|
|
28372
|
+
const filled = docker3.sync(localCloneFillArgv(from, name, localGuestUser()), {
|
|
28373
|
+
timeoutMs: LOCAL_CLONE_FILL_TIMEOUT_MS
|
|
28374
|
+
});
|
|
28375
|
+
if (filled.error || filled.status !== 0) {
|
|
28376
|
+
throw new ContainmentRefusalError(`could not copy the clone into its volume ${name}: ${(filled.error?.message ?? filled.stderr).trim().slice(0, 300)}`);
|
|
28377
|
+
}
|
|
28378
|
+
return name;
|
|
28379
|
+
};
|
|
28074
28380
|
const lease = {
|
|
28075
28381
|
kind: "local",
|
|
28076
28382
|
runId,
|
|
@@ -28080,28 +28386,32 @@ function acquireLocalLease(runId) {
|
|
|
28080
28386
|
// the paths are on this machine AND `canonical()` is what should resolve
|
|
28081
28387
|
// them, which is the behaviour every local run has always had.
|
|
28082
28388
|
pathDomain: "local",
|
|
28389
|
+
// Spread rather than `guestUser: owner`, so a non-root lease has no such key
|
|
28390
|
+
// at all — the same object every local run has always had.
|
|
28391
|
+
...owner === void 0 ? {} : { guestUser: owner },
|
|
28083
28392
|
get released() {
|
|
28084
28393
|
return released;
|
|
28085
28394
|
},
|
|
28086
|
-
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28090
|
-
|
|
28091
|
-
|
|
28092
|
-
|
|
28093
|
-
|
|
28094
|
-
|
|
28095
|
-
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28395
|
+
get cloneVolume() {
|
|
28396
|
+
return cloneVolume;
|
|
28397
|
+
},
|
|
28398
|
+
stage: (local) => {
|
|
28399
|
+
if (released) {
|
|
28400
|
+
return Promise.reject(new LeaseReleasedError("this lease was already released, so a clone volume made now would never be removed."));
|
|
28401
|
+
}
|
|
28402
|
+
try {
|
|
28403
|
+
handLocalTreeToGuest(local, owner);
|
|
28404
|
+
cloneVolume = fillCloneVolume(local.cloneDir);
|
|
28405
|
+
} catch (err) {
|
|
28406
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
28407
|
+
}
|
|
28408
|
+
return Promise.resolve({
|
|
28099
28409
|
cloneDir: local.cloneDir,
|
|
28100
28410
|
scratchRoot: local.scratchRoot,
|
|
28101
28411
|
previewDir: local.previewDir,
|
|
28102
28412
|
...localJailPaths(local.scratchRoot)
|
|
28103
|
-
})
|
|
28104
|
-
|
|
28413
|
+
});
|
|
28414
|
+
},
|
|
28105
28415
|
// The containment function verbatim, which is the point of the barrel
|
|
28106
28416
|
// re-export rather than a copy here: this venue's daemon and this process
|
|
28107
28417
|
// share a filesystem, so the directory it makes under `tmpdir()` is already
|
|
@@ -28171,6 +28481,25 @@ function acquireLocalLease(runId) {
|
|
|
28171
28481
|
const staged = stagedProxies.splice(0, stagedProxies.length);
|
|
28172
28482
|
for (const s of staged)
|
|
28173
28483
|
s.cleanup();
|
|
28484
|
+
const volumeFailures = [];
|
|
28485
|
+
for (const name of createdVolumes.splice(0, createdVolumes.length)) {
|
|
28486
|
+
const removed = docker3.sync(["volume", "rm", "-f", "--", name], {
|
|
28487
|
+
timeoutMs: LOCAL_VOLUME_CREATE_TIMEOUT_MS
|
|
28488
|
+
});
|
|
28489
|
+
if (removed.error || removed.status !== 0) {
|
|
28490
|
+
volumeFailures.push(`${name}: ${(removed.error?.message ?? removed.stderr).trim().slice(0, 200)}`);
|
|
28491
|
+
}
|
|
28492
|
+
}
|
|
28493
|
+
cloneVolume = void 0;
|
|
28494
|
+
if (volumeFailures.length > 0) {
|
|
28495
|
+
return Promise.resolve({
|
|
28496
|
+
kind: "local",
|
|
28497
|
+
released: true,
|
|
28498
|
+
alreadyReleased: false,
|
|
28499
|
+
error: `could not remove the clone volume: ${volumeFailures.join("; ")}`,
|
|
28500
|
+
detail: `the lease is closed, but a clone volume remains (label ${STAGE_VOLUME_LABEL_KEY})`
|
|
28501
|
+
});
|
|
28502
|
+
}
|
|
28174
28503
|
return Promise.resolve({
|
|
28175
28504
|
kind: "local",
|
|
28176
28505
|
released: true,
|
|
@@ -28182,7 +28511,7 @@ function acquireLocalLease(runId) {
|
|
|
28182
28511
|
};
|
|
28183
28512
|
return lease;
|
|
28184
28513
|
}
|
|
28185
|
-
var VenueRollbackError, UNREADABLE_MESSAGE, UNREADABLE_NAME, UNCOERCIBLE_THROWN, UNDESCRIBABLE_THROWN, UNLISTABLE_ROLLBACK_FAILURES, RELEASED_LEASE_CENSUS_REASON, LeaseReleasedError;
|
|
28514
|
+
var VenueRollbackError, UNREADABLE_MESSAGE, UNREADABLE_NAME, UNCOERCIBLE_THROWN, UNDESCRIBABLE_THROWN, UNLISTABLE_ROLLBACK_FAILURES, RELEASED_LEASE_CENSUS_REASON, LeaseReleasedError, STAGE_HELPER_IMAGE, STAGE_VOLUME_LABEL_KEY, VOLUME_NAME_MAX2, LOCAL_VOLUME_CREATE_TIMEOUT_MS, LOCAL_CLONE_FILL_TIMEOUT_MS;
|
|
28186
28515
|
var init_venue = __esm({
|
|
28187
28516
|
"../../packages/envrun/dist/venue.js"() {
|
|
28188
28517
|
"use strict";
|
|
@@ -28207,6 +28536,11 @@ var init_venue = __esm({
|
|
|
28207
28536
|
LeaseReleasedError = class extends Error {
|
|
28208
28537
|
name = "LeaseReleasedError";
|
|
28209
28538
|
};
|
|
28539
|
+
STAGE_HELPER_IMAGE = "busybox:1.37.0";
|
|
28540
|
+
STAGE_VOLUME_LABEL_KEY = "terminalhire.stage";
|
|
28541
|
+
VOLUME_NAME_MAX2 = 128;
|
|
28542
|
+
LOCAL_VOLUME_CREATE_TIMEOUT_MS = 3e4;
|
|
28543
|
+
LOCAL_CLONE_FILL_TIMEOUT_MS = 6e5;
|
|
28210
28544
|
}
|
|
28211
28545
|
});
|
|
28212
28546
|
|
|
@@ -28305,7 +28639,7 @@ function imageForRuntime(runtime, override, version, variant) {
|
|
|
28305
28639
|
if (!shape) {
|
|
28306
28640
|
throw new RunRefusalError(`no container image is mapped for runtime ${JSON.stringify(runtime)}. Refusing to run it in the Node image: a bare "command not found" exits 127, which classifyVerification already owns as ours \u2014 but a test script that RUNS and fails inside on the missing tool exits with its own status and prints to stdout, and that falls through to tests-failed \u2014 a false red blamed on the repo.`);
|
|
28307
28641
|
}
|
|
28308
|
-
if (version === void 0 || version === null)
|
|
28642
|
+
if (version === void 0 || version === null || shape.fixed)
|
|
28309
28643
|
return unversionedImage(shape);
|
|
28310
28644
|
if (!TAG_VERSION.test(version)) {
|
|
28311
28645
|
throw new RunRefusalError(`runtime version ${JSON.stringify(version)} is not a bare version, so no image tag can be built from it. Refusing rather than booting the default: the repo asked for a version, and supplying a different one silently is what TERM-643 fixed.`);
|
|
@@ -28333,8 +28667,8 @@ function resolvePublishedImage(image, runtime, version, variant) {
|
|
|
28333
28667
|
throw new RunRefusalError(`the repo declares ${runtime} ${version}, and no image is published at ${image}` + (shape && shape.declaredIsFloor ? `. Our default is ${shape.defaultVersion}, which is OLDER than that, so falling back would run the repo under a toolchain it says it cannot use` : `. ${runtime} treats a declared version as an exact pin, not a minimum, so a different one is a different environment`) + ". Refusing rather than booting a version the repo did not ask for \u2014 that substitution is what made this class of failure unattributable (TERM-643). Pass an explicit image to override.");
|
|
28334
28668
|
}
|
|
28335
28669
|
function resolveImageForSpec(spec, override) {
|
|
28336
|
-
const declared = spec.runtimeVersion;
|
|
28337
28670
|
const variant = imageVariantFor(spec);
|
|
28671
|
+
const declared = shapeFor(spec.runtime, variant)?.fixed ? null : spec.runtimeVersion;
|
|
28338
28672
|
const image = imageForRuntime(spec.runtime, override, declared, variant);
|
|
28339
28673
|
const constructed = (() => {
|
|
28340
28674
|
if (declared === null)
|
|
@@ -28351,12 +28685,19 @@ function resolveImageForSpec(spec, override) {
|
|
|
28351
28685
|
return image;
|
|
28352
28686
|
}
|
|
28353
28687
|
function installEnvironmentFailureNote(install, image) {
|
|
28354
|
-
const base = `the install step exited ${String(install.exitCode)}, so the test command was never invoked. The repo has not been judged; this is an environment failure
|
|
28688
|
+
const base = `the install step exited ${String(install.exitCode)}, so the test command was never invoked. The repo has not been judged; this is an environment failure.` + refusedHostsSentence(install.egressDenied ?? []);
|
|
28355
28689
|
if (!MISSING_IMAGE_SHAPE.test(`${install.stdout}
|
|
28356
28690
|
${install.stderr}`))
|
|
28357
28691
|
return base;
|
|
28358
28692
|
return `${base} The container image ${image} is not present on this machine \u2014 run \`docker pull ${image}\` and try again.`;
|
|
28359
28693
|
}
|
|
28694
|
+
function refusedHostsSentence(hosts) {
|
|
28695
|
+
if (hosts.length === 0)
|
|
28696
|
+
return "";
|
|
28697
|
+
const named = hosts.slice(0, MAX_NAMED_REFUSED_HOSTS).join(", ");
|
|
28698
|
+
const rest = hosts.length - MAX_NAMED_REFUSED_HOSTS;
|
|
28699
|
+
return ` During install the network proxy refused ${named}` + (rest > 0 ? ` and ${String(rest)} more` : "") + ". Install reaches only the hosts on its allowlist, so a dependency whose install script downloads from any other host cannot get what it asked for.";
|
|
28700
|
+
}
|
|
28360
28701
|
function classifySingleRun(run2) {
|
|
28361
28702
|
return classifyVerification(run2).outcome;
|
|
28362
28703
|
}
|
|
@@ -28418,6 +28759,8 @@ async function runEnvironmentSpec(req) {
|
|
|
28418
28759
|
// TERM-913: the third answer about that machine — which volumes the
|
|
28419
28760
|
// fence mounts in place of the noexec stage. Undefined on a local lease.
|
|
28420
28761
|
stageVolumes: req.lease.stageVolumes,
|
|
28762
|
+
// TERM-1106: the local venue's copy of the clone. Undefined on a hosted lease.
|
|
28763
|
+
cloneVolume: req.lease.cloneVolume,
|
|
28421
28764
|
env,
|
|
28422
28765
|
image,
|
|
28423
28766
|
labels,
|
|
@@ -28452,6 +28795,8 @@ async function runEnvironmentSpec(req) {
|
|
|
28452
28795
|
// TERM-913: the third answer about that machine — which volumes the
|
|
28453
28796
|
// fence mounts in place of the noexec stage. Undefined on a local lease.
|
|
28454
28797
|
stageVolumes: req.lease.stageVolumes,
|
|
28798
|
+
// TERM-1106: the local venue's copy of the clone. Undefined on a hosted lease.
|
|
28799
|
+
cloneVolume: req.lease.cloneVolume,
|
|
28455
28800
|
env,
|
|
28456
28801
|
image,
|
|
28457
28802
|
labels,
|
|
@@ -28511,6 +28856,7 @@ async function runStep(containment, r) {
|
|
|
28511
28856
|
pathDomain: r.pathDomain,
|
|
28512
28857
|
guestUser: r.guestUser,
|
|
28513
28858
|
stageVolumes: r.stageVolumes,
|
|
28859
|
+
cloneVolume: r.cloneVolume,
|
|
28514
28860
|
program: "/bin/sh",
|
|
28515
28861
|
args: ["-c", withUserScriptPath(r.command)]
|
|
28516
28862
|
};
|
|
@@ -28530,10 +28876,11 @@ async function runStep(containment, r) {
|
|
|
28530
28876
|
stdout: res.stdout,
|
|
28531
28877
|
stderr: res.stderr,
|
|
28532
28878
|
timedOut: res.timedOut,
|
|
28533
|
-
wallMs: Date.now() - startedAt
|
|
28879
|
+
wallMs: Date.now() - startedAt,
|
|
28880
|
+
...res.egressDenied && res.egressDenied.length > 0 ? { egressDenied: [...new Set(res.egressDenied)] } : {}
|
|
28534
28881
|
};
|
|
28535
28882
|
}
|
|
28536
|
-
var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, JVM_GRADLE_IMAGE, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE;
|
|
28883
|
+
var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, JVM_GRADLE_IMAGE, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE, MAX_NAMED_REFUSED_HOSTS;
|
|
28537
28884
|
var init_execute = __esm({
|
|
28538
28885
|
"../../packages/envrun/dist/execute.js"() {
|
|
28539
28886
|
"use strict";
|
|
@@ -28620,6 +28967,26 @@ var init_execute = __esm({
|
|
|
28620
28967
|
suffix: "",
|
|
28621
28968
|
defaultVersion: "21",
|
|
28622
28969
|
declaredIsFloor: true
|
|
28970
|
+
},
|
|
28971
|
+
/**
|
|
28972
|
+
* TERM-1123. OUR image (`packages/envrun/images/cpp/Dockerfile`): Debian bookworm
|
|
28973
|
+
* with the C/C++ toolchain, CMake, Meson and the common -dev libraries baked in.
|
|
28974
|
+
* No official image carries that set. Libraries are baked rather than installed per
|
|
28975
|
+
* run because the install step has network only through the allowlist, and apt is
|
|
28976
|
+
* not on it; a repo needing a library the image lacks gets `test-command-unavailable`
|
|
28977
|
+
* (`isMissingSystemDependency`), never a red.
|
|
28978
|
+
*
|
|
28979
|
+
* `fixed`: a C/C++ repo declares no compiler version envspec can read, so there is
|
|
28980
|
+
* nothing to derive and nothing to probe. The tag moves only when the Dockerfile
|
|
28981
|
+
* does. It lives in a PUBLIC repository, apart from the private venue images, so a
|
|
28982
|
+
* developer's own Docker can pull it with no credential.
|
|
28983
|
+
*/
|
|
28984
|
+
cpp: {
|
|
28985
|
+
repository: "us-east1-docker.pkg.dev/terminalhire-pool/runtime-images/cpp",
|
|
28986
|
+
suffix: "",
|
|
28987
|
+
defaultVersion: "1",
|
|
28988
|
+
declaredIsFloor: false,
|
|
28989
|
+
fixed: true
|
|
28623
28990
|
}
|
|
28624
28991
|
};
|
|
28625
28992
|
JVM_GRADLE_IMAGE = {
|
|
@@ -28640,6 +29007,7 @@ var init_execute = __esm({
|
|
|
28640
29007
|
};
|
|
28641
29008
|
manifestProbe = dockerManifestProbe;
|
|
28642
29009
|
MISSING_IMAGE_SHAPE = /Unable to find image ['"][^'"]*['"] locally/i;
|
|
29010
|
+
MAX_NAMED_REFUSED_HOSTS = 5;
|
|
28643
29011
|
}
|
|
28644
29012
|
});
|
|
28645
29013
|
|
|
@@ -28714,7 +29082,7 @@ var init_dsse = __esm({
|
|
|
28714
29082
|
});
|
|
28715
29083
|
|
|
28716
29084
|
// ../../packages/attest/dist/sealedbox.js
|
|
28717
|
-
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as
|
|
29085
|
+
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes9 } from "crypto";
|
|
28718
29086
|
var init_sealedbox = __esm({
|
|
28719
29087
|
"../../packages/attest/dist/sealedbox.js"() {
|
|
28720
29088
|
"use strict";
|
|
@@ -28724,7 +29092,7 @@ var init_sealedbox = __esm({
|
|
|
28724
29092
|
});
|
|
28725
29093
|
|
|
28726
29094
|
// ../../packages/attest/dist/aead.js
|
|
28727
|
-
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as
|
|
29095
|
+
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes10 } from "crypto";
|
|
28728
29096
|
var init_aead = __esm({
|
|
28729
29097
|
"../../packages/attest/dist/aead.js"() {
|
|
28730
29098
|
"use strict";
|
|
@@ -29444,7 +29812,7 @@ var init_result = __esm({
|
|
|
29444
29812
|
});
|
|
29445
29813
|
|
|
29446
29814
|
// ../../packages/envrun/dist/attestation.js
|
|
29447
|
-
import { createHash as createHash8, randomBytes as
|
|
29815
|
+
import { createHash as createHash8, randomBytes as randomBytes11 } from "crypto";
|
|
29448
29816
|
function contradicts(outcome, counts, exitCode) {
|
|
29449
29817
|
const budget = OUTCOME_TO_BUDGET[outcome];
|
|
29450
29818
|
if (budget === null)
|
|
@@ -29624,7 +29992,7 @@ function toAcceptancePredicate(pair, opts = {}) {
|
|
|
29624
29992
|
// RepoDigest (`repo@sha256:…`) carries the repo name and the content hash, and the
|
|
29625
29993
|
// tag it drops is the part a registry can re-point (TERM-893).
|
|
29626
29994
|
enclave_measurement: localMeasurement(patched.containerImageDigest),
|
|
29627
|
-
nonce: opts.nonce ??
|
|
29995
|
+
nonce: opts.nonce ?? randomBytes11(16).toString("hex"),
|
|
29628
29996
|
run_policy: { max_attempts: opts.maxAttempts ?? 1, budget_outcome: budget }
|
|
29629
29997
|
}
|
|
29630
29998
|
};
|
|
@@ -30602,12 +30970,12 @@ function socketErrorReason(err) {
|
|
|
30602
30970
|
return err.name;
|
|
30603
30971
|
}
|
|
30604
30972
|
function fetchAttestationOverTls(req) {
|
|
30605
|
-
return new Promise((
|
|
30973
|
+
return new Promise((resolve5) => {
|
|
30606
30974
|
let whole;
|
|
30607
30975
|
const settle = (reading) => {
|
|
30608
30976
|
if (whole !== void 0)
|
|
30609
30977
|
clearTimeout(whole);
|
|
30610
|
-
|
|
30978
|
+
resolve5(reading);
|
|
30611
30979
|
};
|
|
30612
30980
|
let cert;
|
|
30613
30981
|
let key;
|
|
@@ -31808,7 +32176,7 @@ function makeLease(p) {
|
|
|
31808
32176
|
}
|
|
31809
32177
|
};
|
|
31810
32178
|
}
|
|
31811
|
-
var SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT,
|
|
32179
|
+
var SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT, VOLUME_CREATE_TIMEOUT_MS, EXEC_PROBE_TIMEOUT_MS, POPULATE_TIMEOUT_MS, STAGE_PROOF_PREFIX, IAP_NOT_READY, IAP_BACKEND_UNREACHABLE, IAP_DENIED, TERMINAL_GCP, INSTANCE_NOT_RUNNING, PREEMPTED, HOST_KEY_MISMATCH, SSH_KEY_NOT_READY, DAEMON_NOT_READY, SSH_NOT_ANSWERING, CS_ATTEST_PORT, CS_DOCKER_PORT, CS_READY_BUDGET_MS, CS_ATTEST_TIMEOUT_MS, CS_PULL_TIMEOUT_MS, CS_ATTEST_INTERVAL_MS, OPENSSL_TIMEOUT_MS, CS_RUN_ARGS, CS_GUEST_USER, CS_STAGE_ROOT, CLIENT_CERT_STAND_IN;
|
|
31812
32180
|
var init_hostedVenue = __esm({
|
|
31813
32181
|
"../../packages/envrun/dist/hostedVenue.js"() {
|
|
31814
32182
|
"use strict";
|
|
@@ -31957,18 +32325,18 @@ var init_hostedVenue = __esm({
|
|
|
31957
32325
|
for (let i = 0; i < n; i += 1) {
|
|
31958
32326
|
const server = createServer();
|
|
31959
32327
|
servers.push(server);
|
|
31960
|
-
await new Promise((
|
|
32328
|
+
await new Promise((resolve5, reject) => {
|
|
31961
32329
|
server.once("error", reject);
|
|
31962
32330
|
server.listen(0, "localhost", () => {
|
|
31963
|
-
|
|
32331
|
+
resolve5();
|
|
31964
32332
|
});
|
|
31965
32333
|
});
|
|
31966
32334
|
}
|
|
31967
32335
|
return servers.map((s) => s.address().port);
|
|
31968
32336
|
} finally {
|
|
31969
|
-
await Promise.all(servers.map((s) => new Promise((
|
|
32337
|
+
await Promise.all(servers.map((s) => new Promise((resolve5) => {
|
|
31970
32338
|
s.close(() => {
|
|
31971
|
-
|
|
32339
|
+
resolve5();
|
|
31972
32340
|
});
|
|
31973
32341
|
})));
|
|
31974
32342
|
}
|
|
@@ -31982,11 +32350,9 @@ var init_hostedVenue = __esm({
|
|
|
31982
32350
|
VENUE_SSH_USER = "th-runner";
|
|
31983
32351
|
GCE_METADATA_IDENTITY_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity";
|
|
31984
32352
|
COMPACT_JWT = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
31985
|
-
STAGE_HELPER_IMAGE = "busybox:1.37.0";
|
|
31986
32353
|
VOLUME_CREATE_TIMEOUT_MS = 3e4;
|
|
31987
32354
|
EXEC_PROBE_TIMEOUT_MS = 12e4;
|
|
31988
32355
|
POPULATE_TIMEOUT_MS = STAGE_PUSH_TIMEOUT_MS;
|
|
31989
|
-
STAGE_VOLUME_LABEL_KEY = "terminalhire.stage";
|
|
31990
32356
|
STAGE_PROOF_PREFIX = ".th-proven-";
|
|
31991
32357
|
IAP_NOT_READY = /\b4047\s*[:\]]/;
|
|
31992
32358
|
IAP_BACKEND_UNREACHABLE = /\b4003\s*[:\]]/;
|
|
@@ -32594,6 +32960,9 @@ function detectRuntime(repo) {
|
|
|
32594
32960
|
if (candidates.length === 0 && repo.listFiles("").some((f) => f.endsWith(".java"))) {
|
|
32595
32961
|
candidates.push("jvm");
|
|
32596
32962
|
}
|
|
32963
|
+
if (!candidates.includes("cpp") && MAKEFILE_NAMES.some((file) => repo.exists(file)) && repo.listFiles("").some((f) => C_FAMILY_SOURCE.test(f))) {
|
|
32964
|
+
candidates.push("cpp");
|
|
32965
|
+
}
|
|
32597
32966
|
return { runtime: candidates[0] ?? "unknown", candidates };
|
|
32598
32967
|
}
|
|
32599
32968
|
function readJsonObject(repo, path5) {
|
|
@@ -32746,6 +33115,44 @@ function rehearse(command) {
|
|
|
32746
33115
|
const home = '"$R/.terminalhire-rehearsal-home"';
|
|
32747
33116
|
return `{ R="$HOME"; rm -rf ${copy} ${home}; mkdir -p ${home} && cp -R . ${copy} && (cd ${copy} && HOME=${home} GRADLE_USER_HOME="$R/.gradle" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -Duser.home=$R/.terminalhire-rehearsal-home" ${command}); rm -rf ${copy} ${home}; true; }`;
|
|
32748
33117
|
}
|
|
33118
|
+
function cmakeCode(text) {
|
|
33119
|
+
return text.replace(/#\[(=*)\[[\s\S]*?\]\1\]/g, "").replace(/#.*$/gm, "");
|
|
33120
|
+
}
|
|
33121
|
+
function makeTargets(text) {
|
|
33122
|
+
const targets = /* @__PURE__ */ new Set();
|
|
33123
|
+
for (const line of text.split("\n")) {
|
|
33124
|
+
if (line.startsWith(" "))
|
|
33125
|
+
continue;
|
|
33126
|
+
const rule = /^([^:=#\t][^:=#]*?)[ \t]*::?(?!:?=)/.exec(line);
|
|
33127
|
+
if (rule)
|
|
33128
|
+
for (const name of rule[1].trim().split(/\s+/))
|
|
33129
|
+
targets.add(name);
|
|
33130
|
+
}
|
|
33131
|
+
return targets;
|
|
33132
|
+
}
|
|
33133
|
+
function cppCommands(repo) {
|
|
33134
|
+
const cmake = repo.readText("CMakeLists.txt");
|
|
33135
|
+
if (cmake !== null) {
|
|
33136
|
+
return CMAKE_DECLARES_TESTS.test(cmakeCode(cmake)) ? {
|
|
33137
|
+
install: "cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON",
|
|
33138
|
+
test: "cmake --build build --parallel && ctest --test-dir build --output-on-failure"
|
|
33139
|
+
} : { install: null, test: null };
|
|
33140
|
+
}
|
|
33141
|
+
const meson = repo.readText("meson.build");
|
|
33142
|
+
if (meson !== null) {
|
|
33143
|
+
return /\btest\s*\(/.test(meson) ? { install: "meson setup build", test: "meson test -C build" } : { install: null, test: null };
|
|
33144
|
+
}
|
|
33145
|
+
const name = repo.listNames("").find((file) => MAKEFILE_NAMES.includes(file));
|
|
33146
|
+
const makefile = name === void 0 ? null : repo.readText(name);
|
|
33147
|
+
if (makefile !== null) {
|
|
33148
|
+
const targets = makeTargets(makefile);
|
|
33149
|
+
if (targets.has("test"))
|
|
33150
|
+
return { install: null, test: "make test" };
|
|
33151
|
+
if (targets.has("check"))
|
|
33152
|
+
return { install: null, test: "make check" };
|
|
33153
|
+
}
|
|
33154
|
+
return { install: null, test: null };
|
|
33155
|
+
}
|
|
32749
33156
|
function otherRuntimeCommands(repo, runtime) {
|
|
32750
33157
|
switch (runtime) {
|
|
32751
33158
|
case "go":
|
|
@@ -32789,6 +33196,8 @@ function otherRuntimeCommands(repo, runtime) {
|
|
|
32789
33196
|
test: `${wrapper} test --offline --no-build-cache`
|
|
32790
33197
|
};
|
|
32791
33198
|
}
|
|
33199
|
+
case "cpp":
|
|
33200
|
+
return cppCommands(repo);
|
|
32792
33201
|
default:
|
|
32793
33202
|
return { install: null, test: null };
|
|
32794
33203
|
}
|
|
@@ -32855,7 +33264,7 @@ function matchFirst(text, pattern) {
|
|
|
32855
33264
|
const match2 = pattern.exec(text);
|
|
32856
33265
|
return match2 === null ? null : match2[1];
|
|
32857
33266
|
}
|
|
32858
|
-
var RUNTIME_MANIFESTS, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, EXACT_VERSION;
|
|
33267
|
+
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
32859
33268
|
var init_manifest2 = __esm({
|
|
32860
33269
|
"../../packages/envspec/dist/manifest.js"() {
|
|
32861
33270
|
"use strict";
|
|
@@ -32866,12 +33275,19 @@ var init_manifest2 = __esm({
|
|
|
32866
33275
|
{ runtime: "rust", files: ["Cargo.toml"] },
|
|
32867
33276
|
{ runtime: "jvm", files: ["pom.xml", "build.gradle", "build.gradle.kts"] },
|
|
32868
33277
|
{ runtime: "ruby", files: ["Gemfile"] },
|
|
32869
|
-
{ runtime: "dotnet", files: [] }
|
|
33278
|
+
{ runtime: "dotnet", files: [] },
|
|
33279
|
+
// Last, so an earlier runtime keeps precedence: a Python package with a C
|
|
33280
|
+
// extension built by CMake is still a Python repo, with cpp recorded second.
|
|
33281
|
+
{ runtime: "cpp", files: ["CMakeLists.txt", "meson.build"] }
|
|
32870
33282
|
];
|
|
32871
|
-
|
|
33283
|
+
MAKEFILE_NAMES = ["GNUmakefile", "Makefile", "makefile"];
|
|
33284
|
+
C_FAMILY_SOURCE = /\.(?:c|cc|cpp|cxx|h|hpp|hh)$/i;
|
|
33285
|
+
MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
|
|
32872
33286
|
JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
32873
33287
|
NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
|
|
32874
33288
|
REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
|
|
33289
|
+
CPP_BUILD_FILES = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
|
|
33290
|
+
CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
|
|
32875
33291
|
EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
|
|
32876
33292
|
}
|
|
32877
33293
|
});
|
|
@@ -32944,11 +33360,11 @@ var init_references = __esm({
|
|
|
32944
33360
|
});
|
|
32945
33361
|
|
|
32946
33362
|
// ../../packages/envspec/dist/repo.js
|
|
32947
|
-
import { readdirSync as
|
|
32948
|
-
import { join as join25, relative as relative2, sep as
|
|
33363
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
33364
|
+
import { join as join25, relative as relative2, sep as sep6 } from "path";
|
|
32949
33365
|
function createRepoReader(repoPath) {
|
|
32950
33366
|
const resolveIn = (relativePath) => relativePath === "" ? repoPath : join25(repoPath, relativePath);
|
|
32951
|
-
const toPosix = (absolute) => relative2(repoPath, absolute).split(
|
|
33367
|
+
const toPosix = (absolute) => relative2(repoPath, absolute).split(sep6).join("/");
|
|
32952
33368
|
const readText = (relativePath) => {
|
|
32953
33369
|
try {
|
|
32954
33370
|
return readFileSync12(resolveIn(relativePath), "utf8");
|
|
@@ -32958,7 +33374,7 @@ function createRepoReader(repoPath) {
|
|
|
32958
33374
|
};
|
|
32959
33375
|
const statOf = (relativePath) => {
|
|
32960
33376
|
try {
|
|
32961
|
-
return
|
|
33377
|
+
return statSync5(resolveIn(relativePath));
|
|
32962
33378
|
} catch {
|
|
32963
33379
|
return null;
|
|
32964
33380
|
}
|
|
@@ -32970,7 +33386,7 @@ function createRepoReader(repoPath) {
|
|
|
32970
33386
|
return;
|
|
32971
33387
|
let names;
|
|
32972
33388
|
try {
|
|
32973
|
-
names =
|
|
33389
|
+
names = readdirSync3(dir);
|
|
32974
33390
|
} catch {
|
|
32975
33391
|
return;
|
|
32976
33392
|
}
|
|
@@ -33005,7 +33421,7 @@ function createRepoReader(repoPath) {
|
|
|
33005
33421
|
if (st === null || !st.isDirectory())
|
|
33006
33422
|
return [];
|
|
33007
33423
|
try {
|
|
33008
|
-
return
|
|
33424
|
+
return readdirSync3(resolveIn(relativeDir)).slice().sort();
|
|
33009
33425
|
} catch {
|
|
33010
33426
|
return [];
|
|
33011
33427
|
}
|
|
@@ -33330,7 +33746,7 @@ function deriveFromReader(repo) {
|
|
|
33330
33746
|
});
|
|
33331
33747
|
}
|
|
33332
33748
|
const version = deriveRuntimeVersion(repo, runtime, job);
|
|
33333
|
-
if (version.value === null && runtime !== "unknown") {
|
|
33749
|
+
if (version.value === null && runtime !== "unknown" && runtime !== "cpp") {
|
|
33334
33750
|
unresolved.push({
|
|
33335
33751
|
kind: "runtime-version-undeclared",
|
|
33336
33752
|
runtime,
|
|
@@ -33472,6 +33888,8 @@ function testSearchPaths(runtime, workflowFiles) {
|
|
|
33472
33888
|
paths.push("Gemfile", "Rakefile");
|
|
33473
33889
|
if (runtime === "jvm")
|
|
33474
33890
|
paths.push(...JVM_BUILD_FILES);
|
|
33891
|
+
if (runtime === "cpp")
|
|
33892
|
+
paths.push(...CPP_BUILD_FILES);
|
|
33475
33893
|
if (runtime === "unknown")
|
|
33476
33894
|
paths.push(...MANIFEST_FILENAMES);
|
|
33477
33895
|
return [...new Set(paths)].sort();
|
|
@@ -33492,8 +33910,24 @@ function describeTestSearch(repo, runtime, workflowFiles) {
|
|
|
33492
33910
|
if (runtime === "jvm" && !JVM_BUILD_FILES.some((file) => repo.exists(file))) {
|
|
33493
33911
|
return `${ciPart}, and the repo has Java sources but no build file (${JVM_BUILD_FILES.join(", ")}), so there is no build to run tests with. Compiling the sources with \`javac\` alone would run no tests`;
|
|
33494
33912
|
}
|
|
33913
|
+
if (runtime === "cpp")
|
|
33914
|
+
return `${ciPart}, and ${describeCppSearch(repo)}`;
|
|
33495
33915
|
return `${ciPart}, and no ${runtime} test convention applies to this tree`;
|
|
33496
33916
|
}
|
|
33917
|
+
function describeCppSearch(repo) {
|
|
33918
|
+
const cmake = repo.readText("CMakeLists.txt");
|
|
33919
|
+
if (cmake !== null && !CMAKE_DECLARES_TESTS.test(cmakeCode(cmake))) {
|
|
33920
|
+
return "`CMakeLists.txt` registers no tests: no `enable_testing()`, `include(CTest)` or `add_test()`";
|
|
33921
|
+
}
|
|
33922
|
+
if (cmake === null && repo.exists("meson.build")) {
|
|
33923
|
+
return "`meson.build` declares no `test()`";
|
|
33924
|
+
}
|
|
33925
|
+
const makefile = repo.listNames("").find((name) => MAKEFILE_NAMES.includes(name));
|
|
33926
|
+
if (cmake === null && makefile !== void 0) {
|
|
33927
|
+
return `\`${makefile}\` has no \`test\` or \`check\` target`;
|
|
33928
|
+
}
|
|
33929
|
+
return `no C/C++ test convention applies to this tree (looked in ${CPP_BUILD_FILES.join(", ")})`;
|
|
33930
|
+
}
|
|
33497
33931
|
function installSearchPaths(runtime, workflowFiles) {
|
|
33498
33932
|
const paths = [...workflowFiles];
|
|
33499
33933
|
if (runtime === "node")
|
|
@@ -33502,6 +33936,8 @@ function installSearchPaths(runtime, workflowFiles) {
|
|
|
33502
33936
|
paths.push("poetry.lock", "pyproject.toml", "requirements.txt", "uv.lock");
|
|
33503
33937
|
if (runtime === "jvm")
|
|
33504
33938
|
paths.push(...JVM_BUILD_FILES);
|
|
33939
|
+
if (runtime === "cpp")
|
|
33940
|
+
paths.push(...CPP_BUILD_FILES);
|
|
33505
33941
|
if (runtime === "unknown")
|
|
33506
33942
|
paths.push(...MANIFEST_FILENAMES);
|
|
33507
33943
|
return [...new Set(paths)].sort();
|
|
@@ -33583,15 +34019,20 @@ var init_dist3 = __esm({
|
|
|
33583
34019
|
// ../../packages/envrun/dist/gradleTestSummary.js
|
|
33584
34020
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
33585
34021
|
import { join as join26 } from "path";
|
|
33586
|
-
function stageGradleTestSummary(jailHome) {
|
|
34022
|
+
function stageGradleTestSummary(jailHome, guestUser, io = {}) {
|
|
33587
34023
|
const dir = join26(jailHome, ".gradle", "init.d");
|
|
33588
34024
|
mkdirSync5(dir, { recursive: true });
|
|
33589
34025
|
writeFileSync14(join26(dir, "terminalhire-test-summary.gradle"), GRADLE_TEST_SUMMARY_INIT);
|
|
34026
|
+
const ids2 = io.ids === void 0 ? hostIds() : io.ids;
|
|
34027
|
+
if (ids2?.uid === 0 && guestUser && guestUser.uid !== 0) {
|
|
34028
|
+
(io.chown ?? chownTree)(join26(jailHome, ".gradle"), guestUser);
|
|
34029
|
+
}
|
|
33590
34030
|
}
|
|
33591
34031
|
var GRADLE_TEST_MARKER, GRADLE_TEST_SUMMARY_INIT;
|
|
33592
34032
|
var init_gradleTestSummary = __esm({
|
|
33593
34033
|
"../../packages/envrun/dist/gradleTestSummary.js"() {
|
|
33594
34034
|
"use strict";
|
|
34035
|
+
init_dist();
|
|
33595
34036
|
GRADLE_TEST_MARKER = "terminalhire-gradle-tests:";
|
|
33596
34037
|
GRADLE_TEST_SUMMARY_INIT = `// Written by terminalhire (TERM-1122): prints each Test task's counts, because
|
|
33597
34038
|
// Gradle prints none when every test passes.
|
|
@@ -34179,7 +34620,7 @@ async function verifyWorkingDiff(req) {
|
|
|
34179
34620
|
}
|
|
34180
34621
|
async function runVerification(req, ctx) {
|
|
34181
34622
|
const { runId, startedAt } = ctx;
|
|
34182
|
-
const labels =
|
|
34623
|
+
const labels = runLabels(runId, req.labels);
|
|
34183
34624
|
const progress = req.onProgress ?? (() => {
|
|
34184
34625
|
});
|
|
34185
34626
|
const badTarget = unacceptableTarget(req);
|
|
@@ -34334,9 +34775,9 @@ async function runVerification(req, ctx) {
|
|
|
34334
34775
|
}
|
|
34335
34776
|
const lease = resolved.lease;
|
|
34336
34777
|
try {
|
|
34337
|
-
buildJail(scratch);
|
|
34778
|
+
buildJail(scratch, lease.guestUser);
|
|
34338
34779
|
if (spec.runtime === "jvm")
|
|
34339
|
-
stageGradleTestSummary(localJailPaths(scratch).jail);
|
|
34780
|
+
stageGradleTestSummary(localJailPaths(scratch).jail, lease.guestUser);
|
|
34340
34781
|
const venuePaths = await lease.stage({
|
|
34341
34782
|
cloneDir,
|
|
34342
34783
|
scratchRoot: scratch,
|
|
@@ -34600,14 +35041,14 @@ function migrationUnits(runner, migrations) {
|
|
|
34600
35041
|
return [...byDir.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([id, path5]) => ({ id, path: path5 }));
|
|
34601
35042
|
}
|
|
34602
35043
|
case "alembic":
|
|
34603
|
-
return migrations.filter((p) => /^alembic\/versions\/[^/]+\.py$/.test(p) && !p.endsWith("/__init__.py")).sort().map((path5) => ({ id:
|
|
35044
|
+
return migrations.filter((p) => /^alembic\/versions\/[^/]+\.py$/.test(p) && !p.endsWith("/__init__.py")).sort().map((path5) => ({ id: basename5(path5).replace(/\.py$/, ""), path: path5 }));
|
|
34604
35045
|
case "rails":
|
|
34605
|
-
return migrations.filter((p) => /^db\/migrate\/[^/]+\.rb$/.test(p)).sort().map((path5) => ({ id: /^(\d+)/.exec(
|
|
35046
|
+
return migrations.filter((p) => /^db\/migrate\/[^/]+\.rb$/.test(p)).sort().map((path5) => ({ id: /^(\d+)/.exec(basename5(path5))?.[1] ?? basename5(path5), path: path5 }));
|
|
34606
35047
|
case "sql":
|
|
34607
35048
|
return migrations.filter((p) => p.endsWith(".sql") && !p.startsWith("prisma/migrations/")).sort().map((path5) => ({ id: path5, path: path5 }));
|
|
34608
35049
|
}
|
|
34609
35050
|
}
|
|
34610
|
-
function
|
|
35051
|
+
function basename5(path5) {
|
|
34611
35052
|
const at = path5.lastIndexOf("/");
|
|
34612
35053
|
return at === -1 ? path5 : path5.slice(at + 1);
|
|
34613
35054
|
}
|
|
@@ -34705,7 +35146,7 @@ var init_dbplan = __esm({
|
|
|
34705
35146
|
|
|
34706
35147
|
// ../../packages/envrun/dist/dbstack.js
|
|
34707
35148
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
34708
|
-
import { randomBytes as
|
|
35149
|
+
import { randomBytes as randomBytes12 } from "crypto";
|
|
34709
35150
|
import { mkdirSync as mkdirSync7 } from "fs";
|
|
34710
35151
|
function installCommandFor(runner) {
|
|
34711
35152
|
switch (runner) {
|
|
@@ -34741,7 +35182,7 @@ function docker2(args, timeoutMs = DOCKER_TIMEOUT_MS2) {
|
|
|
34741
35182
|
function generateCredentials(host) {
|
|
34742
35183
|
return {
|
|
34743
35184
|
user: "thverify",
|
|
34744
|
-
password:
|
|
35185
|
+
password: randomBytes12(24).toString("base64url"),
|
|
34745
35186
|
database: "thverify",
|
|
34746
35187
|
host,
|
|
34747
35188
|
port: 5432
|
|
@@ -35361,6 +35802,7 @@ __export(dist_exports, {
|
|
|
35361
35802
|
REDACTED_TARGET_SHA: () => REDACTED_TARGET_SHA,
|
|
35362
35803
|
RELEASED_LEASE_CENSUS_REASON: () => RELEASED_LEASE_CENSUS_REASON,
|
|
35363
35804
|
REPO_DIGEST_RE: () => REPO_DIGEST_RE,
|
|
35805
|
+
RUN_ID_LABEL_KEY: () => RUN_ID_LABEL_KEY,
|
|
35364
35806
|
RUN_IMAGE_SOURCES: () => RUN_IMAGE_SOURCES,
|
|
35365
35807
|
RUN_LABEL_KEY: () => RUN_LABEL_KEY,
|
|
35366
35808
|
RUN_RESULT_FIELDS: () => RUN_RESULT_FIELDS,
|
|
@@ -35433,6 +35875,7 @@ __export(dist_exports, {
|
|
|
35433
35875
|
isBookkeepingTable: () => isBookkeepingTable,
|
|
35434
35876
|
isCommandUnavailable: () => isCommandUnavailable,
|
|
35435
35877
|
isGreen: () => isGreen,
|
|
35878
|
+
isMissingSystemDependency: () => isMissingSystemDependency,
|
|
35436
35879
|
isOurFault: () => isOurFault,
|
|
35437
35880
|
judgeCompleteness: () => judgeCompleteness,
|
|
35438
35881
|
judgeLeaks: () => judgeLeaks,
|
|
@@ -35464,6 +35907,7 @@ __export(dist_exports, {
|
|
|
35464
35907
|
resolveImageForSpec: () => resolveImageForSpec,
|
|
35465
35908
|
resolveLease: () => resolveLease,
|
|
35466
35909
|
runEnvironmentSpec: () => runEnvironmentSpec,
|
|
35910
|
+
runLabels: () => runLabels,
|
|
35467
35911
|
setManifestProbe: () => setManifestProbe,
|
|
35468
35912
|
sha256Hex: () => sha256Hex,
|
|
35469
35913
|
signRunStatement: () => signRunStatement,
|
|
@@ -35782,10 +36226,10 @@ import {
|
|
|
35782
36226
|
mkdtempSync as mkdtempSync5,
|
|
35783
36227
|
renameSync as renameSync6,
|
|
35784
36228
|
existsSync as existsSync13,
|
|
35785
|
-
lstatSync as
|
|
36229
|
+
lstatSync as lstatSync4,
|
|
35786
36230
|
realpathSync as realpathSync2,
|
|
35787
36231
|
rmSync as rmSync10,
|
|
35788
|
-
readdirSync as
|
|
36232
|
+
readdirSync as readdirSync4
|
|
35789
36233
|
} from "fs";
|
|
35790
36234
|
import { join as join29, dirname as dirname8, isAbsolute as isAbsolute4, resolve as pathResolve } from "path";
|
|
35791
36235
|
import { createHash as createHash10 } from "crypto";
|
|
@@ -36368,7 +36812,7 @@ async function sh(cmd, args, opts = {}) {
|
|
|
36368
36812
|
async function confirm(question) {
|
|
36369
36813
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
36370
36814
|
try {
|
|
36371
|
-
const ans = await new Promise((
|
|
36815
|
+
const ans = await new Promise((resolve5) => rl.question(question, resolve5));
|
|
36372
36816
|
return /^y(es)?$/i.test(String(ans).trim());
|
|
36373
36817
|
} finally {
|
|
36374
36818
|
rl.close();
|
|
@@ -36399,7 +36843,7 @@ async function confirmSubmit(flags, question, { unattendedAllowed = true, refusa
|
|
|
36399
36843
|
async function ask(question) {
|
|
36400
36844
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
36401
36845
|
try {
|
|
36402
|
-
const ans = await new Promise((
|
|
36846
|
+
const ans = await new Promise((resolve5) => rl.question(question, resolve5));
|
|
36403
36847
|
return String(ans).trim();
|
|
36404
36848
|
} finally {
|
|
36405
36849
|
rl.close();
|
|
@@ -36513,7 +36957,7 @@ function inferSubmitClaim({
|
|
|
36513
36957
|
}
|
|
36514
36958
|
return { candidates: [] };
|
|
36515
36959
|
}
|
|
36516
|
-
function
|
|
36960
|
+
function canonicalPath2(p) {
|
|
36517
36961
|
if (typeof p !== "string" || p === "") return p;
|
|
36518
36962
|
const resolved = pathResolve(p);
|
|
36519
36963
|
try {
|
|
@@ -38981,7 +39425,7 @@ function assertNoBooleanPath(dest, flagName) {
|
|
|
38981
39425
|
}
|
|
38982
39426
|
function resolveDeliveryDir(flags, claimLocalId, { existsFn, readdirFn } = {}) {
|
|
38983
39427
|
const exists = existsFn ?? existsSync13;
|
|
38984
|
-
const readdir3 = readdirFn ??
|
|
39428
|
+
const readdir3 = readdirFn ?? readdirSync4;
|
|
38985
39429
|
let probing = null;
|
|
38986
39430
|
try {
|
|
38987
39431
|
const base = flags?.dir ? assertNoBooleanPath(pathResolve(String(flags.dir)), "dir") : sliceWorkDirFor(claimLocalId);
|
|
@@ -39129,7 +39573,7 @@ function writeDeliveredBrief(destDir, spec) {
|
|
|
39129
39573
|
function ensureExcludedPackDir(destDir) {
|
|
39130
39574
|
let occupant = null;
|
|
39131
39575
|
try {
|
|
39132
|
-
occupant =
|
|
39576
|
+
occupant = lstatSync4(join29(destDir, BRIEF_DIR));
|
|
39133
39577
|
} catch (err) {
|
|
39134
39578
|
if (err?.code !== "ENOENT") {
|
|
39135
39579
|
return {
|
|
@@ -40085,7 +40529,7 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
40085
40529
|
cwdToplevel: cwdToplevel2,
|
|
40086
40530
|
cwd: pathResolve(dir),
|
|
40087
40531
|
branch,
|
|
40088
|
-
canonicalize:
|
|
40532
|
+
canonicalize: canonicalPath2
|
|
40089
40533
|
});
|
|
40090
40534
|
if (inferred.claim) {
|
|
40091
40535
|
id = inferred.claim.id;
|