@outerlayer/cli 0.34.0-next.20261026145838 → 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.
- package/README.md +6 -0
- package/dist/{agent-setup-FNDKCPUP.js → agent-setup-WOEZELVB.js} +6 -6
- package/dist/{bridge-YJBB4YA3.js → bridge-F5FBQSPZ.js} +2 -2
- package/dist/{build-ZJK6CPNH.js → build-UDOBVX5J.js} +7 -7
- package/dist/{build-disk-7HS4UF7O.js → build-disk-C3WIAHCL.js} +6 -6
- package/dist/build-info.json +1 -1
- package/dist/{builder-MHZKDI4H.js → builder-YNXWVH2V.js} +5 -5
- package/dist/{check-4W4QUBMP.js → check-G2K76NEM.js} +39 -38
- package/dist/{chunk-2ILU7UAU.js → chunk-3VZTNXK5.js} +1 -1
- package/dist/{chunk-4YFMNF5Y.js → chunk-4J2FF5HZ.js} +3 -3
- package/dist/{chunk-CAQCDYKF.js → chunk-5JGPTYLT.js} +2 -2
- package/dist/chunk-6D7H5DMZ.js +152 -0
- package/dist/{chunk-OGPEHSNW.js → chunk-6KFJIU73.js} +1 -1
- package/dist/{chunk-3EJBI5JW.js → chunk-7FQYTM7M.js} +1 -1
- package/dist/{chunk-3N77LUQI.js → chunk-7QAJQDHS.js} +1 -1
- package/dist/{chunk-SI6WDF3U.js → chunk-CFFVCI2I.js} +1 -1
- package/dist/{chunk-7P3H4RY2.js → chunk-CNZRUVZ5.js} +102 -36
- package/dist/{chunk-WMNPKBX5.js → chunk-ELO5XEES.js} +4 -4
- package/dist/{chunk-PP6OX4OT.js → chunk-EPFFYDNX.js} +5 -5
- package/dist/{chunk-CY5TCVBS.js → chunk-ERTIEYDG.js} +6 -6
- package/dist/{chunk-35JMYSUD.js → chunk-EUQZAH2W.js} +2 -2
- package/dist/{chunk-WHDS6MEG.js → chunk-GHFI5GBF.js} +3 -3
- package/dist/{chunk-PXGU3GKY.js → chunk-GJ4KBHBD.js} +1 -1
- package/dist/chunk-GYVMMU3U.js +321 -0
- package/dist/{chunk-VVOSUGEF.js → chunk-H47VGSCW.js} +4 -4
- package/dist/{chunk-TFPNPS4Q.js → chunk-HOUFCUMH.js} +1 -1
- package/dist/{chunk-7JE2WFNK.js → chunk-HVPW272H.js} +3 -3
- package/dist/{chunk-M3ME6TLQ.js → chunk-HVQMIZ22.js} +4 -4
- package/dist/{chunk-7DXVL7TN.js → chunk-IO2BBKG4.js} +3 -3
- package/dist/{chunk-UONH72CJ.js → chunk-IWVGALDC.js} +2 -2
- package/dist/{chunk-ZP7GKJSM.js → chunk-JC6AQ6PC.js} +39 -6
- package/dist/{chunk-6JZXIWX7.js → chunk-JKNOJKVD.js} +34 -9
- package/dist/{chunk-GEQT3RPN.js → chunk-JSZRBQY3.js} +3 -3
- package/dist/{chunk-MOI2KDLH.js → chunk-L5VHGRQE.js} +1 -1
- package/dist/{chunk-SKVA4KWD.js → chunk-LD3BHLK7.js} +3 -3
- package/dist/{chunk-YNMVVQOQ.js → chunk-LQHCNXPK.js} +1 -1
- package/dist/{chunk-PWSUNS6Y.js → chunk-LTN2YFLR.js} +99 -31
- package/dist/{chunk-YHKU2NOZ.js → chunk-LTYBHCL4.js} +1 -1
- package/dist/{chunk-PAK5P2ND.js → chunk-MH6QLXUO.js} +1 -1
- package/dist/{chunk-OS7DJOQJ.js → chunk-MN3PP45X.js} +1 -1
- package/dist/{chunk-25GRRBTH.js → chunk-NHEU5CON.js} +1 -1
- package/dist/{chunk-IM6IL3F4.js → chunk-OQT7RT5I.js} +3 -3
- package/dist/{chunk-KRTD2F3I.js → chunk-P3CHDKXD.js} +3 -3
- package/dist/{chunk-63IVRRIO.js → chunk-P3NPTFLF.js} +3 -3
- package/dist/{chunk-GC5PVMIJ.js → chunk-RR3VDM7M.js} +3 -3
- package/dist/{chunk-G4W4B33M.js → chunk-SDAEXUIX.js} +25 -10
- package/dist/{chunk-SYVRBBLW.js → chunk-SDELQEG4.js} +1 -1
- package/dist/{chunk-AE3OTPUU.js → chunk-SZU4YCBH.js} +964 -1056
- package/dist/{chunk-FDJPDZSR.js → chunk-TS3EFB7S.js} +1 -1
- package/dist/{chunk-EMH2LNIE.js → chunk-V2VJYK46.js} +4 -4
- package/dist/{chunk-KAVZAB3E.js → chunk-VEJWLQ4G.js} +10 -10
- package/dist/{chunk-XTYYF6P7.js → chunk-VUQKGBIQ.js} +14 -7
- package/dist/{chunk-3RLKILPT.js → chunk-WE4C3ODP.js} +2 -2
- package/dist/{chunk-QEIA5Q35.js → chunk-XL2MPORM.js} +4 -4
- package/dist/{chunk-VGRCZE6U.js → chunk-XSFR76J7.js} +3 -3
- package/dist/{chunk-UMYYYF7B.js → chunk-YL3RWW7X.js} +1 -1
- package/dist/{chunk-GSSVJNP2.js → chunk-Z7WMU3RA.js} +3 -2
- package/dist/{chunk-3EHDZTRT.js → chunk-ZIKZZ4LK.js} +3 -3
- package/dist/{claude-launcher-OQZNW7XJ.js → claude-launcher-6KGANL7E.js} +1 -1
- package/dist/{cli-PFWJD5QM.js → cli-5KY3TE3S.js} +114 -112
- package/dist/{command-CM22HPN5.js → command-VQPQDPCZ.js} +24 -24
- package/dist/{config-W2XWNR4C.js → config-WSZIWPP4.js} +7 -7
- package/dist/{connect-cmd-TOWE4P45.js → connect-cmd-LI34HS5Q.js} +6 -6
- package/dist/{container-start-D2OEPMUO.js → container-start-XRR4VC5Z.js} +31 -30
- package/dist/{context-show-cmd-5UJUWBJL.js → context-show-cmd-X27GMOT6.js} +7 -7
- package/dist/{control-plane-launch-XBGHM7DB.js → control-plane-launch-74EIIB5Z.js} +5 -5
- package/dist/dist-HVXLUMRV.js +7 -0
- package/dist/{docker-GA63DVME.js → docker-KZ3OQTPW.js} +5 -5
- package/dist/{doctor-GVWQBHBC.js → doctor-KPKLFIJH.js} +24 -24
- package/dist/{emit-artifact-cmd-TUOLSGHB.js → emit-artifact-cmd-BJICHW2E.js} +11 -11
- package/dist/{emit-criteria-cmd-VEUMA7MZ.js → emit-criteria-cmd-MUWPT25P.js} +8 -8
- package/dist/{emit-demo-cmd-YLEZB6FG.js → emit-demo-cmd-YQN7VKQH.js} +12 -12
- package/dist/{emit-finding-cmd-BX3FWGSX.js → emit-finding-cmd-NPBWVY44.js} +8 -8
- package/dist/{emit-result-cmd-AYXGCIPR.js → emit-result-cmd-7CJZZQGK.js} +10 -10
- package/dist/{emit-step-cmd-CU6GQ3WW.js → emit-step-cmd-ZPLHZAMA.js} +8 -8
- package/dist/{exec-client-QB4OZRGM.js → exec-client-LXHTMVIB.js} +3 -3
- package/dist/find-unit-UKH6JH4S.js +18 -0
- package/dist/{findings-cmd-G4V4M3PO.js → findings-cmd-DZ4ESTHX.js} +8 -8
- package/dist/{fleet-readiness-NJYTQYTA.js → fleet-readiness-YSI4NOHT.js} +5 -5
- package/dist/{governing-source-JQV3ZKD7.js → governing-source-JPRMOB3O.js} +10 -10
- package/dist/{guest-init-IF5LXGUL.js → guest-init-WWL2WL7R.js} +4 -4
- package/dist/{hook-fast-GPUL7T77.js → hook-fast-6EW2MGA2.js} +1 -1
- package/dist/host-checks-NDXNYOMA.js +75 -0
- package/dist/{pending-ERXWVLLG.js → host-key-TSOKVSZX.js} +3 -3
- package/dist/{image-tag-SEMBVB5D.js → image-tag-BTXKIH73.js} +3 -3
- package/dist/index.js +4 -4
- package/dist/{init-FY3HP73H.js → init-MN3MNG7V.js} +21 -21
- package/dist/{init-3NT4YX2X.js → init-V6Y2VNKW.js} +21 -21
- package/dist/{init-cmd-RXHHEAOT.js → init-cmd-QCWLJDIF.js} +11 -11
- package/dist/{init-update-RHS6HWLI.js → init-update-GTFLPDEL.js} +9 -9
- package/dist/{install-cmd-5H6M2KJ7.js → install-cmd-RGO7NWKP.js} +5 -5
- package/dist/{logs-6CMB3DO6.js → logs-LEVH6VAI.js} +2 -2
- package/dist/{loop-V3GED2WX.js → loop-NGFNQF3Y.js} +38 -37
- package/dist/{machine-HLZLH327.js → machine-XG54XYYZ.js} +21 -21
- package/dist/{mcp-serve-cmd-MWGIN6YL.js → mcp-serve-cmd-4O5INM77.js} +4 -4
- package/dist/{paths-T2DEBI62.js → paths-AQ47ZRHK.js} +2 -2
- package/dist/{host-key-5RU322O3.js → pending-UWFDVFOA.js} +3 -3
- package/dist/{pidfile-EAD2OEM7.js → pidfile-PCG2H5T4.js} +3 -3
- package/dist/{record-IMXQNHPB.js → record-SVCNFCGQ.js} +3 -3
- package/dist/{runner-env-V52JUOD5.js → runner-env-YPXWTNP3.js} +9 -9
- package/dist/{runner-identity-J4NCBSBU.js → runner-identity-XPZO3TG4.js} +5 -5
- package/dist/{runner-key-ZUHMQCHZ.js → runner-key-B5EJH25Z.js} +21 -21
- package/dist/{session-sender-XDKMK4SF.js → session-sender-XED7CKHP.js} +11 -11
- package/dist/{spec-cmd-XQV7EIJW.js → spec-cmd-TRXAGQYM.js} +9 -9
- package/dist/{status-ZJJEJ52D.js → status-WQ53MWC3.js} +29 -28
- package/dist/{sync-cmd-BG2M536W.js → sync-cmd-UQVENF25.js} +10 -10
- package/dist/{update-steps-I5JLERXS.js → update-steps-IQVAL64Y.js} +30 -29
- package/dist/{watch-7A63565I.js → watch-EZGDDRTQ.js} +10 -10
- package/dist/{work-claim-cmd-OVPMBIRR.js → work-claim-cmd-YSAJ7DAK.js} +14 -14
- package/dist/{work-cmd-KVFRK24I.js → work-cmd-V5FHLWJM.js} +8 -8
- package/dist/{work-comment-cmd-GC3WAFM7.js → work-comment-cmd-KLEMDYI4.js} +9 -9
- package/dist/{work-demo-cmd-IOB5UDGQ.js → work-demo-cmd-XYRKFP4G.js} +8 -8
- package/dist/{work-logs-cmd-7II6D2JJ.js → work-logs-cmd-DOGPK7Q6.js} +8 -8
- package/dist/{work-open-pr-cmd-FYKRKCDL.js → work-open-pr-cmd-UAUEEP3V.js} +9 -9
- package/dist/{work-pr-cmd-KP3RI7TV.js → work-pr-cmd-LYWL7ZLF.js} +8 -8
- package/dist/{work-proofs-cmd-TVU6QFZN.js → work-proofs-cmd-YMMDAA6W.js} +11 -9
- package/dist/{work-proofs-record-XANZE3K4.js → work-proofs-record-NUCWR7ZJ.js} +17 -15
- package/dist/{work-stop-cmd-5V3DV3HJ.js → work-stop-cmd-MRLJEQRP.js} +8 -8
- package/package.json +1 -1
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +2 -1
- package/skill-pack/maintained/outerlayer-build/SKILL.md +3 -1
- package/skill-pack/maintained/outerlayer-build/references/evidence.md +9 -3
- package/dist/chunk-YZ375YBI.js +0 -151
- package/dist/dist-IOPJIQWK.js +0 -7
- package/dist/find-unit-GGBRYOSO.js +0 -18
|
@@ -1,45 +1,65 @@
|
|
|
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 {
|
|
4
|
+
import { itemsSentence, criteriaOf, hostItemsOf, loadCitationFinder, MAX_TEST_FILE_BYTES, MAX_TEST_FILES, criterionInputs, planCriteria, parseJudgeAnswer, inCriteriaOrder, oneLineReason, judgedItems } from './chunk-6D7H5DMZ.js';
|
|
5
|
+
import { DOCKERD_LOG, HELPER_PATH, HELPER_RECORD } from './chunk-P3NPTFLF.js';
|
|
5
6
|
import { zodProblem } from './chunk-I3LTIAOR.js';
|
|
6
|
-
import { senderPidfile } from './chunk-
|
|
7
|
-
import { installScrubContext } from './chunk-
|
|
8
|
-
import { writeDockerClientConfig } from './chunk-
|
|
7
|
+
import { senderPidfile } from './chunk-LD3BHLK7.js';
|
|
8
|
+
import { installScrubContext } from './chunk-VUQKGBIQ.js';
|
|
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,
|
|
11
|
-
import {
|
|
12
|
-
import { waitForAgent, vmExecArgs } from './chunk-
|
|
13
|
-
import { socketPathProblem, vsockLinks, LEASE_LINK, linkVsockSockets } from './chunk-
|
|
14
|
-
import { describeCallFailure, resolveGoverningSource, prefetchGovernedContext, refusalCode, removeStagedContext, stageGovernedContext, pinnedSourceOf, commandWithShim, logStamp, createRunnerLog, removeKeptContext, formatDuration, retrySend, formatClock,
|
|
15
|
-
import { imageBuildOf, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
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';
|
|
13
|
+
import { waitForAgent, vmExecArgs } from './chunk-3VZTNXK5.js';
|
|
14
|
+
import { socketPathProblem, vsockLinks, LEASE_LINK, linkVsockSockets } from './chunk-6KFJIU73.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';
|
|
16
|
+
import { imageBuildOf, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-ZIKZZ4LK.js';
|
|
17
|
+
import { formatBytes, DEFAULT_CACHE_DISK, allocatedBytes } from './chunk-IWVGALDC.js';
|
|
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';
|
|
18
19
|
import { CLAUDE_CODE_VERSION, FIRECRACKER_VERSION } from './chunk-4UPET3R7.js';
|
|
19
20
|
import { isSameProcess, startTimeOf } from './chunk-JBJTC3UU.js';
|
|
20
|
-
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-
|
|
21
|
+
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-JKNOJKVD.js';
|
|
21
22
|
import { planArtifactUpload } from './chunk-MECW77XP.js';
|
|
22
23
|
import { readCloudConfig } from './chunk-PJDQPF5N.js';
|
|
23
|
-
import { signerFor } from './chunk-
|
|
24
|
-
import {
|
|
25
|
-
import { scrubText } from './chunk-
|
|
24
|
+
import { signerFor } from './chunk-L5VHGRQE.js';
|
|
25
|
+
import { AGENTS_SKILLS_BASE, OUTERLAYER_SKILLS_BASE, resolvePackRoot } from './chunk-ZQSANKS4.js';
|
|
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
|
|
30
|
+
import { CheckItemsSchema } from './chunk-EOTMDIYC.js';
|
|
30
31
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
31
|
-
import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath, jobConsolePath } from './chunk-
|
|
32
|
-
import { JOB_MOUNT, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, claudeCredential, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord,
|
|
32
|
+
import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath, jobConsolePath } from './chunk-GJ4KBHBD.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 {
|
|
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";
|
|
@@ -193,6 +195,37 @@ var GUEST_MAVEN_DIR = `${JOB_MOUNT}/maven`;
|
|
|
193
195
|
function buildMemoryBytes(config) {
|
|
194
196
|
return parseSize(config.build?.memory ?? DEFAULT_MEMORY) ?? parseSize(DEFAULT_MEMORY);
|
|
195
197
|
}
|
|
198
|
+
function machineLimits(config, jobDir) {
|
|
199
|
+
const settings = managedBuildSettingsFor(jobDir);
|
|
200
|
+
if (settings !== void 0) return managedMachineLimits(settings);
|
|
201
|
+
return {
|
|
202
|
+
cpus: config.build?.cpus ?? DEFAULT_CPUS,
|
|
203
|
+
memoryBytes: buildMemoryBytes(config),
|
|
204
|
+
diskBytes: parseSize(config.build?.disk ?? DEFAULT_DISK) ?? parseSize(DEFAULT_DISK)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function recordedLimits(limits) {
|
|
208
|
+
return { memoryBytes: limits.memoryBytes, diskBytes: limits.diskBytes };
|
|
209
|
+
}
|
|
210
|
+
function memoryTextOf(config, jobDir, limits) {
|
|
211
|
+
return managedBuildSettingsFor(jobDir) === void 0 ? config.build?.memory : `${limits.memoryBytes / 1024 ** 3}g`;
|
|
212
|
+
}
|
|
213
|
+
function builderMachineFor(config, jobDir, assets, limits) {
|
|
214
|
+
return {
|
|
215
|
+
kernel: assets.kernel,
|
|
216
|
+
firecracker: assets.firecracker,
|
|
217
|
+
limits: { ...limits, cacheDiskBytes: parseSize(config.build?.cacheDisk ?? DEFAULT_CACHE_DISK) ?? parseSize(DEFAULT_CACHE_DISK) },
|
|
218
|
+
memory: memoryTextOf(config, jobDir, limits),
|
|
219
|
+
allowHosts: withFactoryHosts(config.build?.allowHosts, managedBuildSettingsFor(jobDir)),
|
|
220
|
+
managed: config.managed === true,
|
|
221
|
+
timeLimitMinutes: config.timeLimitMinutes
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function factoryVariablesOf(config, jobDir) {
|
|
225
|
+
const held = managedBuildSettingsFor(jobDir)?.variables;
|
|
226
|
+
if (held !== void 0) return held;
|
|
227
|
+
return config.managed === true ? [] : void 0;
|
|
228
|
+
}
|
|
196
229
|
function firstLine(text2) {
|
|
197
230
|
return text2.trim().split("\n")[0] ?? "";
|
|
198
231
|
}
|
|
@@ -327,11 +360,8 @@ async function provisionMicroVm(input) {
|
|
|
327
360
|
});
|
|
328
361
|
if (unfetched !== void 0) return fail(unfetched);
|
|
329
362
|
}
|
|
330
|
-
const limits =
|
|
331
|
-
|
|
332
|
-
memoryBytes: buildMemoryBytes(config),
|
|
333
|
-
diskBytes: parseSize(config.build?.disk ?? DEFAULT_DISK) ?? parseSize(DEFAULT_DISK)
|
|
334
|
-
};
|
|
363
|
+
const limits = machineLimits(config, jobDir);
|
|
364
|
+
const memoryText = memoryTextOf(config, jobDir, limits);
|
|
335
365
|
let buildsInBuilder = imageBuildOf(config) === "vm";
|
|
336
366
|
if (buildsInBuilder && !vm.builderDiskPinned()) {
|
|
337
367
|
if (config.managed === true) return fail(MANAGED_NO_BUILDER_DISK_REASON);
|
|
@@ -351,17 +381,7 @@ async function provisionMicroVm(input) {
|
|
|
351
381
|
deadlineMs: input.deadlineMs,
|
|
352
382
|
now: input.now,
|
|
353
383
|
guestHelper: { path: assets.helper, sha256: assets.helperSha256 },
|
|
354
|
-
...buildsInBuilder ? {
|
|
355
|
-
inBuilder: {
|
|
356
|
-
kernel: assets.kernel,
|
|
357
|
-
firecracker: assets.firecracker,
|
|
358
|
-
limits: { ...limits, cacheDiskBytes: parseSize(config.build?.cacheDisk ?? DEFAULT_CACHE_DISK) ?? parseSize(DEFAULT_CACHE_DISK) },
|
|
359
|
-
memory: config.build?.memory,
|
|
360
|
-
allowHosts: config.build?.allowHosts,
|
|
361
|
-
managed: config.managed === true,
|
|
362
|
-
timeLimitMinutes: config.timeLimitMinutes
|
|
363
|
-
}
|
|
364
|
-
} : {}
|
|
384
|
+
...buildsInBuilder ? { inBuilder: builderMachineFor(config, jobDir, assets, limits) } : {}
|
|
365
385
|
});
|
|
366
386
|
if (!obtained.ok) return obtained.aborted === true ? { ok: false, reason: obtained.reason, aborted: true } : fail(obtained.reason);
|
|
367
387
|
const { report } = obtained.built;
|
|
@@ -375,6 +395,7 @@ async function provisionMicroVm(input) {
|
|
|
375
395
|
imageId: report.imageId,
|
|
376
396
|
containerEnv: report.apply.containerEnv,
|
|
377
397
|
builtIns: true,
|
|
398
|
+
limits: recordedLimits(limits),
|
|
378
399
|
lifecycle: report.apply.lifecycle,
|
|
379
400
|
...governingSource.kind === "governed" ? { governing: { repository: governingSource.repository, ref: governingSource.ref, commit: governingSource.sha } } : {},
|
|
380
401
|
...report.docker === true ? { docker: true } : {}
|
|
@@ -387,7 +408,8 @@ async function provisionMicroVm(input) {
|
|
|
387
408
|
repositories,
|
|
388
409
|
hostEnv: runtime.hostEnv,
|
|
389
410
|
containerEnv: report.apply.containerEnv,
|
|
390
|
-
builtIns: true
|
|
411
|
+
builtIns: true,
|
|
412
|
+
factoryVariables: factoryVariablesOf(config, jobDir)
|
|
391
413
|
};
|
|
392
414
|
const env = buildEnvironment(envInput);
|
|
393
415
|
if (env.skippedTokenVariables.length > 0) {
|
|
@@ -406,7 +428,7 @@ async function provisionMicroVm(input) {
|
|
|
406
428
|
let why = reason;
|
|
407
429
|
if (!aborted && (state.cgroup !== void 0 || state.jailed !== void 0)) {
|
|
408
430
|
try {
|
|
409
|
-
if (await microVmOomKills(runtime, state) > 0) why = outOfMemoryReason(
|
|
431
|
+
if (await microVmOomKills(runtime, state) > 0) why = outOfMemoryReason(memoryText);
|
|
410
432
|
} catch (err) {
|
|
411
433
|
log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
|
|
412
434
|
}
|
|
@@ -425,7 +447,7 @@ async function provisionMicroVm(input) {
|
|
|
425
447
|
imageConfig = { env: madeInBuilder.env, user: madeInBuilder.user };
|
|
426
448
|
} else {
|
|
427
449
|
try {
|
|
428
|
-
state.rootfs = await ensureRootfs({ host: vm.rootfs, runnerDir: runnerDirPath, imageId: report.imageId, memoryBytes:
|
|
450
|
+
state.rootfs = await ensureRootfs({ host: vm.rootfs, runnerDir: runnerDirPath, imageId: report.imageId, memoryBytes: limits.memoryBytes });
|
|
429
451
|
imageConfig = await vm.rootfs.imageConfig(report.imageId);
|
|
430
452
|
} catch (err) {
|
|
431
453
|
return bail(`the image could not be converted to a root disk: ${describeCallFailure(err)}`);
|
|
@@ -678,7 +700,8 @@ function vmExecFor(input, argv) {
|
|
|
678
700
|
containerEnv: state.containerEnv ?? {},
|
|
679
701
|
builtIns: state.builtIns === true,
|
|
680
702
|
identity: input.job.commitIdentity,
|
|
681
|
-
imageEnv: state.imageEnv
|
|
703
|
+
imageEnv: state.imageEnv,
|
|
704
|
+
factoryVariables: factoryVariablesOf(input.config, input.jobDir)
|
|
682
705
|
});
|
|
683
706
|
const options = {
|
|
684
707
|
socket: state.vsockPath,
|
|
@@ -707,7 +730,8 @@ async function startMicroVmCommand(input) {
|
|
|
707
730
|
containerEnv: state.containerEnv ?? {},
|
|
708
731
|
builtIns: state.builtIns === true,
|
|
709
732
|
identity: input.job.commitIdentity,
|
|
710
|
-
imageEnv: state.imageEnv
|
|
733
|
+
imageEnv: state.imageEnv,
|
|
734
|
+
factoryVariables: factoryVariablesOf(input.config, input.jobDir)
|
|
711
735
|
});
|
|
712
736
|
const started = await input.runtime.vm.exec(state.vsockPath, {
|
|
713
737
|
argv: ["/bin/sh", "-c", commandWithShim(input.job, input.cmdline)],
|
|
@@ -749,7 +773,8 @@ async function syncInMicroVm(input) {
|
|
|
749
773
|
containerEnv: state.containerEnv ?? {},
|
|
750
774
|
builtIns: state.builtIns === true,
|
|
751
775
|
identity: job.commitIdentity,
|
|
752
|
-
imageEnv: state.imageEnv
|
|
776
|
+
imageEnv: state.imageEnv,
|
|
777
|
+
factoryVariables: factoryVariablesOf(input.config, input.jobDir)
|
|
753
778
|
});
|
|
754
779
|
const result = await runtime.vm.exec(state.vsockPath, {
|
|
755
780
|
argv: ["outerlayer", "sync"],
|
|
@@ -873,6 +898,7 @@ async function startCheckMicroVm(input) {
|
|
|
873
898
|
const { config, runtime, job, jobDir, tokens, log } = input;
|
|
874
899
|
const { vm } = runtime;
|
|
875
900
|
const tag = `#${job.item}`;
|
|
901
|
+
const limits = machineLimits(config, jobDir);
|
|
876
902
|
const built = job.microvm;
|
|
877
903
|
const repository = built?.repository ?? job.repository;
|
|
878
904
|
if (built === void 0 || built.imageId === void 0 || repository === void 0 || job.branch === void 0) {
|
|
@@ -922,7 +948,7 @@ async function startCheckMicroVm(input) {
|
|
|
922
948
|
state.rootfs = built.rootfs;
|
|
923
949
|
} else {
|
|
924
950
|
try {
|
|
925
|
-
state.rootfs = await ensureRootfs({ host: vm.rootfs, runnerDir: runnerDirPath, imageId: built.imageId, memoryBytes:
|
|
951
|
+
state.rootfs = await ensureRootfs({ host: vm.rootfs, runnerDir: runnerDirPath, imageId: built.imageId, memoryBytes: limits.memoryBytes });
|
|
926
952
|
} catch (err) {
|
|
927
953
|
return fail(`the image could not be converted to a root disk: ${describeCallFailure(err)}`);
|
|
928
954
|
}
|
|
@@ -940,16 +966,11 @@ async function startCheckMicroVm(input) {
|
|
|
940
966
|
log: (line) => log.line(`${tag} ${line}`),
|
|
941
967
|
readOnly: true
|
|
942
968
|
});
|
|
943
|
-
runtime.tunnels.set(buildId, await startServicesFor(runtime, config, buildDir2, checksDir, void 0, false, gitRemote));
|
|
969
|
+
runtime.tunnels.set(buildId, await startServicesFor(runtime, config, buildDir2, checksDir, void 0, false, gitRemote, "socket", void 0, void 0, void 0, jobDir));
|
|
944
970
|
} catch (err) {
|
|
945
971
|
return fail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
946
972
|
}
|
|
947
973
|
if (abandoned()) return fail(ABANDONED);
|
|
948
|
-
const limits = {
|
|
949
|
-
cpus: config.build?.cpus ?? DEFAULT_CPUS,
|
|
950
|
-
memoryBytes: buildMemoryBytes(config),
|
|
951
|
-
diskBytes: parseSize(config.build?.disk ?? DEFAULT_DISK) ?? parseSize(DEFAULT_DISK)
|
|
952
|
-
};
|
|
953
974
|
const links = vsockLinks({ destinations: config.build?.destinations?.length ?? 0, builtIns: false });
|
|
954
975
|
const configPath = join(buildDir2, "vm.json");
|
|
955
976
|
const helper = await helperFor(runtime, (line) => log.line(`${tag} ${line}`), config.managed === true);
|
|
@@ -1009,7 +1030,16 @@ async function startCheckMicroVm(input) {
|
|
|
1009
1030
|
const failed = await boot();
|
|
1010
1031
|
if (failed !== void 0) return fail(failed);
|
|
1011
1032
|
if (abandoned()) return fail(ABANDONED);
|
|
1012
|
-
const env = checkEnvironment({
|
|
1033
|
+
const env = checkEnvironment({
|
|
1034
|
+
config,
|
|
1035
|
+
item: job.item,
|
|
1036
|
+
repository,
|
|
1037
|
+
hostEnv: runtime.hostEnv,
|
|
1038
|
+
containerEnv: built.containerEnv ?? {},
|
|
1039
|
+
imageEnv: built.imageEnv,
|
|
1040
|
+
// The checks of a managed build get its factory's variables, as its command did, and none of the runner's own.
|
|
1041
|
+
factoryVariables: factoryVariablesOf(config, jobDir)
|
|
1042
|
+
});
|
|
1013
1043
|
const evalInput = { dir: join(checksDir, EVAL_INPUT_NAME), delivered: false };
|
|
1014
1044
|
const deliverEvalInput = async () => {
|
|
1015
1045
|
await unpackIntoGuest(runtime, state, evalInput.dir, EVAL_INPUT_DIR, { readOnly: true });
|
|
@@ -1149,82 +1179,6 @@ async function stopOrphanedMicroVms(input) {
|
|
|
1149
1179
|
return stopped;
|
|
1150
1180
|
}
|
|
1151
1181
|
|
|
1152
|
-
// src/runner/microvm/guest-usage.ts
|
|
1153
|
-
var GUEST_USAGE_INTERVAL_MS = 15e3;
|
|
1154
|
-
var MS_PER_TICK = 10;
|
|
1155
|
-
function usageScript(procDir = "/proc") {
|
|
1156
|
-
return [
|
|
1157
|
-
`echo OL-USAGE-MEMINFO; cat ${procDir}/meminfo`,
|
|
1158
|
-
`echo OL-USAGE-STAT; cat ${procDir}/stat`,
|
|
1159
|
-
`if [ -r ${procDir}/pressure/memory ]; then echo OL-USAGE-PSI-MEMORY; cat ${procDir}/pressure/memory; fi`,
|
|
1160
|
-
`if [ -r ${procDir}/pressure/cpu ]; then echo OL-USAGE-PSI-CPU; cat ${procDir}/pressure/cpu; fi`
|
|
1161
|
-
].join("; ");
|
|
1162
|
-
}
|
|
1163
|
-
function section(stdout, name) {
|
|
1164
|
-
const lines = stdout.split("\n");
|
|
1165
|
-
const start = lines.indexOf(`OL-USAGE-${name}`);
|
|
1166
|
-
if (start === -1) return void 0;
|
|
1167
|
-
const end = lines.findIndex((line, index) => index > start && line.startsWith("OL-USAGE-"));
|
|
1168
|
-
return lines.slice(start + 1, end === -1 ? void 0 : end).join("\n");
|
|
1169
|
-
}
|
|
1170
|
-
function meminfoKb(text2, key) {
|
|
1171
|
-
const match = new RegExp(`^${key}:\\s+(\\d+)\\s+kB\\s*$`, "m").exec(text2);
|
|
1172
|
-
return match === null ? void 0 : Number(match[1]);
|
|
1173
|
-
}
|
|
1174
|
-
function pressureTotal(text2) {
|
|
1175
|
-
if (text2 === void 0) return void 0;
|
|
1176
|
-
const match = /^some\s.*\btotal=(\d+)\s*$/m.exec(text2);
|
|
1177
|
-
return match === null ? void 0 : Number(match[1]);
|
|
1178
|
-
}
|
|
1179
|
-
function parseGuestSample(stdout) {
|
|
1180
|
-
const meminfo = section(stdout, "MEMINFO");
|
|
1181
|
-
const stat = section(stdout, "STAT");
|
|
1182
|
-
if (meminfo === void 0 || stat === void 0) return void 0;
|
|
1183
|
-
const totalKb = meminfoKb(meminfo, "MemTotal");
|
|
1184
|
-
const availableKb = meminfoKb(meminfo, "MemAvailable");
|
|
1185
|
-
const aggregate = /^cpu\s+(\d+(?:\s+\d+){7,})\s*$/m.exec(stat);
|
|
1186
|
-
if (totalKb === void 0 || availableKb === void 0 || totalKb <= 0 || availableKb > totalKb || aggregate === null) return void 0;
|
|
1187
|
-
const [user, nice, system, , , irq, softirq, steal] = aggregate[1].split(/\s+/).map(Number);
|
|
1188
|
-
const vcpus = stat.split("\n").filter((line) => /^cpu\d+\s/.test(line)).length;
|
|
1189
|
-
if (vcpus < 1) return void 0;
|
|
1190
|
-
const memoryPressureUs = pressureTotal(section(stdout, "PSI-MEMORY"));
|
|
1191
|
-
const cpuPressureUs = pressureTotal(section(stdout, "PSI-CPU"));
|
|
1192
|
-
return {
|
|
1193
|
-
memoryBytes: totalKb * 1024,
|
|
1194
|
-
vcpus,
|
|
1195
|
-
usedBytes: (totalKb - availableKb) * 1024,
|
|
1196
|
-
cpuBusyMs: (user + nice + system + irq + softirq + steal) * MS_PER_TICK,
|
|
1197
|
-
...memoryPressureUs === void 0 ? {} : { memoryPressureUs },
|
|
1198
|
-
...cpuPressureUs === void 0 ? {} : { cpuPressureUs }
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
function foldSample(usage, sample) {
|
|
1202
|
-
return { peakMemoryBytes: Math.max(usage?.peakMemoryBytes ?? 0, sample.usedBytes), last: sample };
|
|
1203
|
-
}
|
|
1204
|
-
function usageRecord(usage) {
|
|
1205
|
-
if (usage === void 0) return void 0;
|
|
1206
|
-
const { last } = usage;
|
|
1207
|
-
return {
|
|
1208
|
-
guestMemoryBytes: last.memoryBytes,
|
|
1209
|
-
guestVcpus: last.vcpus,
|
|
1210
|
-
peakMemoryBytes: usage.peakMemoryBytes,
|
|
1211
|
-
cpuBusyMs: last.cpuBusyMs,
|
|
1212
|
-
...last.memoryPressureUs === void 0 ? {} : { memoryPressureUs: last.memoryPressureUs },
|
|
1213
|
-
...last.cpuPressureUs === void 0 ? {} : { cpuPressureUs: last.cpuPressureUs }
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
async function sampleGuest(runtime, state) {
|
|
1217
|
-
if (state.vsockPath === void 0) return void 0;
|
|
1218
|
-
try {
|
|
1219
|
-
const result = await withinTime(
|
|
1220
|
-
(signal) => runtime.vm.exec(state.vsockPath, { argv: ["/bin/sh", "-c", usageScript()], user: "root", cwd: "/", env: { PATH: GUEST_PATH } }, { signal })
|
|
1221
|
-
);
|
|
1222
|
-
return result.status === 0 ? parseGuestSample(result.stdout) : void 0;
|
|
1223
|
-
} catch {
|
|
1224
|
-
return void 0;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
1182
|
// src/runner/verdict.ts
|
|
1229
1183
|
var VERDICT_SETTLE_MS = 5 * 6e4;
|
|
1230
1184
|
var VERDICT_READ_EVERY_MS = 3e4;
|
|
@@ -1310,722 +1264,397 @@ async function settleVerdict(deps) {
|
|
|
1310
1264
|
await deps.sleep(everyMs);
|
|
1311
1265
|
}
|
|
1312
1266
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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("; ");
|
|
1318
1278
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
this.dirs = /* @__PURE__ */ new Map();
|
|
1326
|
-
this.lastByKey = /* @__PURE__ */ new Map();
|
|
1327
|
-
}
|
|
1328
|
-
/** Starts keeping lines for `item` in `jobDirPath`. Idempotent. */
|
|
1329
|
-
open(item, jobDirPath) {
|
|
1330
|
-
this.dirs.set(Number(item), jobDirPath);
|
|
1331
|
-
}
|
|
1332
|
-
/** Stops keeping lines for `item`; its file stays on disk until housekeeping removes the job directory. */
|
|
1333
|
-
close(item) {
|
|
1334
|
-
const number = Number(item);
|
|
1335
|
-
this.dirs.delete(number);
|
|
1336
|
-
for (const key of [...this.lastByKey.keys()]) {
|
|
1337
|
-
if (key.startsWith(`${number}:`)) this.lastByKey.delete(key);
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
/** The log with every line it writes for an open item also kept in that item's file. */
|
|
1341
|
-
wrap(log) {
|
|
1342
|
-
return {
|
|
1343
|
-
line: (text2) => {
|
|
1344
|
-
log.line(text2);
|
|
1345
|
-
this.keepItemLine(text2);
|
|
1346
|
-
},
|
|
1347
|
-
error: (text2) => {
|
|
1348
|
-
log.error(text2);
|
|
1349
|
-
this.keepItemLine(text2);
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
|
-
/** One line for `item` that does not itself start with its number. */
|
|
1354
|
-
note(item, text2) {
|
|
1355
|
-
this.append(Number(item), text2);
|
|
1356
|
-
}
|
|
1357
|
-
/** One line, written only when `text` differs from the last one written under `key` for the item. */
|
|
1358
|
-
noteChanged(item, key, text2) {
|
|
1359
|
-
const number = Number(item);
|
|
1360
|
-
const slot = `${number}:${key}`;
|
|
1361
|
-
if (this.lastByKey.get(slot) === text2) return;
|
|
1362
|
-
this.lastByKey.set(slot, text2);
|
|
1363
|
-
this.append(number, text2);
|
|
1364
|
-
}
|
|
1365
|
-
keepItemLine(text2) {
|
|
1366
|
-
const match = ITEM_LINE.exec(text2);
|
|
1367
|
-
if (match !== null) this.append(Number(match[1]), text2);
|
|
1368
|
-
}
|
|
1369
|
-
append(item, text2) {
|
|
1370
|
-
const dir = this.dirs.get(item);
|
|
1371
|
-
if (dir === void 0) return;
|
|
1372
|
-
try {
|
|
1373
|
-
appendFileSync(join(dir, RUNNER_LOG_FILE), `${logStamp(this.now())} ${text2.replace(/[\r\n]+/g, " ")}
|
|
1374
|
-
`, { mode: 384 });
|
|
1375
|
-
} catch {
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
};
|
|
1379
|
-
function readEnd(path, maxBytes) {
|
|
1380
|
-
let fd;
|
|
1381
|
-
try {
|
|
1382
|
-
fd = openSync(path, "r");
|
|
1383
|
-
const size = fstatSync(fd).size;
|
|
1384
|
-
const length = Math.min(size, maxBytes);
|
|
1385
|
-
const buffer = Buffer.alloc(length);
|
|
1386
|
-
readSync(fd, buffer, 0, length, size - length);
|
|
1387
|
-
return buffer.toString("utf8");
|
|
1388
|
-
} catch {
|
|
1389
|
-
return void 0;
|
|
1390
|
-
} finally {
|
|
1391
|
-
if (fd !== void 0) closeSync(fd);
|
|
1392
|
-
}
|
|
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");
|
|
1393
1285
|
}
|
|
1394
|
-
function
|
|
1395
|
-
const
|
|
1396
|
-
|
|
1397
|
-
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]);
|
|
1398
1289
|
}
|
|
1399
|
-
function
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
if (bytes[start - 1] !== 10) {
|
|
1404
|
-
const newline = bytes.indexOf(10, start);
|
|
1405
|
-
if (newline + 1 < bytes.length) {
|
|
1406
|
-
start = newline + 1;
|
|
1407
|
-
} else {
|
|
1408
|
-
while ((bytes[start] & 192) === 128) start++;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
return `[runner log truncated: the first ${start} bytes were cut]
|
|
1412
|
-
${bytes.subarray(start).toString("utf8")}`;
|
|
1413
|
-
}
|
|
1414
|
-
function buildReleaseLog(jobDirPath, endStage, scrub) {
|
|
1415
|
-
const parts = [];
|
|
1416
|
-
const own = readEnd(join(jobDirPath, RUNNER_LOG_FILE), READ_LIMIT_BYTES);
|
|
1417
|
-
if (own !== void 0 && own.trim() !== "") parts.push(own.endsWith("\n") ? own.slice(0, -1) : own);
|
|
1418
|
-
if (isTailedStage(endStage)) {
|
|
1419
|
-
const file = `${endStage}.log`;
|
|
1420
|
-
const stage = readEnd(join(jobDirPath, file), READ_LIMIT_BYTES);
|
|
1421
|
-
const tail = stage === void 0 ? [] : lastLines(stage);
|
|
1422
|
-
if (tail.length > 0) parts.push(`--- the last ${tail.length} lines of ${file} ---`, ...tail);
|
|
1423
|
-
}
|
|
1424
|
-
if (endStage !== "provision") {
|
|
1425
|
-
const provision = readEnd(join(jobDirPath, "provision.log"), READ_LIMIT_BYTES);
|
|
1426
|
-
const problems = provision === void 0 ? [] : provision.split("\n").filter((line) => PROVISION_PROBLEM.test(line)).slice(-50);
|
|
1427
|
-
if (problems.length > 0) parts.push(`--- provision problems (${problems.length} lines of provision.log) ---`, ...problems);
|
|
1428
|
-
}
|
|
1429
|
-
if (parts.length === 0) return void 0;
|
|
1430
|
-
return capRunnerLog(scrub(parts.join("\n")) + "\n");
|
|
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]);
|
|
1431
1294
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1451
|
-
|
|
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 }
|
|
1452
1315
|
};
|
|
1453
|
-
if (input.aborted()) return bail(ENDED);
|
|
1454
|
-
mkdirSync(buildDir2, { recursive: true });
|
|
1455
|
-
chmodSync(buildDir2, 493);
|
|
1456
|
-
try {
|
|
1457
|
-
await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true, routeLog: (line) => input.log.line(`#${item.number} ${line}`) });
|
|
1458
|
-
} catch (err) {
|
|
1459
|
-
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
1460
|
-
}
|
|
1461
|
-
input.save(state, environment);
|
|
1462
|
-
if (input.aborted()) return bail(ENDED);
|
|
1463
|
-
const checkout = join(workDir, "checkout");
|
|
1464
|
-
const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
|
|
1465
|
-
const cloned = await runtime.clone({ remote, dest: checkout });
|
|
1466
|
-
if (!cloned.ok) return bail(cloned.reason);
|
|
1467
|
-
if (input.aborted()) return bail(ENDED);
|
|
1468
|
-
return { ok: true, workdir: checkout, state, environment };
|
|
1469
1316
|
}
|
|
1470
|
-
function
|
|
1471
|
-
return
|
|
1317
|
+
function foldSample(usage, sample) {
|
|
1318
|
+
return { peakMemoryBytes: Math.max(usage?.peakMemoryBytes ?? 0, sample.usedBytes), last: sample };
|
|
1472
1319
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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 }
|
|
1482
1330
|
};
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
part(input.criterion.text);
|
|
1487
|
-
part(input.criterion.proof ?? "");
|
|
1488
|
-
for (const path of paths) {
|
|
1489
|
-
part(path);
|
|
1490
|
-
part(input.testFiles.get(path) ?? "");
|
|
1491
|
-
}
|
|
1492
|
-
return hash.digest("hex");
|
|
1493
|
-
}
|
|
1494
|
-
var HTTPS_LINK = /^https:\/\/\S+$/;
|
|
1495
|
-
function refsFor(id, citations) {
|
|
1496
|
-
const refs = (citations[id] ?? []).slice(0, 20).map((citation) => ({
|
|
1497
|
-
path: citation.path,
|
|
1498
|
-
line: citation.line,
|
|
1499
|
-
// Only an https link becomes an href on a page; any other is left off rather than refusing the check.
|
|
1500
|
-
...HTTPS_LINK.test(citation.href ?? "") ? { href: citation.href } : {}
|
|
1501
|
-
}));
|
|
1502
|
-
return refs.length === 0 ? void 0 : refs;
|
|
1503
|
-
}
|
|
1504
|
-
function planCriteria(input) {
|
|
1505
|
-
const previousById = new Map(input.previous.map((item) => [item.id, item]));
|
|
1506
|
-
const settled = [];
|
|
1507
|
-
const toJudge = [];
|
|
1508
|
-
for (const criterion of input.criteria) {
|
|
1509
|
-
const inputs = input.inputs.get(criterion.id);
|
|
1510
|
-
const base = { id: criterion.id, criterion: criterion.id, ...inputs === void 0 ? {} : { inputs } };
|
|
1511
|
-
const refs = refsFor(criterion.id, input.citations);
|
|
1512
|
-
if (criterion.proof !== null) {
|
|
1513
|
-
settled.push({ ...base, result: "skipped", reason: ARTIFACT_PROOF_REASON });
|
|
1514
|
-
continue;
|
|
1515
|
-
}
|
|
1516
|
-
if (refs === void 0) {
|
|
1517
|
-
settled.push({ ...base, result: "fail", reason: NO_CITING_TEST_REASON });
|
|
1518
|
-
continue;
|
|
1519
|
-
}
|
|
1520
|
-
const kept = previousById.get(criterion.id);
|
|
1521
|
-
if (kept !== void 0 && inputs !== void 0 && kept.inputs === inputs && (kept.result === "pass" || kept.result === "fail")) {
|
|
1522
|
-
settled.push({ ...base, result: kept.result, ...kept.reason === void 0 ? {} : { reason: kept.reason }, refs });
|
|
1523
|
-
continue;
|
|
1524
|
-
}
|
|
1525
|
-
toJudge.push(criterion);
|
|
1526
|
-
}
|
|
1527
|
-
return { settled, toJudge };
|
|
1528
|
-
}
|
|
1529
|
-
var MAX_ANSWER_ENTRIES = 500;
|
|
1530
|
-
function readAnswer(raw) {
|
|
1531
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, problem: "expected an object" };
|
|
1532
|
-
const { result, items } = raw;
|
|
1533
|
-
if (result !== void 0 && result !== "pass" && result !== "fail") return { ok: false, problem: 'result: must be "pass" or "fail"' };
|
|
1534
|
-
if (items !== void 0 && !Array.isArray(items)) return { ok: false, problem: "items: must be an array" };
|
|
1535
|
-
const entries = items ?? [];
|
|
1536
|
-
if (entries.length > MAX_ANSWER_ENTRIES) return { ok: false, problem: `items: at most ${MAX_ANSWER_ENTRIES} entries` };
|
|
1537
|
-
const read = [];
|
|
1538
|
-
for (const [index, entry] of entries.entries()) {
|
|
1539
|
-
const { id, result: itemResult, reason } = typeof entry === "object" && entry !== null ? entry : {};
|
|
1540
|
-
if (typeof id !== "string" || id === "" || id.length > 200) return { ok: false, problem: `items.${index}.id: must be text of 1 to 200 characters` };
|
|
1541
|
-
if (itemResult !== "pass" && itemResult !== "fail") return { ok: false, problem: `items.${index}.result: must be "pass" or "fail"` };
|
|
1542
|
-
if (reason !== void 0 && typeof reason !== "string") return { ok: false, problem: `items.${index}.reason: must be text` };
|
|
1543
|
-
read.push({ id, result: itemResult, ...reason === void 0 ? {} : { reason } });
|
|
1544
|
-
}
|
|
1545
|
-
return { ok: true, answer: { ...result === void 0 ? {} : { result }, items: read } };
|
|
1546
|
-
}
|
|
1547
|
-
function parseJudgeAnswer(text2) {
|
|
1548
|
-
if (text2 === void 0 || text2.trim() === "") return void 0;
|
|
1549
|
-
let raw;
|
|
1331
|
+
}
|
|
1332
|
+
async function sampleGuest(runtime, state) {
|
|
1333
|
+
if (state.vsockPath === void 0) return void 0;
|
|
1550
1334
|
try {
|
|
1551
|
-
|
|
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;
|
|
1552
1339
|
} catch {
|
|
1553
|
-
return
|
|
1340
|
+
return void 0;
|
|
1554
1341
|
}
|
|
1555
|
-
const read = readAnswer(raw);
|
|
1556
|
-
return read.ok ? read : { ok: false, reason: `the judge's items.json does not match its shape: ${read.problem}` };
|
|
1557
|
-
}
|
|
1558
|
-
function oneLineReason(text2) {
|
|
1559
|
-
if (text2 === void 0) return void 0;
|
|
1560
|
-
const line = text2.replace(/\s+/g, " ").trim();
|
|
1561
|
-
if (line === "") return void 0;
|
|
1562
|
-
return line.length <= CHECK_ITEM_REASON_MAX_LENGTH ? line : `${line.slice(0, CHECK_ITEM_REASON_MAX_LENGTH - 1)}\u2026`;
|
|
1563
|
-
}
|
|
1564
|
-
function judgedItems(input) {
|
|
1565
|
-
return input.toJudge.map((criterion) => {
|
|
1566
|
-
const said = input.results.get(criterion.id);
|
|
1567
|
-
const inputs = input.inputs.get(criterion.id);
|
|
1568
|
-
const refs = refsFor(criterion.id, input.citations);
|
|
1569
|
-
const reason = said === void 0 ? NO_VERDICT_REASON : oneLineReason(said.reason);
|
|
1570
|
-
return {
|
|
1571
|
-
id: criterion.id,
|
|
1572
|
-
criterion: criterion.id,
|
|
1573
|
-
result: said?.result ?? "fail",
|
|
1574
|
-
...reason === void 0 ? {} : { reason },
|
|
1575
|
-
...refs === void 0 ? {} : { refs },
|
|
1576
|
-
...inputs === void 0 || said === void 0 ? {} : { inputs }
|
|
1577
|
-
};
|
|
1578
|
-
});
|
|
1579
|
-
}
|
|
1580
|
-
function inCriteriaOrder(items, criteria) {
|
|
1581
|
-
const order = new Map(criteria.map((criterion, index) => [criterion.id, index]));
|
|
1582
|
-
return [...items].sort((a, b) => (order.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.id) ?? Number.MAX_SAFE_INTEGER));
|
|
1583
1342
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
var MAX_CHECK_ITEMS_BYTES = 1024 * 1024;
|
|
1596
|
-
function commandItems(read, scrub) {
|
|
1597
|
-
if (read.status !== 0 || read.stdout.trim() === "") return { ok: true, items: void 0 };
|
|
1598
|
-
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` };
|
|
1599
|
-
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;
|
|
1600
1354
|
try {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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;
|
|
1608
1383
|
}
|
|
1609
|
-
return { ok: true, items: parsed.data.map((item) => item.reason === void 0 ? item : { ...item, reason: scrub(item.reason) }) };
|
|
1610
1384
|
}
|
|
1611
|
-
function
|
|
1612
|
-
|
|
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 };
|
|
1613
1401
|
}
|
|
1614
|
-
function
|
|
1615
|
-
|
|
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
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return startSender(input);
|
|
1616
1426
|
}
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1427
|
+
function isLive(sender) {
|
|
1428
|
+
if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
|
|
1619
1429
|
try {
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const base = realpathSync(root);
|
|
1623
|
-
return inside.startsWith(base + sep) ? readFileSync(inside, "utf8") : void 0;
|
|
1430
|
+
process.kill(sender.pid, 0);
|
|
1431
|
+
return true;
|
|
1624
1432
|
} catch {
|
|
1625
|
-
return
|
|
1433
|
+
return false;
|
|
1626
1434
|
}
|
|
1627
1435
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
const own = readInside(root, brief);
|
|
1631
|
-
if (own !== void 0) return own;
|
|
1632
|
-
const match = MAINTAINED_BRIEF.exec(brief);
|
|
1633
|
-
if (match === null || match[2].split("/").includes("..") || !MAINTAINED_SKILLS.includes(match[1])) return void 0;
|
|
1436
|
+
function signalHost(sender, signal) {
|
|
1437
|
+
if (!isLive(sender)) return;
|
|
1634
1438
|
try {
|
|
1635
|
-
|
|
1439
|
+
process.kill(-sender.pid, signal);
|
|
1636
1440
|
} catch {
|
|
1637
|
-
|
|
1441
|
+
try {
|
|
1442
|
+
process.kill(sender.pid, signal);
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1638
1445
|
}
|
|
1639
1446
|
}
|
|
1640
|
-
function
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
} catch {
|
|
1647
|
-
return [];
|
|
1648
|
-
}
|
|
1649
|
-
const files = [];
|
|
1650
|
-
for (const name of names.filter((entry) => /\.ya?ml$/.test(entry)).sort().slice(0, MAX_VALIDATOR_FILES)) {
|
|
1651
|
-
const path = `${VALIDATORS_DIR}/${name}`;
|
|
1652
|
-
const content = readInside(root, path);
|
|
1653
|
-
if (content !== void 0) files.push({ path, content });
|
|
1654
|
-
}
|
|
1655
|
-
return files;
|
|
1656
|
-
}
|
|
1657
|
-
var CRITERIA_CHECK = "acceptance-criteria";
|
|
1658
|
-
function asHostReads(file, base, log, tag) {
|
|
1659
|
-
const upgraded = upgradedStarterText(file.path, file.content, { base });
|
|
1660
|
-
if (upgraded === void 0) return file;
|
|
1661
|
-
log.line(`${tag} ${file.path} is an unedited copy an earlier release wrote, so it is read as this CLI writes it`);
|
|
1662
|
-
return { path: file.path, content: upgraded };
|
|
1663
|
-
}
|
|
1664
|
-
function checksIn(root, log, tag) {
|
|
1665
|
-
const base = existsSync(join(root, AGENTS_SKILLS_BASE)) ? AGENTS_SKILLS_BASE : OUTERLAYER_SKILLS_BASE;
|
|
1666
|
-
const policyText = readInside(root, POLICY_FILE);
|
|
1667
|
-
const policy = policyText === void 0 ? null : asHostReads({ path: POLICY_FILE, content: policyText }, base, log, tag);
|
|
1668
|
-
const loaded = parsePolicy(policy, validatorFilesIn(root).map((file) => asHostReads(file, base, log, tag)));
|
|
1669
|
-
for (const problem of loaded.problems) log.error(`${tag} ${problem.file}: ${problem.problem}`);
|
|
1670
|
-
const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null);
|
|
1671
|
-
const checks = [];
|
|
1672
|
-
for (const def of hostChecks) {
|
|
1673
|
-
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 } });
|
|
1674
|
-
else if (def.command !== void 0) checks.push({ name: def.declaresEmit, command: def.command });
|
|
1675
|
-
}
|
|
1676
|
-
const unrecorded = loaded.customs.filter((def) => def.level !== "off" && def.declaresEmit === CRITERIA_CHECK && !checks.some((check) => check.name === CRITERIA_CHECK)).map((def) => def.id);
|
|
1677
|
-
return { checks, unrecorded };
|
|
1678
|
-
}
|
|
1679
|
-
async function readDefinitions(input, repository, scratch, token, tokens) {
|
|
1680
|
-
const { config, job, deps } = input;
|
|
1681
|
-
const tag = `#${job.item}`;
|
|
1682
|
-
const stage = deps.stage ?? stageGovernedContext;
|
|
1683
|
-
const controlPlaneUrl = (controlPlane) => deps.runtime.remoteFor?.(controlPlane) ?? `${deps.runtime.github.gitUrl}/${controlPlane}.git`;
|
|
1684
|
-
const governing = (job.container ?? job.microvm)?.governing;
|
|
1685
|
-
const kept = keptContextTree(input.jobDir, governing);
|
|
1686
|
-
if (kept !== void 0) return { ok: true, ...checksIn(kept, deps.log, tag) };
|
|
1687
|
-
const staged = await stage({
|
|
1688
|
-
config,
|
|
1689
|
-
repository,
|
|
1690
|
-
...governing === void 0 ? {} : { source: { repository: governing.repository, ref: governing.ref, sha: governing.commit } },
|
|
1691
|
-
buildId: `checks-${randomBytes(4).toString("hex")}`,
|
|
1692
|
-
buildDir: scratch,
|
|
1693
|
-
fetchImpl: deps.fetchImpl,
|
|
1694
|
-
controlPlaneUrl,
|
|
1695
|
-
controlPlaneToken: tokens === void 0 ? void 0 : () => tokens.controlPlane(),
|
|
1696
|
-
log: deps.log,
|
|
1697
|
-
tag
|
|
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" }
|
|
1698
1453
|
});
|
|
1699
|
-
|
|
1700
|
-
if (staged.kind === "staged") {
|
|
1701
|
-
try {
|
|
1702
|
-
return { ok: true, ...checksIn(join(staged.dir, "tree"), deps.log, tag) };
|
|
1703
|
-
} finally {
|
|
1704
|
-
rmSync(staged.dir, { recursive: true, force: true });
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
const repo = join(scratch, "repo");
|
|
1708
|
-
const remote = deps.runtime.remoteFor?.(repository) ?? `${deps.runtime.github.gitUrl}/${repository}.git`;
|
|
1709
|
-
const started = (job.container ?? job.microvm)?.startCommit;
|
|
1710
|
-
const commit = started !== void 0 && started !== "" ? started : job.environment?.recipe?.sourceCommit ?? await deps.runtime.readHead(remote, scratch);
|
|
1711
|
-
if (commit === void 0) return { ok: false, reason: "the repository's default branch could not be read" };
|
|
1712
|
-
const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: repo, commit, token });
|
|
1713
|
-
if (!fetched.ok) return { ok: false, reason: fetched.reason };
|
|
1714
|
-
deps.log.line(`${tag} check definitions read at ${commit.slice(0, 12)}`);
|
|
1715
|
-
return { ok: true, ...checksIn(repo, deps.log, tag) };
|
|
1454
|
+
return status === 0;
|
|
1716
1455
|
}
|
|
1717
|
-
function
|
|
1718
|
-
|
|
1456
|
+
async function exitReported(pid) {
|
|
1457
|
+
const logged = exitsLogged.get(pid);
|
|
1719
1458
|
let timer;
|
|
1720
|
-
const
|
|
1721
|
-
timer = setTimeout(
|
|
1722
|
-
timer.unref();
|
|
1459
|
+
const bound = new Promise((resolve) => {
|
|
1460
|
+
timer = setTimeout(resolve, EXIT_REPORT_WAIT_MS);
|
|
1461
|
+
timer.unref?.();
|
|
1723
1462
|
});
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
const ran = job.build?.workDir !== void 0 ? "builtin:process" : "a provision hook of the host's own";
|
|
1728
|
-
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);
|
|
1729
1466
|
}
|
|
1730
|
-
function
|
|
1731
|
-
const {
|
|
1467
|
+
async function stopSender(input) {
|
|
1468
|
+
const { job, deps } = input;
|
|
1469
|
+
const sender = job.sender;
|
|
1470
|
+
if (sender === void 0) return;
|
|
1732
1471
|
const tag = `#${job.item}`;
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const sent = await retrySend(
|
|
1741
|
-
() => postJson(
|
|
1742
|
-
config.url,
|
|
1743
|
-
"/v1/emitted-results",
|
|
1744
|
-
config.apiKey,
|
|
1745
|
-
config.appId,
|
|
1746
|
-
{
|
|
1747
|
-
schemaVersion: 1,
|
|
1748
|
-
// A field left undefined is not sent: the request is JSON.
|
|
1749
|
-
emit: { clientEmitId, name, result, body, link: opts.link, items: opts.items, emittedAt: now().toISOString(), item: { number: Number(job.item) } }
|
|
1750
|
-
},
|
|
1751
|
-
deps.fetchImpl,
|
|
1752
|
-
void 0,
|
|
1753
|
-
signer
|
|
1754
|
-
),
|
|
1755
|
-
{ now: deps.now }
|
|
1756
|
-
);
|
|
1757
|
-
if (sent.ok) deps.log.line(`${tag} check ${name} ${result}${body === void 0 ? "" : `: ${body}`}`);
|
|
1758
|
-
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;
|
|
1759
1479
|
};
|
|
1760
|
-
}
|
|
1761
|
-
async function runHostChecks(input) {
|
|
1762
|
-
const { config, job, jobDir, deps } = input;
|
|
1763
|
-
const { runtime, log } = deps;
|
|
1764
|
-
const tag = `#${job.item}`;
|
|
1765
|
-
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
1766
|
-
const repository = job.repository ?? job.container?.repository;
|
|
1767
|
-
if (repository === void 0) return;
|
|
1768
|
-
const jobFilePath = join(jobDir, "job.json");
|
|
1769
|
-
const tokens = runtime.credentials.get(jobDir);
|
|
1770
|
-
let readToken;
|
|
1771
1480
|
try {
|
|
1772
|
-
|
|
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);
|
|
1773
1495
|
} catch (err) {
|
|
1774
|
-
log.error(`${tag}
|
|
1496
|
+
deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1775
1497
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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();
|
|
1786
1513
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
return;
|
|
1514
|
+
/** Starts keeping lines for `item` in `jobDirPath`. Idempotent. */
|
|
1515
|
+
open(item, jobDirPath) {
|
|
1516
|
+
this.dirs.set(Number(item), jobDirPath);
|
|
1791
1517
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
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
|
+
}
|
|
1796
1525
|
}
|
|
1797
|
-
log
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
if (tokens === void 0) return await failAll("the build's repository tokens are no longer held, so the branch cannot be fetched");
|
|
1808
|
-
if (job.branch === void 0) return await failAll("the build pushed no branch: the claim names none");
|
|
1809
|
-
const claimed = repositoriesOf({ repositories: job.repositories, repository, branch: job.branch });
|
|
1810
|
-
if (claimed.length === 0) return await failAll("the claim names no repository to check out");
|
|
1811
|
-
const remoteOf = (name) => runtime.remoteFor?.(name) ?? `${runtime.github.gitUrl}/${name}.git`;
|
|
1812
|
-
const remote = remoteOf(repository);
|
|
1813
|
-
const pushed = /* @__PURE__ */ new Map();
|
|
1814
|
-
const unpushed = [];
|
|
1815
|
-
let primaryHead;
|
|
1816
|
-
for (const entry of claimed) {
|
|
1817
|
-
let entryToken = readToken;
|
|
1818
|
-
if (!entry.primary) {
|
|
1819
|
-
try {
|
|
1820
|
-
entryToken = await tokens.token("read", entry.repository);
|
|
1821
|
-
} catch (err) {
|
|
1822
|
-
return await failAll(`no read token for ${entry.repository}: ${describeCallFailure(err)}`);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
const read = await (deps.readBranch ?? readBranchHead)({ remote: remoteOf(entry.repository), dir: scratch, branch: entry.branch, token: entryToken });
|
|
1826
|
-
if (read.kind === "unreadable") return await failAll(`the branch ${entry.branch} could not be read${entry.primary ? "" : ` on ${entry.repository}`}: ${read.reason}`);
|
|
1827
|
-
if (read.kind === "absent") {
|
|
1828
|
-
unpushed.push(entry);
|
|
1829
|
-
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);
|
|
1830
1536
|
}
|
|
1831
|
-
pushed.set(entry.repository, entry.branch);
|
|
1832
|
-
if (entry.primary) primaryHead = read.sha;
|
|
1833
|
-
}
|
|
1834
|
-
if (pushed.size === 0) {
|
|
1835
|
-
return await failAll(`the build pushed no branch: ${unpushed.map((entry) => `${entry.branch} does not exist on ${entry.repository}`).join("; ")}`);
|
|
1836
|
-
}
|
|
1837
|
-
const repositoriesToCheckOut = claimed;
|
|
1838
|
-
const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
|
|
1839
|
-
const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
|
|
1840
|
-
const environmentLog = join(jobDir, "checks", "environment.log");
|
|
1841
|
-
let transcripts = { ok: false, reason: "the runner holds no way to read the build's sessions" };
|
|
1842
|
-
if (deps.collectSessions !== void 0) {
|
|
1843
|
-
try {
|
|
1844
|
-
transcripts = await deps.collectSessions();
|
|
1845
|
-
} catch (err) {
|
|
1846
|
-
transcripts = { ok: false, reason: describeCallFailure(err) };
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
if (job.microvm !== void 0) {
|
|
1850
|
-
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");
|
|
1851
|
-
try {
|
|
1852
|
-
await deps.endBuildMicroVm();
|
|
1853
|
-
} catch (err) {
|
|
1854
|
-
return await failAll(`the build's microVM could not be stopped, so no check environment was made: ${describeCallFailure(err)}`);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
let abandoned = false;
|
|
1858
|
-
const setup = async () => {
|
|
1859
|
-
const started = job.microvm !== void 0 ? await startCheckMicroVm({
|
|
1860
|
-
config,
|
|
1861
|
-
runtime,
|
|
1862
|
-
job,
|
|
1863
|
-
jobDir,
|
|
1864
|
-
tokens,
|
|
1865
|
-
log,
|
|
1866
|
-
aborted: () => abandoned,
|
|
1867
|
-
save: (state) => {
|
|
1868
|
-
if (abandoned) return;
|
|
1869
|
-
job.checkMicrovm = state;
|
|
1870
|
-
writeJobFile(jobFilePath, job);
|
|
1871
|
-
}
|
|
1872
|
-
}) : await startCheckContainer({
|
|
1873
|
-
config,
|
|
1874
|
-
runtime,
|
|
1875
|
-
job,
|
|
1876
|
-
jobDir,
|
|
1877
|
-
tokens,
|
|
1878
|
-
log,
|
|
1879
|
-
save: (state) => {
|
|
1880
|
-
job.checkContainer = state;
|
|
1881
|
-
writeJobFile(jobFilePath, job);
|
|
1882
|
-
}
|
|
1883
|
-
});
|
|
1884
|
-
if (!started.ok) return `the check environment could not be made: ${started.reason}`;
|
|
1885
|
-
const made = "container" in started ? started.container : started.environment;
|
|
1886
|
-
const steps = [
|
|
1887
|
-
{
|
|
1888
|
-
argv: ["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"],
|
|
1889
|
-
workdir: "/build/work",
|
|
1890
|
-
failed: (status) => `configuring git failed (exit ${status})`
|
|
1891
|
-
},
|
|
1892
|
-
...repositoriesToCheckOut.flatMap((entry) => {
|
|
1893
|
-
const branch = pushed.get(entry.repository);
|
|
1894
|
-
const path = entry.primary ? CHECKOUT_DIR : repositoryPath(entry.repository);
|
|
1895
|
-
const clone = {
|
|
1896
|
-
argv: ["git", "clone", "--filter=blob:none", "--single-branch", ...branch === void 0 ? [] : ["--branch", branch], `https://github.com/${entry.repository}.git`, path],
|
|
1897
|
-
workdir: "/build/work",
|
|
1898
|
-
failed: (status) => `the clone of ${branch ?? `the default branch of ${entry.repository}`} failed (exit ${status})`
|
|
1899
|
-
};
|
|
1900
|
-
return entry.primary && repositoriesToCheckOut.length > 1 ? [
|
|
1901
|
-
clone,
|
|
1902
|
-
{
|
|
1903
|
-
argv: ["sh", "-c", 'mkdir -p "$(dirname "$1")" && ln -s "$2" "$1"', "link", repositoryPath(entry.repository), CHECKOUT_DIR],
|
|
1904
|
-
workdir: "/build/work",
|
|
1905
|
-
failed: (status) => `linking ${entry.repository} at ${repositoryPath(entry.repository)} failed (exit ${status})`
|
|
1906
|
-
}
|
|
1907
|
-
] : [clone];
|
|
1908
|
-
})
|
|
1909
|
-
];
|
|
1910
|
-
for (const step of steps) {
|
|
1911
|
-
if (abandoned) return "the check environment setup ran out of time";
|
|
1912
|
-
const ran = await made.exec(step.argv, step.workdir, { logPath: environmentLog });
|
|
1913
|
-
if (ran.status !== 0) return `the check environment could not be prepared: ${step.failed(ran.status)}`;
|
|
1914
|
-
}
|
|
1915
|
-
const lifecycleDir = lifecycleWorkdir(repositoriesToCheckOut, (job.container ?? job.microvm)?.governing?.repository);
|
|
1916
|
-
for (const step of lifecycleSteps((job.container ?? job.microvm)?.lifecycle ?? {})) {
|
|
1917
|
-
log.line(`${tag} checks lifecycle ${step.name} (${step.source})`);
|
|
1918
|
-
const ran = await made.exec(step.argv, lifecycleDir, { logPath: environmentLog });
|
|
1919
|
-
if (ran.status !== 0) return `the check environment could not be prepared: ${lifecycleFailureReason(step, ran.status)}`;
|
|
1920
|
-
}
|
|
1921
|
-
return made;
|
|
1922
1537
|
};
|
|
1923
|
-
|
|
1924
|
-
|
|
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;
|
|
1925
1558
|
try {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
]);
|
|
1930
|
-
if (outcome === "timeout") {
|
|
1931
|
-
abandoned = true;
|
|
1932
|
-
return await failAll(`the check environment setup ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
|
|
1933
|
-
}
|
|
1934
|
-
settled = outcome;
|
|
1935
|
-
} finally {
|
|
1936
|
-
setupTimer.cancel();
|
|
1559
|
+
appendFileSync(join(dir, RUNNER_LOG_FILE), `${logStamp(this.now())} ${text2.replace(/[\r\n]+/g, " ")}
|
|
1560
|
+
`, { mode: 384 });
|
|
1561
|
+
} catch {
|
|
1937
1562
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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++;
|
|
1956
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) => {
|
|
1957
1632
|
try {
|
|
1958
|
-
await
|
|
1633
|
+
await cleanupProcess(runtime, state);
|
|
1959
1634
|
} catch (err) {
|
|
1960
|
-
|
|
1961
|
-
}
|
|
1962
|
-
let usable = true;
|
|
1963
|
-
for (const check of checks) {
|
|
1964
|
-
if (!usable) {
|
|
1965
|
-
await record(check, "fail", "the check environment could not be restarted after an earlier check ran out of time");
|
|
1966
|
-
continue;
|
|
1967
|
-
}
|
|
1968
|
-
const checkStartedMs = now().getTime();
|
|
1969
|
-
const logPath = join(jobDir, "checks", `${check.name}.log`);
|
|
1970
|
-
await ready.exec(["rm", "-f", CHECK_ITEMS_FILE], CHECKOUT_DIR, { logPath });
|
|
1971
|
-
const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath });
|
|
1972
|
-
running.catch(() => void 0);
|
|
1973
|
-
const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
|
|
1974
|
-
let outcome;
|
|
1975
|
-
try {
|
|
1976
|
-
outcome = await Promise.race([running, timer.done]);
|
|
1977
|
-
} catch (err) {
|
|
1978
|
-
timer.cancel();
|
|
1979
|
-
await record(check, "fail", `the command could not be run: ${describeCallFailure(err)}`);
|
|
1980
|
-
continue;
|
|
1981
|
-
}
|
|
1982
|
-
timer.cancel();
|
|
1983
|
-
if (outcome === "timeout") {
|
|
1984
|
-
await record(check, "fail", `ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
|
|
1985
|
-
const restarting = ready.restart().catch(() => false);
|
|
1986
|
-
const restartTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
|
|
1987
|
-
usable = await Promise.race([restarting, restartTimer.done]) === true;
|
|
1988
|
-
restartTimer.cancel();
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
const read = await ready.exec(["head", "-c", String(MAX_CHECK_ITEMS_BYTES + 1), CHECK_ITEMS_FILE], CHECKOUT_DIR);
|
|
1992
|
-
const items = commandItems(read, (text2) => scrubReason(text2, config) ?? text2);
|
|
1993
|
-
if (!items.ok) {
|
|
1994
|
-
await record(check, "fail", items.reason);
|
|
1995
|
-
continue;
|
|
1996
|
-
}
|
|
1997
|
-
const failing = items.items === void 0 ? void 0 : itemsSentence(items.items, { one: "check item", many: "check items" });
|
|
1998
|
-
if (outcome.status === 0 && failing === void 0) {
|
|
1999
|
-
log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
|
|
2000
|
-
await record(check, "pass", void 0, "", items.items);
|
|
2001
|
-
continue;
|
|
2002
|
-
}
|
|
2003
|
-
if (outcome.status === 0) {
|
|
2004
|
-
await record(check, "fail", failing, "", items.items);
|
|
2005
|
-
continue;
|
|
2006
|
-
}
|
|
2007
|
-
const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
|
|
2008
|
-
const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
|
|
2009
|
-
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)}`);
|
|
2010
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}`) });
|
|
2011
1644
|
} catch (err) {
|
|
2012
|
-
|
|
2013
|
-
} finally {
|
|
2014
|
-
if (job.checkContainer !== void 0) {
|
|
2015
|
-
await cleanupContainer(runtime, job.checkContainer).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
|
|
2016
|
-
delete job.checkContainer;
|
|
2017
|
-
writeJobFile(jobFilePath, job);
|
|
2018
|
-
}
|
|
2019
|
-
if (job.checkMicrovm !== void 0) {
|
|
2020
|
-
await cleanupMicroVm(runtime, job.checkMicrovm).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
|
|
2021
|
-
delete job.checkMicrovm;
|
|
2022
|
-
writeJobFile(jobFilePath, job);
|
|
2023
|
-
}
|
|
2024
|
-
rmSync(scratch, { recursive: true, force: true });
|
|
2025
|
-
rmSync(join(jobDir, "checks", EVAL_INPUT_NAME), { recursive: true, force: true });
|
|
2026
|
-
if (existsSync(scratch)) log.error(`${tag} the checks' scratch directory could not be removed`);
|
|
2027
|
-
log.line(`${tag} checks done (${formatDuration(now().getTime() - startedMs)})`);
|
|
1645
|
+
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
2028
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);
|
|
2029
1658
|
}
|
|
2030
1659
|
|
|
2031
1660
|
// src/runner/judge-verdict.ts
|
|
@@ -2109,15 +1738,10 @@ ${rows}
|
|
|
2109
1738
|
}
|
|
2110
1739
|
|
|
2111
1740
|
// src/runner/judge.ts
|
|
2112
|
-
var
|
|
1741
|
+
var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
|
|
2113
1742
|
var MAX_DIFF_BYTES = 4 * 1024 * 1024;
|
|
2114
|
-
var MAX_TEST_FILES = 2e4;
|
|
2115
|
-
var MAX_TEST_FILE_BYTES = 1024 * 1024;
|
|
2116
1743
|
var NO_CLAUDE = "the image has no Claude Code for the judge";
|
|
2117
1744
|
var REPORT_CAPTION = "The host's criteria judge, each criterion judged against the test that cites it";
|
|
2118
|
-
async function loadCitationFinder() {
|
|
2119
|
-
return await import(pathToFileURL(join(resolvePackRoot(), "maintained", "outerlayer-build", "scripts", "criterion-citations.mjs")).href);
|
|
2120
|
-
}
|
|
2121
1745
|
function testFilesIn(root, isTest, limits = { files: MAX_TEST_FILES, bytes: MAX_TEST_FILE_BYTES }) {
|
|
2122
1746
|
const files = [];
|
|
2123
1747
|
const walk = (dir, prefix) => {
|
|
@@ -2151,26 +1775,6 @@ async function hostPassedNames(config, item, fetchImpl) {
|
|
|
2151
1775
|
} while (since !== null);
|
|
2152
1776
|
return [...latest].filter(([, result]) => result === "pass").map(([name]) => name);
|
|
2153
1777
|
}
|
|
2154
|
-
function criteriaOf(workItem) {
|
|
2155
|
-
const criteria = Object(Object(workItem).criteria);
|
|
2156
|
-
if (!Array.isArray(criteria?.items)) return void 0;
|
|
2157
|
-
const drafted = criteria.drafted === true;
|
|
2158
|
-
const items = [];
|
|
2159
|
-
for (const entry of criteria.items) {
|
|
2160
|
-
const { id, text: text2, proof } = Object(entry);
|
|
2161
|
-
if (typeof id !== "string" || typeof text2 !== "string") continue;
|
|
2162
|
-
items.push({ id, text: text2, proof: typeof proof === "string" ? proof : null, drafted });
|
|
2163
|
-
}
|
|
2164
|
-
return items.length === 0 ? void 0 : items;
|
|
2165
|
-
}
|
|
2166
|
-
function hostItemsOf(workItem, name) {
|
|
2167
|
-
const checks = Object(workItem).checks;
|
|
2168
|
-
if (!Array.isArray(checks)) return [];
|
|
2169
|
-
const check = checks.find((entry) => Object(entry).name === name);
|
|
2170
|
-
if (check?.source !== "host") return [];
|
|
2171
|
-
const parsed = CheckItemsSchema.safeParse(check.items);
|
|
2172
|
-
return parsed.success ? parsed.data : [];
|
|
2173
|
-
}
|
|
2174
1778
|
function environmentFor(input) {
|
|
2175
1779
|
const { config, job, jobDir, deps } = input;
|
|
2176
1780
|
if (deps.judgeEnvironment !== void 0) return deps.judgeEnvironment;
|
|
@@ -2391,7 +1995,7 @@ async function runJudge(input) {
|
|
|
2391
1995
|
const reason = at.reason ?? failing;
|
|
2392
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) });
|
|
2393
1997
|
const link = await uploadReport(check, report, { branch: at.branch, head: at.head });
|
|
2394
|
-
await record(check.name, result, reason, { keyTail: at.limitKey === true ?
|
|
1998
|
+
await record(check.name, result, reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL : "", link, items: sendable(items) });
|
|
2395
1999
|
}
|
|
2396
2000
|
async function finishWhole(check, at) {
|
|
2397
2001
|
const where = { head: at.head, branch: at.branch };
|
|
@@ -2414,7 +2018,7 @@ async function runJudge(input) {
|
|
|
2414
2018
|
}
|
|
2415
2019
|
async function recordWhole(check, at) {
|
|
2416
2020
|
const link = at.report === void 0 ? void 0 : await uploadReport(check, at.report, { branch: at.branch, head: at.head });
|
|
2417
|
-
await record(check.name, at.result, at.reason, { keyTail: at.limitKey === true ?
|
|
2021
|
+
await record(check.name, at.result, at.reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL : "", link, items: sendable(at.items) });
|
|
2418
2022
|
}
|
|
2419
2023
|
function sendable(items) {
|
|
2420
2024
|
if (items === void 0) return void 0;
|
|
@@ -2650,7 +2254,7 @@ function mergeScrubConfig(a, b) {
|
|
|
2650
2254
|
function releaseScrub(config, home) {
|
|
2651
2255
|
const merged = mergeScrubConfig(config.scrub, readCloudConfig(home).scrub);
|
|
2652
2256
|
const secrets = [...headerVariables(config.build?.destinations), ...CLAUDE_CREDENTIAL_VARIABLES].map((name) => process.env[name] ?? "");
|
|
2653
|
-
const held = [currentClaudeCredential(process.env)?.value, ...removedClaudeCredentialValues(), ...managedClaudeKeyValues()].filter((value) => value !== void 0);
|
|
2257
|
+
const held = [currentClaudeCredential(process.env)?.value, ...removedClaudeCredentialValues(), ...managedClaudeKeyValues(), ...managedBuildSettingValues()].filter((value) => value !== void 0);
|
|
2654
2258
|
return { literals: [...scrubList(merged?.literals), ...secrets, ...held], patterns: scrubList(merged?.patterns) };
|
|
2655
2259
|
}
|
|
2656
2260
|
function transcriptScrub(config, secrets) {
|
|
@@ -2927,6 +2531,15 @@ function readTunnelRecord(path) {
|
|
|
2927
2531
|
return void 0;
|
|
2928
2532
|
}
|
|
2929
2533
|
}
|
|
2534
|
+
var VALUES_NOT_HELD = "the runner restarted and could not read this build's variables from its factory again, so it cannot remove their values from what it uploads";
|
|
2535
|
+
var SCRUBBED_RELEASE_LOG = "release-log.txt";
|
|
2536
|
+
function readScrubbedReleaseLog(jobDirPath) {
|
|
2537
|
+
try {
|
|
2538
|
+
return readFileSync(join(jobDirPath, SCRUBBED_RELEASE_LOG), "utf8");
|
|
2539
|
+
} catch {
|
|
2540
|
+
return void 0;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2930
2543
|
async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
2931
2544
|
const spawnFn = deps.spawnFn ?? spawn;
|
|
2932
2545
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -2935,6 +2548,7 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
2935
2548
|
const logPath = join(jobDirPath, "command.log");
|
|
2936
2549
|
const tag = `#${job.item}`;
|
|
2937
2550
|
deps.attemptLogs?.open(job.item, jobDirPath);
|
|
2551
|
+
const valuesNotHeld = (job.managedVariables?.length ?? 0) > 0 && managedBuildSettingsFor(jobDirPath) === void 0;
|
|
2938
2552
|
if (job.sender !== void 0) {
|
|
2939
2553
|
await stopSender({ job, deps: { spawnFn, runtime: deps.container, log } });
|
|
2940
2554
|
delete job.sender;
|
|
@@ -2978,6 +2592,10 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
2978
2592
|
writeJobFile(jobFilePath, job);
|
|
2979
2593
|
}
|
|
2980
2594
|
const uploadSessions = async (workdir) => {
|
|
2595
|
+
if (valuesNotHeld) {
|
|
2596
|
+
log.error(`${tag} sync skipped: ${VALUES_NOT_HELD}`);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2981
2599
|
if (job.container === void 0 && job.microvm === void 0 && job.build?.exec === void 0 && workdirProblem(workdir) !== void 0) {
|
|
2982
2600
|
log.line(`${tag} sync skipped: workdir ${workdir} is gone`);
|
|
2983
2601
|
} else if (job.itemKey === void 0) {
|
|
@@ -3036,7 +2654,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
3036
2654
|
log.error(`${tag} removing an earlier attempt's check environment failed: ${describeCallFailure(err)} \xB7 continuing`);
|
|
3037
2655
|
}
|
|
3038
2656
|
}
|
|
3039
|
-
if (outcome === "ok" && !job.cleanupRan
|
|
2657
|
+
if (valuesNotHeld && outcome === "ok" && !job.cleanupRan) log.error(`${tag} the host's judge and checks skipped: ${VALUES_NOT_HELD}`);
|
|
2658
|
+
if (outcome === "ok" && !job.cleanupRan && job.judgeRan !== true && !valuesNotHeld) {
|
|
3040
2659
|
setStage(job, jobDirPath, "judge");
|
|
3041
2660
|
job.judgeRan = true;
|
|
3042
2661
|
writeJobFile(jobFilePath, job);
|
|
@@ -3051,7 +2670,7 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
3051
2670
|
log.error(`${tag} the host's judge stopped: ${describeCallFailure(err)} \xB7 continuing`);
|
|
3052
2671
|
}
|
|
3053
2672
|
}
|
|
3054
|
-
if (outcome === "ok" && !job.cleanupRan && job.checksRan !== true) {
|
|
2673
|
+
if (outcome === "ok" && !job.cleanupRan && job.checksRan !== true && !valuesNotHeld) {
|
|
3055
2674
|
setStage(job, jobDirPath, "checks");
|
|
3056
2675
|
job.checksRan = true;
|
|
3057
2676
|
writeJobFile(jobFilePath, job);
|
|
@@ -3265,12 +2884,17 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
3265
2884
|
writeJobFile(jobFilePath, job);
|
|
3266
2885
|
}
|
|
3267
2886
|
const sentAtMs = now().getTime();
|
|
3268
|
-
const runnerLog =
|
|
2887
|
+
const runnerLog = valuesNotHeld ? `The runner sends no log for this attempt: ${VALUES_NOT_HELD}.
|
|
2888
|
+
` : releaseLogFor(jobDirPath, job, config);
|
|
3269
2889
|
const detail = outcome === void 0 ? { log: runnerLog } : { stage: job.endStage, exitCode: job.endExitCode, reason: job.reason, environment: job.environment, log: runnerLog };
|
|
3270
2890
|
const release = await retrySend(() => releaseLease(config, job.item, outcome, detail, deps.fetchImpl), {
|
|
3271
2891
|
now: deps.now,
|
|
3272
2892
|
sleep: deps.sleep
|
|
3273
2893
|
});
|
|
2894
|
+
if (!release.ok && release.retryable && job.managedVariables !== void 0 && runnerLog !== void 0) {
|
|
2895
|
+
writeFileSync(join(jobDirPath, SCRUBBED_RELEASE_LOG), runnerLog, { mode: RUNNER_FILE_MODE });
|
|
2896
|
+
}
|
|
2897
|
+
dropManagedBuildSettings(jobDirPath);
|
|
3274
2898
|
const alreadyReleased = release.ok && wasAlreadyReleased(release.value, outcome, sentAtMs);
|
|
3275
2899
|
const endedAt = now();
|
|
3276
2900
|
job.outcome = outcome;
|
|
@@ -3323,244 +2947,528 @@ async function retryFailedReleases(jobsDirPath, config, deps) {
|
|
|
3323
2947
|
if (!job || job.releaseFailed === void 0 || job.releaseFailed === ALREADY_RELEASED || isUnfinished(job)) {
|
|
3324
2948
|
continue;
|
|
3325
2949
|
}
|
|
3326
|
-
const runnerLog = releaseLogFor(dirPath, job, config);
|
|
2950
|
+
const runnerLog = readScrubbedReleaseLog(dirPath) ?? releaseLogFor(dirPath, job, config);
|
|
3327
2951
|
const detail = job.outcome === void 0 ? { log: runnerLog } : { stage: job.endStage, exitCode: job.endExitCode, reason: job.reason, environment: job.environment, log: runnerLog };
|
|
3328
2952
|
const release = await retrySend(() => releaseLease(config, job.item, job.outcome, detail, deps.fetchImpl), {
|
|
3329
2953
|
now: deps.now,
|
|
3330
2954
|
sleep: deps.sleep
|
|
3331
2955
|
});
|
|
3332
2956
|
if (release.ok || !release.retryable) {
|
|
2957
|
+
rmSync(join(dirPath, SCRUBBED_RELEASE_LOG), { force: true });
|
|
3333
2958
|
job.releaseFailed = void 0;
|
|
3334
2959
|
writeJobFile(jobFilePath, job);
|
|
3335
2960
|
} else {
|
|
3336
2961
|
job.releaseFailed = release.reason;
|
|
3337
2962
|
writeJobFile(jobFilePath, job);
|
|
3338
2963
|
}
|
|
3339
|
-
results.push({ job, released: release.ok });
|
|
3340
|
-
}
|
|
3341
|
-
return results;
|
|
3342
|
-
}
|
|
3343
|
-
function isUnfinished(job) {
|
|
3344
|
-
return job.outcome === void 0 && job.endedAt === void 0;
|
|
3345
|
-
}
|
|
3346
|
-
function unfinishedJobs(jobsDirPath) {
|
|
3347
|
-
if (!existsSync(jobsDirPath)) return [];
|
|
3348
|
-
const found = [];
|
|
3349
|
-
for (const entry of readdirSync(jobsDirPath, { withFileTypes: true })) {
|
|
3350
|
-
if (!entry.isDirectory()) continue;
|
|
3351
|
-
const dirPath = join(jobsDirPath, entry.name);
|
|
3352
|
-
const job = readJobFile(join(dirPath, "job.json"));
|
|
3353
|
-
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
|
+
}
|
|
3354
3149
|
}
|
|
3355
|
-
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
}
|
|
3360
|
-
|
|
3361
|
-
return
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
return isContainerTracked(job) || isMicroVmTracked(job);
|
|
3365
|
-
}
|
|
3366
|
-
function trackingOf(job) {
|
|
3367
|
-
return job.container?.tracking ?? job.microvm?.tracking;
|
|
3368
|
-
}
|
|
3369
|
-
function isReadable(job) {
|
|
3370
|
-
return readsJobState(job, READ_JOB_STATE_FORMATS);
|
|
3371
|
-
}
|
|
3372
|
-
function listUnreadableJobs(jobsDirPath) {
|
|
3373
|
-
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) };
|
|
3374
3159
|
}
|
|
3375
|
-
function
|
|
3376
|
-
return
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
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();
|
|
3382
3166
|
});
|
|
3167
|
+
return { done, cancel: () => clearTimeout(timer) };
|
|
3383
3168
|
}
|
|
3384
|
-
function
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
);
|
|
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`;
|
|
3388
3172
|
}
|
|
3389
|
-
|
|
3390
|
-
const
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
const
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
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
|
+
};
|
|
3406
3203
|
}
|
|
3407
|
-
|
|
3408
|
-
// src/runner/sender.ts
|
|
3409
|
-
var STOP_WAIT_MS = 3e4;
|
|
3410
|
-
var POLL_MS = 200;
|
|
3411
|
-
var ASK_AGAIN_MS = 1e3;
|
|
3412
|
-
var EXIT_REPORT_WAIT_MS = 2e3;
|
|
3413
|
-
var exitsLogged = /* @__PURE__ */ new Map();
|
|
3414
|
-
var BUILD_HOME = "/build/home";
|
|
3415
|
-
async function startSender(input) {
|
|
3204
|
+
async function runHostChecks(input) {
|
|
3416
3205
|
const { config, job, jobDir, deps } = input;
|
|
3206
|
+
const { runtime, log } = deps;
|
|
3417
3207
|
const tag = `#${job.item}`;
|
|
3418
|
-
|
|
3419
|
-
const
|
|
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;
|
|
3420
3214
|
try {
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
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
|
+
}
|
|
3433
3326
|
});
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
const runtime = deps.runtime ?? createContainerRuntime();
|
|
3473
|
-
const pidfile = senderPidfile(BUILD_HOME, Number(job.item));
|
|
3474
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
3475
|
-
const running = async () => {
|
|
3476
|
-
const probe = await runtime.host.exec({ container: state.container, user: state.agentUser ?? ADDED_USER, env: { literals: {}, passed: {} }, argv: ["/bin/sh", "-c", senderAliveScript(pidfile)] });
|
|
3477
|
-
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;
|
|
3478
3365
|
};
|
|
3366
|
+
const setupTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
|
|
3367
|
+
let settled;
|
|
3479
3368
|
try {
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
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);
|
|
3486
3376
|
}
|
|
3487
|
-
|
|
3488
|
-
|
|
3377
|
+
settled = outcome;
|
|
3378
|
+
} finally {
|
|
3379
|
+
setupTimer.cancel();
|
|
3489
3380
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
}
|
|
3493
|
-
function isLive(sender) {
|
|
3494
|
-
if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
|
|
3495
|
-
try {
|
|
3496
|
-
process.kill(sender.pid, 0);
|
|
3497
|
-
return true;
|
|
3498
|
-
} catch {
|
|
3499
|
-
return false;
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3502
|
-
function signalHost(sender, signal) {
|
|
3503
|
-
if (!isLive(sender)) return;
|
|
3504
|
-
try {
|
|
3505
|
-
process.kill(-sender.pid, signal);
|
|
3506
|
-
} catch {
|
|
3381
|
+
if (typeof settled === "string") return await failAll(settled);
|
|
3382
|
+
const ready = settled;
|
|
3507
3383
|
try {
|
|
3508
|
-
|
|
3509
|
-
|
|
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)}`);
|
|
3510
3399
|
}
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
|
|
3516
|
-
user: state.agentUser ?? ADDED_USER,
|
|
3517
|
-
cwd: "/",
|
|
3518
|
-
env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
|
|
3519
|
-
});
|
|
3520
|
-
return status === 0;
|
|
3521
|
-
}
|
|
3522
|
-
async function exitReported(pid) {
|
|
3523
|
-
const logged = exitsLogged.get(pid);
|
|
3524
|
-
let timer;
|
|
3525
|
-
const bound = new Promise((resolve) => {
|
|
3526
|
-
timer = setTimeout(resolve, EXIT_REPORT_WAIT_MS);
|
|
3527
|
-
timer.unref?.();
|
|
3528
|
-
});
|
|
3529
|
-
await Promise.race([logged, bound]);
|
|
3530
|
-
clearTimeout(timer);
|
|
3531
|
-
exitsLogged.delete(pid);
|
|
3532
|
-
}
|
|
3533
|
-
async function stopSender(input) {
|
|
3534
|
-
const { job, deps } = input;
|
|
3535
|
-
const sender = job.sender;
|
|
3536
|
-
if (sender === void 0) return;
|
|
3537
|
-
const tag = `#${job.item}`;
|
|
3538
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
3539
|
-
const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
3540
|
-
const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
3541
|
-
const signalInside = async (signal) => {
|
|
3542
|
-
if (inContainer !== void 0) return signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
|
|
3543
|
-
if (inGuest !== void 0) return signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
|
|
3544
|
-
return void 0;
|
|
3545
|
-
};
|
|
3546
|
-
try {
|
|
3547
|
-
let reached = true;
|
|
3548
|
-
if (inContainer !== void 0 || inGuest !== void 0) reached = await signalInside("TERM");
|
|
3549
|
-
else signalHost(sender, "SIGTERM");
|
|
3550
|
-
const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
|
|
3551
|
-
for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) {
|
|
3552
|
-
await sleep(POLL_MS);
|
|
3553
|
-
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)}`);
|
|
3554
3404
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
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);
|
|
3559
3453
|
}
|
|
3560
|
-
await exitReported(sender.pid);
|
|
3561
3454
|
} catch (err) {
|
|
3562
|
-
|
|
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)})`);
|
|
3563
3471
|
}
|
|
3564
3472
|
}
|
|
3565
3473
|
|
|
3566
|
-
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 };
|