@devrouter/cli 0.1.1 → 0.1.3

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/dist/devrouter.js CHANGED
@@ -4275,7 +4275,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4275
4275
  const config = parseConfig(parsed ?? {}, configPath);
4276
4276
  const requiredVersion = config.devrouter?.version;
4277
4277
  if (requiredVersion && !hasWarnedVersionMismatch) {
4278
- const cliVersion = true ? "0.1.1" : "0.0.0-dev";
4278
+ const cliVersion = true ? "0.1.3" : "0.0.0-dev";
4279
4279
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4280
4280
  hasWarnedVersionMismatch = true;
4281
4281
  process.stderr.write(
@@ -5263,10 +5263,11 @@ function serializeSnapshot(snapshot) {
5263
5263
  throw new Error("Capacity reservation snapshot exceeds byte limit.");
5264
5264
  return contents;
5265
5265
  }
5266
- var import_node_fs9, import_node_path11, MAX_BYTES2, CapacityHistoryError, CapacitySnapshotChangedError, CapacityStore;
5266
+ var import_node_crypto6, import_node_fs9, import_node_path11, MAX_BYTES2, CapacityHistoryError, CapacitySnapshotChangedError, CapacityStore;
5267
5267
  var init_capacity_store = __esm({
5268
5268
  "src/core/capacity-store.ts"() {
5269
5269
  "use strict";
5270
+ import_node_crypto6 = require("crypto");
5270
5271
  import_node_fs9 = __toESM(require("fs"));
5271
5272
  import_node_path11 = __toESM(require("path"));
5272
5273
  init_atomic_file();
@@ -5274,11 +5275,13 @@ var init_capacity_store = __esm({
5274
5275
  init_file_lock();
5275
5276
  MAX_BYTES2 = 1048576;
5276
5277
  CapacityHistoryError = class extends Error {
5277
- constructor(code) {
5278
+ constructor(code, cause = null, location = null) {
5278
5279
  super(
5279
5280
  code === "capacity-ledger-lost" ? "Capacity ledger history is lost; restore verified capacity-reservations.json." : "Capacity history cannot be proven; inspect the private lifecycle journals."
5280
5281
  );
5281
5282
  this.code = code;
5283
+ this.cause = cause;
5284
+ this.location = location;
5282
5285
  this.name = "CapacityHistoryError";
5283
5286
  }
5284
5287
  };
@@ -5351,9 +5354,10 @@ var init_capacity_store = __esm({
5351
5354
  const buffer = Buffer.alloc(MAX_BYTES2 + 1);
5352
5355
  const count = import_node_fs9.default.readSync(descriptor, buffer, 0, buffer.length, 0);
5353
5356
  if (count > MAX_BYTES2) throw new Error("Capacity reservation snapshot exceeds byte limit.");
5354
- const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
5357
+ const bytes2 = buffer.subarray(0, count);
5358
+ const value = JSON.parse(bytes2.toString("utf8"));
5355
5359
  validate(value);
5356
- this.ledgerGeneration = `${stat.dev}:${stat.ino}`;
5360
+ this.ledgerGeneration = this.generationIdentity(stat, bytes2);
5357
5361
  if (value.revision < minimumRevision) return { kind: "stale", revision: value.revision };
5358
5362
  this.observedLedger = true;
5359
5363
  return { kind: "intact", revision: value.revision, snapshot: value };
@@ -5406,20 +5410,30 @@ var init_capacity_store = __esm({
5406
5410
  return snapshot;
5407
5411
  }
5408
5412
  commit(snapshot) {
5409
- this.write(this.file, serializeSnapshot(snapshot));
5413
+ const contents = serializeSnapshot(snapshot);
5414
+ this.write(this.file, contents);
5410
5415
  this.observedLedger = true;
5411
- this.rememberGeneration();
5416
+ this.rememberGeneration(contents);
5412
5417
  this.establish();
5413
5418
  }
5414
5419
  /** Remember the published snapshot identity for the next generation fence. */
5415
- rememberGeneration() {
5420
+ rememberGeneration(contents) {
5416
5421
  try {
5417
5422
  const stat = import_node_fs9.default.statSync(this.file);
5418
- this.ledgerGeneration = stat.isFile() ? `${stat.dev}:${stat.ino}` : void 0;
5423
+ this.ledgerGeneration = stat.isFile() ? this.generationIdentity(stat, contents) : void 0;
5419
5424
  } catch {
5420
5425
  this.ledgerGeneration = void 0;
5421
5426
  }
5422
5427
  }
5428
+ /**
5429
+ * File identity plus content digest. Deleting and recreating the snapshot can
5430
+ * reuse its inode number, so the inode alone cannot fence a replacement that
5431
+ * reuses a revision number.
5432
+ */
5433
+ generationIdentity(stat, contents) {
5434
+ const digest3 = (0, import_node_crypto6.createHash)("sha256").update(contents).digest("hex");
5435
+ return `${stat.dev}:${stat.ino}:${digest3}`;
5436
+ }
5423
5437
  /**
5424
5438
  * Durable evidence that recorded history is gone, for a stop whose physical
5425
5439
  * cessation is already proven. The caller holds that proof; this writes no
@@ -5898,11 +5912,11 @@ function retain(session, previous) {
5898
5912
  environment: { ...environment }
5899
5913
  };
5900
5914
  }
5901
- var import_node_crypto6, import_node_fs10, import_node_path12, CONTROLLER_SNAPSHOT_BYTES, EVENT_KINDS, LEGACY_EVENT_KINDS, PROJECTION_STATUSES, ControllerStore;
5915
+ var import_node_crypto7, import_node_fs10, import_node_path12, CONTROLLER_SNAPSHOT_BYTES, EVENT_KINDS, LEGACY_EVENT_KINDS, PROJECTION_STATUSES, ControllerStore;
5902
5916
  var init_controller_store = __esm({
5903
5917
  "src/core/controller-store.ts"() {
5904
5918
  "use strict";
5905
- import_node_crypto6 = require("crypto");
5919
+ import_node_crypto7 = require("crypto");
5906
5920
  import_node_fs10 = __toESM(require("fs"));
5907
5921
  import_node_path12 = __toESM(require("path"));
5908
5922
  init_atomic_file();
@@ -5993,7 +6007,7 @@ var init_controller_store = __esm({
5993
6007
  provenance(identity2) {
5994
6008
  if (identity2.version !== 2) throw new Error("Controller binding provenance is unavailable.");
5995
6009
  return {
5996
- digest: (0, import_node_crypto6.createHash)("sha256").update(JSON.stringify(["devrouter-controller-binding-v1", identity2.store, identity2.key])).digest("hex"),
6010
+ digest: (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(["devrouter-controller-binding-v1", identity2.store, identity2.key])).digest("hex"),
5997
6011
  enrolledEpoch: identity2.enrolledEpoch
5998
6012
  };
5999
6013
  }
@@ -6017,7 +6031,7 @@ var init_controller_store = __esm({
6017
6031
  if (!current || current.store !== captured.store || current.epoch !== captured.epoch || JSON.stringify(current.fingerprint) !== JSON.stringify(captured.fingerprint) || identity2?.version !== 2)
6018
6032
  throw new Error("Controller binding incarnation changed.");
6019
6033
  this.checkProvenance(current, identity2);
6020
- return (0, import_node_crypto6.createHmac)("sha256", Buffer.from(identity2.key, "hex")).update(JSON.stringify({ owner, config })).digest("hex");
6034
+ return (0, import_node_crypto7.createHmac)("sha256", Buffer.from(identity2.key, "hex")).update(JSON.stringify({ owner, config })).digest("hex");
6021
6035
  };
6022
6036
  }
6023
6037
  read() {
@@ -6070,7 +6084,7 @@ var init_controller_store = __esm({
6070
6084
  const identity2 = existing?.version === 2 ? existing : {
6071
6085
  version: 2,
6072
6086
  store,
6073
- key: (0, import_node_crypto6.randomBytes)(32).toString("hex"),
6087
+ key: (0, import_node_crypto7.randomBytes)(32).toString("hex"),
6074
6088
  enrolledEpoch: epoch
6075
6089
  };
6076
6090
  if (existing?.version !== 2)
@@ -6116,7 +6130,7 @@ var init_controller_store = __esm({
6116
6130
  if (!previous) {
6117
6131
  previous = {
6118
6132
  version: 2,
6119
- store: (0, import_node_crypto6.randomUUID)(),
6133
+ store: (0, import_node_crypto7.randomUUID)(),
6120
6134
  epoch: 0,
6121
6135
  revision: 0,
6122
6136
  parkingRevision: 0,
@@ -6259,7 +6273,7 @@ var init_reliability_contract = __esm({
6259
6273
 
6260
6274
  // src/core/reliability-operation-store.ts
6261
6275
  function installedCliVersion() {
6262
- return true ? "0.1.1" : "0.0.0-dev";
6276
+ return true ? "0.1.3" : "0.0.0-dev";
6263
6277
  }
6264
6278
  function compareCliVersions(left, right) {
6265
6279
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -6275,7 +6289,7 @@ function identityKey(identity2) {
6275
6289
  if (!import_node_path13.default.isAbsolute(identity2.repoPath) || import_node_path13.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
6276
6290
  throw new Error("Invalid reliability workspace identity.");
6277
6291
  }
6278
- return (0, import_node_crypto7.createHash)("sha256").update(identity2.repoPath).digest("hex");
6292
+ return (0, import_node_crypto8.createHash)("sha256").update(identity2.repoPath).digest("hex");
6279
6293
  }
6280
6294
  function reliabilityOperationPath(identity2) {
6281
6295
  return import_node_path13.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
@@ -6392,6 +6406,7 @@ function validate2(record5, identity2) {
6392
6406
  "revision",
6393
6407
  "state",
6394
6408
  "worker",
6409
+ "stopWorkStarted",
6395
6410
  "effectSequence",
6396
6411
  "outcome",
6397
6412
  "result",
@@ -6573,13 +6588,24 @@ function validate2(record5, identity2) {
6573
6588
  throw new Error("Reliability worker identity is invalid.");
6574
6589
  }
6575
6590
  }
6591
+ if (record5.stopWorkStarted !== void 0 && record5.stopWorkStarted !== null) {
6592
+ if (record5.stopWorkStarted !== true) throw new Error("Invalid stop work marker.");
6593
+ }
6594
+ }
6595
+ function journalEntryLocation(name) {
6596
+ const printable = name.replace(/[^A-Za-z0-9._-]/g, "?");
6597
+ return printable.length > MAX_REPORTED_ENTRY_NAME ? `${printable.slice(0, MAX_REPORTED_ENTRY_NAME - 3)}...` : printable;
6576
6598
  }
6577
6599
  function createLifecycleCapacityStore(directory = import_node_path13.default.join(DEVROUTER_HOME, "controller")) {
6578
6600
  return new CapacityStore(directory, void 0, () => {
6579
6601
  try {
6580
6602
  return listUnsettledCapacityBindings().floor;
6581
- } catch {
6582
- throw new CapacityHistoryError("capacity-history-unprovable");
6603
+ } catch (error) {
6604
+ throw error instanceof ReliabilityJournalError ? new CapacityHistoryError("capacity-history-unprovable", error.cause, error.location) : new CapacityHistoryError(
6605
+ "capacity-history-unprovable",
6606
+ "journal-enumeration-failed",
6607
+ null
6608
+ );
6583
6609
  }
6584
6610
  });
6585
6611
  }
@@ -6638,11 +6664,12 @@ function readBoundedPrivateJson(file, label2) {
6638
6664
  try {
6639
6665
  const stat = import_node_fs11.default.fstatSync(descriptor);
6640
6666
  if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
6641
- throw new Error(`${label2} is not a bounded private file.`);
6667
+ throw new JournalFileGuardError(`${label2} is not a bounded private file.`);
6642
6668
  }
6643
6669
  const bytes2 = Buffer.alloc(MAX_RECORD_BYTES + 1);
6644
6670
  const count = import_node_fs11.default.readSync(descriptor, bytes2, 0, bytes2.length, 0);
6645
- if (count > MAX_RECORD_BYTES) throw new Error(`${label2} exceeds its byte limit.`);
6671
+ if (count > MAX_RECORD_BYTES)
6672
+ throw new JournalFileGuardError(`${label2} exceeds its byte limit.`);
6646
6673
  return JSON.parse(bytes2.subarray(0, count).toString("utf8"));
6647
6674
  } finally {
6648
6675
  import_node_fs11.default.closeSync(descriptor);
@@ -6650,7 +6677,11 @@ function readBoundedPrivateJson(file, label2) {
6650
6677
  }
6651
6678
  function assertReliabilityEnumerationTime(startedAtMs) {
6652
6679
  if (performance.now() - startedAtMs > MAX_RELIABILITY_ENUMERATION_MS)
6653
- throw new Error("Reliability journal enumeration exceeded its time limit.");
6680
+ throw new ReliabilityJournalError(
6681
+ "journal-enumeration-timeout",
6682
+ null,
6683
+ "Reliability journal enumeration exceeded its time limit."
6684
+ );
6654
6685
  }
6655
6686
  function reliabilityDirectory() {
6656
6687
  return import_node_path13.default.join(DEVROUTER_HOME, "reliability");
@@ -6671,22 +6702,56 @@ function listReliabilityOperations() {
6671
6702
  stat = import_node_fs11.default.lstatSync(directory);
6672
6703
  } catch (error) {
6673
6704
  if (error.code === "ENOENT") return [];
6674
- throw error;
6705
+ throw new ReliabilityJournalError(
6706
+ "journal-directory-unreadable",
6707
+ null,
6708
+ "Reliability journal directory could not be read."
6709
+ );
6675
6710
  }
6676
6711
  if (!stat.isDirectory() || stat.isSymbolicLink())
6677
- throw new Error("Reliability journal directory is unsafe.");
6712
+ throw new ReliabilityJournalError(
6713
+ "journal-directory-unsafe",
6714
+ null,
6715
+ "Reliability journal directory is unsafe."
6716
+ );
6678
6717
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
6679
- throw new Error("Reliability journal directory is not private.");
6718
+ throw new ReliabilityJournalError(
6719
+ "journal-directory-unsafe",
6720
+ null,
6721
+ "Reliability journal directory is not private."
6722
+ );
6680
6723
  const entries = [];
6681
- const handle = import_node_fs11.default.opendirSync(directory);
6724
+ let handle;
6725
+ try {
6726
+ handle = import_node_fs11.default.opendirSync(directory);
6727
+ } catch {
6728
+ throw new ReliabilityJournalError(
6729
+ "journal-directory-unreadable",
6730
+ null,
6731
+ "Reliability journal directory could not be read."
6732
+ );
6733
+ }
6682
6734
  try {
6683
6735
  for (; ; ) {
6684
6736
  assertReliabilityEnumerationTime(startedAtMs);
6685
- const entry = handle.readSync();
6737
+ let entry;
6738
+ try {
6739
+ entry = handle.readSync();
6740
+ } catch {
6741
+ throw new ReliabilityJournalError(
6742
+ "journal-directory-unreadable",
6743
+ null,
6744
+ "Reliability journal directory could not be read."
6745
+ );
6746
+ }
6686
6747
  if (!entry) break;
6687
6748
  entries.push(entry);
6688
6749
  if (entries.length > MAX_RELIABILITY_DIRECTORY_ENTRIES)
6689
- throw new Error("Reliability journal directory exceeds its entry limit.");
6750
+ throw new ReliabilityJournalError(
6751
+ "journal-enumeration-limit",
6752
+ null,
6753
+ "Reliability journal directory exceeds its entry limit."
6754
+ );
6690
6755
  }
6691
6756
  } finally {
6692
6757
  handle.closeSync();
@@ -6695,27 +6760,82 @@ function listReliabilityOperations() {
6695
6760
  for (const entry of entries) {
6696
6761
  assertReliabilityEnumerationTime(startedAtMs);
6697
6762
  if (entry.isSymbolicLink())
6698
- throw new Error("Reliability journal directory contains a symlink.");
6763
+ throw new ReliabilityJournalError(
6764
+ "journal-entry-unsupported",
6765
+ journalEntryLocation(entry.name),
6766
+ "Reliability journal directory contains a symlink."
6767
+ );
6699
6768
  if (!entry.isFile())
6700
- throw new Error("Reliability journal directory contains an unsupported entry.");
6769
+ throw new ReliabilityJournalError(
6770
+ "journal-entry-unsupported",
6771
+ journalEntryLocation(entry.name),
6772
+ "Reliability journal directory contains an unsupported entry."
6773
+ );
6701
6774
  if (RELIABILITY_LOCK_NAME_RE.test(entry.name) || RELIABILITY_ATOMIC_TEMP_NAME_RE.test(entry.name))
6702
6775
  continue;
6703
6776
  const match = RELIABILITY_JOURNAL_NAME_RE.exec(entry.name);
6704
- if (!match) throw new Error("Reliability journal directory contains an unsupported entry.");
6777
+ if (!match)
6778
+ throw new ReliabilityJournalError(
6779
+ "journal-entry-unsupported",
6780
+ journalEntryLocation(entry.name),
6781
+ "Reliability journal directory contains an unsupported entry."
6782
+ );
6705
6783
  journals.push({ name: entry.name, key: match[1] });
6706
6784
  }
6707
6785
  if (journals.length > MAX_RELIABILITY_JOURNALS)
6708
- throw new Error("Reliability journal directory exceeds its journal limit.");
6786
+ throw new ReliabilityJournalError(
6787
+ "journal-enumeration-limit",
6788
+ null,
6789
+ "Reliability journal directory exceeds its journal limit."
6790
+ );
6709
6791
  journals.sort((left, right) => left.name.localeCompare(right.name));
6710
6792
  const records = [];
6711
6793
  for (const journal of journals) {
6712
6794
  assertReliabilityEnumerationTime(startedAtMs);
6713
6795
  const file = import_node_path13.default.join(directory, journal.name);
6714
- const identity2 = journalIdentity(readBoundedPrivateJson(file, "Reliability journal"));
6715
- if (identityKey(identity2) !== journal.key)
6716
- throw new Error("Reliability journal filename does not match its identity.");
6717
- const record5 = readReliabilityOperation(identity2);
6718
- if (!record5) throw new Error("Reliability journal disappeared during enumeration.");
6796
+ let raw;
6797
+ try {
6798
+ raw = readBoundedPrivateJson(file, "Reliability journal");
6799
+ } catch (error) {
6800
+ const fsCode = error?.code;
6801
+ const cause = error instanceof JournalFileGuardError ? "journal-entry-unsafe" : error instanceof SyntaxError || !fsCode ? "journal-invalid" : "journal-unreadable";
6802
+ throw new ReliabilityJournalError(
6803
+ cause,
6804
+ journalEntryLocation(journal.name),
6805
+ error instanceof JournalFileGuardError ? error.message : cause === "journal-invalid" ? "Reliability journal entry is invalid." : "Reliability journal entry could not be read."
6806
+ );
6807
+ }
6808
+ if (raw === void 0)
6809
+ throw new ReliabilityJournalError(
6810
+ "journal-unstable",
6811
+ journalEntryLocation(journal.name),
6812
+ "Reliability journal disappeared during enumeration."
6813
+ );
6814
+ let identity2;
6815
+ let record5;
6816
+ try {
6817
+ identity2 = journalIdentity(raw);
6818
+ if (identityKey(identity2) !== journal.key)
6819
+ throw new ReliabilityJournalError(
6820
+ "journal-identity-mismatch",
6821
+ journalEntryLocation(journal.name),
6822
+ "Reliability journal filename does not match its identity."
6823
+ );
6824
+ record5 = readReliabilityOperation(identity2);
6825
+ } catch (error) {
6826
+ if (error instanceof ReliabilityJournalError) throw error;
6827
+ throw new ReliabilityJournalError(
6828
+ "journal-invalid",
6829
+ journalEntryLocation(journal.name),
6830
+ "Reliability journal entry is invalid."
6831
+ );
6832
+ }
6833
+ if (!record5)
6834
+ throw new ReliabilityJournalError(
6835
+ "journal-unstable",
6836
+ journalEntryLocation(journal.name),
6837
+ "Reliability journal disappeared during enumeration."
6838
+ );
6719
6839
  records.push(record5);
6720
6840
  assertReliabilityEnumerationTime(startedAtMs);
6721
6841
  }
@@ -6872,11 +6992,11 @@ function setReliabilityHumanPin(identity2, expectedJournalRevision, expectedProt
6872
6992
  };
6873
6993
  });
6874
6994
  }
6875
- var import_node_crypto7, import_node_fs11, import_node_path13, import_node_util3, MAX_RECORD_BYTES, MAX_RELIABILITY_JOURNALS, MAX_RELIABILITY_DIRECTORY_ENTRIES, MAX_RELIABILITY_ENUMERATION_MS, RELIABILITY_JOURNAL_NAME_RE, RELIABILITY_LOCK_NAME_RE, RELIABILITY_ATOMIC_TEMP_NAME_RE;
6995
+ var import_node_crypto8, import_node_fs11, import_node_path13, import_node_util3, MAX_RECORD_BYTES, MAX_RELIABILITY_JOURNALS, MAX_RELIABILITY_DIRECTORY_ENTRIES, MAX_RELIABILITY_ENUMERATION_MS, RELIABILITY_JOURNAL_NAME_RE, RELIABILITY_LOCK_NAME_RE, RELIABILITY_ATOMIC_TEMP_NAME_RE, ReliabilityJournalError, MAX_REPORTED_ENTRY_NAME, JournalFileGuardError;
6876
6996
  var init_reliability_operation_store = __esm({
6877
6997
  "src/core/reliability-operation-store.ts"() {
6878
6998
  "use strict";
6879
- import_node_crypto7 = require("crypto");
6999
+ import_node_crypto8 = require("crypto");
6880
7000
  import_node_fs11 = __toESM(require("fs"));
6881
7001
  import_node_path13 = __toESM(require("path"));
6882
7002
  import_node_util3 = require("util");
@@ -6893,6 +7013,21 @@ var init_reliability_operation_store = __esm({
6893
7013
  RELIABILITY_JOURNAL_NAME_RE = /^([0-9a-f]{64})\.json$/;
6894
7014
  RELIABILITY_LOCK_NAME_RE = /^[0-9a-f]{64}\.json\.lock(?:\.[0-9]+\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.candidate(?:\.stale)?|\.queue\.[0-9]+\.[0-9]+\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}(?:\.candidate)?)?$/;
6895
7015
  RELIABILITY_ATOMIC_TEMP_NAME_RE = /^\.[0-9a-f]{64}\.json\.[0-9]+\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/;
7016
+ ReliabilityJournalError = class extends Error {
7017
+ constructor(cause, location, message) {
7018
+ super(message);
7019
+ this.cause = cause;
7020
+ this.location = location;
7021
+ this.name = "ReliabilityJournalError";
7022
+ }
7023
+ };
7024
+ MAX_REPORTED_ENTRY_NAME = 160;
7025
+ JournalFileGuardError = class extends Error {
7026
+ constructor(message) {
7027
+ super(message);
7028
+ this.name = "JournalFileGuardError";
7029
+ }
7030
+ };
6896
7031
  }
6897
7032
  });
6898
7033
 
@@ -7258,7 +7393,7 @@ var init_managed_stop_baseline = __esm({
7258
7393
 
7259
7394
  // src/core/managed-runtime-state.ts
7260
7395
  function stateKey(repoPath, workspace) {
7261
- return (0, import_node_crypto8.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
7396
+ return (0, import_node_crypto9.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
7262
7397
  }
7263
7398
  function managedRuntimeStatePath(repoPath, workspace) {
7264
7399
  return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
@@ -7337,11 +7472,11 @@ function readManagedRuntimeState(repoPath, workspace, read2 = (file) => import_n
7337
7472
  }
7338
7473
  return validateState(parsed, repoPath, workspace);
7339
7474
  }
7340
- var import_node_crypto8, import_node_fs13, import_node_path17;
7475
+ var import_node_crypto9, import_node_fs13, import_node_path17;
7341
7476
  var init_managed_runtime_state = __esm({
7342
7477
  "src/core/managed-runtime-state.ts"() {
7343
7478
  "use strict";
7344
- import_node_crypto8 = require("crypto");
7479
+ import_node_crypto9 = require("crypto");
7345
7480
  import_node_fs13 = __toESM(require("fs"));
7346
7481
  import_node_path17 = __toESM(require("path"));
7347
7482
  init_atomic_file();
@@ -8416,7 +8551,7 @@ async function observeControllerBinding(directory, input2) {
8416
8551
  };
8417
8552
  }
8418
8553
  async function controllerRequest(directory, input2, emit, timeoutMs) {
8419
- const request2 = parseControllerRequest({ ...input2, version: 1, id: (0, import_node_crypto9.randomUUID)() });
8554
+ const request2 = parseControllerRequest({ ...input2, version: 1, id: (0, import_node_crypto10.randomUUID)() });
8420
8555
  const socketPath = import_node_path20.default.join(directory, "control.sock");
8421
8556
  const stat = import_node_fs14.default.lstatSync(socketPath);
8422
8557
  if (!stat.isSocket() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
@@ -8426,7 +8561,7 @@ async function controllerRequest(directory, input2, emit, timeoutMs) {
8426
8561
  let buffer = Buffer.alloc(0);
8427
8562
  let handshake = false;
8428
8563
  let completed = false;
8429
- const handshakeId = (0, import_node_crypto9.randomUUID)();
8564
+ const handshakeId = (0, import_node_crypto10.randomUUID)();
8430
8565
  const timeout = request2.method === "watch" || request2.method === "operation-watch" ? Math.min(request2.timeout + 5, 2147483) * 1e3 : 5e3;
8431
8566
  const timer = setTimeout(
8432
8567
  () => {
@@ -8492,11 +8627,11 @@ async function controllerRequest(directory, input2, emit, timeoutMs) {
8492
8627
  });
8493
8628
  });
8494
8629
  }
8495
- var import_node_crypto9, import_node_fs14, import_node_net, import_node_path20, CONTROLLER_OPERATION_WATCH_SECONDS, CONTROLLER_OPERATION_DEFAULT_WAIT_SECONDS, CONTROLLER_OPERATION_MAX_WAIT_SECONDS, CONTROLLER_ID_RE2, ControllerResponseDeadlineError, ControllerOperationSubmissionError;
8630
+ var import_node_crypto10, import_node_fs14, import_node_net, import_node_path20, CONTROLLER_OPERATION_WATCH_SECONDS, CONTROLLER_OPERATION_DEFAULT_WAIT_SECONDS, CONTROLLER_OPERATION_MAX_WAIT_SECONDS, CONTROLLER_ID_RE2, ControllerResponseDeadlineError, ControllerOperationSubmissionError;
8496
8631
  var init_controller_client = __esm({
8497
8632
  "src/core/controller-client.ts"() {
8498
8633
  "use strict";
8499
- import_node_crypto9 = require("crypto");
8634
+ import_node_crypto10 = require("crypto");
8500
8635
  import_node_fs14 = __toESM(require("fs"));
8501
8636
  import_node_net = __toESM(require("net"));
8502
8637
  import_node_path20 = __toESM(require("path"));
@@ -8632,11 +8767,11 @@ var init_reliability_output = __esm({
8632
8767
 
8633
8768
  // src/core/controller-monitor.ts
8634
8769
  function controllerCapability(selector) {
8635
- return selector === "runtime" ? "runtime" : `app-${(0, import_node_crypto10.createHash)("sha256").update(selector).digest("hex")}`;
8770
+ return selector === "runtime" ? "runtime" : `app-${(0, import_node_crypto11.createHash)("sha256").update(selector).digest("hex")}`;
8636
8771
  }
8637
8772
  function sessionConsumerId(input2) {
8638
8773
  const { store, epoch, session, generation } = input2;
8639
- return `session-${(0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([store, epoch, session, generation])).digest("hex")}`;
8774
+ return `session-${(0, import_node_crypto11.createHash)("sha256").update(JSON.stringify([store, epoch, session, generation])).digest("hex")}`;
8640
8775
  }
8641
8776
  function environmentIdentity(environment) {
8642
8777
  return {
@@ -8657,11 +8792,11 @@ function parkingConsumers(consumers) {
8657
8792
  consentRevision
8658
8793
  })).sort((a, b) => a.id.localeCompare(b.id));
8659
8794
  }
8660
- var import_node_crypto10, import_node_util7, ControllerObservationBindingChanged, UNFINISHED_PARK_SCAN_MS, ControllerMonitor;
8795
+ var import_node_crypto11, import_node_util7, ControllerObservationBindingChanged, UNFINISHED_PARK_SCAN_MS, ControllerMonitor;
8661
8796
  var init_controller_monitor = __esm({
8662
8797
  "src/core/controller-monitor.ts"() {
8663
8798
  "use strict";
8664
- import_node_crypto10 = require("crypto");
8799
+ import_node_crypto11 = require("crypto");
8665
8800
  import_node_util7 = require("util");
8666
8801
  init_reliability_operation_store();
8667
8802
  init_reliability_output();
@@ -8935,7 +9070,7 @@ var init_controller_monitor = __esm({
8935
9070
  const projections = /* @__PURE__ */ new Map();
8936
9071
  for (const consumer of consumers) {
8937
9072
  const transient = structuredClone(journal.state);
8938
- const consumerId = `observer-${(0, import_node_crypto10.createHash)("sha256").update(consumer.id).digest("hex")}`;
9073
+ const consumerId = `observer-${(0, import_node_crypto11.createHash)("sha256").update(consumer.id).digest("hex")}`;
8939
9074
  transient.consumers = [
8940
9075
  {
8941
9076
  id: consumerId,
@@ -9055,9 +9190,11 @@ function listDevsyWorkspaces() {
9055
9190
  );
9056
9191
  if (result.status !== 0) {
9057
9192
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
9058
- throw new Error(
9193
+ const failure2 = new Error(
9059
9194
  `devsy workspace list failed: ${details || "devsy is not installed or unavailable"}`
9060
9195
  );
9196
+ if (result.error) failure2.code = result.error.code;
9197
+ throw failure2;
9061
9198
  }
9062
9199
  let parsed;
9063
9200
  try {
@@ -9353,7 +9490,7 @@ function writeManifestRecord(cacheRoot, manifest) {
9353
9490
  import_node_fs16.default.mkdirSync(directory, { recursive: true, mode: 448 });
9354
9491
  const temporary = import_node_path22.default.join(
9355
9492
  directory,
9356
- `.${DEVSY_AGENT_MANIFEST_FILE}.${process.pid}.${(0, import_node_crypto11.randomUUID)()}.tmp`
9493
+ `.${DEVSY_AGENT_MANIFEST_FILE}.${process.pid}.${(0, import_node_crypto12.randomUUID)()}.tmp`
9357
9494
  );
9358
9495
  const record5 = {
9359
9496
  version: 1,
@@ -9451,7 +9588,7 @@ function managedBinaryPath(cacheRoot, devsyVersion, asset) {
9451
9588
  return import_node_path22.default.join(cacheRoot, `v${devsyVersion}`, asset.name);
9452
9589
  }
9453
9590
  function hashOpenFile(file) {
9454
- const digest3 = (0, import_node_crypto11.createHash)("sha256");
9591
+ const digest3 = (0, import_node_crypto12.createHash)("sha256");
9455
9592
  const buffer = Buffer.allocUnsafe(1024 * 1024);
9456
9593
  let position = 0;
9457
9594
  for (; ; ) {
@@ -9647,7 +9784,7 @@ async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloade
9647
9784
  import_node_fs16.default.mkdirSync(directory, { recursive: true, mode: 448 });
9648
9785
  const temporaryPath = import_node_path22.default.join(
9649
9786
  directory,
9650
- `.${import_node_path22.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto11.randomUUID)()}.tmp`
9787
+ `.${import_node_path22.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto12.randomUUID)()}.tmp`
9651
9788
  );
9652
9789
  let handle;
9653
9790
  try {
@@ -9659,7 +9796,7 @@ async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloade
9659
9796
  directFailure = error;
9660
9797
  }
9661
9798
  handle = await import_node_fs16.default.promises.open(temporaryPath, "wx", 384);
9662
- const digest3 = (0, import_node_crypto11.createHash)("sha256");
9799
+ const digest3 = (0, import_node_crypto12.createHash)("sha256");
9663
9800
  let size = 0;
9664
9801
  const writeChunk = async (chunk) => {
9665
9802
  size += chunk.byteLength;
@@ -9795,12 +9932,12 @@ async function prepareDevsyAgent(options = {}) {
9795
9932
  return { ...preparedFromInspection(published), changed: true, transport };
9796
9933
  });
9797
9934
  }
9798
- var import_node_child_process7, import_node_crypto11, import_node_fs16, import_node_path22, PINNED_DEVSY_AGENT_VERSION, SUPPORTED_DEVSY_MIN_VERSION, SUPPORTED_DEVSY_MAX_VERSION, SUPPORTED_DEVSY_RANGE, DEVSY_AGENT_SETUP_COMMAND, DEVSY_RELEASE_TAG_PREFIX, DEVSY_RELEASE_METADATA_BASE_URL, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, DEVSY_AGENT_MANIFEST_FILE, DEVSY_AGENT_ASSET_NAMES, GITHUB_CLI_STDERR_TAIL_BYTES;
9935
+ var import_node_child_process7, import_node_crypto12, import_node_fs16, import_node_path22, PINNED_DEVSY_AGENT_VERSION, SUPPORTED_DEVSY_MIN_VERSION, SUPPORTED_DEVSY_MAX_VERSION, SUPPORTED_DEVSY_RANGE, DEVSY_AGENT_SETUP_COMMAND, DEVSY_RELEASE_TAG_PREFIX, DEVSY_RELEASE_METADATA_BASE_URL, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, DEVSY_AGENT_MANIFEST_FILE, DEVSY_AGENT_ASSET_NAMES, GITHUB_CLI_STDERR_TAIL_BYTES;
9799
9936
  var init_devsy_agent = __esm({
9800
9937
  "src/core/devsy-agent.ts"() {
9801
9938
  "use strict";
9802
9939
  import_node_child_process7 = require("child_process");
9803
- import_node_crypto11 = require("crypto");
9940
+ import_node_crypto12 = require("crypto");
9804
9941
  import_node_fs16 = __toESM(require("fs"));
9805
9942
  import_node_path22 = __toESM(require("path"));
9806
9943
  init_file_lock();
@@ -10037,7 +10174,7 @@ function isWildcard(values) {
10037
10174
  return values?.length === 1 && values[0] === "*";
10038
10175
  }
10039
10176
  function sha256(contents) {
10040
- return (0, import_node_crypto12.createHash)("sha256").update(contents, "utf-8").digest("hex");
10177
+ return (0, import_node_crypto13.createHash)("sha256").update(contents, "utf-8").digest("hex");
10041
10178
  }
10042
10179
  function safeComposeProject(value) {
10043
10180
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
@@ -10123,6 +10260,9 @@ ${JSON.stringify(effective, null, 2)}
10123
10260
  contents
10124
10261
  };
10125
10262
  }
10263
+ function writeManagedDevcontainerConfig(plan) {
10264
+ writeFileAtomically(plan.generatedPath, plan.contents);
10265
+ }
10126
10266
  function inspectManagedDevcontainerGeneratedConfig(plan) {
10127
10267
  let stat;
10128
10268
  try {
@@ -10260,12 +10400,12 @@ function stopExactManagedService(containerId, service, options) {
10260
10400
  throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
10261
10401
  }
10262
10402
  }
10263
- var import_node_child_process8, import_node_crypto12, import_node_fs18, import_node_path25, import_yaml3, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER, COMPOSE_HASH_TIMEOUT_MS;
10403
+ var import_node_child_process8, import_node_crypto13, import_node_fs18, import_node_path25, import_yaml3, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER, COMPOSE_HASH_TIMEOUT_MS;
10264
10404
  var init_devcontainer_profile = __esm({
10265
10405
  "src/core/devcontainer-profile.ts"() {
10266
10406
  "use strict";
10267
10407
  import_node_child_process8 = require("child_process");
10268
- import_node_crypto12 = require("crypto");
10408
+ import_node_crypto13 = require("crypto");
10269
10409
  import_node_fs18 = __toESM(require("fs"));
10270
10410
  import_node_path25 = __toESM(require("path"));
10271
10411
  import_yaml3 = __toESM(require("yaml"));
@@ -10291,7 +10431,11 @@ function listDevpodWorkspacesRaw(options = {}) {
10291
10431
  }
10292
10432
  if (result.error || result.status !== 0) {
10293
10433
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
10294
- throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
10434
+ const failure2 = new Error(
10435
+ `devpod list failed: ${details || "devpod is not installed or unavailable"}`
10436
+ );
10437
+ if (result.error) failure2.code = result.error.code;
10438
+ throw failure2;
10295
10439
  }
10296
10440
  let parsed;
10297
10441
  try {
@@ -10583,25 +10727,23 @@ function writeWorkspaceRuntimeConfig(config) {
10583
10727
  writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
10584
10728
  `);
10585
10729
  }
10730
+ function readRegistrySnapshot(runtime, read2, snapshots) {
10731
+ try {
10732
+ return read2();
10733
+ } catch (error) {
10734
+ if (error?.code === "ENOENT") return void 0;
10735
+ snapshots.unavailable.push(runtime);
10736
+ snapshots.errors[runtime] = error instanceof Error ? error.message : String(error);
10737
+ return void 0;
10738
+ }
10739
+ }
10586
10740
  function getWorkspaceRegistrySnapshots() {
10587
10741
  if (cachedSnapshots) return cachedSnapshots;
10588
10742
  const snapshots = { unavailable: [], errors: {} };
10589
- if (isRuntimeInstalled("devsy")) {
10590
- try {
10591
- snapshots.devsy = listDevsyWorkspaces();
10592
- } catch (error) {
10593
- snapshots.unavailable.push("devsy");
10594
- snapshots.errors.devsy = error instanceof Error ? error.message : String(error);
10595
- }
10596
- }
10597
- if (isRuntimeInstalled("devpod")) {
10598
- try {
10599
- snapshots.devpod = listDevpodWorkspacesRaw();
10600
- } catch (error) {
10601
- snapshots.unavailable.push("devpod");
10602
- snapshots.errors.devpod = error instanceof Error ? error.message : String(error);
10603
- }
10604
- }
10743
+ const devsy = readRegistrySnapshot("devsy", listDevsyWorkspaces, snapshots);
10744
+ if (devsy !== void 0) snapshots.devsy = devsy;
10745
+ const devpod = readRegistrySnapshot("devpod", listDevpodWorkspacesRaw, snapshots);
10746
+ if (devpod !== void 0) snapshots.devpod = devpod;
10605
10747
  cachedSnapshots = snapshots;
10606
10748
  return snapshots;
10607
10749
  }
@@ -11380,11 +11522,19 @@ function absentRegistrationIdentity(state) {
11380
11522
  resetWorkspaceRuntimeCaches();
11381
11523
  if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
11382
11524
  throw new Error("Stop provider changed.");
11383
- if (!state.workspace || !isLinkedWorktree(state.repoPath))
11384
- throw new Error("Absent stop requires a linked workspace.");
11385
- const record5 = readWorkspaceOwnership(state.repoPath, state.workspace);
11386
- if (!record5 || record5.workspace !== state.workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
11387
- throw new Error("Absent stop workspace ownership changed.");
11525
+ const linked = isLinkedWorktree(state.repoPath);
11526
+ if (linked !== (state.workspace !== void 0))
11527
+ throw new Error("Absent stop requires the exact workspace identity.");
11528
+ let identity2;
11529
+ if (linked) {
11530
+ const workspace = state.workspace;
11531
+ const record5 = readWorkspaceOwnership(state.repoPath, workspace);
11532
+ if (!record5 || record5.workspace !== workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
11533
+ throw new Error("Absent stop workspace ownership changed.");
11534
+ identity2 = { kind: "linked", record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
11535
+ } else {
11536
+ identity2 = { kind: "primary", repoPath: state.repoPath, devpodId: state.devpodId };
11537
+ }
11388
11538
  for (const entries of [
11389
11539
  listDevsyWorkspaces(),
11390
11540
  listDevpodWorkspacesRaw({ allowMissingExecutable: true })
@@ -11394,7 +11544,7 @@ function absentRegistrationIdentity(state) {
11394
11544
  ))
11395
11545
  throw new Error("Absent stop requires both provider registrations to remain absent.");
11396
11546
  }
11397
- return { record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
11547
+ return identity2;
11398
11548
  }
11399
11549
  function replacementObservation(state) {
11400
11550
  resetWorkspaceRuntimeCaches();
@@ -11504,7 +11654,7 @@ function proveManagedStop(state) {
11504
11654
  const identityKeys = ["context", "providerId", "uid", "sourcePath", "sourceContainer"];
11505
11655
  const current = identityOf(owner.workspace);
11506
11656
  const changed = identityKeys.filter((key) => baseline[key] !== current[key]);
11507
- if (changed.length === 0 && state.workspace !== void 0 && isLinkedWorktree(state.repoPath) && inspectManagedStopContainers(baseline.project, baseline.endpoint).length === 0) {
11657
+ if (changed.length === 0 && inspectManagedStopContainers(baseline.project, baseline.endpoint).length === 0) {
11508
11658
  proveAbsentPopulationStop(state, baseline, current);
11509
11659
  return { status: "proven-absent", containers: [] };
11510
11660
  }
@@ -11751,7 +11901,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
11751
11901
  return normalizeUniqueHosts(names);
11752
11902
  }
11753
11903
  function parseCertificateDnsHosts(pem) {
11754
- const certificate = new import_node_crypto13.X509Certificate(pem);
11904
+ const certificate = new import_node_crypto14.X509Certificate(pem);
11755
11905
  const subjectAltName = certificate.subjectAltName ?? "";
11756
11906
  if (subjectAltName.length === 0) {
11757
11907
  return [];
@@ -11951,12 +12101,12 @@ Run: ${tlsSetupCommand(options.repoPath)}`
11951
12101
  );
11952
12102
  }
11953
12103
  }
11954
- var import_node_child_process14, import_node_crypto13, import_node_fs23, import_node_path31, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
12104
+ var import_node_child_process14, import_node_crypto14, import_node_fs23, import_node_path31, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
11955
12105
  var init_tls = __esm({
11956
12106
  "src/core/tls.ts"() {
11957
12107
  "use strict";
11958
12108
  import_node_child_process14 = require("child_process");
11959
- import_node_crypto13 = require("crypto");
12109
+ import_node_crypto14 = require("crypto");
11960
12110
  import_node_fs23 = __toESM(require("fs"));
11961
12111
  import_node_path31 = __toESM(require("path"));
11962
12112
  init_docker();
@@ -12455,6 +12605,41 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
12455
12605
  throw new Error("Initial managed stop left a workload running.");
12456
12606
  return true;
12457
12607
  }
12608
+ function restoreRecordedManagedDevcontainerConfig(repoPath) {
12609
+ const plan = recordedManagedDevcontainerRestorePlan(repoPath);
12610
+ if (!plan) return false;
12611
+ writeManagedDevcontainerConfig(plan);
12612
+ if (inspectManagedDevcontainerGeneratedConfig(plan).status !== "valid") {
12613
+ throw new Error(
12614
+ `Managed Dev Container path '${plan.generatedPath}' could not be restored from the recorded state.`
12615
+ );
12616
+ }
12617
+ return true;
12618
+ }
12619
+ function recordedManagedDevcontainerRestorePlan(repoPath) {
12620
+ try {
12621
+ if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return void 0;
12622
+ const linked = isLinkedWorktree(repoPath);
12623
+ const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
12624
+ const state = readManagedRuntimeState(repoPath, workspace);
12625
+ if (!state) return void 0;
12626
+ const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
12627
+ if (!runtime.config.managedRuntime || runtime.profile !== state.profile || runtime.workspace !== workspace)
12628
+ return void 0;
12629
+ const plan = inspectManagedDevcontainerConfig({
12630
+ repoPath,
12631
+ config: runtime.config,
12632
+ profile: runtime.resolvedProfile,
12633
+ linked
12634
+ });
12635
+ if (inspectManagedDevcontainerGeneratedConfig(plan).status !== "missing") return void 0;
12636
+ if (plan.sourceConfigSha256 !== state.sourceConfigSha256 || plan.effectiveConfigSha256 !== state.effectiveConfigSha256)
12637
+ return void 0;
12638
+ return plan;
12639
+ } catch {
12640
+ return void 0;
12641
+ }
12642
+ }
12458
12643
  function stopRetainedManagedDevsyWorkspace(options) {
12459
12644
  const { repoPath, devsyId } = options;
12460
12645
  const linked = isLinkedWorktree(repoPath);
@@ -13515,7 +13700,7 @@ function validateRoot(root) {
13515
13700
  return resolved;
13516
13701
  }
13517
13702
  function hashKey(value) {
13518
- return (0, import_node_crypto14.createHash)("sha256").update(value).digest("hex");
13703
+ return (0, import_node_crypto15.createHash)("sha256").update(value).digest("hex");
13519
13704
  }
13520
13705
  function cloneFence(fence) {
13521
13706
  return { ...fence };
@@ -13538,11 +13723,11 @@ function isErrno2(error, code) {
13538
13723
  function claimError(code, message) {
13539
13724
  return new NetworkClaimError(code, message);
13540
13725
  }
13541
- var import_node_crypto14, import_node_fs27, import_node_path35, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
13726
+ var import_node_crypto15, import_node_fs27, import_node_path35, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
13542
13727
  var init_network_claims = __esm({
13543
13728
  "src/core/network-claims.ts"() {
13544
13729
  "use strict";
13545
- import_node_crypto14 = require("crypto");
13730
+ import_node_crypto15 = require("crypto");
13546
13731
  import_node_fs27 = __toESM(require("fs"));
13547
13732
  import_node_path35 = __toESM(require("path"));
13548
13733
  init_atomic_file();
@@ -13595,7 +13780,7 @@ var init_network_claims = __esm({
13595
13780
  function networkOwnerKey(repoPath) {
13596
13781
  if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
13597
13782
  throw new Error("Invalid network owner path.");
13598
- return (0, import_node_crypto15.createHash)("sha256").update(repoPath).digest("hex");
13783
+ return (0, import_node_crypto16.createHash)("sha256").update(repoPath).digest("hex");
13599
13784
  }
13600
13785
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13601
13786
  let names;
@@ -13635,7 +13820,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13635
13820
  } finally {
13636
13821
  import_node_fs28.default.closeSync(descriptor);
13637
13822
  }
13638
- if (typeof value.daemonId !== "string" || `${(0, import_node_crypto15.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
13823
+ if (typeof value.daemonId !== "string" || `${(0, import_node_crypto16.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
13639
13824
  throw new Error("Retained network daemon identity is invalid.");
13640
13825
  const claim2 = readNetworkClaim({
13641
13826
  daemonId: value.daemonId,
@@ -13649,11 +13834,11 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13649
13834
  }
13650
13835
  return found;
13651
13836
  }
13652
- var import_node_crypto15, import_node_fs28, import_node_path36;
13837
+ var import_node_crypto16, import_node_fs28, import_node_path36;
13653
13838
  var init_network_claim_lookup = __esm({
13654
13839
  "src/core/network-claim-lookup.ts"() {
13655
13840
  "use strict";
13656
- import_node_crypto15 = require("crypto");
13841
+ import_node_crypto16 = require("crypto");
13657
13842
  import_node_fs28 = __toESM(require("fs"));
13658
13843
  import_node_path36 = __toESM(require("path"));
13659
13844
  init_network_claims();
@@ -13680,7 +13865,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
13680
13865
  if (definition.name !== "docker" || ![true, "true"].includes(agent.local) || agent.driver !== void 0 && agent.driver !== "docker" && agent.driver !== "" || docker.path !== "${DOCKER_PATH}" || docker.builder !== "${DOCKER_BUILDER}" || ![false, "false"].includes(docker.install) || (provider === "devsy" ? docker.elevation !== "${DOCKER_ELEVATION}" : docker.elevation !== void 0 && docker.elevation !== "") || env.DOCKER_HOST !== "${DOCKER_HOST}" || Object.keys(env).some((key) => key !== "DOCKER_HOST") || host.global !== true || dockerPath.default !== "docker" || actualCommand.length !== 1 || typeof actualCommand[0] !== "string" || actualCommand[0].trim() !== command || Object.keys(exec).some((key) => key !== "command") || agent.path !== void 0 && agent.path !== "" || agent.downloadURL !== void 0 && agent.downloadURL !== "") {
13681
13866
  throw new Error("Docker provider definition is not qualified for network allocation.");
13682
13867
  }
13683
- return (0, import_node_crypto16.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
13868
+ return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
13684
13869
  }
13685
13870
  function read(provider, args) {
13686
13871
  const result = (0, import_node_child_process16.spawnSync)(provider, args, {
@@ -13772,12 +13957,12 @@ function inspectNetworkProviderBinding(input2) {
13772
13957
  );
13773
13958
  }
13774
13959
  }
13775
- var import_node_child_process16, import_node_crypto16;
13960
+ var import_node_child_process16, import_node_crypto17;
13776
13961
  var init_network_provider_inspect = __esm({
13777
13962
  "src/core/network-provider-inspect.ts"() {
13778
13963
  "use strict";
13779
13964
  import_node_child_process16 = require("child_process");
13780
- import_node_crypto16 = require("crypto");
13965
+ import_node_crypto17 = require("crypto");
13781
13966
  init_devpod_environment();
13782
13967
  init_devsy_agent();
13783
13968
  }
@@ -13853,6 +14038,7 @@ function inspectExactOwnership(devsyId, worktreePath) {
13853
14038
  }
13854
14039
  function mutateOwnedDevsyWorkspace(action2, devsyId, worktreePath) {
13855
14040
  return withMutationLock(`Devsy ${action2}`, worktreePath, () => {
14041
+ restoreRecordedManagedDevcontainerConfig(worktreePath);
13856
14042
  if (action2 === "stop") {
13857
14043
  const stopped = stopRetainedManagedDevsyWorkspace({
13858
14044
  repoPath: worktreePath,
@@ -14261,7 +14447,7 @@ function handleOperationRequest(state, event) {
14261
14447
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
14262
14448
  return blocked(
14263
14449
  state,
14264
- `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish.`
14450
+ `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish, or when no stop worker is running rerun 'devrouter stop <path> --delete', removing the exact provider registration first when the stop cannot prove the recorded population.`
14265
14451
  );
14266
14452
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
14267
14453
  return blocked(
@@ -15095,14 +15281,14 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
15095
15281
  }
15096
15282
  }
15097
15283
  function newLifecycleIds() {
15098
- return { requestId: (0, import_node_crypto17.randomUUID)(), operationId: (0, import_node_crypto17.randomUUID)(), workerId: (0, import_node_crypto17.randomUUID)() };
15284
+ return { requestId: (0, import_node_crypto18.randomUUID)(), operationId: (0, import_node_crypto18.randomUUID)(), workerId: (0, import_node_crypto18.randomUUID)() };
15099
15285
  }
15100
- var import_node_child_process18, import_node_crypto17, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
15286
+ var import_node_child_process18, import_node_crypto18, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
15101
15287
  var init_reliability_worker = __esm({
15102
15288
  "src/core/reliability-worker.ts"() {
15103
15289
  "use strict";
15104
15290
  import_node_child_process18 = require("child_process");
15105
- import_node_crypto17 = require("crypto");
15291
+ import_node_crypto18 = require("crypto");
15106
15292
  import_node_fs30 = __toESM(require("fs"));
15107
15293
  import_node_path38 = __toESM(require("path"));
15108
15294
  init_file_lock();
@@ -15790,7 +15976,7 @@ function readRegularFile(filePath) {
15790
15976
  return readRegularFileBytes(filePath)?.toString("utf-8");
15791
15977
  }
15792
15978
  function adapterFingerprint(adapter) {
15793
- return (0, import_node_crypto18.createHash)("sha256").update(adapter).digest("hex");
15979
+ return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
15794
15980
  }
15795
15981
  function resolveManagedPostStartPlan(repoPath) {
15796
15982
  const adapterPath = import_node_path41.default.join(repoPath, MANAGED_ADAPTER_PATH);
@@ -15880,12 +16066,12 @@ function runManagedProcessAction(options) {
15880
16066
  }
15881
16067
  return "drifted";
15882
16068
  }
15883
- var import_node_child_process23, import_node_crypto18, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
16069
+ var import_node_child_process23, import_node_crypto19, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
15884
16070
  var init_managed_post_start = __esm({
15885
16071
  "src/core/managed-post-start.ts"() {
15886
16072
  "use strict";
15887
16073
  import_node_child_process23 = require("child_process");
15888
- import_node_crypto18 = require("crypto");
16074
+ import_node_crypto19 = require("crypto");
15889
16075
  import_node_fs33 = __toESM(require("fs"));
15890
16076
  import_node_path41 = __toESM(require("path"));
15891
16077
  init_devcontainer_config();
@@ -15896,26 +16082,132 @@ var init_managed_post_start = __esm({
15896
16082
  }
15897
16083
  });
15898
16084
 
16085
+ // src/core/managed-mount-nesting.ts
16086
+ function normalizeDestination(value) {
16087
+ const trimmed = value.trim().replace(/\/+$/, "");
16088
+ return trimmed === "" ? "/" : trimmed;
16089
+ }
16090
+ function isNestedWithin(destination, candidate) {
16091
+ return candidate === "/" ? destination !== "/" : destination.startsWith(`${candidate}/`);
16092
+ }
16093
+ function unescapeMountField(value) {
16094
+ return value.replace(
16095
+ /\\([0-7]{3})/g,
16096
+ (_match, octal) => String.fromCharCode(Number.parseInt(octal, 8))
16097
+ );
16098
+ }
16099
+ function parseMountinfoDestinations(text4) {
16100
+ const destinations = /* @__PURE__ */ new Set();
16101
+ for (const line of text4.split("\n")) {
16102
+ const fields2 = line.split(" ");
16103
+ if (fields2.length < 5 || !/^\d+$/.test(fields2[0] ?? "")) continue;
16104
+ destinations.add(normalizeDestination(unescapeMountField(fields2[4] ?? "")));
16105
+ }
16106
+ return [...destinations];
16107
+ }
16108
+ function isNestedIndex(destinations, index) {
16109
+ return destinations.some(
16110
+ (candidate, other) => other !== index && isNestedWithin(destinations[index] ?? "", candidate)
16111
+ );
16112
+ }
16113
+ function configuredDestinations(configured) {
16114
+ return configured.map((mount) => normalizeDestination(mount.Destination));
16115
+ }
16116
+ function classifyManagedMountNesting(configured, effectiveDestinations, containerId) {
16117
+ const destinations = configuredDestinations(configured);
16118
+ const nested = configured.filter((_mount, index) => isNestedIndex(destinations, index));
16119
+ const identity2 = containerId ? { container: containerId } : {};
16120
+ if (nested.length === 0) {
16121
+ return { status: "not-applicable", checked: [], unwound: [], reason: NESTED_MOUNT_REASON };
16122
+ }
16123
+ if (effectiveDestinations === null) {
16124
+ return {
16125
+ status: "unverified",
16126
+ checked: [],
16127
+ unwound: [],
16128
+ ...identity2,
16129
+ reason: UNREADABLE_MOUNT_REASON
16130
+ };
16131
+ }
16132
+ const effective = new Set(effectiveDestinations.map(normalizeDestination));
16133
+ const checked = [];
16134
+ const unwound = [];
16135
+ for (const mount of nested) {
16136
+ const destination = normalizeDestination(mount.Destination);
16137
+ if (effective.has(destination)) {
16138
+ checked.push(mount.Destination);
16139
+ continue;
16140
+ }
16141
+ const nestedWithin = destinations.filter((candidate) => isNestedWithin(destination, candidate)).sort((a, b) => a.length - b.length)[0];
16142
+ unwound.push({
16143
+ destination: mount.Destination,
16144
+ source: mount.Source,
16145
+ nestedWithin: nestedWithin ?? "/"
16146
+ });
16147
+ }
16148
+ return {
16149
+ status: unwound.length > 0 ? "unwound" : "effective",
16150
+ checked,
16151
+ unwound,
16152
+ ...identity2
16153
+ };
16154
+ }
16155
+ function readContainerMountinfo(containerId, options) {
16156
+ const result = (0, import_node_child_process24.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
16157
+ encoding: "utf-8",
16158
+ timeout: options.timeoutMs ?? 5e3,
16159
+ stdio: ["ignore", "pipe", "pipe"],
16160
+ ...networkDockerOptions()
16161
+ });
16162
+ if (result.status !== 0 || typeof result.stdout !== "string") return null;
16163
+ const destinations = parseMountinfoDestinations(result.stdout);
16164
+ return destinations.length > 0 ? destinations : null;
16165
+ }
16166
+ function observeManagedMountNesting(container, options = {}) {
16167
+ const destinations = configuredDestinations(container.mounts);
16168
+ const effective = destinations.some((_destination, index) => isNestedIndex(destinations, index)) ? readContainerMountinfo(container.id, options) : null;
16169
+ return classifyManagedMountNesting(container.mounts, effective, container.id);
16170
+ }
16171
+ var import_node_child_process24, NESTED_MOUNT_REASON, UNREADABLE_MOUNT_REASON;
16172
+ var init_managed_mount_nesting = __esm({
16173
+ "src/core/managed-mount-nesting.ts"() {
16174
+ "use strict";
16175
+ import_node_child_process24 = require("child_process");
16176
+ init_network_effect_scope();
16177
+ NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
16178
+ UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
16179
+ }
16180
+ });
16181
+
15899
16182
  // src/core/managed-runtime-status.ts
16183
+ function recoveryPath(repoPath) {
16184
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(repoPath)) return repoPath;
16185
+ return `'${repoPath.replaceAll("'", "'\\''")}'`;
16186
+ }
15900
16187
  function reliabilityRecovery(reason, repoPath) {
16188
+ const checkout = recoveryPath(repoPath);
15901
16189
  switch (reason) {
15902
16190
  case "stop-incomplete":
15903
16191
  case "operation-unknown":
15904
- return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
16192
+ return [`Run: devrouter stop ${checkout}`, `Run: devrouter ensure ${checkout} --repair`];
15905
16193
  case "start-refused":
15906
16194
  return [
15907
- `Run: devrouter doctor ${repoPath} to read the refused fixed host-port claim and its holder`,
15908
- `Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${repoPath}`
16195
+ `Run: devrouter doctor --repo ${checkout} to read the refused fixed host-port claim and its holder`,
16196
+ `Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${checkout}`
15909
16197
  ];
15910
16198
  case "unadmittable":
15911
16199
  return [
15912
- `Run: devrouter stop ${repoPath}`,
15913
- `Lower the requested capacity in .devrouter.yml, then run: devrouter ensure ${repoPath}`
16200
+ `Run: devrouter stop ${checkout}`,
16201
+ `Lower the requested capacity in .devrouter.yml, then run: devrouter ensure ${checkout}`
15914
16202
  ];
15915
16203
  case "capacity-parked":
15916
- return [`Run: devrouter ensure ${repoPath} (waits for capacity to free up)`];
16204
+ return [
16205
+ "Parked for host capacity: the enrolled foreground controller resumes this checkout after its pressure dwell and live-consumer checks pass.",
16206
+ `Inspect the parked intent with: devrouter status --repo ${checkout} --json`,
16207
+ `'devrouter ensure' refuses while the journal stays parked; release the intent instead with: devrouter stop ${checkout}`
16208
+ ];
15917
16209
  case "capacity-waiting":
15918
- return [`Run: devrouter ensure ${repoPath} (still queued until capacity frees up)`];
16210
+ return [`Run: devrouter ensure ${checkout} (still queued until capacity frees up)`];
15919
16211
  default:
15920
16212
  return [];
15921
16213
  }
@@ -16162,6 +16454,7 @@ function inspectManagedRuntime(options) {
16162
16454
  (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
16163
16455
  )?.Destination;
16164
16456
  const primaryActive = Boolean(primary?.state.Running);
16457
+ const mountNesting = primary && primaryActive ? observeManagedMountNesting(primary) : void 0;
16165
16458
  if (generatedConfigMissing && (state || primaryActive || managedServicesActive)) {
16166
16459
  drift.push("managed generated Dev Container configuration is missing");
16167
16460
  }
@@ -16228,7 +16521,8 @@ function inspectManagedRuntime(options) {
16228
16521
  processStatuses,
16229
16522
  activeServices: sortedUnique2(activeServices),
16230
16523
  activeProcesses: sortedUnique2(activeProcesses),
16231
- drift
16524
+ drift,
16525
+ ...mountNesting ? { mountNesting } : {}
16232
16526
  };
16233
16527
  }
16234
16528
  function collectManagedRuntimeStatus(options) {
@@ -16376,6 +16670,7 @@ function collectManagedRuntimeStatus(options) {
16376
16670
  baseServiceStatuses: inspection.baseStatuses,
16377
16671
  processStatuses: inspection.processStatuses,
16378
16672
  drift: sortedUnique2(drift),
16673
+ ...inspection.mountNesting ? { mountNesting: inspection.mountNesting } : {},
16379
16674
  ...inspection.plan?.sourceConfigSha256 || state?.sourceConfigSha256 ? { sourceConfigSha256: inspection.plan?.sourceConfigSha256 ?? state?.sourceConfigSha256 } : {},
16380
16675
  ...inspection.plan?.effectiveConfigSha256 || state?.effectiveConfigSha256 ? {
16381
16676
  effectiveConfigSha256: inspection.plan?.effectiveConfigSha256 ?? state?.effectiveConfigSha256
@@ -16390,6 +16685,7 @@ var init_managed_runtime_status = __esm({
16390
16685
  init_devcontainer_profile();
16391
16686
  init_devpod_environment();
16392
16687
  init_host_routes();
16688
+ init_managed_mount_nesting();
16393
16689
  init_managed_post_start();
16394
16690
  init_managed_runtime_state();
16395
16691
  init_reliability_operation_store();
@@ -16409,7 +16705,7 @@ function readDocker(endpoint, args, timeoutMs) {
16409
16705
  const env = { ...process.env };
16410
16706
  delete env.DOCKER_CONTEXT;
16411
16707
  delete env.DOCKER_HOST;
16412
- const result = (0, import_node_child_process24.spawnSync)("docker", ["--host", endpoint, ...args], {
16708
+ const result = (0, import_node_child_process25.spawnSync)("docker", ["--host", endpoint, ...args], {
16413
16709
  env,
16414
16710
  encoding: "utf-8",
16415
16711
  timeout: timeoutMs,
@@ -16440,7 +16736,14 @@ function id4(value) {
16440
16736
  }
16441
16737
  function rows(output2) {
16442
16738
  const trimmed = output2.trim();
16443
- return trimmed ? trimmed.split(/\r?\n/).map((line) => JSON.parse(line)) : [];
16739
+ return trimmed ? trimmed.split(/\r?\n/).map((line) => parseRecord(line)) : [];
16740
+ }
16741
+ function parseRecord(line) {
16742
+ try {
16743
+ return JSON.parse(line);
16744
+ } catch {
16745
+ throw new NetworkInventoryError("Docker network inventory contains a malformed record.");
16746
+ }
16444
16747
  }
16445
16748
  function ids(output2) {
16446
16749
  const values = output2.trim() ? output2.trim().split(/\r?\n/).map(id4) : [];
@@ -16485,7 +16788,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
16485
16788
  return output2;
16486
16789
  };
16487
16790
  const info = record4(
16488
- JSON.parse(
16791
+ parseRecord(
16489
16792
  read2(["info", "--format", '{"id":{{json .ID}},"pools":{{json .DefaultAddressPools}}}'])
16490
16793
  )
16491
16794
  );
@@ -16564,18 +16867,18 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
16564
16867
  }
16565
16868
  return result;
16566
16869
  }
16567
- var import_node_child_process24, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
16870
+ var import_node_child_process25, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
16568
16871
  var init_network_inventory = __esm({
16569
16872
  "src/core/network-inventory.ts"() {
16570
16873
  "use strict";
16571
- import_node_child_process24 = require("child_process");
16874
+ import_node_child_process25 = require("child_process");
16572
16875
  init_devpod_environment();
16573
16876
  NetworkInventoryError = class extends Error {
16574
16877
  };
16575
16878
  MAX_IDS = 4096;
16576
16879
  MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
16577
16880
  FULL_ID = /^[a-f0-9]{64}$/;
16578
- NETWORK_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"driver":{{json .Driver}},"ipam":{{json .IPAM.Config}},"activeEndpoints":{{len .Containers}},"project":{{json (index .Labels "com.docker.compose.project")}},"network":{{json (index .Labels "com.docker.compose.network")}}';
16881
+ NETWORK_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"driver":{{json .Driver}},"ipam":{{json .IPAM.Config}},"activeEndpoints":{{len .Containers}},"project":{{json (index .Labels "com.docker.compose.project")}},"network":{{json (index .Labels "com.docker.compose.network")}}}';
16579
16882
  CONTAINER_TEMPLATE = '{"id":{{json .Id}},"networks":{{json .NetworkSettings.Networks}}}';
16580
16883
  }
16581
16884
  });
@@ -16962,7 +17265,7 @@ function parseLinuxNetworkRoutes(output2) {
16962
17265
  function collectNetworkRoutes(options) {
16963
17266
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
16964
17267
  return { status: "unknown", routes: [] };
16965
- const result = (0, import_node_child_process25.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
17268
+ const result = (0, import_node_child_process26.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
16966
17269
  encoding: "utf8",
16967
17270
  timeout: 5e3,
16968
17271
  maxBuffer: 1024 * 1024
@@ -16970,11 +17273,11 @@ function collectNetworkRoutes(options) {
16970
17273
  if (result.error || result.status !== 0) return { status: "unknown", routes: [] };
16971
17274
  return parseLinuxNetworkRoutes(result.stdout);
16972
17275
  }
16973
- var import_node_child_process25, import_node_net3;
17276
+ var import_node_child_process26, import_node_net3;
16974
17277
  var init_network_routes = __esm({
16975
17278
  "src/core/network-routes.ts"() {
16976
17279
  "use strict";
16977
- import_node_child_process25 = require("child_process");
17280
+ import_node_child_process26 = require("child_process");
16978
17281
  import_node_net3 = require("net");
16979
17282
  init_network_capacity();
16980
17283
  init_network_local_daemon();
@@ -17019,11 +17322,13 @@ function networkCapacityCheck(report) {
17019
17322
  (pool) => pool.candidates.some((candidate) => candidate.overlappingNetworkIds.length === 0)
17020
17323
  );
17021
17324
  const managedDetails = managed ? ` Managed policy: ${managed.policy.status}; /${managed.policy.requestedPrefixLength} capacity: ${managed.configuredPolicyCapacity.status}; claims: ${managed.claims.reserved} reserved, ${managed.claims.attached} attached, ${managed.claims.uncertain} uncertain.` : "";
17325
+ const blockers = report.allocation.blockers.slice(0, 4).map((blocker) => blocker.slice(0, 120));
17326
+ const blockerDetails = blockers.length > 0 ? ` Missing evidence: ${blockers.join("; ")}.` : "";
17022
17327
  return {
17023
17328
  id: "global.network-capacity",
17024
17329
  level: legacyAvailable ? "ok" : "warn",
17025
17330
  summary: exhausted ? "Docker default address pools are exhausted for new networks." : legacyAvailable ? "Docker default pools have unoccupied capacity; managed allocation is not configured." : "Network allocation readiness requires complete route and capacity evidence.",
17026
- details: `${report.pools.length} pool(s); ${retained} network(s) have no active endpoints but retain container references. Allocation readiness: ${report.allocation.status}.${managedDetails}`,
17331
+ details: `${report.pools.length} pool(s); ${retained} network(s) have no active endpoints but retain container references. Allocation readiness: ${report.allocation.status}.${blockerDetails}${managedDetails}`,
17027
17332
  suggestion: exhausted ? "Existing network reuse can continue. Review operator-approved route-safe pools or exact ownership-aware recovery. Stop and worktree removal do not release subnets; do not prune automatically." : "Review complete Docker, LAN, VPN and guest route evidence before allocating a new subnet. Unknown evidence does not authorize cleanup."
17028
17333
  };
17029
17334
  }
@@ -17041,21 +17346,21 @@ var init_network_diagnostics = __esm({
17041
17346
  });
17042
17347
 
17043
17348
  // src/core/network-compose.ts
17044
- var import_node_crypto19;
17349
+ var import_node_crypto20;
17045
17350
  var init_network_compose = __esm({
17046
17351
  "src/core/network-compose.ts"() {
17047
17352
  "use strict";
17048
- import_node_crypto19 = require("crypto");
17353
+ import_node_crypto20 = require("crypto");
17049
17354
  }
17050
17355
  });
17051
17356
 
17052
17357
  // src/core/network-compose-files.ts
17053
- var import_node_child_process26, import_node_crypto20, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
17358
+ var import_node_child_process27, import_node_crypto21, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
17054
17359
  var init_network_compose_files = __esm({
17055
17360
  "src/core/network-compose-files.ts"() {
17056
17361
  "use strict";
17057
- import_node_child_process26 = require("child_process");
17058
- import_node_crypto20 = require("crypto");
17362
+ import_node_child_process27 = require("child_process");
17363
+ import_node_crypto21 = require("crypto");
17059
17364
  import_node_fs35 = __toESM(require("fs"));
17060
17365
  import_node_path43 = __toESM(require("path"));
17061
17366
  import_yaml4 = __toESM(require("yaml"));
@@ -17067,11 +17372,11 @@ var init_network_compose_files = __esm({
17067
17372
  });
17068
17373
 
17069
17374
  // src/core/network-connected-route.ts
17070
- var import_node_child_process27;
17375
+ var import_node_child_process28;
17071
17376
  var init_network_connected_route = __esm({
17072
17377
  "src/core/network-connected-route.ts"() {
17073
17378
  "use strict";
17074
- import_node_child_process27 = require("child_process");
17379
+ import_node_child_process28 = require("child_process");
17075
17380
  init_network_capacity();
17076
17381
  }
17077
17382
  });
@@ -17087,11 +17392,11 @@ var init_network_lifecycle = __esm({
17087
17392
  });
17088
17393
 
17089
17394
  // src/core/network-managed.ts
17090
- var import_node_crypto21;
17395
+ var import_node_crypto22;
17091
17396
  var init_network_managed = __esm({
17092
17397
  "src/core/network-managed.ts"() {
17093
17398
  "use strict";
17094
- import_node_crypto21 = require("crypto");
17399
+ import_node_crypto22 = require("crypto");
17095
17400
  init_devpod_environment();
17096
17401
  init_network_capacity();
17097
17402
  init_network_claim_lookup();
@@ -17161,11 +17466,11 @@ function resolveLinkedTarget(repoPath) {
17161
17466
  gitCommonDir: resolveGitCommonDir(repoPath)
17162
17467
  };
17163
17468
  }
17164
- var import_node_child_process28, import_node_fs36, import_node_path44;
17469
+ var import_node_child_process29, import_node_fs36, import_node_path44;
17165
17470
  var init_workspace_ensure = __esm({
17166
17471
  "src/core/workspace-ensure.ts"() {
17167
17472
  "use strict";
17168
- import_node_child_process28 = require("child_process");
17473
+ import_node_child_process29 = require("child_process");
17169
17474
  import_node_fs36 = __toESM(require("fs"));
17170
17475
  import_node_path44 = __toESM(require("path"));
17171
17476
  init_devcontainer_profile();
@@ -17445,6 +17750,44 @@ function restoreParkedIntentAfterFailedResume(input2) {
17445
17750
  record5.state = transition2.state;
17446
17751
  });
17447
17752
  }
17753
+ function snapshotLifecycleStopIntent(state) {
17754
+ return {
17755
+ intentRevision: state.intentRevision,
17756
+ observationsAfterMs: state.observationsAfterMs,
17757
+ desired: state.desired,
17758
+ phase: state.phase,
17759
+ profile: state.profile,
17760
+ admission: state.admission,
17761
+ stopProof: { ...state.stopProof },
17762
+ requests: state.requests.map((request2) => ({ ...request2 })),
17763
+ observations: state.observations.map((observation) => ({ ...observation })),
17764
+ operation: state.operation ? { ...state.operation } : null
17765
+ };
17766
+ }
17767
+ function withdrawUnstartedLifecycleStop(input2) {
17768
+ return updateReliabilityOperation(input2.identity, (record5) => {
17769
+ const state = record5.state;
17770
+ const intent = input2.intent;
17771
+ if (!intent.wroteIntent) return false;
17772
+ if (!matchesFence(record5, intent.fence)) return false;
17773
+ if (record5.worker || record5.stopWorkStarted) return false;
17774
+ if (state.desired !== "stopped-by-user" || state.phase !== "stopping") return false;
17775
+ if (state.stopProof.workloadsStopped || state.stopProof.routesRemoved) return false;
17776
+ if (!(0, import_node_util11.isDeepStrictEqual)(state.operation, intent.recordedOperation)) return false;
17777
+ const prior = intent.prior;
17778
+ state.intentRevision = prior.intentRevision;
17779
+ state.observationsAfterMs = prior.observationsAfterMs;
17780
+ state.desired = prior.desired;
17781
+ state.phase = prior.phase;
17782
+ state.profile = prior.profile;
17783
+ state.admission = prior.admission;
17784
+ state.stopProof = { ...prior.stopProof };
17785
+ state.requests = prior.requests.map((request2) => ({ ...request2 }));
17786
+ state.observations = prior.observations.map((observation) => ({ ...observation }));
17787
+ state.operation = prior.operation ? { ...prior.operation } : null;
17788
+ return true;
17789
+ });
17790
+ }
17448
17791
  function prepareResumeLifecycleOperation(input2) {
17449
17792
  const ids2 = newLifecycleIds();
17450
17793
  return updateReliabilityOperation(input2.identity, (record5) => {
@@ -17597,12 +17940,37 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
17597
17940
  const copiedOptions = { ...options };
17598
17941
  const copiedCommand = command ? [...command] : void 0;
17599
17942
  if (kind === "stop") {
17600
- const fence = updateReliabilityOperation(identity2, (record5) => {
17943
+ const intent = updateReliabilityOperation(identity2, (record5) => {
17601
17944
  reconcileDrained(record5);
17945
+ const prior = snapshotLifecycleStopIntent(record5.state);
17602
17946
  stepRecord(record5, { ...reliabilityFence(record5.state), type: "stop" });
17603
- return reliabilityFence(record5.state);
17947
+ return {
17948
+ fence: reliabilityFence(record5.state),
17949
+ prior,
17950
+ recordedOperation: record5.state.operation ? { ...record5.state.operation } : null,
17951
+ wroteIntent: record5.state.intentRevision !== prior.intentRevision
17952
+ };
17604
17953
  });
17605
- return runLifecycleWorker({ kind, repoPath, identity: identity2, ...ids2, fence, options: copiedOptions });
17954
+ try {
17955
+ return await runLifecycleWorker({
17956
+ kind,
17957
+ repoPath,
17958
+ identity: identity2,
17959
+ ...ids2,
17960
+ fence: intent.fence,
17961
+ options: copiedOptions
17962
+ });
17963
+ } catch (error) {
17964
+ try {
17965
+ withdrawUnstartedLifecycleStop({ identity: identity2, intent });
17966
+ } catch (withdrawal) {
17967
+ process.stderr.write(
17968
+ `Lifecycle stop intent could not be withdrawn: ${withdrawal instanceof Error ? withdrawal.message : String(withdrawal)}
17969
+ `
17970
+ );
17971
+ }
17972
+ throw error;
17973
+ }
17606
17974
  }
17607
17975
  const initial = readReliabilityOperation(identity2) ?? updateReliabilityOperation(identity2, (record5) => record5);
17608
17976
  if (initial.state.executionPolicy === "capacity-managed" || capacityPolicyTargets(repoPath))
@@ -17847,7 +18215,7 @@ async function superviseThroughController(input2) {
17847
18215
  try {
17848
18216
  const bind = () => observeControllerBinding(directory, {
17849
18217
  path: input2.repoPath,
17850
- session: (0, import_node_crypto22.randomUUID)(),
18218
+ session: (0, import_node_crypto23.randomUUID)(),
17851
18219
  profile: input2.profile ?? "full",
17852
18220
  // The protocol requires at least one readiness selector, and every
17853
18221
  // supervised operation depends on runtime readiness; "runtime" is the
@@ -17998,11 +18366,11 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path45
17998
18366
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
17999
18367
  });
18000
18368
  }
18001
- var import_node_crypto22, import_node_path45, import_node_util11, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
18369
+ var import_node_crypto23, import_node_path45, import_node_util11, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
18002
18370
  var init_reliability_lifecycle = __esm({
18003
18371
  "src/core/reliability-lifecycle.ts"() {
18004
18372
  "use strict";
18005
- import_node_crypto22 = require("crypto");
18373
+ import_node_crypto23 = require("crypto");
18006
18374
  import_node_path45 = __toESM(require("path"));
18007
18375
  import_node_util11 = require("util");
18008
18376
  init_capacity_accounting();
@@ -18638,7 +19006,7 @@ function createCapacityController(options) {
18638
19006
  controller,
18639
19007
  policyRevision: policy.revision
18640
19008
  });
18641
- const payloadKey = (0, import_node_crypto23.randomBytes)(32);
19009
+ const payloadKey = (0, import_node_crypto24.randomBytes)(32);
18642
19010
  const acceptedPayloads = /* @__PURE__ */ new Map();
18643
19011
  const settlePreparations = () => {
18644
19012
  if (lifetime.signal.aborted) return;
@@ -18802,7 +19170,7 @@ function createCapacityController(options) {
18802
19170
  {
18803
19171
  ...charge,
18804
19172
  operationId: prepared.operationId,
18805
- reservationId: (0, import_node_crypto23.randomUUID)(),
19173
+ reservationId: (0, import_node_crypto24.randomUUID)(),
18806
19174
  policyRevision: target.current.revision
18807
19175
  },
18808
19176
  {
@@ -18905,7 +19273,7 @@ function createCapacityController(options) {
18905
19273
  throw error;
18906
19274
  }
18907
19275
  }
18908
- const signature = (0, import_node_crypto23.createHmac)("sha256", payloadKey).update(
19276
+ const signature = (0, import_node_crypto24.createHmac)("sha256", payloadKey).update(
18909
19277
  JSON.stringify({
18910
19278
  repoPath: identity2.repoPath,
18911
19279
  profile: environment.profile,
@@ -18925,7 +19293,7 @@ function createCapacityController(options) {
18925
19293
  {
18926
19294
  ...charge,
18927
19295
  operationId: prepared.operationId,
18928
- reservationId: (0, import_node_crypto23.randomUUID)(),
19296
+ reservationId: (0, import_node_crypto24.randomUUID)(),
18929
19297
  policyRevision: current.revision
18930
19298
  },
18931
19299
  { estimates: resolved.estimates, enrollment: resolved.enrollment, pool }
@@ -19060,7 +19428,7 @@ function createCapacityController(options) {
19060
19428
  // the budget module then falls back to the conservative wall duration.
19061
19429
  activeElapsedMs: null,
19062
19430
  profile: environment.profile,
19063
- incidentId: `incident-${(0, import_node_crypto23.randomUUID)()}`
19431
+ incidentId: `incident-${(0, import_node_crypto24.randomUUID)()}`
19064
19432
  });
19065
19433
  } catch {
19066
19434
  return;
@@ -19078,7 +19446,7 @@ function createCapacityController(options) {
19078
19446
  {
19079
19447
  ...charge,
19080
19448
  operationId: prepared.operationId,
19081
- reservationId: (0, import_node_crypto23.randomUUID)(),
19449
+ reservationId: (0, import_node_crypto24.randomUUID)(),
19082
19450
  policyRevision: current.revision
19083
19451
  },
19084
19452
  { estimates: resolved.estimates, enrollment: resolved.enrollment, pool }
@@ -19098,11 +19466,11 @@ function createCapacityController(options) {
19098
19466
  }
19099
19467
  };
19100
19468
  }
19101
- var import_node_crypto23, import_node_path46, import_node_util13;
19469
+ var import_node_crypto24, import_node_path46, import_node_util13;
19102
19470
  var init_capacity_controller = __esm({
19103
19471
  "src/core/capacity-controller.ts"() {
19104
19472
  "use strict";
19105
- import_node_crypto23 = require("crypto");
19473
+ import_node_crypto24 = require("crypto");
19106
19474
  import_node_path46 = __toESM(require("path"));
19107
19475
  import_node_util13 = require("util");
19108
19476
  init_capacity_accounting();
@@ -19199,7 +19567,7 @@ printf '%s %s %s\n' "$pid" "$birth" "$fingerprint"
19199
19567
 
19200
19568
  // src/core/controller-observation.ts
19201
19569
  function digest2(value) {
19202
- return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
19570
+ return (0, import_node_crypto25.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
19203
19571
  }
19204
19572
  function readRoutes() {
19205
19573
  return readHostRouteStateReadOnly((file) => readControllerEvidence(file, 1048576));
@@ -19283,7 +19651,7 @@ function createControllerObservationCollector(resolve, fingerprint) {
19283
19651
  () => persisted.contents[3]
19284
19652
  );
19285
19653
  const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
19286
- if (!state || state.devpodId !== environment.providerId || state.profile !== environment.profile || typeof source.service !== "string" || !source.service || (0, import_node_crypto24.createHash)("sha256").update(persisted.contents[4]).digest("hex") !== state.sourceConfigSha256 || (0, import_node_crypto24.createHash)("sha256").update(persisted.contents[5]).digest("hex") !== state.effectiveConfigSha256)
19654
+ if (!state || state.devpodId !== environment.providerId || state.profile !== environment.profile || typeof source.service !== "string" || !source.service || (0, import_node_crypto25.createHash)("sha256").update(persisted.contents[4]).digest("hex") !== state.sourceConfigSha256 || (0, import_node_crypto25.createHash)("sha256").update(persisted.contents[5]).digest("hex") !== state.effectiveConfigSha256)
19287
19655
  throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
19288
19656
  const identity2 = {
19289
19657
  repoPath: environment.repoPath,
@@ -19458,11 +19826,11 @@ function createControllerObservationCollector(resolve, fingerprint) {
19458
19826
  };
19459
19827
  };
19460
19828
  }
19461
- var import_node_crypto24, import_node_path47, OBSERVATION_INSPECT_TEMPLATE;
19829
+ var import_node_crypto25, import_node_path47, OBSERVATION_INSPECT_TEMPLATE;
19462
19830
  var init_controller_observation = __esm({
19463
19831
  "src/core/controller-observation.ts"() {
19464
19832
  "use strict";
19465
- import_node_crypto24 = require("crypto");
19833
+ import_node_crypto25 = require("crypto");
19466
19834
  import_node_path47 = __toESM(require("path"));
19467
19835
  init_controller_binding();
19468
19836
  init_controller_monitor();
@@ -19487,11 +19855,11 @@ var init_controller_observation = __esm({
19487
19855
  });
19488
19856
 
19489
19857
  // src/core/controller-sessions.ts
19490
- var import_node_crypto25, import_node_util14, ControllerSessions;
19858
+ var import_node_crypto26, import_node_util14, ControllerSessions;
19491
19859
  var init_controller_sessions = __esm({
19492
19860
  "src/core/controller-sessions.ts"() {
19493
19861
  "use strict";
19494
- import_node_crypto25 = require("crypto");
19862
+ import_node_crypto26 = require("crypto");
19495
19863
  import_node_util14 = require("util");
19496
19864
  ControllerSessions = class {
19497
19865
  constructor(store) {
@@ -19663,7 +20031,7 @@ var init_controller_sessions = __esm({
19663
20031
  const session = {
19664
20032
  id: id5,
19665
20033
  environmentId: environment.id,
19666
- generation: (0, import_node_crypto25.randomUUID)(),
20034
+ generation: (0, import_node_crypto26.randomUUID)(),
19667
20035
  requirements: required,
19668
20036
  renewedAtMs: wall,
19669
20037
  parkingConsent: "protected",
@@ -19690,7 +20058,7 @@ var init_controller_sessions = __esm({
19690
20058
  const session = {
19691
20059
  id: previous.session,
19692
20060
  environmentId: environment.id,
19693
- generation: (0, import_node_crypto25.randomUUID)(),
20061
+ generation: (0, import_node_crypto26.randomUUID)(),
19694
20062
  requirements: required,
19695
20063
  renewedAtMs: wall,
19696
20064
  parkingConsent: "protected",
@@ -21134,26 +21502,30 @@ checkout's environment is mid-transition. Wire \`devrouter harness gate\` as a
21134
21502
  {
21135
21503
  "hooks": {
21136
21504
  "PreToolUse": [
21137
- { "matcher": "Bash", "hooks": [
21505
+ { "matcher": ".*", "hooks": [
21138
21506
  { "type": "command", "command": "devrouter harness gate", "timeout": 90 }
21139
21507
  ] }
21140
21508
  ]
21141
21509
  }
21142
21510
  }
21143
21511
 
21144
- The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`, where the shell
21145
- tool is named \`exec_command\`:
21512
+ The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`:
21146
21513
 
21147
21514
  {
21148
21515
  "hooks": {
21149
21516
  "PreToolUse": [
21150
- { "matcher": "exec_command|Bash|shell", "hooks": [
21517
+ { "matcher": ".*", "hooks": [
21151
21518
  { "type": "command", "command": "devrouter harness gate", "timeout": 180 }
21152
21519
  ] }
21153
21520
  ]
21154
21521
  }
21155
21522
  }
21156
21523
 
21524
+ \`.*\` matches every tool, including the shell, file-editing and MCP tools a model
21525
+ can point at the environment. The gate answers a settled checkout immediately, so
21526
+ the price is one short-lived process per gated call; narrow the matcher when that
21527
+ price matters more than covering every tool.
21528
+
21157
21529
  The gate identifies the requesting harness from its payload and answers in that
21158
21530
  harness's accepted shape: a refusal is a \`deny\` in both, while an allowed call is
21159
21531
  \`permissionDecision: allow\` for Claude Code and Codex receives a bare completion
@@ -21849,6 +22221,13 @@ function appendManagedRuntimeRows(rows2, managedRuntime) {
21849
22221
  rows2.push(["Effective config SHA-256", managedRuntime.effectiveConfigSha256 ?? "-"]);
21850
22222
  rows2.push(["Transition phase", managedRuntime.transitionPhase ?? "-"]);
21851
22223
  rows2.push(["Runtime drift", formatResourceNames(managedRuntime.drift)]);
22224
+ if (managedRuntime.mountNesting && managedRuntime.mountNesting.status !== "not-applicable") {
22225
+ const nesting = managedRuntime.mountNesting;
22226
+ rows2.push([
22227
+ "Mount nesting",
22228
+ nesting.status === "unwound" ? `unwound: ${nesting.unwound.map((entry) => entry.destination).join(", ")} (restart the container to re-apply)` : nesting.status === "unverified" ? `unverified (${nesting.reason ?? "unknown"})` : `effective (${nesting.checked.join(", ")})`
22229
+ ]);
22230
+ }
21852
22231
  if (managedRuntime.reliability) {
21853
22232
  const reliability = managedRuntime.reliability;
21854
22233
  rows2.push(["Lifecycle intent", reliability.desired]);
@@ -22960,6 +23339,11 @@ async function collectRouterStatus(repoPath) {
22960
23339
  nextSteps.push(`Run: dev app ls --repo ${repo.path}`);
22961
23340
  nextSteps.push("Run: dev app run <name> --repo <path> --yes");
22962
23341
  nextSteps.push("Run: dev ls");
23342
+ if (repo.managedRuntime?.mountNesting?.status === "unwound") {
23343
+ nextSteps.push(
23344
+ `Run: devrouter stop ${repo.path} and then devrouter ensure ${repo.path} to re-apply the unwound nested mount`
23345
+ );
23346
+ }
22963
23347
  }
22964
23348
  const files = getRouterFileLayout();
22965
23349
  if (files.missing.length > 0) {
@@ -23003,7 +23387,7 @@ function outputFromResult(result) {
23003
23387
  return output2.length > 0 ? output2 : void 0;
23004
23388
  }
23005
23389
  function runTool(command, args = []) {
23006
- const result = (0, import_node_child_process29.spawnSync)(command, args, {
23390
+ const result = (0, import_node_child_process30.spawnSync)(command, args, {
23007
23391
  encoding: "utf-8"
23008
23392
  });
23009
23393
  if (result.error) {
@@ -23194,7 +23578,7 @@ function displayPath(filePath) {
23194
23578
  return `~${filePath.slice(home.length)}`;
23195
23579
  }
23196
23580
  function probeInstalledCliVersion(executable) {
23197
- const result = (0, import_node_child_process29.spawnSync)(executable, ["-V"], {
23581
+ const result = (0, import_node_child_process30.spawnSync)(executable, ["-V"], {
23198
23582
  encoding: "utf-8",
23199
23583
  timeout: CLI_PATH_PROBE_TIMEOUT_MS
23200
23584
  });
@@ -23205,7 +23589,7 @@ function probeInstalledCliVersion(executable) {
23205
23589
  }
23206
23590
  function buildCliPathCheck(options = {}) {
23207
23591
  const id5 = "global.cli-path";
23208
- const runningVersion = options.runningVersion ?? (true ? "0.1.1" : "0.0.0-dev");
23592
+ const runningVersion = options.runningVersion ?? (true ? "0.1.3" : "0.0.0-dev");
23209
23593
  if (runningVersion === "0.0.0-dev") {
23210
23594
  return {
23211
23595
  id: id5,
@@ -23380,11 +23764,11 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
23380
23764
  checks.push(nodeToolchainCheck(repoPath));
23381
23765
  return checks;
23382
23766
  }
23383
- var import_node_child_process29, import_node_fs43, import_node_path54, DEVSY_DISABLE_TELEMETRY, CLI_PATH_PROBE_TIMEOUT_MS, INSTALLED_CLI_VERSION_RE;
23767
+ var import_node_child_process30, import_node_fs43, import_node_path54, DEVSY_DISABLE_TELEMETRY, CLI_PATH_PROBE_TIMEOUT_MS, INSTALLED_CLI_VERSION_RE;
23384
23768
  var init_tool_diagnostics = __esm({
23385
23769
  "src/core/tool-diagnostics.ts"() {
23386
23770
  "use strict";
23387
- import_node_child_process29 = require("child_process");
23771
+ import_node_child_process30 = require("child_process");
23388
23772
  import_node_fs43 = __toESM(require("fs"));
23389
23773
  import_node_path54 = __toESM(require("path"));
23390
23774
  init_devsy_agent();
@@ -23871,6 +24255,17 @@ function collectNextSteps(checks, statusNextSteps) {
23871
24255
  }
23872
24256
  return Array.from(steps.values());
23873
24257
  }
24258
+ function capacityHistoryDetails(error) {
24259
+ if (!(error instanceof CapacityHistoryError)) return "capacity-history-unprovable";
24260
+ const parts = [error.code];
24261
+ if (error.cause) parts.push(error.cause);
24262
+ if (error.location) parts.push(`at ${error.location}`);
24263
+ return parts.join("; ");
24264
+ }
24265
+ function capacityHistorySuggestion(error) {
24266
+ const cause = error instanceof CapacityHistoryError ? error.cause : null;
24267
+ return (cause ? CAPACITY_RECOVERY_BY_CAUSE[cause] : void 0) ?? "Preserve ledger and lifecycle records; restore verified history before retrying admission.";
24268
+ }
23874
24269
  async function buildDoctorReport(options = {}) {
23875
24270
  const checks = [];
23876
24271
  const fileLayout = getRouterFileLayout();
@@ -23916,8 +24311,8 @@ async function buildDoctorReport(options = {}) {
23916
24311
  id: "global.capacity-ledger",
23917
24312
  level: "error",
23918
24313
  summary: "Capacity ledger history is unavailable for safe admission.",
23919
- details: error instanceof CapacityHistoryError ? error.code : "capacity-history-unprovable",
23920
- suggestion: absent ? "Preserve ledger and lifecycle records; with every enrolled environment stopped, run: devrouter capacity reconcile --yes" : "Preserve ledger and lifecycle records; restore verified history before retrying admission."
24314
+ details: capacityHistoryDetails(error),
24315
+ suggestion: absent ? "Preserve ledger and lifecycle records; with every enrolled environment stopped, run: devrouter capacity reconcile --yes" : capacityHistorySuggestion(error)
23921
24316
  });
23922
24317
  }
23923
24318
  let statusNextSteps = [];
@@ -24029,6 +24424,43 @@ async function buildDoctorReport(options = {}) {
24029
24424
  ...details ? { details } : {},
24030
24425
  suggestion: managedRuntime.mode === "legacy" ? void 0 : managedRuntime.status === "failed-transition" ? `Inspect: dev status --repo ${repo.path}; resolve the reported drift before retrying ensure.` : managedRuntime.status === "ready" ? void 0 : `Run: dev ensure ${repo.path}`
24031
24426
  });
24427
+ const mountNesting = managedRuntime.mountNesting;
24428
+ if (mountNesting && mountNesting.status !== "not-applicable") {
24429
+ const container = mountNesting.container ?? "unknown";
24430
+ const shortContainer = container === "unknown" ? container : container.slice(0, 12);
24431
+ if (mountNesting.status === "unwound") {
24432
+ const first = mountNesting.unwound[0];
24433
+ addCheck(checks, {
24434
+ id: "repo.managed-mount-nesting",
24435
+ level: "warn",
24436
+ summary: `Managed container ${shortContainer} no longer reports ${first?.destination ?? "a nested mount"} in its own mount table, so the mount underneath it is visible in the container.`,
24437
+ details: [
24438
+ `container=${container}`,
24439
+ ...mountNesting.unwound.map(
24440
+ (entry) => `unwound=${entry.destination} (source ${entry.source} inside ${entry.nestedWithin})`
24441
+ )
24442
+ ].join("; "),
24443
+ suggestion: `Run: devrouter stop ${repo.path} and then devrouter ensure ${repo.path} to restart the exact container, which re-applies the nested mount.`
24444
+ });
24445
+ } else if (mountNesting.status === "unverified") {
24446
+ addCheck(checks, {
24447
+ id: "repo.managed-mount-nesting",
24448
+ level: "warn",
24449
+ summary: `Managed container ${shortContainer} did not report its own mount table, so its nested mounts are unverified.`,
24450
+ details: [
24451
+ `container=${container}`,
24452
+ `reason=${mountNesting.reason ?? "unknown"}`
24453
+ ].join("; "),
24454
+ suggestion: `Run: devrouter exec ${repo.path} -- cat /proc/self/mountinfo to read the container's own mount table.`
24455
+ });
24456
+ } else {
24457
+ addCheck(checks, {
24458
+ id: "repo.managed-mount-nesting",
24459
+ level: "ok",
24460
+ summary: `Managed container ${shortContainer} still reports ${mountNesting.checked.join(", ")} in its own mount table.`
24461
+ });
24462
+ }
24463
+ }
24032
24464
  }
24033
24465
  if (repo.appCount === 0) {
24034
24466
  addCheck(checks, {
@@ -24053,7 +24485,7 @@ async function buildDoctorReport(options = {}) {
24053
24485
  suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
24054
24486
  });
24055
24487
  }
24056
- const cliVersion = true ? "0.1.1" : "0.0.0-dev";
24488
+ const cliVersion = true ? "0.1.3" : "0.0.0-dev";
24057
24489
  const configVersion = config.devrouter?.version;
24058
24490
  const installedKnown = cliVersion !== "0.0.0-dev";
24059
24491
  const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
@@ -24279,7 +24711,7 @@ async function buildDoctorReport(options = {}) {
24279
24711
  nextSteps
24280
24712
  };
24281
24713
  }
24282
- var import_node_fs44, import_node_path56, import_yaml6, POSTGRES_DEFAULTS;
24714
+ var import_node_fs44, import_node_path56, import_yaml6, POSTGRES_DEFAULTS, CAPACITY_RECOVERY_BY_CAUSE;
24283
24715
  var init_doctor = __esm({
24284
24716
  "src/core/doctor.ts"() {
24285
24717
  "use strict";
@@ -24308,6 +24740,14 @@ var init_doctor = __esm({
24308
24740
  POSTGRES_PASSWORD: "prisma",
24309
24741
  POSTGRES_DB: "prisma"
24310
24742
  };
24743
+ CAPACITY_RECOVERY_BY_CAUSE = {
24744
+ "journal-entry-unsupported": "Move the named unrecognised entry out of the private reliability journal directory, preserving its contents; it blocks every capacity read while it stays there.",
24745
+ "journal-directory-unsafe": "Restore private ownership and mode 0700 on the reliability journal directory, then retry.",
24746
+ "journal-directory-unreadable": "Restore read access to the reliability journal directory, then retry.",
24747
+ "journal-invalid": "Preserve the named journal entry and restore verified history before retrying admission.",
24748
+ "journal-unreadable": "Preserve the named journal entry and restore verified history before retrying admission.",
24749
+ "journal-entry-unsafe": "Restore private ownership, mode 0600 and a bounded size on the named journal entry, then retry."
24750
+ };
24311
24751
  }
24312
24752
  });
24313
24753
 
@@ -24609,12 +25049,12 @@ function parseSsPortListeners(stdout, port) {
24609
25049
  return listeners;
24610
25050
  }
24611
25051
  function findPortListeners(port) {
24612
- const result = (0, import_node_child_process30.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
25052
+ const result = (0, import_node_child_process31.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
24613
25053
  encoding: "utf-8"
24614
25054
  });
24615
25055
  if (result.error && result.error.code === "ENOENT") {
24616
25056
  if (process.platform === "linux") {
24617
- const ssResult = (0, import_node_child_process30.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
25057
+ const ssResult = (0, import_node_child_process31.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
24618
25058
  if (ssResult.status === 0 && ssResult.stdout) {
24619
25059
  return parseSsPortListeners(ssResult.stdout, port);
24620
25060
  }
@@ -24643,11 +25083,11 @@ function findPortListeners(port) {
24643
25083
  };
24644
25084
  });
24645
25085
  }
24646
- var import_node_child_process30;
25086
+ var import_node_child_process31;
24647
25087
  var init_ports = __esm({
24648
25088
  "src/util/ports.ts"() {
24649
25089
  "use strict";
24650
- import_node_child_process30 = require("child_process");
25090
+ import_node_child_process31 = require("child_process");
24651
25091
  }
24652
25092
  });
24653
25093
 
@@ -24916,7 +25356,7 @@ var init_status2 = __esm({
24916
25356
 
24917
25357
  // src/core/harness-continuation.ts
24918
25358
  function harnessContinuationPath(repoRoot) {
24919
- const key = (0, import_node_crypto26.createHash)("sha256").update(comparableWorkspacePath(repoRoot)).digest("hex");
25359
+ const key = (0, import_node_crypto27.createHash)("sha256").update(comparableWorkspacePath(repoRoot)).digest("hex");
24920
25360
  return import_node_path57.default.join(DEVROUTER_HOME, "harness", `${key}.json`);
24921
25361
  }
24922
25362
  function counter4(value) {
@@ -25014,11 +25454,11 @@ function settleHarnessContinuation(repoRoot, toolUseId, settlement) {
25014
25454
  } catch {
25015
25455
  }
25016
25456
  }
25017
- var import_node_crypto26, import_node_fs45, import_node_path57, HARNESS_CONTINUATION_MAX_ENTRIES, HARNESS_CONTINUATION_TTL_MS, HARNESS_CONTINUATION_LOCK_WAIT_MS, MAX_LEDGER_BYTES, MAX_TOOL_USE_ID_LENGTH, MAX_PHASE_LENGTH;
25457
+ var import_node_crypto27, import_node_fs45, import_node_path57, HARNESS_CONTINUATION_MAX_ENTRIES, HARNESS_CONTINUATION_TTL_MS, HARNESS_CONTINUATION_LOCK_WAIT_MS, MAX_LEDGER_BYTES, MAX_TOOL_USE_ID_LENGTH, MAX_PHASE_LENGTH;
25018
25458
  var init_harness_continuation = __esm({
25019
25459
  "src/core/harness-continuation.ts"() {
25020
25460
  "use strict";
25021
- import_node_crypto26 = require("crypto");
25461
+ import_node_crypto27 = require("crypto");
25022
25462
  import_node_fs45 = __toESM(require("fs"));
25023
25463
  import_node_path57 = __toESM(require("path"));
25024
25464
  init_atomic_file();
@@ -25085,17 +25525,20 @@ async function waitForHarnessGate(options) {
25085
25525
  options.onObservation?.(observation, now() - started);
25086
25526
  if (!isTransitionalHarnessPhase(observation.phase)) {
25087
25527
  const waitedMs2 = now() - started;
25528
+ let reason = deferred ? "settled-after-wait" : "settled";
25529
+ if (observation.phase === "unknown") reason = "evidence-unavailable";
25088
25530
  return {
25089
25531
  version: 1,
25090
25532
  decision: deferred ? "deferred-allow" : "allow",
25091
- reason: deferred ? "settled-after-wait" : "settled",
25533
+ reason,
25092
25534
  waitedMs: waitedMs2,
25093
25535
  observations,
25094
25536
  observedPhase: observation.phase
25095
25537
  };
25096
25538
  }
25097
25539
  const waitedMs = now() - started;
25098
- if (waitedMs + interval > budget) {
25540
+ const remainingMs = budget - waitedMs;
25541
+ if (remainingMs <= 0) {
25099
25542
  return {
25100
25543
  version: 1,
25101
25544
  decision: "refuse",
@@ -25120,7 +25563,7 @@ async function waitForHarnessGate(options) {
25120
25563
  }
25121
25564
  }
25122
25565
  deferred = true;
25123
- await sleep3(Math.min(interval, budget - waitedMs));
25566
+ await sleep3(Math.min(interval, remainingMs));
25124
25567
  }
25125
25568
  }
25126
25569
  function parseHarnessGateBudget(value) {
@@ -25162,6 +25605,91 @@ __export(harness_exports, {
25162
25605
  detectHarnessHook: () => detectHarnessHook,
25163
25606
  runHarnessCommand: () => runHarnessCommand
25164
25607
  });
25608
+ function commandSegments(command) {
25609
+ const segments = [];
25610
+ let current = "";
25611
+ let quote;
25612
+ for (const char of command) {
25613
+ if (quote) {
25614
+ current += char;
25615
+ if (char === quote) quote = void 0;
25616
+ continue;
25617
+ }
25618
+ if (char === "'" || char === '"') {
25619
+ quote = char;
25620
+ current += char;
25621
+ continue;
25622
+ }
25623
+ if (char === ";" || char === "\n" || char === "&" || char === "|" || char === "(" || char === ")") {
25624
+ segments.push(current);
25625
+ current = "";
25626
+ continue;
25627
+ }
25628
+ current += char;
25629
+ }
25630
+ segments.push(current);
25631
+ return segments;
25632
+ }
25633
+ function segmentWords(segment, limit) {
25634
+ const words = [];
25635
+ let current = "";
25636
+ let quote;
25637
+ const flush = () => {
25638
+ if (current) words.push(current);
25639
+ current = "";
25640
+ };
25641
+ for (const char of segment) {
25642
+ if (quote) {
25643
+ if (char === quote) {
25644
+ quote = void 0;
25645
+ continue;
25646
+ }
25647
+ current += char;
25648
+ continue;
25649
+ }
25650
+ if (char === "'" || char === '"') {
25651
+ quote = char;
25652
+ continue;
25653
+ }
25654
+ if (char === " " || char === " ") {
25655
+ flush();
25656
+ if (words.length >= limit) return words;
25657
+ continue;
25658
+ }
25659
+ current += char;
25660
+ }
25661
+ flush();
25662
+ return words;
25663
+ }
25664
+ function executableName(word) {
25665
+ return word.slice(word.lastIndexOf("/") + 1);
25666
+ }
25667
+ function commandWord(segment) {
25668
+ const words = segmentWords(segment, COMMAND_WORD_LIMIT);
25669
+ let index = 0;
25670
+ for (; ; ) {
25671
+ const word = words[index];
25672
+ if (word === void 0) return void 0;
25673
+ if (word === "env" || /^[A-Za-z_][A-Za-z0-9_]*=/u.test(word)) {
25674
+ index += 1;
25675
+ continue;
25676
+ }
25677
+ break;
25678
+ }
25679
+ const launcher = words[index];
25680
+ if (launcher !== void 0 && LAUNCHER_WORDS.has(executableName(launcher))) {
25681
+ index += 1;
25682
+ const subcommand = words[index];
25683
+ if (subcommand !== void 0 && LAUNCHER_SUBCOMMANDS.has(subcommand)) index += 1;
25684
+ }
25685
+ return words[index];
25686
+ }
25687
+ function isDevrouterLifecycleCommand(command) {
25688
+ return commandSegments(command).some((segment) => {
25689
+ const word = commandWord(segment);
25690
+ return word !== void 0 && DEVROUTER_EXECUTABLES.has(executableName(word));
25691
+ });
25692
+ }
25165
25693
  function detectHarnessHook(payload) {
25166
25694
  const turnId = payload.turn_id;
25167
25695
  return typeof turnId === "string" && turnId.length > 0 ? "codex" : "claude";
@@ -25208,11 +25736,19 @@ function permitReason(decision) {
25208
25736
  }
25209
25737
  return `devrouter: this tool call's wait was cancelled${age}; the harness may have run it. Verify whether it ran before issuing a new call.`;
25210
25738
  }
25211
- switch (decision.decision) {
25212
- case "refuse":
25739
+ switch (decision.reason) {
25740
+ case "budget-exhausted":
25213
25741
  return `devrouter: the managed environment is still ${decision.observedPhase} after waiting ${seconds}s. Do not retry automatically; run 'devrouter status .' and continue once it settles.`;
25214
- case "deferred-allow":
25742
+ case "settled-after-wait":
25215
25743
  return `devrouter: environment settled after ${seconds}s; the tool may run now.`;
25744
+ case "devrouter-command":
25745
+ return "devrouter: lifecycle command; the gate left it to the lifecycle engine and did not observe the environment.";
25746
+ case "unmanaged-checkout":
25747
+ return "devrouter: no managed checkout was found for this call; the gate did not observe environment state.";
25748
+ case "hook-payload-invalid":
25749
+ return "devrouter: the hook payload was unreadable; the gate did not observe environment state.";
25750
+ case "evidence-unavailable":
25751
+ return "devrouter: lifecycle evidence was unavailable; the gate observed no settled phase and the tool proceeds.";
25216
25752
  default:
25217
25753
  return "devrouter: environment settled.";
25218
25754
  }
@@ -25280,10 +25816,9 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25280
25816
  return;
25281
25817
  }
25282
25818
  kind = detectHarnessHook(payload);
25283
- const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
25284
25819
  const toolInput = isRecord7(payload.tool_input) ? payload.tool_input : {};
25285
- const command = typeof toolInput.command === "string" ? toolInput.command : "";
25286
- if (toolName === "Bash" && DEVROUTER_COMMAND_RE.test(command)) {
25820
+ const command = typeof toolInput.command === "string" ? toolInput.command : typeof toolInput.cmd === "string" ? toolInput.cmd : "";
25821
+ if (isDevrouterLifecycleCommand(command)) {
25287
25822
  const decision2 = gateDecision("allow", "devrouter-command");
25288
25823
  process.stdout.write(
25289
25824
  json ? `${JSON.stringify(decision2)}
@@ -25305,7 +25840,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25305
25840
  }
25306
25841
  const budgetMs = parseHarnessGateBudget(options.waitBudgetMs);
25307
25842
  const toolUseId = typeof payload.tool_use_id === "string" && payload.tool_use_id.length > 0 && payload.tool_use_id.length <= MAX_TOOL_USE_ID_LENGTH2 ? payload.tool_use_id : void 0;
25308
- const payloadSha256 = (0, import_node_crypto27.createHash)("sha256").update(JSON.stringify(toolInput)).digest("hex");
25843
+ const payloadSha256 = (0, import_node_crypto28.createHash)("sha256").update(JSON.stringify(toolInput)).digest("hex");
25309
25844
  const claim2 = dependencies.claim ?? ((root, input2) => {
25310
25845
  const outcome = claimHarnessContinuation(root, input2);
25311
25846
  if (outcome.kind === "claimed") {
@@ -25413,17 +25948,20 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25413
25948
  `
25414
25949
  );
25415
25950
  }
25416
- var import_node_crypto27, MAX_HOOK_PAYLOAD_BYTES, STDIN_TIMEOUT_MS, MAX_TOOL_USE_ID_LENGTH2, DEVROUTER_COMMAND_RE;
25951
+ var import_node_crypto28, MAX_HOOK_PAYLOAD_BYTES, STDIN_TIMEOUT_MS, MAX_TOOL_USE_ID_LENGTH2, DEVROUTER_EXECUTABLES, LAUNCHER_WORDS, LAUNCHER_SUBCOMMANDS, COMMAND_WORD_LIMIT;
25417
25952
  var init_harness = __esm({
25418
25953
  "src/commands/harness.ts"() {
25419
25954
  "use strict";
25420
- import_node_crypto27 = require("crypto");
25955
+ import_node_crypto28 = require("crypto");
25421
25956
  init_harness_continuation();
25422
25957
  init_harness_gate();
25423
25958
  MAX_HOOK_PAYLOAD_BYTES = 256 * 1024;
25424
25959
  STDIN_TIMEOUT_MS = 5e3;
25425
25960
  MAX_TOOL_USE_ID_LENGTH2 = 256;
25426
- DEVROUTER_COMMAND_RE = /(?:^|[\s;&|()])(?:npx\s+|pnpm\s+(?:exec\s+)?|npm\s+(?:exec\s+)?|yarn\s+)?devrouter(?:-process)?(?:\s|$)/;
25961
+ DEVROUTER_EXECUTABLES = /* @__PURE__ */ new Set(["devrouter", "devrouter-process"]);
25962
+ LAUNCHER_WORDS = /* @__PURE__ */ new Set(["npx", "pnpm", "npm", "yarn"]);
25963
+ LAUNCHER_SUBCOMMANDS = /* @__PURE__ */ new Set(["exec", "dlx"]);
25964
+ COMMAND_WORD_LIMIT = 8;
25427
25965
  }
25428
25966
  });
25429
25967
 
@@ -25530,7 +26068,7 @@ async function runOpenCommand(name) {
25530
26068
  );
25531
26069
  return;
25532
26070
  }
25533
- const result = (0, import_node_child_process31.spawnSync)("open", [url], { encoding: "utf-8" });
26071
+ const result = (0, import_node_child_process32.spawnSync)("open", [url], { encoding: "utf-8" });
25534
26072
  if (result.status !== 0) {
25535
26073
  const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
25536
26074
  throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
@@ -25538,11 +26076,11 @@ async function runOpenCommand(name) {
25538
26076
  process.stdout.write(`Opened ${url}
25539
26077
  `);
25540
26078
  }
25541
- var import_node_child_process31;
26079
+ var import_node_child_process32;
25542
26080
  var init_open = __esm({
25543
26081
  "src/commands/open.ts"() {
25544
26082
  "use strict";
25545
- import_node_child_process31 = require("child_process");
26083
+ import_node_child_process32 = require("child_process");
25546
26084
  init_docker();
25547
26085
  init_host_routes();
25548
26086
  init_repo_config();
@@ -25565,7 +26103,7 @@ async function runLogsCommand(options) {
25565
26103
  const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
25566
26104
  if (options.follow) {
25567
26105
  args.splice(1, 0, "-f");
25568
- const child = (0, import_node_child_process32.spawn)("docker", args, { stdio: "inherit" });
26106
+ const child = (0, import_node_child_process33.spawn)("docker", args, { stdio: "inherit" });
25569
26107
  const onSignal = () => {
25570
26108
  child.kill("SIGTERM");
25571
26109
  };
@@ -25579,17 +26117,17 @@ async function runLogsCommand(options) {
25579
26117
  });
25580
26118
  });
25581
26119
  } else {
25582
- const result = (0, import_node_child_process32.spawnSync)("docker", args, { stdio: "inherit" });
26120
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, { stdio: "inherit" });
25583
26121
  if (result.status !== 0) {
25584
26122
  throw new Error("Failed to retrieve router logs.");
25585
26123
  }
25586
26124
  }
25587
26125
  }
25588
- var import_node_child_process32;
26126
+ var import_node_child_process33;
25589
26127
  var init_logs = __esm({
25590
26128
  "src/commands/logs.ts"() {
25591
26129
  "use strict";
25592
- import_node_child_process32 = require("child_process");
26130
+ import_node_child_process33 = require("child_process");
25593
26131
  init_docker();
25594
26132
  init_router();
25595
26133
  }
@@ -27538,7 +28076,7 @@ function sanitizeRouterId(value) {
27538
28076
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
27539
28077
  }
27540
28078
  function repoHash(repoPath) {
27541
- return (0, import_node_crypto28.createHash)("sha1").update(import_node_path64.default.resolve(repoPath)).digest("hex").slice(0, 12);
28079
+ return (0, import_node_crypto29.createHash)("sha1").update(import_node_path64.default.resolve(repoPath)).digest("hex").slice(0, 12);
27542
28080
  }
27543
28081
  function asDockerApp(app) {
27544
28082
  return app.runtime === "docker";
@@ -27635,7 +28173,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
27635
28173
  fileArgs.push("-f", resolved);
27636
28174
  }
27637
28175
  const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
27638
- const result = (0, import_node_child_process33.spawnSync)("docker", args, {
28176
+ const result = (0, import_node_child_process34.spawnSync)("docker", args, {
27639
28177
  encoding: "utf-8",
27640
28178
  cwd: repoPath
27641
28179
  });
@@ -27663,7 +28201,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
27663
28201
  "--services",
27664
28202
  ...services
27665
28203
  ];
27666
- const result = (0, import_node_child_process33.spawnSync)("docker", args, {
28204
+ const result = (0, import_node_child_process34.spawnSync)("docker", args, {
27667
28205
  encoding: "utf-8",
27668
28206
  cwd: repoPath
27669
28207
  });
@@ -27689,7 +28227,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
27689
28227
  fileArgs.push("-f", resolved);
27690
28228
  }
27691
28229
  const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
27692
- const result = (0, import_node_child_process33.spawnSync)("docker", args, {
28230
+ const result = (0, import_node_child_process34.spawnSync)("docker", args, {
27693
28231
  encoding: "utf-8",
27694
28232
  cwd: repoPath
27695
28233
  });
@@ -27715,7 +28253,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
27715
28253
  String(tail),
27716
28254
  ...services
27717
28255
  ];
27718
- (0, import_node_child_process33.spawnSync)("docker", args, {
28256
+ (0, import_node_child_process34.spawnSync)("docker", args, {
27719
28257
  stdio: "inherit",
27720
28258
  cwd: repoPath
27721
28259
  });
@@ -27727,7 +28265,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
27727
28265
  fileArgs.push("-f", resolved);
27728
28266
  }
27729
28267
  const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
27730
- const result = (0, import_node_child_process33.spawnSync)("docker", args, {
28268
+ const result = (0, import_node_child_process34.spawnSync)("docker", args, {
27731
28269
  encoding: "utf-8",
27732
28270
  cwd: repoPath
27733
28271
  });
@@ -27741,12 +28279,12 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
27741
28279
  const port = Number(match[1]);
27742
28280
  return Number.isInteger(port) && port > 0 ? port : void 0;
27743
28281
  }
27744
- var import_node_child_process33, import_node_crypto28, import_node_fs51, import_node_path64, import_yaml9;
28282
+ var import_node_child_process34, import_node_crypto29, import_node_fs51, import_node_path64, import_yaml9;
27745
28283
  var init_docker_run = __esm({
27746
28284
  "src/core/docker-run.ts"() {
27747
28285
  "use strict";
27748
- import_node_child_process33 = require("child_process");
27749
- import_node_crypto28 = require("crypto");
28286
+ import_node_child_process34 = require("child_process");
28287
+ import_node_crypto29 = require("crypto");
27750
28288
  import_node_fs51 = __toESM(require("fs"));
27751
28289
  import_node_path64 = __toESM(require("path"));
27752
28290
  import_yaml9 = __toESM(require("yaml"));
@@ -27838,7 +28376,7 @@ function isProcessRunning(pid) {
27838
28376
  }
27839
28377
  }
27840
28378
  function readProcessTree(rootPid) {
27841
- const result = (0, import_node_child_process34.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
28379
+ const result = (0, import_node_child_process35.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
27842
28380
  if (result.status !== 0) {
27843
28381
  return [rootPid];
27844
28382
  }
@@ -27949,12 +28487,12 @@ function detectListeningPorts(pids) {
27949
28487
  if (pids.length === 0) {
27950
28488
  return [];
27951
28489
  }
27952
- const result = (0, import_node_child_process34.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
28490
+ const result = (0, import_node_child_process35.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
27953
28491
  encoding: "utf-8"
27954
28492
  });
27955
28493
  if (result.error && result.error.code === "ENOENT") {
27956
28494
  if (process.platform === "linux") {
27957
- const ssResult = (0, import_node_child_process34.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
28495
+ const ssResult = (0, import_node_child_process35.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
27958
28496
  if (ssResult.status === 0 && ssResult.stdout) {
27959
28497
  return parseSsListeningPorts(ssResult.stdout, new Set(pids));
27960
28498
  }
@@ -28013,7 +28551,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
28013
28551
  app.hostRun.command,
28014
28552
  true
28015
28553
  ) : app.hostRun.command;
28016
- const child = (0, import_node_child_process34.spawn)(spawnCommand, {
28554
+ const child = (0, import_node_child_process35.spawn)(spawnCommand, {
28017
28555
  cwd: commandCwd,
28018
28556
  stdio: "inherit",
28019
28557
  shell: true,
@@ -28381,7 +28919,7 @@ async function execWithAppEnv(options) {
28381
28919
  options.command[0],
28382
28920
  true
28383
28921
  );
28384
- child = (0, import_node_child_process34.spawn)(wrapped, {
28922
+ child = (0, import_node_child_process35.spawn)(wrapped, {
28385
28923
  cwd: deps.repoPath,
28386
28924
  stdio: "inherit",
28387
28925
  shell: true,
@@ -28395,7 +28933,7 @@ async function execWithAppEnv(options) {
28395
28933
  false
28396
28934
  );
28397
28935
  const [cmd, ...wrappedArgs] = wrapped;
28398
- child = (0, import_node_child_process34.spawn)(cmd, wrappedArgs, {
28936
+ child = (0, import_node_child_process35.spawn)(cmd, wrappedArgs, {
28399
28937
  cwd: deps.repoPath,
28400
28938
  stdio: "inherit",
28401
28939
  shell: false,
@@ -28403,7 +28941,7 @@ async function execWithAppEnv(options) {
28403
28941
  });
28404
28942
  }
28405
28943
  } else if (options.shell) {
28406
- child = (0, import_node_child_process34.spawn)(options.command[0], {
28944
+ child = (0, import_node_child_process35.spawn)(options.command[0], {
28407
28945
  cwd: deps.repoPath,
28408
28946
  stdio: "inherit",
28409
28947
  shell: true,
@@ -28411,7 +28949,7 @@ async function execWithAppEnv(options) {
28411
28949
  });
28412
28950
  } else {
28413
28951
  const [command, ...args] = options.command;
28414
- child = (0, import_node_child_process34.spawn)(command, args, {
28952
+ child = (0, import_node_child_process35.spawn)(command, args, {
28415
28953
  cwd: deps.repoPath,
28416
28954
  stdio: "inherit",
28417
28955
  shell: false,
@@ -28432,11 +28970,11 @@ async function execWithAppEnv(options) {
28432
28970
  deps.stopDeps();
28433
28971
  }
28434
28972
  }
28435
- var import_node_child_process34, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
28973
+ var import_node_child_process35, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
28436
28974
  var init_app_run = __esm({
28437
28975
  "src/core/app-run.ts"() {
28438
28976
  "use strict";
28439
- import_node_child_process34 = require("child_process");
28977
+ import_node_child_process35 = require("child_process");
28440
28978
  import_node_net5 = __toESM(require("net"));
28441
28979
  import_node_process = require("process");
28442
28980
  import_promises = require("readline/promises");
@@ -29360,11 +29898,11 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
29360
29898
  networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
29361
29899
  };
29362
29900
  }
29363
- var import_node_child_process35, import_node_fs53, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
29901
+ var import_node_child_process36, import_node_fs53, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
29364
29902
  var init_workspace_cleanup = __esm({
29365
29903
  "src/core/workspace-cleanup.ts"() {
29366
29904
  "use strict";
29367
- import_node_child_process35 = require("child_process");
29905
+ import_node_child_process36 = require("child_process");
29368
29906
  import_node_fs53 = __toESM(require("fs"));
29369
29907
  init_devpod_workspaces();
29370
29908
  init_host_routes();
@@ -29383,7 +29921,7 @@ var init_workspace_cleanup = __esm({
29383
29921
  ];
29384
29922
  defaultCommandRunner = (command, args, input2) => {
29385
29923
  const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
29386
- const result = (0, import_node_child_process35.spawnSync)(command, args, {
29924
+ const result = (0, import_node_child_process36.spawnSync)(command, args, {
29387
29925
  encoding: "utf-8",
29388
29926
  env: environment,
29389
29927
  ...input2 === void 0 ? {} : { input: input2 }
@@ -29480,7 +30018,7 @@ function defaultWorktreePath(mainRepo, ws) {
29480
30018
  return import_node_path66.default.join(mainRepo, "trees", ws);
29481
30019
  }
29482
30020
  function assertDefaultWorktreeRootIgnored(mainRepo) {
29483
- const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
30021
+ const ignored = (0, import_node_child_process37.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
29484
30022
  encoding: "utf-8"
29485
30023
  });
29486
30024
  if (ignored.status !== 0) {
@@ -29608,7 +30146,7 @@ function assertFullDownPreflight(mainRepo, target) {
29608
30146
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
29609
30147
  );
29610
30148
  }
29611
- const status = (0, import_node_child_process36.spawnSync)(
30149
+ const status = (0, import_node_child_process37.spawnSync)(
29612
30150
  "git",
29613
30151
  ["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
29614
30152
  { encoding: "utf-8" }
@@ -29672,11 +30210,11 @@ async function workspaceUp(branch, opts = {}) {
29672
30210
  return selectedPath;
29673
30211
  }
29674
30212
  if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
29675
- const add = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
30213
+ const add = (0, import_node_child_process37.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
29676
30214
  encoding: "utf-8"
29677
30215
  });
29678
30216
  if (add.status !== 0) {
29679
- const addNew = (0, import_node_child_process36.spawnSync)(
30217
+ const addNew = (0, import_node_child_process37.spawnSync)(
29680
30218
  "git",
29681
30219
  ["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
29682
30220
  { encoding: "utf-8" }
@@ -29817,7 +30355,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
29817
30355
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
29818
30356
  if (removeWorktree) {
29819
30357
  if (resolved.worktree && !resolved.worktree.prunable && import_node_fs54.default.existsSync(resolved.worktreePath)) {
29820
- const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
30358
+ const rm = (0, import_node_child_process37.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
29821
30359
  encoding: "utf-8"
29822
30360
  });
29823
30361
  if (rm.status !== 0) {
@@ -29843,11 +30381,11 @@ async function workspaceStop(target, opts = {}) {
29843
30381
  async function workspaceDown(target, opts = {}) {
29844
30382
  return runWorkspaceLifecycle("down", target, opts);
29845
30383
  }
29846
- var import_node_child_process36, import_node_fs54, import_node_path66, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
30384
+ var import_node_child_process37, import_node_fs54, import_node_path66, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
29847
30385
  var init_workspace_lifecycle = __esm({
29848
30386
  "src/core/workspace-lifecycle.ts"() {
29849
30387
  "use strict";
29850
- import_node_child_process36 = require("child_process");
30388
+ import_node_child_process37 = require("child_process");
29851
30389
  import_node_fs54 = __toESM(require("fs"));
29852
30390
  import_node_path66 = __toESM(require("path"));
29853
30391
  init_devpod_mutation();
@@ -30033,7 +30571,7 @@ var init_version = __esm({
30033
30571
 
30034
30572
  // src/cli.ts
30035
30573
  var import_commander = require("commander");
30036
- var CLI_VERSION = true ? "0.1.1" : "0.0.0-dev";
30574
+ var CLI_VERSION = true ? "0.1.3" : "0.0.0-dev";
30037
30575
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
30038
30576
  function withErrorHandling(action2) {
30039
30577
  return async (...args) => {