terminalhire 0.42.22 → 0.42.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1077,8 +1077,8 @@ function makeGitHubGovernor(fetchImpl, cfg) {
1077
1077
  try {
1078
1078
  const res = bound == null ? await fetchP : await Promise.race([
1079
1079
  fetchP,
1080
- new Promise((resolve4) => {
1081
- timer = setTimeout(() => resolve4(null), bound);
1080
+ new Promise((resolve5) => {
1081
+ timer = setTimeout(() => resolve5(null), bound);
1082
1082
  })
1083
1083
  ]);
1084
1084
  if (!res || !res.ok) return null;
@@ -1893,11 +1893,11 @@ function parseGitHubRef(url) {
1893
1893
  kind: m[3] === "pull" ? "pull" : "issue"
1894
1894
  };
1895
1895
  }
1896
- async function ghGraphQL(query, variables, token, signal, governor) {
1896
+ async function ghGraphQL(query2, variables, token, signal, governor) {
1897
1897
  const init = {
1898
1898
  method: "POST",
1899
1899
  headers: { ...ghHeaders(token), "Content-Type": "application/json" },
1900
- body: JSON.stringify({ query, variables }),
1900
+ body: JSON.stringify({ query: query2, variables }),
1901
1901
  signal
1902
1902
  };
1903
1903
  if (governor) {
@@ -5136,8 +5136,8 @@ async function getWithTimeout(governor, url, token, timeoutMs) {
5136
5136
  try {
5137
5137
  const res = await Promise.race([
5138
5138
  getP,
5139
- new Promise((resolve4) => {
5140
- timer = setTimeout(() => resolve4(null), timeoutMs);
5139
+ new Promise((resolve5) => {
5140
+ timer = setTimeout(() => resolve5(null), timeoutMs);
5141
5141
  })
5142
5142
  ]);
5143
5143
  if (res === null) controller.abort();
@@ -6934,7 +6934,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
6934
6934
  });
6935
6935
  const { prehash } = eddsaOpts;
6936
6936
  const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
6937
- const randomBytes12 = eddsaOpts.randomBytes || randomBytes;
6937
+ const randomBytes13 = eddsaOpts.randomBytes || randomBytes;
6938
6938
  const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
6939
6939
  const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
6940
6940
  _abool2(phflag, "phflag");
@@ -7016,7 +7016,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
7016
7016
  signature: 2 * _size,
7017
7017
  seed: _size
7018
7018
  };
7019
- function randomSecretKey(seed = randomBytes12(lengths.seed)) {
7019
+ function randomSecretKey(seed = randomBytes13(lengths.seed)) {
7020
7020
  return _abytes2(seed, lengths.seed, "seed");
7021
7021
  }
7022
7022
  function keygen(seed) {
@@ -11342,7 +11342,7 @@ async function requireStoredLogin() {
11342
11342
  }
11343
11343
  }
11344
11344
  function sleep(ms) {
11345
- return new Promise((resolve4) => setTimeout(resolve4, ms));
11345
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
11346
11346
  }
11347
11347
  var TERMINALHIRE_DIR4, TOKEN_FILE, ALGO, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL, BAKED_IN_CLIENT_ID, MOCK_TOKEN, MOCK_LOGIN;
11348
11348
  var init_github_auth = __esm({
@@ -11759,16 +11759,16 @@ var sleep2;
11759
11759
  var init_sleep = __esm({
11760
11760
  "../../node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs"() {
11761
11761
  "use strict";
11762
- sleep2 = (ms, signal) => new Promise((resolve4) => {
11762
+ sleep2 = (ms, signal) => new Promise((resolve5) => {
11763
11763
  if (signal?.aborted)
11764
- return resolve4();
11764
+ return resolve5();
11765
11765
  const onAbort = () => {
11766
11766
  clearTimeout(timer);
11767
- resolve4();
11767
+ resolve5();
11768
11768
  };
11769
11769
  const timer = setTimeout(() => {
11770
11770
  signal?.removeEventListener("abort", onAbort);
11771
- resolve4();
11771
+ resolve5();
11772
11772
  }, ms);
11773
11773
  signal?.addEventListener("abort", onAbort, { once: true });
11774
11774
  });
@@ -12408,8 +12408,8 @@ var init_stringify = __esm({
12408
12408
  });
12409
12409
 
12410
12410
  // ../../node_modules/@anthropic-ai/sdk/internal/utils/query.mjs
12411
- function stringifyQuery(query) {
12412
- return stringify(query, { arrayFormat: "brackets" });
12411
+ function stringifyQuery(query2) {
12412
+ return stringify(query2, { arrayFormat: "brackets" });
12413
12413
  }
12414
12414
  var init_query = __esm({
12415
12415
  "../../node_modules/@anthropic-ai/sdk/internal/utils/query.mjs"() {
@@ -13897,8 +13897,8 @@ var init_api_promise = __esm({
13897
13897
  init_parse();
13898
13898
  APIPromise = class _APIPromise extends Promise {
13899
13899
  constructor(client, responsePromise, parseResponse = defaultParseResponse) {
13900
- super((resolve4) => {
13901
- resolve4(null);
13900
+ super((resolve5) => {
13901
+ resolve5(null);
13902
13902
  });
13903
13903
  this.responsePromise = responsePromise;
13904
13904
  this.parseResponse = parseResponse;
@@ -14493,9 +14493,9 @@ var init_deployment_runs = __esm({
14493
14493
  * ```
14494
14494
  */
14495
14495
  list(params = {}, options) {
14496
- const { betas, ...query } = params ?? {};
14496
+ const { betas, ...query2 } = params ?? {};
14497
14497
  return this._client.getAPIList("/v1/deployment_runs?beta=true", PageCursor, {
14498
- query,
14498
+ query: query2,
14499
14499
  ...options,
14500
14500
  headers: buildHeaders([
14501
14501
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -14607,9 +14607,9 @@ var init_deployments = __esm({
14607
14607
  * ```
14608
14608
  */
14609
14609
  list(params = {}, options) {
14610
- const { betas, ...query } = params ?? {};
14610
+ const { betas, ...query2 } = params ?? {};
14611
14611
  return this._client.getAPIList("/v1/deployments?beta=true", PageCursor, {
14612
- query,
14612
+ query: query2,
14613
14613
  ...options,
14614
14614
  headers: buildHeaders([
14615
14615
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -14769,9 +14769,9 @@ var init_dreams = __esm({
14769
14769
  * ```
14770
14770
  */
14771
14771
  list(params = {}, options) {
14772
- const { betas, ...query } = params ?? {};
14772
+ const { betas, ...query2 } = params ?? {};
14773
14773
  return this._client.getAPIList("/v1/dreams?beta=true", PageCursor, {
14774
- query,
14774
+ query: query2,
14775
14775
  ...options,
14776
14776
  headers: buildHeaders([
14777
14777
  { "anthropic-beta": [...betas ?? [], "dreaming-2026-04-21"].toString() },
@@ -14902,9 +14902,9 @@ var init_files = __esm({
14902
14902
  * ```
14903
14903
  */
14904
14904
  list(params = {}, options) {
14905
- const { betas, ...query } = params ?? {};
14905
+ const { betas, ...query2 } = params ?? {};
14906
14906
  return this._client.getAPIList("/v1/files?beta=true", Page, {
14907
- query,
14907
+ query: query2,
14908
14908
  ...options,
14909
14909
  headers: buildHeaders([
14910
14910
  { "anthropic-beta": [...betas ?? [], "files-api-2025-04-14"].toString() },
@@ -15052,9 +15052,9 @@ var init_models = __esm({
15052
15052
  * ```
15053
15053
  */
15054
15054
  list(params = {}, options) {
15055
- const { betas, ...query } = params ?? {};
15055
+ const { betas, ...query2 } = params ?? {};
15056
15056
  return this._client.getAPIList("/v1/models?beta=true", Page, {
15057
- query,
15057
+ query: query2,
15058
15058
  ...options,
15059
15059
  headers: buildHeaders([
15060
15060
  { ...betas?.toString() != null ? { "anthropic-beta": betas?.toString() } : void 0 },
@@ -15151,9 +15151,9 @@ var init_user_profiles = __esm({
15151
15151
  * ```
15152
15152
  */
15153
15153
  list(params = {}, options) {
15154
- const { betas, ...query } = params ?? {};
15154
+ const { betas, ...query2 } = params ?? {};
15155
15155
  return this._client.getAPIList("/v1/user_profiles?beta=true", PageCursor, {
15156
- query,
15156
+ query: query2,
15157
15157
  ...options,
15158
15158
  headers: buildHeaders([
15159
15159
  { "anthropic-beta": [...betas ?? [], "user-profiles-2026-03-24"].toString() },
@@ -16012,9 +16012,9 @@ var init_versions = __esm({
16012
16012
  * ```
16013
16013
  */
16014
16014
  list(agentID, params = {}, options) {
16015
- const { betas, ...query } = params ?? {};
16015
+ const { betas, ...query2 } = params ?? {};
16016
16016
  return this._client.getAPIList(path`/v1/agents/${agentID}/versions?beta=true`, PageCursor, {
16017
- query,
16017
+ query: query2,
16018
16018
  ...options,
16019
16019
  headers: buildHeaders([
16020
16020
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -16077,9 +16077,9 @@ var init_agents = __esm({
16077
16077
  * ```
16078
16078
  */
16079
16079
  retrieve(agentID, params = {}, options) {
16080
- const { betas, ...query } = params ?? {};
16080
+ const { betas, ...query2 } = params ?? {};
16081
16081
  return this._client.get(path`/v1/agents/${agentID}?beta=true`, {
16082
- query,
16082
+ query: query2,
16083
16083
  ...options,
16084
16084
  headers: buildHeaders([
16085
16085
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -16122,9 +16122,9 @@ var init_agents = __esm({
16122
16122
  * ```
16123
16123
  */
16124
16124
  list(params = {}, options) {
16125
- const { betas, ...query } = params ?? {};
16125
+ const { betas, ...query2 } = params ?? {};
16126
16126
  return this._client.getAPIList("/v1/agents?beta=true", PageCursor, {
16127
- query,
16127
+ query: query2,
16128
16128
  ...options,
16129
16129
  headers: buildHeaders([
16130
16130
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -16418,16 +16418,16 @@ var init_async_queue = __esm({
16418
16418
  if (__classPrivateFieldGet(this, _AsyncQueue_closed, "f") || signal?.aborted) {
16419
16419
  return Promise.resolve({ done: true, value: void 0 });
16420
16420
  }
16421
- return new Promise((resolve4) => {
16421
+ return new Promise((resolve5) => {
16422
16422
  const waiter = (r) => {
16423
16423
  signal?.removeEventListener("abort", onAbort);
16424
- resolve4(r);
16424
+ resolve5(r);
16425
16425
  };
16426
16426
  const onAbort = () => {
16427
16427
  const idx = __classPrivateFieldGet(this, _AsyncQueue_waiters, "f").indexOf(waiter);
16428
16428
  if (idx >= 0)
16429
16429
  __classPrivateFieldGet(this, _AsyncQueue_waiters, "f").splice(idx, 1);
16430
- resolve4({ done: true, value: void 0 });
16430
+ resolve5({ done: true, value: void 0 });
16431
16431
  };
16432
16432
  __classPrivateFieldGet(this, _AsyncQueue_waiters, "f").push(waiter);
16433
16433
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -17027,13 +17027,13 @@ var init_json_schema = __esm({
17027
17027
 
17028
17028
  // ../../node_modules/@anthropic-ai/sdk/internal/utils/promise.mjs
17029
17029
  function promiseWithResolvers() {
17030
- let resolve4;
17030
+ let resolve5;
17031
17031
  let reject;
17032
17032
  const promise = new Promise((res, rej) => {
17033
- resolve4 = res;
17033
+ resolve5 = res;
17034
17034
  reject = rej;
17035
17035
  });
17036
- return { promise, resolve: resolve4, reject };
17036
+ return { promise, resolve: resolve5, reject };
17037
17037
  }
17038
17038
  var init_promise = __esm({
17039
17039
  "../../node_modules/@anthropic-ai/sdk/internal/utils/promise.mjs"() {
@@ -17635,7 +17635,7 @@ function betaGrepTool(ctx) {
17635
17635
  });
17636
17636
  }
17637
17637
  function runRipgrep(rg, pattern, searchPath, signal) {
17638
- return new Promise((resolve4, reject) => {
17638
+ return new Promise((resolve5, reject) => {
17639
17639
  const proc = cp.spawn(rg, ["-n", "--no-heading", "-e", pattern, "--", searchPath], {
17640
17640
  ...signal ? { signal } : {}
17641
17641
  });
@@ -17657,12 +17657,12 @@ function runRipgrep(rg, pattern, searchPath, signal) {
17657
17657
  if (signal?.aborted)
17658
17658
  return reject(new ToolError("grep: aborted"));
17659
17659
  if (truncated)
17660
- return resolve4(out + `
17660
+ return resolve5(out + `
17661
17661
  [output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
17662
17662
  if (code === 0)
17663
- return resolve4(out);
17663
+ return resolve5(out);
17664
17664
  if (code === 1)
17665
- return resolve4("no matches");
17665
+ return resolve5("no matches");
17666
17666
  reject(new ToolError(`grep: rg failed: ${errOut || `exit ${code}`}`));
17667
17667
  });
17668
17668
  proc.on("error", (e) => {
@@ -17840,8 +17840,8 @@ var init_node = __esm({
17840
17840
  `;
17841
17841
  __classPrivateFieldGet(this, _BashSession_proc, "f").stdin.write(wrapped);
17842
17842
  if (__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel2) < 0) {
17843
- const { promise: sentinelSeen, resolve: resolve4 } = promiseWithResolvers();
17844
- __classPrivateFieldSet(this, _BashSession_waiting, { sentinel: sentinel2, resolve: resolve4 }, "f");
17843
+ const { promise: sentinelSeen, resolve: resolve5 } = promiseWithResolvers();
17844
+ __classPrivateFieldSet(this, _BashSession_waiting, { sentinel: sentinel2, resolve: resolve5 }, "f");
17845
17845
  let timer;
17846
17846
  let onAbort;
17847
17847
  try {
@@ -18214,9 +18214,9 @@ var init_work = __esm({
18214
18214
  * ```
18215
18215
  */
18216
18216
  list(environmentID, params = {}, options) {
18217
- const { betas, ...query } = params ?? {};
18217
+ const { betas, ...query2 } = params ?? {};
18218
18218
  return this._client.getAPIList(path`/v1/environments/${environmentID}/work?beta=true`, PageCursor, {
18219
- query,
18219
+ query: query2,
18220
18220
  ...options,
18221
18221
  headers: buildHeaders([
18222
18222
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -18295,9 +18295,9 @@ var init_work = __esm({
18295
18295
  * ```
18296
18296
  */
18297
18297
  poll(environmentID, params = {}, options) {
18298
- const { betas, "Anthropic-Worker-ID": anthropicWorkerID, ...query } = params ?? {};
18298
+ const { betas, "Anthropic-Worker-ID": anthropicWorkerID, ...query2 } = params ?? {};
18299
18299
  return this._client.get(path`/v1/environments/${environmentID}/work/poll?beta=true`, {
18300
- query,
18300
+ query: query2,
18301
18301
  ...options,
18302
18302
  headers: buildHeaders([
18303
18303
  {
@@ -18494,9 +18494,9 @@ var init_environments = __esm({
18494
18494
  * ```
18495
18495
  */
18496
18496
  list(params = {}, options) {
18497
- const { betas, ...query } = params ?? {};
18497
+ const { betas, ...query2 } = params ?? {};
18498
18498
  return this._client.getAPIList("/v1/environments?beta=true", PageCursor, {
18499
- query,
18499
+ query: query2,
18500
18500
  ...options,
18501
18501
  headers: buildHeaders([
18502
18502
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -18599,9 +18599,9 @@ var init_memories = __esm({
18599
18599
  * ```
18600
18600
  */
18601
18601
  retrieve(memoryID, params, options) {
18602
- const { memory_store_id, betas, ...query } = params;
18602
+ const { memory_store_id, betas, ...query2 } = params;
18603
18603
  return this._client.get(path`/v1/memory_stores/${memory_store_id}/memories/${memoryID}?beta=true`, {
18604
- query,
18604
+ query: query2,
18605
18605
  ...options,
18606
18606
  headers: buildHeaders([
18607
18607
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -18647,9 +18647,9 @@ var init_memories = __esm({
18647
18647
  * ```
18648
18648
  */
18649
18649
  list(memoryStoreID, params = {}, options) {
18650
- const { betas, ...query } = params ?? {};
18650
+ const { betas, ...query2 } = params ?? {};
18651
18651
  return this._client.getAPIList(path`/v1/memory_stores/${memoryStoreID}/memories?beta=true`, PageCursor, {
18652
- query,
18652
+ query: query2,
18653
18653
  ...options,
18654
18654
  headers: buildHeaders([
18655
18655
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -18707,9 +18707,9 @@ var init_memory_versions = __esm({
18707
18707
  * ```
18708
18708
  */
18709
18709
  retrieve(memoryVersionID, params, options) {
18710
- const { memory_store_id, betas, ...query } = params;
18710
+ const { memory_store_id, betas, ...query2 } = params;
18711
18711
  return this._client.get(path`/v1/memory_stores/${memory_store_id}/memory_versions/${memoryVersionID}?beta=true`, {
18712
- query,
18712
+ query: query2,
18713
18713
  ...options,
18714
18714
  headers: buildHeaders([
18715
18715
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -18731,9 +18731,9 @@ var init_memory_versions = __esm({
18731
18731
  * ```
18732
18732
  */
18733
18733
  list(memoryStoreID, params = {}, options) {
18734
- const { betas, ...query } = params ?? {};
18734
+ const { betas, ...query2 } = params ?? {};
18735
18735
  return this._client.getAPIList(path`/v1/memory_stores/${memoryStoreID}/memory_versions?beta=true`, PageCursor, {
18736
- query,
18736
+ query: query2,
18737
18737
  ...options,
18738
18738
  headers: buildHeaders([
18739
18739
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -18859,9 +18859,9 @@ var init_memory_stores = __esm({
18859
18859
  * ```
18860
18860
  */
18861
18861
  list(params = {}, options) {
18862
- const { betas, ...query } = params ?? {};
18862
+ const { betas, ...query2 } = params ?? {};
18863
18863
  return this._client.getAPIList("/v1/memory_stores?beta=true", PageCursor, {
18864
- query,
18864
+ query: query2,
18865
18865
  ...options,
18866
18866
  headers: buildHeaders([
18867
18867
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -19059,9 +19059,9 @@ var init_batches = __esm({
19059
19059
  * ```
19060
19060
  */
19061
19061
  list(params = {}, options) {
19062
- const { betas, ...query } = params ?? {};
19062
+ const { betas, ...query2 } = params ?? {};
19063
19063
  return this._client.getAPIList("/v1/messages/batches?beta=true", Page, {
19064
- query,
19064
+ query: query2,
19065
19065
  ...options,
19066
19066
  headers: buildHeaders([
19067
19067
  { "anthropic-beta": [...betas ?? [], "message-batches-2024-09-24"].toString() },
@@ -19596,12 +19596,12 @@ var init_BetaMessageStream = __esm({
19596
19596
  }
19597
19597
  return this._emit("error", new AnthropicError(String(error)));
19598
19598
  });
19599
- __classPrivateFieldSet(this, _BetaMessageStream_connectedPromise, new Promise((resolve4, reject) => {
19600
- __classPrivateFieldSet(this, _BetaMessageStream_resolveConnectedPromise, resolve4, "f");
19599
+ __classPrivateFieldSet(this, _BetaMessageStream_connectedPromise, new Promise((resolve5, reject) => {
19600
+ __classPrivateFieldSet(this, _BetaMessageStream_resolveConnectedPromise, resolve5, "f");
19601
19601
  __classPrivateFieldSet(this, _BetaMessageStream_rejectConnectedPromise, reject, "f");
19602
19602
  }), "f");
19603
- __classPrivateFieldSet(this, _BetaMessageStream_endPromise, new Promise((resolve4, reject) => {
19604
- __classPrivateFieldSet(this, _BetaMessageStream_resolveEndPromise, resolve4, "f");
19603
+ __classPrivateFieldSet(this, _BetaMessageStream_endPromise, new Promise((resolve5, reject) => {
19604
+ __classPrivateFieldSet(this, _BetaMessageStream_resolveEndPromise, resolve5, "f");
19605
19605
  __classPrivateFieldSet(this, _BetaMessageStream_rejectEndPromise, reject, "f");
19606
19606
  }), "f");
19607
19607
  __classPrivateFieldGet(this, _BetaMessageStream_connectedPromise, "f").catch(() => {
@@ -19771,11 +19771,11 @@ var init_BetaMessageStream = __esm({
19771
19771
  * const message = await stream.emitted('message') // rejects if the stream errors
19772
19772
  */
19773
19773
  emitted(event) {
19774
- return new Promise((resolve4, reject) => {
19774
+ return new Promise((resolve5, reject) => {
19775
19775
  __classPrivateFieldSet(this, _BetaMessageStream_catchingPromiseCreated, true, "f");
19776
19776
  if (event !== "error")
19777
19777
  this.once("error", reject);
19778
- this.once(event, resolve4);
19778
+ this.once(event, resolve5);
19779
19779
  });
19780
19780
  }
19781
19781
  async done() {
@@ -20137,7 +20137,7 @@ var init_BetaMessageStream = __esm({
20137
20137
  if (done) {
20138
20138
  return { value: void 0, done: true };
20139
20139
  }
20140
- return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
20140
+ return new Promise((resolve5, reject) => readQueue.push({ resolve: resolve5, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
20141
20141
  }
20142
20142
  const chunk = pushQueue.shift();
20143
20143
  return { value: chunk, done: false };
@@ -20734,9 +20734,9 @@ var init_events = __esm({
20734
20734
  * ```
20735
20735
  */
20736
20736
  list(sessionID, params = {}, options) {
20737
- const { betas, ...query } = params ?? {};
20737
+ const { betas, ...query2 } = params ?? {};
20738
20738
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/events?beta=true`, PageCursor, {
20739
- query,
20739
+ query: query2,
20740
20740
  ...options,
20741
20741
  headers: buildHeaders([
20742
20742
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -20791,9 +20791,9 @@ var init_events = __esm({
20791
20791
  * ```
20792
20792
  */
20793
20793
  stream(sessionID, params = {}, options) {
20794
- const { betas, ...query } = params ?? {};
20794
+ const { betas, ...query2 } = params ?? {};
20795
20795
  return this._client.get(path`/v1/sessions/${sessionID}/events/stream?beta=true`, {
20796
- query,
20796
+ query: query2,
20797
20797
  ...options,
20798
20798
  headers: buildHeaders([
20799
20799
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -20901,9 +20901,9 @@ var init_resources = __esm({
20901
20901
  * ```
20902
20902
  */
20903
20903
  list(sessionID, params = {}, options) {
20904
- const { betas, ...query } = params ?? {};
20904
+ const { betas, ...query2 } = params ?? {};
20905
20905
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/resources?beta=true`, PageCursor, {
20906
- query,
20906
+ query: query2,
20907
20907
  ...options,
20908
20908
  headers: buildHeaders([
20909
20909
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -20988,9 +20988,9 @@ var init_events2 = __esm({
20988
20988
  * ```
20989
20989
  */
20990
20990
  list(threadID, params, options) {
20991
- const { session_id, betas, ...query } = params;
20991
+ const { session_id, betas, ...query2 } = params;
20992
20992
  return this._client.getAPIList(path`/v1/sessions/${session_id}/threads/${threadID}/events?beta=true`, PageCursor, {
20993
- query,
20993
+ query: query2,
20994
20994
  ...options,
20995
20995
  headers: buildHeaders([
20996
20996
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -21077,9 +21077,9 @@ var init_threads = __esm({
21077
21077
  * ```
21078
21078
  */
21079
21079
  list(sessionID, params = {}, options) {
21080
- const { betas, ...query } = params ?? {};
21080
+ const { betas, ...query2 } = params ?? {};
21081
21081
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/threads?beta=true`, PageCursor, {
21082
- query,
21082
+ query: query2,
21083
21083
  ...options,
21084
21084
  headers: buildHeaders([
21085
21085
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -21214,9 +21214,9 @@ var init_sessions = __esm({
21214
21214
  * ```
21215
21215
  */
21216
21216
  list(params = {}, options) {
21217
- const { betas, ...query } = params ?? {};
21217
+ const { betas, ...query2 } = params ?? {};
21218
21218
  return this._client.getAPIList("/v1/sessions?beta=true", BidirectionalPageCursor, {
21219
- query,
21219
+ query: query2,
21220
21220
  ...options,
21221
21221
  headers: buildHeaders([
21222
21222
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -21341,9 +21341,9 @@ var init_versions2 = __esm({
21341
21341
  * ```
21342
21342
  */
21343
21343
  list(skillID, params = {}, options) {
21344
- const { betas, ...query } = params ?? {};
21344
+ const { betas, ...query2 } = params ?? {};
21345
21345
  return this._client.getAPIList(path`/v1/skills/${skillID}/versions?beta=true`, PageCursor, {
21346
- query,
21346
+ query: query2,
21347
21347
  ...options,
21348
21348
  headers: buildHeaders([
21349
21349
  { "anthropic-beta": [...betas ?? [], "skills-2025-10-02"].toString() },
@@ -21472,9 +21472,9 @@ var init_skills2 = __esm({
21472
21472
  * ```
21473
21473
  */
21474
21474
  list(params = {}, options) {
21475
- const { betas, ...query } = params ?? {};
21475
+ const { betas, ...query2 } = params ?? {};
21476
21476
  return this._client.getAPIList("/v1/skills?beta=true", PageCursor, {
21477
- query,
21477
+ query: query2,
21478
21478
  ...options,
21479
21479
  headers: buildHeaders([
21480
21480
  { "anthropic-beta": [...betas ?? [], "skills-2025-10-02"].toString() },
@@ -21592,9 +21592,9 @@ var init_certificates = __esm({
21592
21592
  * ```
21593
21593
  */
21594
21594
  list(tunnelID, params = {}, options) {
21595
- const { betas, ...query } = params ?? {};
21595
+ const { betas, ...query2 } = params ?? {};
21596
21596
  return this._client.getAPIList(path`/v1/tunnels/${tunnelID}/certificates?beta=true`, PageCursor, {
21597
- query,
21597
+ query: query2,
21598
21598
  ...options,
21599
21599
  headers: buildHeaders([
21600
21600
  { "anthropic-beta": [...betas ?? [], "mcp-tunnels-2026-06-22"].toString() },
@@ -21721,9 +21721,9 @@ var init_tunnels = __esm({
21721
21721
  * ```
21722
21722
  */
21723
21723
  list(params = {}, options) {
21724
- const { betas, ...query } = params ?? {};
21724
+ const { betas, ...query2 } = params ?? {};
21725
21725
  return this._client.getAPIList("/v1/tunnels?beta=true", PageCursor, {
21726
- query,
21726
+ query: query2,
21727
21727
  ...options,
21728
21728
  headers: buildHeaders([
21729
21729
  { "anthropic-beta": [...betas ?? [], "mcp-tunnels-2026-06-22"].toString() },
@@ -21917,9 +21917,9 @@ var init_credentials2 = __esm({
21917
21917
  * ```
21918
21918
  */
21919
21919
  list(vaultID, params = {}, options) {
21920
- const { betas, ...query } = params ?? {};
21920
+ const { betas, ...query2 } = params ?? {};
21921
21921
  return this._client.getAPIList(path`/v1/vaults/${vaultID}/credentials?beta=true`, PageCursor, {
21922
- query,
21922
+ query: query2,
21923
21923
  ...options,
21924
21924
  headers: buildHeaders([
21925
21925
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -22090,9 +22090,9 @@ var init_vaults = __esm({
22090
22090
  * ```
22091
22091
  */
22092
22092
  list(params = {}, options) {
22093
- const { betas, ...query } = params ?? {};
22093
+ const { betas, ...query2 } = params ?? {};
22094
22094
  return this._client.getAPIList("/v1/vaults?beta=true", PageCursor, {
22095
- query,
22095
+ query: query2,
22096
22096
  ...options,
22097
22097
  headers: buildHeaders([
22098
22098
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -22375,12 +22375,12 @@ var init_MessageStream = __esm({
22375
22375
  }
22376
22376
  return this._emit("error", new AnthropicError(String(error)));
22377
22377
  });
22378
- __classPrivateFieldSet(this, _MessageStream_connectedPromise, new Promise((resolve4, reject) => {
22379
- __classPrivateFieldSet(this, _MessageStream_resolveConnectedPromise, resolve4, "f");
22378
+ __classPrivateFieldSet(this, _MessageStream_connectedPromise, new Promise((resolve5, reject) => {
22379
+ __classPrivateFieldSet(this, _MessageStream_resolveConnectedPromise, resolve5, "f");
22380
22380
  __classPrivateFieldSet(this, _MessageStream_rejectConnectedPromise, reject, "f");
22381
22381
  }), "f");
22382
- __classPrivateFieldSet(this, _MessageStream_endPromise, new Promise((resolve4, reject) => {
22383
- __classPrivateFieldSet(this, _MessageStream_resolveEndPromise, resolve4, "f");
22382
+ __classPrivateFieldSet(this, _MessageStream_endPromise, new Promise((resolve5, reject) => {
22383
+ __classPrivateFieldSet(this, _MessageStream_resolveEndPromise, resolve5, "f");
22384
22384
  __classPrivateFieldSet(this, _MessageStream_rejectEndPromise, reject, "f");
22385
22385
  }), "f");
22386
22386
  __classPrivateFieldGet(this, _MessageStream_connectedPromise, "f").catch(() => {
@@ -22550,11 +22550,11 @@ var init_MessageStream = __esm({
22550
22550
  * const message = await stream.emitted('message') // rejects if the stream errors
22551
22551
  */
22552
22552
  emitted(event) {
22553
- return new Promise((resolve4, reject) => {
22553
+ return new Promise((resolve5, reject) => {
22554
22554
  __classPrivateFieldSet(this, _MessageStream_catchingPromiseCreated, true, "f");
22555
22555
  if (event !== "error")
22556
22556
  this.once("error", reject);
22557
- this.once(event, resolve4);
22557
+ this.once(event, resolve5);
22558
22558
  });
22559
22559
  }
22560
22560
  async done() {
@@ -22875,7 +22875,7 @@ var init_MessageStream = __esm({
22875
22875
  if (done) {
22876
22876
  return { value: void 0, done: true };
22877
22877
  }
22878
- return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
22878
+ return new Promise((resolve5, reject) => readQueue.push({ resolve: resolve5, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
22879
22879
  }
22880
22880
  const chunk = pushQueue.shift();
22881
22881
  return { value: chunk, done: false };
@@ -22978,8 +22978,8 @@ var init_batches2 = __esm({
22978
22978
  * }
22979
22979
  * ```
22980
22980
  */
22981
- list(query = {}, options) {
22982
- return this._client.getAPIList("/v1/messages/batches", Page, { query, ...options });
22981
+ list(query2 = {}, options) {
22982
+ return this._client.getAPIList("/v1/messages/batches", Page, { query: query2, ...options });
22983
22983
  }
22984
22984
  /**
22985
22985
  * Delete a Message Batch.
@@ -23235,9 +23235,9 @@ var init_models2 = __esm({
23235
23235
  * use in the API. More recently released models are listed first.
23236
23236
  */
23237
23237
  list(params = {}, options) {
23238
- const { betas, ...query } = params ?? {};
23238
+ const { betas, ...query2 } = params ?? {};
23239
23239
  return this._client.getAPIList("/v1/models", Page, {
23240
- query,
23240
+ query: query2,
23241
23241
  ...options,
23242
23242
  headers: buildHeaders([
23243
23243
  { ...betas?.toString() != null ? { "anthropic-beta": betas?.toString() } : void 0 },
@@ -23586,8 +23586,8 @@ var init_client = __esm({
23586
23586
  }
23587
23587
  return buildHeaders([{ Authorization: `Bearer ${this.authToken}` }]);
23588
23588
  }
23589
- stringifyQuery(query) {
23590
- return stringifyQuery(query);
23589
+ stringifyQuery(query2) {
23590
+ return stringifyQuery(query2);
23591
23591
  }
23592
23592
  getUserAgent() {
23593
23593
  return `${this.constructor.name}/JS ${VERSION}`;
@@ -23598,16 +23598,16 @@ var init_client = __esm({
23598
23598
  makeStatusError(status, error, message, headers) {
23599
23599
  return APIError.generate(status, error, message, headers);
23600
23600
  }
23601
- buildURL(path5, query, defaultBaseURL) {
23601
+ buildURL(path5, query2, defaultBaseURL) {
23602
23602
  const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
23603
23603
  const url = isAbsoluteURL(path5) ? new URL(path5) : new URL(baseURL + (baseURL.endsWith("/") && path5.startsWith("/") ? path5.slice(1) : path5));
23604
23604
  const defaultQuery = this.defaultQuery();
23605
23605
  const pathQuery = Object.fromEntries(url.searchParams);
23606
23606
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
23607
- query = { ...pathQuery, ...defaultQuery, ...query };
23607
+ query2 = { ...pathQuery, ...defaultQuery, ...query2 };
23608
23608
  }
23609
- if (typeof query === "object" && query && !Array.isArray(query)) {
23610
- url.search = this.stringifyQuery(query);
23609
+ if (typeof query2 === "object" && query2 && !Array.isArray(query2)) {
23610
+ url.search = this.stringifyQuery(query2);
23611
23611
  }
23612
23612
  return url.toString();
23613
23613
  }
@@ -23920,14 +23920,14 @@ var init_client = __esm({
23920
23920
  }
23921
23921
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
23922
23922
  const options = { ...inputOptions };
23923
- const { method, path: path5, query, defaultBaseURL } = options;
23923
+ const { method, path: path5, query: query2, defaultBaseURL } = options;
23924
23924
  if (this._authState.resolution) {
23925
23925
  await this._authState.resolution;
23926
23926
  }
23927
23927
  if (!this._baseURLIsExplicit && this._authState.baseURL && this.baseURL !== this._authState.baseURL) {
23928
23928
  this.baseURL = this._authState.baseURL;
23929
23929
  }
23930
- const url = this.buildURL(path5, query, defaultBaseURL);
23930
+ const url = this.buildURL(path5, query2, defaultBaseURL);
23931
23931
  if ("timeout" in options)
23932
23932
  validatePositiveInteger("timeout", options.timeout);
23933
23933
  options.timeout = options.timeout ?? this.timeout;
@@ -24045,8 +24045,8 @@ function stripFallbackBlocks(body) {
24045
24045
  function betaRefusalFallbackMiddleware(fallbacks, options = {}) {
24046
24046
  let warnedMissingState = false;
24047
24047
  return async (request, next, ctx) => {
24048
- const [path5, query] = (ctx.options?.path ?? "").split("?");
24049
- if (fallbacks.length === 0 || ctx.options?.method !== "post" || path5 !== "/v1/messages" || new URLSearchParams(query).get("beta") !== "true" || typeof ctx.options.body !== "object" || ctx.options.body == null) {
24048
+ const [path5, query2] = (ctx.options?.path ?? "").split("?");
24049
+ if (fallbacks.length === 0 || ctx.options?.method !== "post" || path5 !== "/v1/messages" || new URLSearchParams(query2).get("beta") !== "true" || typeof ctx.options.body !== "object" || ctx.options.body == null) {
24050
24050
  return next(request);
24051
24051
  }
24052
24052
  if (ctx.options.body.fallbacks != null) {
@@ -25973,7 +25973,7 @@ function splitProgressChunk(chunk) {
25973
25973
  }
25974
25974
  function shStream(cmd, args, opts = {}) {
25975
25975
  const cap = opts.maxStderrBytes ?? 64 * 1024;
25976
- return new Promise((resolve4, reject) => {
25976
+ return new Promise((resolve5, reject) => {
25977
25977
  void (async () => {
25978
25978
  const spawn6 = opts.spawnFn ?? (await import("child_process")).spawn;
25979
25979
  const child = spawn6(cmd, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
@@ -25991,7 +25991,7 @@ function shStream(cmd, args, opts = {}) {
25991
25991
  child.on("error", (err) => reject(err));
25992
25992
  child.on("close", (code, signal) => {
25993
25993
  if (code === 0) {
25994
- resolve4({ stdout: stdout.trim(), stderr });
25994
+ resolve5({ stdout: stdout.trim(), stderr });
25995
25995
  return;
25996
25996
  }
25997
25997
  const err = new Error(
@@ -26058,6 +26058,36 @@ function isCommandUnavailable(facts, countsRead) {
26058
26058
  return false;
26059
26059
  return facts.stdout.trim().length === 0 && EXEC_FAILURE.test(facts.stderr);
26060
26060
  }
26061
+ function isMissingSystemDependency(facts, countsRead) {
26062
+ if (countsRead)
26063
+ return null;
26064
+ if (facts.exitCode === 0 || facts.exitCode === null)
26065
+ return null;
26066
+ const out = `${facts.stdout}
26067
+ ${facts.stderr}`;
26068
+ for (const pattern of MISSING_SYSTEM_DEPENDENCY) {
26069
+ const hit = pattern.exec(out);
26070
+ if (hit)
26071
+ return hit[0];
26072
+ }
26073
+ return null;
26074
+ }
26075
+ function isOfflineBuildGap(facts, counts) {
26076
+ if (facts.runtime !== "jvm")
26077
+ return null;
26078
+ if (counts !== null && counts.tests_failed > 0)
26079
+ return null;
26080
+ if (facts.exitCode === 0 || facts.exitCode === null)
26081
+ return null;
26082
+ const out = `${facts.stdout}
26083
+ ${facts.stderr}`;
26084
+ for (const pattern of OFFLINE_BUILD_GAP) {
26085
+ const hit = pattern.exec(out);
26086
+ if (hit)
26087
+ return hit[0];
26088
+ }
26089
+ return null;
26090
+ }
26061
26091
  function classifyVerification(facts) {
26062
26092
  if (facts.timedOut) {
26063
26093
  return {
@@ -26082,6 +26112,22 @@ function classifyVerification(facts) {
26082
26112
  reason: `the test command could not be invoked (exit ${String(facts.exitCode)}). This is an environment failure on our side \u2014 a missing interpreter, a binary outside PATH, or a dependency installed somewhere the command cannot see it. The repo has not been judged.`
26083
26113
  };
26084
26114
  }
26115
+ const missing = isMissingSystemDependency(facts, counts !== null);
26116
+ if (missing !== null) {
26117
+ return {
26118
+ outcome: "test-command-unavailable",
26119
+ counts,
26120
+ reason: `the build needs a system library or header our image does not carry (${missing}). This is a gap in our environment, not a failure in the repo. The repo has not been judged.`
26121
+ };
26122
+ }
26123
+ const gap = isOfflineBuildGap(facts, counts);
26124
+ if (gap !== null) {
26125
+ return {
26126
+ outcome: "test-command-unavailable",
26127
+ counts,
26128
+ reason: `the build stopped on something our install step did not fetch (${gap}). The test step has no network, so this is a gap in our environment, not a failure in the repo. The repo has not been judged.`
26129
+ };
26130
+ }
26085
26131
  if (counts !== null) {
26086
26132
  if (counts.tests_failed > 0) {
26087
26133
  return {
@@ -26134,12 +26180,21 @@ function isGreen(outcome) {
26134
26180
  return outcome === "completed";
26135
26181
  }
26136
26182
  function isOurFault(outcome) {
26137
- return outcome === "test-command-unavailable" || outcome === "counts-unparsed";
26183
+ return OUR_FAULT[outcome];
26138
26184
  }
26139
- var int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE;
26185
+ var VERIFICATION_OUTCOMES, int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, OUR_FAULT;
26140
26186
  var init_classify2 = __esm({
26141
26187
  "../../packages/envrun/dist/classify.js"() {
26142
26188
  "use strict";
26189
+ VERIFICATION_OUTCOMES = [
26190
+ "completed",
26191
+ "tests-failed",
26192
+ "no-tests-observed",
26193
+ "counts-unparsed",
26194
+ "test-command-unavailable",
26195
+ "environment-exhausted",
26196
+ "budget-exceeded"
26197
+ ];
26143
26198
  int = (m, i = 1) => m ? Number(m[i]) : 0;
26144
26199
  withSuiteFailures = (counts, suiteLine) => {
26145
26200
  if (counts.tests_failed > 0 || !suiteLine)
@@ -26239,6 +26294,68 @@ var init_classify2 = __esm({
26239
26294
  return { tests_passed: int(passed), tests_failed: int(failed) + int(errors) };
26240
26295
  }
26241
26296
  },
26297
+ {
26298
+ // Maven surefire's per-module summary (TERM-1122), measured against surefire
26299
+ // 3.2.5 through `th run`:
26300
+ //
26301
+ // [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
26302
+ //
26303
+ // The per-CLASS lines carry the same counts followed by `, Time elapsed: …`, so
26304
+ // the end anchor after `Skipped` is what keeps them out; counting both would
26305
+ // double every test. One summary prints per module, so they are SUMMED. An
26306
+ // error is a test that could not run to an assertion, counted as failed so a
26307
+ // green verdict is impossible while one is outstanding, as pytest's are.
26308
+ runner: "maven surefire",
26309
+ read: (out) => {
26310
+ const lines = [
26311
+ ...out.matchAll(/^(?:\[(?:INFO|WARNING|ERROR)\] )?Tests run: (\d+), Failures: (\d+), Errors: (\d+), Skipped: (\d+)(?:, Flakes: \d+)?\s*$/gm)
26312
+ ];
26313
+ if (lines.length === 0)
26314
+ return null;
26315
+ let passed = 0;
26316
+ let failed = 0;
26317
+ for (const m of lines) {
26318
+ const [run2, failures, errors, skipped] = [m[1], m[2], m[3], m[4]].map(Number);
26319
+ failed += failures + errors;
26320
+ passed += Math.max(0, run2 - failures - errors - skipped);
26321
+ }
26322
+ return { tests_passed: passed, tests_failed: failed };
26323
+ }
26324
+ },
26325
+ {
26326
+ // Gradle prints no test counts when every test passes, so envrun installs an
26327
+ // init script that prints one line per Test task (`gradleTestSummary.ts`,
26328
+ // TERM-1122). The marker is ours; its format is fixed there.
26329
+ runner: "gradle",
26330
+ read: (out) => {
26331
+ const lines = [
26332
+ ...out.matchAll(/^terminalhire-gradle-tests: task=\S+ total=(\d+) passed=(\d+) failed=(\d+) skipped=(\d+)\s*$/gm)
26333
+ ];
26334
+ if (lines.length === 0)
26335
+ return null;
26336
+ return {
26337
+ tests_passed: lines.reduce((sum, m) => sum + Number(m[2]), 0),
26338
+ tests_failed: lines.reduce((sum, m) => sum + Number(m[3]), 0)
26339
+ };
26340
+ }
26341
+ },
26342
+ {
26343
+ // CTest's closing summary: `50% tests passed, 1 tests failed out of 2`. Measured
26344
+ // on cmake 3.25 in the cpp image (TERM-1123); the singular `1 test failed` is
26345
+ // CTest's own form for one. Anchored at line start, so a log line quoting it
26346
+ // is not read as the summary. Meson and a bare `make test` print no summary we
26347
+ // can trust and stay unparsed.
26348
+ runner: "ctest",
26349
+ read: (out) => {
26350
+ if (/^No tests were found!!!$/m.test(out))
26351
+ return { tests_passed: 0, tests_failed: 0 };
26352
+ const m = /^\d+% tests passed, (\d+) tests? failed out of (\d+)$/m.exec(out);
26353
+ if (!m)
26354
+ return null;
26355
+ const failed = Number(m[1]);
26356
+ return { tests_passed: Number(m[2]) - failed, tests_failed: failed };
26357
+ }
26358
+ },
26242
26359
  {
26243
26360
  // mocha: ` 440 passing (1s)` and ` 2 failing`
26244
26361
  runner: "mocha",
@@ -26367,6 +26484,35 @@ var init_classify2 = __esm({
26367
26484
  "\\d+\\s+examples?,\\s+(?!0\\b)\\d+\\s+failures?"
26368
26485
  // rspec's summary line
26369
26486
  ].join("|"), "m");
26487
+ MISSING_SYSTEM_DEPENDENCY = [
26488
+ /^(?:\S*\/)?ld(?:\.\w+)?: cannot find -l[\w.+-]+/m,
26489
+ /^\S+:\d+(?::\d+)?: fatal error: [\w./+-]+\.(?:h|hh|hpp|hxx): No such file or directory$/m,
26490
+ /^\S+: error while loading shared libraries: lib[\w.+-]*: cannot open shared object file/m
26491
+ ];
26492
+ OFFLINE_BUILD_GAP = [
26493
+ // Maven, `-o`: `Cannot access central (…) in offline mode and the artifact … has
26494
+ // not been downloaded from it before.`
26495
+ /in offline mode and the artifact \S+ has not been downloaded from it before/,
26496
+ // Maven without `-o`, as a CI-derived `mvn test` runs: it tries the network.
26497
+ /Could not transfer artifact \S+ from\/to /,
26498
+ /Plugin \S+ or one of its dependencies could not be resolved/,
26499
+ // Gradle, `--offline`.
26500
+ /No cached version (?:listing )?(?:of|for) \S+ available for offline mode/,
26501
+ // Gradle without `--offline`, and the wrapper fetching its own distribution.
26502
+ /Could not GET 'https?:\/\//,
26503
+ /^Downloading https:\/\/services\.gradle\.org\/distributions\//m,
26504
+ /No matching toolchains found for requested specification/,
26505
+ /Cannot find a Java installation on your machine matching/
26506
+ ];
26507
+ OUR_FAULT = {
26508
+ completed: false,
26509
+ "tests-failed": false,
26510
+ "no-tests-observed": false,
26511
+ "budget-exceeded": false,
26512
+ "counts-unparsed": true,
26513
+ "test-command-unavailable": true,
26514
+ "environment-exhausted": true
26515
+ };
26370
26516
  }
26371
26517
  });
26372
26518
 
@@ -26400,7 +26546,8 @@ function scrubEnv(source, opts) {
26400
26546
  if (!tmpDir.startsWith("/")) {
26401
26547
  throw new SandboxEnvError(`tmpDir must be an absolute path, got ${JSON.stringify(tmpDir)}`);
26402
26548
  }
26403
- for (const realHome of realHomeCandidates(source)) {
26549
+ const homesToCompare = opts.jailHomeNamespace === "guest" ? [] : realHomeCandidates(source);
26550
+ for (const realHome of homesToCompare) {
26404
26551
  if (jailHome === realHome || jailHome === `${realHome}/`) {
26405
26552
  throw new SandboxEnvError("jailHome is the real HOME \u2014 the sandbox would read the user profile");
26406
26553
  }
@@ -26432,6 +26579,8 @@ function scrubEnv(source, opts) {
26432
26579
  env["GOCACHE"] = join18(jailHome, ".cache", "go-build");
26433
26580
  env["GOFLAGS"] = "-modcacherw";
26434
26581
  env["CARGO_HOME"] = join18(jailHome, ".cargo");
26582
+ env["MAVEN_OPTS"] = `-Dmaven.repo.local=${join18(jailHome, ".m2", "repository")}`;
26583
+ env["GRADLE_USER_HOME"] = join18(jailHome, ".gradle");
26435
26584
  const bundle = join18(jailHome, ".bundle");
26436
26585
  env["GEM_HOME"] = bundle;
26437
26586
  env["BUNDLE_PATH"] = bundle;
@@ -26487,8 +26636,8 @@ var init_reap = __esm({
26487
26636
 
26488
26637
  // ../../packages/containment/dist/fence.js
26489
26638
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
26490
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, realpathSync, writeFileSync as writeFileSync10 } from "fs";
26491
- import { dirname as dirname6, isAbsolute as isAbsolute3, join as join19, posix as posix2 } from "path";
26639
+ import { existsSync as existsSync9, lchownSync, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, realpathSync, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
26640
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute3, join as join19, posix as posix2, resolve as resolve4, sep as sep5 } from "path";
26492
26641
  import { fileURLToPath as fileURLToPath2 } from "url";
26493
26642
  function canonical(path5, label) {
26494
26643
  if (!isAbsolute3(path5)) {
@@ -26515,7 +26664,7 @@ function resolverFor(domain) {
26515
26664
  function pathDomainOf(spec) {
26516
26665
  return spec.pathDomain ?? "local";
26517
26666
  }
26518
- function jailPasswd(uid = idOrNull("getuid"), gid = idOrNull("getgid")) {
26667
+ function jailPasswd(uid = localGuestUser()?.uid ?? null, gid = localGuestUser()?.gid ?? null) {
26519
26668
  const rows = ["root:x:0:0:root:/root:/usr/sbin/nologin"];
26520
26669
  if (uid !== null && gid !== null && uid !== 0) {
26521
26670
  rows.push(`${FENCE_USER}:x:${uid}:${gid}:fenced build user:${GUEST_JAIL}:/usr/sbin/nologin`);
@@ -26524,7 +26673,7 @@ function jailPasswd(uid = idOrNull("getuid"), gid = idOrNull("getgid")) {
26524
26673
  return `${rows.join("\n")}
26525
26674
  `;
26526
26675
  }
26527
- function jailGroup(gid = idOrNull("getgid")) {
26676
+ function jailGroup(gid = localGuestUser()?.gid ?? null) {
26528
26677
  const rows = ["root:x:0:"];
26529
26678
  if (gid !== null && gid !== 0)
26530
26679
  rows.push(`${FENCE_USER}:x:${gid}:`);
@@ -26536,19 +26685,139 @@ function idOrNull(fn) {
26536
26685
  const f = process[fn];
26537
26686
  return typeof f === "function" ? f.call(process) : null;
26538
26687
  }
26539
- function buildJail(root, guestUser) {
26688
+ function hostIds() {
26689
+ const uid = idOrNull("getuid");
26690
+ const gid = idOrNull("getgid");
26691
+ return uid === null || gid === null ? null : { uid, gid };
26692
+ }
26693
+ function localGuestUser(ids2 = hostIds()) {
26694
+ if (ids2 === null)
26695
+ return null;
26696
+ return ids2.uid === 0 ? REMAPPED_GUEST_USER : ids2;
26697
+ }
26698
+ function chownTree(dir, user) {
26699
+ const stack = [dir];
26700
+ for (let p = stack.pop(); p !== void 0; p = stack.pop()) {
26701
+ lchownSync(p, user.uid, user.gid);
26702
+ if (lstatSync3(p).isDirectory()) {
26703
+ for (const entry of readdirSync2(p))
26704
+ stack.push(join19(p, entry));
26705
+ }
26706
+ }
26707
+ }
26708
+ function canonicalPath(p) {
26709
+ let head = resolve4(p);
26710
+ const tail2 = [];
26711
+ for (; ; ) {
26712
+ try {
26713
+ return join19(realpathSync.native(head), ...tail2.reverse());
26714
+ } catch {
26715
+ const up = dirname6(head);
26716
+ if (up === head)
26717
+ return resolve4(p);
26718
+ tail2.push(basename4(head));
26719
+ head = up;
26720
+ }
26721
+ }
26722
+ }
26723
+ function statOrAbsent(p, fn) {
26724
+ try {
26725
+ return fn();
26726
+ } catch (err) {
26727
+ const code = err.code;
26728
+ if (code === "ENOENT" || code === "ENOTDIR")
26729
+ return void 0;
26730
+ throw new FenceError(`cannot stat ${JSON.stringify(p)}: ${err.message}`, {
26731
+ cause: err
26732
+ });
26733
+ }
26734
+ }
26735
+ function fileIdentity(p) {
26736
+ const st = statOrAbsent(p, () => statSync3(p, { bigint: true }));
26737
+ return st === void 0 ? null : { dev: st.dev, ino: st.ino };
26738
+ }
26739
+ function isOrContains(outer, inner) {
26740
+ const fold = process.platform === "win32" ? (s) => s.toLowerCase() : (s) => s;
26741
+ const o = fold(canonicalPath(outer));
26742
+ const i = fold(canonicalPath(inner));
26743
+ const prefix = o.endsWith(sep5) ? o : o + sep5;
26744
+ if (i === o || i.startsWith(prefix))
26745
+ return true;
26746
+ const target = fileIdentity(outer);
26747
+ if (target === null || target.ino === 0n)
26748
+ return false;
26749
+ for (const start of physicalSpellings(canonicalPath(inner))) {
26750
+ for (let a = start; ; a = dirname6(a)) {
26751
+ const id = fileIdentity(a);
26752
+ if (id !== null && id.ino !== 0n && id.dev === target.dev && id.ino === target.ino) {
26753
+ return true;
26754
+ }
26755
+ if (dirname6(a) === a)
26756
+ break;
26757
+ }
26758
+ }
26759
+ return false;
26760
+ }
26761
+ function physicalSpellings(p) {
26762
+ if (process.platform !== "darwin" || p.startsWith(DARWIN_DATA_VOLUME + sep5))
26763
+ return [p];
26764
+ const onData = DARWIN_DATA_VOLUME + p;
26765
+ const logical = fileIdentity(p);
26766
+ const physical = fileIdentity(onData);
26767
+ const same = logical !== null && physical !== null && logical.dev === physical.dev && logical.ino === physical.ino;
26768
+ return same ? [p, onData] : [p];
26769
+ }
26770
+ function realHomes(who) {
26771
+ try {
26772
+ return realHomeCandidates(process.env);
26773
+ } catch (err) {
26774
+ throw new FenceError(`${who}: ${err.message}`, { cause: err });
26775
+ }
26776
+ }
26777
+ function assertHoldsNoRealHome(path5, what, who) {
26778
+ for (const home of realHomes(who)) {
26779
+ if (isOrContains(path5, home)) {
26780
+ throw new FenceError(`${who}: ${what} ${JSON.stringify(path5)} is or contains the real home ${JSON.stringify(home)} \u2014 the jail would expose the developer's profile`);
26781
+ }
26782
+ }
26783
+ }
26784
+ function assertNoForeignLinks(paths) {
26785
+ for (const p of paths) {
26786
+ const st = statOrAbsent(p, () => lstatSync3(p));
26787
+ if (st?.isSymbolicLink() === true) {
26788
+ throw new FenceError(`buildJail: ${JSON.stringify(p)} is a pre-existing symlink \u2014 buildJail would write through it, and a jail directory is never a link`);
26789
+ }
26790
+ if (st?.isFile() === true && st.nlink > 1) {
26791
+ throw new FenceError(`buildJail: ${JSON.stringify(p)} is a pre-existing file with ${String(st.nlink)} hard links \u2014 buildJail would overwrite an inode another path shares`);
26792
+ }
26793
+ }
26794
+ }
26795
+ function buildJail(root, guestUser, io = {}) {
26796
+ const ids2 = io.ids === void 0 ? hostIds() : io.ids;
26797
+ const guest = guestUser ?? localGuestUser(ids2);
26540
26798
  const jail = join19(root, JAIL_SEGMENT);
26541
26799
  const tmp = join19(root, JAIL_TMP_SEGMENT);
26542
- for (const dir of [jail, tmp, join19(jail, ".config"), join19(jail, ".cache"), join19(jail, ".npm")]) {
26800
+ const dirs = [jail, tmp, join19(jail, ".config"), join19(jail, ".cache"), join19(jail, ".npm")];
26801
+ const files = [".npmrc", JAIL_PASSWD_FILE, JAIL_GROUP_FILE, ".gitconfig"].map((f) => join19(jail, f));
26802
+ assertHoldsNoRealHome(root, "the jail root", "buildJail");
26803
+ assertNoForeignLinks([...dirs, ...files]);
26804
+ assertHoldsNoRealHome(jail, "the jail directory", "buildJail");
26805
+ assertHoldsNoRealHome(tmp, "the jail tmp directory", "buildJail");
26806
+ for (const dir of dirs) {
26543
26807
  mkdirSync3(dir, { recursive: true });
26544
26808
  }
26545
26809
  writeFileSync10(join19(jail, ".npmrc"), "", "utf8");
26546
- writeFileSync10(join19(jail, JAIL_PASSWD_FILE), guestUser ? jailPasswd(guestUser.uid, guestUser.gid) : jailPasswd(), "utf8");
26547
- writeFileSync10(join19(jail, JAIL_GROUP_FILE), guestUser ? jailGroup(guestUser.gid) : jailGroup(), "utf8");
26810
+ writeFileSync10(join19(jail, JAIL_PASSWD_FILE), guest ? jailPasswd(guest.uid, guest.gid) : jailPasswd(null, null), "utf8");
26811
+ writeFileSync10(join19(jail, JAIL_GROUP_FILE), guest ? jailGroup(guest.gid) : jailGroup(null), "utf8");
26548
26812
  writeFileSync10(join19(jail, ".gitconfig"), '[user]\n name = sandbox\n email = sandbox@localhost\n[safe]\n directory = *\n[url "https://github.com/"]\n insteadOf = ssh://git@github.com/\n insteadOf = git@github.com:\n', "utf8");
26813
+ if (ids2?.uid === 0 && guest && guest.uid !== 0) {
26814
+ const chown = io.chown ?? chownTree;
26815
+ chown(jail, guest);
26816
+ chown(tmp, guest);
26817
+ }
26549
26818
  return { jail, tmp };
26550
26819
  }
26551
- var FenceError, ContainmentError, ContainmentRefusalError, JAIL_PASSWD_FILE, JAIL_GROUP_FILE, GUEST_JAIL, FENCE_USER, JAIL_SEGMENT, JAIL_TMP_SEGMENT;
26820
+ var FenceError, ContainmentError, ContainmentRefusalError, JAIL_PASSWD_FILE, JAIL_GROUP_FILE, GUEST_JAIL, FENCE_USER, REMAPPED_GUEST_USER, JAIL_SEGMENT, JAIL_TMP_SEGMENT, DARWIN_DATA_VOLUME;
26552
26821
  var init_fence = __esm({
26553
26822
  "../../packages/containment/dist/fence.js"() {
26554
26823
  "use strict";
@@ -26569,8 +26838,10 @@ var init_fence = __esm({
26569
26838
  JAIL_GROUP_FILE = ".fence-group";
26570
26839
  GUEST_JAIL = "/fenced/jail";
26571
26840
  FENCE_USER = "fenced";
26841
+ REMAPPED_GUEST_USER = { uid: 1e3, gid: 1e3 };
26572
26842
  JAIL_SEGMENT = "jail";
26573
26843
  JAIL_TMP_SEGMENT = "tmp";
26844
+ DARWIN_DATA_VOLUME = "/System/Volumes/Data";
26574
26845
  }
26575
26846
  });
26576
26847
 
@@ -26723,7 +26994,27 @@ var init_dockerClient = __esm({
26723
26994
  // ../../packages/containment/dist/egressProxy.js
26724
26995
  import net from "net";
26725
26996
  import http from "http";
26726
- var DEFAULT_INSTALL_ALLOWLIST;
26997
+ function parseDeniedHosts(output) {
26998
+ const seen = /* @__PURE__ */ new Set();
26999
+ for (const line of output.split(/\r?\n/)) {
27000
+ if (!line.startsWith(DENIAL_PREFIX))
27001
+ continue;
27002
+ let parsed;
27003
+ try {
27004
+ parsed = JSON.parse(line.slice(DENIAL_PREFIX.length));
27005
+ } catch {
27006
+ continue;
27007
+ }
27008
+ const host = parsed?.host;
27009
+ if (typeof host !== "string")
27010
+ continue;
27011
+ const h = host.toLowerCase().replace(/\.$/, "");
27012
+ if (HOSTNAME.test(h))
27013
+ seen.add(h);
27014
+ }
27015
+ return [...seen];
27016
+ }
27017
+ var DEFAULT_INSTALL_ALLOWLIST, DENIAL_PREFIX, HOSTNAME;
26727
27018
  var init_egressProxy = __esm({
26728
27019
  "../../packages/containment/dist/egressProxy.js"() {
26729
27020
  "use strict";
@@ -26749,6 +27040,18 @@ var init_egressProxy = __esm({
26749
27040
  // NuGet; the coupling test refuses a mapped runtime whose registry is unreachable, and
26750
27041
  // it caught this one the moment the image landed.
26751
27042
  "api.nuget.org",
27043
+ // TERM-1122, added with the `jvm` image mapping, each from a MEASURED denial
27044
+ // through `th run` (the proxy's 403 in the build's own output), not a guess:
27045
+ // - Maven Central, where both `mvn` and Gradle's `mavenCentral()` resolve.
27046
+ // - `services.gradle.org`, where a Gradle wrapper fetches its distribution.
27047
+ // - `release-assets.githubusercontent.com`, where that fetch LANDS. Measured
27048
+ // chain: services.gradle.org 307 -> github.com 302 -> this host 200. The
27049
+ // `github.com` note below keeps `githubusercontent.com` denied until a real
27050
+ // install needs it; this is that install. Only this one host: raw blobs and
27051
+ // `objects.githubusercontent.com` stay denied.
27052
+ "repo.maven.apache.org",
27053
+ "services.gradle.org",
27054
+ "release-assets.githubusercontent.com",
26752
27055
  // Git dependencies. Approved by Eric 2026-07-22 after a live run measured the
26753
27056
  // chokepoint working under load (20 events, 16 allowed / 4 denied by host).
26754
27057
  //
@@ -26760,24 +27063,47 @@ var init_egressProxy = __esm({
26760
27063
  // allowlist than breadth.
26761
27064
  //
26762
27065
  // What it does NOT grant: `githubusercontent.com` is a DIFFERENT domain, so
26763
- // release assets and raw blobs stay denied. If a real install needs them,
26764
- // that should arrive as a measured egress denial, not a pre-emptive guess.
27066
+ // raw blobs and `objects.githubusercontent.com` stay denied; only
27067
+ // `release-assets.githubusercontent.com` is listed, above, for the Gradle
27068
+ // wrapper. If a real install needs more, that should arrive as a measured
27069
+ // egress denial, not a pre-emptive guess.
27070
+ //
27071
+ // WHO RUNS INSTALL SCRIPTS THROUGH THIS GRANT (TERM-1157). This comment used
27072
+ // to say install "already runs with `--ignore-scripts`". That is true of
27073
+ // `apps/merge-agent` (`src/sandbox/plan.ts` adds the flag on every branch)
27074
+ // and false of `th run`: `packages/envspec/src/manifest.ts` derives a plain
27075
+ // `npm ci`, so there every dependency's `preinstall`/`install`/`postinstall`
27076
+ // executes behind this proxy. TERM-1157 chose to keep them running.
26765
27077
  //
26766
- // Proportionality: install already runs with `--ignore-scripts`, and the
26767
- // fenced environment carries no GitHub token, so the added capability is
26768
- // "fetch a public tarball or ref", not "act as us".
27078
+ // For `th run`, then, the grant reaches code the repo's dependencies chose,
27079
+ // not only the package manager. What bounds it:
27080
+ // - no credential of ours is in the fence: `auditEnv` (`env.ts`) refuses to
27081
+ // spawn when a variable's name or a GitHub-token-shaped value says
27082
+ // otherwise, so a script cannot act on GitHub as us;
27083
+ // - the fence holds the repo's tree and its dependencies, nothing else of
27084
+ // ours;
27085
+ // - every step after install runs with no network at all.
27086
+ // What it does NOT bound: a script can download and run any public GitHub
27087
+ // content, and one that brings its author's own token can push what the
27088
+ // fence holds to its author's account. `registry.npmjs.org` above gives the
27089
+ // same script the same reach (download anything, publish with its own
27090
+ // token), so this entry adds a second address for a capability the install
27091
+ // already has rather than a new one. That is the case for keeping it; it is
27092
+ // not a claim that GitHub is read-only from here.
26769
27093
  "github.com"
26770
27094
  ];
27095
+ DENIAL_PREFIX = "proxy-denied ";
27096
+ HOSTNAME = /^[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/;
26771
27097
  }
26772
27098
  });
26773
27099
 
26774
27100
  // ../../packages/containment/dist/container.js
26775
27101
  import { fileURLToPath as fileURLToPath3 } from "url";
26776
27102
  import { dirname as dirname7, join as join20 } from "path";
26777
- import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync10, mkdtempSync, rmSync as rmSync6 } from "fs";
27103
+ import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync10, mkdtempSync, rmSync as rmSync6, statSync as statSync4 } from "fs";
26778
27104
  import { tmpdir } from "os";
26779
27105
  function scrubEnvPathsFor(containmentKind, host) {
26780
- return containmentKind === "container" ? { jailHome: GUEST.jail, tmpDir: GUEST.tmp } : { jailHome: host.jail, tmpDir: host.tmp };
27106
+ return containmentKind === "container" ? { jailHome: GUEST.jail, tmpDir: GUEST.tmp, jailHomeNamespace: "guest" } : { jailHome: host.jail, tmpDir: host.tmp, jailHomeNamespace: "host" };
26781
27107
  }
26782
27108
  function buildTranslation(spec) {
26783
27109
  const roots = [
@@ -26785,11 +27111,11 @@ function buildTranslation(spec) {
26785
27111
  { raw: spec.jail, guest: GUEST.jail, label: "jail" },
26786
27112
  { raw: spec.tmp, guest: GUEST.tmp, label: "tmp" }
26787
27113
  ];
26788
- const resolve4 = resolverFor(pathDomainOf(spec));
27114
+ const resolve5 = resolverFor(pathDomainOf(spec));
26789
27115
  const pairs = [];
26790
27116
  const seen = /* @__PURE__ */ new Set();
26791
27117
  for (const { raw, guest, label } of roots) {
26792
- for (const host of [raw, resolve4(raw, label)]) {
27118
+ for (const host of [raw, resolve5(raw, label)]) {
26793
27119
  if (seen.has(host))
26794
27120
  continue;
26795
27121
  seen.add(host);
@@ -26872,21 +27198,36 @@ function labelArgs(labels) {
26872
27198
  }
26873
27199
  return out;
26874
27200
  }
26875
- function hostUserFlag() {
26876
- const uid = typeof process.getuid === "function" ? process.getuid() : null;
26877
- const gid = typeof process.getgid === "function" ? process.getgid() : null;
26878
- if (uid === null || gid === null)
27201
+ function hostUserFlag(ids2 = hostIds()) {
27202
+ const guest = localGuestUser(ids2);
27203
+ if (guest === null)
26879
27204
  return [];
26880
- return [`--user=${uid}:${gid}`];
27205
+ return [`--user=${String(guest.uid)}:${String(guest.gid)}`];
26881
27206
  }
26882
- function guestUserFlag(spec) {
27207
+ function guestUserFlag(spec, ids2 = hostIds()) {
26883
27208
  const declared = spec.guestUser;
26884
27209
  if (declared)
26885
27210
  return [`--user=${declared.uid}:${declared.gid}`];
26886
27211
  if (pathDomainOf(spec) === "venue") {
26887
27212
  throw new FenceError("a venue-domain spec must declare guestUser: the tree is owned by the account that staged it on the venue, and this process\u2019s uid is a fact about a different machine. Handing the guest the local id gives it no write access to its own clone, and that EACCES reaches the classifier as the developer\u2019s suite failing.");
26888
27213
  }
26889
- return hostUserFlag();
27214
+ return hostUserFlag(ids2);
27215
+ }
27216
+ function assertTreeHandedToGuest(spec, ids2 = hostIds(), ownerOf2 = (path5) => statSync4(path5).uid) {
27217
+ if (pathDomainOf(spec) !== "local" || ids2?.uid !== 0)
27218
+ return;
27219
+ const guest = spec.guestUser ?? localGuestUser(ids2);
27220
+ if (guest === null || guest.uid === 0)
27221
+ return;
27222
+ for (const [label, path5] of [
27223
+ ["clone", spec.clone],
27224
+ ["jail", spec.jail]
27225
+ ]) {
27226
+ const owner = ownerOf2(path5);
27227
+ if (owner !== guest.uid) {
27228
+ throw new FenceError(`refusing to run: this process is root, so the guest runs as uid ${String(guest.uid)}, but the ${label} at ${path5} is owned by uid ${String(owner)}. The guest could not write its own tree and the EACCES would read as the developer\u2019s suite failing. Hand it over first (chownTree, or buildJail for the jail).`);
27229
+ }
27230
+ }
26890
27231
  }
26891
27232
  function validateVolumeName(name, label) {
26892
27233
  if (!VOLUME_NAME.test(name) || name.length > VOLUME_NAME_MAX) {
@@ -26896,14 +27237,17 @@ function validateVolumeName(name, label) {
26896
27237
  }
26897
27238
  function stageMounts(spec) {
26898
27239
  const domain = pathDomainOf(spec);
26899
- const resolve4 = resolverFor(domain);
27240
+ const resolve5 = resolverFor(domain);
26900
27241
  const volumes = spec.stageVolumes;
26901
27242
  if (domain === "venue") {
27243
+ if (spec.cloneVolume !== void 0) {
27244
+ throw new FenceError("a venue-domain spec must not declare cloneVolume: the venue names both of its volumes in stageVolumes, and a second name for the clone would leave two answers to which one is mounted.");
27245
+ }
26902
27246
  if (volumes === void 0) {
26903
27247
  throw new FenceError("a venue-domain spec must declare stageVolumes: every writable host path on the venue is mounted noexec, so a bind mount of the staged clone cannot run the binaries an install step downloads (esbuild, swc, sharp, node-gyp \u2014 EACCES). The venue that staged the tree names the volumes it populated; a spec without them would reproduce that EACCES and report it as the developer\u2019s suite failing.");
26904
27248
  }
26905
- resolve4(spec.clone, "clone");
26906
- resolve4(spec.jail, "jail");
27249
+ resolve5(spec.clone, "clone");
27250
+ resolve5(spec.jail, "jail");
26907
27251
  return [
26908
27252
  `--volume=${validateVolumeName(volumes.clone, "the clone volume")}:${GUEST.clone}:rw`,
26909
27253
  `--volume=${validateVolumeName(volumes.jail, "the jail volume")}:${GUEST.jail}:rw`
@@ -26912,20 +27256,24 @@ function stageMounts(spec) {
26912
27256
  if (volumes !== void 0) {
26913
27257
  throw new FenceError("a local-domain spec must not declare stageVolumes: the paths are on this machine and ARE the mount sources, and nothing on the local path populates a volume \u2014 honouring the field would mount an empty clone. Volumes exist for the venue\u2019s noexec host only.");
26914
27258
  }
27259
+ const clone = resolve5(spec.clone, "clone");
27260
+ const cloneSource = spec.cloneVolume === void 0 ? clone : validateVolumeName(spec.cloneVolume, "the clone volume");
27261
+ const jail = resolve5(spec.jail, "jail");
27262
+ assertHoldsNoRealHome(jail, "the jail bind source", "stageMounts");
26915
27263
  return [
26916
- `--volume=${resolve4(spec.clone, "clone")}:${GUEST.clone}:rw`,
26917
- `--volume=${resolve4(spec.jail, "jail")}:${GUEST.jail}:rw`
27264
+ `--volume=${cloneSource}:${GUEST.clone}:rw`,
27265
+ `--volume=${jail}:${GUEST.jail}:rw`
26918
27266
  ];
26919
27267
  }
26920
27268
  function guestIdentityMounts(spec) {
26921
27269
  if (guestUserFlag(spec).length === 0)
26922
27270
  return [];
26923
27271
  const domain = pathDomainOf(spec);
26924
- const resolve4 = resolverFor(domain);
27272
+ const resolve5 = resolverFor(domain);
26925
27273
  const under = domain === "venue" ? venueJoin : join20;
26926
- const jail = resolve4(spec.jail, "jail");
26927
- const passwd = resolve4(under(jail, JAIL_PASSWD_FILE), "the jail passwd file");
26928
- const group = resolve4(under(jail, JAIL_GROUP_FILE), "the jail group file");
27274
+ const jail = resolve5(spec.jail, "jail");
27275
+ const passwd = resolve5(under(jail, JAIL_PASSWD_FILE), "the jail passwd file");
27276
+ const group = resolve5(under(jail, JAIL_GROUP_FILE), "the jail group file");
26929
27277
  return [`--volume=${passwd}:/etc/passwd:ro`, `--volume=${group}:/etc/group:ro`];
26930
27278
  }
26931
27279
  function containerArgs(spec, env, opts) {
@@ -27040,6 +27388,9 @@ function containerArgs(spec, env, opts) {
27040
27388
  args.push(`--env=${k}=${url}`);
27041
27389
  }
27042
27390
  args.push("--env=NO_PROXY=", "--env=no_proxy=");
27391
+ const host = opts.net.proxyHost;
27392
+ const port = String(opts.net.proxyPort);
27393
+ args.push(`--env=JAVA_TOOL_OPTIONS=-Dhttp.proxyHost=${host} -Dhttp.proxyPort=${port} -Dhttps.proxyHost=${host} -Dhttps.proxyPort=${port} -Daether.connector.http.useSystemProperties=true`);
27043
27394
  }
27044
27395
  args.push("--");
27045
27396
  args.push(validateImage(opts.image ?? DEFAULT_CONTAINER_IMAGE));
@@ -27255,7 +27606,14 @@ async function startProxySidecar(d, allow, idBase, staged, labels) {
27255
27606
  await waitForProxyReady(d, proxyName);
27256
27607
  return {
27257
27608
  net: { network: netInt, proxyHost: proxyName, proxyPort: SIDECAR_PROXY_PORT },
27258
- teardown
27609
+ teardown,
27610
+ // A `docker logs` that fails yields no hosts. That loses the hint and
27611
+ // changes nothing else: the list only ever adds words to a reason.
27612
+ deniedHosts: () => {
27613
+ const logs = dockerSync(d, ["logs", proxyName]);
27614
+ return logs.ok ? parseDeniedHosts(`${logs.stdout}
27615
+ ${logs.stderr}`) : [];
27616
+ }
27259
27617
  };
27260
27618
  } catch (err) {
27261
27619
  teardown();
@@ -27321,6 +27679,7 @@ async function runContainedOn(d, spec, env, opts = {}) {
27321
27679
  assertDomainDeclared(d, spec);
27322
27680
  assertNotAmbientlySteered(d, spec);
27323
27681
  assertProxyStagedForVenue(spec, opts.proxyCode);
27682
+ assertTreeHandedToGuest(spec);
27324
27683
  const leaks = auditEnv(env);
27325
27684
  if (leaks.length > 0) {
27326
27685
  throw new FenceError(`refusing to spawn: environment carries credential material (${leaks.join(", ")})`);
@@ -27341,7 +27700,8 @@ async function runContainedOn(d, spec, env, opts = {}) {
27341
27700
  net: sidecar?.net,
27342
27701
  labels: opts.labels
27343
27702
  });
27344
- return await spawnWorkload(d, idBase, argv, opts.timeoutMs ?? 9e5);
27703
+ const result = await spawnWorkload(d, idBase, argv, opts.timeoutMs ?? 9e5);
27704
+ return sidecar ? { ...result, egressDenied: sidecar.deniedHosts() } : result;
27345
27705
  } finally {
27346
27706
  sidecar?.teardown();
27347
27707
  }
@@ -27372,7 +27732,10 @@ var init_container = __esm({
27372
27732
  "http_proxy",
27373
27733
  "https_proxy",
27374
27734
  "NO_PROXY",
27375
- "no_proxy"
27735
+ "no_proxy",
27736
+ // Carries the JVM's copy of the proxy (below), so an inherited value is dropped
27737
+ // for the same reason as the rest.
27738
+ "JAVA_TOOL_OPTIONS"
27376
27739
  ]);
27377
27740
  WINDOWS_DRIVE_ROOT = /^[A-Za-z]:[\\/]/;
27378
27741
  IMAGE_HOST = "(?:[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)+(?::[0-9]+)?|localhost(?::[0-9]+)?)";
@@ -27419,14 +27782,23 @@ var init_dist = __esm({
27419
27782
  });
27420
27783
 
27421
27784
  // ../../packages/envrun/dist/labels.js
27785
+ function runLabels(runId, callerLabels) {
27786
+ return { ...callerLabels ?? {}, [RUN_ID_LABEL_KEY]: runId, [RUN_LABEL_KEY]: "term-350" };
27787
+ }
27422
27788
  function censusTotal(c) {
27423
27789
  return c.containers.length + c.volumes.length + c.networks.length;
27424
27790
  }
27425
- function ids(docker3, args) {
27791
+ function query(docker3, args) {
27426
27792
  const res = docker3.sync([...args], { timeoutMs: 15e3 });
27427
- if (res.error || res.status !== 0)
27428
- return [];
27429
- return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
27793
+ if (res.error || res.status !== 0) {
27794
+ const why = res.error?.message ?? (res.stderr.trim() || `exit ${String(res.status)}`);
27795
+ return { ids: [], failure: `docker ${args.slice(0, 2).join(" ")}: ${why}` };
27796
+ }
27797
+ const ids2 = res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
27798
+ return { ids: ids2, failure: null };
27799
+ }
27800
+ function ids(docker3, args) {
27801
+ return query(docker3, args).ids;
27430
27802
  }
27431
27803
  function census(docker3, label) {
27432
27804
  const filter = `label=${label}`;
@@ -27436,30 +27808,73 @@ function census(docker3, label) {
27436
27808
  networks: ids(docker3, ["network", "ls", "-q", "--filter", filter])
27437
27809
  };
27438
27810
  }
27811
+ function censusReport(docker3, label) {
27812
+ const filter = `label=${label}`;
27813
+ const failures = [];
27814
+ const ask2 = (args) => {
27815
+ const q = query(docker3, args);
27816
+ if (q.failure !== null)
27817
+ failures.push(q.failure);
27818
+ return q.ids;
27819
+ };
27820
+ const taken = {
27821
+ containers: ask2(["ps", "-aq", "--filter", filter]),
27822
+ volumes: ask2(["volume", "ls", "-q", "--filter", filter]),
27823
+ networks: ask2(["network", "ls", "-q", "--filter", filter])
27824
+ };
27825
+ return failures.length === 0 ? { observed: true, census: taken, unobservedReason: null } : { observed: false, census: taken, unobservedReason: failures.join("; ") };
27826
+ }
27439
27827
  function localCensus(label) {
27440
27828
  return census(localDockerClient(), label);
27441
27829
  }
27442
- function judgeLeaks(peak, after) {
27830
+ function judgeLeaks(peak, after, observation) {
27443
27831
  const labelObserved = peak.containers.length > 0;
27832
+ if (!observation.observed) {
27833
+ return {
27834
+ labelObserved,
27835
+ reaped: false,
27836
+ observed: false,
27837
+ clean: false,
27838
+ state: "unobserved",
27839
+ peak,
27840
+ after,
27841
+ note: `UNOBSERVED, not clean: we could not look at what survived teardown (${observation.unobservedReason ?? "no reason given"}), so nothing is known about leaks on this run, in either direction.`
27842
+ };
27843
+ }
27444
27844
  const reaped = censusTotal(after) === 0;
27445
27845
  let note;
27846
+ let state;
27446
27847
  if (!labelObserved && reaped) {
27848
+ state = "inconclusive";
27447
27849
  note = "INCONCLUSIVE, not clean: nothing labelled was ever seen alive, so an empty final census is equally consistent with the label never being applied. The control failed, so the denial proves nothing.";
27448
27850
  } else if (!labelObserved) {
27851
+ state = "leak";
27449
27852
  note = "no labelled container was observed alive AND objects remain \u2014 the label wiring is wrong.";
27450
27853
  } else if (!reaped) {
27854
+ state = "leak";
27451
27855
  note = `LEAK: ${String(censusTotal(after))} labelled object(s) survived teardown (containers=${String(after.containers.length)} volumes=${String(after.volumes.length)} networks=${String(after.networks.length)}).`;
27452
27856
  } else {
27857
+ state = "clean";
27453
27858
  note = `clean: peak ${String(peak.containers.length)} labelled container(s) observed alive, 0 labelled objects remain after teardown.`;
27454
27859
  }
27455
- return { labelObserved, reaped, clean: labelObserved && reaped, peak, after, note };
27860
+ return {
27861
+ labelObserved,
27862
+ reaped,
27863
+ observed: true,
27864
+ clean: labelObserved && reaped,
27865
+ state,
27866
+ peak,
27867
+ after,
27868
+ note
27869
+ };
27456
27870
  }
27457
- var RUN_LABEL_KEY, LabelWatch;
27871
+ var RUN_LABEL_KEY, RUN_ID_LABEL_KEY, LabelWatch, LEAK_STATES;
27458
27872
  var init_labels = __esm({
27459
27873
  "../../packages/envrun/dist/labels.js"() {
27460
27874
  "use strict";
27461
27875
  init_dist();
27462
27876
  RUN_LABEL_KEY = "supergoal.run";
27877
+ RUN_ID_LABEL_KEY = "supergoal.run-id";
27463
27878
  LabelWatch = class {
27464
27879
  label;
27465
27880
  docker;
@@ -27509,6 +27924,7 @@ var init_labels = __esm({
27509
27924
  return this.#samples;
27510
27925
  }
27511
27926
  };
27927
+ LEAK_STATES = ["clean", "leak", "inconclusive", "unobserved"];
27512
27928
  }
27513
27929
  });
27514
27930
 
@@ -27809,6 +28225,7 @@ http
27809
28225
  });
27810
28226
 
27811
28227
  // ../../packages/envrun/dist/venue.js
28228
+ import { randomBytes as randomBytes8 } from "crypto";
27812
28229
  import { join as join22 } from "path";
27813
28230
  function localJailPaths(scratchRoot) {
27814
28231
  return {
@@ -27904,11 +28321,62 @@ function coerceToString(thrown) {
27904
28321
  return UNCOERCIBLE_THROWN;
27905
28322
  }
27906
28323
  }
28324
+ function localTreeOwner(ids2 = hostIds()) {
28325
+ if (ids2?.uid !== 0)
28326
+ return void 0;
28327
+ return localGuestUser(ids2) ?? void 0;
28328
+ }
28329
+ function handLocalTreeToGuest(local, owner, chown = chownTree) {
28330
+ if (owner === void 0)
28331
+ return;
28332
+ chown(local.cloneDir, owner);
28333
+ chown(local.scratchRoot, owner);
28334
+ }
28335
+ function localCloneVolumeName(runId, suffix = randomBytes8(4).toString("hex")) {
28336
+ const tail2 = `-${suffix}`;
28337
+ const safe = runId.replace(/[^a-zA-Z0-9_.-]/g, "-");
28338
+ const head = `th-clone-${safe}`.slice(0, VOLUME_NAME_MAX2 - tail2.length);
28339
+ return validateVolumeName(`${head}${tail2}`, "the local clone volume");
28340
+ }
28341
+ function localCloneFillArgv(from, volume, owner) {
28342
+ const chown = owner === null ? "" : ` && chown -R ${String(owner.uid)}:${String(owner.gid)} /dst`;
28343
+ return [
28344
+ "run",
28345
+ "--rm",
28346
+ "--network=none",
28347
+ `--volume=${from}:/src:ro`,
28348
+ `--volume=${validateVolumeName(volume, "the local clone volume")}:/dst:rw`,
28349
+ "--",
28350
+ STAGE_HELPER_IMAGE,
28351
+ "sh",
28352
+ "-c",
28353
+ `cp -a /src/. /dst/${chown}`
28354
+ ];
28355
+ }
27907
28356
  function acquireLocalLease(runId) {
27908
28357
  const docker3 = localDockerClient();
27909
28358
  const containment = selectContainment([containerContainmentOn(docker3)]);
27910
28359
  let released = false;
27911
28360
  const stagedProxies = [];
28361
+ const owner = localTreeOwner();
28362
+ let cloneVolume;
28363
+ const createdVolumes = [];
28364
+ const fillCloneVolume = (cloneDir) => {
28365
+ const name = localCloneVolumeName(runId);
28366
+ const created = docker3.sync(["volume", "create", `--label=${STAGE_VOLUME_LABEL_KEY}=${runId}`, "--", name], { timeoutMs: LOCAL_VOLUME_CREATE_TIMEOUT_MS });
28367
+ if (created.error || created.status !== 0) {
28368
+ throw new ContainmentRefusalError(`could not create the clone volume ${name} on the local daemon: ${(created.error?.message ?? created.stderr).trim().slice(0, 300)}`);
28369
+ }
28370
+ createdVolumes.push(name);
28371
+ const from = resolverFor("local")(cloneDir, "clone");
28372
+ const filled = docker3.sync(localCloneFillArgv(from, name, localGuestUser()), {
28373
+ timeoutMs: LOCAL_CLONE_FILL_TIMEOUT_MS
28374
+ });
28375
+ if (filled.error || filled.status !== 0) {
28376
+ throw new ContainmentRefusalError(`could not copy the clone into its volume ${name}: ${(filled.error?.message ?? filled.stderr).trim().slice(0, 300)}`);
28377
+ }
28378
+ return name;
28379
+ };
27912
28380
  const lease = {
27913
28381
  kind: "local",
27914
28382
  runId,
@@ -27918,28 +28386,32 @@ function acquireLocalLease(runId) {
27918
28386
  // the paths are on this machine AND `canonical()` is what should resolve
27919
28387
  // them, which is the behaviour every local run has always had.
27920
28388
  pathDomain: "local",
28389
+ // Spread rather than `guestUser: owner`, so a non-root lease has no such key
28390
+ // at all — the same object every local run has always had.
28391
+ ...owner === void 0 ? {} : { guestUser: owner },
27921
28392
  get released() {
27922
28393
  return released;
27923
28394
  },
27924
- stage: (local) => (
27925
- // The local venue IS the developer's machine, so staging is the identity
27926
- // and the paths are already canonical here. This is not a stub: it is the
27927
- // one venue for which the answer is "nothing to copy", and having it go
27928
- // through the same method as a hosted venue is what stops `thrun.ts` from
27929
- // ever holding a path it did not get from a venue.
27930
- //
27931
- // `jail` and `tmp` are DERIVED here rather than carried in on `LocalTree`,
27932
- // because the join differs by side and the venue owns the spelling of its
27933
- // own paths: the host's `join` here, `venueJoin` on a hosted venue. The
27934
- // segments are one constant in `fence.ts`, so the tree `buildJail` wrote
27935
- // and the tree a venue mounts cannot drift apart.
27936
- Promise.resolve({
28395
+ get cloneVolume() {
28396
+ return cloneVolume;
28397
+ },
28398
+ stage: (local) => {
28399
+ if (released) {
28400
+ return Promise.reject(new LeaseReleasedError("this lease was already released, so a clone volume made now would never be removed."));
28401
+ }
28402
+ try {
28403
+ handLocalTreeToGuest(local, owner);
28404
+ cloneVolume = fillCloneVolume(local.cloneDir);
28405
+ } catch (err) {
28406
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
28407
+ }
28408
+ return Promise.resolve({
27937
28409
  cloneDir: local.cloneDir,
27938
28410
  scratchRoot: local.scratchRoot,
27939
28411
  previewDir: local.previewDir,
27940
28412
  ...localJailPaths(local.scratchRoot)
27941
- })
27942
- ),
28413
+ });
28414
+ },
27943
28415
  // The containment function verbatim, which is the point of the barrel
27944
28416
  // re-export rather than a copy here: this venue's daemon and this process
27945
28417
  // share a filesystem, so the directory it makes under `tmpdir()` is already
@@ -27993,7 +28465,7 @@ function acquireLocalLease(runId) {
27993
28465
  observed: false,
27994
28466
  census: { containers: [], volumes: [], networks: [] },
27995
28467
  unobservedReason: RELEASED_LEASE_CENSUS_REASON
27996
- } : { observed: true, census: census(docker3, label), unobservedReason: null }),
28468
+ } : censusReport(docker3, label)),
27997
28469
  publishPreview: (req) => startPreview({ ...req, docker: docker3 }),
27998
28470
  release: () => {
27999
28471
  if (released) {
@@ -28009,6 +28481,25 @@ function acquireLocalLease(runId) {
28009
28481
  const staged = stagedProxies.splice(0, stagedProxies.length);
28010
28482
  for (const s of staged)
28011
28483
  s.cleanup();
28484
+ const volumeFailures = [];
28485
+ for (const name of createdVolumes.splice(0, createdVolumes.length)) {
28486
+ const removed = docker3.sync(["volume", "rm", "-f", "--", name], {
28487
+ timeoutMs: LOCAL_VOLUME_CREATE_TIMEOUT_MS
28488
+ });
28489
+ if (removed.error || removed.status !== 0) {
28490
+ volumeFailures.push(`${name}: ${(removed.error?.message ?? removed.stderr).trim().slice(0, 200)}`);
28491
+ }
28492
+ }
28493
+ cloneVolume = void 0;
28494
+ if (volumeFailures.length > 0) {
28495
+ return Promise.resolve({
28496
+ kind: "local",
28497
+ released: true,
28498
+ alreadyReleased: false,
28499
+ error: `could not remove the clone volume: ${volumeFailures.join("; ")}`,
28500
+ detail: `the lease is closed, but a clone volume remains (label ${STAGE_VOLUME_LABEL_KEY})`
28501
+ });
28502
+ }
28012
28503
  return Promise.resolve({
28013
28504
  kind: "local",
28014
28505
  released: true,
@@ -28020,7 +28511,7 @@ function acquireLocalLease(runId) {
28020
28511
  };
28021
28512
  return lease;
28022
28513
  }
28023
- var VenueRollbackError, UNREADABLE_MESSAGE, UNREADABLE_NAME, UNCOERCIBLE_THROWN, UNDESCRIBABLE_THROWN, UNLISTABLE_ROLLBACK_FAILURES, RELEASED_LEASE_CENSUS_REASON, LeaseReleasedError;
28514
+ var VenueRollbackError, UNREADABLE_MESSAGE, UNREADABLE_NAME, UNCOERCIBLE_THROWN, UNDESCRIBABLE_THROWN, UNLISTABLE_ROLLBACK_FAILURES, RELEASED_LEASE_CENSUS_REASON, LeaseReleasedError, STAGE_HELPER_IMAGE, STAGE_VOLUME_LABEL_KEY, VOLUME_NAME_MAX2, LOCAL_VOLUME_CREATE_TIMEOUT_MS, LOCAL_CLONE_FILL_TIMEOUT_MS;
28024
28515
  var init_venue = __esm({
28025
28516
  "../../packages/envrun/dist/venue.js"() {
28026
28517
  "use strict";
@@ -28045,6 +28536,11 @@ var init_venue = __esm({
28045
28536
  LeaseReleasedError = class extends Error {
28046
28537
  name = "LeaseReleasedError";
28047
28538
  };
28539
+ STAGE_HELPER_IMAGE = "busybox:1.37.0";
28540
+ STAGE_VOLUME_LABEL_KEY = "terminalhire.stage";
28541
+ VOLUME_NAME_MAX2 = 128;
28542
+ LOCAL_VOLUME_CREATE_TIMEOUT_MS = 3e4;
28543
+ LOCAL_CLONE_FILL_TIMEOUT_MS = 6e5;
28048
28544
  }
28049
28545
  });
28050
28546
 
@@ -28113,6 +28609,17 @@ caused by: ${frame}`;
28113
28609
  }
28114
28610
  return frames === 0 ? null : out;
28115
28611
  }
28612
+ function imageVariantFor(spec) {
28613
+ if (spec.runtime !== "jvm")
28614
+ return void 0;
28615
+ const callsGradle = /(?:^|[\s;&|(])gradle(?=\s|$)/;
28616
+ return [spec.installCommand, spec.testCommand].some((c) => c != null && callsGradle.test(c)) ? "gradle" : void 0;
28617
+ }
28618
+ function shapeFor(runtime, variant) {
28619
+ if (runtime === "jvm" && variant === "gradle")
28620
+ return JVM_GRADLE_IMAGE;
28621
+ return RUNTIME_IMAGES[runtime];
28622
+ }
28116
28623
  function atLeast(a, b) {
28117
28624
  const left = a.split(".").map(Number);
28118
28625
  const right = b.split(".").map(Number);
@@ -28125,19 +28632,19 @@ function atLeast(a, b) {
28125
28632
  }
28126
28633
  return true;
28127
28634
  }
28128
- function imageForRuntime(runtime, override, version) {
28635
+ function imageForRuntime(runtime, override, version, variant) {
28129
28636
  if (override)
28130
28637
  return override;
28131
- const shape = RUNTIME_IMAGES[runtime];
28638
+ const shape = shapeFor(runtime, variant);
28132
28639
  if (!shape) {
28133
28640
  throw new RunRefusalError(`no container image is mapped for runtime ${JSON.stringify(runtime)}. Refusing to run it in the Node image: a bare "command not found" exits 127, which classifyVerification already owns as ours \u2014 but a test script that RUNS and fails inside on the missing tool exits with its own status and prints to stdout, and that falls through to tests-failed \u2014 a false red blamed on the repo.`);
28134
28641
  }
28135
- if (version === void 0 || version === null)
28642
+ if (version === void 0 || version === null || shape.fixed)
28136
28643
  return unversionedImage(shape);
28137
28644
  if (!TAG_VERSION.test(version)) {
28138
28645
  throw new RunRefusalError(`runtime version ${JSON.stringify(version)} is not a bare version, so no image tag can be built from it. Refusing rather than booting the default: the repo asked for a version, and supplying a different one silently is what TERM-643 fixed.`);
28139
28646
  }
28140
- return `${shape.repository}:${version}${shape.suffix}`;
28647
+ return `${shape.repository}:${shape.prefix ?? ""}${version}${shape.suffix}`;
28141
28648
  }
28142
28649
  function setManifestProbe(probe) {
28143
28650
  const previous = manifestProbe;
@@ -28150,39 +28657,47 @@ function imageDefinitelyAbsent(image) {
28150
28657
  return false;
28151
28658
  return /manifest unknown|no such manifest/i.test(res.output);
28152
28659
  }
28153
- function resolvePublishedImage(image, runtime, version) {
28660
+ function resolvePublishedImage(image, runtime, version, variant) {
28154
28661
  if (!imageDefinitelyAbsent(image))
28155
28662
  return image;
28156
- const shape = RUNTIME_IMAGES[runtime];
28663
+ const shape = shapeFor(runtime, variant);
28157
28664
  if (shape && shape.declaredIsFloor && atLeast(shape.defaultVersion, version)) {
28158
28665
  return unversionedImage(shape);
28159
28666
  }
28160
28667
  throw new RunRefusalError(`the repo declares ${runtime} ${version}, and no image is published at ${image}` + (shape && shape.declaredIsFloor ? `. Our default is ${shape.defaultVersion}, which is OLDER than that, so falling back would run the repo under a toolchain it says it cannot use` : `. ${runtime} treats a declared version as an exact pin, not a minimum, so a different one is a different environment`) + ". Refusing rather than booting a version the repo did not ask for \u2014 that substitution is what made this class of failure unattributable (TERM-643). Pass an explicit image to override.");
28161
28668
  }
28162
28669
  function resolveImageForSpec(spec, override) {
28163
- const declared = spec.runtimeVersion;
28164
- const image = imageForRuntime(spec.runtime, override, declared);
28670
+ const variant = imageVariantFor(spec);
28671
+ const declared = shapeFor(spec.runtime, variant)?.fixed ? null : spec.runtimeVersion;
28672
+ const image = imageForRuntime(spec.runtime, override, declared, variant);
28165
28673
  const constructed = (() => {
28166
28674
  if (declared === null)
28167
28675
  return null;
28168
28676
  try {
28169
- return imageForRuntime(spec.runtime, void 0, declared);
28677
+ return imageForRuntime(spec.runtime, void 0, declared, variant);
28170
28678
  } catch {
28171
28679
  return null;
28172
28680
  }
28173
28681
  })();
28174
28682
  if (constructed !== null && image === constructed && declared !== null) {
28175
- return resolvePublishedImage(image, spec.runtime, declared);
28683
+ return resolvePublishedImage(image, spec.runtime, declared, variant);
28176
28684
  }
28177
28685
  return image;
28178
28686
  }
28179
28687
  function installEnvironmentFailureNote(install, image) {
28180
- const base = `the install step exited ${String(install.exitCode)}, so the test command was never invoked. The repo has not been judged; this is an environment failure.`;
28688
+ const base = `the install step exited ${String(install.exitCode)}, so the test command was never invoked. The repo has not been judged; this is an environment failure.` + refusedHostsSentence(install.egressDenied ?? []);
28181
28689
  if (!MISSING_IMAGE_SHAPE.test(`${install.stdout}
28182
28690
  ${install.stderr}`))
28183
28691
  return base;
28184
28692
  return `${base} The container image ${image} is not present on this machine \u2014 run \`docker pull ${image}\` and try again.`;
28185
28693
  }
28694
+ function refusedHostsSentence(hosts) {
28695
+ if (hosts.length === 0)
28696
+ return "";
28697
+ const named = hosts.slice(0, MAX_NAMED_REFUSED_HOSTS).join(", ");
28698
+ const rest = hosts.length - MAX_NAMED_REFUSED_HOSTS;
28699
+ return ` During install the network proxy refused ${named}` + (rest > 0 ? ` and ${String(rest)} more` : "") + ". Install reaches only the hosts on its allowlist, so a dependency whose install script downloads from any other host cannot get what it asked for.";
28700
+ }
28186
28701
  function classifySingleRun(run2) {
28187
28702
  return classifyVerification(run2).outcome;
28188
28703
  }
@@ -28199,12 +28714,22 @@ function assertVenueOwnerDeclared(lease) {
28199
28714
  return;
28200
28715
  throw new RunRefusalError("this venue did not say which account owns the tree it staged, so the guest would run under this machine's uid and could not write its own clone. We refuse rather than report that permission error as the repo's tests failing.");
28201
28716
  }
28717
+ function refuseUnbuildableSpec(spec) {
28718
+ if (spec.runtime !== "jvm" || spec.installCommand !== null || spec.testCommand !== null)
28719
+ return;
28720
+ const why = spec.unresolved.find((r) => r.kind === "no-test-command")?.detail;
28721
+ if (why === void 0) {
28722
+ throw new EnvRunError("internal: a jvm spec with no test command carries no no-test-command reason");
28723
+ }
28724
+ throw new RunRefusalError(`refusing to run this repository: ${why}.`);
28725
+ }
28202
28726
  async function runEnvironmentSpec(req) {
28203
28727
  const startedAt = Date.now();
28204
28728
  const containment = req.lease.containment;
28205
28729
  if (containment.kind !== "container") {
28206
28730
  throw new EnvRunError(`phase 2 requires the container tier, got ${containment.kind}. Refusing: a container phase that silently ran under seatbelt would make every container claim vacuous.`);
28207
28731
  }
28732
+ refuseUnbuildableSpec(req.spec);
28208
28733
  assertVenueOwnerDeclared(req.lease);
28209
28734
  const image = resolveImageForSpec(req.spec, req.image);
28210
28735
  const { jail, tmp } = req;
@@ -28234,6 +28759,8 @@ async function runEnvironmentSpec(req) {
28234
28759
  // TERM-913: the third answer about that machine — which volumes the
28235
28760
  // fence mounts in place of the noexec stage. Undefined on a local lease.
28236
28761
  stageVolumes: req.lease.stageVolumes,
28762
+ // TERM-1106: the local venue's copy of the clone. Undefined on a hosted lease.
28763
+ cloneVolume: req.lease.cloneVolume,
28237
28764
  env,
28238
28765
  image,
28239
28766
  labels,
@@ -28247,9 +28774,10 @@ async function runEnvironmentSpec(req) {
28247
28774
  installOk: false
28248
28775
  };
28249
28776
  } else if (req.spec.testCommand === null) {
28777
+ const why = req.spec.unresolved.find((r) => r.kind === "no-test-command")?.detail;
28250
28778
  result = {
28251
28779
  outcome: "no-tests-observed",
28252
- note: "the spec derived no test command, so nothing was executed: nothing failed and nothing ran. Certain, not inferred \u2014 no command was ever invoked.",
28780
+ note: "the spec derived no test command, so nothing was executed: nothing failed and nothing ran. Certain, not inferred \u2014 no command was ever invoked." + (why === void 0 ? "" : ` Why: ${why}.`),
28253
28781
  installOk: true
28254
28782
  };
28255
28783
  } else {
@@ -28267,12 +28795,14 @@ async function runEnvironmentSpec(req) {
28267
28795
  // TERM-913: the third answer about that machine — which volumes the
28268
28796
  // fence mounts in place of the noexec stage. Undefined on a local lease.
28269
28797
  stageVolumes: req.lease.stageVolumes,
28798
+ // TERM-1106: the local venue's copy of the clone. Undefined on a hosted lease.
28799
+ cloneVolume: req.lease.cloneVolume,
28270
28800
  env,
28271
28801
  image,
28272
28802
  labels,
28273
28803
  timeoutMs: req.testTimeoutMs ?? 9e5
28274
28804
  });
28275
- const verdict = classifyVerification(toExecution(test));
28805
+ const verdict = classifyVerification({ ...toExecution(test), runtime: req.spec.runtime });
28276
28806
  result = { outcome: verdict.outcome, note: verdict.reason, installOk: true };
28277
28807
  }
28278
28808
  } finally {
@@ -28286,6 +28816,10 @@ async function runEnvironmentSpec(req) {
28286
28816
  volumes: [],
28287
28817
  networks: []
28288
28818
  };
28819
+ const observation = afterReport ?? {
28820
+ observed: false,
28821
+ unobservedReason: "the run carried no label, so there was nothing to count by"
28822
+ };
28289
28823
  return {
28290
28824
  outcome: result.outcome,
28291
28825
  tier: "container",
@@ -28294,7 +28828,7 @@ async function runEnvironmentSpec(req) {
28294
28828
  test,
28295
28829
  installOk: result.installOk,
28296
28830
  counts: test ? readCounts(test.stdout, test.stderr) : null,
28297
- leaks: judgeLeaks(peak, after),
28831
+ leaks: judgeLeaks(peak, after, observation),
28298
28832
  note: result.note,
28299
28833
  wallMs: Date.now() - startedAt
28300
28834
  };
@@ -28322,6 +28856,7 @@ async function runStep(containment, r) {
28322
28856
  pathDomain: r.pathDomain,
28323
28857
  guestUser: r.guestUser,
28324
28858
  stageVolumes: r.stageVolumes,
28859
+ cloneVolume: r.cloneVolume,
28325
28860
  program: "/bin/sh",
28326
28861
  args: ["-c", withUserScriptPath(r.command)]
28327
28862
  };
@@ -28341,10 +28876,11 @@ async function runStep(containment, r) {
28341
28876
  stdout: res.stdout,
28342
28877
  stderr: res.stderr,
28343
28878
  timedOut: res.timedOut,
28344
- wallMs: Date.now() - startedAt
28879
+ wallMs: Date.now() - startedAt,
28880
+ ...res.egressDenied && res.egressDenied.length > 0 ? { egressDenied: [...new Set(res.egressDenied)] } : {}
28345
28881
  };
28346
28882
  }
28347
- var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE;
28883
+ var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, JVM_GRADLE_IMAGE, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE, MAX_NAMED_REFUSED_HOSTS;
28348
28884
  var init_execute = __esm({
28349
28885
  "../../packages/envrun/dist/execute.js"() {
28350
28886
  "use strict";
@@ -28411,9 +28947,56 @@ var init_execute = __esm({
28411
28947
  suffix: "",
28412
28948
  defaultVersion: "8.0",
28413
28949
  declaredIsFloor: true
28950
+ },
28951
+ /**
28952
+ * TERM-1122. Maven's image, and the one a Gradle WRAPPER repo gets too: the wrapper
28953
+ * downloads its own Gradle, so it needs a JDK and nothing else. MEASURED
28954
+ * 2026-09-18: `maven:3.9-eclipse-temurin-21` carries `git`, `mvn` and JDK 21 on
28955
+ * Ubuntu 24.04, which is why this is not `eclipse-temurin` (no `git`, rejected
28956
+ * under TERM-1139). The JDK sits AFTER maven's own version in the tag, hence
28957
+ * `prefix`, and the major is all envspec derives (`javaMajor`).
28958
+ *
28959
+ * `declaredIsFloor`: a JDK builds for older `--release` targets, so a repo asking
28960
+ * for 16, which has no image, runs on 21. A Gradle toolchain is stricter than a
28961
+ * floor and asks for its exact JDK; Gradle's refusal on the wrong one reads as
28962
+ * ours (`classify.ts`, `isOfflineBuildGap`), never as the repo's.
28963
+ */
28964
+ jvm: {
28965
+ repository: "maven",
28966
+ prefix: "3.9-eclipse-temurin-",
28967
+ suffix: "",
28968
+ defaultVersion: "21",
28969
+ declaredIsFloor: true
28970
+ },
28971
+ /**
28972
+ * TERM-1123. OUR image (`packages/envrun/images/cpp/Dockerfile`): Debian bookworm
28973
+ * with the C/C++ toolchain, CMake, Meson and the common -dev libraries baked in.
28974
+ * No official image carries that set. Libraries are baked rather than installed per
28975
+ * run because the install step has network only through the allowlist, and apt is
28976
+ * not on it; a repo needing a library the image lacks gets `test-command-unavailable`
28977
+ * (`isMissingSystemDependency`), never a red.
28978
+ *
28979
+ * `fixed`: a C/C++ repo declares no compiler version envspec can read, so there is
28980
+ * nothing to derive and nothing to probe. The tag moves only when the Dockerfile
28981
+ * does. It lives in a PUBLIC repository, apart from the private venue images, so a
28982
+ * developer's own Docker can pull it with no credential.
28983
+ */
28984
+ cpp: {
28985
+ repository: "us-east1-docker.pkg.dev/terminalhire-pool/runtime-images/cpp",
28986
+ suffix: "",
28987
+ defaultVersion: "1",
28988
+ declaredIsFloor: false,
28989
+ fixed: true
28414
28990
  }
28415
28991
  };
28416
- unversionedImage = (shape) => `${shape.repository}:${shape.defaultVersion}${shape.suffix}`;
28992
+ JVM_GRADLE_IMAGE = {
28993
+ repository: "gradle",
28994
+ prefix: "jdk",
28995
+ suffix: "",
28996
+ defaultVersion: "21",
28997
+ declaredIsFloor: true
28998
+ };
28999
+ unversionedImage = (shape) => `${shape.repository}:${shape.prefix ?? ""}${shape.defaultVersion}${shape.suffix}`;
28417
29000
  TAG_VERSION = /^\d+(?:\.\d+){0,2}$/;
28418
29001
  dockerManifestProbe = (image) => {
28419
29002
  const res = spawnSync4("docker", ["manifest", "inspect", image], {
@@ -28424,6 +29007,7 @@ var init_execute = __esm({
28424
29007
  };
28425
29008
  manifestProbe = dockerManifestProbe;
28426
29009
  MISSING_IMAGE_SHAPE = /Unable to find image ['"][^'"]*['"] locally/i;
29010
+ MAX_NAMED_REFUSED_HOSTS = 5;
28427
29011
  }
28428
29012
  });
28429
29013
 
@@ -28498,7 +29082,7 @@ var init_dsse = __esm({
28498
29082
  });
28499
29083
 
28500
29084
  // ../../packages/attest/dist/sealedbox.js
28501
- import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes8 } from "crypto";
29085
+ import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes9 } from "crypto";
28502
29086
  var init_sealedbox = __esm({
28503
29087
  "../../packages/attest/dist/sealedbox.js"() {
28504
29088
  "use strict";
@@ -28508,7 +29092,7 @@ var init_sealedbox = __esm({
28508
29092
  });
28509
29093
 
28510
29094
  // ../../packages/attest/dist/aead.js
28511
- import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes9 } from "crypto";
29095
+ import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes10 } from "crypto";
28512
29096
  var init_aead = __esm({
28513
29097
  "../../packages/attest/dist/aead.js"() {
28514
29098
  "use strict";
@@ -29105,7 +29689,14 @@ function answerDidItPass(r) {
29105
29689
  const passed = r.status === "verified" && r.outcome !== null && isGreen(r.outcome);
29106
29690
  return { passed, summary: renderVerdictLine(r), lookAt: r.preview?.url ?? null };
29107
29691
  }
29108
- var RUN_TEST_COMMAND_SOURCES, RUN_IMAGE_SOURCES, RUN_RESULT_SCHEMA, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS;
29692
+ function exitCodeFor(r) {
29693
+ if (r.status === "refused")
29694
+ return 2;
29695
+ if (r.outcome === null)
29696
+ return 2;
29697
+ return OUTCOME_EXIT_CODES[r.outcome];
29698
+ }
29699
+ var RUN_TEST_COMMAND_SOURCES, RUN_IMAGE_SOURCES, RUN_RESULT_SCHEMA, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS, OUTCOME_EXIT_CODES;
29109
29700
  var init_result = __esm({
29110
29701
  "../../packages/envrun/dist/result.js"() {
29111
29702
  "use strict";
@@ -29149,6 +29740,7 @@ var init_result = __esm({
29149
29740
  "containerImageDigest",
29150
29741
  "imageSource",
29151
29742
  "leaksClean",
29743
+ "leakState",
29152
29744
  "venue"
29153
29745
  ];
29154
29746
  RENDER_NONE = null;
@@ -29183,17 +29775,44 @@ var init_result = __esm({
29183
29775
  // Shown only when a human chose the environment. `detected` is the ordinary case and
29184
29776
  // saying so on every run would train the reader to skip the line that matters.
29185
29777
  imageSource: (r) => r.imageSource === "detected" || r.imageSource === "none" ? null : `image source ${r.imageSource} (not signed)`,
29186
- leaksClean: (r) => r.leaksClean === null ? null : r.leaksClean ? null : "WARNING labelled Docker objects survived teardown",
29778
+ // Printed through `leakState` below, which knows WHY a false is false. Rendering both would
29779
+ // print the leak warning on a run where we merely could not look (TERM-1144).
29780
+ leaksClean: RENDER_NONE,
29781
+ // Silent when clean and when refused, as the boolean's line was. The two not-a-leak states
29782
+ // get their own sentence, because "we could not check" read as "we found a leak" is the
29783
+ // exact confusion this field exists to end.
29784
+ leakState: (r) => {
29785
+ switch (r.leakState) {
29786
+ case null:
29787
+ case "clean":
29788
+ return null;
29789
+ case "leak":
29790
+ return "WARNING labelled Docker objects survived teardown";
29791
+ case "inconclusive":
29792
+ return "leak check inconclusive \u2014 no labelled container was seen while the run was live, so finding nothing afterwards proves nothing";
29793
+ case "unobserved":
29794
+ return "leak check not taken \u2014 we could not check what was left after teardown";
29795
+ }
29796
+ },
29187
29797
  // Absent on most runs, so it prints only when there is something to say. Silence
29188
29798
  // here is the honest rendering of "no venue answered": a placeholder line would
29189
29799
  // invite a reader to treat an unanswered probe as a described venue.
29190
29800
  venue: (r) => r.venue === null ? null : renderVenueLine(r.venue)
29191
29801
  };
29802
+ OUTCOME_EXIT_CODES = {
29803
+ completed: 0,
29804
+ "tests-failed": 1,
29805
+ "no-tests-observed": 1,
29806
+ "budget-exceeded": 1,
29807
+ "counts-unparsed": 2,
29808
+ "test-command-unavailable": 2,
29809
+ "environment-exhausted": 2
29810
+ };
29192
29811
  }
29193
29812
  });
29194
29813
 
29195
29814
  // ../../packages/envrun/dist/attestation.js
29196
- import { createHash as createHash8, randomBytes as randomBytes10 } from "crypto";
29815
+ import { createHash as createHash8, randomBytes as randomBytes11 } from "crypto";
29197
29816
  function contradicts(outcome, counts, exitCode) {
29198
29817
  const budget = OUTCOME_TO_BUDGET[outcome];
29199
29818
  if (budget === null)
@@ -29373,7 +29992,7 @@ function toAcceptancePredicate(pair, opts = {}) {
29373
29992
  // RepoDigest (`repo@sha256:…`) carries the repo name and the content hash, and the
29374
29993
  // tag it drops is the part a registry can re-point (TERM-893).
29375
29994
  enclave_measurement: localMeasurement(patched.containerImageDigest),
29376
- nonce: opts.nonce ?? randomBytes10(16).toString("hex"),
29995
+ nonce: opts.nonce ?? randomBytes11(16).toString("hex"),
29377
29996
  run_policy: { max_attempts: opts.maxAttempts ?? 1, budget_outcome: budget }
29378
29997
  }
29379
29998
  };
@@ -30351,12 +30970,12 @@ function socketErrorReason(err) {
30351
30970
  return err.name;
30352
30971
  }
30353
30972
  function fetchAttestationOverTls(req) {
30354
- return new Promise((resolve4) => {
30973
+ return new Promise((resolve5) => {
30355
30974
  let whole;
30356
30975
  const settle = (reading) => {
30357
30976
  if (whole !== void 0)
30358
30977
  clearTimeout(whole);
30359
- resolve4(reading);
30978
+ resolve5(reading);
30360
30979
  };
30361
30980
  let cert;
30362
30981
  let key;
@@ -31499,11 +32118,7 @@ function makeLease(p) {
31499
32118
  }
31500
32119
  try {
31501
32120
  check("counting what the run left behind");
31502
- return Promise.resolve({
31503
- observed: true,
31504
- census: census(p.docker, label),
31505
- unobservedReason: null
31506
- });
32121
+ return Promise.resolve(censusReport(p.docker, label));
31507
32122
  } catch (err) {
31508
32123
  return Promise.resolve({
31509
32124
  observed: false,
@@ -31561,7 +32176,7 @@ function makeLease(p) {
31561
32176
  }
31562
32177
  };
31563
32178
  }
31564
- var SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT, STAGE_HELPER_IMAGE, VOLUME_CREATE_TIMEOUT_MS, EXEC_PROBE_TIMEOUT_MS, POPULATE_TIMEOUT_MS, STAGE_VOLUME_LABEL_KEY, STAGE_PROOF_PREFIX, IAP_NOT_READY, IAP_BACKEND_UNREACHABLE, IAP_DENIED, TERMINAL_GCP, INSTANCE_NOT_RUNNING, PREEMPTED, HOST_KEY_MISMATCH, SSH_KEY_NOT_READY, DAEMON_NOT_READY, SSH_NOT_ANSWERING, CS_ATTEST_PORT, CS_DOCKER_PORT, CS_READY_BUDGET_MS, CS_ATTEST_TIMEOUT_MS, CS_PULL_TIMEOUT_MS, CS_ATTEST_INTERVAL_MS, OPENSSL_TIMEOUT_MS, CS_RUN_ARGS, CS_GUEST_USER, CS_STAGE_ROOT, CLIENT_CERT_STAND_IN;
32179
+ var SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT, VOLUME_CREATE_TIMEOUT_MS, EXEC_PROBE_TIMEOUT_MS, POPULATE_TIMEOUT_MS, STAGE_PROOF_PREFIX, IAP_NOT_READY, IAP_BACKEND_UNREACHABLE, IAP_DENIED, TERMINAL_GCP, INSTANCE_NOT_RUNNING, PREEMPTED, HOST_KEY_MISMATCH, SSH_KEY_NOT_READY, DAEMON_NOT_READY, SSH_NOT_ANSWERING, CS_ATTEST_PORT, CS_DOCKER_PORT, CS_READY_BUDGET_MS, CS_ATTEST_TIMEOUT_MS, CS_PULL_TIMEOUT_MS, CS_ATTEST_INTERVAL_MS, OPENSSL_TIMEOUT_MS, CS_RUN_ARGS, CS_GUEST_USER, CS_STAGE_ROOT, CLIENT_CERT_STAND_IN;
31565
32180
  var init_hostedVenue = __esm({
31566
32181
  "../../packages/envrun/dist/hostedVenue.js"() {
31567
32182
  "use strict";
@@ -31710,18 +32325,18 @@ var init_hostedVenue = __esm({
31710
32325
  for (let i = 0; i < n; i += 1) {
31711
32326
  const server = createServer();
31712
32327
  servers.push(server);
31713
- await new Promise((resolve4, reject) => {
32328
+ await new Promise((resolve5, reject) => {
31714
32329
  server.once("error", reject);
31715
32330
  server.listen(0, "localhost", () => {
31716
- resolve4();
32331
+ resolve5();
31717
32332
  });
31718
32333
  });
31719
32334
  }
31720
32335
  return servers.map((s) => s.address().port);
31721
32336
  } finally {
31722
- await Promise.all(servers.map((s) => new Promise((resolve4) => {
32337
+ await Promise.all(servers.map((s) => new Promise((resolve5) => {
31723
32338
  s.close(() => {
31724
- resolve4();
32339
+ resolve5();
31725
32340
  });
31726
32341
  })));
31727
32342
  }
@@ -31735,11 +32350,9 @@ var init_hostedVenue = __esm({
31735
32350
  VENUE_SSH_USER = "th-runner";
31736
32351
  GCE_METADATA_IDENTITY_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity";
31737
32352
  COMPACT_JWT = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
31738
- STAGE_HELPER_IMAGE = "busybox:1.37.0";
31739
32353
  VOLUME_CREATE_TIMEOUT_MS = 3e4;
31740
32354
  EXEC_PROBE_TIMEOUT_MS = 12e4;
31741
32355
  POPULATE_TIMEOUT_MS = STAGE_PUSH_TIMEOUT_MS;
31742
- STAGE_VOLUME_LABEL_KEY = "terminalhire.stage";
31743
32356
  STAGE_PROOF_PREFIX = ".th-proven-";
31744
32357
  IAP_NOT_READY = /\b4047\s*[:\]]/;
31745
32358
  IAP_BACKEND_UNREACHABLE = /\b4003\s*[:\]]/;
@@ -31771,7 +32384,7 @@ function localDockerPlacement() {
31771
32384
  kind: "local-docker",
31772
32385
  refusal: null,
31773
32386
  venue: () => localVenue(),
31774
- imageFor: (runtime, override, version) => imageForRuntime(runtime, override, version)
32387
+ imageFor: (runtime, override, version, variant) => imageForRuntime(runtime, override, version, variant)
31775
32388
  };
31776
32389
  }
31777
32390
  function hostedPoolPlacement() {
@@ -31779,7 +32392,7 @@ function hostedPoolPlacement() {
31779
32392
  kind: "hosted-pool",
31780
32393
  refusal: null,
31781
32394
  venue: () => hostedVenue(),
31782
- imageFor: (runtime, override, version) => imageForRuntime(runtime, override, version)
32395
+ imageFor: (runtime, override, version, variant) => imageForRuntime(runtime, override, version, variant)
31783
32396
  };
31784
32397
  }
31785
32398
  function placementFor(kind) {
@@ -32344,6 +32957,12 @@ function detectRuntime(repo) {
32344
32957
  if (repo.listNames("").some((name) => /\.(?:csproj|fsproj|sln)$/.test(name))) {
32345
32958
  candidates.push("dotnet");
32346
32959
  }
32960
+ if (candidates.length === 0 && repo.listFiles("").some((f) => f.endsWith(".java"))) {
32961
+ candidates.push("jvm");
32962
+ }
32963
+ if (!candidates.includes("cpp") && MAKEFILE_NAMES.some((file) => repo.exists(file)) && repo.listFiles("").some((f) => C_FAMILY_SOURCE.test(f))) {
32964
+ candidates.push("cpp");
32965
+ }
32347
32966
  return { runtime: candidates[0] ?? "unknown", candidates };
32348
32967
  }
32349
32968
  function readJsonObject(repo, path5) {
@@ -32491,6 +33110,49 @@ function basePythonInstallCommand(repo) {
32491
33110
  }
32492
33111
  return null;
32493
33112
  }
33113
+ function rehearse(command) {
33114
+ const copy = '"$R/.terminalhire-rehearsal"';
33115
+ const home = '"$R/.terminalhire-rehearsal-home"';
33116
+ return `{ R="$HOME"; rm -rf ${copy} ${home}; mkdir -p ${home} && cp -R . ${copy} && (cd ${copy} && HOME=${home} GRADLE_USER_HOME="$R/.gradle" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -Duser.home=$R/.terminalhire-rehearsal-home" ${command}); rm -rf ${copy} ${home}; true; }`;
33117
+ }
33118
+ function cmakeCode(text) {
33119
+ return text.replace(/#\[(=*)\[[\s\S]*?\]\1\]/g, "").replace(/#.*$/gm, "");
33120
+ }
33121
+ function makeTargets(text) {
33122
+ const targets = /* @__PURE__ */ new Set();
33123
+ for (const line of text.split("\n")) {
33124
+ if (line.startsWith(" "))
33125
+ continue;
33126
+ const rule = /^([^:=#\t][^:=#]*?)[ \t]*::?(?!:?=)/.exec(line);
33127
+ if (rule)
33128
+ for (const name of rule[1].trim().split(/\s+/))
33129
+ targets.add(name);
33130
+ }
33131
+ return targets;
33132
+ }
33133
+ function cppCommands(repo) {
33134
+ const cmake = repo.readText("CMakeLists.txt");
33135
+ if (cmake !== null) {
33136
+ return CMAKE_DECLARES_TESTS.test(cmakeCode(cmake)) ? {
33137
+ install: "cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON",
33138
+ test: "cmake --build build --parallel && ctest --test-dir build --output-on-failure"
33139
+ } : { install: null, test: null };
33140
+ }
33141
+ const meson = repo.readText("meson.build");
33142
+ if (meson !== null) {
33143
+ return /\btest\s*\(/.test(meson) ? { install: "meson setup build", test: "meson test -C build" } : { install: null, test: null };
33144
+ }
33145
+ const name = repo.listNames("").find((file) => MAKEFILE_NAMES.includes(file));
33146
+ const makefile = name === void 0 ? null : repo.readText(name);
33147
+ if (makefile !== null) {
33148
+ const targets = makeTargets(makefile);
33149
+ if (targets.has("test"))
33150
+ return { install: null, test: "make test" };
33151
+ if (targets.has("check"))
33152
+ return { install: null, test: "make check" };
33153
+ }
33154
+ return { install: null, test: null };
33155
+ }
32494
33156
  function otherRuntimeCommands(repo, runtime) {
32495
33157
  switch (runtime) {
32496
33158
  case "go":
@@ -32518,11 +33180,24 @@ function otherRuntimeCommands(repo, runtime) {
32518
33180
  case "dotnet":
32519
33181
  return { install: "dotnet restore", test: "dotnet test" };
32520
33182
  case "jvm": {
32521
- if (repo.exists("pom.xml"))
32522
- return { install: "mvn -B dependency:go-offline", test: "mvn -B test" };
33183
+ if (!JVM_BUILD_FILES.some((file) => repo.exists(file)))
33184
+ return { install: null, test: null };
33185
+ if (repo.exists("pom.xml")) {
33186
+ return {
33187
+ install: `mvn -B dependency:go-offline && ${rehearse('mvn -B test -Dmaven.test.failure.ignore=true -Dmaven.repo.local="$R/.m2/repository"')}`,
33188
+ test: "mvn -B -o test"
33189
+ };
33190
+ }
32523
33191
  const wrapper = repo.exists("gradlew") ? "./gradlew" : "gradle";
32524
- return { install: `${wrapper} dependencies`, test: `${wrapper} test` };
33192
+ return {
33193
+ install: `${wrapper} dependencies && ${rehearse(`${wrapper} test --continue`)}`,
33194
+ // `--no-build-cache`: the rehearsal above can fill Gradle's build cache, and a
33195
+ // test task restored FROM-CACHE runs nothing and prints no count.
33196
+ test: `${wrapper} test --offline --no-build-cache`
33197
+ };
32525
33198
  }
33199
+ case "cpp":
33200
+ return cppCommands(repo);
32526
33201
  default:
32527
33202
  return { install: null, test: null };
32528
33203
  }
@@ -32553,6 +33228,15 @@ function searchRuntimeVersion(repo, runtime) {
32553
33228
  push(".ruby-version", repo.readText(".ruby-version"));
32554
33229
  } else if (runtime === "dotnet") {
32555
33230
  push("global.json sdk.version", readStringField(readObjectField(readJsonObject(repo, "global.json"), "sdk"), "version"));
33231
+ } else if (runtime === "jvm") {
33232
+ const pom = repo.readText("pom.xml");
33233
+ const gradle = repo.readText("build.gradle") ?? repo.readText("build.gradle.kts");
33234
+ push("build.gradle toolchain languageVersion", javaMajor(matchFirst(gradle, /JavaLanguageVersion\.of\(\s*["']?(\d+)["']?\s*\)/) ?? matchFirst(gradle, /jvmToolchain\(\s*(\d+)\s*\)/)));
33235
+ push(".java-version", javaMajor(repo.readText(".java-version")));
33236
+ push("pom.xml maven.compiler.release", javaMajor(matchFirst(pom, /<maven\.compiler\.release>\s*([^<\s]+)\s*</)));
33237
+ push("pom.xml java.version", javaMajor(matchFirst(pom, /<java\.version>\s*([^<\s]+)\s*</)));
33238
+ push("pom.xml maven.compiler.source", javaMajor(matchFirst(pom, /<maven\.compiler\.source>\s*([^<\s]+)\s*</)));
33239
+ push("build.gradle sourceCompatibility", javaMajor(matchFirst(gradle, /sourceCompatibility\s*=\s*JavaVersion\.VERSION_([\d_]+)/)?.replace("_", ".") ?? matchFirst(gradle, /sourceCompatibility\s*=\s*["']?([\d.]+)["']?/)));
32556
33240
  }
32557
33241
  let rangeFound = null;
32558
33242
  for (const source of sources) {
@@ -32566,13 +33250,21 @@ function searchRuntimeVersion(repo, runtime) {
32566
33250
  }
32567
33251
  return { version: null, searched: sources.map((s) => s.label), rangeFound };
32568
33252
  }
33253
+ function javaMajor(text) {
33254
+ if (text === null)
33255
+ return null;
33256
+ const match2 = /^(?:1\.(\d+)|(\d+))(?:[._][\d_.]*)?$/.exec(text.trim());
33257
+ if (match2 === null)
33258
+ return null;
33259
+ return match2[1] ?? match2[2] ?? null;
33260
+ }
32569
33261
  function matchFirst(text, pattern) {
32570
33262
  if (text === null)
32571
33263
  return null;
32572
33264
  const match2 = pattern.exec(text);
32573
33265
  return match2 === null ? null : match2[1];
32574
33266
  }
32575
- var RUNTIME_MANIFESTS, MANIFEST_FILENAMES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, EXACT_VERSION;
33267
+ var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
32576
33268
  var init_manifest2 = __esm({
32577
33269
  "../../packages/envspec/dist/manifest.js"() {
32578
33270
  "use strict";
@@ -32583,11 +33275,19 @@ var init_manifest2 = __esm({
32583
33275
  { runtime: "rust", files: ["Cargo.toml"] },
32584
33276
  { runtime: "jvm", files: ["pom.xml", "build.gradle", "build.gradle.kts"] },
32585
33277
  { runtime: "ruby", files: ["Gemfile"] },
32586
- { runtime: "dotnet", files: [] }
33278
+ { runtime: "dotnet", files: [] },
33279
+ // Last, so an earlier runtime keeps precedence: a Python package with a C
33280
+ // extension built by CMake is still a Python repo, with cpp recorded second.
33281
+ { runtime: "cpp", files: ["CMakeLists.txt", "meson.build"] }
32587
33282
  ];
32588
- MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"]).sort();
33283
+ MAKEFILE_NAMES = ["GNUmakefile", "Makefile", "makefile"];
33284
+ C_FAMILY_SOURCE = /\.(?:c|cc|cpp|cxx|h|hpp|hh)$/i;
33285
+ MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
33286
+ JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
32589
33287
  NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
32590
33288
  REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
33289
+ CPP_BUILD_FILES = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
33290
+ CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
32591
33291
  EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
32592
33292
  }
32593
33293
  });
@@ -32660,11 +33360,11 @@ var init_references = __esm({
32660
33360
  });
32661
33361
 
32662
33362
  // ../../packages/envspec/dist/repo.js
32663
- import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
32664
- import { join as join25, relative as relative2, sep as sep5 } from "path";
33363
+ import { readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
33364
+ import { join as join25, relative as relative2, sep as sep6 } from "path";
32665
33365
  function createRepoReader(repoPath) {
32666
33366
  const resolveIn = (relativePath) => relativePath === "" ? repoPath : join25(repoPath, relativePath);
32667
- const toPosix = (absolute) => relative2(repoPath, absolute).split(sep5).join("/");
33367
+ const toPosix = (absolute) => relative2(repoPath, absolute).split(sep6).join("/");
32668
33368
  const readText = (relativePath) => {
32669
33369
  try {
32670
33370
  return readFileSync12(resolveIn(relativePath), "utf8");
@@ -32674,7 +33374,7 @@ function createRepoReader(repoPath) {
32674
33374
  };
32675
33375
  const statOf = (relativePath) => {
32676
33376
  try {
32677
- return statSync3(resolveIn(relativePath));
33377
+ return statSync5(resolveIn(relativePath));
32678
33378
  } catch {
32679
33379
  return null;
32680
33380
  }
@@ -32686,7 +33386,7 @@ function createRepoReader(repoPath) {
32686
33386
  return;
32687
33387
  let names;
32688
33388
  try {
32689
- names = readdirSync2(dir);
33389
+ names = readdirSync3(dir);
32690
33390
  } catch {
32691
33391
  return;
32692
33392
  }
@@ -32721,7 +33421,7 @@ function createRepoReader(repoPath) {
32721
33421
  if (st === null || !st.isDirectory())
32722
33422
  return [];
32723
33423
  try {
32724
- return readdirSync2(resolveIn(relativeDir)).slice().sort();
33424
+ return readdirSync3(resolveIn(relativeDir)).slice().sort();
32725
33425
  } catch {
32726
33426
  return [];
32727
33427
  }
@@ -32982,7 +33682,7 @@ function deriveFromReader(repo) {
32982
33682
  searched: MANIFEST_FILENAMES,
32983
33683
  detail: `no ecosystem manifest was found at the repo root (looked for ${MANIFEST_FILENAMES.join(", ")}), so the language runtime could not be identified`
32984
33684
  });
32985
- } else {
33685
+ } else if (runtime !== "jvm" || JVM_BUILD_FILES.some((file) => repo.exists(file))) {
32986
33686
  derivedFrom.add("manifest");
32987
33687
  }
32988
33688
  if (detection.candidates.length > 1) {
@@ -33046,7 +33746,7 @@ function deriveFromReader(repo) {
33046
33746
  });
33047
33747
  }
33048
33748
  const version = deriveRuntimeVersion(repo, runtime, job);
33049
- if (version.value === null && runtime !== "unknown") {
33749
+ if (version.value === null && runtime !== "unknown" && runtime !== "cpp") {
33050
33750
  unresolved.push({
33051
33751
  kind: "runtime-version-undeclared",
33052
33752
  runtime,
@@ -33146,12 +33846,13 @@ function deriveRuntimeVersion(repo, runtime, job) {
33146
33846
  const setup = SETUP_ACTIONS[runtime];
33147
33847
  const searched = [];
33148
33848
  let rangeFound = null;
33849
+ const pin = (raw) => runtime === "jvm" ? javaMajor(raw) : exactVersion(raw);
33149
33850
  if (job !== null && setup !== void 0) {
33150
33851
  const direct = findActionInput(job, setup.action, setup.input);
33151
33852
  if (direct !== null) {
33152
33853
  searched.push(`${job.file} ${setup.action} ${setup.input}`);
33153
33854
  const reduced = reduceExpression(direct.command, job.singletons);
33154
- const exact = exactVersion(reduced);
33855
+ const exact = pin(reduced);
33155
33856
  if (exact !== null)
33156
33857
  return { value: exact, searched, rangeFound: null };
33157
33858
  if (reduced !== null)
@@ -33160,7 +33861,7 @@ function deriveRuntimeVersion(repo, runtime, job) {
33160
33861
  const viaFile = findActionInput(job, setup.action, setup.fileInput);
33161
33862
  if (viaFile !== null) {
33162
33863
  searched.push(`${job.file} ${setup.action} ${setup.fileInput} -> ${viaFile.command}`);
33163
- const exact = exactVersion(repo.readText(viaFile.command));
33864
+ const exact = pin(repo.readText(viaFile.command));
33164
33865
  if (exact !== null)
33165
33866
  return { value: exact, searched, rangeFound: null };
33166
33867
  }
@@ -33185,6 +33886,10 @@ function testSearchPaths(runtime, workflowFiles) {
33185
33886
  paths.push("**/*_test.go");
33186
33887
  if (runtime === "ruby")
33187
33888
  paths.push("Gemfile", "Rakefile");
33889
+ if (runtime === "jvm")
33890
+ paths.push(...JVM_BUILD_FILES);
33891
+ if (runtime === "cpp")
33892
+ paths.push(...CPP_BUILD_FILES);
33188
33893
  if (runtime === "unknown")
33189
33894
  paths.push(...MANIFEST_FILENAMES);
33190
33895
  return [...new Set(paths)].sort();
@@ -33202,14 +33907,37 @@ function describeTestSearch(repo, runtime, workflowFiles) {
33202
33907
  if (runtime === "python") {
33203
33908
  return `${ciPart}, and nothing in the repo names a test runner \u2014 no \`[tool.pytest.ini_options]\`, no \`pytest.ini\`, and no pytest dependency in \`pyproject.toml\` or a requirements file`;
33204
33909
  }
33910
+ if (runtime === "jvm" && !JVM_BUILD_FILES.some((file) => repo.exists(file))) {
33911
+ return `${ciPart}, and the repo has Java sources but no build file (${JVM_BUILD_FILES.join(", ")}), so there is no build to run tests with. Compiling the sources with \`javac\` alone would run no tests`;
33912
+ }
33913
+ if (runtime === "cpp")
33914
+ return `${ciPart}, and ${describeCppSearch(repo)}`;
33205
33915
  return `${ciPart}, and no ${runtime} test convention applies to this tree`;
33206
33916
  }
33917
+ function describeCppSearch(repo) {
33918
+ const cmake = repo.readText("CMakeLists.txt");
33919
+ if (cmake !== null && !CMAKE_DECLARES_TESTS.test(cmakeCode(cmake))) {
33920
+ return "`CMakeLists.txt` registers no tests: no `enable_testing()`, `include(CTest)` or `add_test()`";
33921
+ }
33922
+ if (cmake === null && repo.exists("meson.build")) {
33923
+ return "`meson.build` declares no `test()`";
33924
+ }
33925
+ const makefile = repo.listNames("").find((name) => MAKEFILE_NAMES.includes(name));
33926
+ if (cmake === null && makefile !== void 0) {
33927
+ return `\`${makefile}\` has no \`test\` or \`check\` target`;
33928
+ }
33929
+ return `no C/C++ test convention applies to this tree (looked in ${CPP_BUILD_FILES.join(", ")})`;
33930
+ }
33207
33931
  function installSearchPaths(runtime, workflowFiles) {
33208
33932
  const paths = [...workflowFiles];
33209
33933
  if (runtime === "node")
33210
33934
  paths.push("package-lock.json", "pnpm-lock.yaml", "yarn.lock");
33211
33935
  if (runtime === "python")
33212
33936
  paths.push("poetry.lock", "pyproject.toml", "requirements.txt", "uv.lock");
33937
+ if (runtime === "jvm")
33938
+ paths.push(...JVM_BUILD_FILES);
33939
+ if (runtime === "cpp")
33940
+ paths.push(...CPP_BUILD_FILES);
33213
33941
  if (runtime === "unknown")
33214
33942
  paths.push(...MANIFEST_FILENAMES);
33215
33943
  return [...new Set(paths)].sort();
@@ -33288,12 +34016,46 @@ var init_dist3 = __esm({
33288
34016
  }
33289
34017
  });
33290
34018
 
34019
+ // ../../packages/envrun/dist/gradleTestSummary.js
34020
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync14 } from "fs";
34021
+ import { join as join26 } from "path";
34022
+ function stageGradleTestSummary(jailHome, guestUser, io = {}) {
34023
+ const dir = join26(jailHome, ".gradle", "init.d");
34024
+ mkdirSync5(dir, { recursive: true });
34025
+ writeFileSync14(join26(dir, "terminalhire-test-summary.gradle"), GRADLE_TEST_SUMMARY_INIT);
34026
+ const ids2 = io.ids === void 0 ? hostIds() : io.ids;
34027
+ if (ids2?.uid === 0 && guestUser && guestUser.uid !== 0) {
34028
+ (io.chown ?? chownTree)(join26(jailHome, ".gradle"), guestUser);
34029
+ }
34030
+ }
34031
+ var GRADLE_TEST_MARKER, GRADLE_TEST_SUMMARY_INIT;
34032
+ var init_gradleTestSummary = __esm({
34033
+ "../../packages/envrun/dist/gradleTestSummary.js"() {
34034
+ "use strict";
34035
+ init_dist();
34036
+ GRADLE_TEST_MARKER = "terminalhire-gradle-tests:";
34037
+ GRADLE_TEST_SUMMARY_INIT = `// Written by terminalhire (TERM-1122): prints each Test task's counts, because
34038
+ // Gradle prints none when every test passes.
34039
+ allprojects {
34040
+ tasks.withType(Test).configureEach { task ->
34041
+ def label = task.path
34042
+ task.afterSuite { desc, result ->
34043
+ if (desc.parent == null) {
34044
+ println "${GRADLE_TEST_MARKER} task=\${label} total=\${result.testCount} passed=\${result.successfulTestCount} failed=\${result.failedTestCount} skipped=\${result.skippedTestCount}"
34045
+ }
34046
+ }
34047
+ }
34048
+ }
34049
+ `;
34050
+ }
34051
+ });
34052
+
33291
34053
  // ../../packages/envrun/dist/thrun.js
33292
34054
  import { execFileSync, spawnSync as spawnSync6 } from "child_process";
33293
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync4, rmSync as rmSync9 } from "fs";
34055
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync4, rmSync as rmSync9 } from "fs";
33294
34056
  import { randomUUID as randomUUID3 } from "crypto";
33295
34057
  import { tmpdir as tmpdir4 } from "os";
33296
- import { join as join26 } from "path";
34058
+ import { join as join27 } from "path";
33297
34059
  function git(repoDir, args, allowNonZero = false) {
33298
34060
  const res = spawnSync6("git", [...args], {
33299
34061
  cwd: repoDir,
@@ -33308,7 +34070,7 @@ function git(repoDir, args, allowNonZero = false) {
33308
34070
  return res.stdout ?? "";
33309
34071
  }
33310
34072
  function collectWorkingDiff(repoDir, opts = {}) {
33311
- if (!existsSync12(join26(repoDir, ".git"))) {
34073
+ if (!existsSync12(join27(repoDir, ".git"))) {
33312
34074
  throw new ThRunError(`${repoDir} is not a git checkout (no .git). \`th run\` ships the working diff, so it needs a repository to read one from.`);
33313
34075
  }
33314
34076
  const headSha = git(repoDir, ["rev-parse", "HEAD"]).trim();
@@ -33456,7 +34218,7 @@ function credentialFreeHome() {
33456
34218
  return credentialFreeHomeDir;
33457
34219
  let made;
33458
34220
  try {
33459
- made = mkdtempSync4(join26(tmpdir4(), "th-run-nohome-"));
34221
+ made = mkdtempSync4(join27(tmpdir4(), "th-run-nohome-"));
33460
34222
  } catch (err) {
33461
34223
  throw new RunRefusalError("could not create the empty directory this clone uses as its home, so the clone would read the credentials on this machine instead. That is our environment failing, not your tests: check that the temp directory is writable.", { cause: err });
33462
34224
  }
@@ -33531,7 +34293,7 @@ function cloneTargetAtUnguarded(opts) {
33531
34293
  if (persisted !== null) {
33532
34294
  throw new RunRefusalError(`refusing to clone from a URL carrying ${persisted}: \`git remote add\` writes the source verbatim into .git/config, which is mounted where the repo\u2019s own test command runs. Fetch with the credential out of band so it is never written to disk \u2014 this runner takes one as an HTTP header, which is never persisted.`);
33533
34295
  }
33534
- mkdirSync5(opts.dest, { recursive: true });
34296
+ mkdirSync6(opts.dest, { recursive: true });
33535
34297
  const runOut = (args) => execFileSync("git", [...gitConfigArgs(), ...args], {
33536
34298
  cwd: opts.dest,
33537
34299
  encoding: "utf8",
@@ -33570,7 +34332,7 @@ function cloneTargetAtUnguarded(opts) {
33570
34332
  }
33571
34333
  function scrubCloneSource(dest, run2) {
33572
34334
  run2(["remote", "remove", "origin"]);
33573
- rmSync9(join26(dest, ".git", "FETCH_HEAD"), { force: true });
34335
+ rmSync9(join27(dest, ".git", "FETCH_HEAD"), { force: true });
33574
34336
  }
33575
34337
  function publishableTarget(url) {
33576
34338
  if (separatorInTarget(url) !== null)
@@ -33718,6 +34480,9 @@ function refusedRun(fields) {
33718
34480
  containerImage: null,
33719
34481
  containerImageDigest: null,
33720
34482
  leaksClean: null,
34483
+ // Null for the same reason, and never `unobserved`: that word says we tried to take a
34484
+ // census and could not, and here there was nothing to take one of (TERM-1144).
34485
+ leakState: null,
33721
34486
  // A refused run never held a lease, so there is no venue to describe. Same
33722
34487
  // reasoning as `leaksClean` above: null because nothing happened, and it must
33723
34488
  // not read as a venue we looked at and could not name.
@@ -33855,7 +34620,7 @@ async function verifyWorkingDiff(req) {
33855
34620
  }
33856
34621
  async function runVerification(req, ctx) {
33857
34622
  const { runId, startedAt } = ctx;
33858
- const labels = { ...req.labels ?? {}, [RUN_LABEL_KEY]: "term-350" };
34623
+ const labels = runLabels(runId, req.labels);
33859
34624
  const progress = req.onProgress ?? (() => {
33860
34625
  });
33861
34626
  const badTarget = unacceptableTarget(req);
@@ -33945,10 +34710,10 @@ async function runVerification(req, ctx) {
33945
34710
  venueIdentity: null
33946
34711
  };
33947
34712
  }
33948
- const stage = join26(req.scratchRoot, runId);
33949
- const cloneDir = join26(stage, "clone");
33950
- const scratch = join26(stage, "scratch");
33951
- mkdirSync5(scratch, { recursive: true });
34713
+ const stage = join27(req.scratchRoot, runId);
34714
+ const cloneDir = join27(stage, "clone");
34715
+ const scratch = join27(stage, "scratch");
34716
+ mkdirSync6(scratch, { recursive: true });
33952
34717
  assertSafeTargetSha(req.targetSha);
33953
34718
  progress("clone", `${publishableTarget(req.targetRepo)} @ ${req.targetSha.slice(0, 12)}`);
33954
34719
  cloneTargetAt({
@@ -33975,7 +34740,8 @@ async function runVerification(req, ctx) {
33975
34740
  const derived = deriveEnvironmentSpec(cloneDir);
33976
34741
  const spec = req.testCommandOverride === void 0 ? derived : { ...derived, testCommand: req.testCommandOverride };
33977
34742
  progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
33978
- const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion);
34743
+ refuseUnbuildableSpec(spec);
34744
+ const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
33979
34745
  const resolved = await resolveLease(placement, runId);
33980
34746
  if (!resolved.ok) {
33981
34747
  return {
@@ -34009,11 +34775,13 @@ async function runVerification(req, ctx) {
34009
34775
  }
34010
34776
  const lease = resolved.lease;
34011
34777
  try {
34012
- buildJail(scratch);
34778
+ buildJail(scratch, lease.guestUser);
34779
+ if (spec.runtime === "jvm")
34780
+ stageGradleTestSummary(localJailPaths(scratch).jail, lease.guestUser);
34013
34781
  const venuePaths = await lease.stage({
34014
34782
  cloneDir,
34015
34783
  scratchRoot: scratch,
34016
- previewDir: join26(stage, "preview"),
34784
+ previewDir: join27(stage, "preview"),
34017
34785
  // On a dispatched run the commit is the statement of what was tested, so
34018
34786
  // it rides with the tree and the venue seam refuses a tree that is not
34019
34787
  // that commit (design §6 item 4, TERM-892 — the guard lives in
@@ -34077,6 +34845,7 @@ async function runVerification(req, ctx) {
34077
34845
  // the operator would sign our name onto their choice.
34078
34846
  imageSource: req.image === void 0 ? "detected" : req.imageOrigin === "operator" ? "operator-declared" : "developer-declared",
34079
34847
  leaksClean: verdict.leaks.clean,
34848
+ leakState: verdict.leaks.state,
34080
34849
  // Built from the LEASE, over the client that ran the steps — never from
34081
34850
  // `req.placement`, which is a request. `venueDescriptor.ts` carries the
34082
34851
  // reasoning and the #735 failure that makes the distinction load-bearing.
@@ -34134,6 +34903,7 @@ var init_thrun = __esm({
34134
34903
  init_execute();
34135
34904
  init_placement();
34136
34905
  init_venue();
34906
+ init_gradleTestSummary();
34137
34907
  init_venueDescriptor();
34138
34908
  init_result();
34139
34909
  ThRunError = class extends Error {
@@ -34271,14 +35041,14 @@ function migrationUnits(runner, migrations) {
34271
35041
  return [...byDir.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([id, path5]) => ({ id, path: path5 }));
34272
35042
  }
34273
35043
  case "alembic":
34274
- return migrations.filter((p) => /^alembic\/versions\/[^/]+\.py$/.test(p) && !p.endsWith("/__init__.py")).sort().map((path5) => ({ id: basename4(path5).replace(/\.py$/, ""), path: path5 }));
35044
+ return migrations.filter((p) => /^alembic\/versions\/[^/]+\.py$/.test(p) && !p.endsWith("/__init__.py")).sort().map((path5) => ({ id: basename5(path5).replace(/\.py$/, ""), path: path5 }));
34275
35045
  case "rails":
34276
- return migrations.filter((p) => /^db\/migrate\/[^/]+\.rb$/.test(p)).sort().map((path5) => ({ id: /^(\d+)/.exec(basename4(path5))?.[1] ?? basename4(path5), path: path5 }));
35046
+ return migrations.filter((p) => /^db\/migrate\/[^/]+\.rb$/.test(p)).sort().map((path5) => ({ id: /^(\d+)/.exec(basename5(path5))?.[1] ?? basename5(path5), path: path5 }));
34277
35047
  case "sql":
34278
35048
  return migrations.filter((p) => p.endsWith(".sql") && !p.startsWith("prisma/migrations/")).sort().map((path5) => ({ id: path5, path: path5 }));
34279
35049
  }
34280
35050
  }
34281
- function basename4(path5) {
35051
+ function basename5(path5) {
34282
35052
  const at = path5.lastIndexOf("/");
34283
35053
  return at === -1 ? path5 : path5.slice(at + 1);
34284
35054
  }
@@ -34376,8 +35146,8 @@ var init_dbplan = __esm({
34376
35146
 
34377
35147
  // ../../packages/envrun/dist/dbstack.js
34378
35148
  import { spawnSync as spawnSync7 } from "child_process";
34379
- import { randomBytes as randomBytes11 } from "crypto";
34380
- import { mkdirSync as mkdirSync6 } from "fs";
35149
+ import { randomBytes as randomBytes12 } from "crypto";
35150
+ import { mkdirSync as mkdirSync7 } from "fs";
34381
35151
  function installCommandFor(runner) {
34382
35152
  switch (runner) {
34383
35153
  case "sql":
@@ -34412,7 +35182,7 @@ function docker2(args, timeoutMs = DOCKER_TIMEOUT_MS2) {
34412
35182
  function generateCredentials(host) {
34413
35183
  return {
34414
35184
  user: "thverify",
34415
- password: randomBytes11(24).toString("base64url"),
35185
+ password: randomBytes12(24).toString("base64url"),
34416
35186
  database: "thverify",
34417
35187
  host,
34418
35188
  port: 5432
@@ -34608,7 +35378,7 @@ function startDatabase(req) {
34608
35378
  if (!ready.ok) {
34609
35379
  throw new DbStackError(`INFRASTRUCTURE/INCONCLUSIVE: Postgres never accepted queries at 127.0.0.1:${String(creds.port)} within the readiness window: ${ready.detail}`);
34610
35380
  }
34611
- const query = (sql) => {
35381
+ const query2 = (sql) => {
34612
35382
  const res = docker2([
34613
35383
  "exec",
34614
35384
  `--env=PGPASSWORD=${creds.password}`,
@@ -34656,7 +35426,7 @@ function startDatabase(req) {
34656
35426
  credentials: creds,
34657
35427
  image,
34658
35428
  readyMs: ready.ms,
34659
- query,
35429
+ query: query2,
34660
35430
  runOnNetwork,
34661
35431
  teardown
34662
35432
  };
@@ -34719,8 +35489,8 @@ async function installLocalMigrationTooling(opts) {
34719
35489
  };
34720
35490
  }
34721
35491
  const { jail, tmp } = buildJail(opts.scratchRoot);
34722
- mkdirSync6(jail, { recursive: true });
34723
- mkdirSync6(tmp, { recursive: true });
35492
+ mkdirSync7(jail, { recursive: true });
35493
+ mkdirSync7(tmp, { recursive: true });
34724
35494
  const spec = {
34725
35495
  profile: "install",
34726
35496
  clone: opts.repoDir,
@@ -35015,6 +35785,7 @@ __export(dist_exports, {
35015
35785
  GOOGLE_JWKS_URL: () => GOOGLE_JWKS_URL,
35016
35786
  HostedVenueError: () => HostedVenueError,
35017
35787
  JWKS_FETCH_TIMEOUT_MS: () => JWKS_FETCH_TIMEOUT_MS,
35788
+ LEAK_STATES: () => LEAK_STATES,
35018
35789
  LOCAL_MEASUREMENT_PREFIX: () => LOCAL_MEASUREMENT_PREFIX,
35019
35790
  LabelWatch: () => LabelWatch,
35020
35791
  MIN_GIT_VERSION_FOR_END_OF_OPTIONS: () => MIN_GIT_VERSION_FOR_END_OF_OPTIONS,
@@ -35031,6 +35802,7 @@ __export(dist_exports, {
35031
35802
  REDACTED_TARGET_SHA: () => REDACTED_TARGET_SHA,
35032
35803
  RELEASED_LEASE_CENSUS_REASON: () => RELEASED_LEASE_CENSUS_REASON,
35033
35804
  REPO_DIGEST_RE: () => REPO_DIGEST_RE,
35805
+ RUN_ID_LABEL_KEY: () => RUN_ID_LABEL_KEY,
35034
35806
  RUN_IMAGE_SOURCES: () => RUN_IMAGE_SOURCES,
35035
35807
  RUN_LABEL_KEY: () => RUN_LABEL_KEY,
35036
35808
  RUN_RESULT_FIELDS: () => RUN_RESULT_FIELDS,
@@ -35048,6 +35820,7 @@ __export(dist_exports, {
35048
35820
  VENUE_GCLOUD_CONFIG: () => VENUE_GCLOUD_CONFIG,
35049
35821
  VENUE_SSH_USER: () => VENUE_SSH_USER,
35050
35822
  VENV_DIR: () => VENV_DIR,
35823
+ VERIFICATION_OUTCOMES: () => VERIFICATION_OUTCOMES,
35051
35824
  VenueRollbackError: () => VenueRollbackError,
35052
35825
  acquireTransactionally: () => acquireTransactionally,
35053
35826
  alembicChainPosition: () => alembicChainPosition,
@@ -35061,6 +35834,7 @@ __export(dist_exports, {
35061
35834
  assertVenueOwnerDeclared: () => assertVenueOwnerDeclared,
35062
35835
  bookkeepingFor: () => bookkeepingFor,
35063
35836
  census: () => census,
35837
+ censusReport: () => censusReport,
35064
35838
  censusTotal: () => censusTotal,
35065
35839
  classifyBootFailure: () => classifyBootFailure,
35066
35840
  classifyProbeFailure: () => classifyProbeFailure,
@@ -35080,6 +35854,7 @@ __export(dist_exports, {
35080
35854
  detectRunner: () => detectRunner,
35081
35855
  endOfOptionsUnsupported: () => endOfOptionsUnsupported,
35082
35856
  ensureVenueServiceCredentials: () => ensureVenueServiceCredentials,
35857
+ exitCodeFor: () => exitCodeFor,
35083
35858
  failureSourceOf: () => failureSourceOf,
35084
35859
  findRunRefusal: () => findRunRefusal,
35085
35860
  gcpBootArgv: () => gcpBootArgv,
@@ -35094,11 +35869,13 @@ __export(dist_exports, {
35094
35869
  identityProbeCommand: () => identityProbeCommand,
35095
35870
  imageForRuntime: () => imageForRuntime,
35096
35871
  imageRepo: () => imageRepo,
35872
+ imageVariantFor: () => imageVariantFor,
35097
35873
  installCommandFor: () => installCommandFor,
35098
35874
  installLocalMigrationTooling: () => installLocalMigrationTooling,
35099
35875
  isBookkeepingTable: () => isBookkeepingTable,
35100
35876
  isCommandUnavailable: () => isCommandUnavailable,
35101
35877
  isGreen: () => isGreen,
35878
+ isMissingSystemDependency: () => isMissingSystemDependency,
35102
35879
  isOurFault: () => isOurFault,
35103
35880
  judgeCompleteness: () => judgeCompleteness,
35104
35881
  judgeLeaks: () => judgeLeaks,
@@ -35123,12 +35900,14 @@ __export(dist_exports, {
35123
35900
  readSchema: () => readSchema,
35124
35901
  recordedApplied: () => recordedApplied,
35125
35902
  refuseSshTransport: () => refuseSshTransport,
35903
+ refuseUnbuildableSpec: () => refuseUnbuildableSpec,
35126
35904
  renderRunReport: () => renderRunReport,
35127
35905
  renderVenueLine: () => renderVenueLine,
35128
35906
  renderVerdictLine: () => renderVerdictLine,
35129
35907
  resolveImageForSpec: () => resolveImageForSpec,
35130
35908
  resolveLease: () => resolveLease,
35131
35909
  runEnvironmentSpec: () => runEnvironmentSpec,
35910
+ runLabels: () => runLabels,
35132
35911
  setManifestProbe: () => setManifestProbe,
35133
35912
  sha256Hex: () => sha256Hex,
35134
35913
  signRunStatement: () => signRunStatement,
@@ -35209,8 +35988,8 @@ __export(cache_store_exports, {
35209
35988
  updateIndexCache: () => updateIndexCache,
35210
35989
  writeIndexCache: () => writeIndexCache
35211
35990
  });
35212
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync14, renameSync as renameSync5 } from "fs";
35213
- import { join as join27 } from "path";
35991
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync15, renameSync as renameSync5 } from "fs";
35992
+ import { join as join28 } from "path";
35214
35993
  import { homedir as homedir13 } from "os";
35215
35994
  function readCacheEntry() {
35216
35995
  try {
@@ -35229,7 +36008,7 @@ function updateIndexCache(patch) {
35229
36008
  ts: Date.now()
35230
36009
  };
35231
36010
  const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
35232
- writeFileSync14(tmp, JSON.stringify(entry), "utf8");
36011
+ writeFileSync15(tmp, JSON.stringify(entry), "utf8");
35233
36012
  renameSync5(tmp, INDEX_CACHE_FILE);
35234
36013
  return entry;
35235
36014
  }
@@ -35241,8 +36020,8 @@ var init_cache_store = __esm({
35241
36020
  "bin/cache-store.js"() {
35242
36021
  "use strict";
35243
36022
  init_state_dir();
35244
- TERMINALHIRE_DIR10 = process.env.TERMINALHIRE_DIR || join27(homedir13(), ".terminalhire");
35245
- INDEX_CACHE_FILE = join27(TERMINALHIRE_DIR10, "index-cache.json");
36023
+ TERMINALHIRE_DIR10 = process.env.TERMINALHIRE_DIR || join28(homedir13(), ".terminalhire");
36024
+ INDEX_CACHE_FILE = join28(TERMINALHIRE_DIR10, "index-cache.json");
35246
36025
  SCHEMA_VERSION2 = 1;
35247
36026
  tmpCounter = 0;
35248
36027
  }
@@ -35442,17 +36221,17 @@ var init_audit2 = __esm({
35442
36221
  init_src();
35443
36222
  import {
35444
36223
  readFileSync as readFileSync14,
35445
- writeFileSync as writeFileSync15,
35446
- mkdirSync as mkdirSync7,
36224
+ writeFileSync as writeFileSync16,
36225
+ mkdirSync as mkdirSync8,
35447
36226
  mkdtempSync as mkdtempSync5,
35448
36227
  renameSync as renameSync6,
35449
36228
  existsSync as existsSync13,
35450
- lstatSync as lstatSync3,
36229
+ lstatSync as lstatSync4,
35451
36230
  realpathSync as realpathSync2,
35452
36231
  rmSync as rmSync10,
35453
- readdirSync as readdirSync3
36232
+ readdirSync as readdirSync4
35454
36233
  } from "fs";
35455
- import { join as join28, dirname as dirname8, isAbsolute as isAbsolute4, resolve as pathResolve } from "path";
36234
+ import { join as join29, dirname as dirname8, isAbsolute as isAbsolute4, resolve as pathResolve } from "path";
35456
36235
  import { createHash as createHash10 } from "crypto";
35457
36236
  import { homedir as homedir14, hostname as osHostname } from "os";
35458
36237
  import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
@@ -35899,10 +36678,10 @@ async function syncFounderVerdicts({
35899
36678
 
35900
36679
  // bin/jpi-claim.js
35901
36680
  init_founder_note_sync();
35902
- var TERMINALHIRE_DIR11 = process.env.TERMINALHIRE_DIR || join28(homedir14(), ".terminalhire");
35903
- var INDEX_CACHE_FILE2 = join28(TERMINALHIRE_DIR11, "index-cache.json");
35904
- var CLAIM_PUSH_MARKER = join28(TERMINALHIRE_DIR11, "claim-push.json");
35905
- var REPO_CONTINUITY_NUDGE_MARKER = join28(TERMINALHIRE_DIR11, "repo-continuity-nudged.json");
36681
+ var TERMINALHIRE_DIR11 = process.env.TERMINALHIRE_DIR || join29(homedir14(), ".terminalhire");
36682
+ var INDEX_CACHE_FILE2 = join29(TERMINALHIRE_DIR11, "index-cache.json");
36683
+ var CLAIM_PUSH_MARKER = join29(TERMINALHIRE_DIR11, "claim-push.json");
36684
+ var REPO_CONTINUITY_NUDGE_MARKER = join29(TERMINALHIRE_DIR11, "repo-continuity-nudged.json");
35906
36685
  function readNudgedClaimIds() {
35907
36686
  try {
35908
36687
  const raw = JSON.parse(readFileSync14(REPO_CONTINUITY_NUDGE_MARKER, "utf8"));
@@ -35916,7 +36695,7 @@ function markClaimNudged(id) {
35916
36695
  const ids2 = readNudgedClaimIds();
35917
36696
  ids2.add(id);
35918
36697
  ensureStateDir(TERMINALHIRE_DIR11);
35919
- writeFileSync15(REPO_CONTINUITY_NUDGE_MARKER, JSON.stringify({ claimIds: [...ids2] }), "utf8");
36698
+ writeFileSync16(REPO_CONTINUITY_NUDGE_MARKER, JSON.stringify({ claimIds: [...ids2] }), "utf8");
35920
36699
  } catch {
35921
36700
  }
35922
36701
  }
@@ -36033,7 +36812,7 @@ async function sh(cmd, args, opts = {}) {
36033
36812
  async function confirm(question) {
36034
36813
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
36035
36814
  try {
36036
- const ans = await new Promise((resolve4) => rl.question(question, resolve4));
36815
+ const ans = await new Promise((resolve5) => rl.question(question, resolve5));
36037
36816
  return /^y(es)?$/i.test(String(ans).trim());
36038
36817
  } finally {
36039
36818
  rl.close();
@@ -36064,7 +36843,7 @@ async function confirmSubmit(flags, question, { unattendedAllowed = true, refusa
36064
36843
  async function ask(question) {
36065
36844
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
36066
36845
  try {
36067
- const ans = await new Promise((resolve4) => rl.question(question, resolve4));
36846
+ const ans = await new Promise((resolve5) => rl.question(question, resolve5));
36068
36847
  return String(ans).trim();
36069
36848
  } finally {
36070
36849
  rl.close();
@@ -36178,7 +36957,7 @@ function inferSubmitClaim({
36178
36957
  }
36179
36958
  return { candidates: [] };
36180
36959
  }
36181
- function canonicalPath(p) {
36960
+ function canonicalPath2(p) {
36182
36961
  if (typeof p !== "string" || p === "") return p;
36183
36962
  const resolved = pathResolve(p);
36184
36963
  try {
@@ -38249,7 +39028,7 @@ async function cmdAttach(id, worktree, branch) {
38249
39028
  function workDirFor(repoFullName, issueNumber) {
38250
39029
  const [owner, repo] = String(repoFullName).split("/");
38251
39030
  const suffix = issueNumber ? `-${issueNumber}` : "";
38252
- return join28(homedir14(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
39031
+ return join29(homedir14(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
38253
39032
  }
38254
39033
  function startBranchFor(repoFullName, issueNumber) {
38255
39034
  const repo = String(repoFullName).split("/")[1] || "claim";
@@ -38505,7 +39284,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
38505
39284
  );
38506
39285
  process.exit(1);
38507
39286
  }
38508
- mkdirSync7(join28(homedir14(), "terminalhire", "work"), { recursive: true });
39287
+ mkdirSync8(join29(homedir14(), "terminalhire", "work"), { recursive: true });
38509
39288
  const { createProgress: createProgress2, parseGitProgress: parseGitProgress2, splitProgressChunk: splitProgressChunk2, shStream: shStream2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
38510
39289
  const progress = createProgress2();
38511
39290
  let forkFullName;
@@ -38633,7 +39412,7 @@ function founderPostingIdOf(claim) {
38633
39412
  }
38634
39413
  function sliceWorkDirFor(claimLocalId) {
38635
39414
  const safe = String(claimLocalId).replace(/[^A-Za-z0-9._-]/g, "-");
38636
- return join28(homedir14(), "terminalhire", "work", `slice-${safe}`);
39415
+ return join29(homedir14(), "terminalhire", "work", `slice-${safe}`);
38637
39416
  }
38638
39417
  function assertNoBooleanPath(dest, flagName) {
38639
39418
  const last = String(dest).split(/[\\/]/).filter(Boolean).pop();
@@ -38646,7 +39425,7 @@ function assertNoBooleanPath(dest, flagName) {
38646
39425
  }
38647
39426
  function resolveDeliveryDir(flags, claimLocalId, { existsFn, readdirFn } = {}) {
38648
39427
  const exists = existsFn ?? existsSync13;
38649
- const readdir3 = readdirFn ?? readdirSync3;
39428
+ const readdir3 = readdirFn ?? readdirSync4;
38650
39429
  let probing = null;
38651
39430
  try {
38652
39431
  const base = flags?.dir ? assertNoBooleanPath(pathResolve(String(flags.dir)), "dir") : sliceWorkDirFor(claimLocalId);
@@ -38758,9 +39537,9 @@ function writeSliceFiles(destDir, files) {
38758
39537
  const unavailable = [];
38759
39538
  for (const f of files) {
38760
39539
  if (typeof f.content === "string") {
38761
- const abs = join28(destDir, f.path);
38762
- mkdirSync7(dirname8(abs), { recursive: true });
38763
- writeFileSync15(abs, f.content, "utf8");
39540
+ const abs = join29(destDir, f.path);
39541
+ mkdirSync8(dirname8(abs), { recursive: true });
39542
+ writeFileSync16(abs, f.content, "utf8");
38764
39543
  written.push(f.path);
38765
39544
  } else {
38766
39545
  unavailable.push({ path: f.path, reason: f.unavailableReason || "(no reason given)" });
@@ -38794,7 +39573,7 @@ function writeDeliveredBrief(destDir, spec) {
38794
39573
  function ensureExcludedPackDir(destDir) {
38795
39574
  let occupant = null;
38796
39575
  try {
38797
- occupant = lstatSync3(join28(destDir, BRIEF_DIR));
39576
+ occupant = lstatSync4(join29(destDir, BRIEF_DIR));
38798
39577
  } catch (err) {
38799
39578
  if (err?.code !== "ENOENT") {
38800
39579
  return {
@@ -38809,12 +39588,12 @@ function ensureExcludedPackDir(destDir) {
38809
39588
  reason: `${BRIEF_DIR}/ already exists in the delivered tree, and excluding it would hide that content from your patch`
38810
39589
  };
38811
39590
  }
38812
- const excludeFile = join28(destDir, ".git", "info", "exclude");
39591
+ const excludeFile = join29(destDir, ".git", "info", "exclude");
38813
39592
  try {
38814
39593
  const existing = existsSync13(excludeFile) ? readFileSync14(excludeFile, "utf8") : "";
38815
39594
  if (!existing.split("\n").includes(BRIEF_EXCLUDE_LINE)) {
38816
- mkdirSync7(dirname8(excludeFile), { recursive: true });
38817
- writeFileSync15(
39595
+ mkdirSync8(dirname8(excludeFile), { recursive: true });
39596
+ writeFileSync16(
38818
39597
  excludeFile,
38819
39598
  `${existing}${existing === "" || existing.endsWith("\n") ? "" : "\n"}${BRIEF_EXCLUDE_LINE}
38820
39599
  `,
@@ -38828,9 +39607,9 @@ function ensureExcludedPackDir(destDir) {
38828
39607
  }
38829
39608
  function writePackFile(destDir, relPath, content, what) {
38830
39609
  try {
38831
- const abs = join28(destDir, relPath);
38832
- mkdirSync7(dirname8(abs), { recursive: true });
38833
- writeFileSync15(abs, content, { encoding: "utf8", flag: "wx" });
39610
+ const abs = join29(destDir, relPath);
39611
+ mkdirSync8(dirname8(abs), { recursive: true });
39612
+ writeFileSync16(abs, content, { encoding: "utf8", flag: "wx" });
38834
39613
  } catch (err) {
38835
39614
  return { written: false, reason: `the ${what} could not be written (${err.message})` };
38836
39615
  }
@@ -39245,7 +40024,7 @@ async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRep
39245
40024
  if (resolvedDir.suffixed) {
39246
40025
  console.log(`terminalhire claim: the usual directory has content \u2014 using ${dest}`);
39247
40026
  }
39248
- mkdirSync7(dest, { recursive: true });
40027
+ mkdirSync8(dest, { recursive: true });
39249
40028
  const branch = `claim/${String(claim.id).replace(/[^A-Za-z0-9._-]/g, "-")}`;
39250
40029
  let engine;
39251
40030
  try {
@@ -39427,7 +40206,7 @@ async function attemptSliceDelivery(id, flags = {}) {
39427
40206
  if (resolvedDir.suffixed) {
39428
40207
  console.log(`terminalhire claim: the usual directory has content \u2014 using ${finalDest}`);
39429
40208
  }
39430
- mkdirSync7(dirname8(finalDest), { recursive: true });
40209
+ mkdirSync8(dirname8(finalDest), { recursive: true });
39431
40210
  let dest = mkdtempSync5(`${finalDest}.tmp-`);
39432
40211
  const { written, unavailable } = writeSliceFiles(dest, body.files);
39433
40212
  const branch = `claim/${String(claim.id).replace(/[^A-Za-z0-9._-]/g, "-")}`;
@@ -39750,7 +40529,7 @@ async function cmdSubmit(id, flags = {}) {
39750
40529
  cwdToplevel: cwdToplevel2,
39751
40530
  cwd: pathResolve(dir),
39752
40531
  branch,
39753
- canonicalize: canonicalPath
40532
+ canonicalize: canonicalPath2
39754
40533
  });
39755
40534
  if (inferred.claim) {
39756
40535
  id = inferred.claim.id;
@@ -39968,7 +40747,7 @@ async function cmdSubmit(id, flags = {}) {
39968
40747
  const head = `${ghUser}:${claim.branch}`;
39969
40748
  const title = flags.title || claim.title;
39970
40749
  const noBody = Boolean(flags["no-body"]);
39971
- const prBodyPath = join28(wt, "PR-BODY.md");
40750
+ const prBodyPath = join29(wt, "PR-BODY.md");
39972
40751
  const bodySource = pickBodySource({
39973
40752
  bodyFileFlag: flags["body-file"],
39974
40753
  noBody,
@@ -40208,7 +40987,7 @@ function readClaimPushMarker() {
40208
40987
  }
40209
40988
  function writeClaimPushMarker(marker) {
40210
40989
  ensureStateDir(TERMINALHIRE_DIR11);
40211
- writeFileSync15(CLAIM_PUSH_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
40990
+ writeFileSync16(CLAIM_PUSH_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
40212
40991
  }
40213
40992
  function clearClaimPushMarker() {
40214
40993
  try {