@outerlayer/cli 0.34.0-next.20261026161241 → 0.34.0-next.20261026182624

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.
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { readsJobState, READ_JOB_STATE_FORMATS, jobStateFormatOf } from './chunk-ZVB57WI6.js';
4
+ import { itemsSentence, criteriaOf, hostItemsOf, loadCitationFinder, MAX_TEST_FILE_BYTES, MAX_TEST_FILES, criterionInputs, planCriteria, parseJudgeAnswer, inCriteriaOrder, oneLineReason, judgedItems } from './chunk-6D7H5DMZ.js';
4
5
  import { DOCKERD_LOG, HELPER_PATH, HELPER_RECORD } from './chunk-P3NPTFLF.js';
5
6
  import { zodProblem } from './chunk-I3LTIAOR.js';
6
7
  import { senderPidfile } from './chunk-LD3BHLK7.js';
7
8
  import { installScrubContext } from './chunk-VUQKGBIQ.js';
8
9
  import { writeDockerClientConfig } from './chunk-CFFVCI2I.js';
9
10
  import { upgradedStarterText } from './chunk-XKJOIQ2Q.js';
10
- import { ADDED_USER, shellQuote, GH_CONFIG_YML, ghHostsYml, hasClaudeCredential, AssetError, obtainImage, ensureRootfs, makeBuildsDirPrivate, gitRemoteFor, startServicesFor, createLeaseChannel, createBuildDisk, machineConfig, startJailedInto, resolveAgentUser, AgentUserError, runBuildSteps, startHostServices, LEASE_SECONDS, managedBuildSettingsFor, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, closeHostBuild, dropManagedClaudeKey, dropManagedBuildSettings, containerSenderCommand, hostCommandEnvironment, execArgv, signalInContainer, syncInContainer, copyTranscriptsFromContainer, MANAGED_UNJAILED_REASON, senderAliveScript, managedMachineLimits, withFactoryHosts, helperFirecrackerNote, managedClaudeKeyValues, managedBuildSettingValues, stopJailed, deleteBuildDisk, managedClaudeKeyFor, startCheckContainer, lifecycleSteps, lifecycleFailureReason, writeEvalInput, DeliveryFailure, EVAL_INPUT_NAME, copyTranscripts, senderSignalScript, containerJudgeEnvironment, ROOT_DISK_ID_PREFIX, copyJudgeTree } from './chunk-CNZRUVZ5.js';
11
- import { readBranchHead, fetchCommit, diffBetween } from './chunk-H47VGSCW.js';
11
+ import { ADDED_USER, shellQuote, GH_CONFIG_YML, ghHostsYml, hasClaudeCredential, AssetError, obtainImage, ensureRootfs, makeBuildsDirPrivate, gitRemoteFor, startServicesFor, createLeaseChannel, createBuildDisk, machineConfig, startJailedInto, resolveAgentUser, AgentUserError, runBuildSteps, createContainerRuntime, containerSenderCommand, hostCommandEnvironment, execArgv, signalInContainer, startHostServices, LEASE_SECONDS, managedBuildSettingsFor, stopContainer, cleanupContainer, outOfMemoryReason, closeHostBuild, dropManagedClaudeKey, dropManagedBuildSettings, startCheckContainer, lifecycleSteps, lifecycleFailureReason, writeEvalInput, DeliveryFailure, EVAL_INPUT_NAME, syncInContainer, copyTranscriptsFromContainer, MANAGED_UNJAILED_REASON, senderAliveScript, managedMachineLimits, withFactoryHosts, helperFirecrackerNote, managedClaudeKeyValues, managedBuildSettingValues, stopJailed, deleteBuildDisk, managedClaudeKeyFor, ROOT_DISK_ID_PREFIX, copyTranscripts, containerJudgeEnvironment, senderSignalScript, copyJudgeTree } from './chunk-CNZRUVZ5.js';
12
+ import { fetchCommit, readBranchHead, diffBetween } from './chunk-H47VGSCW.js';
12
13
  import { waitForAgent, vmExecArgs } from './chunk-3VZTNXK5.js';
13
14
  import { socketPathProblem, vsockLinks, LEASE_LINK, linkVsockSockets } from './chunk-6KFJIU73.js';
14
- import { describeCallFailure, resolveGoverningSource, prefetchGovernedContext, refusalCode, removeStagedContext, stageGovernedContext, pinnedSourceOf, commandWithShim, logStamp, createRunnerLog, removeKeptContext, formatDuration, retrySend, formatClock, WORKFLOW_PERMISSION_REASON, keptContextTree } from './chunk-EPFFYDNX.js';
15
+ import { describeCallFailure, resolveGoverningSource, prefetchGovernedContext, refusalCode, removeStagedContext, stageGovernedContext, pinnedSourceOf, commandWithShim, logStamp, createRunnerLog, removeKeptContext, formatDuration, retrySend, formatClock, keptContextTree, WORKFLOW_PERMISSION_REASON } from './chunk-EPFFYDNX.js';
15
16
  import { imageBuildOf, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-ZIKZZ4LK.js';
16
17
  import { formatBytes, DEFAULT_CACHE_DISK, allocatedBytes } from './chunk-IWVGALDC.js';
17
18
  import { HASH_ARGV, parseProgramHash, JUDGE_PROGRAM, writeJobFile, readJobFile, parseSize, childEnvironment, withAnswerProtocol, judgeCommand, MAX_REPORT_BYTES, JUDGE_MAX_TURNS, JUDGE_DIR, COMMAND_DIR, MAX_ITEMS_BYTES } from './chunk-HVQMIZ22.js';
@@ -21,25 +22,44 @@ import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk
21
22
  import { planArtifactUpload } from './chunk-MECW77XP.js';
22
23
  import { readCloudConfig } from './chunk-PJDQPF5N.js';
23
24
  import { signerFor } from './chunk-L5VHGRQE.js';
24
- import { resolvePackRoot, AGENTS_SKILLS_BASE, OUTERLAYER_SKILLS_BASE } from './chunk-ZQSANKS4.js';
25
+ import { AGENTS_SKILLS_BASE, OUTERLAYER_SKILLS_BASE, resolvePackRoot } from './chunk-ZQSANKS4.js';
25
26
  import { scrubText } from './chunk-Z7WMU3RA.js';
26
27
  import { MAINTAINED_SKILLS } from './chunk-CDDED64E.js';
27
28
  import { resolveVersion } from './chunk-RA3O54FC.js';
28
29
  import { parsePolicy } from './chunk-FGXT5OXS.js';
29
- import { CheckItemsSchema, CHECK_ITEM_REASON_MAX_LENGTH } from './chunk-EOTMDIYC.js';
30
+ import { CheckItemsSchema } from './chunk-EOTMDIYC.js';
30
31
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
31
32
  import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath, jobConsolePath } from './chunk-GJ4KBHBD.js';
32
- import { JOB_MOUNT, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, claudeCredential, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, fdName, buildMavenSettingsXml, CLAUDE_CREDENTIAL_VARIABLES, currentClaudeCredential, removedClaudeCredentialValues, managedClaudeCredential, RELAY_PORT, repositoryPath, lifecycleWorkdir, CHECK_ITEMS_FILE, MAVEN_SETTINGS_FILE, checkEnvironment, EVAL_INPUT_DIR } from './chunk-SDAEXUIX.js';
33
+ import { JOB_MOUNT, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, claudeCredential, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT, repositoryPath, lifecycleWorkdir, CHECK_ITEMS_FILE, fdName, buildMavenSettingsXml, CLAUDE_CREDENTIAL_VARIABLES, currentClaudeCredential, removedClaudeCredentialValues, managedClaudeCredential, checkEnvironment, MAVEN_SETTINGS_FILE, EVAL_INPUT_DIR } from './chunk-SDAEXUIX.js';
33
34
  import { RUNNER_WORDED_REASONS, UNKNOWN_TRACKING_REASON } from './chunk-MESOOLF4.js';
34
- import { existsSync, statSync, appendFileSync, rmSync, mkdirSync, chmodSync, writeFileSync, openSync, readFileSync, closeSync, readdirSync, lstatSync, fstatSync, readSync, realpathSync } from 'fs';
35
+ import { randomBytes, createHash } from 'crypto';
36
+ import { rmSync, existsSync, statSync, appendFileSync, mkdirSync, chmodSync, writeFileSync, openSync, closeSync, readFileSync, readdirSync, lstatSync, realpathSync, fstatSync, readSync } from 'fs';
35
37
  import { join, dirname, delimiter, sep } from 'path';
38
+ import { spawn, spawnSync } from 'child_process';
36
39
  import { once } from 'events';
37
40
  import { createServer } from 'net';
38
- import { spawn, spawnSync } from 'child_process';
39
- import { randomBytes, createHash } from 'crypto';
40
- import { pathToFileURL } from 'url';
41
41
 
42
42
  createRequire(import.meta.url);
43
+ async function serveLease(socketPath, channel, makeServer = createServer, listenOn) {
44
+ const server = makeServer((socket) => channel.accept(socket));
45
+ if (listenOn === void 0) {
46
+ rmSync(socketPath, { force: true });
47
+ server.listen(socketPath);
48
+ await once(server, "listening");
49
+ } else {
50
+ await listenOn(server);
51
+ }
52
+ server.on("error", () => void 0);
53
+ return {
54
+ ...channel,
55
+ listener: server,
56
+ close() {
57
+ channel.close();
58
+ server.close();
59
+ rmSync(socketPath, { force: true });
60
+ }
61
+ };
62
+ }
43
63
  var PROBE_TIMEOUT_MS = 3e4;
44
64
  var PROBE_BYTES = 256 * 1024;
45
65
  function probeScript(dir, alive) {
@@ -104,26 +124,6 @@ function judgeProbe(probe, missedBefore) {
104
124
  if (probe.alive) return { kind: "running" };
105
125
  return missedBefore ? { kind: "lost" } : { kind: "recheck" };
106
126
  }
107
- async function serveLease(socketPath, channel, makeServer = createServer, listenOn) {
108
- const server = makeServer((socket) => channel.accept(socket));
109
- if (listenOn === void 0) {
110
- rmSync(socketPath, { force: true });
111
- server.listen(socketPath);
112
- await once(server, "listening");
113
- } else {
114
- await listenOn(server);
115
- }
116
- server.on("error", () => void 0);
117
- return {
118
- ...channel,
119
- listener: server,
120
- close() {
121
- channel.close();
122
- server.close();
123
- rmSync(socketPath, { force: true });
124
- }
125
- };
126
- }
127
127
 
128
128
  // src/runner/microvm/command-state.ts
129
129
  var MICROVM_COMMAND_DIR = "/build/.command";
@@ -175,6 +175,8 @@ async function stopMicroVmCommand(runtime, state) {
175
175
  (signal) => runtime.vm.exec(state.vsockPath, { argv: ["/bin/sh", "-c", STOP_COMMAND_SCRIPT], user: "root", cwd: "/", env: { PATH: GUEST_PATH } }, { signal })
176
176
  );
177
177
  }
178
+
179
+ // src/runner/microvm/hooks.ts
178
180
  var MANAGED_NO_BUILDER_DISK_REASON = "this release of the CLI pins no builder root disk, and a managed runner builds its images in a builder microVM, never on the host. The build did not start; update the CLI";
179
181
  var DEFAULT_MEMORY = "8g";
180
182
  var DEFAULT_DISK = "40g";
@@ -1177,82 +1179,6 @@ async function stopOrphanedMicroVms(input) {
1177
1179
  return stopped;
1178
1180
  }
1179
1181
 
1180
- // src/runner/microvm/guest-usage.ts
1181
- var GUEST_USAGE_INTERVAL_MS = 15e3;
1182
- var MS_PER_TICK = 10;
1183
- function usageScript(procDir = "/proc") {
1184
- return [
1185
- `echo OL-USAGE-MEMINFO; cat ${procDir}/meminfo`,
1186
- `echo OL-USAGE-STAT; cat ${procDir}/stat`,
1187
- `if [ -r ${procDir}/pressure/memory ]; then echo OL-USAGE-PSI-MEMORY; cat ${procDir}/pressure/memory; fi`,
1188
- `if [ -r ${procDir}/pressure/cpu ]; then echo OL-USAGE-PSI-CPU; cat ${procDir}/pressure/cpu; fi`
1189
- ].join("; ");
1190
- }
1191
- function section(stdout, name) {
1192
- const lines = stdout.split("\n");
1193
- const start = lines.indexOf(`OL-USAGE-${name}`);
1194
- if (start === -1) return void 0;
1195
- const end = lines.findIndex((line, index) => index > start && line.startsWith("OL-USAGE-"));
1196
- return lines.slice(start + 1, end === -1 ? void 0 : end).join("\n");
1197
- }
1198
- function meminfoKb(text2, key) {
1199
- const match = new RegExp(`^${key}:\\s+(\\d+)\\s+kB\\s*$`, "m").exec(text2);
1200
- return match === null ? void 0 : Number(match[1]);
1201
- }
1202
- function pressureTotal(text2) {
1203
- if (text2 === void 0) return void 0;
1204
- const match = /^some\s.*\btotal=(\d+)\s*$/m.exec(text2);
1205
- return match === null ? void 0 : Number(match[1]);
1206
- }
1207
- function parseGuestSample(stdout) {
1208
- const meminfo = section(stdout, "MEMINFO");
1209
- const stat = section(stdout, "STAT");
1210
- if (meminfo === void 0 || stat === void 0) return void 0;
1211
- const totalKb = meminfoKb(meminfo, "MemTotal");
1212
- const availableKb = meminfoKb(meminfo, "MemAvailable");
1213
- const aggregate = /^cpu\s+(\d+(?:\s+\d+){7,})\s*$/m.exec(stat);
1214
- if (totalKb === void 0 || availableKb === void 0 || totalKb <= 0 || availableKb > totalKb || aggregate === null) return void 0;
1215
- const [user, nice, system, , , irq, softirq, steal] = aggregate[1].split(/\s+/).map(Number);
1216
- const vcpus = stat.split("\n").filter((line) => /^cpu\d+\s/.test(line)).length;
1217
- if (vcpus < 1) return void 0;
1218
- const memoryPressureUs = pressureTotal(section(stdout, "PSI-MEMORY"));
1219
- const cpuPressureUs = pressureTotal(section(stdout, "PSI-CPU"));
1220
- return {
1221
- memoryBytes: totalKb * 1024,
1222
- vcpus,
1223
- usedBytes: (totalKb - availableKb) * 1024,
1224
- cpuBusyMs: (user + nice + system + irq + softirq + steal) * MS_PER_TICK,
1225
- ...memoryPressureUs === void 0 ? {} : { memoryPressureUs },
1226
- ...cpuPressureUs === void 0 ? {} : { cpuPressureUs }
1227
- };
1228
- }
1229
- function foldSample(usage, sample) {
1230
- return { peakMemoryBytes: Math.max(usage?.peakMemoryBytes ?? 0, sample.usedBytes), last: sample };
1231
- }
1232
- function usageRecord(usage) {
1233
- if (usage === void 0) return void 0;
1234
- const { last } = usage;
1235
- return {
1236
- guestMemoryBytes: last.memoryBytes,
1237
- guestVcpus: last.vcpus,
1238
- peakMemoryBytes: usage.peakMemoryBytes,
1239
- cpuBusyMs: last.cpuBusyMs,
1240
- ...last.memoryPressureUs === void 0 ? {} : { memoryPressureUs: last.memoryPressureUs },
1241
- ...last.cpuPressureUs === void 0 ? {} : { cpuPressureUs: last.cpuPressureUs }
1242
- };
1243
- }
1244
- async function sampleGuest(runtime, state) {
1245
- if (state.vsockPath === void 0) return void 0;
1246
- try {
1247
- const result = await withinTime(
1248
- (signal) => runtime.vm.exec(state.vsockPath, { argv: ["/bin/sh", "-c", usageScript()], user: "root", cwd: "/", env: { PATH: GUEST_PATH } }, { signal })
1249
- );
1250
- return result.status === 0 ? parseGuestSample(result.stdout) : void 0;
1251
- } catch {
1252
- return void 0;
1253
- }
1254
- }
1255
-
1256
1182
  // src/runner/verdict.ts
1257
1183
  var VERDICT_SETTLE_MS = 5 * 6e4;
1258
1184
  var VERDICT_READ_EVERY_MS = 3e4;
@@ -1338,722 +1264,397 @@ async function settleVerdict(deps) {
1338
1264
  await deps.sleep(everyMs);
1339
1265
  }
1340
1266
  }
1341
- var RUNNER_LOG_FILE = "runner.log";
1342
- var RUNNER_LOG_KEEP_BYTES = 65536;
1343
- function isTailedStage(stage) {
1344
- const tailed = ["provision", "sync", "cleanup", "report"];
1345
- return tailed.includes(stage);
1267
+
1268
+ // src/runner/microvm/guest-usage.ts
1269
+ var GUEST_USAGE_INTERVAL_MS = 15e3;
1270
+ var MS_PER_TICK = 10;
1271
+ function usageScript(procDir = "/proc") {
1272
+ return [
1273
+ `echo OL-USAGE-MEMINFO; cat ${procDir}/meminfo`,
1274
+ `echo OL-USAGE-STAT; cat ${procDir}/stat`,
1275
+ `if [ -r ${procDir}/pressure/memory ]; then echo OL-USAGE-PSI-MEMORY; cat ${procDir}/pressure/memory; fi`,
1276
+ `if [ -r ${procDir}/pressure/cpu ]; then echo OL-USAGE-PSI-CPU; cat ${procDir}/pressure/cpu; fi`
1277
+ ].join("; ");
1346
1278
  }
1347
- var PROVISION_PROBLEM = /\b(warn(ing)?|error|fail(ed|ure)?|refused)\b/i;
1348
- var READ_LIMIT_BYTES = 1048576;
1349
- var ITEM_LINE = /^#(\d+)\b/;
1350
- var AttemptLogBook = class {
1351
- constructor(now = () => /* @__PURE__ */ new Date()) {
1352
- this.now = now;
1353
- this.dirs = /* @__PURE__ */ new Map();
1354
- this.lastByKey = /* @__PURE__ */ new Map();
1355
- }
1356
- /** Starts keeping lines for `item` in `jobDirPath`. Idempotent. */
1357
- open(item, jobDirPath) {
1358
- this.dirs.set(Number(item), jobDirPath);
1359
- }
1360
- /** Stops keeping lines for `item`; its file stays on disk until housekeeping removes the job directory. */
1361
- close(item) {
1362
- const number = Number(item);
1363
- this.dirs.delete(number);
1364
- for (const key of [...this.lastByKey.keys()]) {
1365
- if (key.startsWith(`${number}:`)) this.lastByKey.delete(key);
1366
- }
1367
- }
1368
- /** The log with every line it writes for an open item also kept in that item's file. */
1369
- wrap(log) {
1370
- return {
1371
- line: (text2) => {
1372
- log.line(text2);
1373
- this.keepItemLine(text2);
1374
- },
1375
- error: (text2) => {
1376
- log.error(text2);
1377
- this.keepItemLine(text2);
1378
- }
1379
- };
1380
- }
1381
- /** One line for `item` that does not itself start with its number. */
1382
- note(item, text2) {
1383
- this.append(Number(item), text2);
1384
- }
1385
- /** One line, written only when `text` differs from the last one written under `key` for the item. */
1386
- noteChanged(item, key, text2) {
1387
- const number = Number(item);
1388
- const slot = `${number}:${key}`;
1389
- if (this.lastByKey.get(slot) === text2) return;
1390
- this.lastByKey.set(slot, text2);
1391
- this.append(number, text2);
1392
- }
1393
- keepItemLine(text2) {
1394
- const match = ITEM_LINE.exec(text2);
1395
- if (match !== null) this.append(Number(match[1]), text2);
1396
- }
1397
- append(item, text2) {
1398
- const dir = this.dirs.get(item);
1399
- if (dir === void 0) return;
1400
- try {
1401
- appendFileSync(join(dir, RUNNER_LOG_FILE), `${logStamp(this.now())} ${text2.replace(/[\r\n]+/g, " ")}
1402
- `, { mode: 384 });
1403
- } catch {
1404
- }
1405
- }
1406
- };
1407
- function readEnd(path, maxBytes) {
1408
- let fd;
1409
- try {
1410
- fd = openSync(path, "r");
1411
- const size = fstatSync(fd).size;
1412
- const length = Math.min(size, maxBytes);
1413
- const buffer = Buffer.alloc(length);
1414
- readSync(fd, buffer, 0, length, size - length);
1415
- return buffer.toString("utf8");
1416
- } catch {
1417
- return void 0;
1418
- } finally {
1419
- if (fd !== void 0) closeSync(fd);
1420
- }
1279
+ function section(stdout, name) {
1280
+ const lines = stdout.split("\n");
1281
+ const start = lines.indexOf(`OL-USAGE-${name}`);
1282
+ if (start === -1) return void 0;
1283
+ const end = lines.findIndex((line, index) => index > start && line.startsWith("OL-USAGE-"));
1284
+ return lines.slice(start + 1, end === -1 ? void 0 : end).join("\n");
1421
1285
  }
1422
- function lastLines(text2, count) {
1423
- const lines = text2.split("\n");
1424
- if (lines.at(-1) === "") lines.pop();
1425
- return lines.slice(-200);
1286
+ function meminfoKb(text2, key) {
1287
+ const match = new RegExp(`^${key}:\\s+(\\d+)\\s+kB\\s*$`, "m").exec(text2);
1288
+ return match === null ? void 0 : Number(match[1]);
1426
1289
  }
1427
- function capRunnerLog(text2) {
1428
- const bytes = Buffer.from(text2, "utf8");
1429
- if (bytes.length <= RUNNER_LOG_KEEP_BYTES) return text2;
1430
- let start = bytes.length - RUNNER_LOG_KEEP_BYTES;
1431
- if (bytes[start - 1] !== 10) {
1432
- const newline = bytes.indexOf(10, start);
1433
- if (newline + 1 < bytes.length) {
1434
- start = newline + 1;
1435
- } else {
1436
- while ((bytes[start] & 192) === 128) start++;
1437
- }
1438
- }
1439
- return `[runner log truncated: the first ${start} bytes were cut]
1440
- ${bytes.subarray(start).toString("utf8")}`;
1290
+ function pressureTotal(text2) {
1291
+ if (text2 === void 0) return void 0;
1292
+ const match = /^some\s.*\btotal=(\d+)\s*$/m.exec(text2);
1293
+ return match === null ? void 0 : Number(match[1]);
1441
1294
  }
1442
- function buildReleaseLog(jobDirPath, endStage, scrub) {
1443
- const parts = [];
1444
- const own = readEnd(join(jobDirPath, RUNNER_LOG_FILE), READ_LIMIT_BYTES);
1445
- if (own !== void 0 && own.trim() !== "") parts.push(own.endsWith("\n") ? own.slice(0, -1) : own);
1446
- if (isTailedStage(endStage)) {
1447
- const file = `${endStage}.log`;
1448
- const stage = readEnd(join(jobDirPath, file), READ_LIMIT_BYTES);
1449
- const tail = stage === void 0 ? [] : lastLines(stage);
1450
- if (tail.length > 0) parts.push(`--- the last ${tail.length} lines of ${file} ---`, ...tail);
1451
- }
1452
- if (endStage !== "provision") {
1453
- const provision = readEnd(join(jobDirPath, "provision.log"), READ_LIMIT_BYTES);
1454
- const problems = provision === void 0 ? [] : provision.split("\n").filter((line) => PROVISION_PROBLEM.test(line)).slice(-50);
1455
- if (problems.length > 0) parts.push(`--- provision problems (${problems.length} lines of provision.log) ---`, ...problems);
1456
- }
1457
- if (parts.length === 0) return void 0;
1458
- return capRunnerLog(scrub(parts.join("\n")) + "\n");
1459
- }
1460
- var ENDED = "ended before provision finished";
1461
- async function provisionProcess(input) {
1462
- const { config, runtime, item, repository, jobDir } = input;
1463
- if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
1464
- if (repository === void 0 || repository === null || repository === "") {
1465
- return { ok: false, reason: "the work item names no repository to clone" };
1466
- }
1467
- const buildId = randomBytes(6).toString("hex");
1468
- const buildDir2 = buildDir(config.configPath, buildId);
1469
- const workDir = join(jobDir, "work");
1470
- const state = { buildId, buildDir: buildDir2, workDir };
1471
- const environment = sharedUserEnvironment(input.cliVersion);
1472
- input.save(state, environment);
1473
- const bail = async (reason) => {
1474
- try {
1475
- await cleanupProcess(runtime, state);
1476
- } catch (err) {
1477
- input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
1478
- }
1479
- return { ok: false, reason };
1295
+ function parseGuestSample(stdout) {
1296
+ const meminfo = section(stdout, "MEMINFO");
1297
+ const stat = section(stdout, "STAT");
1298
+ if (meminfo === void 0 || stat === void 0) return void 0;
1299
+ const totalKb = meminfoKb(meminfo, "MemTotal");
1300
+ const availableKb = meminfoKb(meminfo, "MemAvailable");
1301
+ const aggregate = /^cpu\s+(\d+(?:\s+\d+){7,})\s*$/m.exec(stat);
1302
+ if (totalKb === void 0 || availableKb === void 0 || totalKb <= 0 || availableKb > totalKb || aggregate === null) return void 0;
1303
+ const [user, nice, system, , , irq, softirq, steal] = aggregate[1].split(/\s+/).map(Number);
1304
+ const vcpus = stat.split("\n").filter((line) => /^cpu\d+\s/.test(line)).length;
1305
+ if (vcpus < 1) return void 0;
1306
+ const memoryPressureUs = pressureTotal(section(stdout, "PSI-MEMORY"));
1307
+ const cpuPressureUs = pressureTotal(section(stdout, "PSI-CPU"));
1308
+ return {
1309
+ memoryBytes: totalKb * 1024,
1310
+ vcpus,
1311
+ usedBytes: (totalKb - availableKb) * 1024,
1312
+ cpuBusyMs: (user + nice + system + irq + softirq + steal) * MS_PER_TICK,
1313
+ ...memoryPressureUs === void 0 ? {} : { memoryPressureUs },
1314
+ ...cpuPressureUs === void 0 ? {} : { cpuPressureUs }
1480
1315
  };
1481
- if (input.aborted()) return bail(ENDED);
1482
- mkdirSync(buildDir2, { recursive: true });
1483
- chmodSync(buildDir2, 493);
1484
- try {
1485
- await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true, routeLog: (line) => input.log.line(`#${item.number} ${line}`) });
1486
- } catch (err) {
1487
- return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
1488
- }
1489
- input.save(state, environment);
1490
- if (input.aborted()) return bail(ENDED);
1491
- const checkout = join(workDir, "checkout");
1492
- const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
1493
- const cloned = await runtime.clone({ remote, dest: checkout });
1494
- if (!cloned.ok) return bail(cloned.reason);
1495
- if (input.aborted()) return bail(ENDED);
1496
- return { ok: true, workdir: checkout, state, environment };
1497
1316
  }
1498
- function cleanupProcess(runtime, state) {
1499
- return closeHostBuild(runtime, state);
1317
+ function foldSample(usage, sample) {
1318
+ return { peakMemoryBytes: Math.max(usage?.peakMemoryBytes ?? 0, sample.usedBytes), last: sample };
1500
1319
  }
1501
- var ARTIFACT_PROOF_REASON = "Its declared artifact proves it; the judge does not judge artifacts.";
1502
- var NO_CITING_TEST_REASON = "No test cites this criterion, and it declares no artifact proof.";
1503
- var NO_VERDICT_REASON = "The judge gave no result for this criterion.";
1504
- function criterionInputs(input) {
1505
- const paths = [...new Set((input.citations[input.criterion.id] ?? []).map((citation) => citation.path))].sort();
1506
- const hash = createHash("sha256");
1507
- const part = (text2) => {
1508
- hash.update(`${Buffer.byteLength(text2)}:`);
1509
- hash.update(text2);
1320
+ function usageRecord(usage) {
1321
+ if (usage === void 0) return void 0;
1322
+ const { last } = usage;
1323
+ return {
1324
+ guestMemoryBytes: last.memoryBytes,
1325
+ guestVcpus: last.vcpus,
1326
+ peakMemoryBytes: usage.peakMemoryBytes,
1327
+ cpuBusyMs: last.cpuBusyMs,
1328
+ ...last.memoryPressureUs === void 0 ? {} : { memoryPressureUs: last.memoryPressureUs },
1329
+ ...last.cpuPressureUs === void 0 ? {} : { cpuPressureUs: last.cpuPressureUs }
1510
1330
  };
1511
- part("judge-criterion-v1");
1512
- part(input.brief);
1513
- part(input.criterion.id);
1514
- part(input.criterion.text);
1515
- part(input.criterion.proof ?? "");
1516
- for (const path of paths) {
1517
- part(path);
1518
- part(input.testFiles.get(path) ?? "");
1519
- }
1520
- return hash.digest("hex");
1521
- }
1522
- var HTTPS_LINK = /^https:\/\/\S+$/;
1523
- function refsFor(id, citations) {
1524
- const refs = (citations[id] ?? []).slice(0, 20).map((citation) => ({
1525
- path: citation.path,
1526
- line: citation.line,
1527
- // Only an https link becomes an href on a page; any other is left off rather than refusing the check.
1528
- ...HTTPS_LINK.test(citation.href ?? "") ? { href: citation.href } : {}
1529
- }));
1530
- return refs.length === 0 ? void 0 : refs;
1531
- }
1532
- function planCriteria(input) {
1533
- const previousById = new Map(input.previous.map((item) => [item.id, item]));
1534
- const settled = [];
1535
- const toJudge = [];
1536
- for (const criterion of input.criteria) {
1537
- const inputs = input.inputs.get(criterion.id);
1538
- const base = { id: criterion.id, criterion: criterion.id, ...inputs === void 0 ? {} : { inputs } };
1539
- const refs = refsFor(criterion.id, input.citations);
1540
- if (criterion.proof !== null) {
1541
- settled.push({ ...base, result: "skipped", reason: ARTIFACT_PROOF_REASON });
1542
- continue;
1543
- }
1544
- if (refs === void 0) {
1545
- settled.push({ ...base, result: "fail", reason: NO_CITING_TEST_REASON });
1546
- continue;
1547
- }
1548
- const kept = previousById.get(criterion.id);
1549
- if (kept !== void 0 && inputs !== void 0 && kept.inputs === inputs && (kept.result === "pass" || kept.result === "fail")) {
1550
- settled.push({ ...base, result: kept.result, ...kept.reason === void 0 ? {} : { reason: kept.reason }, refs });
1551
- continue;
1552
- }
1553
- toJudge.push(criterion);
1554
- }
1555
- return { settled, toJudge };
1556
- }
1557
- var MAX_ANSWER_ENTRIES = 500;
1558
- function readAnswer(raw) {
1559
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, problem: "expected an object" };
1560
- const { result, items } = raw;
1561
- if (result !== void 0 && result !== "pass" && result !== "fail") return { ok: false, problem: 'result: must be "pass" or "fail"' };
1562
- if (items !== void 0 && !Array.isArray(items)) return { ok: false, problem: "items: must be an array" };
1563
- const entries = items ?? [];
1564
- if (entries.length > MAX_ANSWER_ENTRIES) return { ok: false, problem: `items: at most ${MAX_ANSWER_ENTRIES} entries` };
1565
- const read = [];
1566
- for (const [index, entry] of entries.entries()) {
1567
- const { id, result: itemResult, reason } = typeof entry === "object" && entry !== null ? entry : {};
1568
- if (typeof id !== "string" || id === "" || id.length > 200) return { ok: false, problem: `items.${index}.id: must be text of 1 to 200 characters` };
1569
- if (itemResult !== "pass" && itemResult !== "fail") return { ok: false, problem: `items.${index}.result: must be "pass" or "fail"` };
1570
- if (reason !== void 0 && typeof reason !== "string") return { ok: false, problem: `items.${index}.reason: must be text` };
1571
- read.push({ id, result: itemResult, ...reason === void 0 ? {} : { reason } });
1572
- }
1573
- return { ok: true, answer: { ...result === void 0 ? {} : { result }, items: read } };
1574
- }
1575
- function parseJudgeAnswer(text2) {
1576
- if (text2 === void 0 || text2.trim() === "") return void 0;
1577
- let raw;
1331
+ }
1332
+ async function sampleGuest(runtime, state) {
1333
+ if (state.vsockPath === void 0) return void 0;
1578
1334
  try {
1579
- raw = JSON.parse(text2);
1335
+ const result = await withinTime(
1336
+ (signal) => runtime.vm.exec(state.vsockPath, { argv: ["/bin/sh", "-c", usageScript()], user: "root", cwd: "/", env: { PATH: GUEST_PATH } }, { signal })
1337
+ );
1338
+ return result.status === 0 ? parseGuestSample(result.stdout) : void 0;
1580
1339
  } catch {
1581
- return { ok: false, reason: "the judge's items.json is not JSON" };
1340
+ return void 0;
1582
1341
  }
1583
- const read = readAnswer(raw);
1584
- return read.ok ? read : { ok: false, reason: `the judge's items.json does not match its shape: ${read.problem}` };
1585
- }
1586
- function oneLineReason(text2) {
1587
- if (text2 === void 0) return void 0;
1588
- const line = text2.replace(/\s+/g, " ").trim();
1589
- if (line === "") return void 0;
1590
- return line.length <= CHECK_ITEM_REASON_MAX_LENGTH ? line : `${line.slice(0, CHECK_ITEM_REASON_MAX_LENGTH - 1)}\u2026`;
1591
- }
1592
- function judgedItems(input) {
1593
- return input.toJudge.map((criterion) => {
1594
- const said = input.results.get(criterion.id);
1595
- const inputs = input.inputs.get(criterion.id);
1596
- const refs = refsFor(criterion.id, input.citations);
1597
- const reason = said === void 0 ? NO_VERDICT_REASON : oneLineReason(said.reason);
1598
- return {
1599
- id: criterion.id,
1600
- criterion: criterion.id,
1601
- result: said?.result ?? "fail",
1602
- ...reason === void 0 ? {} : { reason },
1603
- ...refs === void 0 ? {} : { refs },
1604
- ...inputs === void 0 || said === void 0 ? {} : { inputs }
1605
- };
1606
- });
1607
- }
1608
- function inCriteriaOrder(items, criteria) {
1609
- const order = new Map(criteria.map((criterion, index) => [criterion.id, index]));
1610
- return [...items].sort((a, b) => (order.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.id) ?? Number.MAX_SAFE_INTEGER));
1611
- }
1612
- function itemsSentence(items, noun) {
1613
- const failing = items.filter((item) => item.result === "fail").map((item) => item.id);
1614
- if (failing.length === 0) return void 0;
1615
- return `${failing.length} of ${items.length} ${items.length === 1 ? noun.one : noun.many} not satisfied: ${failing.join(", ")}`;
1616
1342
  }
1617
-
1618
- // src/runner/host-checks.ts
1619
- var MAX_VALIDATOR_FILES = 20;
1620
- var VALIDATORS_DIR = ".outerlayer/validators";
1621
- var POLICY_FILE = ".outerlayer/policy.yaml";
1622
- var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
1623
- var MAX_CHECK_ITEMS_BYTES = 1024 * 1024;
1624
- function commandItems(read, scrub) {
1625
- if (read.status !== 0 || read.stdout.trim() === "") return { ok: true, items: void 0 };
1626
- if (Buffer.byteLength(read.stdout) > MAX_CHECK_ITEMS_BYTES) return { ok: false, reason: `its check items file is longer than ${MAX_CHECK_ITEMS_BYTES} bytes` };
1627
- let raw;
1343
+ var STOP_WAIT_MS = 3e4;
1344
+ var POLL_MS = 200;
1345
+ var ASK_AGAIN_MS = 1e3;
1346
+ var EXIT_REPORT_WAIT_MS = 2e3;
1347
+ var exitsLogged = /* @__PURE__ */ new Map();
1348
+ var BUILD_HOME = "/build/home";
1349
+ async function startSender(input) {
1350
+ const { config, job, jobDir, deps } = input;
1351
+ const tag = `#${job.item}`;
1352
+ if (job.itemKey === void 0) return void 0;
1353
+ const spawnFn = deps.spawnFn ?? spawn;
1628
1354
  try {
1629
- raw = JSON.parse(read.stdout);
1630
- } catch {
1631
- return { ok: false, reason: "its check items file (OUTERLAYER_CHECK_ITEMS) is not JSON" };
1632
- }
1633
- const parsed = CheckItemsSchema.safeParse(raw);
1634
- if (!parsed.success) {
1635
- return { ok: false, reason: `its check items file does not match the check item shape: ${zodProblem(parsed.error.issues)}` };
1355
+ const launch = launchFor(config, job, jobDir, deps);
1356
+ const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
1357
+ let pid;
1358
+ let exitLogged;
1359
+ try {
1360
+ const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
1361
+ child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
1362
+ exitLogged = new Promise((resolve) => {
1363
+ child.on("exit", (code, signal) => {
1364
+ deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`);
1365
+ resolve();
1366
+ });
1367
+ });
1368
+ child.unref?.();
1369
+ pid = child.pid;
1370
+ } finally {
1371
+ closeSync(fd);
1372
+ }
1373
+ if (pid === void 0) {
1374
+ deps.log.line(`${tag} session sender did not start: no pid`);
1375
+ return void 0;
1376
+ }
1377
+ exitsLogged.set(pid, exitLogged);
1378
+ deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
1379
+ return { pid, start: startTimeOf(pid) ?? void 0 };
1380
+ } catch (err) {
1381
+ deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
1382
+ return void 0;
1636
1383
  }
1637
- return { ok: true, items: parsed.data.map((item) => item.reason === void 0 ? item : { ...item, reason: scrub(item.reason) }) };
1638
- }
1639
- function isJudgeCheck(check) {
1640
- return "judge" in check;
1641
1384
  }
1642
- function lastLine(text2) {
1643
- return text2.split(/\r\n|\r|\n/).map((line) => line.trim()).filter((line) => line !== "").at(-1);
1385
+ function launchFor(config, job, jobDir, deps) {
1386
+ if (job.microvm !== void 0) {
1387
+ const runtime = deps.runtime ?? createContainerRuntime();
1388
+ const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
1389
+ return { ...microVmSenderCommand({ job, jobDir, runtime, config, cli }), cwd: jobDir };
1390
+ }
1391
+ if (job.container !== void 0) {
1392
+ return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
1393
+ }
1394
+ const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
1395
+ const build = job.build;
1396
+ const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
1397
+ if (build?.exec !== void 0) {
1398
+ return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
1399
+ }
1400
+ return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
1644
1401
  }
1645
- function readInside(root, rel) {
1646
- const path = join(root, rel);
1647
- try {
1648
- if (!lstatSync(path).isFile()) return void 0;
1649
- const inside = realpathSync(path);
1650
- const base = realpathSync(root);
1651
- return inside.startsWith(base + sep) ? readFileSync(inside, "utf8") : void 0;
1652
- } catch {
1653
- return void 0;
1402
+ async function restartSender(input) {
1403
+ const { job, deps } = input;
1404
+ const state = job.container;
1405
+ if (state !== void 0 && job.itemKey !== void 0) {
1406
+ const runtime = deps.runtime ?? createContainerRuntime();
1407
+ const pidfile = senderPidfile(BUILD_HOME, Number(job.item));
1408
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1409
+ const running = async () => {
1410
+ const probe = await runtime.host.exec({ container: state.container, user: state.agentUser ?? ADDED_USER, env: { literals: {}, passed: {} }, argv: ["/bin/sh", "-c", senderAliveScript(pidfile)] });
1411
+ return probe.status === 0 && probe.stdout.trim() === "alive";
1412
+ };
1413
+ try {
1414
+ await signalInContainer(runtime, state, pidfile, "TERM");
1415
+ const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
1416
+ for (let waited = 0; await running() && waited < deadline; waited += 1) await sleep(POLL_MS);
1417
+ if (await running()) {
1418
+ deps.log.error(`#${job.item} the session sender an earlier runner left did not stop in time \xB7 killing it`);
1419
+ await signalInContainer(runtime, state, pidfile, "KILL");
1420
+ }
1421
+ } catch (err) {
1422
+ deps.log.error(`#${job.item} stopping the session sender an earlier runner left failed: ${describeCallFailure(err)} \xB7 continuing`);
1423
+ }
1654
1424
  }
1425
+ return startSender(input);
1655
1426
  }
1656
- var MAINTAINED_BRIEF = /^\.(?:agents|outerlayer)\/skills\/(outerlayer-[a-z]+)\/([A-Za-z0-9_./-]+)$/;
1657
- function readBrief(root, brief) {
1658
- const own = readInside(root, brief);
1659
- if (own !== void 0) return own;
1660
- const match = MAINTAINED_BRIEF.exec(brief);
1661
- if (match === null || match[2].split("/").includes("..") || !MAINTAINED_SKILLS.includes(match[1])) return void 0;
1427
+ function isLive(sender) {
1428
+ if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
1662
1429
  try {
1663
- return readFileSync(join(resolvePackRoot(), "maintained", match[1], match[2]), "utf8");
1430
+ process.kill(sender.pid, 0);
1431
+ return true;
1664
1432
  } catch {
1665
- return void 0;
1433
+ return false;
1666
1434
  }
1667
1435
  }
1668
- function validatorFilesIn(root) {
1669
- const dir = join(root, VALIDATORS_DIR);
1670
- let names;
1436
+ function signalHost(sender, signal) {
1437
+ if (!isLive(sender)) return;
1671
1438
  try {
1672
- if (!lstatSync(dir).isDirectory()) return [];
1673
- names = readdirSync(dir);
1439
+ process.kill(-sender.pid, signal);
1674
1440
  } catch {
1675
- return [];
1676
- }
1677
- const files = [];
1678
- for (const name of names.filter((entry) => /\.ya?ml$/.test(entry)).sort().slice(0, MAX_VALIDATOR_FILES)) {
1679
- const path = `${VALIDATORS_DIR}/${name}`;
1680
- const content = readInside(root, path);
1681
- if (content !== void 0) files.push({ path, content });
1441
+ try {
1442
+ process.kill(sender.pid, signal);
1443
+ } catch {
1444
+ }
1682
1445
  }
1683
- return files;
1684
1446
  }
1685
- var CRITERIA_CHECK = "acceptance-criteria";
1686
- function asHostReads(file, base, log, tag) {
1687
- const upgraded = upgradedStarterText(file.path, file.content, { base });
1688
- if (upgraded === void 0) return file;
1689
- log.line(`${tag} ${file.path} is an unedited copy an earlier release wrote, so it is read as this CLI writes it`);
1690
- return { path: file.path, content: upgraded };
1447
+ async function signalInGuest(runtime, state, pidfile, signal) {
1448
+ const { status } = await runtime.vm.exec(state.vsockPath, {
1449
+ argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
1450
+ user: state.agentUser ?? ADDED_USER,
1451
+ cwd: "/",
1452
+ env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
1453
+ });
1454
+ return status === 0;
1691
1455
  }
1692
- function checksIn(root, log, tag) {
1693
- const base = existsSync(join(root, AGENTS_SKILLS_BASE)) ? AGENTS_SKILLS_BASE : OUTERLAYER_SKILLS_BASE;
1694
- const policyText = readInside(root, POLICY_FILE);
1695
- const policy = policyText === void 0 ? null : asHostReads({ path: POLICY_FILE, content: policyText }, base, log, tag);
1696
- const loaded = parsePolicy(policy, validatorFilesIn(root).map((file) => asHostReads(file, base, log, tag)));
1697
- for (const problem of loaded.problems) log.error(`${tag} ${problem.file}: ${problem.problem}`);
1698
- const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null);
1699
- const checks = [];
1700
- for (const def of hostChecks) {
1701
- if (def.judge !== void 0) checks.push({ name: def.declaresEmit, judge: { brief: def.judge.brief, text: readBrief(root, def.judge.brief), subjects: def.judge.subjects } });
1702
- else if (def.command !== void 0) checks.push({ name: def.declaresEmit, command: def.command });
1703
- }
1704
- const unrecorded = loaded.customs.filter((def) => def.level !== "off" && def.declaresEmit === CRITERIA_CHECK && !checks.some((check) => check.name === CRITERIA_CHECK)).map((def) => def.id);
1705
- return { checks, unrecorded };
1706
- }
1707
- async function readDefinitions(input, repository, scratch, token, tokens) {
1708
- const { config, job, deps } = input;
1709
- const tag = `#${job.item}`;
1710
- const stage = deps.stage ?? stageGovernedContext;
1711
- const controlPlaneUrl = (controlPlane) => deps.runtime.remoteFor?.(controlPlane) ?? `${deps.runtime.github.gitUrl}/${controlPlane}.git`;
1712
- const governing = (job.container ?? job.microvm)?.governing;
1713
- const kept = keptContextTree(input.jobDir, governing);
1714
- if (kept !== void 0) return { ok: true, ...checksIn(kept, deps.log, tag) };
1715
- const staged = await stage({
1716
- config,
1717
- repository,
1718
- ...governing === void 0 ? {} : { source: { repository: governing.repository, ref: governing.ref, sha: governing.commit } },
1719
- buildId: `checks-${randomBytes(4).toString("hex")}`,
1720
- buildDir: scratch,
1721
- fetchImpl: deps.fetchImpl,
1722
- controlPlaneUrl,
1723
- controlPlaneToken: tokens === void 0 ? void 0 : () => tokens.controlPlane(),
1724
- log: deps.log,
1725
- tag
1726
- });
1727
- if (staged.kind === "refused") return { ok: false, reason: staged.reason };
1728
- if (staged.kind === "staged") {
1729
- try {
1730
- return { ok: true, ...checksIn(join(staged.dir, "tree"), deps.log, tag) };
1731
- } finally {
1732
- rmSync(staged.dir, { recursive: true, force: true });
1733
- }
1734
- }
1735
- const repo = join(scratch, "repo");
1736
- const remote = deps.runtime.remoteFor?.(repository) ?? `${deps.runtime.github.gitUrl}/${repository}.git`;
1737
- const started = (job.container ?? job.microvm)?.startCommit;
1738
- const commit = started !== void 0 && started !== "" ? started : job.environment?.recipe?.sourceCommit ?? await deps.runtime.readHead(remote, scratch);
1739
- if (commit === void 0) return { ok: false, reason: "the repository's default branch could not be read" };
1740
- const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: repo, commit, token });
1741
- if (!fetched.ok) return { ok: false, reason: fetched.reason };
1742
- deps.log.line(`${tag} check definitions read at ${commit.slice(0, 12)}`);
1743
- return { ok: true, ...checksIn(repo, deps.log, tag) };
1744
- }
1745
- function timeoutAfter(ms, sleep) {
1746
- if (sleep !== void 0) return { done: sleep(ms).then(() => "timeout"), cancel: () => void 0 };
1456
+ async function exitReported(pid) {
1457
+ const logged = exitsLogged.get(pid);
1747
1458
  let timer;
1748
- const done = new Promise((resolve) => {
1749
- timer = setTimeout(() => resolve("timeout"), ms);
1750
- timer.unref();
1459
+ const bound = new Promise((resolve) => {
1460
+ timer = setTimeout(resolve, EXIT_REPORT_WAIT_MS);
1461
+ timer.unref?.();
1751
1462
  });
1752
- return { done, cancel: () => clearTimeout(timer) };
1753
- }
1754
- function noCleanEnvironment(job) {
1755
- const ran = job.build?.workDir !== void 0 ? "builtin:process" : "a provision hook of the host's own";
1756
- return `the host has no clean environment for checks: this build ran under ${ran}, and checks run only in a new container or microVM from a build's image`;
1463
+ await Promise.race([logged, bound]);
1464
+ clearTimeout(timer);
1465
+ exitsLogged.delete(pid);
1757
1466
  }
1758
- function makeRecorder(input) {
1759
- const { config, job, deps } = input;
1467
+ async function stopSender(input) {
1468
+ const { job, deps } = input;
1469
+ const sender = job.sender;
1470
+ if (sender === void 0) return;
1760
1471
  const tag = `#${job.item}`;
1761
- const now = deps.now ?? (() => /* @__PURE__ */ new Date());
1762
- let signer;
1763
- return async (name, result, sentence, opts = {}) => {
1764
- signer ??= await signerFor(config.configPath);
1765
- const keyTail = opts.keyTail ?? "";
1766
- const body = sentence === void 0 ? void 0 : cutReasonLine(`${scrubReason(sentence, config) ?? "check failed"}${keyTail}`);
1767
- const clientEmitId = `host-${createHash("sha256").update(`${job.claimId}:${job.startedAt}:${name}${opts.idTag === void 0 ? "" : `:${opts.idTag}`}`).digest("hex").slice(0, 40)}`;
1768
- const sent = await retrySend(
1769
- () => postJson(
1770
- config.url,
1771
- "/v1/emitted-results",
1772
- config.apiKey,
1773
- config.appId,
1774
- {
1775
- schemaVersion: 1,
1776
- // A field left undefined is not sent: the request is JSON.
1777
- emit: { clientEmitId, name, result, body, link: opts.link, items: opts.items, emittedAt: now().toISOString(), item: { number: Number(job.item) } }
1778
- },
1779
- deps.fetchImpl,
1780
- void 0,
1781
- signer
1782
- ),
1783
- { now: deps.now }
1784
- );
1785
- if (sent.ok) deps.log.line(`${tag} check ${name} ${result}${body === void 0 ? "" : `: ${body}`}`);
1786
- else deps.log.error(`${tag} check ${name} ${result} could not be recorded: ${sent.reason}`);
1472
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1473
+ const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1474
+ const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1475
+ const signalInside = async (signal) => {
1476
+ if (inContainer !== void 0) return signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
1477
+ if (inGuest !== void 0) return signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
1478
+ return void 0;
1787
1479
  };
1788
- }
1789
- async function runHostChecks(input) {
1790
- const { config, job, jobDir, deps } = input;
1791
- const { runtime, log } = deps;
1792
- const tag = `#${job.item}`;
1793
- const now = deps.now ?? (() => /* @__PURE__ */ new Date());
1794
- const repository = job.repository ?? job.container?.repository;
1795
- if (repository === void 0) return;
1796
- const jobFilePath = join(jobDir, "job.json");
1797
- const tokens = runtime.credentials.get(jobDir);
1798
- let readToken;
1799
1480
  try {
1800
- readToken = await tokens?.token("read");
1481
+ let reached = true;
1482
+ if (inContainer !== void 0 || inGuest !== void 0) reached = await signalInside("TERM");
1483
+ else signalHost(sender, "SIGTERM");
1484
+ const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
1485
+ for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) {
1486
+ await sleep(POLL_MS);
1487
+ if (!reached && (waited + 1) % (ASK_AGAIN_MS / POLL_MS) === 0) reached = await signalInside("TERM");
1488
+ }
1489
+ if (isLive(sender)) {
1490
+ deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
1491
+ await signalInside("KILL");
1492
+ signalHost(sender, "SIGKILL");
1493
+ }
1494
+ await exitReported(sender.pid);
1801
1495
  } catch (err) {
1802
- log.error(`${tag} no read token for the checks: ${describeCallFailure(err)}`);
1496
+ deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
1803
1497
  }
1804
- const scratch = join(jobDir, "checks", "definitions");
1805
- rmSync(scratch, { recursive: true, force: true });
1806
- mkdirSync(scratch, { recursive: true });
1807
- let definitions;
1808
- try {
1809
- definitions = await readDefinitions(input, repository, scratch, readToken, tokens);
1810
- } catch (err) {
1811
- definitions = { ok: false, reason: describeCallFailure(err) };
1812
- } finally {
1813
- removeStagedContext(scratch);
1498
+ }
1499
+ var RUNNER_LOG_FILE = "runner.log";
1500
+ var RUNNER_LOG_KEEP_BYTES = 65536;
1501
+ function isTailedStage(stage) {
1502
+ const tailed = ["provision", "sync", "cleanup", "report"];
1503
+ return tailed.includes(stage);
1504
+ }
1505
+ var PROVISION_PROBLEM = /\b(warn(ing)?|error|fail(ed|ure)?|refused)\b/i;
1506
+ var READ_LIMIT_BYTES = 1048576;
1507
+ var ITEM_LINE = /^#(\d+)\b/;
1508
+ var AttemptLogBook = class {
1509
+ constructor(now = () => /* @__PURE__ */ new Date()) {
1510
+ this.now = now;
1511
+ this.dirs = /* @__PURE__ */ new Map();
1512
+ this.lastByKey = /* @__PURE__ */ new Map();
1814
1513
  }
1815
- if (!definitions.ok) {
1816
- log.line(`${tag} checks not run: the definitions could not be read: ${definitions.reason}`);
1817
- rmSync(scratch, { recursive: true, force: true });
1818
- return;
1514
+ /** Starts keeping lines for `item` in `jobDirPath`. Idempotent. */
1515
+ open(item, jobDirPath) {
1516
+ this.dirs.set(Number(item), jobDirPath);
1819
1517
  }
1820
- const checks = definitions.checks.filter((check) => !isJudgeCheck(check));
1821
- if (checks.length === 0) {
1822
- rmSync(scratch, { recursive: true, force: true });
1823
- return;
1518
+ /** Stops keeping lines for `item`; its file stays on disk until housekeeping removes the job directory. */
1519
+ close(item) {
1520
+ const number = Number(item);
1521
+ this.dirs.delete(number);
1522
+ for (const key of [...this.lastByKey.keys()]) {
1523
+ if (key.startsWith(`${number}:`)) this.lastByKey.delete(key);
1524
+ }
1824
1525
  }
1825
- log.line(`${tag} checks started (${checks.map((check) => check.name).join(", ")})`);
1826
- const startedMs = now().getTime();
1827
- const recorder = makeRecorder({ config, job, deps });
1828
- const record = (check, result, sentence, keyTail = "", items) => recorder(check.name, result, sentence, { keyTail, items });
1829
- const failAll = async (reason, keyTail = "") => {
1830
- log.error(`${tag} checks failed: ${reason}${keyTail}`);
1831
- for (const check of checks) await record(check, "fail", reason, keyTail);
1832
- };
1833
- try {
1834
- if (job.container === void 0 && job.microvm === void 0) return await failAll(noCleanEnvironment(job));
1835
- if (tokens === void 0) return await failAll("the build's repository tokens are no longer held, so the branch cannot be fetched");
1836
- if (job.branch === void 0) return await failAll("the build pushed no branch: the claim names none");
1837
- const claimed = repositoriesOf({ repositories: job.repositories, repository, branch: job.branch });
1838
- if (claimed.length === 0) return await failAll("the claim names no repository to check out");
1839
- const remoteOf = (name) => runtime.remoteFor?.(name) ?? `${runtime.github.gitUrl}/${name}.git`;
1840
- const remote = remoteOf(repository);
1841
- const pushed = /* @__PURE__ */ new Map();
1842
- const unpushed = [];
1843
- let primaryHead;
1844
- for (const entry of claimed) {
1845
- let entryToken = readToken;
1846
- if (!entry.primary) {
1847
- try {
1848
- entryToken = await tokens.token("read", entry.repository);
1849
- } catch (err) {
1850
- return await failAll(`no read token for ${entry.repository}: ${describeCallFailure(err)}`);
1851
- }
1852
- }
1853
- const read = await (deps.readBranch ?? readBranchHead)({ remote: remoteOf(entry.repository), dir: scratch, branch: entry.branch, token: entryToken });
1854
- if (read.kind === "unreadable") return await failAll(`the branch ${entry.branch} could not be read${entry.primary ? "" : ` on ${entry.repository}`}: ${read.reason}`);
1855
- if (read.kind === "absent") {
1856
- unpushed.push(entry);
1857
- continue;
1526
+ /** The log with every line it writes for an open item also kept in that item's file. */
1527
+ wrap(log) {
1528
+ return {
1529
+ line: (text2) => {
1530
+ log.line(text2);
1531
+ this.keepItemLine(text2);
1532
+ },
1533
+ error: (text2) => {
1534
+ log.error(text2);
1535
+ this.keepItemLine(text2);
1858
1536
  }
1859
- pushed.set(entry.repository, entry.branch);
1860
- if (entry.primary) primaryHead = read.sha;
1861
- }
1862
- if (pushed.size === 0) {
1863
- return await failAll(`the build pushed no branch: ${unpushed.map((entry) => `${entry.branch} does not exist on ${entry.repository}`).join("; ")}`);
1537
+ };
1538
+ }
1539
+ /** One line for `item` that does not itself start with its number. */
1540
+ note(item, text2) {
1541
+ this.append(Number(item), text2);
1542
+ }
1543
+ /** One line, written only when `text` differs from the last one written under `key` for the item. */
1544
+ noteChanged(item, key, text2) {
1545
+ const number = Number(item);
1546
+ const slot = `${number}:${key}`;
1547
+ if (this.lastByKey.get(slot) === text2) return;
1548
+ this.lastByKey.set(slot, text2);
1549
+ this.append(number, text2);
1550
+ }
1551
+ keepItemLine(text2) {
1552
+ const match = ITEM_LINE.exec(text2);
1553
+ if (match !== null) this.append(Number(match[1]), text2);
1554
+ }
1555
+ append(item, text2) {
1556
+ const dir = this.dirs.get(item);
1557
+ if (dir === void 0) return;
1558
+ try {
1559
+ appendFileSync(join(dir, RUNNER_LOG_FILE), `${logStamp(this.now())} ${text2.replace(/[\r\n]+/g, " ")}
1560
+ `, { mode: 384 });
1561
+ } catch {
1864
1562
  }
1865
- const repositoriesToCheckOut = claimed;
1866
- const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
1867
- const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
1868
- const environmentLog = join(jobDir, "checks", "environment.log");
1869
- let transcripts = { ok: false, reason: "the runner holds no way to read the build's sessions" };
1870
- if (deps.collectSessions !== void 0) {
1871
- try {
1872
- transcripts = await deps.collectSessions();
1873
- } catch (err) {
1874
- transcripts = { ok: false, reason: describeCallFailure(err) };
1875
- }
1876
- }
1877
- if (job.microvm !== void 0) {
1878
- if (deps.endBuildMicroVm === void 0) return await failAll("the runner has no way to stop the build's microVM, so no check environment was made");
1879
- try {
1880
- await deps.endBuildMicroVm();
1881
- } catch (err) {
1882
- return await failAll(`the build's microVM could not be stopped, so no check environment was made: ${describeCallFailure(err)}`);
1883
- }
1884
- }
1885
- let abandoned = false;
1886
- const setup = async () => {
1887
- const started = job.microvm !== void 0 ? await startCheckMicroVm({
1888
- config,
1889
- runtime,
1890
- job,
1891
- jobDir,
1892
- tokens,
1893
- log,
1894
- aborted: () => abandoned,
1895
- save: (state) => {
1896
- if (abandoned) return;
1897
- job.checkMicrovm = state;
1898
- writeJobFile(jobFilePath, job);
1899
- }
1900
- }) : await startCheckContainer({
1901
- config,
1902
- runtime,
1903
- job,
1904
- jobDir,
1905
- tokens,
1906
- log,
1907
- save: (state) => {
1908
- job.checkContainer = state;
1909
- writeJobFile(jobFilePath, job);
1910
- }
1911
- });
1912
- if (!started.ok) return `the check environment could not be made: ${started.reason}`;
1913
- const made = "container" in started ? started.container : started.environment;
1914
- const steps = [
1915
- {
1916
- argv: ["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"],
1917
- workdir: "/build/work",
1918
- failed: (status) => `configuring git failed (exit ${status})`
1919
- },
1920
- ...repositoriesToCheckOut.flatMap((entry) => {
1921
- const branch = pushed.get(entry.repository);
1922
- const path = entry.primary ? CHECKOUT_DIR : repositoryPath(entry.repository);
1923
- const clone = {
1924
- argv: ["git", "clone", "--filter=blob:none", "--single-branch", ...branch === void 0 ? [] : ["--branch", branch], `https://github.com/${entry.repository}.git`, path],
1925
- workdir: "/build/work",
1926
- failed: (status) => `the clone of ${branch ?? `the default branch of ${entry.repository}`} failed (exit ${status})`
1927
- };
1928
- return entry.primary && repositoriesToCheckOut.length > 1 ? [
1929
- clone,
1930
- {
1931
- argv: ["sh", "-c", 'mkdir -p "$(dirname "$1")" && ln -s "$2" "$1"', "link", repositoryPath(entry.repository), CHECKOUT_DIR],
1932
- workdir: "/build/work",
1933
- failed: (status) => `linking ${entry.repository} at ${repositoryPath(entry.repository)} failed (exit ${status})`
1934
- }
1935
- ] : [clone];
1936
- })
1937
- ];
1938
- for (const step of steps) {
1939
- if (abandoned) return "the check environment setup ran out of time";
1940
- const ran = await made.exec(step.argv, step.workdir, { logPath: environmentLog });
1941
- if (ran.status !== 0) return `the check environment could not be prepared: ${step.failed(ran.status)}`;
1942
- }
1943
- const lifecycleDir = lifecycleWorkdir(repositoriesToCheckOut, (job.container ?? job.microvm)?.governing?.repository);
1944
- for (const step of lifecycleSteps((job.container ?? job.microvm)?.lifecycle ?? {})) {
1945
- log.line(`${tag} checks lifecycle ${step.name} (${step.source})`);
1946
- const ran = await made.exec(step.argv, lifecycleDir, { logPath: environmentLog });
1947
- if (ran.status !== 0) return `the check environment could not be prepared: ${lifecycleFailureReason(step, ran.status)}`;
1948
- }
1949
- return made;
1950
- };
1951
- const setupTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
1952
- let settled;
1953
- try {
1954
- const outcome = await Promise.race([
1955
- setup().catch((err) => `the check environment could not be made: ${describeCallFailure(err)}`),
1956
- setupTimer.done
1957
- ]);
1958
- if (outcome === "timeout") {
1959
- abandoned = true;
1960
- return await failAll(`the check environment setup ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
1961
- }
1962
- settled = outcome;
1963
- } finally {
1964
- setupTimer.cancel();
1965
- }
1966
- if (typeof settled === "string") return await failAll(settled);
1967
- const ready = settled;
1968
- try {
1969
- await writeEvalInput({
1970
- job,
1971
- dir: ready.evalInput.dir,
1972
- scratch,
1973
- remote,
1974
- readToken,
1975
- branchHead: primaryHead ?? (job.container ?? job.microvm)?.startCommit ?? "",
1976
- transcripts,
1977
- scrub: transcriptScrub(config, [config.apiKey, job.itemKey, readToken]),
1978
- readWorkItem: () => readWorkItem(config, job.item, deps.fetchImpl),
1979
- fetchDiff: deps.fetchDiff ?? ((input2) => diffBetween(input2)),
1980
- log
1981
- });
1982
- } catch (err) {
1983
- return await failAll(`the checks' input could not be written: ${describeCallFailure(err)}`);
1563
+ }
1564
+ };
1565
+ function readEnd(path, maxBytes) {
1566
+ let fd;
1567
+ try {
1568
+ fd = openSync(path, "r");
1569
+ const size = fstatSync(fd).size;
1570
+ const length = Math.min(size, maxBytes);
1571
+ const buffer = Buffer.alloc(length);
1572
+ readSync(fd, buffer, 0, length, size - length);
1573
+ return buffer.toString("utf8");
1574
+ } catch {
1575
+ return void 0;
1576
+ } finally {
1577
+ if (fd !== void 0) closeSync(fd);
1578
+ }
1579
+ }
1580
+ function lastLines(text2, count) {
1581
+ const lines = text2.split("\n");
1582
+ if (lines.at(-1) === "") lines.pop();
1583
+ return lines.slice(-200);
1584
+ }
1585
+ function capRunnerLog(text2) {
1586
+ const bytes = Buffer.from(text2, "utf8");
1587
+ if (bytes.length <= RUNNER_LOG_KEEP_BYTES) return text2;
1588
+ let start = bytes.length - RUNNER_LOG_KEEP_BYTES;
1589
+ if (bytes[start - 1] !== 10) {
1590
+ const newline = bytes.indexOf(10, start);
1591
+ if (newline + 1 < bytes.length) {
1592
+ start = newline + 1;
1593
+ } else {
1594
+ while ((bytes[start] & 192) === 128) start++;
1984
1595
  }
1596
+ }
1597
+ return `[runner log truncated: the first ${start} bytes were cut]
1598
+ ${bytes.subarray(start).toString("utf8")}`;
1599
+ }
1600
+ function buildReleaseLog(jobDirPath, endStage, scrub) {
1601
+ const parts = [];
1602
+ const own = readEnd(join(jobDirPath, RUNNER_LOG_FILE), READ_LIMIT_BYTES);
1603
+ if (own !== void 0 && own.trim() !== "") parts.push(own.endsWith("\n") ? own.slice(0, -1) : own);
1604
+ if (isTailedStage(endStage)) {
1605
+ const file = `${endStage}.log`;
1606
+ const stage = readEnd(join(jobDirPath, file), READ_LIMIT_BYTES);
1607
+ const tail = stage === void 0 ? [] : lastLines(stage);
1608
+ if (tail.length > 0) parts.push(`--- the last ${tail.length} lines of ${file} ---`, ...tail);
1609
+ }
1610
+ if (endStage !== "provision") {
1611
+ const provision = readEnd(join(jobDirPath, "provision.log"), READ_LIMIT_BYTES);
1612
+ const problems = provision === void 0 ? [] : provision.split("\n").filter((line) => PROVISION_PROBLEM.test(line)).slice(-50);
1613
+ if (problems.length > 0) parts.push(`--- provision problems (${problems.length} lines of provision.log) ---`, ...problems);
1614
+ }
1615
+ if (parts.length === 0) return void 0;
1616
+ return capRunnerLog(scrub(parts.join("\n")) + "\n");
1617
+ }
1618
+ var ENDED = "ended before provision finished";
1619
+ async function provisionProcess(input) {
1620
+ const { config, runtime, item, repository, jobDir } = input;
1621
+ if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
1622
+ if (repository === void 0 || repository === null || repository === "") {
1623
+ return { ok: false, reason: "the work item names no repository to clone" };
1624
+ }
1625
+ const buildId = randomBytes(6).toString("hex");
1626
+ const buildDir2 = buildDir(config.configPath, buildId);
1627
+ const workDir = join(jobDir, "work");
1628
+ const state = { buildId, buildDir: buildDir2, workDir };
1629
+ const environment = sharedUserEnvironment(input.cliVersion);
1630
+ input.save(state, environment);
1631
+ const bail = async (reason) => {
1985
1632
  try {
1986
- await ready.evalInput.deliver();
1633
+ await cleanupProcess(runtime, state);
1987
1634
  } catch (err) {
1988
- return await failAll(err instanceof DeliveryFailure ? err.message : `the checks' input could not be written: ${describeCallFailure(err)}`);
1989
- }
1990
- let usable = true;
1991
- for (const check of checks) {
1992
- if (!usable) {
1993
- await record(check, "fail", "the check environment could not be restarted after an earlier check ran out of time");
1994
- continue;
1995
- }
1996
- const checkStartedMs = now().getTime();
1997
- const logPath = join(jobDir, "checks", `${check.name}.log`);
1998
- await ready.exec(["rm", "-f", CHECK_ITEMS_FILE], CHECKOUT_DIR, { logPath });
1999
- const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath });
2000
- running.catch(() => void 0);
2001
- const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
2002
- let outcome;
2003
- try {
2004
- outcome = await Promise.race([running, timer.done]);
2005
- } catch (err) {
2006
- timer.cancel();
2007
- await record(check, "fail", `the command could not be run: ${describeCallFailure(err)}`);
2008
- continue;
2009
- }
2010
- timer.cancel();
2011
- if (outcome === "timeout") {
2012
- await record(check, "fail", `ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
2013
- const restarting = ready.restart().catch(() => false);
2014
- const restartTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
2015
- usable = await Promise.race([restarting, restartTimer.done]) === true;
2016
- restartTimer.cancel();
2017
- continue;
2018
- }
2019
- const read = await ready.exec(["head", "-c", String(MAX_CHECK_ITEMS_BYTES + 1), CHECK_ITEMS_FILE], CHECKOUT_DIR);
2020
- const items = commandItems(read, (text2) => scrubReason(text2, config) ?? text2);
2021
- if (!items.ok) {
2022
- await record(check, "fail", items.reason);
2023
- continue;
2024
- }
2025
- const failing = items.items === void 0 ? void 0 : itemsSentence(items.items, { one: "check item", many: "check items" });
2026
- if (outcome.status === 0 && failing === void 0) {
2027
- log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
2028
- await record(check, "pass", void 0, "", items.items);
2029
- continue;
2030
- }
2031
- if (outcome.status === 0) {
2032
- await record(check, "fail", failing, "", items.items);
2033
- continue;
2034
- }
2035
- const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
2036
- const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
2037
- await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}`, "", items.items);
1635
+ input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
2038
1636
  }
1637
+ return { ok: false, reason };
1638
+ };
1639
+ if (input.aborted()) return bail(ENDED);
1640
+ mkdirSync(buildDir2, { recursive: true });
1641
+ chmodSync(buildDir2, 493);
1642
+ try {
1643
+ await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true, routeLog: (line) => input.log.line(`#${item.number} ${line}`) });
2039
1644
  } catch (err) {
2040
- log.error(`${tag} checks stopped: ${describeCallFailure(err)}`);
2041
- } finally {
2042
- if (job.checkContainer !== void 0) {
2043
- await cleanupContainer(runtime, job.checkContainer).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
2044
- delete job.checkContainer;
2045
- writeJobFile(jobFilePath, job);
2046
- }
2047
- if (job.checkMicrovm !== void 0) {
2048
- await cleanupMicroVm(runtime, job.checkMicrovm).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
2049
- delete job.checkMicrovm;
2050
- writeJobFile(jobFilePath, job);
2051
- }
2052
- rmSync(scratch, { recursive: true, force: true });
2053
- rmSync(join(jobDir, "checks", EVAL_INPUT_NAME), { recursive: true, force: true });
2054
- if (existsSync(scratch)) log.error(`${tag} the checks' scratch directory could not be removed`);
2055
- log.line(`${tag} checks done (${formatDuration(now().getTime() - startedMs)})`);
1645
+ return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
2056
1646
  }
1647
+ input.save(state, environment);
1648
+ if (input.aborted()) return bail(ENDED);
1649
+ const checkout = join(workDir, "checkout");
1650
+ const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
1651
+ const cloned = await runtime.clone({ remote, dest: checkout });
1652
+ if (!cloned.ok) return bail(cloned.reason);
1653
+ if (input.aborted()) return bail(ENDED);
1654
+ return { ok: true, workdir: checkout, state, environment };
1655
+ }
1656
+ function cleanupProcess(runtime, state) {
1657
+ return closeHostBuild(runtime, state);
2057
1658
  }
2058
1659
 
2059
1660
  // src/runner/judge-verdict.ts
@@ -2137,15 +1738,10 @@ ${rows}
2137
1738
  }
2138
1739
 
2139
1740
  // src/runner/judge.ts
2140
- var LIMIT_KEY_TAIL2 = " (runner.checks.timeLimitMinutes)";
1741
+ var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
2141
1742
  var MAX_DIFF_BYTES = 4 * 1024 * 1024;
2142
- var MAX_TEST_FILES = 2e4;
2143
- var MAX_TEST_FILE_BYTES = 1024 * 1024;
2144
1743
  var NO_CLAUDE = "the image has no Claude Code for the judge";
2145
1744
  var REPORT_CAPTION = "The host's criteria judge, each criterion judged against the test that cites it";
2146
- async function loadCitationFinder() {
2147
- return await import(pathToFileURL(join(resolvePackRoot(), "maintained", "outerlayer-build", "scripts", "criterion-citations.mjs")).href);
2148
- }
2149
1745
  function testFilesIn(root, isTest, limits = { files: MAX_TEST_FILES, bytes: MAX_TEST_FILE_BYTES }) {
2150
1746
  const files = [];
2151
1747
  const walk = (dir, prefix) => {
@@ -2179,26 +1775,6 @@ async function hostPassedNames(config, item, fetchImpl) {
2179
1775
  } while (since !== null);
2180
1776
  return [...latest].filter(([, result]) => result === "pass").map(([name]) => name);
2181
1777
  }
2182
- function criteriaOf(workItem) {
2183
- const criteria = Object(Object(workItem).criteria);
2184
- if (!Array.isArray(criteria?.items)) return void 0;
2185
- const drafted = criteria.drafted === true;
2186
- const items = [];
2187
- for (const entry of criteria.items) {
2188
- const { id, text: text2, proof } = Object(entry);
2189
- if (typeof id !== "string" || typeof text2 !== "string") continue;
2190
- items.push({ id, text: text2, proof: typeof proof === "string" ? proof : null, drafted });
2191
- }
2192
- return items.length === 0 ? void 0 : items;
2193
- }
2194
- function hostItemsOf(workItem, name) {
2195
- const checks = Object(workItem).checks;
2196
- if (!Array.isArray(checks)) return [];
2197
- const check = checks.find((entry) => Object(entry).name === name);
2198
- if (check?.source !== "host") return [];
2199
- const parsed = CheckItemsSchema.safeParse(check.items);
2200
- return parsed.success ? parsed.data : [];
2201
- }
2202
1778
  function environmentFor(input) {
2203
1779
  const { config, job, jobDir, deps } = input;
2204
1780
  if (deps.judgeEnvironment !== void 0) return deps.judgeEnvironment;
@@ -2419,7 +1995,7 @@ async function runJudge(input) {
2419
1995
  const reason = at.reason ?? failing;
2420
1996
  const report = at.judgeReport !== void 0 && at.judgeReport !== "" ? at.judgeReport : renderJudgeReport({ check: check.name, result, reason, items, commit: at.head, extraHead: at.finder.renderBlock(at.citations) });
2421
1997
  const link = await uploadReport(check, report, { branch: at.branch, head: at.head });
2422
- await record(check.name, result, reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL2 : "", link, items: sendable(items) });
1998
+ await record(check.name, result, reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL : "", link, items: sendable(items) });
2423
1999
  }
2424
2000
  async function finishWhole(check, at) {
2425
2001
  const where = { head: at.head, branch: at.branch };
@@ -2442,7 +2018,7 @@ async function runJudge(input) {
2442
2018
  }
2443
2019
  async function recordWhole(check, at) {
2444
2020
  const link = at.report === void 0 ? void 0 : await uploadReport(check, at.report, { branch: at.branch, head: at.head });
2445
- await record(check.name, at.result, at.reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL2 : "", link, items: sendable(at.items) });
2021
+ await record(check.name, at.result, at.reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL : "", link, items: sendable(at.items) });
2446
2022
  }
2447
2023
  function sendable(items) {
2448
2024
  if (items === void 0) return void 0;
@@ -3385,231 +2961,514 @@ async function retryFailedReleases(jobsDirPath, config, deps) {
3385
2961
  job.releaseFailed = release.reason;
3386
2962
  writeJobFile(jobFilePath, job);
3387
2963
  }
3388
- results.push({ job, released: release.ok });
3389
- }
3390
- return results;
3391
- }
3392
- function isUnfinished(job) {
3393
- return job.outcome === void 0 && job.endedAt === void 0;
3394
- }
3395
- function unfinishedJobs(jobsDirPath) {
3396
- if (!existsSync(jobsDirPath)) return [];
3397
- const found = [];
3398
- for (const entry of readdirSync(jobsDirPath, { withFileTypes: true })) {
3399
- if (!entry.isDirectory()) continue;
3400
- const dirPath = join(jobsDirPath, entry.name);
3401
- const job = readJobFile(join(dirPath, "job.json"));
3402
- if (job && isUnfinished(job)) found.push({ dirPath, job });
2964
+ results.push({ job, released: release.ok });
2965
+ }
2966
+ return results;
2967
+ }
2968
+ function isUnfinished(job) {
2969
+ return job.outcome === void 0 && job.endedAt === void 0;
2970
+ }
2971
+ function unfinishedJobs(jobsDirPath) {
2972
+ if (!existsSync(jobsDirPath)) return [];
2973
+ const found = [];
2974
+ for (const entry of readdirSync(jobsDirPath, { withFileTypes: true })) {
2975
+ if (!entry.isDirectory()) continue;
2976
+ const dirPath = join(jobsDirPath, entry.name);
2977
+ const job = readJobFile(join(dirPath, "job.json"));
2978
+ if (job && isUnfinished(job)) found.push({ dirPath, job });
2979
+ }
2980
+ return found;
2981
+ }
2982
+ function isContainerTracked(job) {
2983
+ return job.container?.tracking === "container";
2984
+ }
2985
+ function isMicroVmTracked(job) {
2986
+ return job.microvm?.tracking === "microvm";
2987
+ }
2988
+ function isTracked(job) {
2989
+ return isContainerTracked(job) || isMicroVmTracked(job);
2990
+ }
2991
+ function trackingOf(job) {
2992
+ return job.container?.tracking ?? job.microvm?.tracking;
2993
+ }
2994
+ function isReadable(job) {
2995
+ return readsJobState(job, READ_JOB_STATE_FORMATS);
2996
+ }
2997
+ function listUnreadableJobs(jobsDirPath) {
2998
+ return unfinishedJobs(jobsDirPath).flatMap(({ dirPath, job }) => isReadable(job) ? [] : [{ dirPath, job, format: jobStateFormatOf(job) }]);
2999
+ }
3000
+ function listTrackedJobs(jobsDirPath) {
3001
+ return unfinishedJobs(jobsDirPath).flatMap(({ dirPath, job }) => {
3002
+ if (!isReadable(job)) return [];
3003
+ const tracking = trackingOf(job);
3004
+ if (tracking === void 0) return [];
3005
+ if ((tracking === "container" || tracking === "microvm") && inTeardown(job)) return [];
3006
+ return [{ dirPath, job, known: tracking === "container" || tracking === "microvm" }];
3007
+ });
3008
+ }
3009
+ function listAdoptableJobs(jobsDirPath) {
3010
+ return unfinishedJobs(jobsDirPath).filter(
3011
+ ({ job }) => isReadable(job) && job.microvm === void 0 && job.pid !== void 0 && job.start !== void 0 && isSameProcess(job.pid, job.start)
3012
+ );
3013
+ }
3014
+ async function recoverLostJobs(jobsDirPath, config, deps) {
3015
+ const recovered = [];
3016
+ for (const { dirPath, job } of unfinishedJobs(jobsDirPath)) {
3017
+ if (!isReadable(job)) continue;
3018
+ const tracking = trackingOf(job);
3019
+ if (tracking !== void 0 && tracking !== "container" && tracking !== "microvm") {
3020
+ recovered.push(await finishJob(job, dirPath, "interrupted", config, deps, { reason: UNKNOWN_TRACKING_REASON }));
3021
+ continue;
3022
+ }
3023
+ if ((tracking === "container" || tracking === "microvm") && !inTeardown(job)) continue;
3024
+ const alive = job.pid !== void 0 && job.start !== void 0 && isSameProcess(job.pid, job.start);
3025
+ if (alive) continue;
3026
+ const recorded = outcomeFromExitCode(dirPath);
3027
+ if (recorded !== void 0) job.exitCode = recorded.code;
3028
+ recovered.push(await finishJob(job, dirPath, recorded?.outcome ?? "interrupted", config, deps));
3029
+ }
3030
+ return recovered;
3031
+ }
3032
+
3033
+ // src/runner/host-checks.ts
3034
+ var MAX_VALIDATOR_FILES = 20;
3035
+ var VALIDATORS_DIR = ".outerlayer/validators";
3036
+ var POLICY_FILE = ".outerlayer/policy.yaml";
3037
+ var LIMIT_KEY_TAIL2 = " (runner.checks.timeLimitMinutes)";
3038
+ var MAX_CHECK_ITEMS_BYTES = 1024 * 1024;
3039
+ function commandItems(read, scrub) {
3040
+ if (read.status !== 0 || read.stdout.trim() === "") return { ok: true, items: void 0 };
3041
+ if (Buffer.byteLength(read.stdout) > MAX_CHECK_ITEMS_BYTES) return { ok: false, reason: `its check items file is longer than ${MAX_CHECK_ITEMS_BYTES} bytes` };
3042
+ let raw;
3043
+ try {
3044
+ raw = JSON.parse(read.stdout);
3045
+ } catch {
3046
+ return { ok: false, reason: "its check items file (OUTERLAYER_CHECK_ITEMS) is not JSON" };
3047
+ }
3048
+ const parsed = CheckItemsSchema.safeParse(raw);
3049
+ if (!parsed.success) {
3050
+ return { ok: false, reason: `its check items file does not match the check item shape: ${zodProblem(parsed.error.issues)}` };
3051
+ }
3052
+ return { ok: true, items: parsed.data.map((item) => item.reason === void 0 ? item : { ...item, reason: scrub(item.reason) }) };
3053
+ }
3054
+ function isJudgeCheck(check) {
3055
+ return "judge" in check;
3056
+ }
3057
+ function lastLine(text2) {
3058
+ return text2.split(/\r\n|\r|\n/).map((line) => line.trim()).filter((line) => line !== "").at(-1);
3059
+ }
3060
+ function readInside(root, rel) {
3061
+ const path = join(root, rel);
3062
+ try {
3063
+ if (!lstatSync(path).isFile()) return void 0;
3064
+ const inside = realpathSync(path);
3065
+ const base = realpathSync(root);
3066
+ return inside.startsWith(base + sep) ? readFileSync(inside, "utf8") : void 0;
3067
+ } catch {
3068
+ return void 0;
3069
+ }
3070
+ }
3071
+ var MAINTAINED_BRIEF = /^\.(?:agents|outerlayer)\/skills\/(outerlayer-[a-z]+)\/([A-Za-z0-9_./-]+)$/;
3072
+ function readBrief(root, brief) {
3073
+ const own = readInside(root, brief);
3074
+ if (own !== void 0) return own;
3075
+ const match = MAINTAINED_BRIEF.exec(brief);
3076
+ if (match === null || match[2].split("/").includes("..") || !MAINTAINED_SKILLS.includes(match[1])) return void 0;
3077
+ try {
3078
+ return readFileSync(join(resolvePackRoot(), "maintained", match[1], match[2]), "utf8");
3079
+ } catch {
3080
+ return void 0;
3081
+ }
3082
+ }
3083
+ function validatorFilesIn(root) {
3084
+ const dir = join(root, VALIDATORS_DIR);
3085
+ let names;
3086
+ try {
3087
+ if (!lstatSync(dir).isDirectory()) return [];
3088
+ names = readdirSync(dir);
3089
+ } catch {
3090
+ return [];
3091
+ }
3092
+ const files = [];
3093
+ for (const name of names.filter((entry) => /\.ya?ml$/.test(entry)).sort().slice(0, MAX_VALIDATOR_FILES)) {
3094
+ const path = `${VALIDATORS_DIR}/${name}`;
3095
+ const content = readInside(root, path);
3096
+ if (content !== void 0) files.push({ path, content });
3097
+ }
3098
+ return files;
3099
+ }
3100
+ var CRITERIA_CHECK = "acceptance-criteria";
3101
+ function asHostReads(file, base, log, tag) {
3102
+ const upgraded = upgradedStarterText(file.path, file.content, { base });
3103
+ if (upgraded === void 0) return file;
3104
+ log.line(`${tag} ${file.path} is an unedited copy an earlier release wrote, so it is read as this CLI writes it`);
3105
+ return { path: file.path, content: upgraded };
3106
+ }
3107
+ function checksIn(root, log, tag) {
3108
+ const base = existsSync(join(root, AGENTS_SKILLS_BASE)) ? AGENTS_SKILLS_BASE : OUTERLAYER_SKILLS_BASE;
3109
+ const policyText = readInside(root, POLICY_FILE);
3110
+ const policy = policyText === void 0 ? null : asHostReads({ path: POLICY_FILE, content: policyText }, base, log, tag);
3111
+ const loaded = parsePolicy(policy, validatorFilesIn(root).map((file) => asHostReads(file, base, log, tag)));
3112
+ for (const problem of loaded.problems) log.error(`${tag} ${problem.file}: ${problem.problem}`);
3113
+ const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null);
3114
+ const checks = [];
3115
+ for (const def of hostChecks) {
3116
+ if (def.judge !== void 0) checks.push({ name: def.declaresEmit, judge: { brief: def.judge.brief, text: readBrief(root, def.judge.brief), subjects: def.judge.subjects } });
3117
+ else if (def.command !== void 0) checks.push({ name: def.declaresEmit, command: def.command });
3118
+ }
3119
+ const unrecorded = loaded.customs.filter((def) => def.level !== "off" && def.declaresEmit === CRITERIA_CHECK && !checks.some((check) => check.name === CRITERIA_CHECK)).map((def) => def.id);
3120
+ return { checks, unrecorded };
3121
+ }
3122
+ async function readDefinitions(input, repository, scratch, token, tokens) {
3123
+ const { config, job, deps } = input;
3124
+ const tag = `#${job.item}`;
3125
+ const stage = deps.stage ?? stageGovernedContext;
3126
+ const controlPlaneUrl = (controlPlane) => deps.runtime.remoteFor?.(controlPlane) ?? `${deps.runtime.github.gitUrl}/${controlPlane}.git`;
3127
+ const governing = (job.container ?? job.microvm)?.governing;
3128
+ const kept = keptContextTree(input.jobDir, governing);
3129
+ if (kept !== void 0) return { ok: true, ...checksIn(kept, deps.log, tag) };
3130
+ const staged = await stage({
3131
+ config,
3132
+ repository,
3133
+ ...governing === void 0 ? {} : { source: { repository: governing.repository, ref: governing.ref, sha: governing.commit } },
3134
+ buildId: `checks-${randomBytes(4).toString("hex")}`,
3135
+ buildDir: scratch,
3136
+ fetchImpl: deps.fetchImpl,
3137
+ controlPlaneUrl,
3138
+ controlPlaneToken: tokens === void 0 ? void 0 : () => tokens.controlPlane(),
3139
+ log: deps.log,
3140
+ tag
3141
+ });
3142
+ if (staged.kind === "refused") return { ok: false, reason: staged.reason };
3143
+ if (staged.kind === "staged") {
3144
+ try {
3145
+ return { ok: true, ...checksIn(join(staged.dir, "tree"), deps.log, tag) };
3146
+ } finally {
3147
+ rmSync(staged.dir, { recursive: true, force: true });
3148
+ }
3403
3149
  }
3404
- return found;
3405
- }
3406
- function isContainerTracked(job) {
3407
- return job.container?.tracking === "container";
3408
- }
3409
- function isMicroVmTracked(job) {
3410
- return job.microvm?.tracking === "microvm";
3411
- }
3412
- function isTracked(job) {
3413
- return isContainerTracked(job) || isMicroVmTracked(job);
3414
- }
3415
- function trackingOf(job) {
3416
- return job.container?.tracking ?? job.microvm?.tracking;
3417
- }
3418
- function isReadable(job) {
3419
- return readsJobState(job, READ_JOB_STATE_FORMATS);
3420
- }
3421
- function listUnreadableJobs(jobsDirPath) {
3422
- return unfinishedJobs(jobsDirPath).flatMap(({ dirPath, job }) => isReadable(job) ? [] : [{ dirPath, job, format: jobStateFormatOf(job) }]);
3150
+ const repo = join(scratch, "repo");
3151
+ const remote = deps.runtime.remoteFor?.(repository) ?? `${deps.runtime.github.gitUrl}/${repository}.git`;
3152
+ const started = (job.container ?? job.microvm)?.startCommit;
3153
+ const commit = started !== void 0 && started !== "" ? started : job.environment?.recipe?.sourceCommit ?? await deps.runtime.readHead(remote, scratch);
3154
+ if (commit === void 0) return { ok: false, reason: "the repository's default branch could not be read" };
3155
+ const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: repo, commit, token });
3156
+ if (!fetched.ok) return { ok: false, reason: fetched.reason };
3157
+ deps.log.line(`${tag} check definitions read at ${commit.slice(0, 12)}`);
3158
+ return { ok: true, ...checksIn(repo, deps.log, tag) };
3423
3159
  }
3424
- function listTrackedJobs(jobsDirPath) {
3425
- return unfinishedJobs(jobsDirPath).flatMap(({ dirPath, job }) => {
3426
- if (!isReadable(job)) return [];
3427
- const tracking = trackingOf(job);
3428
- if (tracking === void 0) return [];
3429
- if ((tracking === "container" || tracking === "microvm") && inTeardown(job)) return [];
3430
- return [{ dirPath, job, known: tracking === "container" || tracking === "microvm" }];
3160
+ function timeoutAfter(ms, sleep) {
3161
+ if (sleep !== void 0) return { done: sleep(ms).then(() => "timeout"), cancel: () => void 0 };
3162
+ let timer;
3163
+ const done = new Promise((resolve) => {
3164
+ timer = setTimeout(() => resolve("timeout"), ms);
3165
+ timer.unref();
3431
3166
  });
3167
+ return { done, cancel: () => clearTimeout(timer) };
3432
3168
  }
3433
- function listAdoptableJobs(jobsDirPath) {
3434
- return unfinishedJobs(jobsDirPath).filter(
3435
- ({ job }) => isReadable(job) && job.microvm === void 0 && job.pid !== void 0 && job.start !== void 0 && isSameProcess(job.pid, job.start)
3436
- );
3169
+ function noCleanEnvironment(job) {
3170
+ const ran = job.build?.workDir !== void 0 ? "builtin:process" : "a provision hook of the host's own";
3171
+ return `the host has no clean environment for checks: this build ran under ${ran}, and checks run only in a new container or microVM from a build's image`;
3437
3172
  }
3438
- async function recoverLostJobs(jobsDirPath, config, deps) {
3439
- const recovered = [];
3440
- for (const { dirPath, job } of unfinishedJobs(jobsDirPath)) {
3441
- if (!isReadable(job)) continue;
3442
- const tracking = trackingOf(job);
3443
- if (tracking !== void 0 && tracking !== "container" && tracking !== "microvm") {
3444
- recovered.push(await finishJob(job, dirPath, "interrupted", config, deps, { reason: UNKNOWN_TRACKING_REASON }));
3445
- continue;
3446
- }
3447
- if ((tracking === "container" || tracking === "microvm") && !inTeardown(job)) continue;
3448
- const alive = job.pid !== void 0 && job.start !== void 0 && isSameProcess(job.pid, job.start);
3449
- if (alive) continue;
3450
- const recorded = outcomeFromExitCode(dirPath);
3451
- if (recorded !== void 0) job.exitCode = recorded.code;
3452
- recovered.push(await finishJob(job, dirPath, recorded?.outcome ?? "interrupted", config, deps));
3453
- }
3454
- return recovered;
3173
+ function makeRecorder(input) {
3174
+ const { config, job, deps } = input;
3175
+ const tag = `#${job.item}`;
3176
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
3177
+ let signer;
3178
+ return async (name, result, sentence, opts = {}) => {
3179
+ signer ??= await signerFor(config.configPath);
3180
+ const keyTail = opts.keyTail ?? "";
3181
+ const body = sentence === void 0 ? void 0 : cutReasonLine(`${scrubReason(sentence, config) ?? "check failed"}${keyTail}`);
3182
+ const clientEmitId = `host-${createHash("sha256").update(`${job.claimId}:${job.startedAt}:${name}${opts.idTag === void 0 ? "" : `:${opts.idTag}`}`).digest("hex").slice(0, 40)}`;
3183
+ const sent = await retrySend(
3184
+ () => postJson(
3185
+ config.url,
3186
+ "/v1/emitted-results",
3187
+ config.apiKey,
3188
+ config.appId,
3189
+ {
3190
+ schemaVersion: 1,
3191
+ // A field left undefined is not sent: the request is JSON.
3192
+ emit: { clientEmitId, name, result, body, link: opts.link, items: opts.items, emittedAt: now().toISOString(), item: { number: Number(job.item) } }
3193
+ },
3194
+ deps.fetchImpl,
3195
+ void 0,
3196
+ signer
3197
+ ),
3198
+ { now: deps.now }
3199
+ );
3200
+ if (sent.ok) deps.log.line(`${tag} check ${name} ${result}${body === void 0 ? "" : `: ${body}`}`);
3201
+ else deps.log.error(`${tag} check ${name} ${result} could not be recorded: ${sent.reason}`);
3202
+ };
3455
3203
  }
3456
-
3457
- // src/runner/sender.ts
3458
- var STOP_WAIT_MS = 3e4;
3459
- var POLL_MS = 200;
3460
- var ASK_AGAIN_MS = 1e3;
3461
- var EXIT_REPORT_WAIT_MS = 2e3;
3462
- var exitsLogged = /* @__PURE__ */ new Map();
3463
- var BUILD_HOME = "/build/home";
3464
- async function startSender(input) {
3204
+ async function runHostChecks(input) {
3465
3205
  const { config, job, jobDir, deps } = input;
3206
+ const { runtime, log } = deps;
3466
3207
  const tag = `#${job.item}`;
3467
- if (job.itemKey === void 0) return void 0;
3468
- const spawnFn = deps.spawnFn ?? spawn;
3208
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
3209
+ const repository = job.repository ?? job.container?.repository;
3210
+ if (repository === void 0) return;
3211
+ const jobFilePath = join(jobDir, "job.json");
3212
+ const tokens = runtime.credentials.get(jobDir);
3213
+ let readToken;
3469
3214
  try {
3470
- const launch = launchFor(config, job, jobDir, deps);
3471
- const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
3472
- let pid;
3473
- let exitLogged;
3474
- try {
3475
- const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
3476
- child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
3477
- exitLogged = new Promise((resolve) => {
3478
- child.on("exit", (code, signal) => {
3479
- deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`);
3480
- resolve();
3481
- });
3215
+ readToken = await tokens?.token("read");
3216
+ } catch (err) {
3217
+ log.error(`${tag} no read token for the checks: ${describeCallFailure(err)}`);
3218
+ }
3219
+ const scratch = join(jobDir, "checks", "definitions");
3220
+ rmSync(scratch, { recursive: true, force: true });
3221
+ mkdirSync(scratch, { recursive: true });
3222
+ let definitions;
3223
+ try {
3224
+ definitions = await readDefinitions(input, repository, scratch, readToken, tokens);
3225
+ } catch (err) {
3226
+ definitions = { ok: false, reason: describeCallFailure(err) };
3227
+ } finally {
3228
+ removeStagedContext(scratch);
3229
+ }
3230
+ if (!definitions.ok) {
3231
+ log.line(`${tag} checks not run: the definitions could not be read: ${definitions.reason}`);
3232
+ rmSync(scratch, { recursive: true, force: true });
3233
+ return;
3234
+ }
3235
+ const checks = definitions.checks.filter((check) => !isJudgeCheck(check));
3236
+ if (checks.length === 0) {
3237
+ rmSync(scratch, { recursive: true, force: true });
3238
+ return;
3239
+ }
3240
+ log.line(`${tag} checks started (${checks.map((check) => check.name).join(", ")})`);
3241
+ const startedMs = now().getTime();
3242
+ const recorder = makeRecorder({ config, job, deps });
3243
+ const record = (check, result, sentence, keyTail = "", items) => recorder(check.name, result, sentence, { keyTail, items });
3244
+ const failAll = async (reason, keyTail = "") => {
3245
+ log.error(`${tag} checks failed: ${reason}${keyTail}`);
3246
+ for (const check of checks) await record(check, "fail", reason, keyTail);
3247
+ };
3248
+ try {
3249
+ if (job.container === void 0 && job.microvm === void 0) return await failAll(noCleanEnvironment(job));
3250
+ if (tokens === void 0) return await failAll("the build's repository tokens are no longer held, so the branch cannot be fetched");
3251
+ if (job.branch === void 0) return await failAll("the build pushed no branch: the claim names none");
3252
+ const claimed = repositoriesOf({ repositories: job.repositories, repository, branch: job.branch });
3253
+ if (claimed.length === 0) return await failAll("the claim names no repository to check out");
3254
+ const remoteOf = (name) => runtime.remoteFor?.(name) ?? `${runtime.github.gitUrl}/${name}.git`;
3255
+ const remote = remoteOf(repository);
3256
+ const pushed = /* @__PURE__ */ new Map();
3257
+ const unpushed = [];
3258
+ let primaryHead;
3259
+ for (const entry of claimed) {
3260
+ let entryToken = readToken;
3261
+ if (!entry.primary) {
3262
+ try {
3263
+ entryToken = await tokens.token("read", entry.repository);
3264
+ } catch (err) {
3265
+ return await failAll(`no read token for ${entry.repository}: ${describeCallFailure(err)}`);
3266
+ }
3267
+ }
3268
+ const read = await (deps.readBranch ?? readBranchHead)({ remote: remoteOf(entry.repository), dir: scratch, branch: entry.branch, token: entryToken });
3269
+ if (read.kind === "unreadable") return await failAll(`the branch ${entry.branch} could not be read${entry.primary ? "" : ` on ${entry.repository}`}: ${read.reason}`);
3270
+ if (read.kind === "absent") {
3271
+ unpushed.push(entry);
3272
+ continue;
3273
+ }
3274
+ pushed.set(entry.repository, entry.branch);
3275
+ if (entry.primary) primaryHead = read.sha;
3276
+ }
3277
+ if (pushed.size === 0) {
3278
+ return await failAll(`the build pushed no branch: ${unpushed.map((entry) => `${entry.branch} does not exist on ${entry.repository}`).join("; ")}`);
3279
+ }
3280
+ const repositoriesToCheckOut = claimed;
3281
+ const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
3282
+ const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
3283
+ const environmentLog = join(jobDir, "checks", "environment.log");
3284
+ let transcripts = { ok: false, reason: "the runner holds no way to read the build's sessions" };
3285
+ if (deps.collectSessions !== void 0) {
3286
+ try {
3287
+ transcripts = await deps.collectSessions();
3288
+ } catch (err) {
3289
+ transcripts = { ok: false, reason: describeCallFailure(err) };
3290
+ }
3291
+ }
3292
+ if (job.microvm !== void 0) {
3293
+ if (deps.endBuildMicroVm === void 0) return await failAll("the runner has no way to stop the build's microVM, so no check environment was made");
3294
+ try {
3295
+ await deps.endBuildMicroVm();
3296
+ } catch (err) {
3297
+ return await failAll(`the build's microVM could not be stopped, so no check environment was made: ${describeCallFailure(err)}`);
3298
+ }
3299
+ }
3300
+ let abandoned = false;
3301
+ const setup = async () => {
3302
+ const started = job.microvm !== void 0 ? await startCheckMicroVm({
3303
+ config,
3304
+ runtime,
3305
+ job,
3306
+ jobDir,
3307
+ tokens,
3308
+ log,
3309
+ aborted: () => abandoned,
3310
+ save: (state) => {
3311
+ if (abandoned) return;
3312
+ job.checkMicrovm = state;
3313
+ writeJobFile(jobFilePath, job);
3314
+ }
3315
+ }) : await startCheckContainer({
3316
+ config,
3317
+ runtime,
3318
+ job,
3319
+ jobDir,
3320
+ tokens,
3321
+ log,
3322
+ save: (state) => {
3323
+ job.checkContainer = state;
3324
+ writeJobFile(jobFilePath, job);
3325
+ }
3482
3326
  });
3483
- child.unref?.();
3484
- pid = child.pid;
3485
- } finally {
3486
- closeSync(fd);
3487
- }
3488
- if (pid === void 0) {
3489
- deps.log.line(`${tag} session sender did not start: no pid`);
3490
- return void 0;
3491
- }
3492
- exitsLogged.set(pid, exitLogged);
3493
- deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
3494
- return { pid, start: startTimeOf(pid) ?? void 0 };
3495
- } catch (err) {
3496
- deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
3497
- return void 0;
3498
- }
3499
- }
3500
- function launchFor(config, job, jobDir, deps) {
3501
- if (job.microvm !== void 0) {
3502
- const runtime = deps.runtime ?? createContainerRuntime();
3503
- const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
3504
- return { ...microVmSenderCommand({ job, jobDir, runtime, config, cli }), cwd: jobDir };
3505
- }
3506
- if (job.container !== void 0) {
3507
- return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
3508
- }
3509
- const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
3510
- const build = job.build;
3511
- const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
3512
- if (build?.exec !== void 0) {
3513
- return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
3514
- }
3515
- return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
3516
- }
3517
- async function restartSender(input) {
3518
- const { job, deps } = input;
3519
- const state = job.container;
3520
- if (state !== void 0 && job.itemKey !== void 0) {
3521
- const runtime = deps.runtime ?? createContainerRuntime();
3522
- const pidfile = senderPidfile(BUILD_HOME, Number(job.item));
3523
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
3524
- const running = async () => {
3525
- const probe = await runtime.host.exec({ container: state.container, user: state.agentUser ?? ADDED_USER, env: { literals: {}, passed: {} }, argv: ["/bin/sh", "-c", senderAliveScript(pidfile)] });
3526
- return probe.status === 0 && probe.stdout.trim() === "alive";
3327
+ if (!started.ok) return `the check environment could not be made: ${started.reason}`;
3328
+ const made = "container" in started ? started.container : started.environment;
3329
+ const steps = [
3330
+ {
3331
+ argv: ["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"],
3332
+ workdir: "/build/work",
3333
+ failed: (status) => `configuring git failed (exit ${status})`
3334
+ },
3335
+ ...repositoriesToCheckOut.flatMap((entry) => {
3336
+ const branch = pushed.get(entry.repository);
3337
+ const path = entry.primary ? CHECKOUT_DIR : repositoryPath(entry.repository);
3338
+ const clone = {
3339
+ argv: ["git", "clone", "--filter=blob:none", "--single-branch", ...branch === void 0 ? [] : ["--branch", branch], `https://github.com/${entry.repository}.git`, path],
3340
+ workdir: "/build/work",
3341
+ failed: (status) => `the clone of ${branch ?? `the default branch of ${entry.repository}`} failed (exit ${status})`
3342
+ };
3343
+ return entry.primary && repositoriesToCheckOut.length > 1 ? [
3344
+ clone,
3345
+ {
3346
+ argv: ["sh", "-c", 'mkdir -p "$(dirname "$1")" && ln -s "$2" "$1"', "link", repositoryPath(entry.repository), CHECKOUT_DIR],
3347
+ workdir: "/build/work",
3348
+ failed: (status) => `linking ${entry.repository} at ${repositoryPath(entry.repository)} failed (exit ${status})`
3349
+ }
3350
+ ] : [clone];
3351
+ })
3352
+ ];
3353
+ for (const step of steps) {
3354
+ if (abandoned) return "the check environment setup ran out of time";
3355
+ const ran = await made.exec(step.argv, step.workdir, { logPath: environmentLog });
3356
+ if (ran.status !== 0) return `the check environment could not be prepared: ${step.failed(ran.status)}`;
3357
+ }
3358
+ const lifecycleDir = lifecycleWorkdir(repositoriesToCheckOut, (job.container ?? job.microvm)?.governing?.repository);
3359
+ for (const step of lifecycleSteps((job.container ?? job.microvm)?.lifecycle ?? {})) {
3360
+ log.line(`${tag} checks lifecycle ${step.name} (${step.source})`);
3361
+ const ran = await made.exec(step.argv, lifecycleDir, { logPath: environmentLog });
3362
+ if (ran.status !== 0) return `the check environment could not be prepared: ${lifecycleFailureReason(step, ran.status)}`;
3363
+ }
3364
+ return made;
3527
3365
  };
3366
+ const setupTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
3367
+ let settled;
3528
3368
  try {
3529
- await signalInContainer(runtime, state, pidfile, "TERM");
3530
- const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
3531
- for (let waited = 0; await running() && waited < deadline; waited += 1) await sleep(POLL_MS);
3532
- if (await running()) {
3533
- deps.log.error(`#${job.item} the session sender an earlier runner left did not stop in time \xB7 killing it`);
3534
- await signalInContainer(runtime, state, pidfile, "KILL");
3369
+ const outcome = await Promise.race([
3370
+ setup().catch((err) => `the check environment could not be made: ${describeCallFailure(err)}`),
3371
+ setupTimer.done
3372
+ ]);
3373
+ if (outcome === "timeout") {
3374
+ abandoned = true;
3375
+ return await failAll(`the check environment setup ran out of time after ${minutes}`, LIMIT_KEY_TAIL2);
3535
3376
  }
3536
- } catch (err) {
3537
- deps.log.error(`#${job.item} stopping the session sender an earlier runner left failed: ${describeCallFailure(err)} \xB7 continuing`);
3377
+ settled = outcome;
3378
+ } finally {
3379
+ setupTimer.cancel();
3538
3380
  }
3539
- }
3540
- return startSender(input);
3541
- }
3542
- function isLive(sender) {
3543
- if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
3544
- try {
3545
- process.kill(sender.pid, 0);
3546
- return true;
3547
- } catch {
3548
- return false;
3549
- }
3550
- }
3551
- function signalHost(sender, signal) {
3552
- if (!isLive(sender)) return;
3553
- try {
3554
- process.kill(-sender.pid, signal);
3555
- } catch {
3381
+ if (typeof settled === "string") return await failAll(settled);
3382
+ const ready = settled;
3556
3383
  try {
3557
- process.kill(sender.pid, signal);
3558
- } catch {
3384
+ await writeEvalInput({
3385
+ job,
3386
+ dir: ready.evalInput.dir,
3387
+ scratch,
3388
+ remote,
3389
+ readToken,
3390
+ branchHead: primaryHead ?? (job.container ?? job.microvm)?.startCommit ?? "",
3391
+ transcripts,
3392
+ scrub: transcriptScrub(config, [config.apiKey, job.itemKey, readToken]),
3393
+ readWorkItem: () => readWorkItem(config, job.item, deps.fetchImpl),
3394
+ fetchDiff: deps.fetchDiff ?? ((input2) => diffBetween(input2)),
3395
+ log
3396
+ });
3397
+ } catch (err) {
3398
+ return await failAll(`the checks' input could not be written: ${describeCallFailure(err)}`);
3559
3399
  }
3560
- }
3561
- }
3562
- async function signalInGuest(runtime, state, pidfile, signal) {
3563
- const { status } = await runtime.vm.exec(state.vsockPath, {
3564
- argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
3565
- user: state.agentUser ?? ADDED_USER,
3566
- cwd: "/",
3567
- env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
3568
- });
3569
- return status === 0;
3570
- }
3571
- async function exitReported(pid) {
3572
- const logged = exitsLogged.get(pid);
3573
- let timer;
3574
- const bound = new Promise((resolve) => {
3575
- timer = setTimeout(resolve, EXIT_REPORT_WAIT_MS);
3576
- timer.unref?.();
3577
- });
3578
- await Promise.race([logged, bound]);
3579
- clearTimeout(timer);
3580
- exitsLogged.delete(pid);
3581
- }
3582
- async function stopSender(input) {
3583
- const { job, deps } = input;
3584
- const sender = job.sender;
3585
- if (sender === void 0) return;
3586
- const tag = `#${job.item}`;
3587
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
3588
- const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
3589
- const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
3590
- const signalInside = async (signal) => {
3591
- if (inContainer !== void 0) return signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
3592
- if (inGuest !== void 0) return signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
3593
- return void 0;
3594
- };
3595
- try {
3596
- let reached = true;
3597
- if (inContainer !== void 0 || inGuest !== void 0) reached = await signalInside("TERM");
3598
- else signalHost(sender, "SIGTERM");
3599
- const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
3600
- for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) {
3601
- await sleep(POLL_MS);
3602
- if (!reached && (waited + 1) % (ASK_AGAIN_MS / POLL_MS) === 0) reached = await signalInside("TERM");
3400
+ try {
3401
+ await ready.evalInput.deliver();
3402
+ } catch (err) {
3403
+ return await failAll(err instanceof DeliveryFailure ? err.message : `the checks' input could not be written: ${describeCallFailure(err)}`);
3603
3404
  }
3604
- if (isLive(sender)) {
3605
- deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
3606
- await signalInside("KILL");
3607
- signalHost(sender, "SIGKILL");
3405
+ let usable = true;
3406
+ for (const check of checks) {
3407
+ if (!usable) {
3408
+ await record(check, "fail", "the check environment could not be restarted after an earlier check ran out of time");
3409
+ continue;
3410
+ }
3411
+ const checkStartedMs = now().getTime();
3412
+ const logPath = join(jobDir, "checks", `${check.name}.log`);
3413
+ await ready.exec(["rm", "-f", CHECK_ITEMS_FILE], CHECKOUT_DIR, { logPath });
3414
+ const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath });
3415
+ running.catch(() => void 0);
3416
+ const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
3417
+ let outcome;
3418
+ try {
3419
+ outcome = await Promise.race([running, timer.done]);
3420
+ } catch (err) {
3421
+ timer.cancel();
3422
+ await record(check, "fail", `the command could not be run: ${describeCallFailure(err)}`);
3423
+ continue;
3424
+ }
3425
+ timer.cancel();
3426
+ if (outcome === "timeout") {
3427
+ await record(check, "fail", `ran out of time after ${minutes}`, LIMIT_KEY_TAIL2);
3428
+ const restarting = ready.restart().catch(() => false);
3429
+ const restartTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
3430
+ usable = await Promise.race([restarting, restartTimer.done]) === true;
3431
+ restartTimer.cancel();
3432
+ continue;
3433
+ }
3434
+ const read = await ready.exec(["head", "-c", String(MAX_CHECK_ITEMS_BYTES + 1), CHECK_ITEMS_FILE], CHECKOUT_DIR);
3435
+ const items = commandItems(read, (text2) => scrubReason(text2, config) ?? text2);
3436
+ if (!items.ok) {
3437
+ await record(check, "fail", items.reason);
3438
+ continue;
3439
+ }
3440
+ const failing = items.items === void 0 ? void 0 : itemsSentence(items.items, { one: "check item", many: "check items" });
3441
+ if (outcome.status === 0 && failing === void 0) {
3442
+ log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
3443
+ await record(check, "pass", void 0, "", items.items);
3444
+ continue;
3445
+ }
3446
+ if (outcome.status === 0) {
3447
+ await record(check, "fail", failing, "", items.items);
3448
+ continue;
3449
+ }
3450
+ const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
3451
+ const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
3452
+ await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}`, "", items.items);
3608
3453
  }
3609
- await exitReported(sender.pid);
3610
3454
  } catch (err) {
3611
- deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
3455
+ log.error(`${tag} checks stopped: ${describeCallFailure(err)}`);
3456
+ } finally {
3457
+ if (job.checkContainer !== void 0) {
3458
+ await cleanupContainer(runtime, job.checkContainer).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
3459
+ delete job.checkContainer;
3460
+ writeJobFile(jobFilePath, job);
3461
+ }
3462
+ if (job.checkMicrovm !== void 0) {
3463
+ await cleanupMicroVm(runtime, job.checkMicrovm).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
3464
+ delete job.checkMicrovm;
3465
+ writeJobFile(jobFilePath, job);
3466
+ }
3467
+ rmSync(scratch, { recursive: true, force: true });
3468
+ rmSync(join(jobDir, "checks", EVAL_INPUT_NAME), { recursive: true, force: true });
3469
+ if (existsSync(scratch)) log.error(`${tag} the checks' scratch directory could not be removed`);
3470
+ log.line(`${tag} checks done (${formatDuration(now().getTime() - startedMs)})`);
3612
3471
  }
3613
3472
  }
3614
3473
 
3615
- export { ALREADY_RELEASED, AttemptLogBook, EXIT_CODE_FILE, GUEST_USAGE_INTERVAL_MS, MICROVM_COMMAND_DIR, RENEW_INTERVAL_SECONDS, appendCommandLog, commandLogOffset, deliverCommitHooks, describeHookEnd, finishJob, foldSample, hookLimitsFor, inTeardown, isContainerTracked, isGroupAlive, isIdle, isMicroVmTracked, isReadable, isTimedOut, isTracked, isUnfinished, jobEnv, judgeProbe, listAdoptableJobs, listTrackedJobs, listUnreadableJobs, microVmCommandLine, microVmOomKills, needsRenew, outcomeFromExitCode, probeCommand, probeMicroVmCommand, provisionMicroVm, provisionProcess, readCpuUsec, readProvisionOut, recoverLostJobs, releaseLease, renewLease, restartSender, retryFailedReleases, runCommand, runHook, sampleGuest, scrubReason, serveLease, setStage, startMicroVmCommand, startSender, stopOrphanedMicroVms, stripTerminalControls, unfinishedJobs, vmAlive, workdirProblem, writeGuestGhConfig };
3474
+ export { ALREADY_RELEASED, AttemptLogBook, EXIT_CODE_FILE, GUEST_USAGE_INTERVAL_MS, MAX_CHECK_ITEMS_BYTES, MICROVM_COMMAND_DIR, RENEW_INTERVAL_SECONDS, appendCommandLog, checksIn, commandItems, commandLogOffset, deliverCommitHooks, describeHookEnd, finishJob, foldSample, hookLimitsFor, inTeardown, isContainerTracked, isGroupAlive, isIdle, isJudgeCheck, isMicroVmTracked, isReadable, isTimedOut, isTracked, isUnfinished, jobEnv, judgeProbe, listAdoptableJobs, listTrackedJobs, listUnreadableJobs, makeRecorder, microVmCommandLine, microVmOomKills, needsRenew, outcomeFromExitCode, probeCommand, probeMicroVmCommand, provisionMicroVm, provisionProcess, readCpuUsec, readDefinitions, readProvisionOut, recoverLostJobs, releaseLease, renewLease, restartSender, retryFailedReleases, runCommand, runHook, runHostChecks, sampleGuest, scrubReason, serveLease, setStage, startMicroVmCommand, startSender, stopOrphanedMicroVms, stripTerminalControls, timeoutAfter, unfinishedJobs, vmAlive, workdirProblem, writeGuestGhConfig };