terminalhire 0.42.21 → 0.42.23

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.
@@ -3554,11 +3554,11 @@ function parseGitHubRef(url) {
3554
3554
  kind: m[3] === "pull" ? "pull" : "issue"
3555
3555
  };
3556
3556
  }
3557
- async function ghGraphQL(query, variables, token, signal, governor) {
3557
+ async function ghGraphQL(query2, variables, token, signal, governor) {
3558
3558
  const init = {
3559
3559
  method: "POST",
3560
3560
  headers: { ...ghHeaders(token), "Content-Type": "application/json" },
3561
- body: JSON.stringify({ query, variables }),
3561
+ body: JSON.stringify({ query: query2, variables }),
3562
3562
  signal
3563
3563
  };
3564
3564
  if (governor) {
@@ -12927,9 +12927,9 @@ async function runLogin() {
12927
12927
  let ghProfile;
12928
12928
  if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
12929
12929
  const { fileURLToPath: fileURLToPath15 } = await import("url");
12930
- const { join: join62 } = await import("path");
12930
+ const { join: join63 } = await import("path");
12931
12931
  const __dirname10 = fileURLToPath15(new URL(".", import.meta.url));
12932
- const fixturePath = join62(__dirname10, "../../fixtures/github-sample.json");
12932
+ const fixturePath = join63(__dirname10, "../../fixtures/github-sample.json");
12933
12933
  const { readFileSync: readFileSync43 } = await import("fs");
12934
12934
  ghProfile = JSON.parse(readFileSync43(fixturePath, "utf8"));
12935
12935
  } else {
@@ -18881,8 +18881,8 @@ var init_stringify = __esm({
18881
18881
  });
18882
18882
 
18883
18883
  // ../../node_modules/@anthropic-ai/sdk/internal/utils/query.mjs
18884
- function stringifyQuery(query) {
18885
- return stringify(query, { arrayFormat: "brackets" });
18884
+ function stringifyQuery(query2) {
18885
+ return stringify(query2, { arrayFormat: "brackets" });
18886
18886
  }
18887
18887
  var init_query = __esm({
18888
18888
  "../../node_modules/@anthropic-ai/sdk/internal/utils/query.mjs"() {
@@ -20966,9 +20966,9 @@ var init_deployment_runs = __esm({
20966
20966
  * ```
20967
20967
  */
20968
20968
  list(params = {}, options) {
20969
- const { betas, ...query } = params ?? {};
20969
+ const { betas, ...query2 } = params ?? {};
20970
20970
  return this._client.getAPIList("/v1/deployment_runs?beta=true", PageCursor, {
20971
- query,
20971
+ query: query2,
20972
20972
  ...options,
20973
20973
  headers: buildHeaders([
20974
20974
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -21080,9 +21080,9 @@ var init_deployments = __esm({
21080
21080
  * ```
21081
21081
  */
21082
21082
  list(params = {}, options) {
21083
- const { betas, ...query } = params ?? {};
21083
+ const { betas, ...query2 } = params ?? {};
21084
21084
  return this._client.getAPIList("/v1/deployments?beta=true", PageCursor, {
21085
- query,
21085
+ query: query2,
21086
21086
  ...options,
21087
21087
  headers: buildHeaders([
21088
21088
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -21242,9 +21242,9 @@ var init_dreams = __esm({
21242
21242
  * ```
21243
21243
  */
21244
21244
  list(params = {}, options) {
21245
- const { betas, ...query } = params ?? {};
21245
+ const { betas, ...query2 } = params ?? {};
21246
21246
  return this._client.getAPIList("/v1/dreams?beta=true", PageCursor, {
21247
- query,
21247
+ query: query2,
21248
21248
  ...options,
21249
21249
  headers: buildHeaders([
21250
21250
  { "anthropic-beta": [...betas ?? [], "dreaming-2026-04-21"].toString() },
@@ -21375,9 +21375,9 @@ var init_files = __esm({
21375
21375
  * ```
21376
21376
  */
21377
21377
  list(params = {}, options) {
21378
- const { betas, ...query } = params ?? {};
21378
+ const { betas, ...query2 } = params ?? {};
21379
21379
  return this._client.getAPIList("/v1/files?beta=true", Page, {
21380
- query,
21380
+ query: query2,
21381
21381
  ...options,
21382
21382
  headers: buildHeaders([
21383
21383
  { "anthropic-beta": [...betas ?? [], "files-api-2025-04-14"].toString() },
@@ -21525,9 +21525,9 @@ var init_models = __esm({
21525
21525
  * ```
21526
21526
  */
21527
21527
  list(params = {}, options) {
21528
- const { betas, ...query } = params ?? {};
21528
+ const { betas, ...query2 } = params ?? {};
21529
21529
  return this._client.getAPIList("/v1/models?beta=true", Page, {
21530
- query,
21530
+ query: query2,
21531
21531
  ...options,
21532
21532
  headers: buildHeaders([
21533
21533
  { ...betas?.toString() != null ? { "anthropic-beta": betas?.toString() } : void 0 },
@@ -21624,9 +21624,9 @@ var init_user_profiles = __esm({
21624
21624
  * ```
21625
21625
  */
21626
21626
  list(params = {}, options) {
21627
- const { betas, ...query } = params ?? {};
21627
+ const { betas, ...query2 } = params ?? {};
21628
21628
  return this._client.getAPIList("/v1/user_profiles?beta=true", PageCursor, {
21629
- query,
21629
+ query: query2,
21630
21630
  ...options,
21631
21631
  headers: buildHeaders([
21632
21632
  { "anthropic-beta": [...betas ?? [], "user-profiles-2026-03-24"].toString() },
@@ -22485,9 +22485,9 @@ var init_versions = __esm({
22485
22485
  * ```
22486
22486
  */
22487
22487
  list(agentID, params = {}, options) {
22488
- const { betas, ...query } = params ?? {};
22488
+ const { betas, ...query2 } = params ?? {};
22489
22489
  return this._client.getAPIList(path`/v1/agents/${agentID}/versions?beta=true`, PageCursor, {
22490
- query,
22490
+ query: query2,
22491
22491
  ...options,
22492
22492
  headers: buildHeaders([
22493
22493
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -22550,9 +22550,9 @@ var init_agents = __esm({
22550
22550
  * ```
22551
22551
  */
22552
22552
  retrieve(agentID, params = {}, options) {
22553
- const { betas, ...query } = params ?? {};
22553
+ const { betas, ...query2 } = params ?? {};
22554
22554
  return this._client.get(path`/v1/agents/${agentID}?beta=true`, {
22555
- query,
22555
+ query: query2,
22556
22556
  ...options,
22557
22557
  headers: buildHeaders([
22558
22558
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -22595,9 +22595,9 @@ var init_agents = __esm({
22595
22595
  * ```
22596
22596
  */
22597
22597
  list(params = {}, options) {
22598
- const { betas, ...query } = params ?? {};
22598
+ const { betas, ...query2 } = params ?? {};
22599
22599
  return this._client.getAPIList("/v1/agents?beta=true", PageCursor, {
22600
- query,
22600
+ query: query2,
22601
22601
  ...options,
22602
22602
  headers: buildHeaders([
22603
22603
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -24687,9 +24687,9 @@ var init_work = __esm({
24687
24687
  * ```
24688
24688
  */
24689
24689
  list(environmentID, params = {}, options) {
24690
- const { betas, ...query } = params ?? {};
24690
+ const { betas, ...query2 } = params ?? {};
24691
24691
  return this._client.getAPIList(path`/v1/environments/${environmentID}/work?beta=true`, PageCursor, {
24692
- query,
24692
+ query: query2,
24693
24693
  ...options,
24694
24694
  headers: buildHeaders([
24695
24695
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -24768,9 +24768,9 @@ var init_work = __esm({
24768
24768
  * ```
24769
24769
  */
24770
24770
  poll(environmentID, params = {}, options) {
24771
- const { betas, "Anthropic-Worker-ID": anthropicWorkerID, ...query } = params ?? {};
24771
+ const { betas, "Anthropic-Worker-ID": anthropicWorkerID, ...query2 } = params ?? {};
24772
24772
  return this._client.get(path`/v1/environments/${environmentID}/work/poll?beta=true`, {
24773
- query,
24773
+ query: query2,
24774
24774
  ...options,
24775
24775
  headers: buildHeaders([
24776
24776
  {
@@ -24967,9 +24967,9 @@ var init_environments = __esm({
24967
24967
  * ```
24968
24968
  */
24969
24969
  list(params = {}, options) {
24970
- const { betas, ...query } = params ?? {};
24970
+ const { betas, ...query2 } = params ?? {};
24971
24971
  return this._client.getAPIList("/v1/environments?beta=true", PageCursor, {
24972
- query,
24972
+ query: query2,
24973
24973
  ...options,
24974
24974
  headers: buildHeaders([
24975
24975
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -25072,9 +25072,9 @@ var init_memories = __esm({
25072
25072
  * ```
25073
25073
  */
25074
25074
  retrieve(memoryID, params, options) {
25075
- const { memory_store_id, betas, ...query } = params;
25075
+ const { memory_store_id, betas, ...query2 } = params;
25076
25076
  return this._client.get(path`/v1/memory_stores/${memory_store_id}/memories/${memoryID}?beta=true`, {
25077
- query,
25077
+ query: query2,
25078
25078
  ...options,
25079
25079
  headers: buildHeaders([
25080
25080
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -25120,9 +25120,9 @@ var init_memories = __esm({
25120
25120
  * ```
25121
25121
  */
25122
25122
  list(memoryStoreID, params = {}, options) {
25123
- const { betas, ...query } = params ?? {};
25123
+ const { betas, ...query2 } = params ?? {};
25124
25124
  return this._client.getAPIList(path`/v1/memory_stores/${memoryStoreID}/memories?beta=true`, PageCursor, {
25125
- query,
25125
+ query: query2,
25126
25126
  ...options,
25127
25127
  headers: buildHeaders([
25128
25128
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -25180,9 +25180,9 @@ var init_memory_versions = __esm({
25180
25180
  * ```
25181
25181
  */
25182
25182
  retrieve(memoryVersionID, params, options) {
25183
- const { memory_store_id, betas, ...query } = params;
25183
+ const { memory_store_id, betas, ...query2 } = params;
25184
25184
  return this._client.get(path`/v1/memory_stores/${memory_store_id}/memory_versions/${memoryVersionID}?beta=true`, {
25185
- query,
25185
+ query: query2,
25186
25186
  ...options,
25187
25187
  headers: buildHeaders([
25188
25188
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -25204,9 +25204,9 @@ var init_memory_versions = __esm({
25204
25204
  * ```
25205
25205
  */
25206
25206
  list(memoryStoreID, params = {}, options) {
25207
- const { betas, ...query } = params ?? {};
25207
+ const { betas, ...query2 } = params ?? {};
25208
25208
  return this._client.getAPIList(path`/v1/memory_stores/${memoryStoreID}/memory_versions?beta=true`, PageCursor, {
25209
- query,
25209
+ query: query2,
25210
25210
  ...options,
25211
25211
  headers: buildHeaders([
25212
25212
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -25332,9 +25332,9 @@ var init_memory_stores = __esm({
25332
25332
  * ```
25333
25333
  */
25334
25334
  list(params = {}, options) {
25335
- const { betas, ...query } = params ?? {};
25335
+ const { betas, ...query2 } = params ?? {};
25336
25336
  return this._client.getAPIList("/v1/memory_stores?beta=true", PageCursor, {
25337
- query,
25337
+ query: query2,
25338
25338
  ...options,
25339
25339
  headers: buildHeaders([
25340
25340
  { "anthropic-beta": [...betas ?? [], "agent-memory-2026-07-22"].toString() },
@@ -25532,9 +25532,9 @@ var init_batches = __esm({
25532
25532
  * ```
25533
25533
  */
25534
25534
  list(params = {}, options) {
25535
- const { betas, ...query } = params ?? {};
25535
+ const { betas, ...query2 } = params ?? {};
25536
25536
  return this._client.getAPIList("/v1/messages/batches?beta=true", Page, {
25537
- query,
25537
+ query: query2,
25538
25538
  ...options,
25539
25539
  headers: buildHeaders([
25540
25540
  { "anthropic-beta": [...betas ?? [], "message-batches-2024-09-24"].toString() },
@@ -27207,9 +27207,9 @@ var init_events = __esm({
27207
27207
  * ```
27208
27208
  */
27209
27209
  list(sessionID, params = {}, options) {
27210
- const { betas, ...query } = params ?? {};
27210
+ const { betas, ...query2 } = params ?? {};
27211
27211
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/events?beta=true`, PageCursor, {
27212
- query,
27212
+ query: query2,
27213
27213
  ...options,
27214
27214
  headers: buildHeaders([
27215
27215
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27264,9 +27264,9 @@ var init_events = __esm({
27264
27264
  * ```
27265
27265
  */
27266
27266
  stream(sessionID, params = {}, options) {
27267
- const { betas, ...query } = params ?? {};
27267
+ const { betas, ...query2 } = params ?? {};
27268
27268
  return this._client.get(path`/v1/sessions/${sessionID}/events/stream?beta=true`, {
27269
- query,
27269
+ query: query2,
27270
27270
  ...options,
27271
27271
  headers: buildHeaders([
27272
27272
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27374,9 +27374,9 @@ var init_resources = __esm({
27374
27374
  * ```
27375
27375
  */
27376
27376
  list(sessionID, params = {}, options) {
27377
- const { betas, ...query } = params ?? {};
27377
+ const { betas, ...query2 } = params ?? {};
27378
27378
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/resources?beta=true`, PageCursor, {
27379
- query,
27379
+ query: query2,
27380
27380
  ...options,
27381
27381
  headers: buildHeaders([
27382
27382
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27461,9 +27461,9 @@ var init_events2 = __esm({
27461
27461
  * ```
27462
27462
  */
27463
27463
  list(threadID, params, options) {
27464
- const { session_id, betas, ...query } = params;
27464
+ const { session_id, betas, ...query2 } = params;
27465
27465
  return this._client.getAPIList(path`/v1/sessions/${session_id}/threads/${threadID}/events?beta=true`, PageCursor, {
27466
- query,
27466
+ query: query2,
27467
27467
  ...options,
27468
27468
  headers: buildHeaders([
27469
27469
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27550,9 +27550,9 @@ var init_threads = __esm({
27550
27550
  * ```
27551
27551
  */
27552
27552
  list(sessionID, params = {}, options) {
27553
- const { betas, ...query } = params ?? {};
27553
+ const { betas, ...query2 } = params ?? {};
27554
27554
  return this._client.getAPIList(path`/v1/sessions/${sessionID}/threads?beta=true`, PageCursor, {
27555
- query,
27555
+ query: query2,
27556
27556
  ...options,
27557
27557
  headers: buildHeaders([
27558
27558
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27687,9 +27687,9 @@ var init_sessions = __esm({
27687
27687
  * ```
27688
27688
  */
27689
27689
  list(params = {}, options) {
27690
- const { betas, ...query } = params ?? {};
27690
+ const { betas, ...query2 } = params ?? {};
27691
27691
  return this._client.getAPIList("/v1/sessions?beta=true", BidirectionalPageCursor, {
27692
- query,
27692
+ query: query2,
27693
27693
  ...options,
27694
27694
  headers: buildHeaders([
27695
27695
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -27814,9 +27814,9 @@ var init_versions2 = __esm({
27814
27814
  * ```
27815
27815
  */
27816
27816
  list(skillID, params = {}, options) {
27817
- const { betas, ...query } = params ?? {};
27817
+ const { betas, ...query2 } = params ?? {};
27818
27818
  return this._client.getAPIList(path`/v1/skills/${skillID}/versions?beta=true`, PageCursor, {
27819
- query,
27819
+ query: query2,
27820
27820
  ...options,
27821
27821
  headers: buildHeaders([
27822
27822
  { "anthropic-beta": [...betas ?? [], "skills-2025-10-02"].toString() },
@@ -27945,9 +27945,9 @@ var init_skills2 = __esm({
27945
27945
  * ```
27946
27946
  */
27947
27947
  list(params = {}, options) {
27948
- const { betas, ...query } = params ?? {};
27948
+ const { betas, ...query2 } = params ?? {};
27949
27949
  return this._client.getAPIList("/v1/skills?beta=true", PageCursor, {
27950
- query,
27950
+ query: query2,
27951
27951
  ...options,
27952
27952
  headers: buildHeaders([
27953
27953
  { "anthropic-beta": [...betas ?? [], "skills-2025-10-02"].toString() },
@@ -28065,9 +28065,9 @@ var init_certificates = __esm({
28065
28065
  * ```
28066
28066
  */
28067
28067
  list(tunnelID, params = {}, options) {
28068
- const { betas, ...query } = params ?? {};
28068
+ const { betas, ...query2 } = params ?? {};
28069
28069
  return this._client.getAPIList(path`/v1/tunnels/${tunnelID}/certificates?beta=true`, PageCursor, {
28070
- query,
28070
+ query: query2,
28071
28071
  ...options,
28072
28072
  headers: buildHeaders([
28073
28073
  { "anthropic-beta": [...betas ?? [], "mcp-tunnels-2026-06-22"].toString() },
@@ -28194,9 +28194,9 @@ var init_tunnels = __esm({
28194
28194
  * ```
28195
28195
  */
28196
28196
  list(params = {}, options) {
28197
- const { betas, ...query } = params ?? {};
28197
+ const { betas, ...query2 } = params ?? {};
28198
28198
  return this._client.getAPIList("/v1/tunnels?beta=true", PageCursor, {
28199
- query,
28199
+ query: query2,
28200
28200
  ...options,
28201
28201
  headers: buildHeaders([
28202
28202
  { "anthropic-beta": [...betas ?? [], "mcp-tunnels-2026-06-22"].toString() },
@@ -28390,9 +28390,9 @@ var init_credentials2 = __esm({
28390
28390
  * ```
28391
28391
  */
28392
28392
  list(vaultID, params = {}, options) {
28393
- const { betas, ...query } = params ?? {};
28393
+ const { betas, ...query2 } = params ?? {};
28394
28394
  return this._client.getAPIList(path`/v1/vaults/${vaultID}/credentials?beta=true`, PageCursor, {
28395
- query,
28395
+ query: query2,
28396
28396
  ...options,
28397
28397
  headers: buildHeaders([
28398
28398
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -28563,9 +28563,9 @@ var init_vaults = __esm({
28563
28563
  * ```
28564
28564
  */
28565
28565
  list(params = {}, options) {
28566
- const { betas, ...query } = params ?? {};
28566
+ const { betas, ...query2 } = params ?? {};
28567
28567
  return this._client.getAPIList("/v1/vaults?beta=true", PageCursor, {
28568
- query,
28568
+ query: query2,
28569
28569
  ...options,
28570
28570
  headers: buildHeaders([
28571
28571
  { "anthropic-beta": [...betas ?? [], "managed-agents-2026-04-01"].toString() },
@@ -29451,8 +29451,8 @@ var init_batches2 = __esm({
29451
29451
  * }
29452
29452
  * ```
29453
29453
  */
29454
- list(query = {}, options) {
29455
- return this._client.getAPIList("/v1/messages/batches", Page, { query, ...options });
29454
+ list(query2 = {}, options) {
29455
+ return this._client.getAPIList("/v1/messages/batches", Page, { query: query2, ...options });
29456
29456
  }
29457
29457
  /**
29458
29458
  * Delete a Message Batch.
@@ -29708,9 +29708,9 @@ var init_models2 = __esm({
29708
29708
  * use in the API. More recently released models are listed first.
29709
29709
  */
29710
29710
  list(params = {}, options) {
29711
- const { betas, ...query } = params ?? {};
29711
+ const { betas, ...query2 } = params ?? {};
29712
29712
  return this._client.getAPIList("/v1/models", Page, {
29713
- query,
29713
+ query: query2,
29714
29714
  ...options,
29715
29715
  headers: buildHeaders([
29716
29716
  { ...betas?.toString() != null ? { "anthropic-beta": betas?.toString() } : void 0 },
@@ -30059,8 +30059,8 @@ var init_client = __esm({
30059
30059
  }
30060
30060
  return buildHeaders([{ Authorization: `Bearer ${this.authToken}` }]);
30061
30061
  }
30062
- stringifyQuery(query) {
30063
- return stringifyQuery(query);
30062
+ stringifyQuery(query2) {
30063
+ return stringifyQuery(query2);
30064
30064
  }
30065
30065
  getUserAgent() {
30066
30066
  return `${this.constructor.name}/JS ${VERSION}`;
@@ -30071,16 +30071,16 @@ var init_client = __esm({
30071
30071
  makeStatusError(status, error2, message, headers) {
30072
30072
  return APIError.generate(status, error2, message, headers);
30073
30073
  }
30074
- buildURL(path6, query, defaultBaseURL) {
30074
+ buildURL(path6, query2, defaultBaseURL) {
30075
30075
  const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
30076
30076
  const url = isAbsoluteURL(path6) ? new URL(path6) : new URL(baseURL + (baseURL.endsWith("/") && path6.startsWith("/") ? path6.slice(1) : path6));
30077
30077
  const defaultQuery = this.defaultQuery();
30078
30078
  const pathQuery = Object.fromEntries(url.searchParams);
30079
30079
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
30080
- query = { ...pathQuery, ...defaultQuery, ...query };
30080
+ query2 = { ...pathQuery, ...defaultQuery, ...query2 };
30081
30081
  }
30082
- if (typeof query === "object" && query && !Array.isArray(query)) {
30083
- url.search = this.stringifyQuery(query);
30082
+ if (typeof query2 === "object" && query2 && !Array.isArray(query2)) {
30083
+ url.search = this.stringifyQuery(query2);
30084
30084
  }
30085
30085
  return url.toString();
30086
30086
  }
@@ -30393,14 +30393,14 @@ var init_client = __esm({
30393
30393
  }
30394
30394
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
30395
30395
  const options = { ...inputOptions };
30396
- const { method, path: path6, query, defaultBaseURL } = options;
30396
+ const { method, path: path6, query: query2, defaultBaseURL } = options;
30397
30397
  if (this._authState.resolution) {
30398
30398
  await this._authState.resolution;
30399
30399
  }
30400
30400
  if (!this._baseURLIsExplicit && this._authState.baseURL && this.baseURL !== this._authState.baseURL) {
30401
30401
  this.baseURL = this._authState.baseURL;
30402
30402
  }
30403
- const url = this.buildURL(path6, query, defaultBaseURL);
30403
+ const url = this.buildURL(path6, query2, defaultBaseURL);
30404
30404
  if ("timeout" in options)
30405
30405
  validatePositiveInteger("timeout", options.timeout);
30406
30406
  options.timeout = options.timeout ?? this.timeout;
@@ -30518,8 +30518,8 @@ function stripFallbackBlocks(body) {
30518
30518
  function betaRefusalFallbackMiddleware(fallbacks, options = {}) {
30519
30519
  let warnedMissingState = false;
30520
30520
  return async (request, next, ctx) => {
30521
- const [path6, query] = (ctx.options?.path ?? "").split("?");
30522
- if (fallbacks.length === 0 || ctx.options?.method !== "post" || path6 !== "/v1/messages" || new URLSearchParams(query).get("beta") !== "true" || typeof ctx.options.body !== "object" || ctx.options.body == null) {
30521
+ const [path6, query2] = (ctx.options?.path ?? "").split("?");
30522
+ if (fallbacks.length === 0 || ctx.options?.method !== "post" || path6 !== "/v1/messages" || new URLSearchParams(query2).get("beta") !== "true" || typeof ctx.options.body !== "object" || ctx.options.body == null) {
30523
30523
  return next(request);
30524
30524
  }
30525
30525
  if (ctx.options.body.fallbacks != null) {
@@ -31787,7 +31787,7 @@ ${facts.stderr}`))
31787
31787
  return null;
31788
31788
  }
31789
31789
  function isCommandUnavailable(facts, countsRead) {
31790
- if (facts.exitCode === 127 || facts.exitCode === 126)
31790
+ if (facts.exitCode === 127 || facts.exitCode === 126 || facts.exitCode === 125)
31791
31791
  return true;
31792
31792
  if (countsRead)
31793
31793
  return false;
@@ -31795,6 +31795,22 @@ function isCommandUnavailable(facts, countsRead) {
31795
31795
  return false;
31796
31796
  return facts.stdout.trim().length === 0 && EXEC_FAILURE.test(facts.stderr);
31797
31797
  }
31798
+ function isOfflineBuildGap(facts, counts) {
31799
+ if (facts.runtime !== "jvm")
31800
+ return null;
31801
+ if (counts !== null && counts.tests_failed > 0)
31802
+ return null;
31803
+ if (facts.exitCode === 0 || facts.exitCode === null)
31804
+ return null;
31805
+ const out = `${facts.stdout}
31806
+ ${facts.stderr}`;
31807
+ for (const pattern of OFFLINE_BUILD_GAP) {
31808
+ const hit = pattern.exec(out);
31809
+ if (hit)
31810
+ return hit[0];
31811
+ }
31812
+ return null;
31813
+ }
31798
31814
  function classifyVerification(facts) {
31799
31815
  if (facts.timedOut) {
31800
31816
  return {
@@ -31819,6 +31835,14 @@ function classifyVerification(facts) {
31819
31835
  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.`
31820
31836
  };
31821
31837
  }
31838
+ const gap = isOfflineBuildGap(facts, counts);
31839
+ if (gap !== null) {
31840
+ return {
31841
+ outcome: "test-command-unavailable",
31842
+ counts,
31843
+ 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.`
31844
+ };
31845
+ }
31822
31846
  if (counts !== null) {
31823
31847
  if (counts.tests_failed > 0) {
31824
31848
  return {
@@ -31873,10 +31897,19 @@ function isGreen(outcome) {
31873
31897
  function isOurFault(outcome) {
31874
31898
  return outcome === "test-command-unavailable" || outcome === "counts-unparsed";
31875
31899
  }
31876
- var int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE;
31900
+ var VERIFICATION_OUTCOMES, int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, OFFLINE_BUILD_GAP;
31877
31901
  var init_classify2 = __esm({
31878
31902
  "../../packages/envrun/dist/classify.js"() {
31879
31903
  "use strict";
31904
+ VERIFICATION_OUTCOMES = [
31905
+ "completed",
31906
+ "tests-failed",
31907
+ "no-tests-observed",
31908
+ "counts-unparsed",
31909
+ "test-command-unavailable",
31910
+ "environment-exhausted",
31911
+ "budget-exceeded"
31912
+ ];
31880
31913
  int = (m, i = 1) => m ? Number(m[i]) : 0;
31881
31914
  withSuiteFailures = (counts, suiteLine) => {
31882
31915
  if (counts.tests_failed > 0 || !suiteLine)
@@ -31976,6 +32009,51 @@ var init_classify2 = __esm({
31976
32009
  return { tests_passed: int(passed), tests_failed: int(failed) + int(errors) };
31977
32010
  }
31978
32011
  },
32012
+ {
32013
+ // Maven surefire's per-module summary (TERM-1122), measured against surefire
32014
+ // 3.2.5 through `th run`:
32015
+ //
32016
+ // [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
32017
+ //
32018
+ // The per-CLASS lines carry the same counts followed by `, Time elapsed: …`, so
32019
+ // the end anchor after `Skipped` is what keeps them out; counting both would
32020
+ // double every test. One summary prints per module, so they are SUMMED. An
32021
+ // error is a test that could not run to an assertion, counted as failed so a
32022
+ // green verdict is impossible while one is outstanding, as pytest's are.
32023
+ runner: "maven surefire",
32024
+ read: (out) => {
32025
+ const lines = [
32026
+ ...out.matchAll(/^(?:\[(?:INFO|WARNING|ERROR)\] )?Tests run: (\d+), Failures: (\d+), Errors: (\d+), Skipped: (\d+)(?:, Flakes: \d+)?\s*$/gm)
32027
+ ];
32028
+ if (lines.length === 0)
32029
+ return null;
32030
+ let passed = 0;
32031
+ let failed = 0;
32032
+ for (const m of lines) {
32033
+ const [run32, failures, errors, skipped] = [m[1], m[2], m[3], m[4]].map(Number);
32034
+ failed += failures + errors;
32035
+ passed += Math.max(0, run32 - failures - errors - skipped);
32036
+ }
32037
+ return { tests_passed: passed, tests_failed: failed };
32038
+ }
32039
+ },
32040
+ {
32041
+ // Gradle prints no test counts when every test passes, so envrun installs an
32042
+ // init script that prints one line per Test task (`gradleTestSummary.ts`,
32043
+ // TERM-1122). The marker is ours; its format is fixed there.
32044
+ runner: "gradle",
32045
+ read: (out) => {
32046
+ const lines = [
32047
+ ...out.matchAll(/^terminalhire-gradle-tests: task=\S+ total=(\d+) passed=(\d+) failed=(\d+) skipped=(\d+)\s*$/gm)
32048
+ ];
32049
+ if (lines.length === 0)
32050
+ return null;
32051
+ return {
32052
+ tests_passed: lines.reduce((sum, m) => sum + Number(m[2]), 0),
32053
+ tests_failed: lines.reduce((sum, m) => sum + Number(m[3]), 0)
32054
+ };
32055
+ }
32056
+ },
31979
32057
  {
31980
32058
  // mocha: ` 440 passing (1s)` and ` 2 failing`
31981
32059
  runner: "mocha",
@@ -32010,6 +32088,79 @@ var init_classify2 = __esm({
32010
32088
  const p = int(passed);
32011
32089
  return { tests_passed: p, tests_failed: Math.max(0, t - p) };
32012
32090
  }
32091
+ },
32092
+ {
32093
+ /**
32094
+ * BARE TAP — assertion lines with no plan and no summary comment (TERM-1138).
32095
+ *
32096
+ * LAST ON PURPOSE. It is the loosest reader here, so anything that a more specific
32097
+ * one can read must reach that one first: `node --test` and node-tap both emit `ok`
32098
+ * lines AND a closing summary, and their readers are ordered above for that reason.
32099
+ * Moving this up would have it answer for both.
32100
+ *
32101
+ * A hand-rolled harness prints neither summary — it emits its assertions and stops.
32102
+ * That is what morebetterltd/wheelhouse does: thirty `ok N - …` lines, exit 0, and
32103
+ * before this every one of those runs reported `counts-unparsed`, which the claim
32104
+ * page renders as "RESULT UNREADABLE — THE SUITE EXITED 0" over a suite that passed.
32105
+ *
32106
+ * ON THE MISSING PLAN LINE. TAP requires `1..N` so a reader can tell a finished
32107
+ * stream from a truncated one, and there is none here, so this cannot prove the
32108
+ * suite reached its end. That is a real limit and it is NOT a reason to refuse,
32109
+ * because it holds this format to a standard no reader in this file meets — the
32110
+ * jest reader greps a line out of stdout that a repository could `echo` without
32111
+ * running anything. These are format adapters, not a trust boundary; the boundary
32112
+ * is that the command is the repository's own or is refused a signature.
32113
+ *
32114
+ * CONTIGUOUS FROM 1, which buys self-consistency rather than security:
32115
+ *
32116
+ * - starting at 1 rejects a TRUNCATION. `testOutputTail` keeps the last lines, so
32117
+ * a stream beginning `ok 47` is the middle of a longer run and counting it would
32118
+ * report three tests for a suite that ran fifty.
32119
+ * - no gaps rejects prose that happens to carry the word, and a stream interleaved
32120
+ * with another's.
32121
+ * - anchored with no leading space rejects TAP SUBTESTS, which indent. Counting
32122
+ * those would double them against their parent.
32123
+ */
32124
+ runner: "bare TAP",
32125
+ read: (out) => {
32126
+ let n = 0;
32127
+ let failed = 0;
32128
+ let passed = 0;
32129
+ let plan = null;
32130
+ for (const line of out.split("\n")) {
32131
+ if (/^Bail out!/.test(line))
32132
+ return null;
32133
+ const p = /^(\d+)\.\.(\d+)\s*$/.exec(line);
32134
+ if (p) {
32135
+ const declared = Number(p[2]) - Number(p[1]) + 1;
32136
+ if (plan !== null && plan !== declared)
32137
+ return null;
32138
+ plan = declared;
32139
+ continue;
32140
+ }
32141
+ const m = /^(not )?ok (\d+)(?:\s|$)/.exec(line);
32142
+ if (!m)
32143
+ continue;
32144
+ if (Number(m[2]) !== n + 1)
32145
+ return null;
32146
+ n += 1;
32147
+ const directive = /#\s*(skip|todo)\b/i.exec(line);
32148
+ const kind = directive?.[1]?.toLowerCase();
32149
+ if (kind === "skip")
32150
+ continue;
32151
+ if (m[1] !== void 0) {
32152
+ if (kind !== "todo")
32153
+ failed += 1;
32154
+ continue;
32155
+ }
32156
+ passed += 1;
32157
+ }
32158
+ if (n === 0)
32159
+ return null;
32160
+ if (plan !== null && plan !== n)
32161
+ return null;
32162
+ return { tests_passed: passed, tests_failed: failed };
32163
+ }
32013
32164
  }
32014
32165
  ];
32015
32166
  SUPPORTED_RUNNERS = READERS.map((r) => r.runner);
@@ -32031,6 +32182,21 @@ var init_classify2 = __esm({
32031
32182
  "\\d+\\s+examples?,\\s+(?!0\\b)\\d+\\s+failures?"
32032
32183
  // rspec's summary line
32033
32184
  ].join("|"), "m");
32185
+ OFFLINE_BUILD_GAP = [
32186
+ // Maven, `-o`: `Cannot access central (…) in offline mode and the artifact … has
32187
+ // not been downloaded from it before.`
32188
+ /in offline mode and the artifact \S+ has not been downloaded from it before/,
32189
+ // Maven without `-o`, as a CI-derived `mvn test` runs: it tries the network.
32190
+ /Could not transfer artifact \S+ from\/to /,
32191
+ /Plugin \S+ or one of its dependencies could not be resolved/,
32192
+ // Gradle, `--offline`.
32193
+ /No cached version (?:listing )?(?:of|for) \S+ available for offline mode/,
32194
+ // Gradle without `--offline`, and the wrapper fetching its own distribution.
32195
+ /Could not GET 'https?:\/\//,
32196
+ /^Downloading https:\/\/services\.gradle\.org\/distributions\//m,
32197
+ /No matching toolchains found for requested specification/,
32198
+ /Cannot find a Java installation on your machine matching/
32199
+ ];
32034
32200
  }
32035
32201
  });
32036
32202
 
@@ -32096,6 +32262,12 @@ function scrubEnv(source, opts) {
32096
32262
  env["GOCACHE"] = join34(jailHome, ".cache", "go-build");
32097
32263
  env["GOFLAGS"] = "-modcacherw";
32098
32264
  env["CARGO_HOME"] = join34(jailHome, ".cargo");
32265
+ env["MAVEN_OPTS"] = `-Dmaven.repo.local=${join34(jailHome, ".m2", "repository")}`;
32266
+ env["GRADLE_USER_HOME"] = join34(jailHome, ".gradle");
32267
+ const bundle = join34(jailHome, ".bundle");
32268
+ env["GEM_HOME"] = bundle;
32269
+ env["BUNDLE_PATH"] = bundle;
32270
+ env["BUNDLE_APP_CONFIG"] = bundle;
32099
32271
  const proxy = opts.proxyUrl ?? DEAD_PROXY;
32100
32272
  env["HTTP_PROXY"] = proxy;
32101
32273
  env["HTTPS_PROXY"] = proxy;
@@ -32396,6 +32568,31 @@ var init_egressProxy = __esm({
32396
32568
  "static.crates.io",
32397
32569
  "index.crates.io",
32398
32570
  "crates.io",
32571
+ // TERM-1139. `ruby` has had an image since the mapping existed and `bundle install`
32572
+ // as its derived command, and this host was missing — so every ruby repo could start
32573
+ // and could never install. Install is the ONLY step with a network grant at all, so a
32574
+ // denial here is terminal, not a slow path.
32575
+ //
32576
+ // One entry, not two: `hostAllowed` is dot-anchored SUFFIX matching, so this covers
32577
+ // `index.rubygems.org` as well. Listing that separately would advertise the grant as
32578
+ // narrower than it is, which the `github.com` note below rejects for the same reason.
32579
+ "rubygems.org",
32580
+ // TERM-1139, added with the `dotnet` image mapping. `dotnet restore` fetches from
32581
+ // NuGet; the coupling test refuses a mapped runtime whose registry is unreachable, and
32582
+ // it caught this one the moment the image landed.
32583
+ "api.nuget.org",
32584
+ // TERM-1122, added with the `jvm` image mapping, each from a MEASURED denial
32585
+ // through `th run` (the proxy's 403 in the build's own output), not a guess:
32586
+ // - Maven Central, where both `mvn` and Gradle's `mavenCentral()` resolve.
32587
+ // - `services.gradle.org`, where a Gradle wrapper fetches its distribution.
32588
+ // - `release-assets.githubusercontent.com`, where that fetch LANDS. Measured
32589
+ // chain: services.gradle.org 307 -> github.com 302 -> this host 200. The
32590
+ // `github.com` note below keeps `githubusercontent.com` denied until a real
32591
+ // install needs it; this is that install. Only this one host: raw blobs and
32592
+ // `objects.githubusercontent.com` stay denied.
32593
+ "repo.maven.apache.org",
32594
+ "services.gradle.org",
32595
+ "release-assets.githubusercontent.com",
32399
32596
  // Git dependencies. Approved by Eric 2026-07-22 after a live run measured the
32400
32597
  // chokepoint working under load (20 events, 16 allowed / 4 denied by host).
32401
32598
  //
@@ -32687,6 +32884,9 @@ function containerArgs(spec, env, opts) {
32687
32884
  args5.push(`--env=${k}=${url}`);
32688
32885
  }
32689
32886
  args5.push("--env=NO_PROXY=", "--env=no_proxy=");
32887
+ const host = opts.net.proxyHost;
32888
+ const port = String(opts.net.proxyPort);
32889
+ args5.push(`--env=JAVA_TOOL_OPTIONS=-Dhttp.proxyHost=${host} -Dhttp.proxyPort=${port} -Dhttps.proxyHost=${host} -Dhttps.proxyPort=${port} -Daether.connector.http.useSystemProperties=true`);
32690
32890
  }
32691
32891
  args5.push("--");
32692
32892
  args5.push(validateImage(opts.image ?? DEFAULT_CONTAINER_IMAGE));
@@ -33019,7 +33219,10 @@ var init_container = __esm({
33019
33219
  "http_proxy",
33020
33220
  "https_proxy",
33021
33221
  "NO_PROXY",
33022
- "no_proxy"
33222
+ "no_proxy",
33223
+ // Carries the JVM's copy of the proxy (below), so an inherited value is dropped
33224
+ // for the same reason as the rest.
33225
+ "JAVA_TOOL_OPTIONS"
33023
33226
  ]);
33024
33227
  WINDOWS_DRIVE_ROOT = /^[A-Za-z]:[\\/]/;
33025
33228
  IMAGE_HOST = "(?:[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)+(?::[0-9]+)?|localhost(?::[0-9]+)?)";
@@ -33069,11 +33272,17 @@ var init_dist = __esm({
33069
33272
  function censusTotal(c) {
33070
33273
  return c.containers.length + c.volumes.length + c.networks.length;
33071
33274
  }
33072
- function ids(docker3, args5) {
33275
+ function query(docker3, args5) {
33073
33276
  const res = docker3.sync([...args5], { timeoutMs: 15e3 });
33074
- if (res.error || res.status !== 0)
33075
- return [];
33076
- return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
33277
+ if (res.error || res.status !== 0) {
33278
+ const why = res.error?.message ?? (res.stderr.trim() || `exit ${String(res.status)}`);
33279
+ return { ids: [], failure: `docker ${args5.slice(0, 2).join(" ")}: ${why}` };
33280
+ }
33281
+ const ids2 = res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
33282
+ return { ids: ids2, failure: null };
33283
+ }
33284
+ function ids(docker3, args5) {
33285
+ return query(docker3, args5).ids;
33077
33286
  }
33078
33287
  function census(docker3, label) {
33079
33288
  const filter = `label=${label}`;
@@ -33083,25 +33292,67 @@ function census(docker3, label) {
33083
33292
  networks: ids(docker3, ["network", "ls", "-q", "--filter", filter])
33084
33293
  };
33085
33294
  }
33295
+ function censusReport(docker3, label) {
33296
+ const filter = `label=${label}`;
33297
+ const failures = [];
33298
+ const ask5 = (args5) => {
33299
+ const q = query(docker3, args5);
33300
+ if (q.failure !== null)
33301
+ failures.push(q.failure);
33302
+ return q.ids;
33303
+ };
33304
+ const taken = {
33305
+ containers: ask5(["ps", "-aq", "--filter", filter]),
33306
+ volumes: ask5(["volume", "ls", "-q", "--filter", filter]),
33307
+ networks: ask5(["network", "ls", "-q", "--filter", filter])
33308
+ };
33309
+ return failures.length === 0 ? { observed: true, census: taken, unobservedReason: null } : { observed: false, census: taken, unobservedReason: failures.join("; ") };
33310
+ }
33086
33311
  function localCensus(label) {
33087
33312
  return census(localDockerClient(), label);
33088
33313
  }
33089
- function judgeLeaks(peak, after) {
33314
+ function judgeLeaks(peak, after, observation) {
33090
33315
  const labelObserved = peak.containers.length > 0;
33316
+ if (!observation.observed) {
33317
+ return {
33318
+ labelObserved,
33319
+ reaped: false,
33320
+ observed: false,
33321
+ clean: false,
33322
+ state: "unobserved",
33323
+ peak,
33324
+ after,
33325
+ 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.`
33326
+ };
33327
+ }
33091
33328
  const reaped = censusTotal(after) === 0;
33092
33329
  let note;
33330
+ let state;
33093
33331
  if (!labelObserved && reaped) {
33332
+ state = "inconclusive";
33094
33333
  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.";
33095
33334
  } else if (!labelObserved) {
33335
+ state = "leak";
33096
33336
  note = "no labelled container was observed alive AND objects remain \u2014 the label wiring is wrong.";
33097
33337
  } else if (!reaped) {
33338
+ state = "leak";
33098
33339
  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)}).`;
33099
33340
  } else {
33341
+ state = "clean";
33100
33342
  note = `clean: peak ${String(peak.containers.length)} labelled container(s) observed alive, 0 labelled objects remain after teardown.`;
33101
33343
  }
33102
- return { labelObserved, reaped, clean: labelObserved && reaped, peak, after, note };
33344
+ return {
33345
+ labelObserved,
33346
+ reaped,
33347
+ observed: true,
33348
+ clean: labelObserved && reaped,
33349
+ state,
33350
+ peak,
33351
+ after,
33352
+ note
33353
+ };
33103
33354
  }
33104
- var RUN_LABEL_KEY, LabelWatch;
33355
+ var RUN_LABEL_KEY, LabelWatch, LEAK_STATES;
33105
33356
  var init_labels = __esm({
33106
33357
  "../../packages/envrun/dist/labels.js"() {
33107
33358
  "use strict";
@@ -33156,6 +33407,7 @@ var init_labels = __esm({
33156
33407
  return this.#samples;
33157
33408
  }
33158
33409
  };
33410
+ LEAK_STATES = ["clean", "leak", "inconclusive", "unobserved"];
33159
33411
  }
33160
33412
  });
33161
33413
 
@@ -33640,7 +33892,7 @@ function acquireLocalLease(runId) {
33640
33892
  observed: false,
33641
33893
  census: { containers: [], volumes: [], networks: [] },
33642
33894
  unobservedReason: RELEASED_LEASE_CENSUS_REASON
33643
- } : { observed: true, census: census(docker3, label), unobservedReason: null }),
33895
+ } : censusReport(docker3, label)),
33644
33896
  publishPreview: (req) => startPreview({ ...req, docker: docker3 }),
33645
33897
  release: () => {
33646
33898
  if (released) {
@@ -33760,6 +34012,17 @@ caused by: ${frame}`;
33760
34012
  }
33761
34013
  return frames === 0 ? null : out;
33762
34014
  }
34015
+ function imageVariantFor(spec) {
34016
+ if (spec.runtime !== "jvm")
34017
+ return void 0;
34018
+ const callsGradle = /(?:^|[\s;&|(])gradle(?=\s|$)/;
34019
+ return [spec.installCommand, spec.testCommand].some((c) => c != null && callsGradle.test(c)) ? "gradle" : void 0;
34020
+ }
34021
+ function shapeFor(runtime, variant) {
34022
+ if (runtime === "jvm" && variant === "gradle")
34023
+ return JVM_GRADLE_IMAGE;
34024
+ return RUNTIME_IMAGES[runtime];
34025
+ }
33763
34026
  function atLeast(a, b) {
33764
34027
  const left = a.split(".").map(Number);
33765
34028
  const right = b.split(".").map(Number);
@@ -33772,19 +34035,19 @@ function atLeast(a, b) {
33772
34035
  }
33773
34036
  return true;
33774
34037
  }
33775
- function imageForRuntime(runtime, override, version2) {
34038
+ function imageForRuntime(runtime, override, version2, variant) {
33776
34039
  if (override)
33777
34040
  return override;
33778
- const shape = RUNTIME_IMAGES[runtime];
34041
+ const shape = shapeFor(runtime, variant);
33779
34042
  if (!shape) {
33780
- throw new RunRefusalError(`no container image is mapped for runtime ${JSON.stringify(runtime)}. Refusing to run it in the Node image: a missing interpreter surfaces as "command not found", which the classifier reads as tests-failed \u2014 a false red blamed on the repo.`);
34043
+ 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.`);
33781
34044
  }
33782
34045
  if (version2 === void 0 || version2 === null)
33783
34046
  return unversionedImage(shape);
33784
34047
  if (!TAG_VERSION.test(version2)) {
33785
34048
  throw new RunRefusalError(`runtime version ${JSON.stringify(version2)} 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.`);
33786
34049
  }
33787
- return `${shape.repository}:${version2}${shape.suffix}`;
34050
+ return `${shape.repository}:${shape.prefix ?? ""}${version2}${shape.suffix}`;
33788
34051
  }
33789
34052
  function setManifestProbe(probe) {
33790
34053
  const previous = manifestProbe;
@@ -33797,10 +34060,10 @@ function imageDefinitelyAbsent(image) {
33797
34060
  return false;
33798
34061
  return /manifest unknown|no such manifest/i.test(res.output);
33799
34062
  }
33800
- function resolvePublishedImage(image, runtime, version2) {
34063
+ function resolvePublishedImage(image, runtime, version2, variant) {
33801
34064
  if (!imageDefinitelyAbsent(image))
33802
34065
  return image;
33803
- const shape = RUNTIME_IMAGES[runtime];
34066
+ const shape = shapeFor(runtime, variant);
33804
34067
  if (shape && shape.declaredIsFloor && atLeast(shape.defaultVersion, version2)) {
33805
34068
  return unversionedImage(shape);
33806
34069
  }
@@ -33808,18 +34071,19 @@ function resolvePublishedImage(image, runtime, version2) {
33808
34071
  }
33809
34072
  function resolveImageForSpec(spec, override) {
33810
34073
  const declared = spec.runtimeVersion;
33811
- const image = imageForRuntime(spec.runtime, override, declared);
34074
+ const variant = imageVariantFor(spec);
34075
+ const image = imageForRuntime(spec.runtime, override, declared, variant);
33812
34076
  const constructed = (() => {
33813
34077
  if (declared === null)
33814
34078
  return null;
33815
34079
  try {
33816
- return imageForRuntime(spec.runtime, void 0, declared);
34080
+ return imageForRuntime(spec.runtime, void 0, declared, variant);
33817
34081
  } catch {
33818
34082
  return null;
33819
34083
  }
33820
34084
  })();
33821
34085
  if (constructed !== null && image === constructed && declared !== null) {
33822
- return resolvePublishedImage(image, spec.runtime, declared);
34086
+ return resolvePublishedImage(image, spec.runtime, declared, variant);
33823
34087
  }
33824
34088
  return image;
33825
34089
  }
@@ -33846,12 +34110,22 @@ function assertVenueOwnerDeclared(lease) {
33846
34110
  return;
33847
34111
  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.");
33848
34112
  }
34113
+ function refuseUnbuildableSpec(spec) {
34114
+ if (spec.runtime !== "jvm" || spec.installCommand !== null || spec.testCommand !== null)
34115
+ return;
34116
+ const why = spec.unresolved.find((r) => r.kind === "no-test-command")?.detail;
34117
+ if (why === void 0) {
34118
+ throw new EnvRunError("internal: a jvm spec with no test command carries no no-test-command reason");
34119
+ }
34120
+ throw new RunRefusalError(`refusing to run this repository: ${why}.`);
34121
+ }
33849
34122
  async function runEnvironmentSpec(req) {
33850
34123
  const startedAt = Date.now();
33851
34124
  const containment = req.lease.containment;
33852
34125
  if (containment.kind !== "container") {
33853
34126
  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.`);
33854
34127
  }
34128
+ refuseUnbuildableSpec(req.spec);
33855
34129
  assertVenueOwnerDeclared(req.lease);
33856
34130
  const image = resolveImageForSpec(req.spec, req.image);
33857
34131
  const { jail, tmp } = req;
@@ -33894,9 +34168,10 @@ async function runEnvironmentSpec(req) {
33894
34168
  installOk: false
33895
34169
  };
33896
34170
  } else if (req.spec.testCommand === null) {
34171
+ const why = req.spec.unresolved.find((r) => r.kind === "no-test-command")?.detail;
33897
34172
  result = {
33898
34173
  outcome: "no-tests-observed",
33899
- 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.",
34174
+ 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}.`),
33900
34175
  installOk: true
33901
34176
  };
33902
34177
  } else {
@@ -33919,7 +34194,7 @@ async function runEnvironmentSpec(req) {
33919
34194
  labels,
33920
34195
  timeoutMs: req.testTimeoutMs ?? 9e5
33921
34196
  });
33922
- const verdict = classifyVerification(toExecution(test));
34197
+ const verdict = classifyVerification({ ...toExecution(test), runtime: req.spec.runtime });
33923
34198
  result = { outcome: verdict.outcome, note: verdict.reason, installOk: true };
33924
34199
  }
33925
34200
  } finally {
@@ -33933,6 +34208,10 @@ async function runEnvironmentSpec(req) {
33933
34208
  volumes: [],
33934
34209
  networks: []
33935
34210
  };
34211
+ const observation = afterReport ?? {
34212
+ observed: false,
34213
+ unobservedReason: "the run carried no label, so there was nothing to count by"
34214
+ };
33936
34215
  return {
33937
34216
  outcome: result.outcome,
33938
34217
  tier: "container",
@@ -33941,7 +34220,7 @@ async function runEnvironmentSpec(req) {
33941
34220
  test,
33942
34221
  installOk: result.installOk,
33943
34222
  counts: test ? readCounts(test.stdout, test.stderr) : null,
33944
- leaks: judgeLeaks(peak, after),
34223
+ leaks: judgeLeaks(peak, after, observation),
33945
34224
  note: result.note,
33946
34225
  wallMs: Date.now() - startedAt
33947
34226
  };
@@ -33991,7 +34270,7 @@ async function runStep(containment, r) {
33991
34270
  wallMs: Date.now() - startedAt
33992
34271
  };
33993
34272
  }
33994
- var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE;
34273
+ var EnvRunError, RunRefusalError, MAX_CAUSE_FRAMES, CHAIN_UNREADABLE, CHAIN_TOO_DEEP, RUNTIME_IMAGES, JVM_GRADLE_IMAGE, unversionedImage, TAG_VERSION, dockerManifestProbe, manifestProbe, MISSING_IMAGE_SHAPE;
33995
34274
  var init_execute = __esm({
33996
34275
  "../../packages/envrun/dist/execute.js"() {
33997
34276
  "use strict";
@@ -34028,9 +34307,66 @@ var init_execute = __esm({
34028
34307
  },
34029
34308
  go: { repository: "golang", suffix: "-bookworm", defaultVersion: "1.23", declaredIsFloor: true },
34030
34309
  ruby: { repository: "ruby", suffix: "-bookworm", defaultVersion: "3.3", declaredIsFloor: false },
34031
- rust: { repository: "rust", suffix: "-bookworm", defaultVersion: "1", declaredIsFloor: true }
34032
- };
34033
- unversionedImage = (shape) => `${shape.repository}:${shape.defaultVersion}${shape.suffix}`;
34310
+ rust: { repository: "rust", suffix: "-bookworm", defaultVersion: "1", declaredIsFloor: true },
34311
+ /**
34312
+ * TERM-1139. `dotnet` was unmapped alongside `jvm`, and the recorded reason was that
34313
+ * neither had "a single obvious base image (gradle vs maven, sdk vs runtime)". That
34314
+ * reason holds for jvm and it does NOT hold here, which is why only this half moved.
34315
+ *
34316
+ * "sdk vs runtime" answers itself: the derived commands are `dotnet restore` and
34317
+ * `dotnet test`, and neither exists on the runtime image. There is no second build
34318
+ * tool competing for the slot the way maven and gradle compete for jvm's — and since
34319
+ * no official image carries both of those, jvm genuinely cannot be served by one key.
34320
+ *
34321
+ * MEASURED, not chosen by tag: `mcr.microsoft.com/dotnet/sdk:8.0` carries `git` and
34322
+ * `dotnet` (881MB). `git` is the property the whole `image-tooling-live` test exists
34323
+ * for, and `eclipse-temurin:21-jdk` was rejected here for lacking it.
34324
+ *
34325
+ * The tag shape fits without touching the builder: the SDK publishes a tag per
34326
+ * version, so `repository:VERSION` is a real image at every version envspec can
34327
+ * derive, with no suffix. That is NOT true of the maven images, where the leading
34328
+ * tag component is maven's version and Java's sits in the suffix.
34329
+ *
34330
+ * `declaredIsFloor` is true on the same reasoning as go and rust: an SDK builds
34331
+ * older target frameworks, so booting 8.0 for a repo asking 6.0 is the supported
34332
+ * case rather than a substitution. A repo asking for something NEWER than our
34333
+ * default still refuses, which is where TERM-643 put that line.
34334
+ */
34335
+ dotnet: {
34336
+ repository: "mcr.microsoft.com/dotnet/sdk",
34337
+ suffix: "",
34338
+ defaultVersion: "8.0",
34339
+ declaredIsFloor: true
34340
+ },
34341
+ /**
34342
+ * TERM-1122. Maven's image, and the one a Gradle WRAPPER repo gets too: the wrapper
34343
+ * downloads its own Gradle, so it needs a JDK and nothing else. MEASURED
34344
+ * 2026-09-18: `maven:3.9-eclipse-temurin-21` carries `git`, `mvn` and JDK 21 on
34345
+ * Ubuntu 24.04, which is why this is not `eclipse-temurin` (no `git`, rejected
34346
+ * under TERM-1139). The JDK sits AFTER maven's own version in the tag, hence
34347
+ * `prefix`, and the major is all envspec derives (`javaMajor`).
34348
+ *
34349
+ * `declaredIsFloor`: a JDK builds for older `--release` targets, so a repo asking
34350
+ * for 16, which has no image, runs on 21. A Gradle toolchain is stricter than a
34351
+ * floor and asks for its exact JDK; Gradle's refusal on the wrong one reads as
34352
+ * ours (`classify.ts`, `isOfflineBuildGap`), never as the repo's.
34353
+ */
34354
+ jvm: {
34355
+ repository: "maven",
34356
+ prefix: "3.9-eclipse-temurin-",
34357
+ suffix: "",
34358
+ defaultVersion: "21",
34359
+ declaredIsFloor: true
34360
+ }
34361
+ };
34362
+ JVM_GRADLE_IMAGE = {
34363
+ repository: "gradle",
34364
+ prefix: "jdk",
34365
+ suffix: "",
34366
+ defaultVersion: "21",
34367
+ declaredIsFloor: true
34368
+ };
34369
+ unversionedImage = (shape) => `${shape.repository}:${shape.prefix ?? ""}${shape.defaultVersion}${shape.suffix}`;
34034
34370
  TAG_VERSION = /^\d+(?:\.\d+){0,2}$/;
34035
34371
  dockerManifestProbe = (image) => {
34036
34372
  const res = spawnSync5("docker", ["manifest", "inspect", image], {
@@ -34722,14 +35058,31 @@ function answerDidItPass(r) {
34722
35058
  const passed = r.status === "verified" && r.outcome !== null && isGreen(r.outcome);
34723
35059
  return { passed, summary: renderVerdictLine(r), lookAt: r.preview?.url ?? null };
34724
35060
  }
34725
- var RUN_TEST_COMMAND_SOURCES, RUN_RESULT_SCHEMA, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS;
35061
+ function exitCodeFor(r) {
35062
+ if (r.status === "refused")
35063
+ return 2;
35064
+ if (r.outcome === null)
35065
+ return 2;
35066
+ return OUTCOME_EXIT_CODES[r.outcome];
35067
+ }
35068
+ var RUN_TEST_COMMAND_SOURCES, RUN_IMAGE_SOURCES, RUN_RESULT_SCHEMA, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS, OUTCOME_EXIT_CODES;
34726
35069
  var init_result = __esm({
34727
35070
  "../../packages/envrun/dist/result.js"() {
34728
35071
  "use strict";
34729
35072
  init_dist2();
34730
35073
  init_classify2();
34731
35074
  init_venueDescriptor();
34732
- RUN_TEST_COMMAND_SOURCES = [...TEST_COMMAND_SOURCES, "developer-declared"];
35075
+ RUN_TEST_COMMAND_SOURCES = [
35076
+ ...TEST_COMMAND_SOURCES,
35077
+ "developer-declared",
35078
+ "operator-declared"
35079
+ ];
35080
+ RUN_IMAGE_SOURCES = [
35081
+ "detected",
35082
+ "none",
35083
+ "operator-declared",
35084
+ "developer-declared"
35085
+ ];
34733
35086
  RUN_RESULT_SCHEMA = "terminalhire.verification-run/1";
34734
35087
  RUN_RESULT_FIELDS = [
34735
35088
  "schema",
@@ -34754,7 +35107,9 @@ var init_result = __esm({
34754
35107
  "preview",
34755
35108
  "containerImage",
34756
35109
  "containerImageDigest",
35110
+ "imageSource",
34757
35111
  "leaksClean",
35112
+ "leakState",
34758
35113
  "venue"
34759
35114
  ];
34760
35115
  RENDER_NONE = null;
@@ -34786,12 +35141,42 @@ var init_result = __esm({
34786
35141
  preview: (r) => r.preview === null ? null : `preview ${r.preview.url}`,
34787
35142
  containerImage: (r) => r.containerImage === null ? null : `image ${r.containerImage}`,
34788
35143
  containerImageDigest: (r) => r.containerImageDigest === null ? null : `image digest ${r.containerImageDigest}`,
34789
- leaksClean: (r) => r.leaksClean === null ? null : r.leaksClean ? null : "WARNING labelled Docker objects survived teardown",
35144
+ // Shown only when a human chose the environment. `detected` is the ordinary case and
35145
+ // saying so on every run would train the reader to skip the line that matters.
35146
+ imageSource: (r) => r.imageSource === "detected" || r.imageSource === "none" ? null : `image source ${r.imageSource} (not signed)`,
35147
+ // Printed through `leakState` below, which knows WHY a false is false. Rendering both would
35148
+ // print the leak warning on a run where we merely could not look (TERM-1144).
35149
+ leaksClean: RENDER_NONE,
35150
+ // Silent when clean and when refused, as the boolean's line was. The two not-a-leak states
35151
+ // get their own sentence, because "we could not check" read as "we found a leak" is the
35152
+ // exact confusion this field exists to end.
35153
+ leakState: (r) => {
35154
+ switch (r.leakState) {
35155
+ case null:
35156
+ case "clean":
35157
+ return null;
35158
+ case "leak":
35159
+ return "WARNING labelled Docker objects survived teardown";
35160
+ case "inconclusive":
35161
+ return "leak check inconclusive \u2014 no labelled container was seen while the run was live, so finding nothing afterwards proves nothing";
35162
+ case "unobserved":
35163
+ return "leak check not taken \u2014 we could not check what was left after teardown";
35164
+ }
35165
+ },
34790
35166
  // Absent on most runs, so it prints only when there is something to say. Silence
34791
35167
  // here is the honest rendering of "no venue answered": a placeholder line would
34792
35168
  // invite a reader to treat an unanswered probe as a described venue.
34793
35169
  venue: (r) => r.venue === null ? null : renderVenueLine(r.venue)
34794
35170
  };
35171
+ OUTCOME_EXIT_CODES = {
35172
+ completed: 0,
35173
+ "tests-failed": 1,
35174
+ "no-tests-observed": 1,
35175
+ "budget-exceeded": 1,
35176
+ "counts-unparsed": 2,
35177
+ "test-command-unavailable": 2,
35178
+ "environment-exhausted": 2
35179
+ };
34795
35180
  }
34796
35181
  });
34797
35182
 
@@ -34912,12 +35297,23 @@ function toAcceptancePredicate(pair, opts = {}) {
34912
35297
  if (why !== null)
34913
35298
  return refuse3("test-command-origin-unattested", `the ${side} run ${why}`);
34914
35299
  }
34915
- if (patched.testCommandSource === "developer-declared") {
34916
- return refuse3("test-command-origin-unattested", "the developer supplied the test command (unreachable \u2014 the source rules above cover it)");
35300
+ if (patched.testCommandSource === "developer-declared" || patched.testCommandSource === "operator-declared") {
35301
+ return refuse3("test-command-origin-unattested", `${patched.testCommandSource === "developer-declared" ? "the developer" : "an operator"} supplied the test command (unreachable \u2014 the source rules above cover it)`);
34917
35302
  }
34918
35303
  if (baseline.testCommandSource !== patched.testCommandSource) {
34919
35304
  return refuse3("test-command-origin-unattested", `the pair disagrees on where the command came from: baseline ${baseline.testCommandSource}, patched ${patched.testCommandSource}. One signed value cannot describe both.`);
34920
35305
  }
35306
+ for (const [side, source, image] of [
35307
+ ["patched", patched.imageSource, patched.containerImage],
35308
+ ["baseline", baseline.imageSource, baseline.containerImage]
35309
+ ]) {
35310
+ const why = IMAGE_SOURCE_IS_SIGNABLE[source](image);
35311
+ if (why !== null)
35312
+ return refuse3("image-origin-unattested", `the ${side} run ${why}`);
35313
+ }
35314
+ if (baseline.imageSource !== patched.imageSource) {
35315
+ return refuse3("image-origin-unattested", `the pair disagrees on where the image came from: baseline ${baseline.imageSource}, patched ${patched.imageSource}. The environment is part of what the statement says was held constant.`);
35316
+ }
34921
35317
  if (baseline.containerImage !== patched.containerImage) {
34922
35318
  return refuse3("pair-disagrees-on-image", `baseline ran in ${String(baseline.containerImage)} and patched in ${String(patched.containerImage)}. The signed measurement names one image, so a pair from two environments would attribute to the patch whatever the image changed.`);
34923
35319
  }
@@ -34974,7 +35370,7 @@ function signRunStatement(predicate, privateKey, keyid) {
34974
35370
  const statement = createAcceptanceStatement(predicate);
34975
35371
  return { statement, envelope: signStatement(statement, privateKey, keyid) };
34976
35372
  }
34977
- var OUTCOME_TO_BUDGET, BASELINE_IS_A_VERDICT, CONTRADICTS_COUNTS, SOURCE_IS_SIGNABLE, ATTEST_REFUSAL_REASONS, refuse3, LOCAL_MEASUREMENT_PREFIX, REFERENCE_DOMAIN_COMPONENT, REFERENCE_DOMAIN_NAME, REFERENCE_IPV6, REFERENCE_DOMAIN, REFERENCE_PATH_COMPONENT, REPO_DIGEST_RE;
35373
+ var OUTCOME_TO_BUDGET, BASELINE_IS_A_VERDICT, CONTRADICTS_COUNTS, SOURCE_IS_SIGNABLE, IMAGE_SOURCE_IS_SIGNABLE, ATTEST_REFUSAL_REASONS, refuse3, LOCAL_MEASUREMENT_PREFIX, REFERENCE_DOMAIN_COMPONENT, REFERENCE_DOMAIN_NAME, REFERENCE_IPV6, REFERENCE_DOMAIN, REFERENCE_PATH_COMPONENT, REPO_DIGEST_RE;
34978
35374
  var init_attestation2 = __esm({
34979
35375
  "../../packages/envrun/dist/attestation.js"() {
34980
35376
  "use strict";
@@ -35030,11 +35426,25 @@ var init_attestation2 = __esm({
35030
35426
  // The party under verification chose it, and no signed member can say so. Per ADR-005
35031
35427
  // Decision 2, emit nothing rather than pick the closest word.
35032
35428
  "developer-declared": () => "had its test command supplied by the developer whose work is under verification, and the signed vocabulary has no member for that. `founder-declared` would attribute the choice to the other party; `detected` would claim the repo chose it. Both are false.",
35429
+ // US, out of band, per posting — the twin of `IMAGE_SOURCE_IS_SIGNABLE['operator-declared']`
35430
+ // and refusing for the same reason. `founder-declared` is the nearest signed member and it
35431
+ // is still false: the poster did not choose this, we did, and signing our own choice under
35432
+ // the counterparty's name is the specific false attribution the `developer-declared` entry
35433
+ // above exists to refuse. Decision 2 — emit nothing rather than pick the closest word.
35434
+ "operator-declared": () => "had its test command chosen by an operator out of band, and the signed vocabulary has no member for that. Signing it would present a hand-picked command as one the repository chose for itself.",
35033
35435
  // `none` means nobody chose a command, so a command beside it is a contradiction. Shadowed
35034
35436
  // today — `thrun.ts` sets `none` only when the derived command is null, and `no-test-command`
35035
35437
  // fires first — but that coupling runs through `runEnvironmentSpec` and lives in no type.
35036
35438
  none: (command) => command === null ? null : `records its command source as \`none\` \u2014 nobody chose one \u2014 beside the command ${JSON.stringify(command)}`
35037
35439
  };
35440
+ IMAGE_SOURCE_IS_SIGNABLE = {
35441
+ detected: () => null,
35442
+ "operator-declared": () => "had its container image chosen by an operator out of band, and the signed vocabulary has no member for that. Signing it would present a hand-picked environment as the one the runtime mapping selected.",
35443
+ "developer-declared": () => "had its container image supplied by the developer whose work is under verification. A chosen interpreter turns a red suite green as surely as a chosen test command.",
35444
+ // `none` means no image was booted, so an image name beside it is a contradiction — the
35445
+ // `SOURCE_IS_SIGNABLE.none` argument, applied to the other field.
35446
+ none: (image) => image === null ? null : `records its image source as \`none\` \u2014 nothing was booted \u2014 beside the image ${JSON.stringify(image)}`
35447
+ };
35038
35448
  ATTEST_REFUSAL_REASONS = [
35039
35449
  "our-environment-failed",
35040
35450
  "run-was-refused",
@@ -35076,7 +35486,11 @@ var init_attestation2 = __esm({
35076
35486
  // The signed triple must be reproducible from what it names — see the guard.
35077
35487
  "patch-binding-incomplete",
35078
35488
  "outcome-contradicts-counts",
35079
- "test-command-origin-unattested"
35489
+ "test-command-origin-unattested",
35490
+ // The environment half of the pair above. `pair-disagrees-on-image` compares image NAMES;
35491
+ // this one asks who CHOSE the name, which no comparison of the two halves can reveal when
35492
+ // both were handed the same override.
35493
+ "image-origin-unattested"
35080
35494
  ];
35081
35495
  refuse3 = (reason, detail) => ({
35082
35496
  ok: false,
@@ -35580,13 +35994,20 @@ function gcpBootArgv(p) {
35580
35994
  // steal". Attaching an account removes that, so the safety now rests on two things,
35581
35995
  // and this change MUST land together with proof #2 — never ahead of it:
35582
35996
  //
35583
- // 1. `--no-scopes` + ZERO project roles bound the BLAST RADIUS of a lifted token.
35584
- // No scopes → the OAuth access-token endpoint serves nothing (measured,
35585
- // TERM-706); zero roles → the token authorizes no GCP API. But it is NOT
35586
- // valueless: it is the attestation evidence itself, so a lifted token still
35587
- // authorizes a false claim about WHERE a run happened (the steal-a-true-token
35588
- // attack in the item-2 spike). `--no-scopes` stays because it removes lateral
35589
- // GCP access; it does not make losing the identity token cheap.
35997
+ // 1. `--no-scopes` and a NARROW grant set bound the BLAST RADIUS of a lifted
35998
+ // token. No scopes → the OAuth access-token endpoint serves nothing
35999
+ // (measured, TERM-706). This said "ZERO project roles" until 2026-09-18, and
36000
+ // that was never true of the account we ship: measured live, `venue-identity@`
36001
+ // holds `roles/confidentialcomputing.workloadUser` on the project and
36002
+ // `roles/artifactregistry.reader` on the `venue-images` repository alone —
36003
+ // the same two grants the Confidential Space note below already named, so this
36004
+ // file contradicted itself. Neither reaches a second repository, a bucket or
36005
+ // another VM, which is the property "zero roles" was reaching for. But the
36006
+ // token is NOT valueless: it is the attestation evidence itself, so a lifted
36007
+ // token still authorizes a false claim about WHERE a run happened (the
36008
+ // steal-a-true-token attack in the item-2 spike). `--no-scopes` stays because
36009
+ // it removes lateral GCP access; it does not make losing the identity token
36010
+ // cheap.
35590
36011
  // 2. The untrusted code cannot reach the metadata server to lift the token at all.
35591
36012
  // The test step runs `--network=none`; the install step runs on an `--internal`
35592
36013
  // network whose only egress is a CONNECT-only, registry-allowlisted proxy that
@@ -37066,11 +37487,7 @@ function makeLease(p) {
37066
37487
  }
37067
37488
  try {
37068
37489
  check("counting what the run left behind");
37069
- return Promise.resolve({
37070
- observed: true,
37071
- census: census(p.docker, label),
37072
- unobservedReason: null
37073
- });
37490
+ return Promise.resolve(censusReport(p.docker, label));
37074
37491
  } catch (err) {
37075
37492
  return Promise.resolve({
37076
37493
  observed: false,
@@ -37338,7 +37755,7 @@ function localDockerPlacement() {
37338
37755
  kind: "local-docker",
37339
37756
  refusal: null,
37340
37757
  venue: () => localVenue(),
37341
- imageFor: (runtime, override, version2) => imageForRuntime(runtime, override, version2)
37758
+ imageFor: (runtime, override, version2, variant) => imageForRuntime(runtime, override, version2, variant)
37342
37759
  };
37343
37760
  }
37344
37761
  function hostedPoolPlacement() {
@@ -37346,7 +37763,7 @@ function hostedPoolPlacement() {
37346
37763
  kind: "hosted-pool",
37347
37764
  refusal: null,
37348
37765
  venue: () => hostedVenue(),
37349
- imageFor: (runtime, override, version2) => imageForRuntime(runtime, override, version2)
37766
+ imageFor: (runtime, override, version2, variant) => imageForRuntime(runtime, override, version2, variant)
37350
37767
  };
37351
37768
  }
37352
37769
  function placementFor(kind) {
@@ -37911,6 +38328,9 @@ function detectRuntime(repo) {
37911
38328
  if (repo.listNames("").some((name) => /\.(?:csproj|fsproj|sln)$/.test(name))) {
37912
38329
  candidates.push("dotnet");
37913
38330
  }
38331
+ if (candidates.length === 0 && repo.listFiles("").some((f) => f.endsWith(".java"))) {
38332
+ candidates.push("jvm");
38333
+ }
37914
38334
  return { runtime: candidates[0] ?? "unknown", candidates };
37915
38335
  }
37916
38336
  function readJsonObject(repo, path6) {
@@ -38058,6 +38478,11 @@ function basePythonInstallCommand(repo) {
38058
38478
  }
38059
38479
  return null;
38060
38480
  }
38481
+ function rehearse(command) {
38482
+ const copy = '"$R/.terminalhire-rehearsal"';
38483
+ const home = '"$R/.terminalhire-rehearsal-home"';
38484
+ 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; }`;
38485
+ }
38061
38486
  function otherRuntimeCommands(repo, runtime) {
38062
38487
  switch (runtime) {
38063
38488
  case "go":
@@ -38085,10 +38510,21 @@ function otherRuntimeCommands(repo, runtime) {
38085
38510
  case "dotnet":
38086
38511
  return { install: "dotnet restore", test: "dotnet test" };
38087
38512
  case "jvm": {
38088
- if (repo.exists("pom.xml"))
38089
- return { install: "mvn -B dependency:go-offline", test: "mvn -B test" };
38513
+ if (!JVM_BUILD_FILES.some((file) => repo.exists(file)))
38514
+ return { install: null, test: null };
38515
+ if (repo.exists("pom.xml")) {
38516
+ return {
38517
+ install: `mvn -B dependency:go-offline && ${rehearse('mvn -B test -Dmaven.test.failure.ignore=true -Dmaven.repo.local="$R/.m2/repository"')}`,
38518
+ test: "mvn -B -o test"
38519
+ };
38520
+ }
38090
38521
  const wrapper = repo.exists("gradlew") ? "./gradlew" : "gradle";
38091
- return { install: `${wrapper} dependencies`, test: `${wrapper} test` };
38522
+ return {
38523
+ install: `${wrapper} dependencies && ${rehearse(`${wrapper} test --continue`)}`,
38524
+ // `--no-build-cache`: the rehearsal above can fill Gradle's build cache, and a
38525
+ // test task restored FROM-CACHE runs nothing and prints no count.
38526
+ test: `${wrapper} test --offline --no-build-cache`
38527
+ };
38092
38528
  }
38093
38529
  default:
38094
38530
  return { install: null, test: null };
@@ -38120,6 +38556,15 @@ function searchRuntimeVersion(repo, runtime) {
38120
38556
  push(".ruby-version", repo.readText(".ruby-version"));
38121
38557
  } else if (runtime === "dotnet") {
38122
38558
  push("global.json sdk.version", readStringField(readObjectField(readJsonObject(repo, "global.json"), "sdk"), "version"));
38559
+ } else if (runtime === "jvm") {
38560
+ const pom = repo.readText("pom.xml");
38561
+ const gradle = repo.readText("build.gradle") ?? repo.readText("build.gradle.kts");
38562
+ push("build.gradle toolchain languageVersion", javaMajor(matchFirst(gradle, /JavaLanguageVersion\.of\(\s*["']?(\d+)["']?\s*\)/) ?? matchFirst(gradle, /jvmToolchain\(\s*(\d+)\s*\)/)));
38563
+ push(".java-version", javaMajor(repo.readText(".java-version")));
38564
+ push("pom.xml maven.compiler.release", javaMajor(matchFirst(pom, /<maven\.compiler\.release>\s*([^<\s]+)\s*</)));
38565
+ push("pom.xml java.version", javaMajor(matchFirst(pom, /<java\.version>\s*([^<\s]+)\s*</)));
38566
+ push("pom.xml maven.compiler.source", javaMajor(matchFirst(pom, /<maven\.compiler\.source>\s*([^<\s]+)\s*</)));
38567
+ push("build.gradle sourceCompatibility", javaMajor(matchFirst(gradle, /sourceCompatibility\s*=\s*JavaVersion\.VERSION_([\d_]+)/)?.replace("_", ".") ?? matchFirst(gradle, /sourceCompatibility\s*=\s*["']?([\d.]+)["']?/)));
38123
38568
  }
38124
38569
  let rangeFound = null;
38125
38570
  for (const source of sources) {
@@ -38133,13 +38578,21 @@ function searchRuntimeVersion(repo, runtime) {
38133
38578
  }
38134
38579
  return { version: null, searched: sources.map((s) => s.label), rangeFound };
38135
38580
  }
38581
+ function javaMajor(text) {
38582
+ if (text === null)
38583
+ return null;
38584
+ const match2 = /^(?:1\.(\d+)|(\d+))(?:[._][\d_.]*)?$/.exec(text.trim());
38585
+ if (match2 === null)
38586
+ return null;
38587
+ return match2[1] ?? match2[2] ?? null;
38588
+ }
38136
38589
  function matchFirst(text, pattern) {
38137
38590
  if (text === null)
38138
38591
  return null;
38139
38592
  const match2 = pattern.exec(text);
38140
38593
  return match2 === null ? null : match2[1];
38141
38594
  }
38142
- var RUNTIME_MANIFESTS, MANIFEST_FILENAMES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, EXACT_VERSION;
38595
+ var RUNTIME_MANIFESTS, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, EXACT_VERSION;
38143
38596
  var init_manifest2 = __esm({
38144
38597
  "../../packages/envspec/dist/manifest.js"() {
38145
38598
  "use strict";
@@ -38153,6 +38606,7 @@ var init_manifest2 = __esm({
38153
38606
  { runtime: "dotnet", files: [] }
38154
38607
  ];
38155
38608
  MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"]).sort();
38609
+ JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
38156
38610
  NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
38157
38611
  REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
38158
38612
  EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
@@ -38549,7 +39003,7 @@ function deriveFromReader(repo) {
38549
39003
  searched: MANIFEST_FILENAMES,
38550
39004
  detail: `no ecosystem manifest was found at the repo root (looked for ${MANIFEST_FILENAMES.join(", ")}), so the language runtime could not be identified`
38551
39005
  });
38552
- } else {
39006
+ } else if (runtime !== "jvm" || JVM_BUILD_FILES.some((file) => repo.exists(file))) {
38553
39007
  derivedFrom.add("manifest");
38554
39008
  }
38555
39009
  if (detection.candidates.length > 1) {
@@ -38713,12 +39167,13 @@ function deriveRuntimeVersion(repo, runtime, job) {
38713
39167
  const setup = SETUP_ACTIONS[runtime];
38714
39168
  const searched = [];
38715
39169
  let rangeFound = null;
39170
+ const pin = (raw) => runtime === "jvm" ? javaMajor(raw) : exactVersion(raw);
38716
39171
  if (job !== null && setup !== void 0) {
38717
39172
  const direct = findActionInput(job, setup.action, setup.input);
38718
39173
  if (direct !== null) {
38719
39174
  searched.push(`${job.file} ${setup.action} ${setup.input}`);
38720
39175
  const reduced = reduceExpression(direct.command, job.singletons);
38721
- const exact = exactVersion(reduced);
39176
+ const exact = pin(reduced);
38722
39177
  if (exact !== null)
38723
39178
  return { value: exact, searched, rangeFound: null };
38724
39179
  if (reduced !== null)
@@ -38727,7 +39182,7 @@ function deriveRuntimeVersion(repo, runtime, job) {
38727
39182
  const viaFile = findActionInput(job, setup.action, setup.fileInput);
38728
39183
  if (viaFile !== null) {
38729
39184
  searched.push(`${job.file} ${setup.action} ${setup.fileInput} -> ${viaFile.command}`);
38730
- const exact = exactVersion(repo.readText(viaFile.command));
39185
+ const exact = pin(repo.readText(viaFile.command));
38731
39186
  if (exact !== null)
38732
39187
  return { value: exact, searched, rangeFound: null };
38733
39188
  }
@@ -38752,6 +39207,8 @@ function testSearchPaths(runtime, workflowFiles) {
38752
39207
  paths.push("**/*_test.go");
38753
39208
  if (runtime === "ruby")
38754
39209
  paths.push("Gemfile", "Rakefile");
39210
+ if (runtime === "jvm")
39211
+ paths.push(...JVM_BUILD_FILES);
38755
39212
  if (runtime === "unknown")
38756
39213
  paths.push(...MANIFEST_FILENAMES);
38757
39214
  return [...new Set(paths)].sort();
@@ -38769,6 +39226,9 @@ function describeTestSearch(repo, runtime, workflowFiles) {
38769
39226
  if (runtime === "python") {
38770
39227
  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`;
38771
39228
  }
39229
+ if (runtime === "jvm" && !JVM_BUILD_FILES.some((file) => repo.exists(file))) {
39230
+ 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`;
39231
+ }
38772
39232
  return `${ciPart}, and no ${runtime} test convention applies to this tree`;
38773
39233
  }
38774
39234
  function installSearchPaths(runtime, workflowFiles) {
@@ -38777,6 +39237,8 @@ function installSearchPaths(runtime, workflowFiles) {
38777
39237
  paths.push("package-lock.json", "pnpm-lock.yaml", "yarn.lock");
38778
39238
  if (runtime === "python")
38779
39239
  paths.push("poetry.lock", "pyproject.toml", "requirements.txt", "uv.lock");
39240
+ if (runtime === "jvm")
39241
+ paths.push(...JVM_BUILD_FILES);
38780
39242
  if (runtime === "unknown")
38781
39243
  paths.push(...MANIFEST_FILENAMES);
38782
39244
  return [...new Set(paths)].sort();
@@ -38855,12 +39317,41 @@ var init_dist3 = __esm({
38855
39317
  }
38856
39318
  });
38857
39319
 
39320
+ // ../../packages/envrun/dist/gradleTestSummary.js
39321
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync23 } from "fs";
39322
+ import { join as join42 } from "path";
39323
+ function stageGradleTestSummary(jailHome) {
39324
+ const dir = join42(jailHome, ".gradle", "init.d");
39325
+ mkdirSync7(dir, { recursive: true });
39326
+ writeFileSync23(join42(dir, "terminalhire-test-summary.gradle"), GRADLE_TEST_SUMMARY_INIT);
39327
+ }
39328
+ var GRADLE_TEST_MARKER, GRADLE_TEST_SUMMARY_INIT;
39329
+ var init_gradleTestSummary = __esm({
39330
+ "../../packages/envrun/dist/gradleTestSummary.js"() {
39331
+ "use strict";
39332
+ GRADLE_TEST_MARKER = "terminalhire-gradle-tests:";
39333
+ GRADLE_TEST_SUMMARY_INIT = `// Written by terminalhire (TERM-1122): prints each Test task's counts, because
39334
+ // Gradle prints none when every test passes.
39335
+ allprojects {
39336
+ tasks.withType(Test).configureEach { task ->
39337
+ def label = task.path
39338
+ task.afterSuite { desc, result ->
39339
+ if (desc.parent == null) {
39340
+ println "${GRADLE_TEST_MARKER} task=\${label} total=\${result.testCount} passed=\${result.successfulTestCount} failed=\${result.failedTestCount} skipped=\${result.skippedTestCount}"
39341
+ }
39342
+ }
39343
+ }
39344
+ }
39345
+ `;
39346
+ }
39347
+ });
39348
+
38858
39349
  // ../../packages/envrun/dist/thrun.js
38859
39350
  import { execFileSync, spawnSync as spawnSync7 } from "child_process";
38860
- import { existsSync as existsSync19, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "fs";
39351
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "fs";
38861
39352
  import { randomUUID as randomUUID3 } from "crypto";
38862
39353
  import { tmpdir as tmpdir4 } from "os";
38863
- import { join as join42 } from "path";
39354
+ import { join as join43 } from "path";
38864
39355
  function git(repoDir, args5, allowNonZero = false) {
38865
39356
  const res = spawnSync7("git", [...args5], {
38866
39357
  cwd: repoDir,
@@ -38875,7 +39366,7 @@ function git(repoDir, args5, allowNonZero = false) {
38875
39366
  return res.stdout ?? "";
38876
39367
  }
38877
39368
  function collectWorkingDiff(repoDir, opts = {}) {
38878
- if (!existsSync19(join42(repoDir, ".git"))) {
39369
+ if (!existsSync19(join43(repoDir, ".git"))) {
38879
39370
  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.`);
38880
39371
  }
38881
39372
  const headSha = git(repoDir, ["rev-parse", "HEAD"]).trim();
@@ -39023,7 +39514,7 @@ function credentialFreeHome() {
39023
39514
  return credentialFreeHomeDir;
39024
39515
  let made;
39025
39516
  try {
39026
- made = mkdtempSync4(join42(tmpdir4(), "th-run-nohome-"));
39517
+ made = mkdtempSync4(join43(tmpdir4(), "th-run-nohome-"));
39027
39518
  } catch (err) {
39028
39519
  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 });
39029
39520
  }
@@ -39098,7 +39589,7 @@ function cloneTargetAtUnguarded(opts) {
39098
39589
  if (persisted !== null) {
39099
39590
  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.`);
39100
39591
  }
39101
- mkdirSync7(opts.dest, { recursive: true });
39592
+ mkdirSync8(opts.dest, { recursive: true });
39102
39593
  const runOut = (args5) => execFileSync("git", [...gitConfigArgs(), ...args5], {
39103
39594
  cwd: opts.dest,
39104
39595
  encoding: "utf8",
@@ -39137,7 +39628,7 @@ function cloneTargetAtUnguarded(opts) {
39137
39628
  }
39138
39629
  function scrubCloneSource(dest, run32) {
39139
39630
  run32(["remote", "remove", "origin"]);
39140
- rmSync11(join42(dest, ".git", "FETCH_HEAD"), { force: true });
39631
+ rmSync11(join43(dest, ".git", "FETCH_HEAD"), { force: true });
39141
39632
  }
39142
39633
  function publishableTarget(url) {
39143
39634
  if (separatorInTarget(url) !== null)
@@ -39276,12 +39767,18 @@ function refusedRun(fields) {
39276
39767
  treeDigest: null,
39277
39768
  baselinePatchSha256: null,
39278
39769
  testCommandSource: "none",
39770
+ // Nothing was booted, so nobody chose an image. Same reasoning as `containerImage: null`
39771
+ // below: it must not read as an image we picked and cannot name.
39772
+ imageSource: "none",
39279
39773
  boundaryRefusals: fields.boundaryRefusals,
39280
39774
  touchedPaths: fields.touchedPaths,
39281
39775
  preview: null,
39282
39776
  containerImage: null,
39283
39777
  containerImageDigest: null,
39284
39778
  leaksClean: null,
39779
+ // Null for the same reason, and never `unobserved`: that word says we tried to take a
39780
+ // census and could not, and here there was nothing to take one of (TERM-1144).
39781
+ leakState: null,
39285
39782
  // A refused run never held a lease, so there is no venue to describe. Same
39286
39783
  // reasoning as `leaksClean` above: null because nothing happened, and it must
39287
39784
  // not read as a venue we looked at and could not name.
@@ -39509,10 +40006,10 @@ async function runVerification(req, ctx) {
39509
40006
  venueIdentity: null
39510
40007
  };
39511
40008
  }
39512
- const stage = join42(req.scratchRoot, runId);
39513
- const cloneDir = join42(stage, "clone");
39514
- const scratch = join42(stage, "scratch");
39515
- mkdirSync7(scratch, { recursive: true });
40009
+ const stage = join43(req.scratchRoot, runId);
40010
+ const cloneDir = join43(stage, "clone");
40011
+ const scratch = join43(stage, "scratch");
40012
+ mkdirSync8(scratch, { recursive: true });
39516
40013
  assertSafeTargetSha(req.targetSha);
39517
40014
  progress("clone", `${publishableTarget(req.targetRepo)} @ ${req.targetSha.slice(0, 12)}`);
39518
40015
  cloneTargetAt({
@@ -39539,7 +40036,8 @@ async function runVerification(req, ctx) {
39539
40036
  const derived = deriveEnvironmentSpec(cloneDir);
39540
40037
  const spec = req.testCommandOverride === void 0 ? derived : { ...derived, testCommand: req.testCommandOverride };
39541
40038
  progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
39542
- const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion);
40039
+ refuseUnbuildableSpec(spec);
40040
+ const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
39543
40041
  const resolved = await resolveLease(placement, runId);
39544
40042
  if (!resolved.ok) {
39545
40043
  return {
@@ -39574,10 +40072,12 @@ async function runVerification(req, ctx) {
39574
40072
  const lease = resolved.lease;
39575
40073
  try {
39576
40074
  buildJail(scratch);
40075
+ if (spec.runtime === "jvm")
40076
+ stageGradleTestSummary(localJailPaths(scratch).jail);
39577
40077
  const venuePaths = await lease.stage({
39578
40078
  cloneDir,
39579
40079
  scratchRoot: scratch,
39580
- previewDir: join42(stage, "preview"),
40080
+ previewDir: join43(stage, "preview"),
39581
40081
  // On a dispatched run the commit is the statement of what was tested, so
39582
40082
  // it rides with the tree and the venue seam refuses a tree that is not
39583
40083
  // that commit (design §6 item 4, TERM-892 — the guard lives in
@@ -39624,7 +40124,7 @@ async function runVerification(req, ctx) {
39624
40124
  // that `founder-declared` (as this did for one review round) signs the counterparty's
39625
40125
  // name onto the developer's choice, which is worse than laundering it as `detected`:
39626
40126
  // it is a specific false attribution inside a field a reviewer trusts.
39627
- testCommandSource: req.testCommandOverride !== void 0 ? req.testCommandOverrideOrigin === "founder" ? "founder-declared" : "developer-declared" : derived.testCommand === null ? "none" : "detected",
40127
+ testCommandSource: req.testCommandOverride !== void 0 ? req.testCommandOverrideOrigin === "founder" ? "founder-declared" : req.testCommandOverrideOrigin === "operator" ? "operator-declared" : "developer-declared" : derived.testCommand === null ? "none" : "detected",
39628
40128
  boundaryRefusals: [],
39629
40129
  touchedPaths: pre.touchedPaths,
39630
40130
  preview: null,
@@ -39634,7 +40134,14 @@ async function runVerification(req, ctx) {
39634
40134
  // that measures one; a run without it is recordable but not attestable —
39635
40135
  // `toAcceptancePredicate` refuses `missing-image-digest` (TERM-893).
39636
40136
  containerImageDigest: null,
40137
+ // The image half of the provenance pair above. `detected` means the runtime mapping
40138
+ // chose the environment; an override names the human. Defaulting an unstated origin to
40139
+ // `developer-declared` is deliberate and matches `testCommandSource`: `th run --image`
40140
+ // runs on the developer's machine, judging the developer's work, so attributing it to
40141
+ // the operator would sign our name onto their choice.
40142
+ imageSource: req.image === void 0 ? "detected" : req.imageOrigin === "operator" ? "operator-declared" : "developer-declared",
39637
40143
  leaksClean: verdict.leaks.clean,
40144
+ leakState: verdict.leaks.state,
39638
40145
  // Built from the LEASE, over the client that ran the steps — never from
39639
40146
  // `req.placement`, which is a request. `venueDescriptor.ts` carries the
39640
40147
  // reasoning and the #735 failure that makes the distinction load-bearing.
@@ -39692,6 +40199,7 @@ var init_thrun = __esm({
39692
40199
  init_execute();
39693
40200
  init_placement();
39694
40201
  init_venue();
40202
+ init_gradleTestSummary();
39695
40203
  init_venueDescriptor();
39696
40204
  init_result();
39697
40205
  ThRunError = class extends Error {
@@ -39935,7 +40443,7 @@ var init_dbplan = __esm({
39935
40443
  // ../../packages/envrun/dist/dbstack.js
39936
40444
  import { spawnSync as spawnSync8 } from "child_process";
39937
40445
  import { randomBytes as randomBytes12 } from "crypto";
39938
- import { mkdirSync as mkdirSync8 } from "fs";
40446
+ import { mkdirSync as mkdirSync9 } from "fs";
39939
40447
  function installCommandFor(runner) {
39940
40448
  switch (runner) {
39941
40449
  case "sql":
@@ -40166,7 +40674,7 @@ function startDatabase(req) {
40166
40674
  if (!ready.ok) {
40167
40675
  throw new DbStackError(`INFRASTRUCTURE/INCONCLUSIVE: Postgres never accepted queries at 127.0.0.1:${String(creds.port)} within the readiness window: ${ready.detail}`);
40168
40676
  }
40169
- const query = (sql) => {
40677
+ const query2 = (sql) => {
40170
40678
  const res = docker2([
40171
40679
  "exec",
40172
40680
  `--env=PGPASSWORD=${creds.password}`,
@@ -40214,7 +40722,7 @@ function startDatabase(req) {
40214
40722
  credentials: creds,
40215
40723
  image,
40216
40724
  readyMs: ready.ms,
40217
- query,
40725
+ query: query2,
40218
40726
  runOnNetwork,
40219
40727
  teardown
40220
40728
  };
@@ -40277,8 +40785,8 @@ async function installLocalMigrationTooling(opts) {
40277
40785
  };
40278
40786
  }
40279
40787
  const { jail, tmp } = buildJail(opts.scratchRoot);
40280
- mkdirSync8(jail, { recursive: true });
40281
- mkdirSync8(tmp, { recursive: true });
40788
+ mkdirSync9(jail, { recursive: true });
40789
+ mkdirSync9(tmp, { recursive: true });
40282
40790
  const spec = {
40283
40791
  profile: "install",
40284
40792
  clone: opts.repoDir,
@@ -40573,6 +41081,7 @@ __export(dist_exports, {
40573
41081
  GOOGLE_JWKS_URL: () => GOOGLE_JWKS_URL,
40574
41082
  HostedVenueError: () => HostedVenueError,
40575
41083
  JWKS_FETCH_TIMEOUT_MS: () => JWKS_FETCH_TIMEOUT_MS,
41084
+ LEAK_STATES: () => LEAK_STATES,
40576
41085
  LOCAL_MEASUREMENT_PREFIX: () => LOCAL_MEASUREMENT_PREFIX,
40577
41086
  LabelWatch: () => LabelWatch,
40578
41087
  MIN_GIT_VERSION_FOR_END_OF_OPTIONS: () => MIN_GIT_VERSION_FOR_END_OF_OPTIONS,
@@ -40589,6 +41098,7 @@ __export(dist_exports, {
40589
41098
  REDACTED_TARGET_SHA: () => REDACTED_TARGET_SHA,
40590
41099
  RELEASED_LEASE_CENSUS_REASON: () => RELEASED_LEASE_CENSUS_REASON,
40591
41100
  REPO_DIGEST_RE: () => REPO_DIGEST_RE,
41101
+ RUN_IMAGE_SOURCES: () => RUN_IMAGE_SOURCES,
40592
41102
  RUN_LABEL_KEY: () => RUN_LABEL_KEY,
40593
41103
  RUN_RESULT_FIELDS: () => RUN_RESULT_FIELDS,
40594
41104
  RUN_RESULT_SCHEMA: () => RUN_RESULT_SCHEMA,
@@ -40605,6 +41115,7 @@ __export(dist_exports, {
40605
41115
  VENUE_GCLOUD_CONFIG: () => VENUE_GCLOUD_CONFIG,
40606
41116
  VENUE_SSH_USER: () => VENUE_SSH_USER,
40607
41117
  VENV_DIR: () => VENV_DIR,
41118
+ VERIFICATION_OUTCOMES: () => VERIFICATION_OUTCOMES,
40608
41119
  VenueRollbackError: () => VenueRollbackError,
40609
41120
  acquireTransactionally: () => acquireTransactionally,
40610
41121
  alembicChainPosition: () => alembicChainPosition,
@@ -40618,6 +41129,7 @@ __export(dist_exports, {
40618
41129
  assertVenueOwnerDeclared: () => assertVenueOwnerDeclared,
40619
41130
  bookkeepingFor: () => bookkeepingFor,
40620
41131
  census: () => census,
41132
+ censusReport: () => censusReport,
40621
41133
  censusTotal: () => censusTotal,
40622
41134
  classifyBootFailure: () => classifyBootFailure,
40623
41135
  classifyProbeFailure: () => classifyProbeFailure,
@@ -40637,6 +41149,7 @@ __export(dist_exports, {
40637
41149
  detectRunner: () => detectRunner,
40638
41150
  endOfOptionsUnsupported: () => endOfOptionsUnsupported,
40639
41151
  ensureVenueServiceCredentials: () => ensureVenueServiceCredentials,
41152
+ exitCodeFor: () => exitCodeFor,
40640
41153
  failureSourceOf: () => failureSourceOf,
40641
41154
  findRunRefusal: () => findRunRefusal,
40642
41155
  gcpBootArgv: () => gcpBootArgv,
@@ -40651,6 +41164,7 @@ __export(dist_exports, {
40651
41164
  identityProbeCommand: () => identityProbeCommand,
40652
41165
  imageForRuntime: () => imageForRuntime,
40653
41166
  imageRepo: () => imageRepo,
41167
+ imageVariantFor: () => imageVariantFor,
40654
41168
  installCommandFor: () => installCommandFor,
40655
41169
  installLocalMigrationTooling: () => installLocalMigrationTooling,
40656
41170
  isBookkeepingTable: () => isBookkeepingTable,
@@ -40680,6 +41194,7 @@ __export(dist_exports, {
40680
41194
  readSchema: () => readSchema,
40681
41195
  recordedApplied: () => recordedApplied,
40682
41196
  refuseSshTransport: () => refuseSshTransport,
41197
+ refuseUnbuildableSpec: () => refuseUnbuildableSpec,
40683
41198
  renderRunReport: () => renderRunReport,
40684
41199
  renderVenueLine: () => renderVenueLine,
40685
41200
  renderVerdictLine: () => renderVerdictLine,
@@ -41061,8 +41576,8 @@ __export(jpi_claim_exports, {
41061
41576
  });
41062
41577
  import {
41063
41578
  readFileSync as readFileSync27,
41064
- writeFileSync as writeFileSync23,
41065
- mkdirSync as mkdirSync9,
41579
+ writeFileSync as writeFileSync24,
41580
+ mkdirSync as mkdirSync10,
41066
41581
  mkdtempSync as mkdtempSync5,
41067
41582
  renameSync as renameSync11,
41068
41583
  existsSync as existsSync20,
@@ -41071,7 +41586,7 @@ import {
41071
41586
  rmSync as rmSync12,
41072
41587
  readdirSync as readdirSync3
41073
41588
  } from "fs";
41074
- import { join as join43, dirname as dirname10, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
41589
+ import { join as join44, dirname as dirname10, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
41075
41590
  import { createHash as createHash11 } from "crypto";
41076
41591
  import { homedir as homedir26, hostname as osHostname } from "os";
41077
41592
  import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync9 } from "child_process";
@@ -41090,7 +41605,7 @@ function markClaimNudged(id) {
41090
41605
  const ids2 = readNudgedClaimIds();
41091
41606
  ids2.add(id);
41092
41607
  ensureStateDir(TERMINALHIRE_DIR17);
41093
- writeFileSync23(REPO_CONTINUITY_NUDGE_MARKER, JSON.stringify({ claimIds: [...ids2] }), "utf8");
41608
+ writeFileSync24(REPO_CONTINUITY_NUDGE_MARKER, JSON.stringify({ claimIds: [...ids2] }), "utf8");
41094
41609
  } catch {
41095
41610
  }
41096
41611
  }
@@ -43355,7 +43870,7 @@ async function cmdAttach(id, worktree, branch) {
43355
43870
  function workDirFor(repoFullName, issueNumber) {
43356
43871
  const [owner, repo] = String(repoFullName).split("/");
43357
43872
  const suffix = issueNumber ? `-${issueNumber}` : "";
43358
- return join43(homedir26(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
43873
+ return join44(homedir26(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
43359
43874
  }
43360
43875
  function startBranchFor(repoFullName, issueNumber) {
43361
43876
  const repo = String(repoFullName).split("/")[1] || "claim";
@@ -43611,7 +44126,7 @@ terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to y
43611
44126
  );
43612
44127
  process.exit(1);
43613
44128
  }
43614
- mkdirSync9(join43(homedir26(), "terminalhire", "work"), { recursive: true });
44129
+ mkdirSync10(join44(homedir26(), "terminalhire", "work"), { recursive: true });
43615
44130
  const { createProgress: createProgress2, parseGitProgress: parseGitProgress2, splitProgressChunk: splitProgressChunk2, shStream: shStream2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
43616
44131
  const progress = createProgress2();
43617
44132
  let forkFullName;
@@ -43737,7 +44252,7 @@ function founderPostingIdOf(claim) {
43737
44252
  }
43738
44253
  function sliceWorkDirFor(claimLocalId) {
43739
44254
  const safe = String(claimLocalId).replace(/[^A-Za-z0-9._-]/g, "-");
43740
- return join43(homedir26(), "terminalhire", "work", `slice-${safe}`);
44255
+ return join44(homedir26(), "terminalhire", "work", `slice-${safe}`);
43741
44256
  }
43742
44257
  function assertNoBooleanPath(dest, flagName) {
43743
44258
  const last = String(dest).split(/[\\/]/).filter(Boolean).pop();
@@ -43854,9 +44369,9 @@ function writeSliceFiles(destDir, files) {
43854
44369
  const unavailable = [];
43855
44370
  for (const f of files) {
43856
44371
  if (typeof f.content === "string") {
43857
- const abs = join43(destDir, f.path);
43858
- mkdirSync9(dirname10(abs), { recursive: true });
43859
- writeFileSync23(abs, f.content, "utf8");
44372
+ const abs = join44(destDir, f.path);
44373
+ mkdirSync10(dirname10(abs), { recursive: true });
44374
+ writeFileSync24(abs, f.content, "utf8");
43860
44375
  written.push(f.path);
43861
44376
  } else {
43862
44377
  unavailable.push({ path: f.path, reason: f.unavailableReason || "(no reason given)" });
@@ -43885,7 +44400,7 @@ function writeDeliveredBrief(destDir, spec) {
43885
44400
  function ensureExcludedPackDir(destDir) {
43886
44401
  let occupant = null;
43887
44402
  try {
43888
- occupant = lstatSync4(join43(destDir, BRIEF_DIR));
44403
+ occupant = lstatSync4(join44(destDir, BRIEF_DIR));
43889
44404
  } catch (err) {
43890
44405
  if (err?.code !== "ENOENT") {
43891
44406
  return {
@@ -43900,12 +44415,12 @@ function ensureExcludedPackDir(destDir) {
43900
44415
  reason: `${BRIEF_DIR}/ already exists in the delivered tree, and excluding it would hide that content from your patch`
43901
44416
  };
43902
44417
  }
43903
- const excludeFile = join43(destDir, ".git", "info", "exclude");
44418
+ const excludeFile = join44(destDir, ".git", "info", "exclude");
43904
44419
  try {
43905
44420
  const existing = existsSync20(excludeFile) ? readFileSync27(excludeFile, "utf8") : "";
43906
44421
  if (!existing.split("\n").includes(BRIEF_EXCLUDE_LINE)) {
43907
- mkdirSync9(dirname10(excludeFile), { recursive: true });
43908
- writeFileSync23(
44422
+ mkdirSync10(dirname10(excludeFile), { recursive: true });
44423
+ writeFileSync24(
43909
44424
  excludeFile,
43910
44425
  `${existing}${existing === "" || existing.endsWith("\n") ? "" : "\n"}${BRIEF_EXCLUDE_LINE}
43911
44426
  `,
@@ -43919,9 +44434,9 @@ function ensureExcludedPackDir(destDir) {
43919
44434
  }
43920
44435
  function writePackFile(destDir, relPath, content, what) {
43921
44436
  try {
43922
- const abs = join43(destDir, relPath);
43923
- mkdirSync9(dirname10(abs), { recursive: true });
43924
- writeFileSync23(abs, content, { encoding: "utf8", flag: "wx" });
44437
+ const abs = join44(destDir, relPath);
44438
+ mkdirSync10(dirname10(abs), { recursive: true });
44439
+ writeFileSync24(abs, content, { encoding: "utf8", flag: "wx" });
43925
44440
  } catch (err) {
43926
44441
  return { written: false, reason: `the ${what} could not be written (${err.message})` };
43927
44442
  }
@@ -44322,7 +44837,7 @@ async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRep
44322
44837
  if (resolvedDir.suffixed) {
44323
44838
  console.log(`terminalhire claim: the usual directory has content \u2014 using ${dest}`);
44324
44839
  }
44325
- mkdirSync9(dest, { recursive: true });
44840
+ mkdirSync10(dest, { recursive: true });
44326
44841
  const branch = `claim/${String(claim.id).replace(/[^A-Za-z0-9._-]/g, "-")}`;
44327
44842
  let engine;
44328
44843
  try {
@@ -44504,7 +45019,7 @@ async function attemptSliceDelivery(id, flags = {}) {
44504
45019
  if (resolvedDir.suffixed) {
44505
45020
  console.log(`terminalhire claim: the usual directory has content \u2014 using ${finalDest}`);
44506
45021
  }
44507
- mkdirSync9(dirname10(finalDest), { recursive: true });
45022
+ mkdirSync10(dirname10(finalDest), { recursive: true });
44508
45023
  let dest = mkdtempSync5(`${finalDest}.tmp-`);
44509
45024
  const { written, unavailable } = writeSliceFiles(dest, body.files);
44510
45025
  const branch = `claim/${String(claim.id).replace(/[^A-Za-z0-9._-]/g, "-")}`;
@@ -45045,7 +45560,7 @@ async function cmdSubmit(id, flags = {}) {
45045
45560
  const head = `${ghUser}:${claim.branch}`;
45046
45561
  const title = flags.title || claim.title;
45047
45562
  const noBody = Boolean(flags["no-body"]);
45048
- const prBodyPath = join43(wt, "PR-BODY.md");
45563
+ const prBodyPath = join44(wt, "PR-BODY.md");
45049
45564
  const bodySource = pickBodySource({
45050
45565
  bodyFileFlag: flags["body-file"],
45051
45566
  noBody,
@@ -45285,7 +45800,7 @@ function readClaimPushMarker() {
45285
45800
  }
45286
45801
  function writeClaimPushMarker(marker) {
45287
45802
  ensureStateDir(TERMINALHIRE_DIR17);
45288
- writeFileSync23(CLAIM_PUSH_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
45803
+ writeFileSync24(CLAIM_PUSH_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
45289
45804
  }
45290
45805
  function clearClaimPushMarker() {
45291
45806
  try {
@@ -46199,10 +46714,10 @@ var init_jpi_claim = __esm({
46199
46714
  init_claim_push_bg();
46200
46715
  init_founder_verdict_sync();
46201
46716
  init_founder_note_sync();
46202
- TERMINALHIRE_DIR17 = process.env.TERMINALHIRE_DIR || join43(homedir26(), ".terminalhire");
46203
- INDEX_CACHE_FILE5 = join43(TERMINALHIRE_DIR17, "index-cache.json");
46204
- CLAIM_PUSH_MARKER = join43(TERMINALHIRE_DIR17, "claim-push.json");
46205
- REPO_CONTINUITY_NUDGE_MARKER = join43(TERMINALHIRE_DIR17, "repo-continuity-nudged.json");
46717
+ TERMINALHIRE_DIR17 = process.env.TERMINALHIRE_DIR || join44(homedir26(), ".terminalhire");
46718
+ INDEX_CACHE_FILE5 = join44(TERMINALHIRE_DIR17, "index-cache.json");
46719
+ CLAIM_PUSH_MARKER = join44(TERMINALHIRE_DIR17, "claim-push.json");
46720
+ REPO_CONTINUITY_NUDGE_MARKER = join44(TERMINALHIRE_DIR17, "repo-continuity-nudged.json");
46206
46721
  API_URL6 = resolveApiBase();
46207
46722
  CLAIM_SYNC_BASE4 = API_URL6;
46208
46723
  CLAIM_CONSENT_VERSION = 1;
@@ -46472,16 +46987,16 @@ import {
46472
46987
  readFileSync as readFileSync28,
46473
46988
  renameSync as renameSync12,
46474
46989
  rmSync as rmSync13,
46475
- writeFileSync as writeFileSync24
46990
+ writeFileSync as writeFileSync25
46476
46991
  } from "fs";
46477
46992
  import { homedir as homedir27 } from "os";
46478
- import { join as join44 } from "path";
46993
+ import { join as join45 } from "path";
46479
46994
  import { randomUUID as randomUUID4 } from "crypto";
46480
46995
  function stateDir3() {
46481
- return process.env["TERMINALHIRE_DIR"] || join44(homedir27(), ".terminalhire");
46996
+ return process.env["TERMINALHIRE_DIR"] || join45(homedir27(), ".terminalhire");
46482
46997
  }
46483
46998
  function postingDraftFilePath() {
46484
- return join44(stateDir3(), "posting-drafts.json");
46999
+ return join45(stateDir3(), "posting-drafts.json");
46485
47000
  }
46486
47001
  function blankFile2() {
46487
47002
  return { version: 1, drafts: [] };
@@ -46508,7 +47023,7 @@ function writeFile(file) {
46508
47023
  const path6 = postingDraftFilePath();
46509
47024
  const tmp = `${path6}.${process.pid}.${randomUUID4()}.tmp`;
46510
47025
  try {
46511
- writeFileSync24(tmp, `${JSON.stringify(file, null, 2)}
47026
+ writeFileSync25(tmp, `${JSON.stringify(file, null, 2)}
46512
47027
  `, {
46513
47028
  encoding: "utf8",
46514
47029
  mode: 384,
@@ -46791,7 +47306,7 @@ __export(jpi_post_exports, {
46791
47306
  import { existsSync as existsSync22, readFileSync as readFileSync29 } from "fs";
46792
47307
  import { spawnSync as spawnSync10 } from "child_process";
46793
47308
  import { createInterface as createInterface10 } from "readline";
46794
- import { basename as basename6, join as join45 } from "path";
47309
+ import { basename as basename6, join as join46 } from "path";
46795
47310
  function parsePostArgs(argv) {
46796
47311
  const flags = {};
46797
47312
  const positional = [];
@@ -46833,15 +47348,15 @@ function ownerRepo(remote) {
46833
47348
  }
46834
47349
  function detectStack(cwd) {
46835
47350
  const stack = [];
46836
- if (existsSync22(join45(cwd, "package.json"))) stack.push("node");
46837
- if (existsSync22(join45(cwd, "next.config.js")) || existsSync22(join45(cwd, "next.config.mjs"))) {
47351
+ if (existsSync22(join46(cwd, "package.json"))) stack.push("node");
47352
+ if (existsSync22(join46(cwd, "next.config.js")) || existsSync22(join46(cwd, "next.config.mjs"))) {
46838
47353
  stack.push("next.js");
46839
47354
  }
46840
- if (existsSync22(join45(cwd, "pyproject.toml")) || existsSync22(join45(cwd, "requirements.txt"))) {
47355
+ if (existsSync22(join46(cwd, "pyproject.toml")) || existsSync22(join46(cwd, "requirements.txt"))) {
46841
47356
  stack.push("python");
46842
47357
  }
46843
- if (existsSync22(join45(cwd, "Cargo.toml"))) stack.push("rust");
46844
- if (existsSync22(join45(cwd, "go.mod"))) stack.push("go");
47358
+ if (existsSync22(join46(cwd, "Cargo.toml"))) stack.push("rust");
47359
+ if (existsSync22(join46(cwd, "go.mod"))) stack.push("go");
46845
47360
  return stack;
46846
47361
  }
46847
47362
  function captureRepository(cwd = process.cwd()) {
@@ -47261,25 +47776,25 @@ var init_jpi_repo = __esm({
47261
47776
  import {
47262
47777
  closeSync as closeSync5,
47263
47778
  existsSync as existsSync23,
47264
- mkdirSync as mkdirSync10,
47779
+ mkdirSync as mkdirSync11,
47265
47780
  openSync as openSync5,
47266
47781
  readFileSync as readFileSync30,
47267
47782
  readdirSync as readdirSync4,
47268
47783
  renameSync as renameSync13,
47269
47784
  statSync as statSync5,
47270
47785
  unlinkSync as unlinkSync4,
47271
- writeFileSync as writeFileSync25
47786
+ writeFileSync as writeFileSync26
47272
47787
  } from "fs";
47273
47788
  import { homedir as homedir28 } from "os";
47274
- import { basename as basename7, dirname as dirname11, join as join46 } from "path";
47789
+ import { basename as basename7, dirname as dirname11, join as join47 } from "path";
47275
47790
  function defaultUrl() {
47276
47791
  return process.env["TERMINALHIRE_RECALL_URL"] || RECALL_URL;
47277
47792
  }
47278
47793
  function stateDir4() {
47279
- return process.env["TERMINALHIRE_DIR"] || join46(homedir28(), ".terminalhire");
47794
+ return process.env["TERMINALHIRE_DIR"] || join47(homedir28(), ".terminalhire");
47280
47795
  }
47281
47796
  function recallCachePath() {
47282
- return join46(stateDir4(), "recall.json");
47797
+ return join47(stateDir4(), "recall.json");
47283
47798
  }
47284
47799
  async function fetchRecalls(url = defaultUrl()) {
47285
47800
  try {
@@ -47307,7 +47822,7 @@ function mutateCache(path6, mutate) {
47307
47822
  const lock = `${path6}.lock`;
47308
47823
  let held = false;
47309
47824
  try {
47310
- mkdirSync10(dirname11(path6), { recursive: true, mode: 448 });
47825
+ mkdirSync11(dirname11(path6), { recursive: true, mode: 448 });
47311
47826
  const deadline = Date.now() + LOCK_WAIT_MS;
47312
47827
  while (!held && Date.now() < deadline) {
47313
47828
  try {
@@ -47331,7 +47846,7 @@ function mutateCache(path6, mutate) {
47331
47846
  if (mutate(existing) === false) return false;
47332
47847
  const tmp = `${path6}.${process.pid}.${tmpCounter3 += 1}.tmp`;
47333
47848
  try {
47334
- writeFileSync25(tmp, `${JSON.stringify(existing, null, 2)}
47849
+ writeFileSync26(tmp, `${JSON.stringify(existing, null, 2)}
47335
47850
  `, { mode: 384 });
47336
47851
  renameSync13(tmp, path6);
47337
47852
  } catch {
@@ -47359,7 +47874,7 @@ function sweepTempFiles(path6) {
47359
47874
  const prefix = `${basename7(path6)}.`;
47360
47875
  for (const name of readdirSync4(dir)) {
47361
47876
  if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
47362
- const full = join46(dir, name);
47877
+ const full = join47(dir, name);
47363
47878
  try {
47364
47879
  if (Date.now() - statSync5(full).mtimeMs > 6e4) unlinkSync4(full);
47365
47880
  } catch {
@@ -47464,7 +47979,7 @@ __export(jpi_run_exports, {
47464
47979
  });
47465
47980
  import { existsSync as existsSync24, readFileSync as readFileSync31, realpathSync as realpathSync4 } from "fs";
47466
47981
  import { execFileSync as execFileSync4 } from "child_process";
47467
- import { join as join47, resolve as resolve5 } from "path";
47982
+ import { join as join48, resolve as resolve5 } from "path";
47468
47983
  import { tmpdir as tmpdir5 } from "os";
47469
47984
  import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "fs";
47470
47985
  function parseArgs2(argv) {
@@ -47489,7 +48004,7 @@ function parseArgs2(argv) {
47489
48004
  function runScratchRoot() {
47490
48005
  let root;
47491
48006
  try {
47492
- root = mkdtempSync6(join47(tmpdir5(), "th-run-"));
48007
+ root = mkdtempSync6(join48(tmpdir5(), "th-run-"));
47493
48008
  } catch (err) {
47494
48009
  process.stderr.write(
47495
48010
  `terminalhire: could not create the temporary directory this run works in, so nothing was cloned and nothing was executed. That is our environment failing, not your tests: check that the temp directory is writable. (${String(err?.message ?? err)})
@@ -47526,7 +48041,7 @@ async function loadEngine() {
47526
48041
  }
47527
48042
  }
47528
48043
  function readConfig2(localDir) {
47529
- const file = join47(localDir, ".th-run.json");
48044
+ const file = join48(localDir, ".th-run.json");
47530
48045
  if (!existsSync24(file)) return {};
47531
48046
  try {
47532
48047
  const parsed = JSON.parse(readFileSync31(file, "utf8"));
@@ -47632,6 +48147,11 @@ async function once(engine, opts) {
47632
48147
  preview: opts.preview,
47633
48148
  ...opts.placement ? { placement: opts.placement } : {},
47634
48149
  ...opts.testCommand ? { testCommandOverride: opts.testCommand } : {},
48150
+ // No `imageOrigin` beside it, deliberately. This flag runs on the DEVELOPER's machine,
48151
+ // judging the developer's work, so it takes `thrun`'s default of `developer` — the same
48152
+ // reasoning that makes `--test-command` developer-declared. An operator override is a
48153
+ // server-side caller, not this flag.
48154
+ ...opts.image ? { image: opts.image } : {},
47635
48155
  onProgress: (stage, detail) => {
47636
48156
  if (!opts.json) process.stderr.write(` ${stage.padEnd(8)} ${detail}
47637
48157
  `);
@@ -47677,8 +48197,7 @@ Expiry and revocation are not built yet (TERM-350 phase 5); Ctrl-C is the only s
47677
48197
  process.stderr.write(` total ${String(Date.now() - started)}ms
47678
48198
  `);
47679
48199
  }
47680
- if (result.status === "refused") return 2;
47681
- return result.outcome === "completed" ? 0 : 1;
48200
+ return engine.exitCodeFor(result);
47682
48201
  }
47683
48202
  async function run10() {
47684
48203
  const argv = process.argv.slice(2);
@@ -47745,6 +48264,7 @@ async function run10() {
47745
48264
  watch: parsed.bools.has("watch"),
47746
48265
  json: parsed.bools.has("json"),
47747
48266
  testCommand: pick2("test-command") ?? null,
48267
+ image: pick2("image") ?? null,
47748
48268
  keepSeconds,
47749
48269
  scratch: runScratchRoot()
47750
48270
  };
@@ -47823,6 +48343,9 @@ Options:
47823
48343
  --keep <seconds> Hold the preview open this long, at most ${String(KEEP_MAX_SECONDS)}
47824
48344
  (default: until Ctrl-C).
47825
48345
  --test-command <cmd> Disclosed override of the derived test command.
48346
+ --image <ref> Disclosed override of the container image. Use when no image is
48347
+ mapped for the repo's runtime. Recorded as developer-declared
48348
+ provenance, which the attestation refuses to sign.
47826
48349
  --help
47827
48350
 
47828
48351
  Defaults are read from .th-run.json in the local checkout when present, so a
@@ -48130,7 +48653,7 @@ function finalize(build) {
48130
48653
  };
48131
48654
  }
48132
48655
  function reconstruct(files, opts = {}) {
48133
- const join62 = opts.joinSidechains !== false;
48656
+ const join63 = opts.joinSidechains !== false;
48134
48657
  const mains = [];
48135
48658
  const sidechains = [];
48136
48659
  for (const file of files) {
@@ -48155,7 +48678,7 @@ function reconstruct(files, opts = {}) {
48155
48678
  }
48156
48679
  const orphanedSidechainPaths = [];
48157
48680
  const joinedPaths = /* @__PURE__ */ new Set();
48158
- if (join62) {
48681
+ if (join63) {
48159
48682
  const sidechainsBySession = /* @__PURE__ */ new Map();
48160
48683
  for (const sc of sidechains) {
48161
48684
  const acc = sidechainsBySession.get(sc.sessionId) ?? [];
@@ -48491,9 +49014,9 @@ __export(trajectory_exports, {
48491
49014
  runTrajectory: () => runTrajectory,
48492
49015
  runTrajectoryPush: () => runTrajectoryPush
48493
49016
  });
48494
- import { existsSync as existsSync25, readFileSync as readFileSync32, readdirSync as readdirSync5, writeFileSync as writeFileSync26 } from "fs";
49017
+ import { existsSync as existsSync25, readFileSync as readFileSync32, readdirSync as readdirSync5, writeFileSync as writeFileSync27 } from "fs";
48495
49018
  import { homedir as homedir29 } from "os";
48496
- import { join as join48 } from "path";
49019
+ import { join as join49 } from "path";
48497
49020
  function isRecord4(value) {
48498
49021
  return typeof value === "object" && value !== null && !Array.isArray(value);
48499
49022
  }
@@ -48525,7 +49048,7 @@ function findJsonlFiles(dir) {
48525
49048
  return out;
48526
49049
  }
48527
49050
  for (const entry of entries) {
48528
- const full = join48(dir, entry.name);
49051
+ const full = join49(dir, entry.name);
48529
49052
  if (entry.isDirectory()) {
48530
49053
  out.push(...findJsonlFiles(full));
48531
49054
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -48646,12 +49169,12 @@ function renderMarkdown(view) {
48646
49169
  return lines.join("\n");
48647
49170
  }
48648
49171
  function writeExportArtifacts(score, markdown) {
48649
- const dir = process.env.TERMINALHIRE_DIR || join48(homedir29(), ".terminalhire");
49172
+ const dir = process.env.TERMINALHIRE_DIR || join49(homedir29(), ".terminalhire");
48650
49173
  ensureStateDir(dir);
48651
- const jsonPath = join48(dir, "trajectory-export.json");
48652
- const mdPath = join48(dir, "trajectory-export.md");
48653
- writeFileSync26(jsonPath, JSON.stringify(score, null, 2) + "\n", "utf8");
48654
- writeFileSync26(mdPath, markdown, "utf8");
49174
+ const jsonPath = join49(dir, "trajectory-export.json");
49175
+ const mdPath = join49(dir, "trajectory-export.md");
49176
+ writeFileSync27(jsonPath, JSON.stringify(score, null, 2) + "\n", "utf8");
49177
+ writeFileSync27(mdPath, markdown, "utf8");
48655
49178
  return { jsonPath, mdPath };
48656
49179
  }
48657
49180
  function renderInward(allNodes, view, files) {
@@ -48670,7 +49193,7 @@ function renderInward(allNodes, view, files) {
48670
49193
  console.log("");
48671
49194
  }
48672
49195
  function buildTrajectory() {
48673
- const projectsDir = join48(homedir29(), ".claude", "projects");
49196
+ const projectsDir = join49(homedir29(), ".claude", "projects");
48674
49197
  if (!existsSync25(projectsDir)) return null;
48675
49198
  const paths = findJsonlFiles(projectsDir);
48676
49199
  if (paths.length === 0) return null;
@@ -49484,10 +50007,10 @@ var init_jpi_intro = __esm({
49484
50007
  });
49485
50008
 
49486
50009
  // src/chat-keystore.ts
49487
- import { existsSync as existsSync26, linkSync as linkSync2, readFileSync as readFileSync33, rmSync as rmSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync27 } from "fs";
50010
+ import { existsSync as existsSync26, linkSync as linkSync2, readFileSync as readFileSync33, rmSync as rmSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync28 } from "fs";
49488
50011
  import { randomBytes as randomBytes13 } from "crypto";
49489
50012
  import { homedir as homedir30 } from "os";
49490
- import { join as join49 } from "path";
50013
+ import { join as join50 } from "path";
49491
50014
  async function loadOrCreateIdentity() {
49492
50015
  const key = await loadKey();
49493
50016
  if (existsSync26(IDENTITY_FILE)) {
@@ -49531,7 +50054,7 @@ Recovery: if you intend to reset your chat identity, delete the file yourself an
49531
50054
  function publishIdentityBlob(blob) {
49532
50055
  const tmpFile = `${IDENTITY_FILE}.${process.pid}.${randomBytes13(6).toString("hex")}.tmp`;
49533
50056
  try {
49534
- writeFileSync27(tmpFile, JSON.stringify(blob, null, 2), {
50057
+ writeFileSync28(tmpFile, JSON.stringify(blob, null, 2), {
49535
50058
  encoding: "utf8",
49536
50059
  mode: 384,
49537
50060
  flag: "wx"
@@ -49560,16 +50083,16 @@ var init_chat_keystore = __esm({
49560
50083
  init_src();
49561
50084
  init_github_auth();
49562
50085
  init_state_dir();
49563
- TERMINALHIRE_DIR18 = process.env.TERMINALHIRE_DIR || join49(homedir30(), ".terminalhire");
49564
- IDENTITY_FILE = join49(TERMINALHIRE_DIR18, "chat-identity.enc");
50086
+ TERMINALHIRE_DIR18 = process.env.TERMINALHIRE_DIR || join50(homedir30(), ".terminalhire");
50087
+ IDENTITY_FILE = join50(TERMINALHIRE_DIR18, "chat-identity.enc");
49565
50088
  HEX64_RE = /^[0-9a-f]{64}$/;
49566
50089
  }
49567
50090
  });
49568
50091
 
49569
50092
  // src/chat-client.ts
49570
- import { existsSync as existsSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync28 } from "fs";
50093
+ import { existsSync as existsSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync29 } from "fs";
49571
50094
  import { homedir as homedir31 } from "os";
49572
- import { join as join50 } from "path";
50095
+ import { join as join51 } from "path";
49573
50096
  function defaultReadPeerPins() {
49574
50097
  try {
49575
50098
  if (!existsSync27(PEERS_FILE)) return {};
@@ -49586,7 +50109,7 @@ function defaultReadPeerPins() {
49586
50109
  }
49587
50110
  function defaultWritePeerPins(pins) {
49588
50111
  ensureStateDir(TERMINALHIRE_DIR19);
49589
- writeFileSync28(PEERS_FILE, JSON.stringify(pins, null, 2), { mode: 384, encoding: "utf8" });
50112
+ writeFileSync29(PEERS_FILE, JSON.stringify(pins, null, 2), { mode: 384, encoding: "utf8" });
49590
50113
  }
49591
50114
  function defaultChatClientDeps() {
49592
50115
  return {
@@ -49778,8 +50301,8 @@ var init_chat_client = __esm({
49778
50301
  init_api_base();
49779
50302
  CHAT_BASE = resolveApiBase();
49780
50303
  GH_SESSION_COOKIE7 = "__jpi_gh_session";
49781
- TERMINALHIRE_DIR19 = process.env.TERMINALHIRE_DIR || join50(homedir31(), ".terminalhire");
49782
- PEERS_FILE = join50(TERMINALHIRE_DIR19, "chat-peers.json");
50304
+ TERMINALHIRE_DIR19 = process.env.TERMINALHIRE_DIR || join51(homedir31(), ".terminalhire");
50305
+ PEERS_FILE = join51(TERMINALHIRE_DIR19, "chat-peers.json");
49783
50306
  REQUEST_TIMEOUT_MS2 = 1e4;
49784
50307
  ChatNotLinkedError = class extends Error {
49785
50308
  constructor(message = missingSessionLines(null).join(" ")) {
@@ -50215,9 +50738,9 @@ __export(jpi_chat_read_exports, {
50215
50738
  syncUnreadBadge: () => syncUnreadBadge,
50216
50739
  writeReadCursor: () => writeReadCursor
50217
50740
  });
50218
- import { existsSync as existsSync28, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
50741
+ import { existsSync as existsSync28, readFileSync as readFileSync35, writeFileSync as writeFileSync30 } from "fs";
50219
50742
  import { homedir as homedir32 } from "os";
50220
- import { join as join51 } from "path";
50743
+ import { join as join52 } from "path";
50221
50744
  async function syncUnreadBadge(deps = {}) {
50222
50745
  const readCookie = deps.readCookie ?? readWebSessionCookie;
50223
50746
  const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
@@ -50239,7 +50762,7 @@ async function syncUnreadBadge(deps = {}) {
50239
50762
  );
50240
50763
  const entry = JSON.parse(readFileSync35(cacheFile, "utf8"));
50241
50764
  entry.unreadChat = { count: total };
50242
- writeFileSync29(cacheFile, JSON.stringify(entry), "utf8");
50765
+ writeFileSync30(cacheFile, JSON.stringify(entry), "utf8");
50243
50766
  } catch {
50244
50767
  }
50245
50768
  }
@@ -50264,7 +50787,7 @@ function writeReadCursor(login, iso, deps = {}) {
50264
50787
  if (prev && iso <= prev) return;
50265
50788
  cursors[login] = iso;
50266
50789
  ensureStateDir(TERMINALHIRE_DIR20);
50267
- writeFileSync29(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
50790
+ writeFileSync30(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
50268
50791
  }
50269
50792
  async function postReadCursor(peerLogin, lastReadAt, deps = {}) {
50270
50793
  const readCookie = deps.readCookie ?? readWebSessionCookie;
@@ -50629,9 +51152,9 @@ var init_jpi_chat_read = __esm({
50629
51152
  init_api_base();
50630
51153
  CHAT_BASE2 = resolveApiBase();
50631
51154
  GH_SESSION_COOKIE8 = "__jpi_gh_session";
50632
- TERMINALHIRE_DIR20 = process.env.TERMINALHIRE_DIR || join51(homedir32(), ".terminalhire");
50633
- READS_FILE = join51(TERMINALHIRE_DIR20, "chat-reads.json");
50634
- INDEX_CACHE_FILE6 = join51(TERMINALHIRE_DIR20, "index-cache.json");
51155
+ TERMINALHIRE_DIR20 = process.env.TERMINALHIRE_DIR || join52(homedir32(), ".terminalhire");
51156
+ READS_FILE = join52(TERMINALHIRE_DIR20, "chat-reads.json");
51157
+ INDEX_CACHE_FILE6 = join52(TERMINALHIRE_DIR20, "index-cache.json");
50635
51158
  REACHABLE_DISPLAY = { shareActivity: false, optin: false, lastSeen: null };
50636
51159
  }
50637
51160
  });
@@ -51172,7 +51695,7 @@ __export(jpi_chat_exports, {
51172
51695
  import { createInterface as createInterface11 } from "readline";
51173
51696
  import { existsSync as existsSync29, readFileSync as readFileSync36 } from "fs";
51174
51697
  import { homedir as homedir33 } from "os";
51175
- import { join as join52 } from "path";
51698
+ import { join as join53 } from "path";
51176
51699
  function defaultPromptAck({ input = process.stdin, output = process.stdout } = {}) {
51177
51700
  if (!input || input.isTTY !== true) return Promise.resolve(false);
51178
51701
  const rl = createInterface11({ input, output });
@@ -51356,8 +51879,8 @@ function mergeMessages(existing, incoming) {
51356
51879
  }
51357
51880
  function readCachedSessionCase() {
51358
51881
  try {
51359
- const p = join52(
51360
- process.env.TERMINALHIRE_DIR || join52(homedir33(), ".terminalhire"),
51882
+ const p = join53(
51883
+ process.env.TERMINALHIRE_DIR || join53(homedir33(), ".terminalhire"),
51361
51884
  "index-cache.json"
51362
51885
  );
51363
51886
  if (!existsSync29(p)) return null;
@@ -52124,8 +52647,8 @@ function sparkline(rows) {
52124
52647
  const tail2 = sorted.slice(-24);
52125
52648
  return tail2.map((c) => SPARK_LEVELS[STATE_LEVEL[c.state] ?? 1]).join("");
52126
52649
  }
52127
- function fuzzyMatch(query, candidate) {
52128
- const q = query.toLowerCase();
52650
+ function fuzzyMatch(query2, candidate) {
52651
+ const q = query2.toLowerCase();
52129
52652
  const c = candidate.toLowerCase();
52130
52653
  if (q === "") return true;
52131
52654
  let i = 0;
@@ -52134,9 +52657,9 @@ function fuzzyMatch(query, candidate) {
52134
52657
  }
52135
52658
  return i === q.length;
52136
52659
  }
52137
- function substrMatch(query, candidate) {
52138
- if (query === "") return true;
52139
- return candidate.toLowerCase().includes(query.toLowerCase());
52660
+ function substrMatch(query2, candidate) {
52661
+ if (query2 === "") return true;
52662
+ return candidate.toLowerCase().includes(query2.toLowerCase());
52140
52663
  }
52141
52664
  function runHubTui({
52142
52665
  input = process.stdin,
@@ -53173,15 +53696,15 @@ __export(mcp_config_exports, {
53173
53696
  writeServerToFile: () => writeServerToFile
53174
53697
  });
53175
53698
  import { homedir as homedir34 } from "os";
53176
- import { join as join53 } from "path";
53177
- import { existsSync as existsSync30, readFileSync as readFileSync37, copyFileSync as copyFileSync3, writeFileSync as writeFileSync30, mkdirSync as mkdirSync11 } from "fs";
53699
+ import { join as join54 } from "path";
53700
+ import { existsSync as existsSync30, readFileSync as readFileSync37, copyFileSync as copyFileSync3, writeFileSync as writeFileSync31, mkdirSync as mkdirSync12 } from "fs";
53178
53701
  import { dirname as dirname12 } from "path";
53179
53702
  function serverEntry() {
53180
53703
  return { command: SERVER_COMMAND, args: [...SERVER_ARGS] };
53181
53704
  }
53182
53705
  function hostConfigPath(host, home = homedir34()) {
53183
53706
  if (!host || !Array.isArray(host.relPath)) return null;
53184
- return join53(home, ...host.relPath);
53707
+ return join54(home, ...host.relPath);
53185
53708
  }
53186
53709
  function jsonSnippet(host) {
53187
53710
  const entry = serverEntry();
@@ -53277,9 +53800,9 @@ function writeServerToFile(configPath, serversKey, entry = serverEntry()) {
53277
53800
  backupPath = `${configPath}.terminalhire-backup-${ts}`;
53278
53801
  copyFileSync3(configPath, backupPath);
53279
53802
  } else {
53280
- mkdirSync11(dirname12(configPath), { recursive: true });
53803
+ mkdirSync12(dirname12(configPath), { recursive: true });
53281
53804
  }
53282
- writeFileSync30(configPath, merged.text, "utf8");
53805
+ writeFileSync31(configPath, merged.text, "utf8");
53283
53806
  return { status: "written", backupPath, added: merged.added };
53284
53807
  }
53285
53808
  async function initMcpStep({
@@ -65526,9 +66049,9 @@ var require_schemes = __commonJS({
65526
66049
  wsComponent.secure = void 0;
65527
66050
  }
65528
66051
  if (wsComponent.resourceName) {
65529
- const [path6, query] = wsComponent.resourceName.split("?");
66052
+ const [path6, query2] = wsComponent.resourceName.split("?");
65530
66053
  wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
65531
- wsComponent.query = query;
66054
+ wsComponent.query = query2;
65532
66055
  wsComponent.resourceName = void 0;
65533
66056
  }
65534
66057
  wsComponent.fragment = void 0;
@@ -75835,7 +76358,7 @@ async function claimWorkspaceResult(args5 = {}) {
75835
76358
  try {
75836
76359
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
75837
76360
  const { existsSync: existsSync37, readFileSync: readFileSync43, lstatSync: lstatSync5 } = await import("fs");
75838
- const { join: join62 } = await import("path");
76361
+ const { join: join63 } = await import("path");
75839
76362
  const { BRIEF_REL_PATH: BRIEF_REL_PATH2, VERIFY_REL_PATH: VERIFY_REL_PATH2, AGENTS_REL_PATH: AGENTS_REL_PATH2, sha256OfUtf8: sha256OfUtf82 } = await Promise.resolve().then(() => (init_jpi_claim(), jpi_claim_exports));
75840
76363
  const packPaths = (c) => {
75841
76364
  const p = {};
@@ -75848,7 +76371,7 @@ async function claimWorkspaceResult(args5 = {}) {
75848
76371
  if (c.workspacePack?.[member] !== true) continue;
75849
76372
  const digest = c.packDigests?.[member];
75850
76373
  if (typeof digest !== "string" || digest === "") continue;
75851
- const abs = join62(c.worktreePath, rel);
76374
+ const abs = join63(c.worktreePath, rel);
75852
76375
  try {
75853
76376
  const st = lstatSync5(abs);
75854
76377
  if (!st.isFile() || st.size > 1024 * 1024) continue;
@@ -75867,7 +76390,7 @@ async function claimWorkspaceResult(args5 = {}) {
75867
76390
  hint: `No workspace has been delivered for this claim yet. A human runs: terminalhire claim start ${c.id} --watch`
75868
76391
  };
75869
76392
  }
75870
- if (!existsSync37(c.worktreePath) || !existsSync37(join62(c.worktreePath, ".git"))) {
76393
+ if (!existsSync37(c.worktreePath) || !existsSync37(join63(c.worktreePath, ".git"))) {
75871
76394
  return {
75872
76395
  status: "not_ready",
75873
76396
  claimId: c.id,
@@ -75985,10 +76508,10 @@ async function run17() {
75985
76508
  let version2 = "0.0.0";
75986
76509
  try {
75987
76510
  const { readFileSync: readFileSync43, existsSync: existsSync37 } = await import("fs");
75988
- const { join: join62 } = await import("path");
76511
+ const { join: join63 } = await import("path");
75989
76512
  const { fileURLToPath: fileURLToPath15 } = await import("url");
75990
76513
  const here = fileURLToPath15(new URL(".", import.meta.url));
75991
- for (const p of [join62(here, "..", "..", "package.json"), join62(here, "..", "package.json")]) {
76514
+ for (const p of [join63(here, "..", "..", "package.json"), join63(here, "..", "package.json")]) {
75992
76515
  if (existsSync37(p)) {
75993
76516
  const pkg = JSON.parse(readFileSync43(p, "utf8"));
75994
76517
  if (pkg.version) {
@@ -76289,10 +76812,10 @@ async function run18() {
76289
76812
  let version2 = "0.0.0";
76290
76813
  try {
76291
76814
  const { readFileSync: readFileSync43, existsSync: existsSync37 } = await import("fs");
76292
- const { join: join62 } = await import("path");
76815
+ const { join: join63 } = await import("path");
76293
76816
  const { fileURLToPath: fileURLToPath15 } = await import("url");
76294
76817
  const here = fileURLToPath15(new URL(".", import.meta.url));
76295
- for (const p of [join62(here, "..", "..", "package.json"), join62(here, "..", "package.json")]) {
76818
+ for (const p of [join63(here, "..", "..", "package.json"), join63(here, "..", "package.json")]) {
76296
76819
  if (existsSync37(p)) {
76297
76820
  const pkg = JSON.parse(readFileSync43(p, "utf8"));
76298
76821
  if (pkg.version) {
@@ -76853,7 +77376,7 @@ __export(signal_exports, {
76853
77376
  });
76854
77377
  import { readFileSync as readFileSync38, readdirSync as readdirSync6 } from "fs";
76855
77378
  import { execFileSync as execFileSync5 } from "child_process";
76856
- import { join as join54 } from "path";
77379
+ import { join as join55 } from "path";
76857
77380
  function safeGit(args5, cwd) {
76858
77381
  try {
76859
77382
  return execFileSync5("git", ["-C", cwd, ...args5], {
@@ -76894,7 +77417,7 @@ function readFileSafe(path6) {
76894
77417
  }
76895
77418
  }
76896
77419
  function tokensFromPackageJson(cwd) {
76897
- const pkg = readJsonSafe(join54(cwd, "package.json"));
77420
+ const pkg = readJsonSafe(join55(cwd, "package.json"));
76898
77421
  if (!pkg || typeof pkg !== "object") return [];
76899
77422
  const p = pkg;
76900
77423
  const deps = {
@@ -76908,9 +77431,9 @@ function workspaceMemberDirs(cwd) {
76908
77431
  const dirs = [cwd];
76909
77432
  for (const group of ["apps", "packages"]) {
76910
77433
  try {
76911
- const groupDir = join54(cwd, group);
77434
+ const groupDir = join55(cwd, group);
76912
77435
  for (const e of readdirSync6(groupDir, { withFileTypes: true })) {
76913
- if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join54(groupDir, e.name));
77436
+ if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join55(groupDir, e.name));
76914
77437
  }
76915
77438
  } catch {
76916
77439
  }
@@ -76918,18 +77441,18 @@ function workspaceMemberDirs(cwd) {
76918
77441
  return dirs;
76919
77442
  }
76920
77443
  function tokensFromRequirementsTxt(cwd) {
76921
- const content = readFileSafe(join54(cwd, "requirements.txt"));
77444
+ const content = readFileSafe(join55(cwd, "requirements.txt"));
76922
77445
  if (!content) return [];
76923
77446
  return content.split("\n").map((l) => l.trim().split(/[>=<!\[;]/)[0].trim().toLowerCase()).filter(Boolean);
76924
77447
  }
76925
77448
  function tokensFromGoMod(cwd) {
76926
- const content = readFileSafe(join54(cwd, "go.mod"));
77449
+ const content = readFileSafe(join55(cwd, "go.mod"));
76927
77450
  if (!content) return [];
76928
77451
  const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
76929
77452
  return ["go", ...requires];
76930
77453
  }
76931
77454
  function tokensFromCargoToml(cwd) {
76932
- const content = readFileSafe(join54(cwd, "Cargo.toml"));
77455
+ const content = readFileSafe(join55(cwd, "Cargo.toml"));
76933
77456
  if (!content) return [];
76934
77457
  const deps = [];
76935
77458
  let inDeps = false;
@@ -76950,7 +77473,7 @@ function tokensFromFileExtensions(cwd) {
76950
77473
  const tokens = [];
76951
77474
  const scanDirs = [cwd];
76952
77475
  try {
76953
- const srcDir = join54(cwd, "src");
77476
+ const srcDir = join55(cwd, "src");
76954
77477
  readdirSync6(srcDir);
76955
77478
  scanDirs.push(srcDir);
76956
77479
  } catch {
@@ -77150,7 +77673,7 @@ var jpi_config_exports = {};
77150
77673
  __export(jpi_config_exports, {
77151
77674
  run: () => run24
77152
77675
  });
77153
- import { join as join55 } from "path";
77676
+ import { join as join56 } from "path";
77154
77677
  import { homedir as homedir35 } from "os";
77155
77678
  function parseNudgeMode2(raw) {
77156
77679
  if (raw === "session" || raw === "always") return raw;
@@ -77338,8 +77861,8 @@ var init_jpi_config = __esm({
77338
77861
  "bin/jpi-config.js"() {
77339
77862
  "use strict";
77340
77863
  init_config();
77341
- TERMINALHIRE_DIR21 = process.env.TERMINALHIRE_DIR || join55(homedir35(), ".terminalhire");
77342
- CONFIG_FILE2 = join55(TERMINALHIRE_DIR21, "config.json");
77864
+ TERMINALHIRE_DIR21 = process.env.TERMINALHIRE_DIR || join56(homedir35(), ".terminalhire");
77865
+ CONFIG_FILE2 = join56(TERMINALHIRE_DIR21, "config.json");
77343
77866
  }
77344
77867
  });
77345
77868
 
@@ -77348,8 +77871,8 @@ var jpi_spinner_exports = {};
77348
77871
  __export(jpi_spinner_exports, {
77349
77872
  run: () => run25
77350
77873
  });
77351
- import { readFileSync as readFileSync39, writeFileSync as writeFileSync31, copyFileSync as copyFileSync4, existsSync as existsSync31 } from "fs";
77352
- import { join as join56 } from "path";
77874
+ import { readFileSync as readFileSync39, writeFileSync as writeFileSync32, copyFileSync as copyFileSync4, existsSync as existsSync31 } from "fs";
77875
+ import { join as join57 } from "path";
77353
77876
  import { homedir as homedir36 } from "os";
77354
77877
  import { createInterface as createInterface13 } from "readline";
77355
77878
  function readConfig3() {
@@ -77362,7 +77885,7 @@ function readConfig3() {
77362
77885
  function writeConfig2(patch) {
77363
77886
  ensureStateDir(TH_DIR);
77364
77887
  const merged = { ...readConfig3(), ...patch };
77365
- writeFileSync31(CONFIG_FILE3, JSON.stringify(merged, null, 2) + "\n", "utf8");
77888
+ writeFileSync32(CONFIG_FILE3, JSON.stringify(merged, null, 2) + "\n", "utf8");
77366
77889
  }
77367
77890
  function backupSettings() {
77368
77891
  if (!existsSync31(SETTINGS_PATH)) return null;
@@ -77596,10 +78119,10 @@ var init_jpi_spinner = __esm({
77596
78119
  "use strict";
77597
78120
  init_spinner();
77598
78121
  init_state_dir();
77599
- TH_DIR = process.env["TERMINALHIRE_DIR"] || join56(homedir36(), ".terminalhire");
77600
- CONFIG_FILE3 = join56(TH_DIR, "config.json");
77601
- SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join56(homedir36(), ".claude", "settings.json");
77602
- CACHE_FILE2 = join56(TH_DIR, "index-cache.json");
78122
+ TH_DIR = process.env["TERMINALHIRE_DIR"] || join57(homedir36(), ".terminalhire");
78123
+ CONFIG_FILE3 = join57(TH_DIR, "config.json");
78124
+ SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join57(homedir36(), ".claude", "settings.json");
78125
+ CACHE_FILE2 = join57(TH_DIR, "index-cache.json");
77603
78126
  }
77604
78127
  });
77605
78128
 
@@ -77608,8 +78131,8 @@ var jpi_sync_exports = {};
77608
78131
  __export(jpi_sync_exports, {
77609
78132
  run: () => run26
77610
78133
  });
77611
- import { readFileSync as readFileSync40, writeFileSync as writeFileSync32, existsSync as existsSync32, rmSync as rmSync16 } from "fs";
77612
- import { join as join57 } from "path";
78134
+ import { readFileSync as readFileSync40, writeFileSync as writeFileSync33, existsSync as existsSync32, rmSync as rmSync16 } from "fs";
78135
+ import { join as join58 } from "path";
77613
78136
  import { homedir as homedir37, hostname as osHostname2 } from "os";
77614
78137
  import { createInterface as createInterface14 } from "readline";
77615
78138
  function oauthSyncBase() {
@@ -77633,7 +78156,7 @@ function readMarker() {
77633
78156
  }
77634
78157
  function writeMarker(marker) {
77635
78158
  ensureStateDir(TH_DIR2);
77636
- writeFileSync32(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
78159
+ writeFileSync33(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
77637
78160
  }
77638
78161
  function clearMarker() {
77639
78162
  try {
@@ -77962,8 +78485,8 @@ var init_jpi_sync = __esm({
77962
78485
  init_open_url();
77963
78486
  init_state_dir();
77964
78487
  init_api_base();
77965
- TH_DIR2 = process.env["TERMINALHIRE_DIR"] || join57(homedir37(), ".terminalhire");
77966
- TIER1_MARKER = join57(TH_DIR2, "tier1.json");
78488
+ TH_DIR2 = process.env["TERMINALHIRE_DIR"] || join58(homedir37(), ".terminalhire");
78489
+ TIER1_MARKER = join58(TH_DIR2, "tier1.json");
77967
78490
  API_URL8 = resolveApiBase();
77968
78491
  warnSharedCredentialsIfNonProd(API_URL8);
77969
78492
  POLL_INTERVAL_MS = 2e3;
@@ -77978,30 +78501,30 @@ __export(jpi_init_exports, {
77978
78501
  run: () => run27
77979
78502
  });
77980
78503
  import { existsSync as existsSync33 } from "fs";
77981
- import { join as join58, resolve as resolve6 } from "path";
78504
+ import { join as join59, resolve as resolve6 } from "path";
77982
78505
  import { fileURLToPath as fileURLToPath10, pathToFileURL } from "url";
77983
78506
  import { createInterface as createInterface15 } from "readline";
77984
78507
  import { spawnSync as spawnSync12 } from "child_process";
77985
78508
  function resolveScript(name) {
77986
- const distPath = resolve6(join58(__dirname5, "..", "..", "dist", "bin", `${name}.js`));
77987
- const legacyPath = resolve6(join58(__dirname5, `${name}.js`));
78509
+ const distPath = resolve6(join59(__dirname5, "..", "..", "dist", "bin", `${name}.js`));
78510
+ const legacyPath = resolve6(join59(__dirname5, `${name}.js`));
77988
78511
  return existsSync33(distPath) ? distPath : legacyPath;
77989
78512
  }
77990
78513
  function resolveSrc(name) {
77991
- const distPath = resolve6(join58(__dirname5, "..", "..", "dist", "src", `${name}.js`));
77992
- const legacyPath = resolve6(join58(__dirname5, "..", "src", `${name}.js`));
78514
+ const distPath = resolve6(join59(__dirname5, "..", "..", "dist", "src", `${name}.js`));
78515
+ const legacyPath = resolve6(join59(__dirname5, "..", "src", `${name}.js`));
77993
78516
  return existsSync33(distPath) ? distPath : legacyPath;
77994
78517
  }
77995
78518
  function resolveInstallJs() {
77996
- const fromDist = resolve6(join58(__dirname5, "..", "..", "install.js"));
77997
- const fromBin = resolve6(join58(__dirname5, "..", "install.js"));
78519
+ const fromDist = resolve6(join59(__dirname5, "..", "..", "install.js"));
78520
+ const fromBin = resolve6(join59(__dirname5, "..", "install.js"));
77998
78521
  if (existsSync33(fromDist)) return fromDist;
77999
78522
  if (existsSync33(fromBin)) return fromBin;
78000
78523
  return fromBin;
78001
78524
  }
78002
78525
  function resolveStatuslineInstallJs() {
78003
- const fromDist = resolve6(join58(__dirname5, "..", "..", "statusline-install.js"));
78004
- const fromBin = resolve6(join58(__dirname5, "..", "statusline-install.js"));
78526
+ const fromDist = resolve6(join59(__dirname5, "..", "..", "statusline-install.js"));
78527
+ const fromBin = resolve6(join59(__dirname5, "..", "statusline-install.js"));
78005
78528
  if (existsSync33(fromDist)) return fromDist;
78006
78529
  if (existsSync33(fromBin)) return fromBin;
78007
78530
  return fromBin;
@@ -79006,7 +79529,7 @@ __export(jpi_save_exports, {
79006
79529
  run: () => run29
79007
79530
  });
79008
79531
  import { readFileSync as readFileSync41, existsSync as existsSync34 } from "fs";
79009
- import { join as join59 } from "path";
79532
+ import { join as join60 } from "path";
79010
79533
  import { homedir as homedir38 } from "os";
79011
79534
  import { fileURLToPath as fileURLToPath12 } from "url";
79012
79535
  function findJobInCache(jobId) {
@@ -79105,8 +79628,8 @@ var init_jpi_save = __esm({
79105
79628
  "bin/jpi-save.js"() {
79106
79629
  "use strict";
79107
79630
  __dirname7 = fileURLToPath12(new URL(".", import.meta.url));
79108
- TERMINALHIRE_DIR22 = process.env.TERMINALHIRE_DIR || join59(homedir38(), ".terminalhire");
79109
- INDEX_CACHE_FILE7 = join59(TERMINALHIRE_DIR22, "index-cache.json");
79631
+ TERMINALHIRE_DIR22 = process.env.TERMINALHIRE_DIR || join60(homedir38(), ".terminalhire");
79632
+ INDEX_CACHE_FILE7 = join60(TERMINALHIRE_DIR22, "index-cache.json");
79110
79633
  }
79111
79634
  });
79112
79635
 
@@ -79156,10 +79679,10 @@ async function run30() {
79156
79679
  console.log(" rough edges you find.");
79157
79680
  console.log(" \u2022 A spot on the founding-contributors wall.");
79158
79681
  console.log("");
79159
- const join62 = await ask5(
79682
+ const join63 = await ask5(
79160
79683
  ' Type "yes" to join the beta as a Founding Contributor (anything else cancels): '
79161
79684
  );
79162
- if ((join62 || "").toLowerCase() !== "yes") {
79685
+ if ((join63 || "").toLowerCase() !== "yes") {
79163
79686
  console.log("\n No problem \u2014 nothing was sent. Run `terminalhire beta` any time.\n");
79164
79687
  rl.close();
79165
79688
  return;
@@ -79250,13 +79773,13 @@ __export(jpi_feedback_exports, {
79250
79773
  });
79251
79774
  import { createInterface as createInterface17 } from "readline";
79252
79775
  import { readFileSync as readFileSync42, existsSync as existsSync35 } from "fs";
79253
- import { join as join60 } from "path";
79776
+ import { join as join61 } from "path";
79254
79777
  import { fileURLToPath as fileURLToPath13 } from "url";
79255
79778
  function readLocalVersion3() {
79256
79779
  try {
79257
79780
  for (const p of [
79258
- join60(__dirname8, "..", "..", "package.json"),
79259
- join60(__dirname8, "..", "package.json")
79781
+ join61(__dirname8, "..", "..", "package.json"),
79782
+ join61(__dirname8, "..", "package.json")
79260
79783
  ]) {
79261
79784
  if (existsSync35(p)) {
79262
79785
  const pkg = JSON.parse(readFileSync42(p, "utf8"));
@@ -79399,7 +79922,7 @@ var init_jpi_feedback = __esm({
79399
79922
  init_package_version();
79400
79923
  init_api_base();
79401
79924
  import { fileURLToPath as fileURLToPath14 } from "url";
79402
- import { join as join61 } from "path";
79925
+ import { join as join62 } from "path";
79403
79926
  import { existsSync as existsSync36 } from "fs";
79404
79927
 
79405
79928
  // src/state-dir-pin.ts
@@ -79499,7 +80022,7 @@ var SUBCOMMANDS = [
79499
80022
  var firstArg = process.argv[2];
79500
80023
  if (!firstArg && !process.stdin.isTTY) {
79501
80024
  const { default: childProcess } = await import("child_process");
79502
- const nudgeScript = join61(__dirname9, "jpi.js");
80025
+ const nudgeScript = join62(__dirname9, "jpi.js");
79503
80026
  const child = childProcess.spawnSync(process.execPath, [nudgeScript], {
79504
80027
  stdio: ["inherit", "inherit", "inherit"]
79505
80028
  });
@@ -80021,8 +80544,8 @@ if (firstArg === "statusline") {
80021
80544
  console.error("Usage: terminalhire statusline --on | --off");
80022
80545
  process.exit(1);
80023
80546
  }
80024
- const fromDist = join61(__dirname9, "..", "..", "statusline-install.js");
80025
- const fromBin = join61(__dirname9, "..", "statusline-install.js");
80547
+ const fromDist = join62(__dirname9, "..", "..", "statusline-install.js");
80548
+ const fromBin = join62(__dirname9, "..", "statusline-install.js");
80026
80549
  const installer = existsSync36(fromDist) ? fromDist : fromBin;
80027
80550
  const { spawnSync: spawnSync13 } = await import("child_process");
80028
80551
  const child = spawnSync13(process.execPath, uninstall ? [installer, "--uninstall"] : [installer], {