@devrouter/cli 0.0.79 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2051,7 +2051,10 @@ var import_node_child_process9 = require("child_process");
2051
2051
  var import_node_crypto4 = require("crypto");
2052
2052
  var import_node_fs8 = __toESM(require("fs"));
2053
2053
  var import_node_path9 = __toESM(require("path"));
2054
- var SUPPORTED_DEVSY_VERSION = "1.16.2";
2054
+ var PINNED_DEVSY_AGENT_VERSION = "1.16.2";
2055
+ var SUPPORTED_DEVSY_MIN_VERSION = "1.16.2";
2056
+ var SUPPORTED_DEVSY_MAX_VERSION = "2.0.0";
2057
+ var SUPPORTED_DEVSY_RANGE = `>=${SUPPORTED_DEVSY_MIN_VERSION} <${SUPPORTED_DEVSY_MAX_VERSION}`;
2055
2058
  var DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
2056
2059
  var DEVSY_AGENT_ASSETS = [
2057
2060
  {
@@ -2082,19 +2085,90 @@ function parseVersion(output) {
2082
2085
  /\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
2083
2086
  )?.[1];
2084
2087
  }
2085
- function compareVersionCore(left, right) {
2086
- const core = (value) => value.split(/[-+]/, 1)[0].split(".").map((part) => Number.parseInt(part, 10));
2087
- const leftParts = core(left);
2088
- const rightParts = core(right);
2089
- for (let index = 0; index < 3; index += 1) {
2090
- const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
2091
- if (difference !== 0) return difference > 0 ? 1 : -1;
2088
+ function parseSemver(value) {
2089
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
2090
+ value.trim()
2091
+ );
2092
+ if (!match) return void 0;
2093
+ return {
2094
+ major: Number(match[1]),
2095
+ minor: Number(match[2]),
2096
+ patch: Number(match[3]),
2097
+ prerelease: match[4] ? match[4].split(".") : []
2098
+ };
2099
+ }
2100
+ function comparePrerelease(left, right) {
2101
+ if (left.length === 0 && right.length === 0) return 0;
2102
+ if (left.length === 0) return 1;
2103
+ if (right.length === 0) return -1;
2104
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
2105
+ const leftPart = left[index];
2106
+ const rightPart = right[index];
2107
+ if (leftPart === void 0) return -1;
2108
+ if (rightPart === void 0) return 1;
2109
+ const leftNumeric = /^\d+$/.test(leftPart);
2110
+ const rightNumeric = /^\d+$/.test(rightPart);
2111
+ if (leftNumeric && rightNumeric) {
2112
+ const difference = Number(leftPart) - Number(rightPart);
2113
+ if (difference !== 0) return difference < 0 ? -1 : 1;
2114
+ continue;
2115
+ }
2116
+ if (leftNumeric) return -1;
2117
+ if (rightNumeric) return 1;
2118
+ if (leftPart !== rightPart) return leftPart < rightPart ? -1 : 1;
2092
2119
  }
2093
2120
  return 0;
2094
2121
  }
2122
+ function compareSemver(left, right) {
2123
+ for (const part of ["major", "minor", "patch"]) {
2124
+ if (left[part] !== right[part]) return left[part] < right[part] ? -1 : 1;
2125
+ }
2126
+ return comparePrerelease(left.prerelease, right.prerelease);
2127
+ }
2128
+ function isSupportedDevsyVersion(version) {
2129
+ const parsed = version === void 0 ? void 0 : parseSemver(version);
2130
+ const minimum = parseSemver(SUPPORTED_DEVSY_MIN_VERSION);
2131
+ const maximum = parseSemver(SUPPORTED_DEVSY_MAX_VERSION);
2132
+ if (!parsed || !minimum || !maximum) return false;
2133
+ if (compareSemver(parsed, minimum) < 0) return false;
2134
+ if (parsed.prerelease.length > 0 && parsed.major === maximum.major && parsed.minor === maximum.minor && parsed.patch === maximum.patch) {
2135
+ return false;
2136
+ }
2137
+ return compareSemver(parsed, maximum) < 0;
2138
+ }
2139
+ var DEVSY_AGENT_MANIFEST_FILE = "manifest.json";
2140
+ function isDevsyAgentAsset(value) {
2141
+ if (!value || typeof value !== "object") return false;
2142
+ const asset = value;
2143
+ return typeof asset.githubAssetId === "number" && Number.isSafeInteger(asset.githubAssetId) && typeof asset.name === "string" && typeof asset.size === "number" && Number.isSafeInteger(asset.size) && asset.size > 0 && typeof asset.sha256 === "string" && /^[0-9a-f]{64}$/.test(asset.sha256) && typeof asset.url === "string" && asset.url.startsWith("https://");
2144
+ }
2145
+ function devsyAgentManifestPath(cacheRoot, devsyVersion) {
2146
+ return import_node_path9.default.join(cacheRoot, `v${devsyVersion}`, DEVSY_AGENT_MANIFEST_FILE);
2147
+ }
2148
+ function readManifestRecord(cacheRoot, devsyVersion) {
2149
+ try {
2150
+ const parsed = JSON.parse(
2151
+ import_node_fs8.default.readFileSync(devsyAgentManifestPath(cacheRoot, devsyVersion), "utf-8")
2152
+ );
2153
+ if (parsed.version !== 1 || parsed.devsyVersion !== devsyVersion) return void 0;
2154
+ if (parsed.origin !== "release") return void 0;
2155
+ if (!Array.isArray(parsed.assets) || parsed.assets.length === 0) return void 0;
2156
+ if (!parsed.assets.every(isDevsyAgentAsset)) return void 0;
2157
+ return { version: devsyVersion, origin: "release", assets: parsed.assets };
2158
+ } catch {
2159
+ return void 0;
2160
+ }
2161
+ }
2162
+ function pinnedManifest(version) {
2163
+ return version === PINNED_DEVSY_AGENT_VERSION ? { version, origin: "pinned", assets: DEVSY_AGENT_ASSETS } : void 0;
2164
+ }
2165
+ function manifestForVersion(resolved, version) {
2166
+ if (resolved.assets) return { version, origin: "pinned", assets: resolved.assets };
2167
+ return pinnedManifest(version) ?? readManifestRecord(resolved.cacheRoot, version);
2168
+ }
2095
2169
  function devsyAgentRepairSuggestion(inspection) {
2096
2170
  if (inspection.state === "stale") {
2097
- return `Install Devsy ${SUPPORTED_DEVSY_VERSION} for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
2171
+ return `Install a supported Devsy release (${SUPPORTED_DEVSY_RANGE}) for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
2098
2172
  }
2099
2173
  return inspection.source === "explicit" ? `Fix or unset DEVSY_AGENT_BINARY, then run: ${DEVSY_AGENT_SETUP_COMMAND}` : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
2100
2174
  }
@@ -2109,8 +2183,8 @@ function nativeAssetName(platform, arch) {
2109
2183
  if (arch === "x64") return "devsy-linux-amd64";
2110
2184
  return void 0;
2111
2185
  }
2112
- function managedBinaryPath(cacheRoot, asset) {
2113
- return import_node_path9.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
2186
+ function managedBinaryPath(cacheRoot, devsyVersion, asset) {
2187
+ return import_node_path9.default.join(cacheRoot, `v${devsyVersion}`, asset.name);
2114
2188
  }
2115
2189
  function hashOpenFile(file) {
2116
2190
  const digest = (0, import_node_crypto4.createHash)("sha256");
@@ -2154,7 +2228,7 @@ function resolvedOptions(options) {
2154
2228
  versionOutput: options.versionOutput ?? installedVersionOutput(),
2155
2229
  cacheRoot: options.cacheRoot ?? import_node_path9.default.join(CACHE_DIR, "devsy", "agents"),
2156
2230
  lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
2157
- assets: options.assets ?? DEVSY_AGENT_ASSETS,
2231
+ assets: options.assets,
2158
2232
  nativeAssetName: options.nativeAssetName
2159
2233
  };
2160
2234
  }
@@ -2163,54 +2237,75 @@ function inspectDevsyAgent(options = {}) {
2163
2237
  const installedVersion = parseVersion(resolved.versionOutput);
2164
2238
  const explicitPath = resolved.env.DEVSY_AGENT_BINARY?.trim();
2165
2239
  const source = explicitPath ? "explicit" : "managed";
2166
- if (installedVersion === void 0 || installedVersion !== SUPPORTED_DEVSY_VERSION && compareVersionCore(installedVersion, SUPPORTED_DEVSY_VERSION) <= 0) {
2240
+ if (!installedVersion) {
2167
2241
  return {
2168
2242
  state: "stale",
2169
2243
  source,
2170
- reason: installedVersion ? `installed Devsy ${installedVersion} is not supported by this Devrouter release` : "the supported Devsy version is not available",
2171
- installedVersion
2244
+ reason: `the installed Devsy version could not be determined; Devrouter supports ${SUPPORTED_DEVSY_RANGE}`
2172
2245
  };
2173
2246
  }
2174
- if (explicitPath) {
2247
+ if (!isSupportedDevsyVersion(installedVersion)) {
2175
2248
  return {
2176
- ...inspectBinary(explicitPath, resolved.assets, "invalid"),
2249
+ state: "stale",
2177
2250
  source,
2178
- binaryPath: explicitPath,
2251
+ reason: `installed Devsy ${installedVersion} is outside the supported range ${SUPPORTED_DEVSY_RANGE}`,
2179
2252
  installedVersion
2180
2253
  };
2181
2254
  }
2182
- if (installedVersion !== SUPPORTED_DEVSY_VERSION) {
2255
+ const manifest = manifestForVersion(resolved, installedVersion);
2256
+ if (!manifest) {
2257
+ if (explicitPath) {
2258
+ return {
2259
+ ...inspectBinary(explicitPath, resolved.assets ?? DEVSY_AGENT_ASSETS, "invalid"),
2260
+ source,
2261
+ binaryPath: explicitPath,
2262
+ installedVersion
2263
+ };
2264
+ }
2183
2265
  return {
2184
2266
  state: "ready",
2185
2267
  source: "host",
2186
- reason: `installed Devsy ${installedVersion} is newer than the verified ${SUPPORTED_DEVSY_VERSION} agent; the host CLI governs its own agent`,
2268
+ reason: `no verified Devsy ${installedVersion} agent manifest is recorded yet; the host CLI governs its own agent`,
2187
2269
  installedVersion,
2188
- drift: { installed: installedVersion, supported: SUPPORTED_DEVSY_VERSION }
2270
+ drift: { installed: installedVersion, supported: PINNED_DEVSY_AGENT_VERSION }
2189
2271
  };
2190
2272
  }
2191
2273
  const expectedName = resolved.nativeAssetName ?? nativeAssetName(resolved.platform, resolved.arch);
2192
- const asset = resolved.assets.find((candidate) => candidate.name === expectedName);
2274
+ const asset = manifest.assets.find((candidate) => candidate.name === expectedName);
2193
2275
  if (!asset) {
2194
2276
  return {
2195
2277
  state: "stale",
2196
2278
  source,
2197
- reason: `this Devrouter release has no pinned Devsy agent for ${resolved.platform}/${resolved.arch}`,
2198
- installedVersion
2279
+ reason: `Devsy ${installedVersion} has no official agent for ${resolved.platform}/${resolved.arch}`,
2280
+ installedVersion,
2281
+ manifestOrigin: manifest.origin
2282
+ };
2283
+ }
2284
+ if (explicitPath) {
2285
+ return {
2286
+ ...inspectBinary(explicitPath, [asset], "invalid"),
2287
+ source,
2288
+ binaryPath: explicitPath,
2289
+ installedVersion,
2290
+ manifestOrigin: manifest.origin
2199
2291
  };
2200
2292
  }
2201
- const binaryPath = managedBinaryPath(resolved.cacheRoot, asset);
2293
+ const binaryPath = managedBinaryPath(resolved.cacheRoot, installedVersion, asset);
2202
2294
  return {
2203
2295
  ...inspectBinary(binaryPath, [asset], "missing"),
2204
2296
  source,
2205
2297
  binaryPath,
2206
2298
  asset,
2207
- installedVersion
2299
+ installedVersion,
2300
+ manifestOrigin: manifest.origin
2208
2301
  };
2209
2302
  }
2210
- function requireReadyDevsyAgent(options = {}) {
2211
- const inspection = inspectDevsyAgent(options);
2212
- if (inspection.state !== "ready") throw new DevsyAgentReadinessError(inspection);
2303
+ function isReadyInspection(inspection) {
2304
+ return inspection.state === "ready" && inspection.installedVersion !== void 0;
2305
+ }
2306
+ function preparedFromInspection(inspection) {
2213
2307
  return {
2308
+ version: inspection.installedVersion,
2214
2309
  binaryPath: inspection.binaryPath,
2215
2310
  source: inspection.source,
2216
2311
  asset: inspection.asset,
@@ -2218,6 +2313,11 @@ function requireReadyDevsyAgent(options = {}) {
2218
2313
  transport: "existing"
2219
2314
  };
2220
2315
  }
2316
+ function requireReadyDevsyAgent(options = {}) {
2317
+ const inspection = inspectDevsyAgent(options);
2318
+ if (!isReadyInspection(inspection)) throw new DevsyAgentReadinessError(inspection);
2319
+ return preparedFromInspection(inspection);
2320
+ }
2221
2321
 
2222
2322
  // src/core/managed-devsy-stop.ts
2223
2323
  var import_node_fs21 = __toESM(require("fs"));
@@ -4402,6 +4502,8 @@ var CapacityStore = class {
4402
4502
  file;
4403
4503
  establishedFile;
4404
4504
  observedLedger = false;
4505
+ /** Identity of the snapshot file read last, so a replacement cannot reuse its revision. */
4506
+ ledgerGeneration;
4405
4507
  readEstablished() {
4406
4508
  let descriptor;
4407
4509
  try {
@@ -4428,7 +4530,7 @@ var CapacityStore = class {
4428
4530
  import_node_fs15.default.closeSync(descriptor);
4429
4531
  }
4430
4532
  }
4431
- read() {
4533
+ inspect() {
4432
4534
  const minimumRevision = this.minimumRevision();
4433
4535
  const established = this.readEstablished();
4434
4536
  let descriptor;
@@ -4439,9 +4541,9 @@ var CapacityStore = class {
4439
4541
  );
4440
4542
  } catch (error) {
4441
4543
  if (error.code === "ENOENT") {
4442
- if (established || this.observedLedger || minimumRevision > 0)
4443
- throw new CapacityHistoryError("capacity-ledger-lost");
4444
- return { version: 1, revision: 0, reservations: [] };
4544
+ this.ledgerGeneration = void 0;
4545
+ if (established || this.observedLedger || minimumRevision > 0) return { kind: "absent" };
4546
+ return { kind: "pristine" };
4445
4547
  }
4446
4548
  throw error;
4447
4549
  }
@@ -4454,16 +4556,36 @@ var CapacityStore = class {
4454
4556
  if (count > MAX_BYTES) throw new Error("Capacity reservation snapshot exceeds byte limit.");
4455
4557
  const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
4456
4558
  validate(value);
4457
- if (value.revision < minimumRevision) throw new CapacityHistoryError("capacity-ledger-lost");
4559
+ this.ledgerGeneration = `${stat.dev}:${stat.ino}`;
4560
+ if (value.revision < minimumRevision) return { kind: "stale", revision: value.revision };
4458
4561
  this.observedLedger = true;
4459
- return value;
4562
+ return { kind: "intact", revision: value.revision, snapshot: value };
4460
4563
  } finally {
4461
4564
  import_node_fs15.default.closeSync(descriptor);
4462
4565
  }
4463
4566
  }
4567
+ read() {
4568
+ const state = this.inspect();
4569
+ if (state.kind === "intact") return state.snapshot;
4570
+ if (state.kind === "pristine") return { version: 1, revision: 0, reservations: [] };
4571
+ throw new CapacityHistoryError("capacity-ledger-lost");
4572
+ }
4464
4573
  establish() {
4465
4574
  if (!this.readEstablished()) this.write(this.establishedFile, '{"version":1}\n');
4466
- for (const file of [this.file, this.establishedFile, this.directory]) {
4575
+ this.syncDurability([this.file, this.establishedFile, this.directory]);
4576
+ }
4577
+ /**
4578
+ * Witness for a ledger that is positively absent. The marker records that
4579
+ * history existed here, so no later process can read the store as a fresh
4580
+ * install and start admitting; the snapshot stays absent until reconciliation.
4581
+ * The caller holds the capacity lock and has proven environment cessation.
4582
+ */
4583
+ establishMarkerOnly() {
4584
+ if (!this.readEstablished()) this.write(this.establishedFile, '{"version":1}\n');
4585
+ this.syncDurability([this.establishedFile, this.directory]);
4586
+ }
4587
+ syncDurability(files) {
4588
+ for (const file of files) {
4467
4589
  const descriptor = import_node_fs15.default.openSync(
4468
4590
  file,
4469
4591
  import_node_fs15.default.constants.O_RDONLY | import_node_fs15.default.constants.O_NOFOLLOW | import_node_fs15.default.constants.O_NONBLOCK
@@ -4479,15 +4601,90 @@ var CapacityStore = class {
4479
4601
  }
4480
4602
  }
4481
4603
  readForMutation() {
4604
+ const expectedGeneration = this.ledgerGeneration;
4482
4605
  const snapshot = this.read();
4606
+ if (expectedGeneration !== void 0 && expectedGeneration !== this.ledgerGeneration)
4607
+ throw new CapacitySnapshotChangedError();
4483
4608
  if (this.observedLedger) this.establish();
4484
4609
  return snapshot;
4485
4610
  }
4486
4611
  commit(snapshot) {
4487
4612
  this.write(this.file, serializeSnapshot(snapshot));
4488
4613
  this.observedLedger = true;
4614
+ this.rememberGeneration();
4489
4615
  this.establish();
4490
4616
  }
4617
+ /** Remember the published snapshot identity for the next generation fence. */
4618
+ rememberGeneration() {
4619
+ try {
4620
+ const stat = import_node_fs15.default.statSync(this.file);
4621
+ this.ledgerGeneration = stat.isFile() ? `${stat.dev}:${stat.ino}` : void 0;
4622
+ } catch {
4623
+ this.ledgerGeneration = void 0;
4624
+ }
4625
+ }
4626
+ /**
4627
+ * Durable evidence that recorded history is gone, for a stop whose physical
4628
+ * cessation is already proven. The caller holds that proof; this writes no
4629
+ * row and asserts nothing about any other environment's charge.
4630
+ */
4631
+ recordLedgerLoss() {
4632
+ assertPrivateDirectory(this.directory);
4633
+ withFileLockSync(
4634
+ `${this.file}.lock`,
4635
+ { activity: "capacity loss witness", waitMs: 100 },
4636
+ () => {
4637
+ if (this.inspect().kind !== "absent")
4638
+ throw new CapacityHistoryError("capacity-ledger-lost");
4639
+ this.establishMarkerOnly();
4640
+ }
4641
+ );
4642
+ }
4643
+ /**
4644
+ * Replace a provably absent ledger with a fresh baseline under the capacity
4645
+ * lock. `plan` runs inside the lock with the authoritative classification and
4646
+ * re-verifies the caller's evidence, returning the baseline to write or
4647
+ * undefined to refuse without mutation. `confirm` runs in the same lock after
4648
+ * the write; returning false withdraws the fresh snapshot, so the store reads
4649
+ * as lost again instead of publishing a baseline the caller cannot stand behind.
4650
+ */
4651
+ reconcileLostHistory(plan, confirm) {
4652
+ assertPrivateDirectory(this.directory);
4653
+ return withFileLockSync(
4654
+ `${this.file}.lock`,
4655
+ { activity: "capacity reconciliation", waitMs: 100 },
4656
+ () => {
4657
+ const baseline = plan(this.inspect());
4658
+ if (!baseline) return { reconciled: false };
4659
+ if (!Number.isSafeInteger(baseline.revision) || baseline.revision < 1)
4660
+ throw new Error("Invalid capacity reconciliation revision.");
4661
+ const pools = structuredClone(baseline.pools ?? []);
4662
+ validatePools(pools);
4663
+ const snapshot = pools.length === 0 ? { version: 1, revision: baseline.revision, reservations: [] } : { version: 1, revision: baseline.revision, reservations: [], pools };
4664
+ validate(snapshot);
4665
+ this.commit(snapshot);
4666
+ if (confirm()) return { reconciled: true, revision: snapshot.revision };
4667
+ this.withdrawSnapshot();
4668
+ return { reconciled: false };
4669
+ }
4670
+ );
4671
+ }
4672
+ /** Remove a snapshot this store just wrote, keeping the loss witness durable. */
4673
+ withdrawSnapshot() {
4674
+ try {
4675
+ import_node_fs15.default.unlinkSync(this.file);
4676
+ } catch (error) {
4677
+ if (error.code !== "ENOENT") throw error;
4678
+ }
4679
+ this.observedLedger = false;
4680
+ this.ledgerGeneration = void 0;
4681
+ const directory = import_node_fs15.default.openSync(this.directory, "r");
4682
+ try {
4683
+ import_node_fs15.default.fsyncSync(directory);
4684
+ } finally {
4685
+ import_node_fs15.default.closeSync(directory);
4686
+ }
4687
+ }
4491
4688
  /**
4492
4689
  * Caller must revoke journal authority and prove exact physical cessation first.
4493
4690
  * An absent reservation still advances the revision to fence that proof.
@@ -5150,7 +5347,7 @@ function retain(session, previous) {
5150
5347
 
5151
5348
  // src/core/reliability-operation-store.ts
5152
5349
  function installedCliVersion() {
5153
- return true ? "0.0.79" : "0.0.0-dev";
5350
+ return true ? "0.1.0" : "0.0.0-dev";
5154
5351
  }
5155
5352
  function compareCliVersions(left, right) {
5156
5353
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -5475,16 +5672,26 @@ function validate2(record5, identity2) {
5475
5672
  function createLifecycleCapacityStore(directory = import_node_path21.default.join(DEVROUTER_HOME, "controller")) {
5476
5673
  return new CapacityStore(directory, void 0, () => {
5477
5674
  try {
5478
- let revision = 0;
5479
- for (const record5 of listReliabilityOperations()) {
5480
- if (record5.capacity) revision = Math.max(revision, record5.capacity.snapshotRevision ?? 1);
5481
- }
5482
- return revision;
5675
+ return listUnsettledCapacityBindings().floor;
5483
5676
  } catch {
5484
5677
  throw new CapacityHistoryError("capacity-history-unprovable");
5485
5678
  }
5486
5679
  });
5487
5680
  }
5681
+ function listUnsettledCapacityBindings() {
5682
+ let floor = 0;
5683
+ const bindings = [];
5684
+ for (const record5 of listReliabilityOperations()) {
5685
+ if (!record5.capacity) continue;
5686
+ floor = Math.max(floor, record5.capacity.snapshotRevision ?? 1);
5687
+ bindings.push({
5688
+ environmentId: record5.state.environmentId,
5689
+ repoPath: record5.identity.repoPath,
5690
+ workspace: record5.identity.workspace
5691
+ });
5692
+ }
5693
+ return { floor, bindings };
5694
+ }
5488
5695
  function assertCapacityEffect(record5, workerId, nowMs, directory = import_node_path21.default.join(DEVROUTER_HOME, "controller")) {
5489
5696
  if (record5.version === 1) return;
5490
5697
  const binding = record5.capacity;
@@ -5707,7 +5914,7 @@ function formatSupportedTcpProtocols() {
5707
5914
  }
5708
5915
 
5709
5916
  // src/core/repo-config.ts
5710
- function compareSemver(a, b) {
5917
+ function compareSemver2(a, b) {
5711
5918
  const parse2 = (v) => {
5712
5919
  const match = v.trim().match(/^v?(\d+)\.(\d+)\.(\d+)$/);
5713
5920
  return match ? { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) } : { major: 0, minor: 0, patch: 0 };
@@ -6956,8 +7163,8 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
6956
7163
  const config = parseConfig(parsed ?? {}, configPath);
6957
7164
  const requiredVersion = config.devrouter?.version;
6958
7165
  if (requiredVersion && !hasWarnedVersionMismatch) {
6959
- const cliVersion = true ? "0.0.79" : "0.0.0-dev";
6960
- if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
7166
+ const cliVersion = true ? "0.1.0" : "0.0.0-dev";
7167
+ if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
6961
7168
  hasWarnedVersionMismatch = true;
6962
7169
  process.stderr.write(
6963
7170
  `
@@ -9602,8 +9809,9 @@ function inspectNetworkProviderBinding(input) {
9602
9809
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(providerContext)) throw new Error();
9603
9810
  const contextArgs = ["--context", providerContext];
9604
9811
  const version = read(provider, provider === "devsy" ? ["--version"] : ["version"]);
9605
- const expectedVersion = provider === "devsy" ? "1.16.2" : "0.6.15";
9606
- if (version.match(/\b(?:v)?(\d+\.\d+\.\d+)\b/)?.[1] !== expectedVersion) throw new Error();
9812
+ const parsedVersion = version.match(/\b(?:v)?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/)?.[1];
9813
+ if (provider === "devsy" ? !isSupportedDevsyVersion(parsedVersion) : parsedVersion !== "0.6.15")
9814
+ throw new Error();
9607
9815
  const definitions = object2(
9608
9816
  JSON.parse(
9609
9817
  read(
@@ -11281,6 +11489,9 @@ function holderAttribution(holder, repoPath) {
11281
11489
  return attribution;
11282
11490
  }
11283
11491
  function remediationFor(holder, attribution) {
11492
+ if (holder.name === ROUTER_CONTAINER_NAME) {
11493
+ return "The shared devrouter router owns this port as a platform entrypoint and must keep running; change the consumer's fixed host binding, or route the dependency through devrouter's TCP protocol route, then run ensure again.";
11494
+ }
11284
11495
  if (attribution.holderWorktreePath) {
11285
11496
  return `Stop the holding workspace with 'devrouter stop ${attribution.holderWorktreePath}', or override the consumer's published host binding through its own configuration, then run ensure again.`;
11286
11497
  }
@@ -13922,6 +14133,17 @@ function assertOverlay(container, repoPath) {
13922
14133
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
13923
14134
  }
13924
14135
  }
14136
+ function assertWorktreeOwnership(container, repoPath) {
14137
+ const workingDir = container.labels["com.docker.compose.project.working_dir"];
14138
+ if (!workingDir || !isInsideWorktree(workingDir, repoPath)) {
14139
+ throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
14140
+ }
14141
+ }
14142
+ function isInsideWorktree(candidate, repoPath) {
14143
+ const normalizedCandidate = comparableWorkspacePath(candidate);
14144
+ const normalizedRoot = comparableWorkspacePath(repoPath);
14145
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path39.default.sep}`);
14146
+ }
13925
14147
  function assertReady(container, label) {
13926
14148
  if (!container.state.Running) {
13927
14149
  throw new Error(`${label} container '${container.id}' is not running.`);
@@ -14194,7 +14416,7 @@ function validateWorkspaceContainers(containers, options) {
14194
14416
  );
14195
14417
  }
14196
14418
  if (options.target.kind === "linked") {
14197
- assertOverlay(matches[0], options.repoPath);
14419
+ assertWorktreeOwnership(matches[0], options.repoPath);
14198
14420
  }
14199
14421
  assertReady(matches[0], `Workspace upstream '${host}'`);
14200
14422
  }
@@ -15875,8 +16097,20 @@ function proveLifecycleStopped() {
15875
16097
  const capacity = createLifecycleCapacityStore(import_node_path40.default.join(DEVROUTER_HOME, "controller"));
15876
16098
  for (let attempt = 0; ; attempt++) {
15877
16099
  try {
15878
- capacity.settleEnvironmentAfterStop(settlement, capacity.read().revision);
15879
- break;
16100
+ const state = capacity.inspect();
16101
+ if (state.kind === "intact") {
16102
+ capacity.settleEnvironmentAfterStop(settlement, state.revision);
16103
+ break;
16104
+ }
16105
+ if (state.kind === "absent") {
16106
+ capacity.recordLedgerLoss();
16107
+ break;
16108
+ }
16109
+ if (state.kind === "pristine") {
16110
+ capacity.settleEnvironmentAfterStop(settlement, 0);
16111
+ break;
16112
+ }
16113
+ throw new CapacityHistoryError("capacity-ledger-lost");
15880
16114
  } catch (error) {
15881
16115
  if (!(error instanceof CapacitySnapshotChangedError) || attempt >= 2) throw error;
15882
16116
  }