@outerlayer/cli 0.6.0 → 0.7.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -0
  3. package/dist/{agent-setup-USZ2SAXC.js → agent-setup-TXLDY5RV.js} +7 -6
  4. package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-CCVPBN7A.js → check-ASZL5BEO.js} +29 -23
  7. package/dist/{chunk-ZILGTTY6.js → chunk-2VO25HHO.js} +3 -3
  8. package/dist/{chunk-OIRNH6UO.js → chunk-5FWNEQNZ.js} +3 -3
  9. package/dist/{chunk-MXB5VB4Y.js → chunk-6IYLDODX.js} +6 -6
  10. package/dist/{chunk-4NLKY4WR.js → chunk-6QWV3YDJ.js} +2 -2
  11. package/dist/{chunk-UMFWVGJO.js → chunk-6ZZHWY2Z.js} +4 -3
  12. package/dist/{chunk-NQXFAGHU.js → chunk-7FTNIAHS.js} +1 -1
  13. package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
  14. package/dist/{chunk-43J3M6KB.js → chunk-AII7CKTH.js} +45 -31
  15. package/dist/{chunk-VMJ3MMBX.js → chunk-AJKEHABI.js} +312 -18
  16. package/dist/{chunk-RBOMWNOG.js → chunk-AQ54MKSQ.js} +2 -2
  17. package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
  18. package/dist/{chunk-ANK2B36Y.js → chunk-D6FXM5OW.js} +6182 -28607
  19. package/dist/{chunk-BNMCXPG7.js → chunk-DGAIVMMN.js} +12 -3
  20. package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
  21. package/dist/{chunk-RZSF5B46.js → chunk-E4PIFIC3.js} +100 -15
  22. package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
  23. package/dist/chunk-HKMPVPO7.js +55 -0
  24. package/dist/{chunk-FA2BOX4O.js → chunk-JPR2WHHF.js} +1 -1
  25. package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
  26. package/dist/{chunk-MIGEDH5P.js → chunk-MQPRB5K7.js} +9 -9
  27. package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
  28. package/dist/chunk-PUN7LMNS.js +39 -0
  29. package/dist/{chunk-JWOQNNN2.js → chunk-Q5G2ODUZ.js} +1 -1
  30. package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
  31. package/dist/{chunk-PMAMOSTP.js → chunk-SSU747O6.js} +85 -25
  32. package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
  33. package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
  34. package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
  35. package/dist/{chunk-IASWYRVG.js → chunk-X2FSFU4C.js} +4 -1
  36. package/dist/chunk-YNJEOGDL.js +22434 -0
  37. package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
  38. package/dist/{chunk-3DEVYTNQ.js → chunk-ZYHKJKSU.js} +1 -1
  39. package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
  40. package/dist/{cli-7WCNPBYC.js → cli-LEVCGAQV.js} +73 -71
  41. package/dist/{command-ARFK3OYF.js → command-FLSYCGIB.js} +47 -22
  42. package/dist/config-I3TVFYLH.js +10 -0
  43. package/dist/{connect-cmd-PCESZLTR.js → connect-cmd-MJMUONDY.js} +4 -4
  44. package/dist/{context-adopt-G5JAELE7.js → context-adopt-CE4KDXS3.js} +5 -4
  45. package/dist/{context-materialize-3HLPZAF6.js → context-materialize-QL33HDJN.js} +5 -4
  46. package/dist/{dist-RDLP4GGJ.js → dist-PXBSA7QX.js} +2 -1
  47. package/dist/{doctor-VOMYOHQU.js → doctor-FVDT3KJZ.js} +23 -16
  48. package/dist/{emit-artifact-cmd-TBF46VXZ.js → emit-artifact-cmd-7RPMRSVP.js} +11 -4
  49. package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
  50. package/dist/{emit-criteria-cmd-3427XKFW.js → emit-criteria-cmd-MKI7WETO.js} +10 -1
  51. package/dist/{emit-finding-cmd-GSI75K36.js → emit-finding-cmd-RMLGZASB.js} +12 -1
  52. package/dist/{emit-result-cmd-3PRQKB55.js → emit-result-cmd-MTXAZ2GV.js} +13 -6
  53. package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
  54. package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
  55. package/dist/{hook-fast-NJBBZQ5Q.js → hook-fast-XBOZ3N6L.js} +1 -1
  56. package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
  57. package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
  58. package/dist/index.js +3 -3
  59. package/dist/{init-WVR4BZQ4.js → init-BACXESVV.js} +21 -14
  60. package/dist/{init-OHYFIKZD.js → init-VLW7H6RW.js} +22 -15
  61. package/dist/{init-cmd-HMFNIFPI.js → init-cmd-VJFB5L7X.js} +10 -9
  62. package/dist/{install-DSUZE2TJ.js → install-NMAPLLBQ.js} +4 -3
  63. package/dist/{install-cmd-453C7IZT.js → install-cmd-B5SEU3G7.js} +2 -2
  64. package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
  65. package/dist/{loop-UAJB34KA.js → loop-D3243JKT.js} +29 -23
  66. package/dist/{machine-2JOWGRSZ.js → machine-EB3GEVI4.js} +20 -13
  67. package/dist/{session-sender-ED26V45B.js → session-sender-LMACEJJS.js} +6 -5
  68. package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
  69. package/dist/{status-6WNXX2O7.js → status-F4THTJJK.js} +24 -18
  70. package/dist/{sync-cmd-YONVGUYS.js → sync-cmd-M6JA3OEE.js} +5 -4
  71. package/dist/{watch-HHKAF26L.js → watch-NB5TGPEZ.js} +5 -4
  72. package/dist/{work-claim-cmd-LXBZTMJA.js → work-claim-cmd-UHMWOS3G.js} +13 -6
  73. package/dist/{work-cmd-OVNF44FY.js → work-cmd-6UMCFTAW.js} +9 -3
  74. package/dist/{work-comment-cmd-6DGQNTAH.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
  75. package/dist/{work-open-pr-cmd-FCZHFHZS.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
  76. package/dist/{work-pr-cmd-QJJVHZDC.js → work-pr-cmd-IDIAFFRU.js} +9 -2
  77. package/package.json +1 -1
  78. package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
  79. package/dist/chunk-7DBI6SNT.js +0 -55
  80. package/dist/config-HL2AIR3K.js +0 -9
@@ -1,22 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-ABIWDUMS.js';
4
- import { readMarker, currentVersion, removeMarker, writeMarker, entryIn, versionDir, switchCurrent, cleanVersions, isInLayout, startedThroughCurrent, stagingDir } from './chunk-YYYXRJUW.js';
5
- import { LEASE_SECONDS, releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, provisionEnvironment, readProvisionOut, resolveHostBuild, workdirProblem, listenOnBroker, hostEnvironment, hostCommandEnvironment, execArgv, microVmCommandLine, runCommand, execCommandLine, startSender, startHostServices, finishJob, isGroupAlive, outcomeFromExitCode, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, ALREADY_RELEASED } from './chunk-VMJ3MMBX.js';
4
+ import { readMarker, currentVersion, removeMarker, writeMarker, entryIn, versionDir, switchCurrent, cleanVersions, isInLayout, startedThroughCurrent, stagingDir } from './chunk-TI7B7UEO.js';
5
+ import { LEASE_SECONDS, releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, provisionEnvironment, readProvisionOut, resolveHostBuild, workdirProblem, listenOnBroker, hostEnvironment, hostCommandEnvironment, execArgv, microVmCommandLine, runCommand, execCommandLine, startSender, startHostServices, finishJob, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, ALREADY_RELEASED } from './chunk-AJKEHABI.js';
6
6
  import { writeJobFile, readJobFile, readJobFileChecked } from './chunk-T6OIJQRT.js';
7
7
  import { RUNNER_PROTOCOL } from './chunk-AIW6GLET.js';
8
8
  import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-UGP76IZ5.js';
9
- import { placeholderQueues, placeholderProblem } from './chunk-MXB5VB4Y.js';
9
+ import { placeholderQueues, placeholderProblem } from './chunk-6IYLDODX.js';
10
10
  import { freeBytes, formatBytes } from './chunk-YYYRBYWG.js';
11
11
  import { RepoFilter } from './chunk-TBT347UY.js';
12
12
  import { checkFit } from './chunk-OUUINEYB.js';
13
- import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, provisionContainer, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandLine, BuildTokens, classifyRefusal, refusalCode, formatClock, measureDisk, createRunnerLog, setUpRunnerCgroup, ensureTunnel, keepGhConfigCurrent, createContainerRuntime, runnerLabelValue, BUILD_LABEL, RUNNER_LABEL } from './chunk-RZSF5B46.js';
13
+ import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, provisionContainer, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandLine, BuildTokens, classifyRefusal, refusalCode, measureDisk, createRunnerLog, setUpRunnerCgroup, ensureTunnel, keepGhConfigCurrent, createContainerRuntime, formatClock, runnerLabelValue, BUILD_LABEL, RUNNER_LABEL } from './chunk-E4PIFIC3.js';
14
14
  import { startTimeOf, claimPidFile, readPidFile, isSameProcess, releasePidFile } from './chunk-N5FOE5PS.js';
15
15
  import { spawnRun } from './chunk-774HEQL6.js';
16
- import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-BNMCXPG7.js';
17
- import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind } from './chunk-3DD6AOWC.js';
16
+ import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-DGAIVMMN.js';
17
+ import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind } from './chunk-DTXOJDEH.js';
18
18
  import { exceedsDisk } from './chunk-F6GFZXZ3.js';
19
- import { parseClaimIdentity, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-AUFG23AA.js';
19
+ import { sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-OD2VTIHN.js';
20
+ import { parseClaimIdentity } from './chunk-HKMPVPO7.js';
20
21
  import { resolveVersion } from './chunk-RA3O54FC.js';
21
22
  import { loadHostKey, ensureHostKey, signerFor } from './chunk-M5POMKKW.js';
22
23
  import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, jobsDir, pidFilePath, spoolDir, RUNNER_FILE_MODE, rootfsDir } from './chunk-ZM2IMMYN.js';
@@ -690,7 +691,7 @@ function selectJobDirs(entries, nowMs, keepDays) {
690
691
  function liveBuildIds(jobs) {
691
692
  const ids = /* @__PURE__ */ new Set();
692
693
  for (const job of jobs) {
693
- for (const id of [job.container?.buildId, job.microvm?.buildId, job.build?.buildId]) if (id !== void 0) ids.add(id);
694
+ for (const id of [job.container?.buildId, job.microvm?.buildId, job.build?.buildId, job.checkContainer?.buildId]) if (id !== void 0) ids.add(id);
694
695
  }
695
696
  return ids;
696
697
  }
@@ -1267,7 +1268,7 @@ var CLAIM_REFUSAL_CODES = new Set(
1267
1268
  "pull_request_from_fork pull_request_on_default_branch pull_request_ambiguous branch_claimed_elsewhere".split(" ")
1268
1269
  );
1269
1270
  async function tryClaimAndStart(state, item, queue) {
1270
- const { postJson: postJson2 } = await import('./work-cmd-OVNF44FY.js');
1271
+ const { postJson: postJson2 } = await import('./work-cmd-6UMCFTAW.js');
1271
1272
  const log = logOf(state);
1272
1273
  const hostKey = await loadHostKey(state.config.configPath);
1273
1274
  const claim = await retrySend(
@@ -1395,6 +1396,7 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity)
1395
1396
  itemKey,
1396
1397
  branch,
1397
1398
  commitIdentity: identity,
1399
+ ...item.specRepository === void 0 || item.specRepository === null || item.specRepository === "" ? {} : { repository: item.specRepository },
1398
1400
  startedAt: startedAt.toISOString()
1399
1401
  };
1400
1402
  writeJobFile(jobFilePath, job);
@@ -1864,8 +1866,39 @@ async function endJob(state, active, outcome, ending) {
1864
1866
  state.jobs.delete(active.dirPath);
1865
1867
  }
1866
1868
  }
1869
+ async function renewLeaseIfDue(state, active, nowMs) {
1870
+ const log = logOf(state);
1871
+ const tag = `#${active.job.item}`;
1872
+ if (!needsRenew(active.lastRenewAtMs, nowMs)) return true;
1873
+ const renew = await retrySend(() => renewLease(state.config, active.job.item, state.deps.fetchImpl), {
1874
+ now: state.deps.now,
1875
+ sleep: state.deps.sleep
1876
+ });
1877
+ const result = renew.ok ? renew.value : "retry_later";
1878
+ if (result === "lease_lost") {
1879
+ if (active.ending) {
1880
+ log.error(`${tag} lease lost while the checks ran`);
1881
+ return false;
1882
+ }
1883
+ log.error(`${tag} lease lost \xB7 ending job as lease_lost`);
1884
+ await endJob(state, active, "lease_lost");
1885
+ return false;
1886
+ }
1887
+ if (result === "renewed") {
1888
+ active.job.lastRenewAt = new Date(nowMs).toISOString();
1889
+ writeJobFile(join(active.dirPath, "job.json"), active.job);
1890
+ log.line(`${tag} lease renewed \xB7 expires ${formatClock(new Date(nowMs + LEASE_SECONDS * 1e3), new Date(nowMs))}`);
1891
+ } else {
1892
+ log.error(`${tag} renew did not land \xB7 retrying in ${RENEW_INTERVAL_SECONDS / 60}m`);
1893
+ }
1894
+ active.lastRenewAtMs = nowMs;
1895
+ return true;
1896
+ }
1867
1897
  async function watchTick(state, active, nowMs, logMtimeMs) {
1868
- if (active.ending) return;
1898
+ if (active.ending) {
1899
+ if (active.job.stage === "checks") await renewLeaseIfDue(state, active, nowMs);
1900
+ return;
1901
+ }
1869
1902
  const log = logOf(state);
1870
1903
  const tag = `#${active.job.item}`;
1871
1904
  const pid = active.job.pid;
@@ -1883,26 +1916,7 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
1883
1916
  }
1884
1917
  return;
1885
1918
  }
1886
- if (needsRenew(active.lastRenewAtMs, nowMs)) {
1887
- const renew = await retrySend(() => renewLease(state.config, active.job.item, state.deps.fetchImpl), {
1888
- now: state.deps.now,
1889
- sleep: state.deps.sleep
1890
- });
1891
- const result = renew.ok ? renew.value : "retry_later";
1892
- if (result === "lease_lost") {
1893
- log.error(`${tag} lease lost \xB7 ending job as lease_lost`);
1894
- await endJob(state, active, "lease_lost");
1895
- return;
1896
- }
1897
- if (result === "renewed") {
1898
- active.job.lastRenewAt = new Date(nowMs).toISOString();
1899
- writeJobFile(join(active.dirPath, "job.json"), active.job);
1900
- log.line(`${tag} lease renewed \xB7 expires ${formatClock(new Date(nowMs + LEASE_SECONDS * 1e3), new Date(nowMs))}`);
1901
- } else {
1902
- log.error(`${tag} renew did not land \xB7 retrying in ${RENEW_INTERVAL_SECONDS / 60}m`);
1903
- }
1904
- active.lastRenewAtMs = nowMs;
1905
- }
1919
+ if (!await renewLeaseIfDue(state, active, nowMs)) return;
1906
1920
  const tokens = active.job.container === void 0 && active.job.microvm === void 0 ? void 0 : runtimeOf(state).credentials.get(active.dirPath);
1907
1921
  if (tokens !== void 0) {
1908
1922
  await tokens.renewReadIfDue();
@@ -1978,7 +1992,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
1978
1992
  const outcome = await onStartUnderMarker({
1979
1993
  configPath,
1980
1994
  own: ownVersionOf(deps),
1981
- check: update.check ? () => update.check(configPath) : async () => (await import('./check-CCVPBN7A.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
1995
+ check: update.check ? () => update.check(configPath) : async () => (await import('./check-ASZL5BEO.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
1982
1996
  now: () => (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime(),
1983
1997
  log
1984
1998
  });
@@ -1,30 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { writeJobFile, readJobFile } from './chunk-T6OIJQRT.js';
4
- import { senderPidfile } from './chunk-ZILGTTY6.js';
5
- import { DOCKERD_LOG } from './chunk-CBPPJSAR.js';
6
- import { installScrubContext } from './chunk-UMFWVGJO.js';
7
- import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, senderSignalScript } from './chunk-RZSF5B46.js';
8
- import { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
4
+ import { senderPidfile } from './chunk-2VO25HHO.js';
5
+ import { DOCKERD_LOG } from './chunk-WVGSB3Z2.js';
6
+ import { installScrubContext } from './chunk-6ZZHWY2Z.js';
7
+ import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, syncInContainer, formatDuration, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, startCheckContainer, lifecycleSteps, lifecycleFailureReason, deleteBuildDisk, senderSignalScript } from './chunk-E4PIFIC3.js';
8
+ import { CLAUDE_CODE_VERSION, readBranchHead, fetchCommit } from './chunk-CEBIA5FD.js';
9
9
  import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
10
10
  import { childEnvironment } from './chunk-774HEQL6.js';
11
- import { waitForAgent, vmExecArgs } from './chunk-W4SQCOZU.js';
12
- import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-B2G7JHHB.js';
13
- import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-BNMCXPG7.js';
14
- import { planArtifactUpload } from './chunk-3DEVYTNQ.js';
15
- import { scrubText } from './chunk-ANK2B36Y.js';
16
- import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
11
+ import { waitForAgent, vmExecArgs } from './chunk-ZOAEPWGL.js';
12
+ import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-R7STSBLC.js';
13
+ import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-DGAIVMMN.js';
14
+ import { planArtifactUpload } from './chunk-ZYHKJKSU.js';
15
+ import { scrubText } from './chunk-D6FXM5OW.js';
16
+ import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-DTXOJDEH.js';
17
17
  import { parseSize } from './chunk-F6GFZXZ3.js';
18
- import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT } from './chunk-AUFG23AA.js';
18
+ import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, withTunnelRecord, RELAY_PORT } from './chunk-OD2VTIHN.js';
19
+ import { withHooksPath, authorEnvironment, commitIdentityRecord } from './chunk-HKMPVPO7.js';
20
+ import { parsePolicy } from './chunk-7ZR5UPMB.js';
19
21
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
20
22
  import { resolveVersion } from './chunk-RA3O54FC.js';
21
23
  import { signerFor } from './chunk-M5POMKKW.js';
22
24
  import { runnerDir, buildDir, vsockPath, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
23
25
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
24
26
  import { spawn, spawnSync } from 'child_process';
25
- import { randomBytes } from 'crypto';
26
- import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync } from 'fs';
27
- import { join, isAbsolute, dirname, delimiter } from 'path';
27
+ import { randomBytes, createHash } from 'crypto';
28
+ import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync, lstatSync, realpathSync } from 'fs';
29
+ import { join, isAbsolute, dirname, delimiter, sep } from 'path';
28
30
 
29
31
  createRequire(import.meta.url);
30
32
  var DEFAULT_MEMORY = "8g";
@@ -143,12 +145,20 @@ async function provisionMicroVm(input) {
143
145
  };
144
146
  input.save(state, environment);
145
147
  const bail = async (reason, aborted = false) => {
148
+ let why = reason;
149
+ if (!aborted && state.cgroup !== void 0) {
150
+ try {
151
+ if (runtime.vm.cgroup.oomKills(state.cgroup) > 0) why = outOfMemoryReason(config.build?.memory);
152
+ } catch (err) {
153
+ log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
154
+ }
155
+ }
146
156
  try {
147
157
  await cleanupMicroVm(runtime, state);
148
158
  } catch (err) {
149
159
  log.error(`${tag} cleanup failed: ${describeCallFailure(err)}`);
150
160
  }
151
- return aborted ? { ok: false, reason: "ended before provision finished", aborted: true } : fail(reason);
161
+ return aborted ? { ok: false, reason: "ended before provision finished", aborted: true } : fail(why);
152
162
  };
153
163
  if (input.aborted()) return bail("", true);
154
164
  let imageConfig;
@@ -717,6 +727,266 @@ async function provisionProcess(input) {
717
727
  function cleanupProcess(runtime, state) {
718
728
  return closeHostBuild(runtime, state);
719
729
  }
730
+ var MAX_VALIDATOR_FILES = 20;
731
+ var VALIDATORS_DIR = ".outerlayer/validators";
732
+ var POLICY_FILE = ".outerlayer/policy.yaml";
733
+ var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
734
+ function lastLine(text2) {
735
+ return text2.split(/\r\n|\r|\n/).map((line) => line.trim()).filter((line) => line !== "").at(-1);
736
+ }
737
+ function readInside(root, rel) {
738
+ const path = join(root, rel);
739
+ try {
740
+ if (!lstatSync(path).isFile()) return void 0;
741
+ const inside = realpathSync(path);
742
+ const base = realpathSync(root);
743
+ return inside.startsWith(base + sep) ? readFileSync(inside, "utf8") : void 0;
744
+ } catch {
745
+ return void 0;
746
+ }
747
+ }
748
+ function validatorFilesIn(root) {
749
+ const dir = join(root, VALIDATORS_DIR);
750
+ let names;
751
+ try {
752
+ if (!lstatSync(dir).isDirectory()) return [];
753
+ names = readdirSync(dir);
754
+ } catch {
755
+ return [];
756
+ }
757
+ const files = [];
758
+ for (const name of names.filter((entry) => /\.ya?ml$/.test(entry)).sort().slice(0, MAX_VALIDATOR_FILES)) {
759
+ const path = `${VALIDATORS_DIR}/${name}`;
760
+ const content = readInside(root, path);
761
+ if (content !== void 0) files.push({ path, content });
762
+ }
763
+ return files;
764
+ }
765
+ function checksIn(root, log, tag) {
766
+ const policy = readInside(root, POLICY_FILE);
767
+ const loaded = parsePolicy(policy === void 0 ? null : { path: POLICY_FILE, content: policy }, validatorFilesIn(root));
768
+ for (const problem of loaded.problems) log.error(`${tag} ${problem.file}: ${problem.problem}`);
769
+ const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null && def.command !== void 0);
770
+ return hostChecks.map((def) => ({ name: def.declaresEmit, command: def.command }));
771
+ }
772
+ async function readDefinitions(input, repository, scratch, token) {
773
+ const { config, job, deps } = input;
774
+ const tag = `#${job.item}`;
775
+ const stage = deps.stage ?? stageGovernedContext;
776
+ const controlPlaneUrl = (controlPlane) => deps.runtime.remoteFor?.(controlPlane) ?? `${deps.runtime.github.gitUrl}/${controlPlane}.git`;
777
+ const staged = await stage({
778
+ config,
779
+ repository,
780
+ buildId: `checks-${randomBytes(4).toString("hex")}`,
781
+ buildDir: scratch,
782
+ fetchImpl: deps.fetchImpl,
783
+ controlPlaneUrl
784
+ });
785
+ if (staged.kind === "refused") return { ok: false, reason: staged.reason };
786
+ if (staged.kind === "staged") {
787
+ try {
788
+ return { ok: true, checks: checksIn(join(staged.dir, "tree"), deps.log, tag) };
789
+ } finally {
790
+ rmSync(staged.dir, { recursive: true, force: true });
791
+ }
792
+ }
793
+ const repo = join(scratch, "repo");
794
+ const remote = deps.runtime.remoteFor?.(repository) ?? `${deps.runtime.github.gitUrl}/${repository}.git`;
795
+ const commit = job.environment?.recipe?.sourceCommit ?? await deps.runtime.readHead(remote, scratch);
796
+ if (commit === void 0) return { ok: false, reason: "the repository's default branch could not be read" };
797
+ const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: repo, commit, token });
798
+ if (!fetched.ok) return { ok: false, reason: fetched.reason };
799
+ return { ok: true, checks: checksIn(repo, deps.log, tag) };
800
+ }
801
+ function timeoutAfter(ms, sleep) {
802
+ if (sleep !== void 0) return { done: sleep(ms).then(() => "timeout"), cancel: () => void 0 };
803
+ let timer;
804
+ const done = new Promise((resolve) => {
805
+ timer = setTimeout(() => resolve("timeout"), ms);
806
+ timer.unref();
807
+ });
808
+ return { done, cancel: () => clearTimeout(timer) };
809
+ }
810
+ function noCleanEnvironment(job) {
811
+ const ran = job.microvm !== void 0 ? "builtin:vm" : job.build?.workDir !== void 0 ? "builtin:process" : "a provision hook of the host's own";
812
+ return `the host has no clean environment for checks: this build ran under ${ran}, and checks run only in a new container from a container build's image`;
813
+ }
814
+ async function runHostChecks(input) {
815
+ const { config, job, jobDir, deps } = input;
816
+ const { runtime, log } = deps;
817
+ const tag = `#${job.item}`;
818
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
819
+ const repository = job.repository ?? job.container?.repository;
820
+ if (repository === void 0) return;
821
+ const jobFilePath = join(jobDir, "job.json");
822
+ const tokens = runtime.credentials.get(jobDir);
823
+ let readToken;
824
+ try {
825
+ readToken = await tokens?.token("read");
826
+ } catch (err) {
827
+ log.error(`${tag} no read token for the checks: ${describeCallFailure(err)}`);
828
+ }
829
+ const scratch = join(jobDir, "checks", "definitions");
830
+ rmSync(scratch, { recursive: true, force: true });
831
+ mkdirSync(scratch, { recursive: true });
832
+ let definitions;
833
+ try {
834
+ definitions = await readDefinitions(input, repository, scratch, readToken);
835
+ } catch (err) {
836
+ definitions = { ok: false, reason: describeCallFailure(err) };
837
+ } finally {
838
+ removeStagedContext(scratch);
839
+ }
840
+ if (!definitions.ok) {
841
+ log.line(`${tag} checks not run: the definitions could not be read: ${definitions.reason}`);
842
+ rmSync(scratch, { recursive: true, force: true });
843
+ return;
844
+ }
845
+ const checks = definitions.checks;
846
+ if (checks.length === 0) {
847
+ rmSync(scratch, { recursive: true, force: true });
848
+ return;
849
+ }
850
+ log.line(`${tag} checks started (${checks.map((check) => check.name).join(", ")})`);
851
+ const startedMs = now().getTime();
852
+ let signer;
853
+ const record = async (check, result, sentence, keyTail = "") => {
854
+ signer ??= await signerFor(config.configPath);
855
+ const body = sentence === void 0 ? void 0 : cutReasonLine(`${scrubReason(sentence, config) ?? "check failed"}${keyTail}`);
856
+ const clientEmitId = `host-${createHash("sha256").update(`${job.claimId}:${job.startedAt}:${check.name}`).digest("hex").slice(0, 40)}`;
857
+ const sent = await retrySend(
858
+ () => postJson(
859
+ config.url,
860
+ "/v1/emitted-results",
861
+ config.apiKey,
862
+ config.appId,
863
+ { schemaVersion: 1, emit: { clientEmitId, name: check.name, result, ...body === void 0 ? {} : { body }, emittedAt: now().toISOString(), item: { number: Number(job.item) } } },
864
+ deps.fetchImpl,
865
+ void 0,
866
+ signer
867
+ ),
868
+ { now: deps.now }
869
+ );
870
+ if (sent.ok) log.line(`${tag} check ${check.name} ${result}${body === void 0 ? "" : `: ${body}`}`);
871
+ else log.error(`${tag} check ${check.name} ${result} could not be recorded: ${sent.reason}`);
872
+ };
873
+ const failAll = async (reason, keyTail = "") => {
874
+ log.error(`${tag} checks failed: ${reason}${keyTail}`);
875
+ for (const check of checks) await record(check, "fail", reason, keyTail);
876
+ };
877
+ let container;
878
+ try {
879
+ if (job.container === void 0) return await failAll(noCleanEnvironment(job));
880
+ if (tokens === void 0) return await failAll("the build's repository tokens are no longer held, so the branch cannot be fetched");
881
+ const branch = job.branch;
882
+ if (branch === void 0) return await failAll("the build pushed no branch: the claim names none");
883
+ const remote = runtime.remoteFor?.(repository) ?? `${runtime.github.gitUrl}/${repository}.git`;
884
+ const read = await (deps.readBranch ?? readBranchHead)({ remote, dir: scratch, branch, token: readToken });
885
+ if (read.kind === "absent") return await failAll(`the build pushed no branch: ${branch} does not exist on ${repository}`);
886
+ if (read.kind === "unreadable") return await failAll(`the branch ${branch} could not be read: ${read.reason}`);
887
+ const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
888
+ const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
889
+ const environmentLog = join(jobDir, "checks", "environment.log");
890
+ const setup = async () => {
891
+ const started = await startCheckContainer({
892
+ config,
893
+ runtime,
894
+ job,
895
+ jobDir,
896
+ tokens,
897
+ log,
898
+ save: (state) => {
899
+ job.checkContainer = state;
900
+ writeJobFile(jobFilePath, job);
901
+ }
902
+ });
903
+ if (!started.ok) return `the check environment could not be made: ${started.reason}`;
904
+ container = started.container;
905
+ const made = started.container;
906
+ const steps = [
907
+ {
908
+ argv: ["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"],
909
+ workdir: "/build/work",
910
+ failed: (status) => `configuring git failed (exit ${status})`
911
+ },
912
+ {
913
+ argv: ["git", "clone", "--filter=blob:none", "--single-branch", "--branch", branch, `https://github.com/${repository}.git`, CHECKOUT_DIR],
914
+ workdir: "/build/work",
915
+ failed: (status) => `the clone of ${branch} failed (exit ${status})`
916
+ }
917
+ ];
918
+ for (const step of steps) {
919
+ const ran = await made.exec(step.argv, step.workdir, { logPath: environmentLog });
920
+ if (ran.status !== 0) return `the check environment could not be prepared: ${step.failed(ran.status)}`;
921
+ }
922
+ for (const step of lifecycleSteps(job.container?.lifecycle ?? {})) {
923
+ log.line(`${tag} checks lifecycle ${step.name} (${step.source})`);
924
+ const ran = await made.exec(step.argv, CHECKOUT_DIR, { logPath: environmentLog });
925
+ if (ran.status !== 0) return `the check environment could not be prepared: ${lifecycleFailureReason(step, ran.status)}`;
926
+ }
927
+ return made;
928
+ };
929
+ const setupTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
930
+ let settled;
931
+ try {
932
+ const outcome = await Promise.race([setup(), setupTimer.done]);
933
+ if (outcome === "timeout") return await failAll(`the check environment setup ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
934
+ settled = outcome;
935
+ } finally {
936
+ setupTimer.cancel();
937
+ }
938
+ if (typeof settled === "string") return await failAll(settled);
939
+ const ready = settled;
940
+ let usable = true;
941
+ for (const check of checks) {
942
+ if (!usable) {
943
+ await record(check, "fail", "the check environment could not be restarted after an earlier check ran out of time");
944
+ continue;
945
+ }
946
+ const checkStartedMs = now().getTime();
947
+ const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath: join(jobDir, "checks", `${check.name}.log`) });
948
+ running.catch(() => void 0);
949
+ const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
950
+ let outcome;
951
+ try {
952
+ outcome = await Promise.race([running, timer.done]);
953
+ } catch (err) {
954
+ timer.cancel();
955
+ await record(check, "fail", `the command could not be run: ${describeCallFailure(err)}`);
956
+ continue;
957
+ }
958
+ timer.cancel();
959
+ if (outcome === "timeout") {
960
+ await record(check, "fail", `ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
961
+ const restarting = ready.restart().catch(() => false);
962
+ const restartTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
963
+ usable = await Promise.race([restarting, restartTimer.done]) === true;
964
+ restartTimer.cancel();
965
+ continue;
966
+ }
967
+ if (outcome.status === 0) {
968
+ log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
969
+ await record(check, "pass");
970
+ continue;
971
+ }
972
+ const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
973
+ const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
974
+ await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}`);
975
+ }
976
+ } catch (err) {
977
+ log.error(`${tag} checks stopped: ${describeCallFailure(err)}`);
978
+ } finally {
979
+ if (container !== void 0 || job.checkContainer !== void 0) {
980
+ const state = container?.state ?? job.checkContainer;
981
+ await cleanupContainer(runtime, state).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
982
+ delete job.checkContainer;
983
+ writeJobFile(jobFilePath, job);
984
+ }
985
+ rmSync(scratch, { recursive: true, force: true });
986
+ if (existsSync(scratch)) log.error(`${tag} the checks' scratch directory could not be removed`);
987
+ log.line(`${tag} checks done (${formatDuration(now().getTime() - startedMs)})`);
988
+ }
989
+ }
720
990
 
721
991
  // src/runner/job.ts
722
992
  var LEASE_SECONDS = 900;
@@ -1181,7 +1451,7 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1181
1451
  await stopProcessGroup(job.pid);
1182
1452
  }
1183
1453
  if (outcome !== void 0 && job.endStage === void 0) {
1184
- const TEARDOWN_STAGES = ["sync", "cleanup", "report"];
1454
+ const TEARDOWN_STAGES = ["checks", "sync", "cleanup", "report"];
1185
1455
  const staleTeardownStage = job.stage !== void 0 && TEARDOWN_STAGES.includes(job.stage);
1186
1456
  job.endStage = ending?.stage ?? (staleTeardownStage ? job.exitCode !== void 0 ? "command" : void 0 : job.stage);
1187
1457
  job.endExitCode = ending?.exitCode ?? (outcome === "ok" || outcome === "failed" ? job.exitCode : void 0);
@@ -1191,11 +1461,35 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1191
1461
  job.reason = scrubReason(ending.reason, config);
1192
1462
  writeJobFile(jobFilePath, job);
1193
1463
  }
1464
+ if (job.checkContainer !== void 0 && job.checksRan === true) {
1465
+ try {
1466
+ await cleanupContainer(deps.container ?? createContainerRuntime(), job.checkContainer);
1467
+ delete job.checkContainer;
1468
+ writeJobFile(jobFilePath, job);
1469
+ } catch (err) {
1470
+ log.error(`${tag} removing an earlier attempt's check environment failed: ${describeCallFailure(err)} \xB7 continuing`);
1471
+ }
1472
+ }
1473
+ if (outcome === "ok" && !job.cleanupRan && job.checksRan !== true) {
1474
+ setStage(job, jobDirPath, "checks");
1475
+ job.checksRan = true;
1476
+ writeJobFile(jobFilePath, job);
1477
+ try {
1478
+ await runHostChecks({
1479
+ config,
1480
+ job,
1481
+ jobDir: jobDirPath,
1482
+ deps: { fetchImpl: deps.fetchImpl, runtime: deps.container ?? createContainerRuntime(), log, now: deps.now, ...deps.hostChecks }
1483
+ });
1484
+ } catch (err) {
1485
+ log.error(`${tag} the host's checks stopped: ${describeCallFailure(err)} \xB7 continuing`);
1486
+ }
1487
+ }
1194
1488
  let outOfMemory;
1195
1489
  if (vmBuild?.state.cgroup !== void 0 && outcome !== void 0 && outcome !== "ok" && ending?.reason === void 0) {
1196
1490
  try {
1197
1491
  if (vmBuild.runtime.vm.cgroup.oomKills(vmBuild.state.cgroup) > 0) {
1198
- outOfMemory = `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${config.build?.memory ?? "8g"}) plus 256 MiB for Firecracker. Raise runner.build.memory, or lower runner.concurrency`;
1492
+ outOfMemory = outOfMemoryReason(config.build?.memory);
1199
1493
  }
1200
1494
  } catch (err) {
1201
1495
  log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { MIN_ENGINE_MAJOR } from './chunk-RZSF5B46.js';
4
- import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-Z62HNKEJ.js';
3
+ import { MIN_ENGINE_MAJOR } from './chunk-E4PIFIC3.js';
4
+ import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-CEBIA5FD.js';
5
5
  import { spawnSync } from 'child_process';
6
6
  import { existsSync, accessSync, constants, statSync, readFileSync } from 'fs';
7
7
 
@@ -2,6 +2,7 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { startTimeOf, claimPidFile, RunnerAlreadyRunningError, releasePidFile } from './chunk-N5FOE5PS.js';
4
4
  import { RecipeError, locateRecipe, readRecipe, assertBuildable, writeSanitizedRecipe, classifyProperties, SANITIZED_NAME } from './chunk-QOZKPLVJ.js';
5
+ import { withGitConfig } from './chunk-HKMPVPO7.js';
5
6
  import { RUNNER_DIR_MODE, repoCacheDir, imagesDir, attemptStamp, runnerDir, rootfsPathFor, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
6
7
  import { mkdirSync, rmSync, existsSync, mkdtempSync, cpSync, readFileSync, writeFileSync, readdirSync, lstatSync, readlinkSync } from 'fs';
7
8
  import { tmpdir } from 'os';
@@ -20,10 +21,10 @@ function tokenHeader(token) {
20
21
  return `Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
21
22
  }
22
23
  function tokenSettings(remote, token) {
23
- return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: `http.${remote}.extraHeader`, GIT_CONFIG_VALUE_0: tokenHeader(token) };
24
+ return (env) => withGitConfig(env, `http.${remote}.extraHeader`, tokenHeader(token));
24
25
  }
25
- function git(cwd, args, timeoutMs, extraEnv = {}) {
26
- const env = { ...gitEnvironment(process.env), ...extraEnv };
26
+ function git(cwd, args, timeoutMs, extend = (env) => env) {
27
+ const env = extend(gitEnvironment(process.env));
27
28
  return new Promise((resolve, reject) => {
28
29
  execFile(
29
30
  "git",
@@ -44,7 +45,7 @@ function defaultBranchOf(lsRemote) {
44
45
  async function fetchDefaultBranch(opts) {
45
46
  const created = !existsSync(opts.dir);
46
47
  mkdirSync(opts.dir, { recursive: true, mode: RUNNER_DIR_MODE });
47
- const credential = opts.token === void 0 ? {} : tokenSettings(opts.remote, opts.token);
48
+ const credential = opts.token === void 0 ? void 0 : tokenSettings(opts.remote, opts.token);
48
49
  const over = (args) => git(opts.dir, args, void 0, credential);
49
50
  try {
50
51
  const defaultBranch = defaultBranchOf(await over(["ls-remote", "--symref", opts.remote, "HEAD"]));
@@ -84,6 +85,31 @@ async function readDefaultBranchHead(remote, dir) {
84
85
  }
85
86
  return out.match(/^([0-9a-f]{40}|[0-9a-f]{64})\tHEAD$/m)?.[1];
86
87
  }
88
+ async function readBranchHead(input) {
89
+ const credential = input.token === void 0 ? void 0 : tokenSettings(input.remote, input.token);
90
+ mkdirSync(input.dir, { recursive: true, mode: RUNNER_DIR_MODE });
91
+ let out;
92
+ try {
93
+ out = await git(input.dir, ["ls-remote", "--heads", input.remote, `refs/heads/${input.branch}`], HEAD_TIMEOUT_MS, credential);
94
+ } catch (err) {
95
+ return { kind: "unreadable", reason: (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0] };
96
+ }
97
+ const sha = out.match(/^([0-9a-f]{40}|[0-9a-f]{64})\trefs\/heads\//m)?.[1];
98
+ return sha === void 0 ? { kind: "absent" } : { kind: "found", sha };
99
+ }
100
+ async function fetchCommit(input) {
101
+ const credential = input.token === void 0 ? void 0 : tokenSettings(input.remote, input.token);
102
+ try {
103
+ mkdirSync(input.dir, { recursive: true, mode: RUNNER_DIR_MODE });
104
+ await git(input.dir, ["init", "--quiet"]);
105
+ await git(input.dir, ["fetch", "--quiet", "--depth", "1", "--no-tags", input.remote, input.commit], CLONE_TIMEOUT_MS, credential);
106
+ await git(input.dir, ["checkout", "--quiet", "--detach", "--force", "FETCH_HEAD"]);
107
+ return { ok: true };
108
+ } catch (err) {
109
+ const detail = (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0];
110
+ return { ok: false, reason: `the fetch of ${input.commit.slice(0, 8)} from ${input.remote} failed: ${detail}` };
111
+ }
112
+ }
87
113
  var CLONE_TIMEOUT_MS = 6e5;
88
114
  async function cloneRepository(remote, dest, timeoutMs = CLONE_TIMEOUT_MS) {
89
115
  mkdirSync(dirname(dest), { recursive: true, mode: RUNNER_DIR_MODE });
@@ -467,4 +493,4 @@ async function prune(host, repository, repoRecords, keep, runnerDirPath) {
467
493
  return removed;
468
494
  }
469
495
 
470
- export { CLAUDE_CODE_VERSION, DEFAULT_BASE_IMAGE, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_BASE_URL, GUEST_ASSETS_VERSION, GUEST_HELPER_SHA256, GUEST_KERNEL_SHA256, MICROVM_ARCHES, buildRepositoryImage, cloneRepository, firecrackerArch, guestAssetsPinned, readDefaultBranchHead };
496
+ export { CLAUDE_CODE_VERSION, DEFAULT_BASE_IMAGE, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_BASE_URL, GUEST_ASSETS_VERSION, GUEST_HELPER_SHA256, GUEST_KERNEL_SHA256, MICROVM_ARCHES, buildRepositoryImage, cloneRepository, fetchCommit, firecrackerArch, guestAssetsPinned, readBranchHead, readDefaultBranchHead };