@devrouter/cli 0.1.0 → 0.1.2

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.0" : "0.0.0-dev";
4278
+ const cliVersion = true ? "0.1.2" : "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.0" : "0.0.0-dev";
6276
+ return true ? "0.1.2" : "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`);
@@ -6574,12 +6588,20 @@ function validate2(record5, identity2) {
6574
6588
  }
6575
6589
  }
6576
6590
  }
6591
+ function journalEntryLocation(name) {
6592
+ const printable = name.replace(/[^A-Za-z0-9._-]/g, "?");
6593
+ return printable.length > MAX_REPORTED_ENTRY_NAME ? `${printable.slice(0, MAX_REPORTED_ENTRY_NAME - 3)}...` : printable;
6594
+ }
6577
6595
  function createLifecycleCapacityStore(directory = import_node_path13.default.join(DEVROUTER_HOME, "controller")) {
6578
6596
  return new CapacityStore(directory, void 0, () => {
6579
6597
  try {
6580
6598
  return listUnsettledCapacityBindings().floor;
6581
- } catch {
6582
- throw new CapacityHistoryError("capacity-history-unprovable");
6599
+ } catch (error) {
6600
+ throw error instanceof ReliabilityJournalError ? new CapacityHistoryError("capacity-history-unprovable", error.cause, error.location) : new CapacityHistoryError(
6601
+ "capacity-history-unprovable",
6602
+ "journal-enumeration-failed",
6603
+ null
6604
+ );
6583
6605
  }
6584
6606
  });
6585
6607
  }
@@ -6638,11 +6660,12 @@ function readBoundedPrivateJson(file, label2) {
6638
6660
  try {
6639
6661
  const stat = import_node_fs11.default.fstatSync(descriptor);
6640
6662
  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.`);
6663
+ throw new JournalFileGuardError(`${label2} is not a bounded private file.`);
6642
6664
  }
6643
6665
  const bytes2 = Buffer.alloc(MAX_RECORD_BYTES + 1);
6644
6666
  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.`);
6667
+ if (count > MAX_RECORD_BYTES)
6668
+ throw new JournalFileGuardError(`${label2} exceeds its byte limit.`);
6646
6669
  return JSON.parse(bytes2.subarray(0, count).toString("utf8"));
6647
6670
  } finally {
6648
6671
  import_node_fs11.default.closeSync(descriptor);
@@ -6650,7 +6673,11 @@ function readBoundedPrivateJson(file, label2) {
6650
6673
  }
6651
6674
  function assertReliabilityEnumerationTime(startedAtMs) {
6652
6675
  if (performance.now() - startedAtMs > MAX_RELIABILITY_ENUMERATION_MS)
6653
- throw new Error("Reliability journal enumeration exceeded its time limit.");
6676
+ throw new ReliabilityJournalError(
6677
+ "journal-enumeration-timeout",
6678
+ null,
6679
+ "Reliability journal enumeration exceeded its time limit."
6680
+ );
6654
6681
  }
6655
6682
  function reliabilityDirectory() {
6656
6683
  return import_node_path13.default.join(DEVROUTER_HOME, "reliability");
@@ -6671,22 +6698,56 @@ function listReliabilityOperations() {
6671
6698
  stat = import_node_fs11.default.lstatSync(directory);
6672
6699
  } catch (error) {
6673
6700
  if (error.code === "ENOENT") return [];
6674
- throw error;
6701
+ throw new ReliabilityJournalError(
6702
+ "journal-directory-unreadable",
6703
+ null,
6704
+ "Reliability journal directory could not be read."
6705
+ );
6675
6706
  }
6676
6707
  if (!stat.isDirectory() || stat.isSymbolicLink())
6677
- throw new Error("Reliability journal directory is unsafe.");
6708
+ throw new ReliabilityJournalError(
6709
+ "journal-directory-unsafe",
6710
+ null,
6711
+ "Reliability journal directory is unsafe."
6712
+ );
6678
6713
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
6679
- throw new Error("Reliability journal directory is not private.");
6714
+ throw new ReliabilityJournalError(
6715
+ "journal-directory-unsafe",
6716
+ null,
6717
+ "Reliability journal directory is not private."
6718
+ );
6680
6719
  const entries = [];
6681
- const handle = import_node_fs11.default.opendirSync(directory);
6720
+ let handle;
6721
+ try {
6722
+ handle = import_node_fs11.default.opendirSync(directory);
6723
+ } catch {
6724
+ throw new ReliabilityJournalError(
6725
+ "journal-directory-unreadable",
6726
+ null,
6727
+ "Reliability journal directory could not be read."
6728
+ );
6729
+ }
6682
6730
  try {
6683
6731
  for (; ; ) {
6684
6732
  assertReliabilityEnumerationTime(startedAtMs);
6685
- const entry = handle.readSync();
6733
+ let entry;
6734
+ try {
6735
+ entry = handle.readSync();
6736
+ } catch {
6737
+ throw new ReliabilityJournalError(
6738
+ "journal-directory-unreadable",
6739
+ null,
6740
+ "Reliability journal directory could not be read."
6741
+ );
6742
+ }
6686
6743
  if (!entry) break;
6687
6744
  entries.push(entry);
6688
6745
  if (entries.length > MAX_RELIABILITY_DIRECTORY_ENTRIES)
6689
- throw new Error("Reliability journal directory exceeds its entry limit.");
6746
+ throw new ReliabilityJournalError(
6747
+ "journal-enumeration-limit",
6748
+ null,
6749
+ "Reliability journal directory exceeds its entry limit."
6750
+ );
6690
6751
  }
6691
6752
  } finally {
6692
6753
  handle.closeSync();
@@ -6695,27 +6756,82 @@ function listReliabilityOperations() {
6695
6756
  for (const entry of entries) {
6696
6757
  assertReliabilityEnumerationTime(startedAtMs);
6697
6758
  if (entry.isSymbolicLink())
6698
- throw new Error("Reliability journal directory contains a symlink.");
6759
+ throw new ReliabilityJournalError(
6760
+ "journal-entry-unsupported",
6761
+ journalEntryLocation(entry.name),
6762
+ "Reliability journal directory contains a symlink."
6763
+ );
6699
6764
  if (!entry.isFile())
6700
- throw new Error("Reliability journal directory contains an unsupported entry.");
6765
+ throw new ReliabilityJournalError(
6766
+ "journal-entry-unsupported",
6767
+ journalEntryLocation(entry.name),
6768
+ "Reliability journal directory contains an unsupported entry."
6769
+ );
6701
6770
  if (RELIABILITY_LOCK_NAME_RE.test(entry.name) || RELIABILITY_ATOMIC_TEMP_NAME_RE.test(entry.name))
6702
6771
  continue;
6703
6772
  const match = RELIABILITY_JOURNAL_NAME_RE.exec(entry.name);
6704
- if (!match) throw new Error("Reliability journal directory contains an unsupported entry.");
6773
+ if (!match)
6774
+ throw new ReliabilityJournalError(
6775
+ "journal-entry-unsupported",
6776
+ journalEntryLocation(entry.name),
6777
+ "Reliability journal directory contains an unsupported entry."
6778
+ );
6705
6779
  journals.push({ name: entry.name, key: match[1] });
6706
6780
  }
6707
6781
  if (journals.length > MAX_RELIABILITY_JOURNALS)
6708
- throw new Error("Reliability journal directory exceeds its journal limit.");
6782
+ throw new ReliabilityJournalError(
6783
+ "journal-enumeration-limit",
6784
+ null,
6785
+ "Reliability journal directory exceeds its journal limit."
6786
+ );
6709
6787
  journals.sort((left, right) => left.name.localeCompare(right.name));
6710
6788
  const records = [];
6711
6789
  for (const journal of journals) {
6712
6790
  assertReliabilityEnumerationTime(startedAtMs);
6713
6791
  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.");
6792
+ let raw;
6793
+ try {
6794
+ raw = readBoundedPrivateJson(file, "Reliability journal");
6795
+ } catch (error) {
6796
+ const fsCode = error?.code;
6797
+ const cause = error instanceof JournalFileGuardError ? "journal-entry-unsafe" : error instanceof SyntaxError || !fsCode ? "journal-invalid" : "journal-unreadable";
6798
+ throw new ReliabilityJournalError(
6799
+ cause,
6800
+ journalEntryLocation(journal.name),
6801
+ error instanceof JournalFileGuardError ? error.message : cause === "journal-invalid" ? "Reliability journal entry is invalid." : "Reliability journal entry could not be read."
6802
+ );
6803
+ }
6804
+ if (raw === void 0)
6805
+ throw new ReliabilityJournalError(
6806
+ "journal-unstable",
6807
+ journalEntryLocation(journal.name),
6808
+ "Reliability journal disappeared during enumeration."
6809
+ );
6810
+ let identity2;
6811
+ let record5;
6812
+ try {
6813
+ identity2 = journalIdentity(raw);
6814
+ if (identityKey(identity2) !== journal.key)
6815
+ throw new ReliabilityJournalError(
6816
+ "journal-identity-mismatch",
6817
+ journalEntryLocation(journal.name),
6818
+ "Reliability journal filename does not match its identity."
6819
+ );
6820
+ record5 = readReliabilityOperation(identity2);
6821
+ } catch (error) {
6822
+ if (error instanceof ReliabilityJournalError) throw error;
6823
+ throw new ReliabilityJournalError(
6824
+ "journal-invalid",
6825
+ journalEntryLocation(journal.name),
6826
+ "Reliability journal entry is invalid."
6827
+ );
6828
+ }
6829
+ if (!record5)
6830
+ throw new ReliabilityJournalError(
6831
+ "journal-unstable",
6832
+ journalEntryLocation(journal.name),
6833
+ "Reliability journal disappeared during enumeration."
6834
+ );
6719
6835
  records.push(record5);
6720
6836
  assertReliabilityEnumerationTime(startedAtMs);
6721
6837
  }
@@ -6872,11 +6988,11 @@ function setReliabilityHumanPin(identity2, expectedJournalRevision, expectedProt
6872
6988
  };
6873
6989
  });
6874
6990
  }
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;
6991
+ 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
6992
  var init_reliability_operation_store = __esm({
6877
6993
  "src/core/reliability-operation-store.ts"() {
6878
6994
  "use strict";
6879
- import_node_crypto7 = require("crypto");
6995
+ import_node_crypto8 = require("crypto");
6880
6996
  import_node_fs11 = __toESM(require("fs"));
6881
6997
  import_node_path13 = __toESM(require("path"));
6882
6998
  import_node_util3 = require("util");
@@ -6893,6 +7009,21 @@ var init_reliability_operation_store = __esm({
6893
7009
  RELIABILITY_JOURNAL_NAME_RE = /^([0-9a-f]{64})\.json$/;
6894
7010
  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
7011
  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$/;
7012
+ ReliabilityJournalError = class extends Error {
7013
+ constructor(cause, location, message) {
7014
+ super(message);
7015
+ this.cause = cause;
7016
+ this.location = location;
7017
+ this.name = "ReliabilityJournalError";
7018
+ }
7019
+ };
7020
+ MAX_REPORTED_ENTRY_NAME = 160;
7021
+ JournalFileGuardError = class extends Error {
7022
+ constructor(message) {
7023
+ super(message);
7024
+ this.name = "JournalFileGuardError";
7025
+ }
7026
+ };
6896
7027
  }
6897
7028
  });
6898
7029
 
@@ -7258,7 +7389,7 @@ var init_managed_stop_baseline = __esm({
7258
7389
 
7259
7390
  // src/core/managed-runtime-state.ts
7260
7391
  function stateKey(repoPath, workspace) {
7261
- return (0, import_node_crypto8.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
7392
+ return (0, import_node_crypto9.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
7262
7393
  }
7263
7394
  function managedRuntimeStatePath(repoPath, workspace) {
7264
7395
  return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
@@ -7337,11 +7468,11 @@ function readManagedRuntimeState(repoPath, workspace, read2 = (file) => import_n
7337
7468
  }
7338
7469
  return validateState(parsed, repoPath, workspace);
7339
7470
  }
7340
- var import_node_crypto8, import_node_fs13, import_node_path17;
7471
+ var import_node_crypto9, import_node_fs13, import_node_path17;
7341
7472
  var init_managed_runtime_state = __esm({
7342
7473
  "src/core/managed-runtime-state.ts"() {
7343
7474
  "use strict";
7344
- import_node_crypto8 = require("crypto");
7475
+ import_node_crypto9 = require("crypto");
7345
7476
  import_node_fs13 = __toESM(require("fs"));
7346
7477
  import_node_path17 = __toESM(require("path"));
7347
7478
  init_atomic_file();
@@ -8416,7 +8547,7 @@ async function observeControllerBinding(directory, input2) {
8416
8547
  };
8417
8548
  }
8418
8549
  async function controllerRequest(directory, input2, emit, timeoutMs) {
8419
- const request2 = parseControllerRequest({ ...input2, version: 1, id: (0, import_node_crypto9.randomUUID)() });
8550
+ const request2 = parseControllerRequest({ ...input2, version: 1, id: (0, import_node_crypto10.randomUUID)() });
8420
8551
  const socketPath = import_node_path20.default.join(directory, "control.sock");
8421
8552
  const stat = import_node_fs14.default.lstatSync(socketPath);
8422
8553
  if (!stat.isSocket() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
@@ -8426,7 +8557,7 @@ async function controllerRequest(directory, input2, emit, timeoutMs) {
8426
8557
  let buffer = Buffer.alloc(0);
8427
8558
  let handshake = false;
8428
8559
  let completed = false;
8429
- const handshakeId = (0, import_node_crypto9.randomUUID)();
8560
+ const handshakeId = (0, import_node_crypto10.randomUUID)();
8430
8561
  const timeout = request2.method === "watch" || request2.method === "operation-watch" ? Math.min(request2.timeout + 5, 2147483) * 1e3 : 5e3;
8431
8562
  const timer = setTimeout(
8432
8563
  () => {
@@ -8492,11 +8623,11 @@ async function controllerRequest(directory, input2, emit, timeoutMs) {
8492
8623
  });
8493
8624
  });
8494
8625
  }
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;
8626
+ 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
8627
  var init_controller_client = __esm({
8497
8628
  "src/core/controller-client.ts"() {
8498
8629
  "use strict";
8499
- import_node_crypto9 = require("crypto");
8630
+ import_node_crypto10 = require("crypto");
8500
8631
  import_node_fs14 = __toESM(require("fs"));
8501
8632
  import_node_net = __toESM(require("net"));
8502
8633
  import_node_path20 = __toESM(require("path"));
@@ -8632,11 +8763,11 @@ var init_reliability_output = __esm({
8632
8763
 
8633
8764
  // src/core/controller-monitor.ts
8634
8765
  function controllerCapability(selector) {
8635
- return selector === "runtime" ? "runtime" : `app-${(0, import_node_crypto10.createHash)("sha256").update(selector).digest("hex")}`;
8766
+ return selector === "runtime" ? "runtime" : `app-${(0, import_node_crypto11.createHash)("sha256").update(selector).digest("hex")}`;
8636
8767
  }
8637
8768
  function sessionConsumerId(input2) {
8638
8769
  const { store, epoch, session, generation } = input2;
8639
- return `session-${(0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([store, epoch, session, generation])).digest("hex")}`;
8770
+ return `session-${(0, import_node_crypto11.createHash)("sha256").update(JSON.stringify([store, epoch, session, generation])).digest("hex")}`;
8640
8771
  }
8641
8772
  function environmentIdentity(environment) {
8642
8773
  return {
@@ -8657,11 +8788,11 @@ function parkingConsumers(consumers) {
8657
8788
  consentRevision
8658
8789
  })).sort((a, b) => a.id.localeCompare(b.id));
8659
8790
  }
8660
- var import_node_crypto10, import_node_util7, ControllerObservationBindingChanged, UNFINISHED_PARK_SCAN_MS, ControllerMonitor;
8791
+ var import_node_crypto11, import_node_util7, ControllerObservationBindingChanged, UNFINISHED_PARK_SCAN_MS, ControllerMonitor;
8661
8792
  var init_controller_monitor = __esm({
8662
8793
  "src/core/controller-monitor.ts"() {
8663
8794
  "use strict";
8664
- import_node_crypto10 = require("crypto");
8795
+ import_node_crypto11 = require("crypto");
8665
8796
  import_node_util7 = require("util");
8666
8797
  init_reliability_operation_store();
8667
8798
  init_reliability_output();
@@ -8935,7 +9066,7 @@ var init_controller_monitor = __esm({
8935
9066
  const projections = /* @__PURE__ */ new Map();
8936
9067
  for (const consumer of consumers) {
8937
9068
  const transient = structuredClone(journal.state);
8938
- const consumerId = `observer-${(0, import_node_crypto10.createHash)("sha256").update(consumer.id).digest("hex")}`;
9069
+ const consumerId = `observer-${(0, import_node_crypto11.createHash)("sha256").update(consumer.id).digest("hex")}`;
8939
9070
  transient.consumers = [
8940
9071
  {
8941
9072
  id: consumerId,
@@ -9353,7 +9484,7 @@ function writeManifestRecord(cacheRoot, manifest) {
9353
9484
  import_node_fs16.default.mkdirSync(directory, { recursive: true, mode: 448 });
9354
9485
  const temporary = import_node_path22.default.join(
9355
9486
  directory,
9356
- `.${DEVSY_AGENT_MANIFEST_FILE}.${process.pid}.${(0, import_node_crypto11.randomUUID)()}.tmp`
9487
+ `.${DEVSY_AGENT_MANIFEST_FILE}.${process.pid}.${(0, import_node_crypto12.randomUUID)()}.tmp`
9357
9488
  );
9358
9489
  const record5 = {
9359
9490
  version: 1,
@@ -9451,7 +9582,7 @@ function managedBinaryPath(cacheRoot, devsyVersion, asset) {
9451
9582
  return import_node_path22.default.join(cacheRoot, `v${devsyVersion}`, asset.name);
9452
9583
  }
9453
9584
  function hashOpenFile(file) {
9454
- const digest3 = (0, import_node_crypto11.createHash)("sha256");
9585
+ const digest3 = (0, import_node_crypto12.createHash)("sha256");
9455
9586
  const buffer = Buffer.allocUnsafe(1024 * 1024);
9456
9587
  let position = 0;
9457
9588
  for (; ; ) {
@@ -9647,7 +9778,7 @@ async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloade
9647
9778
  import_node_fs16.default.mkdirSync(directory, { recursive: true, mode: 448 });
9648
9779
  const temporaryPath = import_node_path22.default.join(
9649
9780
  directory,
9650
- `.${import_node_path22.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto11.randomUUID)()}.tmp`
9781
+ `.${import_node_path22.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto12.randomUUID)()}.tmp`
9651
9782
  );
9652
9783
  let handle;
9653
9784
  try {
@@ -9659,7 +9790,7 @@ async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloade
9659
9790
  directFailure = error;
9660
9791
  }
9661
9792
  handle = await import_node_fs16.default.promises.open(temporaryPath, "wx", 384);
9662
- const digest3 = (0, import_node_crypto11.createHash)("sha256");
9793
+ const digest3 = (0, import_node_crypto12.createHash)("sha256");
9663
9794
  let size = 0;
9664
9795
  const writeChunk = async (chunk) => {
9665
9796
  size += chunk.byteLength;
@@ -9795,12 +9926,12 @@ async function prepareDevsyAgent(options = {}) {
9795
9926
  return { ...preparedFromInspection(published), changed: true, transport };
9796
9927
  });
9797
9928
  }
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;
9929
+ 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
9930
  var init_devsy_agent = __esm({
9800
9931
  "src/core/devsy-agent.ts"() {
9801
9932
  "use strict";
9802
9933
  import_node_child_process7 = require("child_process");
9803
- import_node_crypto11 = require("crypto");
9934
+ import_node_crypto12 = require("crypto");
9804
9935
  import_node_fs16 = __toESM(require("fs"));
9805
9936
  import_node_path22 = __toESM(require("path"));
9806
9937
  init_file_lock();
@@ -10037,7 +10168,7 @@ function isWildcard(values) {
10037
10168
  return values?.length === 1 && values[0] === "*";
10038
10169
  }
10039
10170
  function sha256(contents) {
10040
- return (0, import_node_crypto12.createHash)("sha256").update(contents, "utf-8").digest("hex");
10171
+ return (0, import_node_crypto13.createHash)("sha256").update(contents, "utf-8").digest("hex");
10041
10172
  }
10042
10173
  function safeComposeProject(value) {
10043
10174
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
@@ -10260,12 +10391,12 @@ function stopExactManagedService(containerId, service, options) {
10260
10391
  throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
10261
10392
  }
10262
10393
  }
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;
10394
+ 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
10395
  var init_devcontainer_profile = __esm({
10265
10396
  "src/core/devcontainer-profile.ts"() {
10266
10397
  "use strict";
10267
10398
  import_node_child_process8 = require("child_process");
10268
- import_node_crypto12 = require("crypto");
10399
+ import_node_crypto13 = require("crypto");
10269
10400
  import_node_fs18 = __toESM(require("fs"));
10270
10401
  import_node_path25 = __toESM(require("path"));
10271
10402
  import_yaml3 = __toESM(require("yaml"));
@@ -11751,7 +11882,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
11751
11882
  return normalizeUniqueHosts(names);
11752
11883
  }
11753
11884
  function parseCertificateDnsHosts(pem) {
11754
- const certificate = new import_node_crypto13.X509Certificate(pem);
11885
+ const certificate = new import_node_crypto14.X509Certificate(pem);
11755
11886
  const subjectAltName = certificate.subjectAltName ?? "";
11756
11887
  if (subjectAltName.length === 0) {
11757
11888
  return [];
@@ -11951,12 +12082,12 @@ Run: ${tlsSetupCommand(options.repoPath)}`
11951
12082
  );
11952
12083
  }
11953
12084
  }
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;
12085
+ 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
12086
  var init_tls = __esm({
11956
12087
  "src/core/tls.ts"() {
11957
12088
  "use strict";
11958
12089
  import_node_child_process14 = require("child_process");
11959
- import_node_crypto13 = require("crypto");
12090
+ import_node_crypto14 = require("crypto");
11960
12091
  import_node_fs23 = __toESM(require("fs"));
11961
12092
  import_node_path31 = __toESM(require("path"));
11962
12093
  init_docker();
@@ -13515,7 +13646,7 @@ function validateRoot(root) {
13515
13646
  return resolved;
13516
13647
  }
13517
13648
  function hashKey(value) {
13518
- return (0, import_node_crypto14.createHash)("sha256").update(value).digest("hex");
13649
+ return (0, import_node_crypto15.createHash)("sha256").update(value).digest("hex");
13519
13650
  }
13520
13651
  function cloneFence(fence) {
13521
13652
  return { ...fence };
@@ -13538,11 +13669,11 @@ function isErrno2(error, code) {
13538
13669
  function claimError(code, message) {
13539
13670
  return new NetworkClaimError(code, message);
13540
13671
  }
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;
13672
+ 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
13673
  var init_network_claims = __esm({
13543
13674
  "src/core/network-claims.ts"() {
13544
13675
  "use strict";
13545
- import_node_crypto14 = require("crypto");
13676
+ import_node_crypto15 = require("crypto");
13546
13677
  import_node_fs27 = __toESM(require("fs"));
13547
13678
  import_node_path35 = __toESM(require("path"));
13548
13679
  init_atomic_file();
@@ -13595,7 +13726,7 @@ var init_network_claims = __esm({
13595
13726
  function networkOwnerKey(repoPath) {
13596
13727
  if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
13597
13728
  throw new Error("Invalid network owner path.");
13598
- return (0, import_node_crypto15.createHash)("sha256").update(repoPath).digest("hex");
13729
+ return (0, import_node_crypto16.createHash)("sha256").update(repoPath).digest("hex");
13599
13730
  }
13600
13731
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13601
13732
  let names;
@@ -13635,7 +13766,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13635
13766
  } finally {
13636
13767
  import_node_fs28.default.closeSync(descriptor);
13637
13768
  }
13638
- if (typeof value.daemonId !== "string" || `${(0, import_node_crypto15.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
13769
+ if (typeof value.daemonId !== "string" || `${(0, import_node_crypto16.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
13639
13770
  throw new Error("Retained network daemon identity is invalid.");
13640
13771
  const claim2 = readNetworkClaim({
13641
13772
  daemonId: value.daemonId,
@@ -13649,11 +13780,11 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13649
13780
  }
13650
13781
  return found;
13651
13782
  }
13652
- var import_node_crypto15, import_node_fs28, import_node_path36;
13783
+ var import_node_crypto16, import_node_fs28, import_node_path36;
13653
13784
  var init_network_claim_lookup = __esm({
13654
13785
  "src/core/network-claim-lookup.ts"() {
13655
13786
  "use strict";
13656
- import_node_crypto15 = require("crypto");
13787
+ import_node_crypto16 = require("crypto");
13657
13788
  import_node_fs28 = __toESM(require("fs"));
13658
13789
  import_node_path36 = __toESM(require("path"));
13659
13790
  init_network_claims();
@@ -13680,7 +13811,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
13680
13811
  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
13812
  throw new Error("Docker provider definition is not qualified for network allocation.");
13682
13813
  }
13683
- return (0, import_node_crypto16.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
13814
+ return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
13684
13815
  }
13685
13816
  function read(provider, args) {
13686
13817
  const result = (0, import_node_child_process16.spawnSync)(provider, args, {
@@ -13772,12 +13903,12 @@ function inspectNetworkProviderBinding(input2) {
13772
13903
  );
13773
13904
  }
13774
13905
  }
13775
- var import_node_child_process16, import_node_crypto16;
13906
+ var import_node_child_process16, import_node_crypto17;
13776
13907
  var init_network_provider_inspect = __esm({
13777
13908
  "src/core/network-provider-inspect.ts"() {
13778
13909
  "use strict";
13779
13910
  import_node_child_process16 = require("child_process");
13780
- import_node_crypto16 = require("crypto");
13911
+ import_node_crypto17 = require("crypto");
13781
13912
  init_devpod_environment();
13782
13913
  init_devsy_agent();
13783
13914
  }
@@ -15095,14 +15226,14 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
15095
15226
  }
15096
15227
  }
15097
15228
  function newLifecycleIds() {
15098
- return { requestId: (0, import_node_crypto17.randomUUID)(), operationId: (0, import_node_crypto17.randomUUID)(), workerId: (0, import_node_crypto17.randomUUID)() };
15229
+ return { requestId: (0, import_node_crypto18.randomUUID)(), operationId: (0, import_node_crypto18.randomUUID)(), workerId: (0, import_node_crypto18.randomUUID)() };
15099
15230
  }
15100
- var import_node_child_process18, import_node_crypto17, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
15231
+ var import_node_child_process18, import_node_crypto18, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
15101
15232
  var init_reliability_worker = __esm({
15102
15233
  "src/core/reliability-worker.ts"() {
15103
15234
  "use strict";
15104
15235
  import_node_child_process18 = require("child_process");
15105
- import_node_crypto17 = require("crypto");
15236
+ import_node_crypto18 = require("crypto");
15106
15237
  import_node_fs30 = __toESM(require("fs"));
15107
15238
  import_node_path38 = __toESM(require("path"));
15108
15239
  init_file_lock();
@@ -15790,7 +15921,7 @@ function readRegularFile(filePath) {
15790
15921
  return readRegularFileBytes(filePath)?.toString("utf-8");
15791
15922
  }
15792
15923
  function adapterFingerprint(adapter) {
15793
- return (0, import_node_crypto18.createHash)("sha256").update(adapter).digest("hex");
15924
+ return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
15794
15925
  }
15795
15926
  function resolveManagedPostStartPlan(repoPath) {
15796
15927
  const adapterPath = import_node_path41.default.join(repoPath, MANAGED_ADAPTER_PATH);
@@ -15880,12 +16011,12 @@ function runManagedProcessAction(options) {
15880
16011
  }
15881
16012
  return "drifted";
15882
16013
  }
15883
- var import_node_child_process23, import_node_crypto18, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
16014
+ var import_node_child_process23, import_node_crypto19, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
15884
16015
  var init_managed_post_start = __esm({
15885
16016
  "src/core/managed-post-start.ts"() {
15886
16017
  "use strict";
15887
16018
  import_node_child_process23 = require("child_process");
15888
- import_node_crypto18 = require("crypto");
16019
+ import_node_crypto19 = require("crypto");
15889
16020
  import_node_fs33 = __toESM(require("fs"));
15890
16021
  import_node_path41 = __toESM(require("path"));
15891
16022
  init_devcontainer_config();
@@ -15897,24 +16028,45 @@ var init_managed_post_start = __esm({
15897
16028
  });
15898
16029
 
15899
16030
  // src/core/managed-runtime-status.ts
16031
+ function recoveryPath(repoPath) {
16032
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(repoPath)) return repoPath;
16033
+ return `'${repoPath.replaceAll("'", "'\\''")}'`;
16034
+ }
15900
16035
  function reliabilityRecovery(reason, repoPath) {
16036
+ const checkout = recoveryPath(repoPath);
15901
16037
  switch (reason) {
15902
16038
  case "stop-incomplete":
15903
16039
  case "operation-unknown":
15904
- return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
16040
+ return [`Run: devrouter stop ${checkout}`, `Run: devrouter ensure ${checkout} --repair`];
16041
+ case "start-refused":
16042
+ return [
16043
+ `Run: devrouter doctor --repo ${checkout} to read the refused fixed host-port claim and its holder`,
16044
+ `Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${checkout}`
16045
+ ];
15905
16046
  case "unadmittable":
15906
16047
  return [
15907
- `Run: devrouter stop ${repoPath}`,
15908
- `Lower the requested capacity in .devrouter.yml, then run: devrouter ensure ${repoPath}`
16048
+ `Run: devrouter stop ${checkout}`,
16049
+ `Lower the requested capacity in .devrouter.yml, then run: devrouter ensure ${checkout}`
15909
16050
  ];
15910
16051
  case "capacity-parked":
15911
- return [`Run: devrouter ensure ${repoPath} (waits for capacity to free up)`];
16052
+ return [
16053
+ "Parked for host capacity: the enrolled foreground controller resumes this checkout after its pressure dwell and live-consumer checks pass.",
16054
+ `Inspect the parked intent with: devrouter status --repo ${checkout} --json`,
16055
+ `'devrouter ensure' refuses while the journal stays parked; release the intent instead with: devrouter stop ${checkout}`
16056
+ ];
15912
16057
  case "capacity-waiting":
15913
- return [`Run: devrouter ensure ${repoPath} (still queued until capacity frees up)`];
16058
+ return [`Run: devrouter ensure ${checkout} (still queued until capacity frees up)`];
15914
16059
  default:
15915
16060
  return [];
15916
16061
  }
15917
16062
  }
16063
+ function refusedManagedStart(result) {
16064
+ if (result?.ok !== true) return false;
16065
+ const value = result.value;
16066
+ if (typeof value !== "object" || value === null) return false;
16067
+ const conflicts = value.hostPortConflicts;
16068
+ return Array.isArray(conflicts) && conflicts.length > 0;
16069
+ }
15918
16070
  function readReliabilityStatus(options) {
15919
16071
  let provider;
15920
16072
  try {
@@ -15934,7 +16086,7 @@ function readReliabilityStatus(options) {
15934
16086
  return void 0;
15935
16087
  }
15936
16088
  if (!record5) return void 0;
15937
- const reason = reliabilityAttention(record5.state);
16089
+ const reason = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
15938
16090
  return {
15939
16091
  desired: record5.state.desired,
15940
16092
  phase: record5.state.phase,
@@ -17007,11 +17159,13 @@ function networkCapacityCheck(report) {
17007
17159
  (pool) => pool.candidates.some((candidate) => candidate.overlappingNetworkIds.length === 0)
17008
17160
  );
17009
17161
  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.` : "";
17162
+ const blockers = report.allocation.blockers.slice(0, 4).map((blocker) => blocker.slice(0, 120));
17163
+ const blockerDetails = blockers.length > 0 ? ` Missing evidence: ${blockers.join("; ")}.` : "";
17010
17164
  return {
17011
17165
  id: "global.network-capacity",
17012
17166
  level: legacyAvailable ? "ok" : "warn",
17013
17167
  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.",
17014
- details: `${report.pools.length} pool(s); ${retained} network(s) have no active endpoints but retain container references. Allocation readiness: ${report.allocation.status}.${managedDetails}`,
17168
+ details: `${report.pools.length} pool(s); ${retained} network(s) have no active endpoints but retain container references. Allocation readiness: ${report.allocation.status}.${blockerDetails}${managedDetails}`,
17015
17169
  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."
17016
17170
  };
17017
17171
  }
@@ -17029,21 +17183,21 @@ var init_network_diagnostics = __esm({
17029
17183
  });
17030
17184
 
17031
17185
  // src/core/network-compose.ts
17032
- var import_node_crypto19;
17186
+ var import_node_crypto20;
17033
17187
  var init_network_compose = __esm({
17034
17188
  "src/core/network-compose.ts"() {
17035
17189
  "use strict";
17036
- import_node_crypto19 = require("crypto");
17190
+ import_node_crypto20 = require("crypto");
17037
17191
  }
17038
17192
  });
17039
17193
 
17040
17194
  // src/core/network-compose-files.ts
17041
- var import_node_child_process26, import_node_crypto20, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
17195
+ var import_node_child_process26, import_node_crypto21, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
17042
17196
  var init_network_compose_files = __esm({
17043
17197
  "src/core/network-compose-files.ts"() {
17044
17198
  "use strict";
17045
17199
  import_node_child_process26 = require("child_process");
17046
- import_node_crypto20 = require("crypto");
17200
+ import_node_crypto21 = require("crypto");
17047
17201
  import_node_fs35 = __toESM(require("fs"));
17048
17202
  import_node_path43 = __toESM(require("path"));
17049
17203
  import_yaml4 = __toESM(require("yaml"));
@@ -17075,11 +17229,11 @@ var init_network_lifecycle = __esm({
17075
17229
  });
17076
17230
 
17077
17231
  // src/core/network-managed.ts
17078
- var import_node_crypto21;
17232
+ var import_node_crypto22;
17079
17233
  var init_network_managed = __esm({
17080
17234
  "src/core/network-managed.ts"() {
17081
17235
  "use strict";
17082
- import_node_crypto21 = require("crypto");
17236
+ import_node_crypto22 = require("crypto");
17083
17237
  init_devpod_environment();
17084
17238
  init_network_capacity();
17085
17239
  init_network_claim_lookup();
@@ -17835,7 +17989,7 @@ async function superviseThroughController(input2) {
17835
17989
  try {
17836
17990
  const bind = () => observeControllerBinding(directory, {
17837
17991
  path: input2.repoPath,
17838
- session: (0, import_node_crypto22.randomUUID)(),
17992
+ session: (0, import_node_crypto23.randomUUID)(),
17839
17993
  profile: input2.profile ?? "full",
17840
17994
  // The protocol requires at least one readiness selector, and every
17841
17995
  // supervised operation depends on runtime readiness; "runtime" is the
@@ -17986,11 +18140,11 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path45
17986
18140
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
17987
18141
  });
17988
18142
  }
17989
- 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;
18143
+ 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;
17990
18144
  var init_reliability_lifecycle = __esm({
17991
18145
  "src/core/reliability-lifecycle.ts"() {
17992
18146
  "use strict";
17993
- import_node_crypto22 = require("crypto");
18147
+ import_node_crypto23 = require("crypto");
17994
18148
  import_node_path45 = __toESM(require("path"));
17995
18149
  import_node_util11 = require("util");
17996
18150
  init_capacity_accounting();
@@ -18626,7 +18780,7 @@ function createCapacityController(options) {
18626
18780
  controller,
18627
18781
  policyRevision: policy.revision
18628
18782
  });
18629
- const payloadKey = (0, import_node_crypto23.randomBytes)(32);
18783
+ const payloadKey = (0, import_node_crypto24.randomBytes)(32);
18630
18784
  const acceptedPayloads = /* @__PURE__ */ new Map();
18631
18785
  const settlePreparations = () => {
18632
18786
  if (lifetime.signal.aborted) return;
@@ -18790,7 +18944,7 @@ function createCapacityController(options) {
18790
18944
  {
18791
18945
  ...charge,
18792
18946
  operationId: prepared.operationId,
18793
- reservationId: (0, import_node_crypto23.randomUUID)(),
18947
+ reservationId: (0, import_node_crypto24.randomUUID)(),
18794
18948
  policyRevision: target.current.revision
18795
18949
  },
18796
18950
  {
@@ -18893,7 +19047,7 @@ function createCapacityController(options) {
18893
19047
  throw error;
18894
19048
  }
18895
19049
  }
18896
- const signature = (0, import_node_crypto23.createHmac)("sha256", payloadKey).update(
19050
+ const signature = (0, import_node_crypto24.createHmac)("sha256", payloadKey).update(
18897
19051
  JSON.stringify({
18898
19052
  repoPath: identity2.repoPath,
18899
19053
  profile: environment.profile,
@@ -18913,7 +19067,7 @@ function createCapacityController(options) {
18913
19067
  {
18914
19068
  ...charge,
18915
19069
  operationId: prepared.operationId,
18916
- reservationId: (0, import_node_crypto23.randomUUID)(),
19070
+ reservationId: (0, import_node_crypto24.randomUUID)(),
18917
19071
  policyRevision: current.revision
18918
19072
  },
18919
19073
  { estimates: resolved.estimates, enrollment: resolved.enrollment, pool }
@@ -19048,7 +19202,7 @@ function createCapacityController(options) {
19048
19202
  // the budget module then falls back to the conservative wall duration.
19049
19203
  activeElapsedMs: null,
19050
19204
  profile: environment.profile,
19051
- incidentId: `incident-${(0, import_node_crypto23.randomUUID)()}`
19205
+ incidentId: `incident-${(0, import_node_crypto24.randomUUID)()}`
19052
19206
  });
19053
19207
  } catch {
19054
19208
  return;
@@ -19066,7 +19220,7 @@ function createCapacityController(options) {
19066
19220
  {
19067
19221
  ...charge,
19068
19222
  operationId: prepared.operationId,
19069
- reservationId: (0, import_node_crypto23.randomUUID)(),
19223
+ reservationId: (0, import_node_crypto24.randomUUID)(),
19070
19224
  policyRevision: current.revision
19071
19225
  },
19072
19226
  { estimates: resolved.estimates, enrollment: resolved.enrollment, pool }
@@ -19086,11 +19240,11 @@ function createCapacityController(options) {
19086
19240
  }
19087
19241
  };
19088
19242
  }
19089
- var import_node_crypto23, import_node_path46, import_node_util13;
19243
+ var import_node_crypto24, import_node_path46, import_node_util13;
19090
19244
  var init_capacity_controller = __esm({
19091
19245
  "src/core/capacity-controller.ts"() {
19092
19246
  "use strict";
19093
- import_node_crypto23 = require("crypto");
19247
+ import_node_crypto24 = require("crypto");
19094
19248
  import_node_path46 = __toESM(require("path"));
19095
19249
  import_node_util13 = require("util");
19096
19250
  init_capacity_accounting();
@@ -19187,7 +19341,7 @@ printf '%s %s %s\n' "$pid" "$birth" "$fingerprint"
19187
19341
 
19188
19342
  // src/core/controller-observation.ts
19189
19343
  function digest2(value) {
19190
- return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
19344
+ return (0, import_node_crypto25.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
19191
19345
  }
19192
19346
  function readRoutes() {
19193
19347
  return readHostRouteStateReadOnly((file) => readControllerEvidence(file, 1048576));
@@ -19271,7 +19425,7 @@ function createControllerObservationCollector(resolve, fingerprint) {
19271
19425
  () => persisted.contents[3]
19272
19426
  );
19273
19427
  const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
19274
- 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)
19428
+ 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)
19275
19429
  throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
19276
19430
  const identity2 = {
19277
19431
  repoPath: environment.repoPath,
@@ -19446,11 +19600,11 @@ function createControllerObservationCollector(resolve, fingerprint) {
19446
19600
  };
19447
19601
  };
19448
19602
  }
19449
- var import_node_crypto24, import_node_path47, OBSERVATION_INSPECT_TEMPLATE;
19603
+ var import_node_crypto25, import_node_path47, OBSERVATION_INSPECT_TEMPLATE;
19450
19604
  var init_controller_observation = __esm({
19451
19605
  "src/core/controller-observation.ts"() {
19452
19606
  "use strict";
19453
- import_node_crypto24 = require("crypto");
19607
+ import_node_crypto25 = require("crypto");
19454
19608
  import_node_path47 = __toESM(require("path"));
19455
19609
  init_controller_binding();
19456
19610
  init_controller_monitor();
@@ -19475,11 +19629,11 @@ var init_controller_observation = __esm({
19475
19629
  });
19476
19630
 
19477
19631
  // src/core/controller-sessions.ts
19478
- var import_node_crypto25, import_node_util14, ControllerSessions;
19632
+ var import_node_crypto26, import_node_util14, ControllerSessions;
19479
19633
  var init_controller_sessions = __esm({
19480
19634
  "src/core/controller-sessions.ts"() {
19481
19635
  "use strict";
19482
- import_node_crypto25 = require("crypto");
19636
+ import_node_crypto26 = require("crypto");
19483
19637
  import_node_util14 = require("util");
19484
19638
  ControllerSessions = class {
19485
19639
  constructor(store) {
@@ -19651,7 +19805,7 @@ var init_controller_sessions = __esm({
19651
19805
  const session = {
19652
19806
  id: id5,
19653
19807
  environmentId: environment.id,
19654
- generation: (0, import_node_crypto25.randomUUID)(),
19808
+ generation: (0, import_node_crypto26.randomUUID)(),
19655
19809
  requirements: required,
19656
19810
  renewedAtMs: wall,
19657
19811
  parkingConsent: "protected",
@@ -19678,7 +19832,7 @@ var init_controller_sessions = __esm({
19678
19832
  const session = {
19679
19833
  id: previous.session,
19680
19834
  environmentId: environment.id,
19681
- generation: (0, import_node_crypto25.randomUUID)(),
19835
+ generation: (0, import_node_crypto26.randomUUID)(),
19682
19836
  requirements: required,
19683
19837
  renewedAtMs: wall,
19684
19838
  parkingConsent: "protected",
@@ -21024,7 +21178,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
21024
21178
  - **TLS**: namespaced hosts (\`web.<ws>.localhost\`) are not covered by the \`*.localhost\` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.
21025
21179
  - **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
21026
21180
  - **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once per installed Devsy release, rerunning it after a Devsy update, so Devrouter acquires and verifies the official Linux agent in its own machine cache. Devrouter supports Devsy releases in the range >=1.16.2 <2.0.0 and refuses a release whose published asset has no SHA-256 digest. \`doctor\` reports \`ready\`, \`missing\`, \`stale\`, or \`invalid\` without network access, and \`ensure\` fails before the provider queue when readiness is not \`ready\`. An explicit \`DEVSY_AGENT_BINARY\` remains authoritative and must match the official asset for the installed release.
21027
- - **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
21181
+ - **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. A start refused before any provider mutation \u2014 a fixed published host port already held by a running container \u2014 stays visible in \`status\` as a \`start-refused\` attention reason naming the read-only \`doctor\` check and the consumer-side fix; an explicit \`stop\` releases the intent and the reason, and a later admitted ensure clears it. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
21028
21182
  - **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
21029
21183
  - **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
21030
21184
  - **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
@@ -21122,26 +21276,30 @@ checkout's environment is mid-transition. Wire \`devrouter harness gate\` as a
21122
21276
  {
21123
21277
  "hooks": {
21124
21278
  "PreToolUse": [
21125
- { "matcher": "Bash", "hooks": [
21279
+ { "matcher": ".*", "hooks": [
21126
21280
  { "type": "command", "command": "devrouter harness gate", "timeout": 90 }
21127
21281
  ] }
21128
21282
  ]
21129
21283
  }
21130
21284
  }
21131
21285
 
21132
- The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`, where the shell
21133
- tool is named \`exec_command\`:
21286
+ The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`:
21134
21287
 
21135
21288
  {
21136
21289
  "hooks": {
21137
21290
  "PreToolUse": [
21138
- { "matcher": "exec_command|Bash|shell", "hooks": [
21291
+ { "matcher": ".*", "hooks": [
21139
21292
  { "type": "command", "command": "devrouter harness gate", "timeout": 180 }
21140
21293
  ] }
21141
21294
  ]
21142
21295
  }
21143
21296
  }
21144
21297
 
21298
+ \`.*\` matches every tool, including the shell, file-editing and MCP tools a model
21299
+ can point at the environment. The gate answers a settled checkout immediately, so
21300
+ the price is one short-lived process per gated call; narrow the matcher when that
21301
+ price matters more than covering every tool.
21302
+
21145
21303
  The gate identifies the requesting harness from its payload and answers in that
21146
21304
  harness's accepted shape: a refusal is a \`deny\` in both, while an allowed call is
21147
21305
  \`permissionDecision: allow\` for Claude Code and Codex receives a bare completion
@@ -21299,7 +21457,7 @@ function buildOnboardingPrompt(options = {}) {
21299
21457
  "- if kind=app and runtime=proxy:",
21300
21458
  " - HTTP-only optional readiness: { path: /api/health, statuses: [200], contentType: application/json }; statuses default to [200], explicit lists allow unique 2xx/4xx codes, never redirects. Paths stay on the configured host with no query, fragment, percent escapes, backslash or dot segments. Media types match their case-insensitive base type.",
21301
21459
  " - Declared application readiness failures return applicationReadiness.status=application-error and a nonzero ensure exit, while retaining tooling and routes for application fixes; do not reset caches or recreate the provider. Absent readiness means legacy route liveness, not full application readiness.",
21302
- " - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. Unverifiable evidence also refuses.",
21460
+ " - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. The refused start stays visible in `devrouter status` as a `start-refused` attention reason that names the read-only `doctor` check and the consumer-side fix; an explicit `stop` releases the intent and the reason, and a later admitted ensure clears it. Unverifiable evidence also refuses.",
21303
21461
  ' - upstream: "host:port" (an already-running port, e.g. a devcontainer published on 127.0.0.1:3000, or a container reachable by name on a shared Docker network such as `derivatives-db:5432`)',
21304
21462
  ` - upstream may use the \`${WORKSPACE_PLACEHOLDER}\` placeholder (e.g. \`${WORKSPACE_PLACEHOLDER}-app:3000\`) to target a per-workspace devcontainer alias; it is substituted with the resolved workspace token at runtime and re-validated. Do NOT put \`${WORKSPACE_PLACEHOLDER}\` in \`host\` (rejected) \u2014 the host is auto-namespaced.`,
21305
21463
  " - managed `devrouter ensure` requires every HTTP/TCP proxy upstream to begin with the exact resolved workspace/project alias prefix before it mutates DevPod or routes",
@@ -23193,7 +23351,7 @@ function probeInstalledCliVersion(executable) {
23193
23351
  }
23194
23352
  function buildCliPathCheck(options = {}) {
23195
23353
  const id5 = "global.cli-path";
23196
- const runningVersion = options.runningVersion ?? (true ? "0.1.0" : "0.0.0-dev");
23354
+ const runningVersion = options.runningVersion ?? (true ? "0.1.2" : "0.0.0-dev");
23197
23355
  if (runningVersion === "0.0.0-dev") {
23198
23356
  return {
23199
23357
  id: id5,
@@ -23859,6 +24017,17 @@ function collectNextSteps(checks, statusNextSteps) {
23859
24017
  }
23860
24018
  return Array.from(steps.values());
23861
24019
  }
24020
+ function capacityHistoryDetails(error) {
24021
+ if (!(error instanceof CapacityHistoryError)) return "capacity-history-unprovable";
24022
+ const parts = [error.code];
24023
+ if (error.cause) parts.push(error.cause);
24024
+ if (error.location) parts.push(`at ${error.location}`);
24025
+ return parts.join("; ");
24026
+ }
24027
+ function capacityHistorySuggestion(error) {
24028
+ const cause = error instanceof CapacityHistoryError ? error.cause : null;
24029
+ return (cause ? CAPACITY_RECOVERY_BY_CAUSE[cause] : void 0) ?? "Preserve ledger and lifecycle records; restore verified history before retrying admission.";
24030
+ }
23862
24031
  async function buildDoctorReport(options = {}) {
23863
24032
  const checks = [];
23864
24033
  const fileLayout = getRouterFileLayout();
@@ -23904,8 +24073,8 @@ async function buildDoctorReport(options = {}) {
23904
24073
  id: "global.capacity-ledger",
23905
24074
  level: "error",
23906
24075
  summary: "Capacity ledger history is unavailable for safe admission.",
23907
- details: error instanceof CapacityHistoryError ? error.code : "capacity-history-unprovable",
23908
- 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."
24076
+ details: capacityHistoryDetails(error),
24077
+ suggestion: absent ? "Preserve ledger and lifecycle records; with every enrolled environment stopped, run: devrouter capacity reconcile --yes" : capacityHistorySuggestion(error)
23909
24078
  });
23910
24079
  }
23911
24080
  let statusNextSteps = [];
@@ -24041,7 +24210,7 @@ async function buildDoctorReport(options = {}) {
24041
24210
  suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
24042
24211
  });
24043
24212
  }
24044
- const cliVersion = true ? "0.1.0" : "0.0.0-dev";
24213
+ const cliVersion = true ? "0.1.2" : "0.0.0-dev";
24045
24214
  const configVersion = config.devrouter?.version;
24046
24215
  const installedKnown = cliVersion !== "0.0.0-dev";
24047
24216
  const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
@@ -24267,7 +24436,7 @@ async function buildDoctorReport(options = {}) {
24267
24436
  nextSteps
24268
24437
  };
24269
24438
  }
24270
- var import_node_fs44, import_node_path56, import_yaml6, POSTGRES_DEFAULTS;
24439
+ var import_node_fs44, import_node_path56, import_yaml6, POSTGRES_DEFAULTS, CAPACITY_RECOVERY_BY_CAUSE;
24271
24440
  var init_doctor = __esm({
24272
24441
  "src/core/doctor.ts"() {
24273
24442
  "use strict";
@@ -24296,6 +24465,14 @@ var init_doctor = __esm({
24296
24465
  POSTGRES_PASSWORD: "prisma",
24297
24466
  POSTGRES_DB: "prisma"
24298
24467
  };
24468
+ CAPACITY_RECOVERY_BY_CAUSE = {
24469
+ "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.",
24470
+ "journal-directory-unsafe": "Restore private ownership and mode 0700 on the reliability journal directory, then retry.",
24471
+ "journal-directory-unreadable": "Restore read access to the reliability journal directory, then retry.",
24472
+ "journal-invalid": "Preserve the named journal entry and restore verified history before retrying admission.",
24473
+ "journal-unreadable": "Preserve the named journal entry and restore verified history before retrying admission.",
24474
+ "journal-entry-unsafe": "Restore private ownership, mode 0600 and a bounded size on the named journal entry, then retry."
24475
+ };
24299
24476
  }
24300
24477
  });
24301
24478
 
@@ -24904,7 +25081,7 @@ var init_status2 = __esm({
24904
25081
 
24905
25082
  // src/core/harness-continuation.ts
24906
25083
  function harnessContinuationPath(repoRoot) {
24907
- const key = (0, import_node_crypto26.createHash)("sha256").update(comparableWorkspacePath(repoRoot)).digest("hex");
25084
+ const key = (0, import_node_crypto27.createHash)("sha256").update(comparableWorkspacePath(repoRoot)).digest("hex");
24908
25085
  return import_node_path57.default.join(DEVROUTER_HOME, "harness", `${key}.json`);
24909
25086
  }
24910
25087
  function counter4(value) {
@@ -25002,11 +25179,11 @@ function settleHarnessContinuation(repoRoot, toolUseId, settlement) {
25002
25179
  } catch {
25003
25180
  }
25004
25181
  }
25005
- 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;
25182
+ 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;
25006
25183
  var init_harness_continuation = __esm({
25007
25184
  "src/core/harness-continuation.ts"() {
25008
25185
  "use strict";
25009
- import_node_crypto26 = require("crypto");
25186
+ import_node_crypto27 = require("crypto");
25010
25187
  import_node_fs45 = __toESM(require("fs"));
25011
25188
  import_node_path57 = __toESM(require("path"));
25012
25189
  init_atomic_file();
@@ -25073,17 +25250,20 @@ async function waitForHarnessGate(options) {
25073
25250
  options.onObservation?.(observation, now() - started);
25074
25251
  if (!isTransitionalHarnessPhase(observation.phase)) {
25075
25252
  const waitedMs2 = now() - started;
25253
+ let reason = deferred ? "settled-after-wait" : "settled";
25254
+ if (observation.phase === "unknown") reason = "evidence-unavailable";
25076
25255
  return {
25077
25256
  version: 1,
25078
25257
  decision: deferred ? "deferred-allow" : "allow",
25079
- reason: deferred ? "settled-after-wait" : "settled",
25258
+ reason,
25080
25259
  waitedMs: waitedMs2,
25081
25260
  observations,
25082
25261
  observedPhase: observation.phase
25083
25262
  };
25084
25263
  }
25085
25264
  const waitedMs = now() - started;
25086
- if (waitedMs + interval > budget) {
25265
+ const remainingMs = budget - waitedMs;
25266
+ if (remainingMs <= 0) {
25087
25267
  return {
25088
25268
  version: 1,
25089
25269
  decision: "refuse",
@@ -25108,7 +25288,7 @@ async function waitForHarnessGate(options) {
25108
25288
  }
25109
25289
  }
25110
25290
  deferred = true;
25111
- await sleep3(Math.min(interval, budget - waitedMs));
25291
+ await sleep3(Math.min(interval, remainingMs));
25112
25292
  }
25113
25293
  }
25114
25294
  function parseHarnessGateBudget(value) {
@@ -25150,6 +25330,91 @@ __export(harness_exports, {
25150
25330
  detectHarnessHook: () => detectHarnessHook,
25151
25331
  runHarnessCommand: () => runHarnessCommand
25152
25332
  });
25333
+ function commandSegments(command) {
25334
+ const segments = [];
25335
+ let current = "";
25336
+ let quote;
25337
+ for (const char of command) {
25338
+ if (quote) {
25339
+ current += char;
25340
+ if (char === quote) quote = void 0;
25341
+ continue;
25342
+ }
25343
+ if (char === "'" || char === '"') {
25344
+ quote = char;
25345
+ current += char;
25346
+ continue;
25347
+ }
25348
+ if (char === ";" || char === "\n" || char === "&" || char === "|" || char === "(" || char === ")") {
25349
+ segments.push(current);
25350
+ current = "";
25351
+ continue;
25352
+ }
25353
+ current += char;
25354
+ }
25355
+ segments.push(current);
25356
+ return segments;
25357
+ }
25358
+ function segmentWords(segment, limit) {
25359
+ const words = [];
25360
+ let current = "";
25361
+ let quote;
25362
+ const flush = () => {
25363
+ if (current) words.push(current);
25364
+ current = "";
25365
+ };
25366
+ for (const char of segment) {
25367
+ if (quote) {
25368
+ if (char === quote) {
25369
+ quote = void 0;
25370
+ continue;
25371
+ }
25372
+ current += char;
25373
+ continue;
25374
+ }
25375
+ if (char === "'" || char === '"') {
25376
+ quote = char;
25377
+ continue;
25378
+ }
25379
+ if (char === " " || char === " ") {
25380
+ flush();
25381
+ if (words.length >= limit) return words;
25382
+ continue;
25383
+ }
25384
+ current += char;
25385
+ }
25386
+ flush();
25387
+ return words;
25388
+ }
25389
+ function executableName(word) {
25390
+ return word.slice(word.lastIndexOf("/") + 1);
25391
+ }
25392
+ function commandWord(segment) {
25393
+ const words = segmentWords(segment, COMMAND_WORD_LIMIT);
25394
+ let index = 0;
25395
+ for (; ; ) {
25396
+ const word = words[index];
25397
+ if (word === void 0) return void 0;
25398
+ if (word === "env" || /^[A-Za-z_][A-Za-z0-9_]*=/u.test(word)) {
25399
+ index += 1;
25400
+ continue;
25401
+ }
25402
+ break;
25403
+ }
25404
+ const launcher = words[index];
25405
+ if (launcher !== void 0 && LAUNCHER_WORDS.has(executableName(launcher))) {
25406
+ index += 1;
25407
+ const subcommand = words[index];
25408
+ if (subcommand !== void 0 && LAUNCHER_SUBCOMMANDS.has(subcommand)) index += 1;
25409
+ }
25410
+ return words[index];
25411
+ }
25412
+ function isDevrouterLifecycleCommand(command) {
25413
+ return commandSegments(command).some((segment) => {
25414
+ const word = commandWord(segment);
25415
+ return word !== void 0 && DEVROUTER_EXECUTABLES.has(executableName(word));
25416
+ });
25417
+ }
25153
25418
  function detectHarnessHook(payload) {
25154
25419
  const turnId = payload.turn_id;
25155
25420
  return typeof turnId === "string" && turnId.length > 0 ? "codex" : "claude";
@@ -25196,11 +25461,19 @@ function permitReason(decision) {
25196
25461
  }
25197
25462
  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.`;
25198
25463
  }
25199
- switch (decision.decision) {
25200
- case "refuse":
25464
+ switch (decision.reason) {
25465
+ case "budget-exhausted":
25201
25466
  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.`;
25202
- case "deferred-allow":
25467
+ case "settled-after-wait":
25203
25468
  return `devrouter: environment settled after ${seconds}s; the tool may run now.`;
25469
+ case "devrouter-command":
25470
+ return "devrouter: lifecycle command; the gate left it to the lifecycle engine and did not observe the environment.";
25471
+ case "unmanaged-checkout":
25472
+ return "devrouter: no managed checkout was found for this call; the gate did not observe environment state.";
25473
+ case "hook-payload-invalid":
25474
+ return "devrouter: the hook payload was unreadable; the gate did not observe environment state.";
25475
+ case "evidence-unavailable":
25476
+ return "devrouter: lifecycle evidence was unavailable; the gate observed no settled phase and the tool proceeds.";
25204
25477
  default:
25205
25478
  return "devrouter: environment settled.";
25206
25479
  }
@@ -25268,10 +25541,9 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25268
25541
  return;
25269
25542
  }
25270
25543
  kind = detectHarnessHook(payload);
25271
- const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
25272
25544
  const toolInput = isRecord7(payload.tool_input) ? payload.tool_input : {};
25273
- const command = typeof toolInput.command === "string" ? toolInput.command : "";
25274
- if (toolName === "Bash" && DEVROUTER_COMMAND_RE.test(command)) {
25545
+ const command = typeof toolInput.command === "string" ? toolInput.command : typeof toolInput.cmd === "string" ? toolInput.cmd : "";
25546
+ if (isDevrouterLifecycleCommand(command)) {
25275
25547
  const decision2 = gateDecision("allow", "devrouter-command");
25276
25548
  process.stdout.write(
25277
25549
  json ? `${JSON.stringify(decision2)}
@@ -25293,7 +25565,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25293
25565
  }
25294
25566
  const budgetMs = parseHarnessGateBudget(options.waitBudgetMs);
25295
25567
  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;
25296
- const payloadSha256 = (0, import_node_crypto27.createHash)("sha256").update(JSON.stringify(toolInput)).digest("hex");
25568
+ const payloadSha256 = (0, import_node_crypto28.createHash)("sha256").update(JSON.stringify(toolInput)).digest("hex");
25297
25569
  const claim2 = dependencies.claim ?? ((root, input2) => {
25298
25570
  const outcome = claimHarnessContinuation(root, input2);
25299
25571
  if (outcome.kind === "claimed") {
@@ -25401,17 +25673,20 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25401
25673
  `
25402
25674
  );
25403
25675
  }
25404
- var import_node_crypto27, MAX_HOOK_PAYLOAD_BYTES, STDIN_TIMEOUT_MS, MAX_TOOL_USE_ID_LENGTH2, DEVROUTER_COMMAND_RE;
25676
+ 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;
25405
25677
  var init_harness = __esm({
25406
25678
  "src/commands/harness.ts"() {
25407
25679
  "use strict";
25408
- import_node_crypto27 = require("crypto");
25680
+ import_node_crypto28 = require("crypto");
25409
25681
  init_harness_continuation();
25410
25682
  init_harness_gate();
25411
25683
  MAX_HOOK_PAYLOAD_BYTES = 256 * 1024;
25412
25684
  STDIN_TIMEOUT_MS = 5e3;
25413
25685
  MAX_TOOL_USE_ID_LENGTH2 = 256;
25414
- DEVROUTER_COMMAND_RE = /(?:^|[\s;&|()])(?:npx\s+|pnpm\s+(?:exec\s+)?|npm\s+(?:exec\s+)?|yarn\s+)?devrouter(?:-process)?(?:\s|$)/;
25686
+ DEVROUTER_EXECUTABLES = /* @__PURE__ */ new Set(["devrouter", "devrouter-process"]);
25687
+ LAUNCHER_WORDS = /* @__PURE__ */ new Set(["npx", "pnpm", "npm", "yarn"]);
25688
+ LAUNCHER_SUBCOMMANDS = /* @__PURE__ */ new Set(["exec", "dlx"]);
25689
+ COMMAND_WORD_LIMIT = 8;
25415
25690
  }
25416
25691
  });
25417
25692
 
@@ -27526,7 +27801,7 @@ function sanitizeRouterId(value) {
27526
27801
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
27527
27802
  }
27528
27803
  function repoHash(repoPath) {
27529
- return (0, import_node_crypto28.createHash)("sha1").update(import_node_path64.default.resolve(repoPath)).digest("hex").slice(0, 12);
27804
+ return (0, import_node_crypto29.createHash)("sha1").update(import_node_path64.default.resolve(repoPath)).digest("hex").slice(0, 12);
27530
27805
  }
27531
27806
  function asDockerApp(app) {
27532
27807
  return app.runtime === "docker";
@@ -27729,12 +28004,12 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
27729
28004
  const port = Number(match[1]);
27730
28005
  return Number.isInteger(port) && port > 0 ? port : void 0;
27731
28006
  }
27732
- var import_node_child_process33, import_node_crypto28, import_node_fs51, import_node_path64, import_yaml9;
28007
+ var import_node_child_process33, import_node_crypto29, import_node_fs51, import_node_path64, import_yaml9;
27733
28008
  var init_docker_run = __esm({
27734
28009
  "src/core/docker-run.ts"() {
27735
28010
  "use strict";
27736
28011
  import_node_child_process33 = require("child_process");
27737
- import_node_crypto28 = require("crypto");
28012
+ import_node_crypto29 = require("crypto");
27738
28013
  import_node_fs51 = __toESM(require("fs"));
27739
28014
  import_node_path64 = __toESM(require("path"));
27740
28015
  import_yaml9 = __toESM(require("yaml"));
@@ -30021,7 +30296,7 @@ var init_version = __esm({
30021
30296
 
30022
30297
  // src/cli.ts
30023
30298
  var import_commander = require("commander");
30024
- var CLI_VERSION = true ? "0.1.0" : "0.0.0-dev";
30299
+ var CLI_VERSION = true ? "0.1.2" : "0.0.0-dev";
30025
30300
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
30026
30301
  function withErrorHandling(action2) {
30027
30302
  return async (...args) => {