@botbuddy/cli 1.42.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.42.0",
3
+ "version": "1.43.0",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,9 +12,11 @@ import { VERSION } from "./version.mjs";
12
12
  import { machineUuid } from "./machine-id.mjs";
13
13
 
14
14
  export const SCHEMA_VERSION = 1;
15
- export const DEFAULT_PROJECTED_ENDPOINTS = 10;
16
- export const DEFAULT_WARN_PRESSURE = 192;
17
- export const DEFAULT_FAIL_PRESSURE = 224;
15
+ // BOT-2206: preflight applies policy v3's network term (BOT-2202). Docker's built-in
16
+ // default-address-pools are 172.17–31.0.0/16 as /16s (15) + 192.168.0.0/16 as /20s (16).
17
+ export const DEFAULT_NETWORK_LIMIT = 31;
18
+ // The relay's accepted upper bound for `network_limit` (Helper DockerAddressPools).
19
+ const MAX_NETWORK_LIMIT = 1_000_000;
18
20
 
19
21
  // BOT-1681 / BOT-1630 Step 3 — explicit Docker-engine allowlist. The org cut the
20
22
  // dev fleet back from OrbStack to Docker Desktop (OrbStack Helper heap runaway,
@@ -93,7 +95,8 @@ COMMANDS
93
95
  hygiene Inventory stopped Supabase containers and empty
94
96
  supabase_network_* bridge networks. This is a dry-run unless
95
97
  --apply is explicitly present.
96
- preflight Read-only network-pressure check for a prospective managed stack.
98
+ preflight Read-only check that the Docker daemon has a network for one more
99
+ managed stack (the server's admission network term).
97
100
 
98
101
  REQUIRED SELECTOR
99
102
  --context <name> Explicit Docker context, e.g. orbstack
@@ -114,10 +117,12 @@ HYGIENE OPTIONS
114
117
  Without it, no mutation.
115
118
  --json Emit exactly one compact machine-readable receipt.
116
119
 
117
- PREFLIGHT OPTIONS
118
- --projected-endpoints <n> Expected endpoints in the new stack (default ${DEFAULT_PROJECTED_ENDPOINTS})
119
- --warn-pressure <n> Warn threshold (default ${DEFAULT_WARN_PRESSURE})
120
- --fail-pressure <n> Refuse threshold (default ${DEFAULT_FAIL_PRESSURE})
120
+ PREFLIGHT
121
+ Counts user-defined bridge networks against the daemon's network limit, read
122
+ from docker info DefaultAddressPools (absent or malformed: Docker's default
123
+ ${DEFAULT_NETWORK_LIMIT}). The default bridge holds one subnet, so a start is
124
+ admitted while bridges + 1 <= limit - 1; it warns when the start would take the
125
+ last network and refuses (exit 2) when none is left. Endpoints are not scored.
121
126
  --json Emit exactly one compact machine-readable receipt.
122
127
 
123
128
  Engine allowlist (preflight): OrbStack and Docker Desktop are admitted by
@@ -169,15 +174,6 @@ const BUILTIN_NETWORKS = new Set(["bridge", "host", "none"]);
169
174
  // project's resources before its first container reaches running state.
170
175
  const ACTIVE_STATES = new Set(["created", "running", "paused", "restarting", "removing"]);
171
176
 
172
- function positiveInteger(value, option, errors, { allowZero = false } = {}) {
173
- const parsed = Number(value);
174
- if (!Number.isInteger(parsed) || parsed < (allowZero ? 0 : 1)) {
175
- errors.push(`${option} must be ${allowZero ? "a non-negative" : "a positive"} integer`);
176
- return null;
177
- }
178
- return parsed;
179
- }
180
-
181
177
  export function parseDockerArgs(argv) {
182
178
  const [command, ...rest] = argv;
183
179
  const errors = [];
@@ -189,9 +185,6 @@ export function parseDockerArgs(argv) {
189
185
  candidateIds: [],
190
186
  apply: false,
191
187
  json: false,
192
- projectedEndpoints: DEFAULT_PROJECTED_ENDPOINTS,
193
- warnPressure: DEFAULT_WARN_PRESSURE,
194
- failPressure: DEFAULT_FAIL_PRESSURE,
195
188
  };
196
189
 
197
190
  const needValue = (option, index) => {
@@ -246,30 +239,12 @@ export function parseDockerArgs(argv) {
246
239
  }
247
240
  break;
248
241
  }
249
- case "--projected-endpoints": {
250
- const value = needValue(arg, i);
251
- if (value !== null) {
252
- opts.projectedEndpoints = positiveInteger(value, arg, errors, { allowZero: true });
253
- i += 1;
254
- }
255
- break;
256
- }
257
- case "--warn-pressure": {
258
- const value = needValue(arg, i);
259
- if (value !== null) {
260
- opts.warnPressure = positiveInteger(value, arg, errors);
261
- i += 1;
262
- }
242
+ case "--projected-endpoints":
243
+ case "--warn-pressure":
244
+ case "--fail-pressure":
245
+ errors.push(`${arg} was retired (BOT-2206): preflight now checks Docker networks against the daemon's network limit, like server admission; endpoints are not scored`);
246
+ if (rest[i + 1] !== undefined && !rest[i + 1].startsWith("--")) i += 1;
263
247
  break;
264
- }
265
- case "--fail-pressure": {
266
- const value = needValue(arg, i);
267
- if (value !== null) {
268
- opts.failPressure = positiveInteger(value, arg, errors);
269
- i += 1;
270
- }
271
- break;
272
- }
273
248
  case "--apply": opts.apply = true; break;
274
249
  case "--json": opts.json = true; break;
275
250
  case "--help":
@@ -306,9 +281,6 @@ export function parseDockerArgs(argv) {
306
281
  if (!opts.lockSlot || opts.lockSlot === "default") errors.push("--apply requires a non-default --lock-slot from the owning stack lifecycle");
307
282
  if (opts.candidateIds.length === 0) errors.push("--apply requires at least one exact --candidate-id from the reviewed dry run");
308
283
  }
309
- if (opts.warnPressure !== null && opts.failPressure !== null && opts.failPressure <= opts.warnPressure) {
310
- errors.push("--fail-pressure must be greater than --warn-pressure");
311
- }
312
284
  }
313
285
 
314
286
  return { command, opts, errors };
@@ -765,6 +737,7 @@ function validateDockerEngine(runDocker, opts, allowlist = DOCKER_ENGINE_ALLOWLI
765
737
 
766
738
  return {
767
739
  selector,
740
+ networkLimit: networkLimitFromDockerInfo(info),
768
741
  receipt: {
769
742
  requested: opts.context ? { type: "context", value: opts.context } : { type: "endpoint", value: opts.endpoint },
770
743
  resolved_endpoint: resolvedEndpoint,
@@ -780,29 +753,73 @@ function validateDockerEngine(runDocker, opts, allowlist = DOCKER_ENGINE_ALLOWLI
780
753
  };
781
754
  }
782
755
 
783
- export function evaluatePressure({
784
- bridgeNetworks,
785
- attachedEndpoints,
786
- projectedEndpoints = DEFAULT_PROJECTED_ENDPOINTS,
787
- warn = DEFAULT_WARN_PRESSURE,
788
- fail = DEFAULT_FAIL_PRESSURE,
756
+ function isIPv4(address) {
757
+ const octets = address.split(".");
758
+ return octets.length === 4 && octets.every((octet) => /^(0|[1-9][0-9]{0,2})$/.test(octet) && Number(octet) <= 255);
759
+ }
760
+
761
+ /**
762
+ * BOT-2206: how many bridge networks the daemon can allocate — the Helper's
763
+ * DockerAddressPools.networkLimit (0.2.26), so preflight and the census agree. Each
764
+ * IPv4 `DefaultAddressPools` entry yields 2^(Size − prefix) subnets; IPv6 pools never
765
+ * raise it. Absent pools (Docker's built-ins report null) or ANY malformed pool is
766
+ * unknown → Docker's default 31, never a partial sum and never a refusal.
767
+ */
768
+ export function networkLimitFromDockerInfo(info) {
769
+ const fallback = { network_limit: DEFAULT_NETWORK_LIMIT, network_limit_source: "default" };
770
+ const pools = info?.DefaultAddressPools;
771
+ if (!Array.isArray(pools) || pools.length === 0) return fallback;
772
+ let total = 0;
773
+ let sawIPv4 = false;
774
+ for (const pool of pools) {
775
+ const parts = typeof pool?.Base === "string" ? pool.Base.split("/") : [];
776
+ const size = pool?.Size;
777
+ if (parts.length !== 2 || !/^[0-9]+$/.test(parts[1]) || !Number.isInteger(size)) return fallback;
778
+ const prefix = Number(parts[1]);
779
+ if (parts[0].includes(":")) {
780
+ if (prefix > 128 || size < prefix || size > 128) return fallback;
781
+ continue;
782
+ }
783
+ if (!isIPv4(parts[0]) || prefix > 32 || size < prefix || size > 32) return fallback;
784
+ sawIPv4 = true;
785
+ const bits = size - prefix;
786
+ total = Math.min(MAX_NETWORK_LIMIT, total + (bits >= 20 ? MAX_NETWORK_LIMIT : 2 ** bits));
787
+ }
788
+ return sawIPv4 && total > 0 ? { network_limit: total, network_limit_source: "daemon" } : fallback;
789
+ }
790
+
791
+ /**
792
+ * BOT-2206: policy v3's network term (BOT-2202), evaluated locally. The default
793
+ * `bridge` network holds one subnet, so one more start is admitted while
794
+ * `user bridges + 1 <= network_limit - 1`. It warns when that start takes the last
795
+ * usable network. current/projected_pressure_units keep the server's v3 meaning.
796
+ */
797
+ export function evaluateNetworks({
798
+ userBridgeNetworks,
799
+ networkLimit = DEFAULT_NETWORK_LIMIT,
800
+ networkLimitSource = "default",
789
801
  }) {
790
- const current = Number(bridgeNetworks) + (2 * Number(attachedEndpoints));
791
- const projected = current + 1 + (2 * Number(projectedEndpoints));
792
- const status = projected >= fail ? "fail" : projected >= warn ? "warn" : "ok";
802
+ const current = Number(userBridgeNetworks);
803
+ const limit = Number(networkLimit);
804
+ const usable = limit - 1;
805
+ const projected = current + 1;
806
+ const status = projected > usable ? "fail" : projected === usable ? "warn" : "ok";
793
807
  return {
794
- metric: "conservative_network_pressure_units",
795
- formula: "bridge_networks + (2 * attached_endpoints)",
808
+ metric: "docker_networks",
809
+ policy: "host_docker_pressure_v1",
810
+ policy_version: 3,
811
+ formula: "user_bridge_networks + 1 <= network_limit - 1",
812
+ network_limit: limit,
813
+ network_limit_source: networkLimitSource,
814
+ // Every network the daemon holds, including the default bridge: `networks n/limit`.
815
+ networks_in_use: current + 1,
816
+ network_free_slots: Math.max(0, usable - current),
796
817
  current_pressure_units: current,
797
818
  projected_pressure_units: projected,
798
- projected_endpoints: Number(projectedEndpoints),
799
- warn_pressure: Number(warn),
800
- fail_pressure: Number(fail),
801
819
  status,
802
- // BOT-1412: a host-wide interface-pressure refusal is not a per-stack
803
- // `supabase_local` lock wait. Emit the relay's capacity condition in the
804
- // machine receipt so callers park on the global policy and receive the
805
- // eventual capacity signal, rather than retrying Docker locally.
820
+ // BOT-1412: a host-wide refusal is not a per-stack `supabase_local` lock wait.
821
+ // Emit the relay's capacity condition so callers park on the global policy and
822
+ // receive the eventual capacity signal, rather than retrying Docker locally.
806
823
  ...(status === "fail" ? {
807
824
  wait: {
808
825
  kind: "global_capacity",
@@ -810,10 +827,15 @@ export function evaluatePressure({
810
827
  command: "bb-wait 'capacity:policy=host_docker_pressure_v1,slots>=1'",
811
828
  },
812
829
  } : {}),
813
- note: "Conservative pressure proxy; OrbStack does not expose a kernel interface ceiling through the Docker API.",
814
830
  };
815
831
  }
816
832
 
833
+ /** `networks 31/31 (default limit)` — the preflight's one-line network verdict. */
834
+ export function formatNetworks(pressure) {
835
+ const source = pressure.network_limit_source === "daemon" ? "daemon address pools" : "Docker default limit";
836
+ return `networks ${pressure.networks_in_use}/${pressure.network_limit} (${source})`;
837
+ }
838
+
817
839
  function baseReceipt(command, opts, now) {
818
840
  return {
819
841
  schema_version: SCHEMA_VERSION,
@@ -858,7 +880,7 @@ export function buildReliabilityTelemetry(receipt, { dedupeKey } = {}) {
858
880
  }
859
881
  const ghostInventory = (receipt.inventory_errors?.length ?? 0) > 0;
860
882
  // A successful target check is not a capacity measurement. If discovery
861
- // fails before evaluatePressure produces a decision, suppress the generic
883
+ // fails before evaluateNetworks produces a decision, suppress the generic
862
884
  // observation so the evidence view cannot count a zero-filled false pass.
863
885
  // Classified daemon and persistent-inventory faults remain reportable.
864
886
  if (receipt.command === "preflight" && !receipt.pressure && !unavailable && !ghostInventory) {
@@ -1108,13 +1130,12 @@ export function runDockerWorkflow(argv, {
1108
1130
  }
1109
1131
 
1110
1132
  if (parsed.command === "preflight") {
1111
- receipt.pressure = evaluatePressure({
1112
- bridgeNetworks: inventory.counts.user_bridge_networks,
1113
- attachedEndpoints: inventory.counts.attached_endpoints,
1114
- projectedEndpoints: parsed.opts.projectedEndpoints,
1115
- warn: parsed.opts.warnPressure,
1116
- fail: parsed.opts.failPressure,
1133
+ receipt.pressure = evaluateNetworks({
1134
+ userBridgeNetworks: inventory.counts.user_bridge_networks,
1135
+ networkLimit: validated.networkLimit.network_limit,
1136
+ networkLimitSource: validated.networkLimit.network_limit_source,
1117
1137
  });
1138
+ const networks = formatNetworks(receipt.pressure);
1118
1139
  // BOT-1681: dry-run hygiene is the OrbStack-only remedy. On Docker Desktop it
1119
1140
  // refuses, so the pressure warning/refusal must not tell operators to run it;
1120
1141
  // point them at the engine-aware recommendation instead.
@@ -1122,13 +1143,13 @@ export function runDockerWorkflow(argv, {
1122
1143
  if (receipt.pressure.status === "warn") {
1123
1144
  receipt.outcome = "warn";
1124
1145
  receipt.warnings.push(pressureEngine === "orbstack"
1125
- ? "Projected OrbStack network pressure is at or above the warning threshold; run dry-run hygiene before starting another stack."
1126
- : "Projected Docker network pressure is at or above the warning threshold; reduce Docker load (see recommendation) before starting another stack.");
1146
+ ? `This start takes the last free Docker network (${networks}); run dry-run hygiene before starting another stack.`
1147
+ : `This start takes the last free Docker network (${networks}); reduce Docker load (see recommendation) before starting another stack.`);
1127
1148
  } else if (receipt.pressure.status === "fail") {
1128
1149
  receipt.outcome = "refused";
1129
1150
  receipt.errors.push(pressureEngine === "orbstack"
1130
- ? "Projected OrbStack network pressure is at or above the refusal threshold; cleanup or release an owning stack before start."
1131
- : "Projected Docker network pressure is at or above the refusal threshold; release an owning stack (see recommendation) before start.");
1151
+ ? `No Docker network is free for another stack (${networks}); cleanup or release an owning stack before start.`
1152
+ : `No Docker network is free for another stack (${networks}); release an owning stack (see recommendation) before start.`);
1132
1153
  return { exitCode: EXIT.PRESSURE, receipt, json: parsed.opts.json };
1133
1154
  }
1134
1155
  return { exitCode: EXIT.OK, receipt, json: parsed.opts.json };
@@ -1275,7 +1296,7 @@ export function formatHumanReceipt(receipt) {
1275
1296
  }
1276
1297
  if (!receipt.inventory_errors?.length) lines.push(" none");
1277
1298
  if (receipt.pressure) {
1278
- lines.push(`Pressure: ${receipt.pressure.projected_pressure_units} projected (${receipt.pressure.status}; warn ${receipt.pressure.warn_pressure}, fail ${receipt.pressure.fail_pressure})`);
1299
+ lines.push(`Capacity: ${formatNetworks(receipt.pressure)} — ${receipt.pressure.status}; attached endpoints ${receipt.inventory?.attached_endpoints ?? "?"} (not scored)`);
1279
1300
  }
1280
1301
  lines.push(`Reliability telemetry: ${receipt.telemetry?.status || "not_attempted"}`);
1281
1302
  for (const warning of receipt.warnings) lines.push(`Warning: ${warning}`);
package/src/stack.mjs CHANGED
@@ -60,6 +60,9 @@ const CALLBACK_PORT_REMOTE_REFUSAL =
60
60
  "(pre-forward GatewayPorts probe). Use --local-only for a run that needs a callback port.";
61
61
  export const DEFAULT_RECEIPT_MAX_BYTES = 10240;
62
62
  export const DEFAULT_TIMEOUT_SEC = 3600;
63
+ /** BOT-2230: the bounds the control plane enforces on an owner place hold (hold_stack_lease_place). */
64
+ export const HOLD_ON_TIMEOUT_MIN_SEC = 60;
65
+ export const HOLD_ON_TIMEOUT_MAX_SEC = 14400;
63
66
  /** How often a run renews its stack directory's retention while it still exists
64
67
  * (BOT-1798): frequent enough to cover the pre-child queue/provision waits. */
65
68
  const RETENTION_REFRESH_MS = 5 * 60 * 1000;
@@ -104,6 +107,13 @@ ${bold("up OPTIONS")}
104
107
  --idle-ttl <seconds> Idle seconds before the reaper STOPS an unused stack (default 1800).
105
108
  --timeout <seconds> Max seconds to park for capacity before giving up (default ${DEFAULT_TIMEOUT_SEC}).
106
109
  --no-wait If the host is full, print the queue position and exit (don't park).
110
+ --hold-on-timeout <seconds>
111
+ BOT-2230: when the --timeout park runs out with the lease still queued,
112
+ keep its queue place for <seconds> (60–14400) instead of losing it: the
113
+ lease stays queued but is not dispatched, and re-running the same
114
+ \`stack up\` (same agent + slot) before then re-attaches to it. The exit-2
115
+ receipt carries queue_hold {held, until, queue_position}; \`stack done
116
+ <lease_id>\` gives the place up.
107
117
  --claim-only LOCAL-EXEC only: wait for and hold a provisioning lease without starting
108
118
  Supabase. Resume with the identical \`stack up --local-exec\` command.
109
119
  --wait-capacity <dur> Opt-in (BOT-2024): on a TRANSIENT HOST_CAPACITY_SOURCE_STALE refusal, park
@@ -255,7 +265,7 @@ export function parseStackArgs(argv) {
255
265
  slot: null, host: null, repo: null, ticket: null, ticketUrl: null,
256
266
  prId: null, prUrl: null, purpose: null, idleTtl: null, stackPath: ".", stackPathExplicit: false,
257
267
  timeout: DEFAULT_TIMEOUT_SEC, provisionTimeout: null, reapTimeout: 300, hardTtl: null,
258
- connectionFile: null, noWait: false, claimOnly: false, localExec: false, waitCapacity: null,
268
+ connectionFile: null, noWait: false, claimOnly: false, localExec: false, waitCapacity: null, holdOnTimeout: null,
259
269
  dockerContext: null, dockerEndpoint: null,
260
270
  // BOT-2142: execution placement (null = command default), remote-lease callback + bundle cap.
261
271
  execution: null, remote: false, localOnly: false, callbackPort: null, bundleMaxBytes: null,
@@ -356,6 +366,15 @@ export function parseStackArgs(argv) {
356
366
  }
357
367
  break;
358
368
  }
369
+ case "--hold-on-timeout": {
370
+ if (need(a, rest[i + 1])) {
371
+ const n = Number(rest[++i]);
372
+ if (!Number.isInteger(n) || n < HOLD_ON_TIMEOUT_MIN_SEC || n > HOLD_ON_TIMEOUT_MAX_SEC) {
373
+ errors.push(`--hold-on-timeout must be a whole number of seconds from ${HOLD_ON_TIMEOUT_MIN_SEC} to ${HOLD_ON_TIMEOUT_MAX_SEC}`);
374
+ } else opts.holdOnTimeout = n;
375
+ }
376
+ break;
377
+ }
359
378
  case "--no-wait": opts.noWait = true; break;
360
379
  case "--claim-only": opts.claimOnly = true; break;
361
380
  case "--local-exec": opts.localExec = true; break;
@@ -410,6 +429,11 @@ export function parseStackArgs(argv) {
410
429
  if (opts.waitCapacity != null && !["up", "run"].includes(command)) {
411
430
  errors.push("--wait-capacity is valid only with `stack up` or `stack run`");
412
431
  }
432
+ if (opts.holdOnTimeout != null && command !== "up") {
433
+ errors.push("--hold-on-timeout is valid only with `stack up`");
434
+ } else if (opts.holdOnTimeout != null && opts.noWait) {
435
+ errors.push("--hold-on-timeout cannot be combined with --no-wait: without a park there is no timeout to keep a place across");
436
+ }
413
437
  if (opts.claimOnly && (command !== "up" || !opts.localExec)) {
414
438
  errors.push("--claim-only is valid only with `stack up --local-exec`");
415
439
  }
@@ -453,6 +477,22 @@ export function describeQueuePosition(d) {
453
477
  return d?.scored_ticket_id ? `${where}, scored under ${d.scored_ticket_id}` : where;
454
478
  }
455
479
 
480
+ /**
481
+ * BOT-2230: hold a timed-out queued lease's place for its owner (`hold_stack_lease_place`).
482
+ * Never throws: a refusal or a control plane without the tool is reported as `held: false`
483
+ * with the server's code, so the caller decides (the hermetic lane then cancels as before).
484
+ */
485
+ export async function holdQueuePlace(call, leaseId, holdSecs) {
486
+ let res;
487
+ try { res = await call("hold_stack_lease_place", { lease_id: leaseId, hold_secs: holdSecs }); }
488
+ catch (err) { return { held: false, code: null, error: String(err?.message ?? err) }; }
489
+ const d = res?.data;
490
+ if (res?.ok && d?.success) {
491
+ return { held: true, until: d.queue_hold_until ?? null, queue_position: d.queue_position ?? null, hold_secs: d.hold_secs ?? holdSecs };
492
+ }
493
+ return { held: false, code: d?.code ?? null, error: d?.message || d?.error || res?.error || "hold refused" };
494
+ }
495
+
456
496
  /** BOT-2019: the queue fields a server returned for a queued lease, for a receipt. */
457
497
  /**
458
498
  * BOT-2099: the human lines for the server's unit ledger (`stack_lease_unit_ledger`) — who
@@ -2107,7 +2147,14 @@ export async function cmdUp(opts, {
2107
2147
  let ledger = d.ledger ?? null;
2108
2148
  try { const read = await call("get_stack_lease", { lease_id: leaseId }); if (read?.ok && read.data?.success && read.data.ledger) ledger = read.data.ledger; } catch { /* keep the request-time ledger */ }
2109
2149
  for (const line of describeUnitLedger(ledger)) process.stderr.write(`stack: ${line}\n`);
2110
- return emitResult(buildReceipt({ command: "up", outcome: "timeout", lease_id: leaseId, state: "queued", slot, error: `parked ${opts.timeout}s without capacity`, ...(ledger ? { unit_ledger: ledger } : {}) }), opts, EXIT.TIMEOUT);
2150
+ // BOT-2230: keep the queue place for the owner's next attempt instead of losing it.
2151
+ const queueHold = opts.holdOnTimeout ? await holdQueuePlace(call, leaseId, opts.holdOnTimeout) : null;
2152
+ if (queueHold?.held) {
2153
+ process.stderr.write(`stack: lease ${leaseId} keeps queue place ${queueHold.queue_position ?? "?"} until ${queueHold.until} — re-run the same \`stack up\` before then to re-attach (\`stack done ${leaseId}\` gives it up)\n`);
2154
+ } else if (queueHold) {
2155
+ process.stderr.write(`${yellow("⚠")} stack: lease ${leaseId} could not keep its queue place (${queueHold.code ?? "error"}: ${queueHold.error})\n`);
2156
+ }
2157
+ return emitResult(buildReceipt({ command: "up", outcome: "timeout", lease_id: leaseId, state: "queued", slot, error: `parked ${opts.timeout}s without capacity`, ...(ledger ? { unit_ledger: ledger } : {}), ...(queueHold ? { queue_hold: queueHold } : {}) }), opts, EXIT.TIMEOUT);
2111
2158
  }
2112
2159
  if (parked.auth) return emitResult(buildReceipt({ command: "up", outcome: "error", lease_id: leaseId, error: "unauthorized on wait stream" }), opts, EXIT.AUTH);
2113
2160
  if (parked.error) return emitResult(buildReceipt({ command: "up", outcome: "error", lease_id: leaseId, error: parked.error }), opts, EXIT.BACKEND);