@outerlayer/cli 0.28.3 → 0.29.0
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/CHANGELOG.md +19 -0
- package/dist/{agent-setup-IDF3EXM4.js → agent-setup-S3KI7TER.js} +14 -14
- package/dist/{artifact-link-cmd-LEI6EOZE.js → artifact-link-cmd-LIJEXVN5.js} +2 -2
- package/dist/{bridge-JN3VJ23L.js → bridge-CP25XV3W.js} +2 -2
- package/dist/{build-JCYOHPLL.js → build-ZS4Y5MJU.js} +4 -4
- package/dist/build-info.json +1 -1
- package/dist/{builder-HCTVP2O7.js → builder-FDUOMHY3.js} +6 -6
- package/dist/{check-F2TJCQ7S.js → check-5XB22HKS.js} +42 -41
- package/dist/{chunk-6NFXOQKZ.js → chunk-23PFIW35.js} +1 -1
- package/dist/{chunk-SATUCMAJ.js → chunk-25Z6SBMQ.js} +2 -2
- package/dist/{chunk-W7BH5YN6.js → chunk-2KOXAMMB.js} +39 -28
- package/dist/{chunk-EPGMCEVP.js → chunk-2YLKMVFV.js} +2 -2
- package/dist/{chunk-QBF462QH.js → chunk-3KMYWLMK.js} +1 -1
- package/dist/{chunk-5B7BEQPR.js → chunk-427AVP6J.js} +2 -2
- package/dist/{chunk-VWFOKRYS.js → chunk-42TFWHFF.js} +1 -1
- package/dist/{chunk-X7SFJZIG.js → chunk-6AG2XW5S.js} +29 -10
- package/dist/{chunk-BUP5N3X6.js → chunk-6XQPCQBW.js} +1 -1
- package/dist/{chunk-WL3QF3QF.js → chunk-77YT7YC4.js} +18 -16
- package/dist/{chunk-EZ3DZXUH.js → chunk-7H7RUQCZ.js} +3 -3
- package/dist/{chunk-LRFU6RPD.js → chunk-A2E7MU2H.js} +55 -47
- package/dist/{chunk-MFI55TNI.js → chunk-BFA2LNJG.js} +22 -6
- package/dist/{chunk-N5EEEKT6.js → chunk-C7F6U2YZ.js} +3 -2
- package/dist/{chunk-M6NNXKRM.js → chunk-CU7TJL6S.js} +1 -1
- package/dist/{chunk-T7LJ6AIC.js → chunk-D3D2CERC.js} +5 -5
- package/dist/{chunk-RKOTJQVG.js → chunk-DXI6A6JN.js} +10 -4
- package/dist/{chunk-KJIUFOC4.js → chunk-F72ABZRD.js} +1 -1
- package/dist/{chunk-UXLLWIA2.js → chunk-FA6XWYRH.js} +7 -7
- package/dist/{chunk-P2RWZCV7.js → chunk-FUGRIAG7.js} +2 -2
- package/dist/{chunk-JYVBEWBC.js → chunk-GG3WS4C5.js} +3 -3
- package/dist/{chunk-O47BTZSN.js → chunk-GI73HOI2.js} +1 -1
- package/dist/{chunk-2BZTGJII.js → chunk-GJJDPZS2.js} +7 -7
- package/dist/{chunk-QKXF7CLR.js → chunk-GJOF6357.js} +4 -4
- package/dist/{chunk-TG7FAHRP.js → chunk-H6DGJBXG.js} +5 -5
- package/dist/{chunk-QFNFBQCG.js → chunk-HRWL7NSJ.js} +5 -5
- package/dist/chunk-I3LTIAOR.js +11 -0
- package/dist/{chunk-OOUHNKKX.js → chunk-IG45QISA.js} +2 -2
- package/dist/{chunk-AV5MJY45.js → chunk-IGEJUPDE.js} +3 -3
- package/dist/{chunk-QZBXLLTV.js → chunk-JL6VZHAJ.js} +38 -1
- package/dist/{chunk-RXFKXZJS.js → chunk-KBIJZAH4.js} +1 -1
- package/dist/{chunk-UHU7H57K.js → chunk-LKN4J37L.js} +151 -34
- package/dist/{chunk-ILU6664L.js → chunk-M4QY7YZ5.js} +360 -74
- package/dist/{chunk-4SJ57DH7.js → chunk-MO2NI6W6.js} +35 -27
- package/dist/{chunk-HWOU2HDH.js → chunk-MX3PPJNS.js} +1 -1
- package/dist/{chunk-ITAIEJ2T.js → chunk-MZJAIRXM.js} +4 -4
- package/dist/{chunk-YYGXCRIW.js → chunk-NAHTO4XM.js} +1 -1
- package/dist/{chunk-JGML2VUN.js → chunk-NX2G6PH6.js} +1 -1
- package/dist/{chunk-OO6OADZN.js → chunk-OD6WUNKZ.js} +3 -3
- package/dist/{chunk-DSKOHPXN.js → chunk-OKAJSYZ2.js} +1 -1
- package/dist/{chunk-DWVAG73B.js → chunk-PAHZQQB3.js} +1 -1
- package/dist/{chunk-NMIYK2G2.js → chunk-PGA5BVHQ.js} +3 -3
- package/dist/{chunk-YTR435DO.js → chunk-PP7C7JQO.js} +3 -3
- package/dist/{chunk-DQYMQ47E.js → chunk-S2Q65ZOT.js} +1 -1
- package/dist/{chunk-R7TMTFJP.js → chunk-SHVE5JZO.js} +1 -1
- package/dist/{chunk-JZJLKTZT.js → chunk-SN3C57RV.js} +5 -5
- package/dist/{chunk-TOHKSZKI.js → chunk-TZGQQY7Y.js} +7 -7
- package/dist/{chunk-VM6BX2WJ.js → chunk-UPKFSNSU.js} +5 -5
- package/dist/{chunk-TCBVV2JK.js → chunk-V47KJGNE.js} +3 -3
- package/dist/{chunk-NKD5BNRX.js → chunk-VT3BKV5D.js} +3 -3
- package/dist/{chunk-HOU33DW5.js → chunk-VY4YVFL5.js} +1 -1
- package/dist/{chunk-SVODOMYC.js → chunk-WEMNLTWB.js} +7 -7
- package/dist/{chunk-DIQN5SEL.js → chunk-YSH4KCUJ.js} +1 -1
- package/dist/{claude-launcher-LVYZ4YJI.js → claude-launcher-SHIQTBS6.js} +5 -5
- package/dist/{cli-5LLFR2SX.js → cli-EN2WGVM6.js} +126 -120
- package/dist/{command-YXOMIXOJ.js → command-2VFWA2RF.js} +30 -30
- package/dist/{config-TNV5HHLO.js → config-HJQ4YE57.js} +2 -2
- package/dist/{connect-cmd-43IWARIV.js → connect-cmd-T6IAT5FT.js} +12 -12
- package/dist/{container-start-YG45GLFE.js → container-start-C5ZCXXMF.js} +29 -29
- package/dist/{context-cleanup-LTH77FC3.js → context-cleanup-MV3X6NSF.js} +3 -3
- package/dist/{control-plane-launch-TIK265OT.js → control-plane-launch-5FZ6LJOC.js} +10 -10
- package/dist/{dist-JFVKLUHL.js → dist-H5IYGH3T.js} +2 -2
- package/dist/{docker-2IIRBQAB.js → docker-RFN6UWTT.js} +6 -6
- package/dist/{doctor-3SRHLRF5.js → doctor-VXDBXKSL.js} +32 -32
- package/dist/{emit-artifact-cmd-RM2X3PBJ.js → emit-artifact-cmd-M52RLUBU.js} +16 -16
- package/dist/{emit-cmd-QUABLVO6.js → emit-cmd-HTNETHAE.js} +6 -6
- package/dist/{emit-criteria-cmd-ZUI7DFND.js → emit-criteria-cmd-APDCA6FK.js} +13 -13
- package/dist/{emit-finding-cmd-W2FU4CCF.js → emit-finding-cmd-XEX5XVVI.js} +13 -13
- package/dist/{emit-result-cmd-JINMQ7QG.js → emit-result-cmd-HFC3WDWB.js} +52 -25
- package/dist/{emit-step-cmd-7FZWQTV3.js → emit-step-cmd-LTRPF2YX.js} +14 -14
- package/dist/{exec-client-6ZJJ4KVH.js → exec-client-GHFNXOY6.js} +3 -3
- package/dist/{fleet-readiness-3A67MUAU.js → fleet-readiness-SJA7NFR2.js} +12 -12
- package/dist/{governing-source-KQMKM5CI.js → governing-source-PBYOKKAJ.js} +17 -17
- package/dist/{guest-init-A4HHI2UR.js → guest-init-MQRIALWR.js} +4 -4
- package/dist/{hook-fast-UJPAWDAO.js → hook-fast-JKYCVO4R.js} +1 -1
- package/dist/{host-key-NGRCMJZ7.js → host-key-DBYH3RTZ.js} +3 -3
- package/dist/{image-tag-TEOKZVD5.js → image-tag-XNODAHAW.js} +3 -3
- package/dist/index.js +4 -4
- package/dist/{init-XP55Q4ZT.js → init-JJPJ2XLL.js} +29 -29
- package/dist/{init-HQW3LHIF.js → init-ZUMSDYMW.js} +29 -29
- package/dist/{init-cmd-XSOVMNNE.js → init-cmd-342RW7DZ.js} +22 -22
- package/dist/{init-update-MZ4IRFC7.js → init-update-4MCW5W72.js} +21 -21
- package/dist/{install-NPTR35MG.js → install-GODA5G7U.js} +4 -4
- package/dist/{install-cmd-4PXPAFQX.js → install-cmd-YNQ5DCZP.js} +5 -5
- package/dist/{layout-BBTQPS6V.js → layout-DBAL2HTQ.js} +4 -4
- package/dist/{login-browser-WSOSQH2T.js → login-browser-B6E4GNIF.js} +2 -2
- package/dist/{logs-Z4L4I3OR.js → logs-KAG3MAUO.js} +2 -2
- package/dist/loop-ZXJFOVCS.js +81 -0
- package/dist/{machine-ATDPHWL5.js → machine-F3TVUBZY.js} +25 -25
- package/dist/{machine-skills-O7W6NMWB.js → machine-skills-6MLTVBCF.js} +4 -4
- package/dist/{mcp-serve-cmd-NQM2WDYI.js → mcp-serve-cmd-SGISNCB3.js} +4 -4
- package/dist/{paths-2VSGK6E2.js → paths-PG4JNLZG.js} +2 -2
- package/dist/{pending-SFZ2AYSK.js → pending-GG252ERT.js} +3 -3
- package/dist/{pidfile-3FT5OLUE.js → pidfile-7SYDMT2O.js} +3 -3
- package/dist/{record-7SSRLSI2.js → record-ZGUOOYJH.js} +3 -3
- package/dist/{runner-env-2Z2DVSUJ.js → runner-env-WI6LKWIU.js} +5 -5
- package/dist/{runner-identity-B37HHOZR.js → runner-identity-JIC54L5Q.js} +6 -6
- package/dist/{runner-key-SICUOQY5.js → runner-key-RXCKMQRL.js} +29 -29
- package/dist/{session-sender-7MSVJZ6Q.js → session-sender-JAAMA5E6.js} +17 -17
- package/dist/{spec-cmd-KAQ7MX4O.js → spec-cmd-BUFLEQ6L.js} +14 -14
- package/dist/{starter-pack-5SOT5V4I.js → starter-pack-I5W5JAYT.js} +5 -5
- package/dist/{status-AI22UPKC.js → status-E7V365CD.js} +33 -32
- package/dist/{sync-cmd-7K3WVQKO.js → sync-cmd-U5FUBXJY.js} +16 -16
- package/dist/{watch-NCYESLO2.js → watch-AOPZJOUM.js} +16 -16
- package/dist/{work-claim-cmd-IOUEHYIY.js → work-claim-cmd-Z2UZD37I.js} +17 -17
- package/dist/{work-cmd-FOI74X5B.js → work-cmd-XLVQDNTC.js} +14 -14
- package/dist/{work-comment-cmd-3C44H4CG.js → work-comment-cmd-THLQUSMH.js} +14 -14
- package/dist/{work-logs-cmd-YGAWCX3X.js → work-logs-cmd-2NFG6ZY6.js} +14 -14
- package/dist/{work-open-pr-cmd-HUG5HAVC.js → work-open-pr-cmd-J22A2YCD.js} +15 -15
- package/dist/{work-pr-cmd-L2EDOYXH.js → work-pr-cmd-SYAENEH4.js} +14 -14
- package/dist/{work-stop-cmd-GGDVKC67.js → work-stop-cmd-FP453YCB.js} +14 -14
- package/package.json +1 -1
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +40 -39
- package/skill-pack/maintained/outerlayer-build/SKILL.md +5 -9
- package/skill-pack/maintained/outerlayer-build/references/criteria-judge.md +15 -12
- package/skill-pack/maintained/outerlayer-build/references/release.md +11 -6
- package/skill-pack/shipped.json +1 -0
- package/skill-pack/validators/acceptance-criteria.yaml +7 -2
- package/dist/loop-QQBLJ7GN.js +0 -80
|
@@ -1,31 +1,33 @@
|
|
|
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 { DOCKERD_LOG, HELPER_PATH, HELPER_RECORD } from './chunk-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
4
|
+
import { DOCKERD_LOG, HELPER_PATH, HELPER_RECORD } from './chunk-OD6WUNKZ.js';
|
|
5
|
+
import { zodProblem } from './chunk-I3LTIAOR.js';
|
|
6
|
+
import { senderPidfile } from './chunk-PP7C7JQO.js';
|
|
7
|
+
import { installScrubContext } from './chunk-GJJDPZS2.js';
|
|
8
|
+
import { writeDockerClientConfig } from './chunk-KBIJZAH4.js';
|
|
9
|
+
import { ADDED_USER, shellQuote, GH_CONFIG_YML, ghHostsYml, AssetError, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createLeaseChannel, createBuildDisk, machineConfig, resolveAgentUser, AgentUserError, runBuildSteps, LEASE_SECONDS, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, containerSenderCommand, signalInContainer, syncInContainer, copyTranscriptsFromContainer, senderAliveScript, helperFirecrackerNote, HelperRefusal, isLiteralAddress, deleteBuildDisk, startCheckContainer, lifecycleSteps, lifecycleFailureReason, writeEvalInput, DeliveryFailure, EVAL_INPUT_NAME, copyTranscripts, senderSignalScript, containerJudgeEnvironment, copyJudgeTree } from './chunk-MO2NI6W6.js';
|
|
10
|
+
import { waitForAgent, vmExecArgs } from './chunk-77YT7YC4.js';
|
|
11
|
+
import { socketPathProblem, vsockLinks, LEASE_LINK, linkVsockSockets } from './chunk-YSH4KCUJ.js';
|
|
12
|
+
import { describeCallFailure, resolveGoverningSource, refusalCode, removeStagedContext, stageGovernedContext, pinnedSourceOf, commandWithShim, logStamp, createRunnerLog, removeKeptContext, formatDuration, retrySend, formatClock, keptContextTree } from './chunk-SN3C57RV.js';
|
|
13
|
+
import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-NAHTO4XM.js';
|
|
14
|
+
import { readBranchHead, fetchCommit, diffBetween } from './chunk-V47KJGNE.js';
|
|
14
15
|
import { isSameProcess, startTimeOf } from './chunk-JBJTC3UU.js';
|
|
15
|
-
import { HASH_ARGV, parseProgramHash, JUDGE_PROGRAM, writeJobFile, readJobFile, childEnvironment,
|
|
16
|
+
import { HASH_ARGV, parseProgramHash, JUDGE_PROGRAM, writeJobFile, readJobFile, childEnvironment, withAnswerProtocol, judgeCommand, MAX_REPORT_BYTES, JUDGE_MAX_TURNS, JUDGE_DIR, COMMAND_DIR, MAX_ITEMS_BYTES } from './chunk-BFA2LNJG.js';
|
|
16
17
|
import { parseSize } from './chunk-YJLHUKYS.js';
|
|
17
18
|
import { CLAUDE_CODE_VERSION, FIRECRACKER_VERSION } from './chunk-SJZL5F3F.js';
|
|
18
|
-
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-
|
|
19
|
-
import { planArtifactUpload } from './chunk-
|
|
20
|
-
import { scrubText } from './chunk-
|
|
19
|
+
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-6AG2XW5S.js';
|
|
20
|
+
import { planArtifactUpload } from './chunk-6XQPCQBW.js';
|
|
21
|
+
import { scrubText } from './chunk-LKN4J37L.js';
|
|
21
22
|
import { readCloudConfig } from './chunk-PJDQPF5N.js';
|
|
22
|
-
import { signerFor } from './chunk-
|
|
23
|
-
import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath } from './chunk-
|
|
24
|
-
import { JOB_MOUNT, claudeCredential, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, parseIsolation, proxyEnvironment, withProxyWarningHidden, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT, repositoryPath, lifecycleWorkdir, currentClaudeCredential, removedClaudeCredentialValues, checkEnvironment, EVAL_INPUT_DIR } from './chunk-
|
|
25
|
-
import { resolvePackRoot } from './chunk-
|
|
23
|
+
import { signerFor } from './chunk-SHVE5JZO.js';
|
|
24
|
+
import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath } from './chunk-PAHZQQB3.js';
|
|
25
|
+
import { JOB_MOUNT, claudeCredential, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, parseIsolation, proxyEnvironment, withProxyWarningHidden, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT, repositoryPath, lifecycleWorkdir, CHECK_ITEMS_FILE, currentClaudeCredential, removedClaudeCredentialValues, checkEnvironment, EVAL_INPUT_DIR } from './chunk-C7F6U2YZ.js';
|
|
26
|
+
import { resolvePackRoot } from './chunk-NX2G6PH6.js';
|
|
26
27
|
import { MAINTAINED_SKILLS } from './chunk-CDDED64E.js';
|
|
27
28
|
import { resolveVersion } from './chunk-RA3O54FC.js';
|
|
28
|
-
import { parsePolicy } from './chunk-
|
|
29
|
+
import { parsePolicy } from './chunk-DXI6A6JN.js';
|
|
30
|
+
import { CheckItemsSchema, CHECK_ITEM_REASON_MAX_LENGTH } from './chunk-JL6VZHAJ.js';
|
|
29
31
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
30
32
|
import { fdName } from './chunk-XD4ZCMA4.js';
|
|
31
33
|
import { RUNNER_WORDED_REASONS, UNKNOWN_TRACKING_REASON } from './chunk-MESOOLF4.js';
|
|
@@ -821,6 +823,12 @@ function microVmJudgeEnvironment(input) {
|
|
|
821
823
|
const needsVm = () => {
|
|
822
824
|
if ((state.firecracker !== void 0 || state.jailed !== void 0) && !isRunning(runtime, state)) throw new Error("the build's microVM is gone");
|
|
823
825
|
};
|
|
826
|
+
const readJudgeFile = async (name, maxBytes) => {
|
|
827
|
+
needsVm();
|
|
828
|
+
const file = `${GUEST_JUDGE_DIR}/${name}`;
|
|
829
|
+
const read = await asRoot(runtime, state, ["/bin/sh", "-c", `[ -f ${file} ] && [ ! -L ${file} ] && head -c ${maxBytes + 1} ${file}`]);
|
|
830
|
+
return read.status === 0 && Buffer.byteLength(read.stdout) <= maxBytes ? read.stdout : void 0;
|
|
831
|
+
};
|
|
824
832
|
return {
|
|
825
833
|
stop: async () => {
|
|
826
834
|
needsVm();
|
|
@@ -865,12 +873,8 @@ function microVmJudgeEnvironment(input) {
|
|
|
865
873
|
appendFileSync(opts.logPath, ran.stdout + ran.stderr);
|
|
866
874
|
return ran;
|
|
867
875
|
},
|
|
868
|
-
readReport:
|
|
869
|
-
|
|
870
|
-
const file = `${GUEST_JUDGE_DIR}/report.html`;
|
|
871
|
-
const read = await asRoot(runtime, state, ["/bin/sh", "-c", `[ -f ${file} ] && [ ! -L ${file} ] && head -c ${MAX_REPORT_BYTES + 1} ${file}`]);
|
|
872
|
-
return read.status === 0 && Buffer.byteLength(read.stdout) <= MAX_REPORT_BYTES ? read.stdout : void 0;
|
|
873
|
-
}
|
|
876
|
+
readReport: () => readJudgeFile("report.html", MAX_REPORT_BYTES),
|
|
877
|
+
readItems: () => readJudgeFile("items.json", MAX_ITEMS_BYTES)
|
|
874
878
|
};
|
|
875
879
|
}
|
|
876
880
|
async function startCheckMicroVm(input) {
|
|
@@ -1538,10 +1542,144 @@ async function provisionProcess(input) {
|
|
|
1538
1542
|
function cleanupProcess(runtime, state) {
|
|
1539
1543
|
return closeHostBuild(runtime, state);
|
|
1540
1544
|
}
|
|
1545
|
+
var ARTIFACT_PROOF_REASON = "Its declared artifact proves it; the judge does not judge artifacts.";
|
|
1546
|
+
var NO_CITING_TEST_REASON = "No test cites this criterion, and it declares no artifact proof.";
|
|
1547
|
+
var NO_VERDICT_REASON = "The judge gave no result for this criterion.";
|
|
1548
|
+
function criterionInputs(input) {
|
|
1549
|
+
const paths = [...new Set((input.citations[input.criterion.id] ?? []).map((citation) => citation.path))].sort();
|
|
1550
|
+
const hash = createHash("sha256");
|
|
1551
|
+
const part = (text2) => {
|
|
1552
|
+
hash.update(`${Buffer.byteLength(text2)}:`);
|
|
1553
|
+
hash.update(text2);
|
|
1554
|
+
};
|
|
1555
|
+
part("judge-criterion-v1");
|
|
1556
|
+
part(input.brief);
|
|
1557
|
+
part(input.criterion.id);
|
|
1558
|
+
part(input.criterion.text);
|
|
1559
|
+
part(input.criterion.proof ?? "");
|
|
1560
|
+
for (const path of paths) {
|
|
1561
|
+
part(path);
|
|
1562
|
+
part(input.testFiles.get(path) ?? "");
|
|
1563
|
+
}
|
|
1564
|
+
return hash.digest("hex");
|
|
1565
|
+
}
|
|
1566
|
+
var HTTPS_LINK = /^https:\/\/\S+$/;
|
|
1567
|
+
function refsFor(id, citations) {
|
|
1568
|
+
const refs = (citations[id] ?? []).slice(0, 20).map((citation) => ({
|
|
1569
|
+
path: citation.path,
|
|
1570
|
+
line: citation.line,
|
|
1571
|
+
// Only an https link becomes an href on a page; any other is left off rather than refusing the check.
|
|
1572
|
+
...HTTPS_LINK.test(citation.href ?? "") ? { href: citation.href } : {}
|
|
1573
|
+
}));
|
|
1574
|
+
return refs.length === 0 ? void 0 : refs;
|
|
1575
|
+
}
|
|
1576
|
+
function planCriteria(input) {
|
|
1577
|
+
const previousById = new Map(input.previous.map((item) => [item.id, item]));
|
|
1578
|
+
const settled = [];
|
|
1579
|
+
const toJudge = [];
|
|
1580
|
+
for (const criterion of input.criteria) {
|
|
1581
|
+
const inputs = input.inputs.get(criterion.id);
|
|
1582
|
+
const base = { id: criterion.id, criterion: criterion.id, ...inputs === void 0 ? {} : { inputs } };
|
|
1583
|
+
const refs = refsFor(criterion.id, input.citations);
|
|
1584
|
+
if (criterion.proof !== null) {
|
|
1585
|
+
settled.push({ ...base, result: "skipped", reason: ARTIFACT_PROOF_REASON });
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
if (refs === void 0) {
|
|
1589
|
+
settled.push({ ...base, result: "fail", reason: NO_CITING_TEST_REASON });
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
const kept = previousById.get(criterion.id);
|
|
1593
|
+
if (kept !== void 0 && inputs !== void 0 && kept.inputs === inputs && (kept.result === "pass" || kept.result === "fail")) {
|
|
1594
|
+
settled.push({ ...base, result: kept.result, ...kept.reason === void 0 ? {} : { reason: kept.reason }, refs });
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
toJudge.push(criterion);
|
|
1598
|
+
}
|
|
1599
|
+
return { settled, toJudge };
|
|
1600
|
+
}
|
|
1601
|
+
var MAX_ANSWER_ENTRIES = 500;
|
|
1602
|
+
function readAnswer(raw) {
|
|
1603
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, problem: "expected an object" };
|
|
1604
|
+
const { result, items } = raw;
|
|
1605
|
+
if (result !== void 0 && result !== "pass" && result !== "fail") return { ok: false, problem: 'result: must be "pass" or "fail"' };
|
|
1606
|
+
if (items !== void 0 && !Array.isArray(items)) return { ok: false, problem: "items: must be an array" };
|
|
1607
|
+
const entries = items ?? [];
|
|
1608
|
+
if (entries.length > MAX_ANSWER_ENTRIES) return { ok: false, problem: `items: at most ${MAX_ANSWER_ENTRIES} entries` };
|
|
1609
|
+
const read = [];
|
|
1610
|
+
for (const [index, entry] of entries.entries()) {
|
|
1611
|
+
const { id, result: itemResult, reason } = typeof entry === "object" && entry !== null ? entry : {};
|
|
1612
|
+
if (typeof id !== "string" || id === "" || id.length > 200) return { ok: false, problem: `items.${index}.id: must be text of 1 to 200 characters` };
|
|
1613
|
+
if (itemResult !== "pass" && itemResult !== "fail") return { ok: false, problem: `items.${index}.result: must be "pass" or "fail"` };
|
|
1614
|
+
if (reason !== void 0 && typeof reason !== "string") return { ok: false, problem: `items.${index}.reason: must be text` };
|
|
1615
|
+
read.push({ id, result: itemResult, ...reason === void 0 ? {} : { reason } });
|
|
1616
|
+
}
|
|
1617
|
+
return { ok: true, answer: { ...result === void 0 ? {} : { result }, items: read } };
|
|
1618
|
+
}
|
|
1619
|
+
function parseJudgeAnswer(text2) {
|
|
1620
|
+
if (text2 === void 0 || text2.trim() === "") return void 0;
|
|
1621
|
+
let raw;
|
|
1622
|
+
try {
|
|
1623
|
+
raw = JSON.parse(text2);
|
|
1624
|
+
} catch {
|
|
1625
|
+
return { ok: false, reason: "the judge's items.json is not JSON" };
|
|
1626
|
+
}
|
|
1627
|
+
const read = readAnswer(raw);
|
|
1628
|
+
return read.ok ? read : { ok: false, reason: `the judge's items.json does not match its shape: ${read.problem}` };
|
|
1629
|
+
}
|
|
1630
|
+
function oneLineReason(text2) {
|
|
1631
|
+
if (text2 === void 0) return void 0;
|
|
1632
|
+
const line = text2.replace(/\s+/g, " ").trim();
|
|
1633
|
+
if (line === "") return void 0;
|
|
1634
|
+
return line.length <= CHECK_ITEM_REASON_MAX_LENGTH ? line : `${line.slice(0, CHECK_ITEM_REASON_MAX_LENGTH - 1)}\u2026`;
|
|
1635
|
+
}
|
|
1636
|
+
function judgedItems(input) {
|
|
1637
|
+
return input.toJudge.map((criterion) => {
|
|
1638
|
+
const said = input.results.get(criterion.id);
|
|
1639
|
+
const inputs = input.inputs.get(criterion.id);
|
|
1640
|
+
const refs = refsFor(criterion.id, input.citations);
|
|
1641
|
+
const reason = said === void 0 ? NO_VERDICT_REASON : oneLineReason(said.reason);
|
|
1642
|
+
return {
|
|
1643
|
+
id: criterion.id,
|
|
1644
|
+
criterion: criterion.id,
|
|
1645
|
+
result: said?.result ?? "fail",
|
|
1646
|
+
...reason === void 0 ? {} : { reason },
|
|
1647
|
+
...refs === void 0 ? {} : { refs },
|
|
1648
|
+
...inputs === void 0 || said === void 0 ? {} : { inputs }
|
|
1649
|
+
};
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function inCriteriaOrder(items, criteria) {
|
|
1653
|
+
const order = new Map(criteria.map((criterion, index) => [criterion.id, index]));
|
|
1654
|
+
return [...items].sort((a, b) => (order.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.id) ?? Number.MAX_SAFE_INTEGER));
|
|
1655
|
+
}
|
|
1656
|
+
function itemsSentence(items, noun) {
|
|
1657
|
+
const failing = items.filter((item) => item.result === "fail").map((item) => item.id);
|
|
1658
|
+
if (failing.length === 0) return void 0;
|
|
1659
|
+
return `${failing.length} of ${items.length} ${items.length === 1 ? noun.one : noun.many} not satisfied: ${failing.join(", ")}`;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// src/runner/host-checks.ts
|
|
1541
1663
|
var MAX_VALIDATOR_FILES = 20;
|
|
1542
1664
|
var VALIDATORS_DIR = ".outerlayer/validators";
|
|
1543
1665
|
var POLICY_FILE = ".outerlayer/policy.yaml";
|
|
1544
1666
|
var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
|
|
1667
|
+
var MAX_CHECK_ITEMS_BYTES = 1024 * 1024;
|
|
1668
|
+
function commandItems(read, scrub) {
|
|
1669
|
+
if (read.status !== 0 || read.stdout.trim() === "") return { ok: true, items: void 0 };
|
|
1670
|
+
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` };
|
|
1671
|
+
let raw;
|
|
1672
|
+
try {
|
|
1673
|
+
raw = JSON.parse(read.stdout);
|
|
1674
|
+
} catch {
|
|
1675
|
+
return { ok: false, reason: "its check items file (OUTERLAYER_CHECK_ITEMS) is not JSON" };
|
|
1676
|
+
}
|
|
1677
|
+
const parsed = CheckItemsSchema.safeParse(raw);
|
|
1678
|
+
if (!parsed.success) {
|
|
1679
|
+
return { ok: false, reason: `its check items file does not match the check item shape: ${zodProblem(parsed.error.issues)}` };
|
|
1680
|
+
}
|
|
1681
|
+
return { ok: true, items: parsed.data.map((item) => item.reason === void 0 ? item : { ...item, reason: scrub(item.reason) }) };
|
|
1682
|
+
}
|
|
1545
1683
|
function isJudgeCheck(check) {
|
|
1546
1684
|
return "judge" in check;
|
|
1547
1685
|
}
|
|
@@ -1595,7 +1733,7 @@ function checksIn(root, log, tag) {
|
|
|
1595
1733
|
const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null);
|
|
1596
1734
|
const checks = [];
|
|
1597
1735
|
for (const def of hostChecks) {
|
|
1598
|
-
if (def.judge !== void 0) checks.push({ name: def.declaresEmit, judge: { brief: def.judge.brief, text: readBrief(root, def.judge.brief) } });
|
|
1736
|
+
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 } });
|
|
1599
1737
|
else if (def.command !== void 0) checks.push({ name: def.declaresEmit, command: def.command });
|
|
1600
1738
|
}
|
|
1601
1739
|
return checks;
|
|
@@ -1665,7 +1803,11 @@ function makeRecorder(input) {
|
|
|
1665
1803
|
"/v1/emitted-results",
|
|
1666
1804
|
config.apiKey,
|
|
1667
1805
|
config.appId,
|
|
1668
|
-
{
|
|
1806
|
+
{
|
|
1807
|
+
schemaVersion: 1,
|
|
1808
|
+
// A field left undefined is not sent: the request is JSON.
|
|
1809
|
+
emit: { clientEmitId, name, result, body, link: opts.link, items: opts.items, emittedAt: now().toISOString(), item: { number: Number(job.item) } }
|
|
1810
|
+
},
|
|
1669
1811
|
deps.fetchImpl,
|
|
1670
1812
|
void 0,
|
|
1671
1813
|
signer
|
|
@@ -1715,7 +1857,7 @@ async function runHostChecks(input) {
|
|
|
1715
1857
|
log.line(`${tag} checks started (${checks.map((check) => check.name).join(", ")})`);
|
|
1716
1858
|
const startedMs = now().getTime();
|
|
1717
1859
|
const recorder = makeRecorder({ config, job, deps });
|
|
1718
|
-
const record = (check, result, sentence, keyTail = "") => recorder(check.name, result, sentence, { keyTail });
|
|
1860
|
+
const record = (check, result, sentence, keyTail = "", items) => recorder(check.name, result, sentence, { keyTail, items });
|
|
1719
1861
|
const failAll = async (reason, keyTail = "") => {
|
|
1720
1862
|
log.error(`${tag} checks failed: ${reason}${keyTail}`);
|
|
1721
1863
|
for (const check of checks) await record(check, "fail", reason, keyTail);
|
|
@@ -1884,7 +2026,9 @@ async function runHostChecks(input) {
|
|
|
1884
2026
|
continue;
|
|
1885
2027
|
}
|
|
1886
2028
|
const checkStartedMs = now().getTime();
|
|
1887
|
-
const
|
|
2029
|
+
const logPath = join(jobDir, "checks", `${check.name}.log`);
|
|
2030
|
+
await ready.exec(["rm", "-f", CHECK_ITEMS_FILE], CHECKOUT_DIR, { logPath });
|
|
2031
|
+
const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath });
|
|
1888
2032
|
running.catch(() => void 0);
|
|
1889
2033
|
const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
|
|
1890
2034
|
let outcome;
|
|
@@ -1904,14 +2048,25 @@ async function runHostChecks(input) {
|
|
|
1904
2048
|
restartTimer.cancel();
|
|
1905
2049
|
continue;
|
|
1906
2050
|
}
|
|
1907
|
-
|
|
2051
|
+
const read = await ready.exec(["head", "-c", String(MAX_CHECK_ITEMS_BYTES + 1), CHECK_ITEMS_FILE], CHECKOUT_DIR);
|
|
2052
|
+
const items = commandItems(read, (text2) => scrubReason(text2, config) ?? text2);
|
|
2053
|
+
if (!items.ok) {
|
|
2054
|
+
await record(check, "fail", items.reason);
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
const failing = items.items === void 0 ? void 0 : itemsSentence(items.items, { one: "check item", many: "check items" });
|
|
2058
|
+
if (outcome.status === 0 && failing === void 0) {
|
|
1908
2059
|
log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
|
|
1909
|
-
await record(check, "pass");
|
|
2060
|
+
await record(check, "pass", void 0, "", items.items);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
if (outcome.status === 0) {
|
|
2064
|
+
await record(check, "fail", failing, "", items.items);
|
|
1910
2065
|
continue;
|
|
1911
2066
|
}
|
|
1912
2067
|
const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
|
|
1913
2068
|
const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
|
|
1914
|
-
await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}
|
|
2069
|
+
await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}`, "", items.items);
|
|
1915
2070
|
}
|
|
1916
2071
|
} catch (err) {
|
|
1917
2072
|
log.error(`${tag} checks stopped: ${describeCallFailure(err)}`);
|
|
@@ -1934,9 +2089,6 @@ async function runHostChecks(input) {
|
|
|
1934
2089
|
}
|
|
1935
2090
|
|
|
1936
2091
|
// src/runner/judge-verdict.ts
|
|
1937
|
-
function mechanicalFailures(criteria, citations) {
|
|
1938
|
-
return criteria.filter((criterion) => criterion.proof === null && (citations[criterion.id]?.length ?? 0) === 0).map((criterion) => criterion.id);
|
|
1939
|
-
}
|
|
1940
2092
|
function firstLine2(text2) {
|
|
1941
2093
|
return text2.trim().split(/\r\n|\r|\n/)[0]?.trim() ?? "";
|
|
1942
2094
|
}
|
|
@@ -1952,26 +2104,68 @@ function parseOutput(stdout) {
|
|
|
1952
2104
|
}
|
|
1953
2105
|
var RESULT_LINE = /^result:\s*(pass|fail)\s*$/i;
|
|
1954
2106
|
var UNMET_LINE = /^unmet:\s*(\S+)\s*$/i;
|
|
1955
|
-
function
|
|
1956
|
-
|
|
1957
|
-
if (run.kind === "timeout") return { result: "fail", reason: `the judge ran out of time after ${input.minutes}`, limitKey: true };
|
|
2107
|
+
function readJudgeReply(run, minutes) {
|
|
2108
|
+
if (run.kind === "timeout") return { kind: "none", reason: `the judge ran out of time after ${minutes}`, limitKey: true };
|
|
1958
2109
|
const output = parseOutput(run.stdout);
|
|
1959
2110
|
if (output === void 0) {
|
|
1960
2111
|
const said = firstLine2(run.stderr);
|
|
1961
|
-
return {
|
|
2112
|
+
return { kind: "none", reason: `the judge exited ${run.status} and printed no result${said === "" ? "" : `: ${said}`}` };
|
|
1962
2113
|
}
|
|
1963
|
-
if (output.subtype === "error_max_turns") return {
|
|
2114
|
+
if (output.subtype === "error_max_turns") return { kind: "none", reason: `the judge reached the turn limit of ${JUDGE_MAX_TURNS} turns before it gave a result` };
|
|
1964
2115
|
const lines = output.result.split(/\r\n|\r|\n/).map((line) => line.trim()).filter((line) => line !== "");
|
|
1965
2116
|
const verdict = RESULT_LINE.exec(lines.at(-1) ?? "")?.[1]?.toLowerCase();
|
|
1966
|
-
if (verdict === void 0) return {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2117
|
+
if (verdict === void 0) return { kind: "none", reason: "the judge gave no `result:` line" };
|
|
2118
|
+
return { kind: "answered", verdict, unmet: new Set(lines.flatMap((line) => UNMET_LINE.exec(line)?.[1] ?? [])) };
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// src/runner/judge-report.ts
|
|
2122
|
+
function escape(text2) {
|
|
2123
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2124
|
+
}
|
|
2125
|
+
var RESULT_LABEL = { pass: "satisfied", fail: "not satisfied", skipped: "not judged" };
|
|
2126
|
+
function refsCell(item) {
|
|
2127
|
+
return (item.refs ?? []).map((ref) => {
|
|
2128
|
+
const label = escape(ref.line === void 0 ? ref.path : `${ref.path}:${ref.line}`);
|
|
2129
|
+
return ref.href === void 0 ? `<code>${label}</code>` : `<a href="${escape(ref.href)}"><code>${label}</code></a>`;
|
|
2130
|
+
}).join("<br>");
|
|
2131
|
+
}
|
|
2132
|
+
function renderJudgeReport(input) {
|
|
2133
|
+
const count = (result) => input.items.filter((item) => item.result === result).length;
|
|
2134
|
+
const summary = `${count("pass")} satisfied, ${count("fail")} not satisfied, ${count("skipped")} not judged`;
|
|
2135
|
+
const rows = input.items.map(
|
|
2136
|
+
(item) => `<tr class="${item.result}"><td><code>${escape(item.id)}</code></td><td>${RESULT_LABEL[item.result]}</td><td>${escape(item.reason ?? "")}</td><td>${refsCell(item)}</td></tr>`
|
|
2137
|
+
).join("\n");
|
|
2138
|
+
return `<!doctype html>
|
|
2139
|
+
<html lang="en">
|
|
2140
|
+
<head>
|
|
2141
|
+
<meta charset="utf-8">
|
|
2142
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2143
|
+
<title>${escape(input.check)}</title>
|
|
2144
|
+
<style>
|
|
2145
|
+
:root { color-scheme: light dark; --fail: #b42318; --pass: #067647; --muted: #667085; }
|
|
2146
|
+
body { font: 14px/1.5 system-ui, sans-serif; margin: 24px; max-width: 1100px; }
|
|
2147
|
+
table { border-collapse: collapse; width: 100%; }
|
|
2148
|
+
th, td { text-align: left; vertical-align: top; padding: 6px 10px; border-bottom: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
|
|
2149
|
+
tr.fail td:nth-child(2) { color: var(--fail); font-weight: 600; }
|
|
2150
|
+
tr.pass td:nth-child(2) { color: var(--pass); }
|
|
2151
|
+
tr.skipped td:nth-child(2) { color: var(--muted); }
|
|
2152
|
+
.meta { color: var(--muted); }
|
|
2153
|
+
</style>
|
|
2154
|
+
${input.extraHead ?? ""}
|
|
2155
|
+
</head>
|
|
2156
|
+
<body>
|
|
2157
|
+
<h1>${escape(input.check)}: ${input.result}</h1>
|
|
2158
|
+
<p>${escape(summary)}.${input.reason === void 0 ? "" : ` ${escape(input.reason)}.`}</p>
|
|
2159
|
+
<p class="meta">Judged at commit <code>${escape(input.commit)}</code>.</p>
|
|
2160
|
+
<table>
|
|
2161
|
+
<thead><tr><th>Item</th><th>Result</th><th>Reason</th><th>Tests</th></tr></thead>
|
|
2162
|
+
<tbody>
|
|
2163
|
+
${rows}
|
|
2164
|
+
</tbody>
|
|
2165
|
+
</table>
|
|
2166
|
+
</body>
|
|
2167
|
+
</html>
|
|
2168
|
+
`;
|
|
1975
2169
|
}
|
|
1976
2170
|
|
|
1977
2171
|
// src/runner/judge.ts
|
|
@@ -2029,6 +2223,14 @@ function criteriaOf(workItem) {
|
|
|
2029
2223
|
}
|
|
2030
2224
|
return items.length === 0 ? void 0 : items;
|
|
2031
2225
|
}
|
|
2226
|
+
function hostItemsOf(workItem, name) {
|
|
2227
|
+
const checks = Object(workItem).checks;
|
|
2228
|
+
if (!Array.isArray(checks)) return [];
|
|
2229
|
+
const check = checks.find((entry) => Object(entry).name === name);
|
|
2230
|
+
if (check?.source !== "host") return [];
|
|
2231
|
+
const parsed = CheckItemsSchema.safeParse(check.items);
|
|
2232
|
+
return parsed.success ? parsed.data : [];
|
|
2233
|
+
}
|
|
2032
2234
|
function environmentFor(input) {
|
|
2033
2235
|
const { config, job, jobDir, deps } = input;
|
|
2034
2236
|
if (deps.judgeEnvironment !== void 0) return deps.judgeEnvironment;
|
|
@@ -2105,7 +2307,8 @@ async function runJudge(input) {
|
|
|
2105
2307
|
if (environment === void 0) return await fail(check, `${NO_CLAUDE}: this build ran outside a container or microVM`);
|
|
2106
2308
|
const expected = (job.container ?? job.microvm)?.judgeProgram;
|
|
2107
2309
|
if (expected === void 0) return await fail(check, NO_CLAUDE);
|
|
2108
|
-
|
|
2310
|
+
const brief = check.judge.text;
|
|
2311
|
+
if (brief === void 0) return await fail(check, `the brief ${check.judge.brief} is not in the definitions repository`);
|
|
2109
2312
|
if (tokens === void 0) return await fail(check, "the build's repository tokens are no longer held, so the branch cannot be fetched");
|
|
2110
2313
|
const credential = claudeCredential(runtime.hostEnv);
|
|
2111
2314
|
if (credential === void 0) return await fail(check, "the host holds no Claude credential for the judge");
|
|
@@ -2125,17 +2328,34 @@ async function runJudge(input) {
|
|
|
2125
2328
|
if (read.kind === "unreadable") return await fail(check, `the branch ${branch} could not be read: ${read.reason}`);
|
|
2126
2329
|
const head = read.sha;
|
|
2127
2330
|
let criteria;
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2331
|
+
let previous = [];
|
|
2332
|
+
if (check.judge.subjects === "criteria") {
|
|
2333
|
+
let workItem;
|
|
2334
|
+
try {
|
|
2335
|
+
workItem = await readWorkItem(config, job.item, deps.fetchImpl);
|
|
2336
|
+
} catch (err) {
|
|
2337
|
+
return await fail(check, `the item's criteria could not be read: ${describeCallFailure(err)}`);
|
|
2338
|
+
}
|
|
2339
|
+
criteria = criteriaOf(workItem);
|
|
2340
|
+
if (criteria === void 0) return await fail(check, "no criteria are recorded for the item");
|
|
2341
|
+
previous = hostItemsOf(workItem, check.name);
|
|
2132
2342
|
}
|
|
2133
|
-
if (criteria === void 0) return await fail(check, "no criteria are recorded for the item");
|
|
2134
2343
|
const branchDir = join(scratch, "branch");
|
|
2135
2344
|
const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: branchDir, commit: head, token: readToken });
|
|
2136
2345
|
if (!fetched.ok) return await fail(check, `the build's tests could not be fetched: ${fetched.reason}`);
|
|
2137
2346
|
const finder = await loadCitationFinder();
|
|
2138
|
-
const
|
|
2347
|
+
const list = criteria ?? [];
|
|
2348
|
+
const testFiles = testFilesIn(branchDir, finder.TEST_FILE);
|
|
2349
|
+
const citations = finder.buildCitations({ ids: list.map((criterion) => criterion.id), files: testFiles, repository: repo, sha: head });
|
|
2350
|
+
const testFileText = new Map(testFiles.map((file) => [file.path, file.contents]));
|
|
2351
|
+
const inputs = new Map(list.map((criterion) => [criterion.id, criterionInputs({ brief, criterion, citations, testFiles: testFileText })]));
|
|
2352
|
+
const plan = planCriteria({ criteria: list, citations, inputs, previous });
|
|
2353
|
+
if (plan.toJudge.length < list.length) {
|
|
2354
|
+
log.line(`${tag} judge: ${plan.toJudge.length} of ${list.length} criteria to judge, ${list.length - plan.toJudge.length} settled without the judge`);
|
|
2355
|
+
}
|
|
2356
|
+
if (criteria !== void 0 && plan.toJudge.length === 0) {
|
|
2357
|
+
return await finish(check, { criteria, items: plan.settled, citations, head, branch, finder, judgeReport: void 0 });
|
|
2358
|
+
}
|
|
2139
2359
|
let diff = "";
|
|
2140
2360
|
const startCommit = (job.container ?? job.microvm)?.startCommit;
|
|
2141
2361
|
if (startCommit !== void 0) {
|
|
@@ -2147,19 +2367,26 @@ async function runJudge(input) {
|
|
|
2147
2367
|
[the diff was cut at ${MAX_DIFF_BYTES} bytes]
|
|
2148
2368
|
` : bytes.toString("utf8");
|
|
2149
2369
|
}
|
|
2150
|
-
await environment.prepare(
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2370
|
+
await environment.prepare(
|
|
2371
|
+
{
|
|
2372
|
+
...criteria === void 0 ? {} : {
|
|
2373
|
+
// Only the criteria the judge is asked about. The others are settled.
|
|
2374
|
+
"criteria.json": `${JSON.stringify({ criteria: plan.toJudge }, null, 2)}
|
|
2154
2375
|
`,
|
|
2155
|
-
|
|
2156
|
-
|
|
2376
|
+
"citations.json": `${JSON.stringify(citations, null, 2)}
|
|
2377
|
+
`
|
|
2378
|
+
},
|
|
2379
|
+
"diff.patch": diff
|
|
2380
|
+
},
|
|
2381
|
+
branchDir
|
|
2382
|
+
);
|
|
2157
2383
|
const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
|
|
2158
2384
|
const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
|
|
2159
2385
|
const clear = async () => {
|
|
2160
2386
|
await environment.clear?.().catch((err) => log.error(`${tag} what the judge left in its folder could not be removed: ${describeCallFailure(err)}`));
|
|
2161
2387
|
};
|
|
2162
|
-
const
|
|
2388
|
+
const prompt = withAnswerProtocol(brief, check.judge.subjects);
|
|
2389
|
+
const running = environment.run(judgeCommand({ brief: prompt, credential }), { logPath: join(jobDir, "judge.log") });
|
|
2163
2390
|
running.catch(() => void 0);
|
|
2164
2391
|
const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
|
|
2165
2392
|
let outcome;
|
|
@@ -2172,27 +2399,86 @@ async function runJudge(input) {
|
|
|
2172
2399
|
timer.cancel();
|
|
2173
2400
|
}
|
|
2174
2401
|
let report;
|
|
2175
|
-
|
|
2402
|
+
let answerText;
|
|
2403
|
+
const readOutputs = async () => {
|
|
2176
2404
|
try {
|
|
2177
2405
|
report = await environment.readReport();
|
|
2178
2406
|
} catch (err) {
|
|
2179
2407
|
log.error(`${tag} the judge's report could not be read: ${describeCallFailure(err)}`);
|
|
2180
2408
|
}
|
|
2409
|
+
try {
|
|
2410
|
+
answerText = await environment.readItems();
|
|
2411
|
+
} catch (err) {
|
|
2412
|
+
log.error(`${tag} the judge's items could not be read: ${describeCallFailure(err)}`);
|
|
2413
|
+
}
|
|
2181
2414
|
};
|
|
2182
2415
|
if (outcome === "timeout") {
|
|
2183
|
-
await
|
|
2416
|
+
await readOutputs();
|
|
2184
2417
|
await environment.stop().catch((err) => log.error(`${tag} the judge could not be stopped after its time ran out: ${describeCallFailure(err)}`));
|
|
2418
|
+
} else {
|
|
2419
|
+
await readOutputs();
|
|
2185
2420
|
}
|
|
2186
|
-
const decision = decideVerdict({
|
|
2187
|
-
run: outcome === "timeout" ? { kind: "timeout" } : { kind: "exited", status: outcome.status, stdout: outcome.stdout, stderr: outcome.stderr },
|
|
2188
|
-
criteria,
|
|
2189
|
-
citations,
|
|
2190
|
-
minutes
|
|
2191
|
-
});
|
|
2192
|
-
if (outcome !== "timeout") await readReport2();
|
|
2193
2421
|
await clear();
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2422
|
+
const run = outcome === "timeout" ? { kind: "timeout" } : { kind: "exited", status: outcome.status, stdout: outcome.stdout, stderr: outcome.stderr };
|
|
2423
|
+
const reply = readJudgeReply(run, minutes);
|
|
2424
|
+
const answer = parseJudgeAnswer(answerText);
|
|
2425
|
+
if (criteria === void 0) return await finishWhole(check, { reply, answer, report, head, branch });
|
|
2426
|
+
const judged = (results2) => [
|
|
2427
|
+
...plan.settled,
|
|
2428
|
+
...judgedItems({ toJudge: plan.toJudge, results: results2, citations, inputs })
|
|
2429
|
+
];
|
|
2430
|
+
if (answer?.ok === true) {
|
|
2431
|
+
const results2 = new Map(answer.answer.items.map((entry) => [entry.id, entry]));
|
|
2432
|
+
return await finish(check, { criteria, items: judged(results2), citations, head, branch, finder, judgeReport: void 0 });
|
|
2433
|
+
}
|
|
2434
|
+
if (reply.kind === "none") {
|
|
2435
|
+
return await finish(check, { criteria, items: judged(/* @__PURE__ */ new Map()), citations, head, branch, finder, judgeReport: report, reason: reply.reason, limitKey: reply.limitKey === true });
|
|
2436
|
+
}
|
|
2437
|
+
if (answer !== void 0) {
|
|
2438
|
+
return await finish(check, { criteria, items: judged(/* @__PURE__ */ new Map()), citations, head, branch, finder, judgeReport: report, reason: answer.reason });
|
|
2439
|
+
}
|
|
2440
|
+
const failsAll = reply.verdict === "fail" && reply.unmet.size === 0;
|
|
2441
|
+
const results = new Map(plan.toJudge.map((criterion) => [criterion.id, { result: failsAll || reply.unmet.has(criterion.id) ? "fail" : "pass" }]));
|
|
2442
|
+
return await finish(check, { criteria, items: judged(results), citations, head, branch, finder, judgeReport: report });
|
|
2443
|
+
}
|
|
2444
|
+
async function finish(check, at) {
|
|
2445
|
+
const items = inCriteriaOrder(at.items, at.criteria);
|
|
2446
|
+
const failing = itemsSentence(items, { one: "criterion", many: "criteria" });
|
|
2447
|
+
const result = failing === void 0 ? "pass" : "fail";
|
|
2448
|
+
const reason = at.reason ?? failing;
|
|
2449
|
+
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) });
|
|
2450
|
+
const link = await uploadReport(check, report, { branch: at.branch, head: at.head });
|
|
2451
|
+
await record(check.name, result, reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL2 : "", link, items: sendable(items) });
|
|
2452
|
+
}
|
|
2453
|
+
async function finishWhole(check, at) {
|
|
2454
|
+
const where = { head: at.head, branch: at.branch };
|
|
2455
|
+
if (at.answer?.ok === true) {
|
|
2456
|
+
const items = at.answer.answer.items.map((entry) => {
|
|
2457
|
+
const reason2 = oneLineReason(entry.reason);
|
|
2458
|
+
return { id: entry.id, result: entry.result, ...reason2 === void 0 ? {} : { reason: reason2 } };
|
|
2459
|
+
});
|
|
2460
|
+
const failing = itemsSentence(items, { one: "check item", many: "check items" });
|
|
2461
|
+
const said = at.answer.answer.result ?? (at.reply.kind === "answered" ? at.reply.verdict : void 0);
|
|
2462
|
+
const result = said === "pass" && failing === void 0 ? "pass" : "fail";
|
|
2463
|
+
const reason = failing ?? (said === "fail" ? "the judge failed the change" : said === void 0 ? at.reply.kind === "none" ? at.reply.reason : "the judge gave no result" : void 0);
|
|
2464
|
+
const report = renderJudgeReport({ check: check.name, result, reason, items, commit: at.head });
|
|
2465
|
+
return await recordWhole(check, { ...where, result, reason, items, report });
|
|
2466
|
+
}
|
|
2467
|
+
if (at.reply.kind === "none") return await recordWhole(check, { ...where, result: "fail", reason: at.reply.reason, report: at.report, limitKey: at.reply.limitKey === true });
|
|
2468
|
+
if (at.answer !== void 0) return await recordWhole(check, { ...where, result: "fail", reason: at.answer.reason, report: at.report });
|
|
2469
|
+
const failed = at.reply.verdict === "fail";
|
|
2470
|
+
return await recordWhole(check, { ...where, result: at.reply.verdict, reason: failed ? "the judge failed the change" : void 0, report: at.report });
|
|
2471
|
+
}
|
|
2472
|
+
async function recordWhole(check, at) {
|
|
2473
|
+
const link = at.report === void 0 ? void 0 : await uploadReport(check, at.report, { branch: at.branch, head: at.head });
|
|
2474
|
+
await record(check.name, at.result, at.reason, { keyTail: at.limitKey === true ? LIMIT_KEY_TAIL2 : "", link, items: sendable(at.items) });
|
|
2475
|
+
}
|
|
2476
|
+
function sendable(items) {
|
|
2477
|
+
if (items === void 0) return void 0;
|
|
2478
|
+
const parsed = CheckItemsSchema.safeParse(items);
|
|
2479
|
+
if (parsed.success) return parsed.data;
|
|
2480
|
+
log.error(`${tag} the judge's check items were left off the result: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
2481
|
+
return void 0;
|
|
2196
2482
|
}
|
|
2197
2483
|
async function uploadReport(check, report, at) {
|
|
2198
2484
|
const bytes = Buffer.from(report, "utf8");
|