@devrouter/cli 0.0.67 → 0.0.68

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.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/core/devpod-exec.ts
26
26
  var import_node_child_process28 = require("child_process");
27
- var import_node_crypto17 = require("crypto");
27
+ var import_node_crypto21 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
30
30
  var import_node_child_process3 = require("child_process");
@@ -141,35 +141,56 @@ function isProcessAlive(pid) {
141
141
  return error.code === "EPERM";
142
142
  }
143
143
  }
144
- function processBirthIdentity(pid) {
144
+ function processBirthIdentityWithCause(pid) {
145
+ const causeParts = [];
145
146
  try {
146
147
  const stat = import_node_fs2.default.readFileSync(`/proc/${pid}/stat`, "utf-8");
147
148
  const commandEnd = stat.lastIndexOf(")");
148
149
  if (commandEnd >= 0) {
149
- const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
150
- const startTime = fields[19];
151
- if (startTime) return `proc:${startTime}`;
150
+ const fields2 = stat.slice(commandEnd + 1).trim().split(/\s+/);
151
+ const startTime = fields2[19];
152
+ if (startTime) return { identity: `proc:${startTime}` };
153
+ causeParts.push(`procfs /proc/${pid}/stat had no start-time field`);
152
154
  }
153
- } catch {
155
+ } catch (error) {
156
+ const code = error.code;
157
+ if (code !== "ENOENT")
158
+ causeParts.push(`procfs /proc/${pid}/stat read failed (${code ?? "unknown"})`);
154
159
  }
155
160
  const result = (0, import_node_child_process.spawnSync)("ps", ["-o", "lstart=", "-o", "command=", "-p", String(pid)], {
156
161
  encoding: "utf-8",
157
162
  env: { ...process.env, LC_ALL: "C" }
158
163
  });
159
- const startedAt = result.status === 0 ? result.stdout.trim().replace(/\s+/g, " ") : "";
160
- return startedAt ? `ps:${(0, import_node_crypto2.createHash)("sha256").update(startedAt).digest("hex")}` : void 0;
164
+ if (result.error) {
165
+ causeParts.push(
166
+ `ps spawn failed (${result.error.code ?? result.error.message})`
167
+ );
168
+ } else if (result.status !== 0) {
169
+ causeParts.push(
170
+ result.status === null && result.signal ? `ps was killed by signal ${result.signal}` : `ps exited with status ${result.status}` + (result.signal ? ` on ${result.signal}` : "")
171
+ );
172
+ } else {
173
+ const startedAt = result.stdout.trim().replace(/\s+/g, " ");
174
+ if (startedAt)
175
+ return { identity: `ps:${(0, import_node_crypto2.createHash)("sha256").update(startedAt).digest("hex")}` };
176
+ causeParts.push(`ps produced no start time for pid ${pid}`);
177
+ }
178
+ return { cause: causeParts.join("; ") };
179
+ }
180
+ function processBirthIdentity(pid) {
181
+ return processBirthIdentityWithCause(pid).identity;
161
182
  }
162
183
  function parseLockOwner(value) {
163
- const fields = value.split(":");
164
- const pid = Number(fields[0]);
184
+ const fields2 = value.split(":");
185
+ const pid = Number(fields2[0]);
165
186
  if (!Number.isInteger(pid) || pid <= 0) return void 0;
166
- if (fields.length !== 3 || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(fields[2])) {
187
+ if (fields2.length !== 3 || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(fields2[2])) {
167
188
  return { pid };
168
189
  }
169
190
  try {
170
- const processBirth = Buffer.from(fields[1], "base64url").toString("utf-8");
191
+ const processBirth = Buffer.from(fields2[1], "base64url").toString("utf-8");
171
192
  const canonical = Buffer.from(processBirth).toString("base64url");
172
- return canonical === fields[1] && /^(proc|ps):/.test(processBirth) ? { pid, processBirth } : { pid };
193
+ return canonical === fields2[1] && /^(proc|ps):/.test(processBirth) ? { pid, processBirth } : { pid };
173
194
  } catch {
174
195
  return { pid };
175
196
  }
@@ -282,14 +303,15 @@ function tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache) {
282
303
  }
283
304
  }
284
305
  function acquireFileLock(lockPath, options) {
285
- const processBirth = processBirthIdentity(process.pid);
286
- if (!processBirth) {
306
+ const processBirth = processBirthIdentityWithCause(process.pid);
307
+ const birthIdentity = processBirth.identity;
308
+ if (!birthIdentity) {
287
309
  throw new Error(
288
- `could not determine process identity for ${options.activity} lock: process inspection may be unavailable or sandbox-denied; run the canonical command in a permitted host context`
310
+ `could not determine process identity for ${options.activity} lock at ${lockPath}: ${processBirth.cause ?? "process inspection returned no result"}. process inspection may be unavailable or denied by the execution sandbox; verify with LC_ALL=C ps -o lstart= -o command= -p <any-live-pid> and, if that fails with an operation-not-permitted error, run the canonical command in a permitted host context outside the sandbox. the lock stays fail-closed; no identity fallback was attempted.`
289
311
  );
290
312
  }
291
313
  const ownerId = (0, import_node_crypto2.randomUUID)();
292
- const owner = `${process.pid}:${Buffer.from(processBirth).toString("base64url")}:${ownerId}`;
314
+ const owner = `${process.pid}:${Buffer.from(birthIdentity).toString("base64url")}:${ownerId}`;
293
315
  const candidatePath = `${lockPath}.${process.pid}.${ownerId}.candidate`;
294
316
  const staleLinkPath = `${candidatePath}.stale`;
295
317
  const enqueuedAtMs = Date.now();
@@ -811,7 +833,7 @@ function requireSameContainerPopulation(expected, actual) {
811
833
  throw new Error("Managed stop Docker inspection observed a changed population.");
812
834
  }
813
835
  const expectedSet = new Set(expected);
814
- if (actual.some((id3) => !expectedSet.has(id3))) {
836
+ if (actual.some((id5) => !expectedSet.has(id5))) {
815
837
  throw new Error("Managed stop Docker inspection observed a changed population.");
816
838
  }
817
839
  }
@@ -849,7 +871,7 @@ function inspectWorkspaceContainers(options) {
849
871
  `docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
850
872
  );
851
873
  }
852
- ids2 = listed.stdout.split(/\r?\n/).map((id3) => id3.trim()).filter(Boolean);
874
+ ids2 = listed.stdout.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
853
875
  }
854
876
  if (ids2.length === 0) return [];
855
877
  const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
@@ -1024,10 +1046,10 @@ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
1024
1046
  assertUniqueContainerIds(ids2);
1025
1047
  return ids2;
1026
1048
  }
1027
- function hasExactManagedStopMissingObjectError(stderr, id3) {
1049
+ function hasExactManagedStopMissingObjectError(stderr, id5) {
1028
1050
  if (typeof stderr !== "string") return false;
1029
1051
  const line = stderr.endsWith("\r\n") ? stderr.slice(0, -2) : stderr.endsWith("\n") ? stderr.slice(0, -1) : stderr;
1030
- return line.toLowerCase() === `error: no such object: ${id3}`;
1052
+ return line.toLowerCase() === `error: no such object: ${id5}`;
1031
1053
  }
1032
1054
  function assertManagedStopContainersAbsent(endpoint, ids2) {
1033
1055
  assertManagedStopEndpoint(endpoint);
@@ -1039,14 +1061,14 @@ function assertManagedStopContainersAbsent(endpoint, ids2) {
1039
1061
  const env = { ...process.env };
1040
1062
  delete env.DOCKER_CONTEXT;
1041
1063
  delete env.DOCKER_HOST;
1042
- for (const id3 of ids2) {
1043
- const result = (0, import_node_child_process3.spawnSync)("docker", ["--host", endpoint, "inspect", "--format", "{{.Id}}", id3], {
1064
+ for (const id5 of ids2) {
1065
+ const result = (0, import_node_child_process3.spawnSync)("docker", ["--host", endpoint, "inspect", "--format", "{{.Id}}", id5], {
1044
1066
  env,
1045
1067
  encoding: "utf-8",
1046
1068
  timeout: MANAGED_STOP_DOCKER_TIMEOUT_MS,
1047
1069
  maxBuffer: MANAGED_STOP_DOCKER_MAX_BUFFER
1048
1070
  });
1049
- if (result.status !== 1 || result.error || result.signal || result.stdout !== "" && result.stdout !== "\n" && result.stdout !== "\r\n" || !hasExactManagedStopMissingObjectError(result.stderr, id3)) {
1071
+ if (result.status !== 1 || result.error || result.signal || result.stdout !== "" && result.stdout !== "\n" && result.stdout !== "\r\n" || !hasExactManagedStopMissingObjectError(result.stderr, id5)) {
1050
1072
  throw new Error("Managed stop Docker inspection did not prove container absence.");
1051
1073
  }
1052
1074
  }
@@ -2473,7 +2495,7 @@ function stopExactManagedService(containerId, service, options) {
2473
2495
  var import_node_fs10 = __toESM(require("fs"));
2474
2496
  var import_node_path12 = __toESM(require("path"));
2475
2497
  function proveManagedComposePopulation(options) {
2476
- const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers } = options;
2498
+ const { plan, repoPath, composeProject, providerRoot, featureDirectory, containers } = options;
2477
2499
  const services = /* @__PURE__ */ new Set();
2478
2500
  for (const container of containers) {
2479
2501
  const service = container.labels["com.docker.compose.service"] ?? "";
@@ -2491,7 +2513,7 @@ function proveManagedComposePopulation(options) {
2491
2513
  for (const file of files.slice(plan.composeFiles.length)) {
2492
2514
  if (import_node_path12.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2493
2515
  import_node_path12.default.basename(file)
2494
- ) || import_node_fs10.default.realpathSync(file) !== import_node_path12.default.join(import_node_fs10.default.realpathSync(devsyRoot), import_node_path12.default.relative(devsyRoot, file))) {
2516
+ ) || import_node_fs10.default.realpathSync(file) !== import_node_path12.default.join(import_node_fs10.default.realpathSync(providerRoot), import_node_path12.default.relative(providerRoot, file))) {
2495
2517
  throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
2496
2518
  }
2497
2519
  }
@@ -3220,8 +3242,8 @@ function managedRunnerId(uid, providerId) {
3220
3242
  if (uid === void 0) {
3221
3243
  return providerId;
3222
3244
  }
3223
- const bytes = Buffer.byteLength(uid);
3224
- return bytes === 16 || bytes === 40 ? uid : providerId;
3245
+ const bytes2 = Buffer.byteLength(uid);
3246
+ return bytes2 === 16 || bytes2 === 40 ? uid : providerId;
3225
3247
  }
3226
3248
  function identityOf(value) {
3227
3249
  return {
@@ -3309,7 +3331,7 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
3309
3331
  repoPath: state.repoPath,
3310
3332
  composeProject: state.composeProject,
3311
3333
  featureDirectory,
3312
- devsyRoot,
3334
+ providerRoot: devsyRoot,
3313
3335
  containers
3314
3336
  });
3315
3337
  if (primary.id !== primaryId)
@@ -3413,7 +3435,7 @@ function proveReplacementStopAbsence(state, baseline, replacement) {
3413
3435
  const newRunner = managedRunnerId(before.uid, baseline.providerId);
3414
3436
  const runners = oldRunner === newRunner ? [oldRunner] : [oldRunner, newRunner];
3415
3437
  const assertEmptyPopulation = () => {
3416
- if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || runners.some((id3) => inspectProviderRunnerContainers(baseline.endpoint, id3).length !== 0))
3438
+ if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || runners.some((id5) => inspectProviderRunnerContainers(baseline.endpoint, id5).length !== 0))
3417
3439
  throw new Error("Replacement stop observed a remaining workspace population.");
3418
3440
  };
3419
3441
  assertEmptyPopulation();
@@ -3496,6 +3518,7 @@ function managedStopRouteReferences(state) {
3496
3518
  }
3497
3519
 
3498
3520
  // src/core/repo-config.ts
3521
+ var import_node_crypto7 = require("crypto");
3499
3522
  var import_node_fs14 = __toESM(require("fs"));
3500
3523
  var import_node_path18 = __toESM(require("path"));
3501
3524
  var import_yaml3 = __toESM(require("yaml"));
@@ -3858,10 +3881,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
3858
3881
  }
3859
3882
  const now = (/* @__PURE__ */ new Date()).toISOString();
3860
3883
  const replacements = inputs.map((input) => {
3861
- const id3 = buildHostRouteId(input.repoPath, input.name);
3884
+ const id5 = buildHostRouteId(input.repoPath, input.name);
3862
3885
  return hostRouteFromInput(
3863
3886
  input,
3864
- routes.find((route) => route.id === id3),
3887
+ routes.find((route) => route.id === id5),
3865
3888
  now
3866
3889
  );
3867
3890
  });
@@ -3903,6 +3926,10 @@ var VALID_HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-
3903
3926
  var DEVROUTER_VERSION_RE = /^\d+\.\d+\.\d+$/;
3904
3927
  var VALID_ENV_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/i;
3905
3928
  var VALID_ENV_VAR_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
3929
+ var MAX_CAPACITY_PROFILES = 256;
3930
+ var MAX_CAPACITY_OPERATIONS = 64;
3931
+ var MAX_CAPACITY_TRANSITIONS = 1024;
3932
+ var MAX_CAPACITY_NAME_LENGTH = 64;
3906
3933
  var UPSTREAM_TEMPLATE_RE = /^(?:\$\{WORKSPACE\}|[a-zA-Z0-9._-])+:\d{1,5}$/;
3907
3934
  function assertUpstreamSpec(upstream, label) {
3908
3935
  if (upstream.includes(WORKSPACE_PLACEHOLDER)) {
@@ -4458,11 +4485,265 @@ function parseManagedRuntime(value, configPath) {
4458
4485
  processes
4459
4486
  };
4460
4487
  }
4488
+ function parseCapacitySafeInteger(value, pathLabel, minimum) {
4489
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || Object.is(value, -0)) {
4490
+ throw new Error(`${pathLabel} must be a safe integer.`);
4491
+ }
4492
+ if (value < minimum) {
4493
+ throw new Error(`${pathLabel} must be at least ${minimum}.`);
4494
+ }
4495
+ return value;
4496
+ }
4497
+ function parseCapacityName(value, pathLabel) {
4498
+ if (typeof value !== "string" || value.length === 0) {
4499
+ throw new Error(`${pathLabel} must be a non-empty string.`);
4500
+ }
4501
+ if (value !== value.trim()) {
4502
+ throw new Error(`${pathLabel} must not have leading or trailing whitespace.`);
4503
+ }
4504
+ if (value.length > MAX_CAPACITY_NAME_LENGTH) {
4505
+ throw new Error(
4506
+ `${pathLabel} exceeds the maximum length of ${MAX_CAPACITY_NAME_LENGTH} characters.`
4507
+ );
4508
+ }
4509
+ if ([...value].some(
4510
+ (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127
4511
+ )) {
4512
+ throw new Error(`${pathLabel} must not contain control characters.`);
4513
+ }
4514
+ return value;
4515
+ }
4516
+ function parseCapacityProfileKey(value, pathLabel, declaredProfiles) {
4517
+ const key = parseCapacityName(value, pathLabel);
4518
+ if (key === "full") return key;
4519
+ const names = key.split(",");
4520
+ const seen = /* @__PURE__ */ new Set();
4521
+ for (const [index, name] of names.entries()) {
4522
+ if (!PROFILE_NAME_RE.test(name)) {
4523
+ throw new Error(`${pathLabel}[${index}] is not a valid profile name.`);
4524
+ }
4525
+ if (seen.has(name)) {
4526
+ throw new Error(`${pathLabel} contains duplicate profile '${name}'.`);
4527
+ }
4528
+ if (!declaredProfiles || !Object.hasOwn(declaredProfiles, name)) {
4529
+ throw new Error(`${pathLabel} references undefined profile '${name}'.`);
4530
+ }
4531
+ seen.add(name);
4532
+ }
4533
+ return [...names].sort().join(",");
4534
+ }
4535
+ function parseCapacityDimension(value, pathLabel, runtime) {
4536
+ const dimension = ensureObject(value, pathLabel);
4537
+ ensureAllowedKeys(dimension, ["steadyBytes", "startupTotalBytes"], pathLabel);
4538
+ const minimum = runtime ? 1 : 0;
4539
+ const steadyBytes = parseCapacitySafeInteger(
4540
+ dimension.steadyBytes,
4541
+ `${pathLabel}.steadyBytes`,
4542
+ minimum
4543
+ );
4544
+ const startupTotalBytes = parseCapacitySafeInteger(
4545
+ dimension.startupTotalBytes,
4546
+ `${pathLabel}.startupTotalBytes`,
4547
+ minimum
4548
+ );
4549
+ if (startupTotalBytes < steadyBytes) {
4550
+ throw new Error(`${pathLabel}.startupTotalBytes must be at least steadyBytes.`);
4551
+ }
4552
+ return { steadyBytes, startupTotalBytes };
4553
+ }
4554
+ function parseCapacityOperation(value, pathLabel) {
4555
+ const operation = ensureObject(value, pathLabel);
4556
+ ensureAllowedKeys(operation, ["hostIncrementBytes", "runtimeIncrementBytes"], pathLabel);
4557
+ return {
4558
+ hostIncrementBytes: parseCapacitySafeInteger(
4559
+ operation.hostIncrementBytes,
4560
+ `${pathLabel}.hostIncrementBytes`,
4561
+ 0
4562
+ ),
4563
+ runtimeIncrementBytes: parseCapacitySafeInteger(
4564
+ operation.runtimeIncrementBytes,
4565
+ `${pathLabel}.runtimeIncrementBytes`,
4566
+ 0
4567
+ )
4568
+ };
4569
+ }
4570
+ function parseCapacityOperations(value, pathLabel) {
4571
+ const rawOperations = ensureObject(value, pathLabel);
4572
+ const operationEntries = Object.entries(rawOperations);
4573
+ if (operationEntries.length === 0) {
4574
+ throw new Error(`${pathLabel} must define at least one named operation.`);
4575
+ }
4576
+ if (operationEntries.length > MAX_CAPACITY_OPERATIONS) {
4577
+ throw new Error(`${pathLabel} exceeds the maximum of ${MAX_CAPACITY_OPERATIONS} operations.`);
4578
+ }
4579
+ const operations = {};
4580
+ for (const [operationName, operationValue] of operationEntries) {
4581
+ const name = parseCapacityName(operationName, `${pathLabel} key`);
4582
+ if (!/^[a-z][a-z0-9._-]*$/.test(name)) {
4583
+ throw new Error(`${pathLabel}.${operationName} is not a valid operation name.`);
4584
+ }
4585
+ operations[name] = parseCapacityOperation(operationValue, `${pathLabel}.${operationName}`);
4586
+ }
4587
+ return operations;
4588
+ }
4589
+ function parseCapacityTransition(value, pathLabel) {
4590
+ const transition2 = ensureObject(value, pathLabel);
4591
+ ensureAllowedKeys(transition2, ["hostTotalBytes", "runtimeTotalBytes"], pathLabel);
4592
+ return {
4593
+ hostTotalBytes: parseCapacitySafeInteger(
4594
+ transition2.hostTotalBytes,
4595
+ `${pathLabel}.hostTotalBytes`,
4596
+ 0
4597
+ ),
4598
+ runtimeTotalBytes: parseCapacitySafeInteger(
4599
+ transition2.runtimeTotalBytes,
4600
+ `${pathLabel}.runtimeTotalBytes`,
4601
+ 1
4602
+ )
4603
+ };
4604
+ }
4605
+ function parseCapacityEstimates(value, configPath, declaredProfiles) {
4606
+ if (value === void 0) return void 0;
4607
+ const pathLabel = `${configPath}.capacity`;
4608
+ const capacity = ensureObject(value, pathLabel);
4609
+ ensureAllowedKeys(capacity, ["version", "profiles", "transitions"], pathLabel);
4610
+ const version = parseCapacitySafeInteger(capacity.version, `${pathLabel}.version`, 1);
4611
+ if (version !== 1) {
4612
+ throw new Error(`${pathLabel}.version must be 1.`);
4613
+ }
4614
+ const rawProfiles = ensureObject(capacity.profiles, `${pathLabel}.profiles`);
4615
+ const profileEntries = Object.entries(rawProfiles);
4616
+ if (profileEntries.length === 0) {
4617
+ throw new Error(`${pathLabel}.profiles must define at least one exact profile combination.`);
4618
+ }
4619
+ if (profileEntries.length > MAX_CAPACITY_PROFILES) {
4620
+ throw new Error(
4621
+ `${pathLabel}.profiles exceeds the maximum of ${MAX_CAPACITY_PROFILES} combinations.`
4622
+ );
4623
+ }
4624
+ const profiles = {};
4625
+ for (const [rawKey, profileValue] of profileEntries) {
4626
+ const profileKey = parseCapacityProfileKey(
4627
+ rawKey,
4628
+ `${pathLabel}.profiles key`,
4629
+ declaredProfiles
4630
+ );
4631
+ if (Object.hasOwn(profiles, profileKey)) {
4632
+ throw new Error(
4633
+ `${pathLabel}.profiles contains aliases for the same exact combination '${profileKey}'.`
4634
+ );
4635
+ }
4636
+ const profile = ensureObject(profileValue, `${pathLabel}.profiles.${rawKey}`);
4637
+ ensureAllowedKeys(
4638
+ profile,
4639
+ ["host", "runtime", "operations"],
4640
+ `${pathLabel}.profiles.${rawKey}`
4641
+ );
4642
+ profiles[profileKey] = {
4643
+ host: parseCapacityDimension(profile.host, `${pathLabel}.profiles.${rawKey}.host`, false),
4644
+ runtime: parseCapacityDimension(
4645
+ profile.runtime,
4646
+ `${pathLabel}.profiles.${rawKey}.runtime`,
4647
+ true
4648
+ ),
4649
+ operations: parseCapacityOperations(
4650
+ profile.operations,
4651
+ `${pathLabel}.profiles.${rawKey}.operations`
4652
+ )
4653
+ };
4654
+ }
4655
+ let transitions;
4656
+ if (capacity.transitions !== void 0) {
4657
+ const rawTransitions = ensureObject(capacity.transitions, `${pathLabel}.transitions`);
4658
+ const transitionEntries = Object.entries(rawTransitions);
4659
+ if (transitionEntries.length === 0) {
4660
+ throw new Error(`${pathLabel}.transitions must define at least one transition.`);
4661
+ }
4662
+ transitions = {};
4663
+ let transitionCount = 0;
4664
+ for (const [rawSource, targetValue] of transitionEntries) {
4665
+ const source = parseCapacityProfileKey(
4666
+ rawSource,
4667
+ `${pathLabel}.transitions source key`,
4668
+ declaredProfiles
4669
+ );
4670
+ if (!Object.hasOwn(profiles, source)) {
4671
+ throw new Error(
4672
+ `${pathLabel}.transitions source '${source}' must have a profile estimate.`
4673
+ );
4674
+ }
4675
+ if (Object.hasOwn(transitions, source)) {
4676
+ throw new Error(`${pathLabel}.transitions contains aliases for source '${source}'.`);
4677
+ }
4678
+ const rawTargets = ensureObject(targetValue, `${pathLabel}.transitions.${rawSource}`);
4679
+ const targetEntries = Object.entries(rawTargets);
4680
+ if (targetEntries.length === 0) {
4681
+ throw new Error(`${pathLabel}.transitions.${rawSource} must define a target.`);
4682
+ }
4683
+ const targets = {};
4684
+ for (const [rawTarget, transitionValue] of targetEntries) {
4685
+ transitionCount += 1;
4686
+ if (transitionCount > MAX_CAPACITY_TRANSITIONS) {
4687
+ throw new Error(
4688
+ `${pathLabel}.transitions exceeds the maximum of ${MAX_CAPACITY_TRANSITIONS} entries.`
4689
+ );
4690
+ }
4691
+ const target = parseCapacityProfileKey(
4692
+ rawTarget,
4693
+ `${pathLabel}.transitions.${rawSource} target key`,
4694
+ declaredProfiles
4695
+ );
4696
+ if (!Object.hasOwn(profiles, target)) {
4697
+ throw new Error(
4698
+ `${pathLabel}.transitions target '${target}' must have a profile estimate.`
4699
+ );
4700
+ }
4701
+ if (Object.hasOwn(targets, target)) {
4702
+ throw new Error(
4703
+ `${pathLabel}.transitions.${rawSource} contains aliases for '${target}'.`
4704
+ );
4705
+ }
4706
+ const transition2 = parseCapacityTransition(
4707
+ transitionValue,
4708
+ `${pathLabel}.transitions.${rawSource}.${rawTarget}`
4709
+ );
4710
+ const sourceProfile = profiles[source];
4711
+ const targetProfile = profiles[target];
4712
+ if (transition2.hostTotalBytes < Math.max(sourceProfile.host.steadyBytes, targetProfile.host.steadyBytes)) {
4713
+ throw new Error(
4714
+ `${pathLabel}.transitions.${rawSource}.${rawTarget}.hostTotalBytes must cover both steady host estimates.`
4715
+ );
4716
+ }
4717
+ if (transition2.runtimeTotalBytes < Math.max(sourceProfile.runtime.steadyBytes, targetProfile.runtime.steadyBytes)) {
4718
+ throw new Error(
4719
+ `${pathLabel}.transitions.${rawSource}.${rawTarget}.runtimeTotalBytes must cover both steady runtime estimates.`
4720
+ );
4721
+ }
4722
+ targets[target] = transition2;
4723
+ }
4724
+ transitions[source] = targets;
4725
+ }
4726
+ }
4727
+ return {
4728
+ version: 1,
4729
+ profiles,
4730
+ ...transitions ? { transitions } : {}
4731
+ };
4732
+ }
4461
4733
  function parseConfig(raw, configPath) {
4462
4734
  const root = ensureObject(raw, configPath);
4463
4735
  ensureAllowedKeys(
4464
4736
  root,
4465
- ["version", "devrouter", "project", "secretManager", "managedRuntime", "profiles", "apps"],
4737
+ [
4738
+ "version",
4739
+ "devrouter",
4740
+ "project",
4741
+ "secretManager",
4742
+ "managedRuntime",
4743
+ "profiles",
4744
+ "capacity",
4745
+ "apps"
4746
+ ],
4466
4747
  configPath
4467
4748
  );
4468
4749
  const version = toIntegerOrThrow(root.version, `${configPath}.version`);
@@ -4534,6 +4815,7 @@ function parseConfig(raw, configPath) {
4534
4815
  }
4535
4816
  const managedRuntime = parseManagedRuntime(root.managedRuntime, configPath);
4536
4817
  const profiles = parseProfiles(root.profiles, configPath, apps, managedRuntime);
4818
+ const capacity = parseCapacityEstimates(root.capacity, configPath, profiles);
4537
4819
  return {
4538
4820
  version: 1,
4539
4821
  ...devrouter ? { devrouter } : {},
@@ -4541,6 +4823,7 @@ function parseConfig(raw, configPath) {
4541
4823
  ...secretManager ? { secretManager } : {},
4542
4824
  ...managedRuntime ? { managedRuntime } : {},
4543
4825
  ...profiles ? { profiles } : {},
4826
+ ...capacity ? { capacity } : {},
4544
4827
  apps
4545
4828
  };
4546
4829
  }
@@ -4875,7 +5158,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs14.default.rea
4875
5158
  const config = parseConfig(parsed ?? {}, configPath);
4876
5159
  const requiredVersion = config.devrouter?.version;
4877
5160
  if (requiredVersion && !hasWarnedVersionMismatch) {
4878
- const cliVersion = true ? "0.0.67" : "0.0.0-dev";
5161
+ const cliVersion = true ? "0.0.68" : "0.0.0-dev";
4879
5162
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4880
5163
  hasWarnedVersionMismatch = true;
4881
5164
  process.stderr.write(
@@ -5054,7 +5337,7 @@ async function ensureNetwork(name) {
5054
5337
 
5055
5338
  // src/core/tls.ts
5056
5339
  var import_node_child_process13 = require("child_process");
5057
- var import_node_crypto7 = require("crypto");
5340
+ var import_node_crypto8 = require("crypto");
5058
5341
  var import_node_fs15 = __toESM(require("fs"));
5059
5342
  var import_node_path19 = __toESM(require("path"));
5060
5343
  var DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
@@ -5124,7 +5407,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
5124
5407
  return normalizeUniqueHosts(names);
5125
5408
  }
5126
5409
  function parseCertificateDnsHosts(pem) {
5127
- const certificate = new import_node_crypto7.X509Certificate(pem);
5410
+ const certificate = new import_node_crypto8.X509Certificate(pem);
5128
5411
  const subjectAltName = certificate.subjectAltName ?? "";
5129
5412
  if (subjectAltName.length === 0) {
5130
5413
  return [];
@@ -5533,7 +5816,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
5533
5816
  repoPath,
5534
5817
  composeProject: state.composeProject,
5535
5818
  featureDirectory,
5536
- devsyRoot,
5819
+ providerRoot: devsyRoot,
5537
5820
  containers
5538
5821
  });
5539
5822
  if (previous && (!sameSet(
@@ -5633,12 +5916,12 @@ function networkProviderEnvironment(binding, source) {
5633
5916
  }
5634
5917
 
5635
5918
  // src/core/network-claim-lookup.ts
5636
- var import_node_crypto9 = require("crypto");
5919
+ var import_node_crypto10 = require("crypto");
5637
5920
  var import_node_fs18 = __toESM(require("fs"));
5638
5921
  var import_node_path23 = __toESM(require("path"));
5639
5922
 
5640
5923
  // src/core/network-claims.ts
5641
- var import_node_crypto8 = require("crypto");
5924
+ var import_node_crypto9 = require("crypto");
5642
5925
  var import_node_fs17 = __toESM(require("fs"));
5643
5926
  var import_node_path22 = __toESM(require("path"));
5644
5927
 
@@ -6828,7 +7111,7 @@ function assertDirectory(directory) {
6828
7111
  }
6829
7112
  }
6830
7113
  function hashKey(value) {
6831
- return (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
7114
+ return (0, import_node_crypto9.createHash)("sha256").update(value).digest("hex");
6832
7115
  }
6833
7116
  function cloneFence(fence) {
6834
7117
  return { ...fence };
@@ -6856,7 +7139,7 @@ function claimError(code, message) {
6856
7139
  function networkOwnerKey(repoPath) {
6857
7140
  if (!import_node_path23.default.isAbsolute(repoPath) || import_node_path23.default.resolve(repoPath) !== repoPath)
6858
7141
  throw new Error("Invalid network owner path.");
6859
- return (0, import_node_crypto9.createHash)("sha256").update(repoPath).digest("hex");
7142
+ return (0, import_node_crypto10.createHash)("sha256").update(repoPath).digest("hex");
6860
7143
  }
6861
7144
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6862
7145
  let names;
@@ -6881,22 +7164,22 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6881
7164
  );
6882
7165
  try {
6883
7166
  if (!import_node_fs18.default.fstatSync(descriptor).isFile()) throw new Error();
6884
- const bytes = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
7167
+ const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
6885
7168
  let size = 0;
6886
- while (size < bytes.length) {
6887
- const count = import_node_fs18.default.readSync(descriptor, bytes, size, bytes.length - size, null);
7169
+ while (size < bytes2.length) {
7170
+ const count = import_node_fs18.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
6888
7171
  if (!count) break;
6889
7172
  size += count;
6890
7173
  }
6891
7174
  if (size > NETWORK_CLAIMS_MAX_FILE_BYTES) throw new Error();
6892
- value = JSON.parse(bytes.subarray(0, size).toString("utf8"));
7175
+ value = JSON.parse(bytes2.subarray(0, size).toString("utf8"));
6893
7176
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
6894
7177
  } catch {
6895
7178
  throw new Error("Retained network claim could not be read safely.");
6896
7179
  } finally {
6897
7180
  import_node_fs18.default.closeSync(descriptor);
6898
7181
  }
6899
- if (typeof value.daemonId !== "string" || `${(0, import_node_crypto9.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
7182
+ if (typeof value.daemonId !== "string" || `${(0, import_node_crypto10.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
6900
7183
  throw new Error("Retained network daemon identity is invalid.");
6901
7184
  const claim2 = readNetworkClaim({
6902
7185
  daemonId: value.daemonId,
@@ -6913,7 +7196,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6913
7196
 
6914
7197
  // src/core/network-provider-inspect.ts
6915
7198
  var import_node_child_process14 = require("child_process");
6916
- var import_node_crypto10 = require("crypto");
7199
+ var import_node_crypto11 = require("crypto");
6917
7200
  function object(value) {
6918
7201
  if (!value || typeof value !== "object" || Array.isArray(value))
6919
7202
  throw new Error("Invalid provider evidence.");
@@ -6933,7 +7216,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
6933
7216
  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 !== "") {
6934
7217
  throw new Error("Docker provider definition is not qualified for network allocation.");
6935
7218
  }
6936
- return (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
7219
+ return (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
6937
7220
  }
6938
7221
  function read(provider, args) {
6939
7222
  const result = (0, import_node_child_process14.spawnSync)(provider, args, {
@@ -6995,9 +7278,9 @@ function inspectNetworkProviderBinding(input) {
6995
7278
  const option = (key, localOnly = false) => {
6996
7279
  const value = localOnly ? stored[key] : stored[key] ?? settings[key];
6997
7280
  if (value === void 0) return null;
6998
- const text3 = object(value).value;
6999
- if (typeof text3 !== "string") throw new Error();
7000
- return text3 || null;
7281
+ const text4 = object(value).value;
7282
+ if (typeof text4 !== "string") throw new Error();
7283
+ return text4 || null;
7001
7284
  };
7002
7285
  if (workspaceProvider && workspaceProvider.name !== "docker") throw new Error();
7003
7286
  if (option("DOCKER_ELEVATION") && option("DOCKER_ELEVATION") !== "none") throw new Error();
@@ -7197,12 +7480,12 @@ function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
7197
7480
  function assertDevsyTarget(devsyId, repoPath) {
7198
7481
  const workspaces = listDevsyWorkspaces();
7199
7482
  const existing = selectDevsyWorkspace(workspaces, repoPath);
7200
- const id3 = devsyId ?? existing?.id;
7201
- if (id3) {
7202
- const before = inspectDevsyWorkspaceOwnership(workspaces, id3, repoPath);
7483
+ const id5 = devsyId ?? existing?.id;
7484
+ if (id5) {
7485
+ const before = inspectDevsyWorkspaceOwnership(workspaces, id5, repoPath);
7203
7486
  if (before.status === "conflict") throw new Error(before.reason);
7204
7487
  }
7205
- return id3;
7488
+ return id5;
7206
7489
  }
7207
7490
  async function startDevsyWorkspace(options) {
7208
7491
  const activity = options.recreate ? "Devsy recreate" : "Devsy start";
@@ -7314,7 +7597,572 @@ var ExecutionOutcomeError = class extends Error {
7314
7597
  };
7315
7598
 
7316
7599
  // src/core/reliability-lifecycle.ts
7317
- var import_node_path33 = __toESM(require("path"));
7600
+ var import_node_crypto20 = require("crypto");
7601
+ var import_node_path38 = __toESM(require("path"));
7602
+ var import_node_util6 = require("util");
7603
+
7604
+ // src/core/capacity-accounting.ts
7605
+ function bytes(value) {
7606
+ return Number.isSafeInteger(value) && value >= 0;
7607
+ }
7608
+ function evaluateCapacity(budgets, samples, charges, requested, nowMs, maxSampleAgeMs, pools = []) {
7609
+ if (!bytes(nowMs) || !bytes(maxSampleAgeMs)) throw new Error("Invalid capacity sample clock.");
7610
+ for (const domain of Object.keys(requested.totals).sort()) {
7611
+ const fail2 = (reason2) => ({ admitted: false, domain, reason: reason2 });
7612
+ const budget = budgets[domain];
7613
+ const sample = samples[domain];
7614
+ if (!budget || !sample || !bytes(budget.capacityBytes) || !bytes(budget.protectedHeadroomBytes) || budget.protectedHeadroomBytes >= budget.capacityBytes || !Number.isSafeInteger(budget.startupSlots) || budget.startupSlots < 1 || !Number.isSafeInteger(budget.heavySlots) || budget.heavySlots < 1 || !bytes(sample.unmanagedBytes) || !bytes(sample.sharedBytes) || !bytes(sample.sampledAtMs) || Object.values(sample.ownedBytes).some((value) => !bytes(value)) || !bytes(requested.totals[domain]))
7615
+ return fail2("unknown");
7616
+ if (sample.sampledAtMs > nowMs || nowMs - sample.sampledAtMs > maxSampleAgeMs)
7617
+ return fail2("stale");
7618
+ if (sample.pressure !== "normal")
7619
+ return fail2(sample.pressure === "pressured" ? "pressure" : "unknown");
7620
+ const totals = /* @__PURE__ */ new Map();
7621
+ let startups = 0;
7622
+ let heavy = 0;
7623
+ for (const charge of charges) {
7624
+ if (!Object.hasOwn(charge.totals, domain)) continue;
7625
+ if (!bytes(charge.totals[domain]) || totals.has(charge.environmentId)) return fail2("unknown");
7626
+ totals.set(charge.environmentId, charge.totals[domain]);
7627
+ if (charge.environmentId !== requested.environmentId) {
7628
+ if (charge.startup) startups++;
7629
+ if (charge.heavy) heavy++;
7630
+ }
7631
+ }
7632
+ totals.set(
7633
+ requested.environmentId,
7634
+ Math.max(totals.get(requested.environmentId) ?? 0, requested.totals[domain])
7635
+ );
7636
+ for (const [environment, observed] of Object.entries(sample.ownedBytes))
7637
+ totals.set(environment, Math.max(totals.get(environment) ?? 0, observed));
7638
+ let used = BigInt(sample.unmanagedBytes) + BigInt(sample.sharedBytes);
7639
+ for (const total of totals.values()) used += BigInt(total);
7640
+ for (const pool of pools) {
7641
+ if (pool.hostDomain !== domain) continue;
7642
+ if (!bytes(pool.hostChargeCeilingBytes)) return fail2("unknown");
7643
+ used += BigInt(pool.hostChargeCeilingBytes);
7644
+ }
7645
+ if (used > BigInt(budget.capacityBytes) - BigInt(budget.protectedHeadroomBytes))
7646
+ return fail2("memory");
7647
+ const existing = charges.find((charge) => charge.environmentId === requested.environmentId);
7648
+ if ((requested.startup || existing?.startup) && startups >= budget.startupSlots)
7649
+ return fail2("startup-slot");
7650
+ if ((requested.heavy || existing?.heavy) && heavy >= budget.heavySlots)
7651
+ return fail2("heavy-slot");
7652
+ }
7653
+ if (Object.keys(requested.totals).length === 0)
7654
+ throw new Error("Capacity request has no domains.");
7655
+ return { admitted: true };
7656
+ }
7657
+
7658
+ // src/core/capacity-policy.ts
7659
+ var import_node_fs20 = __toESM(require("fs"));
7660
+ var import_node_path25 = __toESM(require("path"));
7661
+
7662
+ // src/core/capacity-store.ts
7663
+ var import_node_fs21 = __toESM(require("fs"));
7664
+ var import_node_path26 = __toESM(require("path"));
7665
+ var MAX_BYTES = 1048576;
7666
+ var CapacitySnapshotChangedError = class extends Error {
7667
+ constructor() {
7668
+ super("Capacity snapshot changed.");
7669
+ this.name = "CapacitySnapshotChangedError";
7670
+ }
7671
+ };
7672
+ function assertPrivateDirectory(directoryPath) {
7673
+ import_node_fs21.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
7674
+ const directory = import_node_fs21.default.lstatSync(directoryPath);
7675
+ if (!directory.isDirectory() || directory.isSymbolicLink() || directory.uid !== process.getuid?.() || (directory.mode & 63) !== 0)
7676
+ throw new Error("Capacity reservation directory is not private.");
7677
+ }
7678
+ function object2(value, fields2) {
7679
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== fields2.length || Object.keys(value).some((key) => !fields2.includes(key)))
7680
+ throw new Error("Invalid capacity reservation fields.");
7681
+ }
7682
+ function id2(value) {
7683
+ return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
7684
+ }
7685
+ function validatePools(value) {
7686
+ if (!Array.isArray(value) || value.length > 256) throw new Error("Invalid capacity pools.");
7687
+ const daemons = /* @__PURE__ */ new Set();
7688
+ const runtimes = /* @__PURE__ */ new Set();
7689
+ for (const pool of value) {
7690
+ object2(pool, ["daemonId", "runtimeDomain", "hostDomain", "hostChargeCeilingBytes"]);
7691
+ if (typeof pool.daemonId !== "string" || pool.daemonId.length === 0 || pool.daemonId.length > 256 || pool.daemonId !== pool.daemonId.trim() || [...pool.daemonId].some(
7692
+ (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127
7693
+ ) || !id2(pool.runtimeDomain) || !id2(pool.hostDomain) || pool.runtimeDomain === pool.hostDomain || !Number.isSafeInteger(pool.hostChargeCeilingBytes) || Number(pool.hostChargeCeilingBytes) <= 0 || daemons.has(pool.daemonId) || runtimes.has(pool.runtimeDomain))
7694
+ throw new Error("Invalid capacity pool identity or ceiling.");
7695
+ daemons.add(pool.daemonId);
7696
+ runtimes.add(pool.runtimeDomain);
7697
+ }
7698
+ }
7699
+ function validate(value) {
7700
+ const hasPools = !!value && typeof value === "object" && Object.hasOwn(value, "pools");
7701
+ object2(value, ["version", "revision", "reservations", ...hasPools ? ["pools"] : []]);
7702
+ if (hasPools) validatePools(value.pools);
7703
+ if (value.version !== 1 || !Number.isSafeInteger(value.revision) || Number(value.revision) < 0 || !Array.isArray(value.reservations) || value.reservations.length > 256)
7704
+ throw new Error("Invalid capacity reservation snapshot.");
7705
+ const environments = /* @__PURE__ */ new Set();
7706
+ const operations = /* @__PURE__ */ new Set();
7707
+ const reservations = /* @__PURE__ */ new Set();
7708
+ for (const reservation of value.reservations) {
7709
+ object2(reservation, [
7710
+ "environmentId",
7711
+ "operationId",
7712
+ "reservationId",
7713
+ "policyRevision",
7714
+ "totals",
7715
+ "startup",
7716
+ "heavy"
7717
+ ]);
7718
+ if (!id2(reservation.environmentId) || !id2(reservation.operationId) || !id2(reservation.reservationId) || !Number.isSafeInteger(reservation.policyRevision) || Number(reservation.policyRevision) < 1 || typeof reservation.startup !== "boolean" || typeof reservation.heavy !== "boolean" || environments.has(reservation.environmentId) || operations.has(reservation.operationId) || reservations.has(reservation.reservationId))
7719
+ throw new Error("Invalid capacity reservation identity.");
7720
+ environments.add(reservation.environmentId);
7721
+ operations.add(reservation.operationId);
7722
+ reservations.add(reservation.reservationId);
7723
+ const totals = reservation.totals;
7724
+ if (!totals || typeof totals !== "object" || Array.isArray(totals) || Object.keys(totals).length < 1 || Object.keys(totals).length > 256 || Object.entries(totals).some(
7725
+ ([key, bytes2]) => !id2(key) || !Number.isSafeInteger(bytes2) || Number(bytes2) < 0
7726
+ ))
7727
+ throw new Error("Invalid capacity reservation totals.");
7728
+ }
7729
+ }
7730
+ function mergePools(pools, incoming) {
7731
+ let changed = false;
7732
+ for (const requested of incoming) {
7733
+ const retained = pools.find((pool) => pool.daemonId === requested.daemonId);
7734
+ if (retained) {
7735
+ if (retained.hostDomain !== requested.hostDomain || retained.runtimeDomain !== requested.runtimeDomain)
7736
+ throw new Error("Capacity pool binding changed.");
7737
+ if (requested.hostChargeCeilingBytes > retained.hostChargeCeilingBytes) {
7738
+ retained.hostChargeCeilingBytes = requested.hostChargeCeilingBytes;
7739
+ changed = true;
7740
+ }
7741
+ } else {
7742
+ pools.push(structuredClone(requested));
7743
+ changed = true;
7744
+ }
7745
+ }
7746
+ validatePools(pools);
7747
+ return changed;
7748
+ }
7749
+ function serializeSnapshot(snapshot) {
7750
+ const contents = `${JSON.stringify(snapshot)}
7751
+ `;
7752
+ if (Buffer.byteLength(contents) > MAX_BYTES)
7753
+ throw new Error("Capacity reservation snapshot exceeds byte limit.");
7754
+ return contents;
7755
+ }
7756
+ var CapacityStore = class {
7757
+ constructor(directory) {
7758
+ this.directory = directory;
7759
+ this.file = import_node_path26.default.join(directory, "capacity-reservations.json");
7760
+ }
7761
+ file;
7762
+ read() {
7763
+ let descriptor;
7764
+ try {
7765
+ descriptor = import_node_fs21.default.openSync(this.file, import_node_fs21.default.constants.O_RDONLY | import_node_fs21.default.constants.O_NOFOLLOW);
7766
+ } catch (error) {
7767
+ if (error.code === "ENOENT")
7768
+ return { version: 1, revision: 0, reservations: [] };
7769
+ throw error;
7770
+ }
7771
+ try {
7772
+ const stat = import_node_fs21.default.fstatSync(descriptor);
7773
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_BYTES)
7774
+ throw new Error("Unsafe capacity reservation snapshot.");
7775
+ const buffer = Buffer.alloc(MAX_BYTES + 1);
7776
+ const count = import_node_fs21.default.readSync(descriptor, buffer, 0, buffer.length, 0);
7777
+ if (count > MAX_BYTES) throw new Error("Capacity reservation snapshot exceeds byte limit.");
7778
+ const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
7779
+ validate(value);
7780
+ return value;
7781
+ } finally {
7782
+ import_node_fs21.default.closeSync(descriptor);
7783
+ }
7784
+ }
7785
+ /**
7786
+ * Caller must revoke journal authority and prove exact physical cessation first.
7787
+ * An absent reservation still advances the revision to fence that proof.
7788
+ */
7789
+ settleEnvironmentAfterStop(environmentId, expectedRevision) {
7790
+ if (!id2(environmentId)) throw new Error("Invalid capacity environment ID.");
7791
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
7792
+ throw new Error("Invalid capacity settlement revision.");
7793
+ assertPrivateDirectory(this.directory);
7794
+ return withFileLockSync(
7795
+ `${this.file}.lock`,
7796
+ { activity: "capacity settlement", waitMs: 100 },
7797
+ () => {
7798
+ const snapshot = this.read();
7799
+ if (snapshot.revision !== expectedRevision) throw new CapacitySnapshotChangedError();
7800
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER)
7801
+ throw new Error("Capacity reservation revision exhausted.");
7802
+ const index = snapshot.reservations.findIndex(
7803
+ (entry) => entry.environmentId === environmentId
7804
+ );
7805
+ if (index >= 0) snapshot.reservations.splice(index, 1);
7806
+ snapshot.revision++;
7807
+ validate(snapshot);
7808
+ writeFileAtomically(this.file, serializeSnapshot(snapshot));
7809
+ return { settled: index >= 0, revision: snapshot.revision };
7810
+ }
7811
+ );
7812
+ }
7813
+ /** Retain positively observed pools even when their charge already exceeds admission budgets. */
7814
+ mergeObservedPools(pools, expectedRevision) {
7815
+ validatePools(pools);
7816
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
7817
+ throw new Error("Invalid capacity expected revision.");
7818
+ assertPrivateDirectory(this.directory);
7819
+ return withFileLockSync(
7820
+ `${this.file}.lock`,
7821
+ { activity: "capacity pool observation", waitMs: 100 },
7822
+ () => {
7823
+ const snapshot = this.read();
7824
+ if (snapshot.revision !== expectedRevision) throw new CapacitySnapshotChangedError();
7825
+ const merged = structuredClone(snapshot.pools ?? []);
7826
+ if (!mergePools(merged, pools)) return { changed: false, revision: snapshot.revision };
7827
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER)
7828
+ throw new Error("Capacity reservation revision exhausted.");
7829
+ snapshot.pools = merged;
7830
+ snapshot.revision++;
7831
+ validate(snapshot);
7832
+ writeFileAtomically(this.file, serializeSnapshot(snapshot));
7833
+ return { changed: true, revision: snapshot.revision };
7834
+ }
7835
+ );
7836
+ }
7837
+ /**
7838
+ * Caller must revoke pool launch authority and prove exact VM cessation first.
7839
+ * Container or environment cessation alone never permits releasing this ceiling.
7840
+ */
7841
+ settlePoolAfterCessation(identity2, expectedRevision) {
7842
+ object2(identity2, ["daemonId", "runtimeDomain", "hostDomain"]);
7843
+ validatePools([{ ...identity2, hostChargeCeilingBytes: 1 }]);
7844
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
7845
+ throw new Error("Invalid capacity settlement revision.");
7846
+ assertPrivateDirectory(this.directory);
7847
+ return withFileLockSync(
7848
+ `${this.file}.lock`,
7849
+ { activity: "capacity pool settlement", waitMs: 100 },
7850
+ () => {
7851
+ const snapshot = this.read();
7852
+ if (snapshot.revision !== expectedRevision) throw new CapacitySnapshotChangedError();
7853
+ const pools = snapshot.pools ?? [];
7854
+ const index = pools.findIndex((pool2) => pool2.daemonId === identity2.daemonId);
7855
+ const pool = pools[index];
7856
+ if (pool && (pool.hostDomain !== identity2.hostDomain || pool.runtimeDomain !== identity2.runtimeDomain) || pools.some(
7857
+ (entry) => entry.runtimeDomain === identity2.runtimeDomain && entry.daemonId !== identity2.daemonId
7858
+ ) || snapshot.reservations.some((entry) => Object.hasOwn(entry.totals, identity2.runtimeDomain)))
7859
+ throw new Error("Capacity pool cessation identity remains bound.");
7860
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER)
7861
+ throw new Error("Capacity reservation revision exhausted.");
7862
+ if (pool) pools.splice(index, 1);
7863
+ snapshot.revision++;
7864
+ validate(snapshot);
7865
+ writeFileAtomically(this.file, serializeSnapshot(snapshot));
7866
+ return { settled: !!pool, revision: snapshot.revision };
7867
+ }
7868
+ );
7869
+ }
7870
+ /** Caller persists phase proof and revokes authority before reducing this exact row. */
7871
+ reduceAfterPhase(target, expectedRevision) {
7872
+ validate({ version: 1, revision: 0, reservations: [target] });
7873
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
7874
+ throw new Error("Invalid capacity settlement revision.");
7875
+ assertPrivateDirectory(this.directory);
7876
+ withFileLockSync(
7877
+ `${this.file}.lock`,
7878
+ { activity: "capacity phase settlement", waitMs: 100 },
7879
+ () => {
7880
+ const snapshot = this.read();
7881
+ if (snapshot.revision !== expectedRevision) throw new CapacitySnapshotChangedError();
7882
+ const index = snapshot.reservations.findIndex(
7883
+ (entry) => entry.environmentId === target.environmentId
7884
+ );
7885
+ const current = snapshot.reservations[index];
7886
+ if (!current || current.reservationId !== target.reservationId || current.operationId !== target.operationId || current.policyRevision !== target.policyRevision)
7887
+ throw new Error("Capacity phase settlement predecessor changed.");
7888
+ if (Object.keys(current.totals).length !== Object.keys(target.totals).length || Object.entries(target.totals).some(
7889
+ ([domain, bytes2]) => !Object.hasOwn(current.totals, domain) || bytes2 > current.totals[domain]
7890
+ ) || target.startup && !current.startup || target.heavy && !current.heavy)
7891
+ throw new Error("Capacity phase settlement cannot acquire resources.");
7892
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER)
7893
+ throw new Error("Capacity reservation revision exhausted.");
7894
+ snapshot.reservations[index] = structuredClone(target);
7895
+ snapshot.revision++;
7896
+ validate(snapshot);
7897
+ writeFileAtomically(this.file, serializeSnapshot(snapshot));
7898
+ }
7899
+ );
7900
+ }
7901
+ reserve(request, budgets, samples, nowMs, maxSampleAgeMs, previous, expectedRevision, requestedPool) {
7902
+ validate({ version: 1, revision: 0, reservations: [request] });
7903
+ if (requestedPool !== void 0) {
7904
+ validatePools([requestedPool]);
7905
+ if (!Object.hasOwn(request.totals, requestedPool.hostDomain) || !Object.hasOwn(request.totals, requestedPool.runtimeDomain))
7906
+ throw new Error("Capacity pool request lacks its bound domains.");
7907
+ }
7908
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
7909
+ throw new Error("Invalid capacity expected revision.");
7910
+ assertPrivateDirectory(this.directory);
7911
+ return withFileLockSync(
7912
+ `${this.file}.lock`,
7913
+ { activity: "capacity reservation", waitMs: 100 },
7914
+ () => {
7915
+ const snapshot = this.read();
7916
+ if (snapshot.revision !== expectedRevision) throw new CapacitySnapshotChangedError();
7917
+ const pools = structuredClone(snapshot.pools ?? []);
7918
+ const poolChanged = mergePools(pools, requestedPool === void 0 ? [] : [requestedPool]);
7919
+ const existing = snapshot.reservations.find(
7920
+ (entry) => entry.environmentId === request.environmentId
7921
+ );
7922
+ if (previous) {
7923
+ if (snapshot.revision !== previous.revision || !existing || existing.reservationId !== previous.reservationId || existing.operationId !== previous.operationId || existing.policyRevision !== request.policyRevision)
7924
+ throw new Error("Capacity expansion predecessor changed.");
7925
+ const totals = { ...existing.totals };
7926
+ for (const [domain, bytes2] of Object.entries(request.totals))
7927
+ totals[domain] = Math.max(totals[domain] ?? 0, bytes2);
7928
+ request = {
7929
+ ...request,
7930
+ totals,
7931
+ startup: existing.startup || request.startup,
7932
+ heavy: existing.heavy || request.heavy
7933
+ };
7934
+ } else if (existing) {
7935
+ if (existing.reservationId !== request.reservationId || existing.operationId !== request.operationId || existing.policyRevision !== request.policyRevision || existing.startup !== request.startup || existing.heavy !== request.heavy || Object.keys(existing.totals).length !== Object.keys(request.totals).length || Object.entries(existing.totals).some(
7936
+ ([domain, bytes2]) => request.totals[domain] !== bytes2
7937
+ ))
7938
+ throw new Error("Environment retains an earlier capacity reservation.");
7939
+ }
7940
+ const decision = evaluateCapacity(
7941
+ budgets,
7942
+ samples,
7943
+ snapshot.reservations,
7944
+ request,
7945
+ nowMs,
7946
+ maxSampleAgeMs,
7947
+ pools
7948
+ );
7949
+ if (!decision.admitted) return decision;
7950
+ if (!previous && existing && !poolChanged)
7951
+ return { admitted: true, revision: snapshot.revision, joined: true };
7952
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER)
7953
+ throw new Error("Capacity reservation revision exhausted.");
7954
+ snapshot.revision++;
7955
+ if (poolChanged) snapshot.pools = pools;
7956
+ if (existing)
7957
+ snapshot.reservations[snapshot.reservations.indexOf(existing)] = structuredClone(request);
7958
+ else snapshot.reservations.push(structuredClone(request));
7959
+ validate(snapshot);
7960
+ writeFileAtomically(this.file, serializeSnapshot(snapshot));
7961
+ return { admitted: true, revision: snapshot.revision, joined: false };
7962
+ }
7963
+ );
7964
+ }
7965
+ };
7966
+
7967
+ // src/core/controller-client.ts
7968
+ var import_node_crypto12 = require("crypto");
7969
+ var import_node_fs22 = __toESM(require("fs"));
7970
+ var import_node_net2 = __toESM(require("net"));
7971
+ var import_node_path28 = __toESM(require("path"));
7972
+
7973
+ // src/core/controller-protocol.ts
7974
+ var import_node_path27 = __toESM(require("path"));
7975
+ var CONTROLLER_FRAME_BYTES = 64 * 1024;
7976
+ var CONTROLLER_MAX_OPERATION_REQUEST_BYTES = 32 * 1024;
7977
+ var CONTROLLER_PROFILE_TOKEN_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7978
+ var CONTROLLER_PROFILE_RE = new RegExp(
7979
+ `^(?:${CONTROLLER_PROFILE_TOKEN_RE.source.slice(1, -1)})(?:,(?:${CONTROLLER_PROFILE_TOKEN_RE.source.slice(1, -1)}))*$`
7980
+ );
7981
+
7982
+ // src/core/controller-store.ts
7983
+ var import_node_crypto13 = require("crypto");
7984
+ var import_node_fs23 = __toESM(require("fs"));
7985
+ var import_node_path29 = __toESM(require("path"));
7986
+ var CONTROLLER_SNAPSHOT_BYTES = 1048576;
7987
+ function fail() {
7988
+ throw new Error("Invalid controller snapshot.");
7989
+ }
7990
+ function fields(value, expected) {
7991
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail();
7992
+ const actual = Object.keys(value);
7993
+ if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) fail();
7994
+ }
7995
+ function text2(value, max = 256) {
7996
+ return typeof value === "string" && value.length > 0 && value.length <= max && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
7997
+ }
7998
+ function id3(value) {
7999
+ return text2(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
8000
+ }
8001
+ function counter(value) {
8002
+ return Number.isSafeInteger(value) && Number(value) >= 0;
8003
+ }
8004
+ function validateControllerSnapshot(value) {
8005
+ fields(value, [
8006
+ "version",
8007
+ "store",
8008
+ "epoch",
8009
+ "revision",
8010
+ "nextSequence",
8011
+ "environments",
8012
+ "sessions",
8013
+ "events"
8014
+ ]);
8015
+ if (value.version !== 1 || !id3(value.store) || !counter(value.epoch) || !counter(value.revision) || !counter(value.nextSequence) || value.nextSequence < 1 || !Array.isArray(value.environments) || value.environments.length > 32 || !Array.isArray(value.sessions) || value.sessions.length > 128 || !Array.isArray(value.events) || value.events.length > 256)
8016
+ fail();
8017
+ const environments = /* @__PURE__ */ new Set();
8018
+ const checkouts = /* @__PURE__ */ new Set();
8019
+ for (const env of value.environments) {
8020
+ fields(env, [
8021
+ "id",
8022
+ "repoPath",
8023
+ "workspace",
8024
+ "provider",
8025
+ "providerId",
8026
+ "profile",
8027
+ "fingerprint"
8028
+ ]);
8029
+ if (!id3(env.id) || environments.has(env.id) || !text2(env.repoPath, 4096) || !import_node_path29.default.isAbsolute(env.repoPath) || import_node_path29.default.resolve(env.repoPath) !== env.repoPath || checkouts.has(env.repoPath) || !id3(env.workspace) || !id3(env.providerId) || !text2(env.profile) || !/^[a-z0-9-]+(?:,[a-z0-9-]+)*$/.test(env.profile) || !text2(env.fingerprint, 128) || !/^[a-f0-9]{64}$/.test(env.fingerprint) || !["devpod", "devsy"].includes(String(env.provider)))
8030
+ fail();
8031
+ environments.add(env.id);
8032
+ checkouts.add(env.repoPath);
8033
+ }
8034
+ const sessions = /* @__PURE__ */ new Set();
8035
+ const usedEnvironments = /* @__PURE__ */ new Set();
8036
+ for (const session of value.sessions) {
8037
+ fields(session, [
8038
+ "id",
8039
+ "environmentId",
8040
+ "generation",
8041
+ "requirements",
8042
+ "renewedAtMs",
8043
+ ...Object.hasOwn(session, "observation") ? ["observation"] : []
8044
+ ]);
8045
+ if (!id3(session.id) || sessions.has(session.id) || !id3(session.environmentId) || !environments.has(session.environmentId) || !id3(session.generation) || !counter(session.renewedAtMs) || !Array.isArray(session.requirements) || session.requirements.length < 1 || session.requirements.length > 16 || new Set(session.requirements).size !== session.requirements.length || session.requirements.some(
8046
+ (r) => !text2(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
8047
+ ))
8048
+ fail();
8049
+ if (session.observation !== void 0) {
8050
+ const observation = session.observation;
8051
+ fields(observation, [
8052
+ "status",
8053
+ "sampledAtMs",
8054
+ "validUntilMs",
8055
+ "journalRevision",
8056
+ "runtimeFingerprint"
8057
+ ]);
8058
+ if (![
8059
+ "READY",
8060
+ "APP_ERROR",
8061
+ "BLOCKED",
8062
+ "STOPPED",
8063
+ "STARTING",
8064
+ "RECOVERING",
8065
+ "WAITING_CAPACITY",
8066
+ "PARKED_CAPACITY",
8067
+ "UNKNOWN"
8068
+ ].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !text2(observation.runtimeFingerprint, 128) || !/^[a-f0-9]{64}$/.test(observation.runtimeFingerprint))
8069
+ fail();
8070
+ }
8071
+ sessions.add(session.id);
8072
+ usedEnvironments.add(session.environmentId);
8073
+ }
8074
+ if (usedEnvironments.size !== environments.size) fail();
8075
+ let previous = 0;
8076
+ for (const event of value.events) {
8077
+ fields(event, ["sequence", "session", "generation", "kind"]);
8078
+ if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !id3(event.session) || !id3(event.generation) || !["acquired", "renewed", "released", "expired", "invalidated", "observed"].includes(
8079
+ String(event.kind)
8080
+ ))
8081
+ fail();
8082
+ previous = event.sequence;
8083
+ }
8084
+ if (Buffer.byteLength(JSON.stringify(value.events)) > 262144 || Buffer.byteLength(JSON.stringify(value)) + 1 > CONTROLLER_SNAPSHOT_BYTES)
8085
+ fail();
8086
+ }
8087
+ var ControllerStore = class {
8088
+ constructor(directory, write = writeFileAtomically) {
8089
+ this.directory = directory;
8090
+ this.write = write;
8091
+ this.file = import_node_path29.default.join(directory, "snapshot.json");
8092
+ }
8093
+ file;
8094
+ read() {
8095
+ let fd;
8096
+ try {
8097
+ fd = import_node_fs23.default.openSync(
8098
+ this.file,
8099
+ import_node_fs23.default.constants.O_RDONLY | import_node_fs23.default.constants.O_NOFOLLOW | import_node_fs23.default.constants.O_NONBLOCK
8100
+ );
8101
+ } catch (error) {
8102
+ if (error.code === "ENOENT") return void 0;
8103
+ throw new Error("Controller snapshot cannot be opened.");
8104
+ }
8105
+ try {
8106
+ const stat = import_node_fs23.default.fstatSync(fd);
8107
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > CONTROLLER_SNAPSHOT_BYTES)
8108
+ fail();
8109
+ const bytes2 = Buffer.alloc(CONTROLLER_SNAPSHOT_BYTES + 1);
8110
+ let length = 0;
8111
+ while (length < bytes2.length) {
8112
+ const count = import_node_fs23.default.readSync(fd, bytes2, length, bytes2.length - length, null);
8113
+ if (!count) break;
8114
+ length += count;
8115
+ }
8116
+ if (length > CONTROLLER_SNAPSHOT_BYTES) fail();
8117
+ const value = JSON.parse(bytes2.subarray(0, length).toString("utf8"));
8118
+ validateControllerSnapshot(value);
8119
+ return value;
8120
+ } catch {
8121
+ throw new Error("Controller snapshot is unreadable or invalid.");
8122
+ } finally {
8123
+ import_node_fs23.default.closeSync(fd);
8124
+ }
8125
+ }
8126
+ persist(snapshot) {
8127
+ validateControllerSnapshot(snapshot);
8128
+ this.read();
8129
+ const contents = `${JSON.stringify(snapshot)}
8130
+ `;
8131
+ try {
8132
+ this.write(this.file, contents);
8133
+ } catch {
8134
+ const current = this.read();
8135
+ if (!current || `${JSON.stringify(current)}
8136
+ ` !== contents)
8137
+ throw new Error("Controller snapshot commit failed.");
8138
+ for (const file of [this.file, this.directory]) {
8139
+ const fd = import_node_fs23.default.openSync(file, import_node_fs23.default.constants.O_RDONLY | import_node_fs23.default.constants.O_NOFOLLOW);
8140
+ try {
8141
+ import_node_fs23.default.fsyncSync(fd);
8142
+ } finally {
8143
+ import_node_fs23.default.closeSync(fd);
8144
+ }
8145
+ }
8146
+ }
8147
+ }
8148
+ startIncarnation() {
8149
+ const previous = this.read();
8150
+ if (previous && (previous.epoch === Number.MAX_SAFE_INTEGER || previous.revision === Number.MAX_SAFE_INTEGER))
8151
+ throw new Error("Controller snapshot counters exhausted.");
8152
+ const snapshot = {
8153
+ version: 1,
8154
+ store: previous?.store ?? (0, import_node_crypto13.randomUUID)(),
8155
+ epoch: (previous?.epoch ?? 0) + 1,
8156
+ revision: (previous?.revision ?? 0) + 1,
8157
+ nextSequence: 1,
8158
+ environments: [],
8159
+ sessions: [],
8160
+ events: []
8161
+ };
8162
+ this.persist(snapshot);
8163
+ return snapshot;
8164
+ }
8165
+ };
7318
8166
 
7319
8167
  // src/core/reliability-contract.ts
7320
8168
  var RELIABILITY_CONTRACT_VERSION = 2;
@@ -7643,7 +8491,6 @@ function canExecAfterInterruptedEnsure(state) {
7643
8491
  return state.executionPolicy === "manual" && (state.desired === "running" || quiescent && state.desired === "stopped-by-user") && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && (state.phase === "recovering" || quiescent) || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
7644
8492
  }
7645
8493
  function handleOperationRequest(state, event) {
7646
- if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
7647
8494
  const previous = state.operationHistory.find((entry) => entry.key === event.key);
7648
8495
  if (previous)
7649
8496
  return unchanged(
@@ -7652,6 +8499,8 @@ function handleOperationRequest(state, event) {
7652
8499
  );
7653
8500
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
7654
8501
  return unchanged(state, "conflict");
8502
+ if (state.executionPolicy !== "manual" && state.operationHistory.length >= RELIABILITY_MAX_ITEMS)
8503
+ return unchanged(state, "blocked");
7655
8504
  if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
7656
8505
  return blocked(
7657
8506
  state,
@@ -7674,7 +8523,7 @@ function handleOperationRequest(state, event) {
7674
8523
  state,
7675
8524
  `exec requires a running workspace (runtimeRunning=${event.runtimeRunning}, desired='${state.desired}').`
7676
8525
  );
7677
- const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
8526
+ const rollover = state.executionPolicy === "manual" && state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
7678
8527
  let retired = -1;
7679
8528
  if (rollover) {
7680
8529
  const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
@@ -7696,6 +8545,7 @@ function handleOperationRequest(state, event) {
7696
8545
  state.desired = "running";
7697
8546
  state.phase = "queued";
7698
8547
  state.profile = event.profile;
8548
+ if (state.executionPolicy === "capacity-managed") state.admission = "waiting";
7699
8549
  state.stopProof = { workloadsStopped: false, routesRemoved: false };
7700
8550
  state.consumers = [cloneConsumer(event.consumer)];
7701
8551
  state.requests = [
@@ -7721,7 +8571,11 @@ function handleOperationRequest(state, event) {
7721
8571
  profile: event.profile,
7722
8572
  consumer: cloneConsumer(event.consumer)
7723
8573
  });
7724
- return transition(state, "accepted");
8574
+ return transition(
8575
+ state,
8576
+ "accepted",
8577
+ state.executionPolicy === "capacity-managed" ? [effect(state, "request-admission", event.operationId)] : []
8578
+ );
7725
8579
  }
7726
8580
  function handleAdmission(state, event) {
7727
8581
  if (state.executionPolicy === "manual" || state.desired !== "running" && state.desired !== "parked-for-capacity" || state.requests.length === 0) {
@@ -7846,7 +8700,7 @@ function handleObservation(state, event, nowMs) {
7846
8700
  return transition(state, "accepted");
7847
8701
  }
7848
8702
  function handleStop(state) {
7849
- if (state.desired === "stopped-by-user" && state.intentRevision > 0)
8703
+ if (state.desired === "stopped-by-user" && state.phase === "stopping")
7850
8704
  return unchanged(state, "joined");
7851
8705
  const intentRevision = advance(state.intentRevision);
7852
8706
  if (intentRevision === null) return unchanged(state, "blocked");
@@ -8068,7 +8922,7 @@ function stepReliability(state, event, nowMs) {
8068
8922
  if (!isReliabilityCounter(nowMs) || nowMs < state.observationsAfterMs)
8069
8923
  throw new Error("Invalid reliability clock.");
8070
8924
  const result = applyReliabilityEvent(state, event, nowMs);
8071
- if (result.outcome === "accepted" && result.state.executionPolicy === "manual" && result.state.operation) {
8925
+ if (result.outcome === "accepted" && result.state.operation) {
8072
8926
  const operation = result.state.operation;
8073
8927
  result.state.operationHistory = result.state.operationHistory.map(
8074
8928
  (entry) => entry.id === operation.id ? { ...entry, ...operation } : entry
@@ -8081,11 +8935,12 @@ function stepReliability(state, event, nowMs) {
8081
8935
  }
8082
8936
 
8083
8937
  // src/core/reliability-operation-store.ts
8084
- var import_node_crypto11 = require("crypto");
8085
- var import_node_fs20 = __toESM(require("fs"));
8086
- var import_node_path25 = __toESM(require("path"));
8938
+ var import_node_crypto14 = require("crypto");
8939
+ var import_node_fs24 = __toESM(require("fs"));
8940
+ var import_node_path30 = __toESM(require("path"));
8941
+ var import_node_util5 = require("util");
8087
8942
  function installedCliVersion() {
8088
- return true ? "0.0.67" : "0.0.0-dev";
8943
+ return true ? "0.0.68" : "0.0.0-dev";
8089
8944
  }
8090
8945
  function compareCliVersions(left, right) {
8091
8946
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -8098,21 +8953,112 @@ function compareCliVersions(left, right) {
8098
8953
  return 0;
8099
8954
  }
8100
8955
  var MAX_RECORD_BYTES = 1048576;
8956
+ var MAX_RELIABILITY_JOURNALS = 256;
8957
+ var MAX_RELIABILITY_DIRECTORY_ENTRIES = MAX_RELIABILITY_JOURNALS * 2;
8101
8958
  function identityKey(identity2) {
8102
- if (!import_node_path25.default.isAbsolute(identity2.repoPath) || import_node_path25.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
8959
+ if (!import_node_path30.default.isAbsolute(identity2.repoPath) || import_node_path30.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
8103
8960
  throw new Error("Invalid reliability workspace identity.");
8104
8961
  }
8105
- return (0, import_node_crypto11.createHash)("sha256").update(identity2.repoPath).digest("hex");
8962
+ return (0, import_node_crypto14.createHash)("sha256").update(identity2.repoPath).digest("hex");
8106
8963
  }
8107
8964
  function reliabilityOperationPath(identity2) {
8108
- return import_node_path25.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
8965
+ return import_node_path30.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
8109
8966
  }
8110
8967
  function keys(value, expected) {
8111
8968
  if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
8112
8969
  throw new Error("Reliability record contains unsupported fields.");
8113
8970
  }
8114
8971
  }
8115
- function validate(record5, identity2) {
8972
+ function exactKeys(value, expected, message) {
8973
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== expected.length || Object.keys(value).some((key) => !expected.includes(key))) {
8974
+ throw new Error(message);
8975
+ }
8976
+ }
8977
+ function validateCapacityPhaseSettlement(record5, state, enrollment) {
8978
+ const settlement = record5.phaseSettlement;
8979
+ if (settlement === void 0 || settlement === null) return;
8980
+ if (record5.version !== 2 || !enrollment || state.executionPolicy !== "capacity-managed")
8981
+ throw new Error("Phase settlement requires durable capacity enrollment.");
8982
+ exactKeys(
8983
+ settlement,
8984
+ ["id", "fence", "target", "estimatesDigest"],
8985
+ "Invalid capacity phase settlement fields."
8986
+ );
8987
+ exactKeys(
8988
+ settlement.fence,
8989
+ ["environmentId", "intentRevision", "runtimeGeneration", "controllerEpoch"],
8990
+ "Invalid capacity phase settlement fence."
8991
+ );
8992
+ exactKeys(
8993
+ settlement.target,
8994
+ [
8995
+ "environmentId",
8996
+ "operationId",
8997
+ "reservationId",
8998
+ "policyRevision",
8999
+ "totals",
9000
+ "startup",
9001
+ "heavy"
9002
+ ],
9003
+ "Invalid capacity phase settlement target fields."
9004
+ );
9005
+ const target = settlement.target;
9006
+ const totals = target.totals;
9007
+ if (!isReliabilityId(settlement.id) || !isReliabilityId(settlement.fence.environmentId) || settlement.fence.environmentId !== state.environmentId || !isReliabilityCounter(settlement.fence.intentRevision) || !isReliabilityCounter(settlement.fence.runtimeGeneration) || !isReliabilityCounter(settlement.fence.controllerEpoch) || !isReliabilityId(target.environmentId) || target.environmentId !== state.environmentId || !isReliabilityId(target.operationId) || !isReliabilityId(target.reservationId) || !Number.isSafeInteger(target.policyRevision) || target.policyRevision < 1 || target.startup !== false || target.heavy !== false || typeof settlement.estimatesDigest !== "string" || !/^[0-9a-f]{64}$/.test(settlement.estimatesDigest))
9008
+ throw new Error("Invalid capacity phase settlement.");
9009
+ if (!totals || typeof totals !== "object" || Array.isArray(totals) || Object.keys(totals).length < 1 || Object.keys(totals).length > 256 || Object.entries(totals).some(
9010
+ ([domain, bytes2]) => !isReliabilityId(domain) || !Number.isSafeInteger(bytes2) || bytes2 < 0
9011
+ ))
9012
+ throw new Error("Invalid capacity phase settlement totals.");
9013
+ const capacity = record5.capacity;
9014
+ if (capacity?.validUntilMs !== 0 || target.operationId !== capacity?.operationId || target.reservationId !== capacity?.reservationId || target.policyRevision !== capacity?.policyRevision || settlement.estimatesDigest !== enrollment.estimatesDigest)
9015
+ throw new Error("Capacity phase settlement does not match durable capacity authority.");
9016
+ const targetDomains = Object.keys(totals);
9017
+ if (targetDomains.length !== 2 || !targetDomains.includes(enrollment.hostDomain) || !targetDomains.includes(enrollment.runtimeDomain))
9018
+ throw new Error("Capacity phase settlement domains do not match enrollment.");
9019
+ }
9020
+ function validateStartupWitness(record5, state, enrollment) {
9021
+ const witness = record5.startupWitness;
9022
+ if (witness === void 0 || witness === null) return;
9023
+ if (record5.version !== 2 || !enrollment || state.executionPolicy !== "capacity-managed")
9024
+ throw new Error("Startup witness requires current durable managed intent.");
9025
+ exactKeys(
9026
+ witness,
9027
+ [
9028
+ "operationId",
9029
+ "fence",
9030
+ "provider",
9031
+ "profile",
9032
+ "sourceConfigSha256",
9033
+ "effectiveConfigSha256",
9034
+ "composeFiles",
9035
+ "primaryService",
9036
+ "startupServices",
9037
+ "retainedContainerIds"
9038
+ ],
9039
+ "Invalid capacity startup witness fields."
9040
+ );
9041
+ exactKeys(
9042
+ witness.fence,
9043
+ ["environmentId", "intentRevision", "runtimeGeneration", "controllerEpoch"],
9044
+ "Invalid capacity startup witness fence."
9045
+ );
9046
+ exactKeys(
9047
+ witness.provider,
9048
+ ["id", "context", "uid", "sourceContainer"],
9049
+ "Invalid capacity startup witness provider fields."
9050
+ );
9051
+ const digest = (value) => typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
9052
+ const boundedText = (value) => typeof value === "string" && value.length <= 256 && ![...value].some(
9053
+ (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127
9054
+ );
9055
+ const absolutePath = (value) => typeof value === "string" && value.length <= 4096 && !value.includes("\0") && import_node_path30.default.isAbsolute(value) && import_node_path30.default.resolve(value) === value;
9056
+ const unique = (values) => new Set(values).size === values.length;
9057
+ const fence = witness.fence;
9058
+ if (!isReliabilityId(witness.operationId) || !isReliabilityId(fence.environmentId) || !isReliabilityCounter(fence.intentRevision) || !isReliabilityCounter(fence.runtimeGeneration) || !isReliabilityCounter(fence.controllerEpoch) || !isReliabilityProfile(witness.profile) || !digest(witness.sourceConfigSha256) || !digest(witness.effectiveConfigSha256) || !boundedText(witness.provider.id) || witness.provider.id.trim() !== witness.provider.id || witness.provider.id === "" || witness.provider.id !== enrollment.providerId || !boundedText(witness.provider.context) || !boundedText(witness.provider.uid) || !boundedText(witness.provider.sourceContainer) || !Array.isArray(witness.composeFiles) || witness.composeFiles.length < 1 || witness.composeFiles.length > 16 || !witness.composeFiles.every(absolutePath) || !unique(witness.composeFiles) || !Array.isArray(witness.startupServices) || witness.startupServices.length < 1 || witness.startupServices.length > 64 || !witness.startupServices.every((service) => isReliabilityId(service) && service.length <= 64) || !unique(witness.startupServices) || !isReliabilityId(witness.primaryService) || witness.primaryService.length > 64 || !witness.startupServices.includes(witness.primaryService) || !Array.isArray(witness.retainedContainerIds) || witness.retainedContainerIds.length > 256 || !witness.retainedContainerIds.every(digest) || !unique(witness.retainedContainerIds))
9059
+ throw new Error("Invalid capacity startup witness.");
9060
+ }
9061
+ function validate2(record5, identity2) {
8116
9062
  keys(record5, [
8117
9063
  "version",
8118
9064
  "identity",
@@ -8121,7 +9067,16 @@ function validate(record5, identity2) {
8121
9067
  "worker",
8122
9068
  "effectSequence",
8123
9069
  "outcome",
8124
- "writtenByVersion"
9070
+ "result",
9071
+ "writtenByVersion",
9072
+ ...record5.version === 2 ? [
9073
+ "capacity",
9074
+ "enrollment",
9075
+ "activeProfile",
9076
+ "preparation",
9077
+ "phaseSettlement",
9078
+ "startupWitness"
9079
+ ] : []
8125
9080
  ]);
8126
9081
  if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
8127
9082
  throw new Error(
@@ -8129,13 +9084,57 @@ function validate(record5, identity2) {
8129
9084
  );
8130
9085
  }
8131
9086
  keys(record5.identity, ["repoPath", "workspace", "provider"]);
8132
- if (record5.version !== 1 || record5.identity.repoPath !== identity2.repoPath || record5.identity.workspace !== identity2.workspace || record5.identity.provider !== identity2.provider || !isReliabilityCounter(record5.revision) || !isReliabilityCounter(record5.effectSequence)) {
9087
+ if (record5.version !== 1 && record5.version !== 2 || record5.identity.repoPath !== identity2.repoPath || record5.identity.workspace !== identity2.workspace || record5.identity.provider !== identity2.provider || !isReliabilityCounter(record5.revision) || !isReliabilityCounter(record5.effectSequence)) {
8133
9088
  throw new Error("Reliability record has invalid version, ownership, or counters.");
8134
9089
  }
9090
+ if (record5.version === 2) {
9091
+ const capacity = record5.capacity;
9092
+ if (capacity === void 0) throw new Error("Invalid capacity authority binding.");
9093
+ if (capacity !== null) {
9094
+ keys(capacity, [
9095
+ "reservationId",
9096
+ "operationId",
9097
+ "workerId",
9098
+ "policyRevision",
9099
+ "validUntilMs",
9100
+ "snapshotRevision",
9101
+ "controller",
9102
+ "execSteady"
9103
+ ]);
9104
+ if (capacity.controller !== void 0) {
9105
+ keys(capacity.controller, ["store", "epoch"]);
9106
+ if (!isReliabilityId(capacity.controller.store) || !isReliabilityCounter(capacity.controller.epoch) || capacity.controller.epoch < 1)
9107
+ throw new Error("Invalid capacity controller identity.");
9108
+ }
9109
+ }
9110
+ if (capacity !== null && (!isReliabilityId(capacity.reservationId) || !isReliabilityId(capacity.operationId) || !isReliabilityId(capacity.workerId) || !Number.isSafeInteger(capacity.policyRevision) || capacity.policyRevision < 1 || !isReliabilityCounter(capacity.validUntilMs) || capacity.snapshotRevision !== void 0 && (!isReliabilityCounter(capacity.snapshotRevision) || capacity.snapshotRevision < 1)))
9111
+ throw new Error("Invalid capacity authority binding.");
9112
+ }
8135
9113
  const state = record5.state;
8136
9114
  assertReliabilityState(state);
8137
- if (state.environmentId !== identityKey(identity2) || state.executionPolicy !== "manual") {
8138
- throw new Error("Reliability record does not grant manual authority for this workspace.");
9115
+ if (state.environmentId !== identityKey(identity2)) {
9116
+ throw new Error("Reliability record does not match this workspace.");
9117
+ }
9118
+ if (record5.enrollment !== void 0) {
9119
+ const enrollment = record5.enrollment;
9120
+ keys(enrollment, [
9121
+ "policyRevision",
9122
+ "gitCommonDir",
9123
+ "providerId",
9124
+ "hostDomain",
9125
+ "runtimeDomain",
9126
+ "endpoint",
9127
+ "daemonId",
9128
+ "estimatesDigest"
9129
+ ]);
9130
+ const absolute2 = (value) => typeof value === "string" && value.length <= 4096 && !value.includes("\0") && import_node_path30.default.isAbsolute(value) && import_node_path30.default.resolve(value) === value;
9131
+ const providerIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && ![...value].some(
9132
+ (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127
9133
+ );
9134
+ if (record5.version !== 2 || state.executionPolicy !== "capacity-managed" || !Number.isSafeInteger(enrollment.policyRevision) || enrollment.policyRevision < 1 || !absolute2(enrollment.gitCommonDir) || !absolute2(enrollment.endpoint) || !providerIdentity(enrollment.providerId) || !providerIdentity(enrollment.daemonId) || !isReliabilityId(enrollment.hostDomain) || !isReliabilityId(enrollment.runtimeDomain) || enrollment.hostDomain === enrollment.runtimeDomain || typeof enrollment.estimatesDigest !== "string" || !/^[0-9a-f]{64}$/.test(enrollment.estimatesDigest) || record5.activeProfile !== null && (typeof record5.activeProfile !== "string" || record5.activeProfile.length > 256 || !/^[a-z0-9,-]+$/.test(record5.activeProfile)))
9135
+ throw new Error("Invalid durable capacity enrollment.");
9136
+ } else if (state.executionPolicy !== "manual" || record5.activeProfile !== void 0) {
9137
+ throw new Error("Managed lifecycle requires durable capacity enrollment.");
8139
9138
  }
8140
9139
  keys(state, [
8141
9140
  "contractVersion",
@@ -8170,6 +9169,52 @@ function validate(record5, identity2) {
8170
9169
  keys(entry, ["key", "mode", "consumerId", "operationId", "profile", "intentRevision"]);
8171
9170
  for (const entry of state.observations)
8172
9171
  keys(entry, ["capability", "infrastructure", "application", "observedAtMs", "validForMs"]);
9172
+ if (record5.preparation !== void 0 && record5.preparation !== null) {
9173
+ if (record5.version !== 2 || record5.enrollment === void 0 || state.executionPolicy !== "capacity-managed")
9174
+ throw new Error("Preparation receipt requires durable capacity enrollment.");
9175
+ const preparation = record5.preparation;
9176
+ keys(preparation, ["operationId", "profile", "fence"]);
9177
+ keys(preparation.fence, [
9178
+ "environmentId",
9179
+ "intentRevision",
9180
+ "runtimeGeneration",
9181
+ "controllerEpoch"
9182
+ ]);
9183
+ if (!isReliabilityId(preparation.operationId) || !isReliabilityProfile(preparation.profile) || !isReliabilityId(preparation.fence.environmentId) || preparation.fence.environmentId !== state.environmentId || !isReliabilityCounter(preparation.fence.intentRevision) || !isReliabilityCounter(preparation.fence.runtimeGeneration) || !isReliabilityCounter(preparation.fence.controllerEpoch))
9184
+ throw new Error("Invalid preparation receipt.");
9185
+ const preparedOperation = state.operationHistory.find(
9186
+ (entry) => entry.id === preparation.operationId
9187
+ );
9188
+ if (preparedOperation?.kind !== "ensure" || preparedOperation.profile !== preparation.profile || preparedOperation.status !== "COMPLETED")
9189
+ throw new Error("Preparation receipt does not match a completed ensure operation.");
9190
+ }
9191
+ validateCapacityPhaseSettlement(record5, state, record5.enrollment);
9192
+ validateStartupWitness(record5, state, record5.enrollment);
9193
+ const execSteady = record5.capacity?.execSteady;
9194
+ if (execSteady !== void 0) {
9195
+ const enrollment = record5.enrollment;
9196
+ if (record5.version !== 2 || !enrollment || state.executionPolicy !== "capacity-managed")
9197
+ throw new Error("Exec steady receipt requires durable capacity enrollment.");
9198
+ exactKeys(
9199
+ execSteady,
9200
+ ["profile", "estimatesDigest", "fence", "totals"],
9201
+ "Invalid exec steady receipt fields."
9202
+ );
9203
+ exactKeys(
9204
+ execSteady.fence,
9205
+ ["environmentId", "intentRevision", "runtimeGeneration", "controllerEpoch"],
9206
+ "Invalid exec steady receipt fence."
9207
+ );
9208
+ if (!isReliabilityProfile(execSteady.profile) || typeof execSteady.estimatesDigest !== "string" || !/^[0-9a-f]{64}$/.test(execSteady.estimatesDigest) || execSteady.estimatesDigest !== enrollment.estimatesDigest || execSteady.fence.environmentId !== state.environmentId || !isReliabilityCounter(execSteady.fence.intentRevision) || !isReliabilityCounter(execSteady.fence.runtimeGeneration) || !isReliabilityCounter(execSteady.fence.controllerEpoch))
9209
+ throw new Error("Invalid exec steady receipt.");
9210
+ exactKeys(
9211
+ execSteady.totals,
9212
+ [enrollment.hostDomain, enrollment.runtimeDomain],
9213
+ "Exec steady receipt domains do not match enrollment."
9214
+ );
9215
+ if (!Object.values(execSteady.totals).every(isReliabilityCounter))
9216
+ throw new Error("Invalid exec steady receipt totals.");
9217
+ }
8173
9218
  if (state.operation) {
8174
9219
  const current = state.operationHistory.find((entry) => entry.id === state.operation?.id);
8175
9220
  if (!current || operationKeys.some(
@@ -8186,6 +9231,12 @@ function validate(record5, identity2) {
8186
9231
  if (outcome.operationId !== state.operation?.id || !["completed", "not-started", "completion-unknown"].includes(outcome.status) || (outcome.status === "completed" ? !validExit(outcome.exitCode) : outcome.exitCode !== null) || outcome.transport.exitCode !== null && !validExit(outcome.transport.exitCode) || outcome.transport.signal !== null && (typeof outcome.transport.signal !== "string" || !/^SIG[A-Z0-9]{1,16}$/.test(outcome.transport.signal))) {
8187
9232
  throw new Error("Reliability execution outcome is invalid.");
8188
9233
  }
9234
+ if (record5.result !== void 0 && record5.result !== null) {
9235
+ const serialized = JSON.stringify(record5.result);
9236
+ if (typeof serialized !== "string" || serialized.length > 262144 || record5.result.ok !== true && record5.result.ok !== false || (record5.result.ok ? "message" in record5.result : !("message" in record5.result)) || !record5.result.ok && (typeof record5.result.message !== "string" || record5.result.message.length > 4096)) {
9237
+ throw new Error("Reliability operation result is invalid.");
9238
+ }
9239
+ }
8189
9240
  }
8190
9241
  if (record5.worker !== null) {
8191
9242
  keys(record5.worker, ["id", "operationId", "pid", "birth"]);
@@ -8194,37 +9245,59 @@ function validate(record5, identity2) {
8194
9245
  }
8195
9246
  }
8196
9247
  }
9248
+ function assertCapacityEffect(record5, workerId, nowMs, directory = import_node_path30.default.join(DEVROUTER_HOME, "controller")) {
9249
+ if (record5.version === 1) return;
9250
+ const binding = record5.capacity;
9251
+ if (!binding || binding.workerId !== workerId || binding.operationId !== record5.state.operation?.id || binding.validUntilMs <= nowMs)
9252
+ throw new Error("Capacity effect authority is absent or stale.");
9253
+ if (record5.state.executionPolicy === "capacity-managed") {
9254
+ const controller = new ControllerStore(directory).read();
9255
+ if (!binding.controller || !controller || controller.store !== binding.controller.store || controller.epoch !== binding.controller.epoch)
9256
+ throw new Error("Capacity controller incarnation changed.");
9257
+ }
9258
+ const snapshot = new CapacityStore(directory).read();
9259
+ if (binding.snapshotRevision === void 0 || snapshot.revision !== binding.snapshotRevision)
9260
+ throw new Error("Capacity snapshot authority is absent or stale.");
9261
+ const reservation = snapshot.reservations.find(
9262
+ (entry) => entry.reservationId === binding.reservationId
9263
+ );
9264
+ if (!reservation || reservation.environmentId !== record5.state.environmentId || reservation.operationId !== binding.operationId || reservation.policyRevision !== binding.policyRevision)
9265
+ throw new Error("Capacity effect reservation does not match lifecycle intent.");
9266
+ }
8197
9267
  function readReliabilityOperation(identity2) {
8198
9268
  const file = reliabilityOperationPath(identity2);
9269
+ const parsed = readBoundedPrivateJson(file, "Reliability record");
9270
+ if (parsed === void 0) return void 0;
9271
+ const record5 = parsed;
9272
+ validate2(record5, identity2);
9273
+ return record5;
9274
+ }
9275
+ function readBoundedPrivateJson(file, label) {
8199
9276
  let descriptor;
8200
9277
  try {
8201
- descriptor = import_node_fs20.default.openSync(
9278
+ descriptor = import_node_fs24.default.openSync(
8202
9279
  file,
8203
- import_node_fs20.default.constants.O_RDONLY | import_node_fs20.default.constants.O_NOFOLLOW | import_node_fs20.default.constants.O_NONBLOCK
9280
+ import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW | import_node_fs24.default.constants.O_NONBLOCK
8204
9281
  );
8205
9282
  } catch (error) {
8206
9283
  if (error.code === "ENOENT") return void 0;
8207
9284
  throw error;
8208
9285
  }
8209
9286
  try {
8210
- const stat = import_node_fs20.default.fstatSync(descriptor);
8211
- if (!stat.isFile() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
8212
- throw new Error("Reliability record is not a bounded private file.");
8213
- }
8214
- const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
8215
- const count = import_node_fs20.default.readSync(descriptor, bytes, 0, bytes.length, 0);
8216
- if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
8217
- const record5 = JSON.parse(
8218
- bytes.subarray(0, count).toString("utf8")
8219
- );
8220
- validate(record5, identity2);
8221
- return record5;
9287
+ const stat = import_node_fs24.default.fstatSync(descriptor);
9288
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
9289
+ throw new Error(`${label} is not a bounded private file.`);
9290
+ }
9291
+ const bytes2 = Buffer.alloc(MAX_RECORD_BYTES + 1);
9292
+ const count = import_node_fs24.default.readSync(descriptor, bytes2, 0, bytes2.length, 0);
9293
+ if (count > MAX_RECORD_BYTES) throw new Error(`${label} exceeds its byte limit.`);
9294
+ return JSON.parse(bytes2.subarray(0, count).toString("utf8"));
8222
9295
  } finally {
8223
- import_node_fs20.default.closeSync(descriptor);
9296
+ import_node_fs24.default.closeSync(descriptor);
8224
9297
  }
8225
9298
  }
8226
9299
  function persist(record5) {
8227
- validate(record5, record5.identity);
9300
+ validate2(record5, record5.identity);
8228
9301
  const contents = `${JSON.stringify(record5)}
8229
9302
  `;
8230
9303
  if (Buffer.byteLength(contents) > MAX_RECORD_BYTES)
@@ -8235,23 +9308,23 @@ function persist(record5) {
8235
9308
  } catch (error) {
8236
9309
  const reread = readReliabilityOperation(record5.identity);
8237
9310
  if (!reread || JSON.stringify(reread) !== JSON.stringify(record5)) throw error;
8238
- const descriptor = import_node_fs20.default.openSync(file, import_node_fs20.default.constants.O_RDONLY | import_node_fs20.default.constants.O_NOFOLLOW);
9311
+ const descriptor = import_node_fs24.default.openSync(file, import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW);
8239
9312
  try {
8240
- import_node_fs20.default.fsyncSync(descriptor);
9313
+ import_node_fs24.default.fsyncSync(descriptor);
8241
9314
  } finally {
8242
- import_node_fs20.default.closeSync(descriptor);
9315
+ import_node_fs24.default.closeSync(descriptor);
8243
9316
  }
8244
- const directory = import_node_fs20.default.openSync(import_node_path25.default.dirname(file), "r");
9317
+ const directory = import_node_fs24.default.openSync(import_node_path30.default.dirname(file), "r");
8245
9318
  try {
8246
- import_node_fs20.default.fsyncSync(directory);
9319
+ import_node_fs24.default.fsyncSync(directory);
8247
9320
  } finally {
8248
- import_node_fs20.default.closeSync(directory);
9321
+ import_node_fs24.default.closeSync(directory);
8249
9322
  }
8250
9323
  }
8251
9324
  }
8252
9325
  function updateReliabilityOperation(identity2, operation) {
8253
9326
  const file = reliabilityOperationPath(identity2);
8254
- import_node_fs20.default.mkdirSync(import_node_path25.default.dirname(file), { recursive: true, mode: 448 });
9327
+ import_node_fs24.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true, mode: 448 });
8255
9328
  return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
8256
9329
  const record5 = readReliabilityOperation(identity2) ?? {
8257
9330
  version: 1,
@@ -8264,23 +9337,37 @@ function updateReliabilityOperation(identity2, operation) {
8264
9337
  };
8265
9338
  if (record5.revision === Number.MAX_SAFE_INTEGER)
8266
9339
  throw new Error("Reliability record revision is exhausted.");
9340
+ const previousVersion = record5.version;
9341
+ const previousEnrollment = record5.enrollment && { ...record5.enrollment };
8267
9342
  const result = operation(record5);
8268
9343
  if (result && typeof result.then === "function") {
8269
9344
  throw new Error("Reliability record updates must be synchronous.");
8270
9345
  }
9346
+ if (record5.version < previousVersion || previousEnrollment && !(0, import_node_util5.isDeepStrictEqual)(record5.enrollment, previousEnrollment))
9347
+ throw new Error("Durable capacity enrollment cannot be downgraded or replaced.");
8271
9348
  record5.writtenByVersion = installedCliVersion();
8272
- validate(record5, identity2);
9349
+ validate2(record5, identity2);
8273
9350
  record5.revision += 1;
8274
9351
  persist(record5);
8275
9352
  return result;
8276
9353
  });
8277
9354
  }
9355
+ function clearStartupWitness(identity2, fence) {
9356
+ updateReliabilityOperation(identity2, (record5) => {
9357
+ if (record5.startupWitness === void 0 || record5.startupWitness === null) return;
9358
+ const live = reliabilityFence(record5.state);
9359
+ if (Object.entries(fence).some(([key, value]) => value !== live[key]))
9360
+ throw new Error("Startup witness clearing fence changed.");
9361
+ record5.startupWitness = null;
9362
+ });
9363
+ }
8278
9364
 
8279
9365
  // src/core/reliability-worker.ts
8280
9366
  var import_node_child_process16 = require("child_process");
8281
- var import_node_crypto12 = require("crypto");
8282
- var import_node_fs21 = __toESM(require("fs"));
8283
- var import_node_path26 = __toESM(require("path"));
9367
+ var import_node_crypto15 = require("crypto");
9368
+ var import_node_fs25 = __toESM(require("fs"));
9369
+ var import_node_path31 = __toESM(require("path"));
9370
+ var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
8284
9371
  function workerGroupAbsent(pid) {
8285
9372
  try {
8286
9373
  process.kill(-pid, 0);
@@ -8292,10 +9379,10 @@ function workerGroupAbsent(pid) {
8292
9379
 
8293
9380
  // src/core/traefik-route-health.ts
8294
9381
  var import_node_child_process17 = require("child_process");
8295
- var import_node_fs22 = __toESM(require("fs"));
8296
- var import_node_path27 = __toESM(require("path"));
9382
+ var import_node_fs26 = __toESM(require("fs"));
9383
+ var import_node_path32 = __toESM(require("path"));
8297
9384
  var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
8298
- var ROUTER_RELOAD_LOCK_FILE = import_node_path27.default.join(DEVROUTER_HOME, "router-reload.lock");
9385
+ var ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
8299
9386
  var ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
8300
9387
  var DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
8301
9388
  var DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -8564,7 +9651,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
8564
9651
  throw new Error(
8565
9652
  "Traefik routes did not reload; shared router restart is disabled for this operation."
8566
9653
  );
8567
- import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9654
+ import_node_fs26.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8568
9655
  return withFileLock(
8569
9656
  ROUTER_RELOAD_LOCK_FILE,
8570
9657
  {
@@ -8616,19 +9703,19 @@ function assertTraefikRoutesRemoved(routes) {
8616
9703
 
8617
9704
  // src/core/workspace-ensure.ts
8618
9705
  var import_node_child_process26 = require("child_process");
8619
- var import_node_fs28 = __toESM(require("fs"));
8620
- var import_node_path32 = __toESM(require("path"));
9706
+ var import_node_fs32 = __toESM(require("fs"));
9707
+ var import_node_path37 = __toESM(require("path"));
8621
9708
 
8622
9709
  // src/core/devpod-mutation.ts
8623
9710
  var import_node_child_process18 = require("child_process");
8624
- var import_node_fs23 = __toESM(require("fs"));
8625
- var import_node_path28 = __toESM(require("path"));
8626
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path28.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
9711
+ var import_node_fs27 = __toESM(require("fs"));
9712
+ var import_node_path33 = __toESM(require("path"));
9713
+ var DEVPOD_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
8627
9714
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
8628
9715
  var DevpodStartPostconditionError = class extends Error {
8629
9716
  };
8630
9717
  function withMutationLock2(activity, target, operation) {
8631
- import_node_fs23.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9718
+ import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8632
9719
  return withFileLockSync(
8633
9720
  DEVPOD_MUTATION_LOCK_FILE,
8634
9721
  {
@@ -8967,7 +10054,7 @@ function transportFailure() {
8967
10054
 
8968
10055
  // src/core/managed-host-preparation.ts
8969
10056
  var import_node_child_process20 = require("child_process");
8970
- var import_node_fs24 = __toESM(require("fs"));
10057
+ var import_node_fs28 = __toESM(require("fs"));
8971
10058
  var PREPARATION_TIMEOUT_MS = 6e4;
8972
10059
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
8973
10060
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -9010,7 +10097,7 @@ function runPreparationCommand(argv, cwd) {
9010
10097
  }
9011
10098
  function readConfigBytes(configPath) {
9012
10099
  try {
9013
- return import_node_fs24.default.readFileSync(configPath);
10100
+ return import_node_fs28.default.readFileSync(configPath);
9014
10101
  } catch {
9015
10102
  return void 0;
9016
10103
  }
@@ -9045,9 +10132,9 @@ async function runManagedHostPreparation(repoPath, config) {
9045
10132
 
9046
10133
  // src/core/managed-post-start.ts
9047
10134
  var import_node_child_process21 = require("child_process");
9048
- var import_node_crypto13 = require("crypto");
9049
- var import_node_fs25 = __toESM(require("fs"));
9050
- var import_node_path29 = __toESM(require("path"));
10135
+ var import_node_crypto16 = require("crypto");
10136
+ var import_node_fs29 = __toESM(require("fs"));
10137
+ var import_node_path34 = __toESM(require("path"));
9051
10138
  var MANAGED_MARKER = "devrouter:managed devcontainer";
9052
10139
  var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
9053
10140
  var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
@@ -9060,7 +10147,7 @@ function renderDeliveryScript(targetPath) {
9060
10147
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
9061
10148
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
9062
10149
  }
9063
- const temporaryPrefix = import_node_path29.default.posix.basename(targetPath);
10150
+ const temporaryPrefix = import_node_path34.default.posix.basename(targetPath);
9064
10151
  return `set -eu
9065
10152
  umask 077
9066
10153
  runtime_root=/tmp/devrouter
@@ -9095,32 +10182,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
9095
10182
  }
9096
10183
  }
9097
10184
  function readRegularFileBytes(filePath) {
9098
- if (!import_node_fs25.default.existsSync(filePath) || !import_node_fs25.default.lstatSync(filePath).isFile()) return void 0;
9099
- return import_node_fs25.default.readFileSync(filePath);
10185
+ if (!import_node_fs29.default.existsSync(filePath) || !import_node_fs29.default.lstatSync(filePath).isFile()) return void 0;
10186
+ return import_node_fs29.default.readFileSync(filePath);
9100
10187
  }
9101
10188
  function readRegularFile(filePath) {
9102
10189
  return readRegularFileBytes(filePath)?.toString("utf-8");
9103
10190
  }
9104
10191
  function adapterFingerprint(adapter) {
9105
- return (0, import_node_crypto13.createHash)("sha256").update(adapter).digest("hex");
10192
+ return (0, import_node_crypto16.createHash)("sha256").update(adapter).digest("hex");
9106
10193
  }
9107
10194
  function resolveProcessHelperPath() {
9108
10195
  const candidates = [
9109
- import_node_path29.default.resolve(__dirname, "..", "bin", "devrouter-process"),
9110
- import_node_path29.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
10196
+ import_node_path34.default.resolve(__dirname, "..", "bin", "devrouter-process"),
10197
+ import_node_path34.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
9111
10198
  ];
9112
- const helperPath = candidates.find((candidate) => import_node_fs25.default.existsSync(candidate));
10199
+ const helperPath = candidates.find((candidate) => import_node_fs29.default.existsSync(candidate));
9113
10200
  if (!helperPath) {
9114
10201
  throw new Error("Could not locate the packaged devrouter-process helper.");
9115
10202
  }
9116
10203
  return helperPath;
9117
10204
  }
9118
10205
  function resolveManagedPostStartPlan(repoPath) {
9119
- const adapterPath = import_node_path29.default.join(repoPath, MANAGED_ADAPTER_PATH);
10206
+ const adapterPath = import_node_path34.default.join(repoPath, MANAGED_ADAPTER_PATH);
9120
10207
  const adapterBytes = readRegularFileBytes(adapterPath);
9121
10208
  const adapterText = adapterBytes?.toString("utf-8");
9122
- const dockerfilePath = import_node_path29.default.join(repoPath, ".devcontainer", "Dockerfile");
9123
- const devcontainerPath = import_node_path29.default.join(repoPath, ".devcontainer", "devcontainer.json");
10209
+ const dockerfilePath = import_node_path34.default.join(repoPath, ".devcontainer", "Dockerfile");
10210
+ const devcontainerPath = import_node_path34.default.join(repoPath, ".devcontainer", "devcontainer.json");
9124
10211
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
9125
10212
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
9126
10213
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -9161,7 +10248,7 @@ function resolveManagedPostStartPlan(repoPath) {
9161
10248
  }
9162
10249
  function runManagedPostStart(options) {
9163
10250
  if (options.plan.kind !== "runtime") return;
9164
- const helper = import_node_fs25.default.readFileSync(resolveProcessHelperPath());
10251
+ const helper = import_node_fs29.default.readFileSync(resolveProcessHelperPath());
9165
10252
  deliverRuntimeFile(
9166
10253
  options.container.id,
9167
10254
  RUNTIME_HELPER_PATH,
@@ -9710,14 +10797,14 @@ function record4(value) {
9710
10797
  }
9711
10798
  return value;
9712
10799
  }
9713
- function text2(value) {
10800
+ function text3(value) {
9714
10801
  if (typeof value !== "string" || !value || value.includes("\0")) {
9715
10802
  throw new NetworkInventoryError("Docker network inventory contains an invalid identity.");
9716
10803
  }
9717
10804
  return value;
9718
10805
  }
9719
- function id2(value) {
9720
- const result = text2(value);
10806
+ function id4(value) {
10807
+ const result = text3(value);
9721
10808
  if (!FULL_ID.test(result))
9722
10809
  throw new NetworkInventoryError("Docker inventory requires full object IDs.");
9723
10810
  return result;
@@ -9727,7 +10814,7 @@ function rows(output) {
9727
10814
  return trimmed ? trimmed.split(/\r?\n/).map((line) => JSON.parse(line)) : [];
9728
10815
  }
9729
10816
  function ids(output) {
9730
- const values = output.trim() ? output.trim().split(/\r?\n/).map(id2) : [];
10817
+ const values = output.trim() ? output.trim().split(/\r?\n/).map(id4) : [];
9731
10818
  if (values.length > MAX_IDS || new Set(values).size !== values.length) {
9732
10819
  throw new NetworkInventoryError("Docker network inventory has duplicate or too many objects.");
9733
10820
  }
@@ -9773,7 +10860,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9773
10860
  read2(["info", "--format", '{"id":{{json .ID}},"pools":{{json .DefaultAddressPools}}}'])
9774
10861
  )
9775
10862
  );
9776
- result.daemonId = text2(info.id);
10863
+ result.daemonId = text3(info.id);
9777
10864
  if (options.expectedDaemonId && result.daemonId !== options.expectedDaemonId) {
9778
10865
  throw new NetworkInventoryError("Docker daemon identity changed before network inspection.");
9779
10866
  }
@@ -9781,7 +10868,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9781
10868
  throw new NetworkInventoryError("Docker default pools are unavailable.");
9782
10869
  result.pools = info.pools.map((value) => {
9783
10870
  const pool = record4(value);
9784
- const base = text2(pool.Base);
10871
+ const base = text3(pool.Base);
9785
10872
  if (!Number.isSafeInteger(pool.Size) || Number(pool.Size) < 0 || Number(pool.Size) > 128) {
9786
10873
  throw new NetworkInventoryError("Docker default pool allocation size is invalid.");
9787
10874
  }
@@ -9796,11 +10883,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9796
10883
  throw new NetworkInventoryError("Docker network IPAM or endpoint evidence is unavailable.");
9797
10884
  }
9798
10885
  return {
9799
- id: id2(network.id),
9800
- name: text2(network.name),
9801
- driver: text2(network.driver),
10886
+ id: id4(network.id),
10887
+ name: text3(network.name),
10888
+ driver: text3(network.driver),
9802
10889
  subnets: (network.ipam === null ? [] : network.ipam).map(
9803
- (entry) => text2(record4(entry).Subnet)
10890
+ (entry) => text3(record4(entry).Subnet)
9804
10891
  ),
9805
10892
  activeEndpoints: Number(network.activeEndpoints),
9806
10893
  retainedContainerIds: [],
@@ -9816,11 +10903,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9816
10903
  const inspected = [];
9817
10904
  for (const value of containers) {
9818
10905
  const container = record4(value);
9819
- const containerId = id2(container.id);
10906
+ const containerId = id4(container.id);
9820
10907
  inspected.push(containerId);
9821
10908
  for (const membership of Object.values(record4(container.networks))) {
9822
10909
  const reference = record4(membership);
9823
- const networkId = id2(reference.NetworkID);
10910
+ const networkId = id4(reference.NetworkID);
9824
10911
  const network = byId.get(networkId);
9825
10912
  if (!network)
9826
10913
  throw new NetworkInventoryError("A retained container references an unobserved network.");
@@ -9838,7 +10925,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9838
10925
  "Docker object population changed during network inspection."
9839
10926
  );
9840
10927
  }
9841
- const finalId = text2(JSON.parse(read2(["info", "--format", "{{json .ID}}"])));
10928
+ const finalId = text3(JSON.parse(read2(["info", "--format", "{{json .ID}}"])));
9842
10929
  if (finalId !== result.daemonId)
9843
10930
  throw new NetworkInventoryError("Docker daemon identity changed during inspection.");
9844
10931
  result.status = "complete";
@@ -9851,36 +10938,36 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9851
10938
 
9852
10939
  // src/core/network-routes.ts
9853
10940
  var import_node_child_process23 = require("child_process");
9854
- var import_node_net2 = require("net");
10941
+ var import_node_net3 = require("net");
9855
10942
 
9856
10943
  // src/core/network-local-daemon.ts
9857
- var import_node_fs26 = __toESM(require("fs"));
9858
- var import_node_path30 = __toESM(require("path"));
10944
+ var import_node_fs30 = __toESM(require("fs"));
10945
+ var import_node_path35 = __toESM(require("path"));
9859
10946
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
9860
10947
  const deadline = Date.now() + 2e3;
9861
10948
  try {
9862
10949
  if (!endpoint.startsWith("unix:///")) return false;
9863
10950
  const socket = endpoint.slice("unix://".length);
9864
- const table = import_node_fs26.default.readFileSync(import_node_path30.default.join(procRoot, "net/unix"), "utf8");
10951
+ const table = import_node_fs30.default.readFileSync(import_node_path35.default.join(procRoot, "net/unix"), "utf8");
9865
10952
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
9866
10953
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
9867
10954
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
9868
10955
  const inode = `socket:[${rows2[0][6]}]`;
9869
- const ownNamespace = import_node_fs26.default.readlinkSync(import_node_path30.default.join(procRoot, "self/ns/net"));
9870
- const pids = import_node_fs26.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
10956
+ const ownNamespace = import_node_fs30.default.readlinkSync(import_node_path35.default.join(procRoot, "self/ns/net"));
10957
+ const pids = import_node_fs30.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
9871
10958
  if (pids.length > 4096) return false;
9872
10959
  for (const pid of pids) {
9873
10960
  if (Date.now() > deadline) return false;
9874
10961
  try {
9875
- const root = import_node_path30.default.join(procRoot, pid);
9876
- if (import_node_path30.default.basename(import_node_fs26.default.readlinkSync(import_node_path30.default.join(root, "exe"))) !== "dockerd") continue;
9877
- if (import_node_fs26.default.readlinkSync(import_node_path30.default.join(root, "ns/net")) !== ownNamespace) continue;
9878
- const descriptors = import_node_fs26.default.readdirSync(import_node_path30.default.join(root, "fd"));
10962
+ const root = import_node_path35.default.join(procRoot, pid);
10963
+ if (import_node_path35.default.basename(import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "exe"))) !== "dockerd") continue;
10964
+ if (import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "ns/net")) !== ownNamespace) continue;
10965
+ const descriptors = import_node_fs30.default.readdirSync(import_node_path35.default.join(root, "fd"));
9879
10966
  if (descriptors.length > 4096) continue;
9880
10967
  if (descriptors.some((fd) => {
9881
10968
  if (Date.now() > deadline) return false;
9882
10969
  try {
9883
- return import_node_fs26.default.readlinkSync(import_node_path30.default.join(root, "fd", fd)) === inode;
10970
+ return import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "fd", fd)) === inode;
9884
10971
  } catch {
9885
10972
  return false;
9886
10973
  }
@@ -9906,7 +10993,7 @@ function parseLinuxNetworkRoutes(output) {
9906
10993
  const value = row;
9907
10994
  if (typeof value.dst !== "string") throw new Error();
9908
10995
  if (value.dst === "default") continue;
9909
- if ((0, import_node_net2.isIP)(value.dst.split("/")[0]) === 6) continue;
10996
+ if ((0, import_node_net3.isIP)(value.dst.split("/")[0]) === 6) continue;
9910
10997
  const cidr = parseIPv4Cidr(value.dst.includes("/") ? value.dst : `${value.dst}/32`);
9911
10998
  if (!cidr || typeof value.dev !== "string" || !value.dev) throw new Error();
9912
10999
  routes.push({ cidr, interface: value.dev, source: "host" });
@@ -9955,10 +11042,10 @@ function inspectLegacyNetworkCapacity(input) {
9955
11042
  }
9956
11043
 
9957
11044
  // src/core/network-managed.ts
9958
- var import_node_crypto16 = require("crypto");
11045
+ var import_node_crypto19 = require("crypto");
9959
11046
 
9960
11047
  // src/core/network-compose.ts
9961
- var import_node_crypto14 = require("crypto");
11048
+ var import_node_crypto17 = require("crypto");
9962
11049
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
9963
11050
  function isRecord4(value) {
9964
11051
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -10764,19 +11851,19 @@ function stableValue(value) {
10764
11851
  }
10765
11852
  function fingerprintComposeNetworkModel(input) {
10766
11853
  const projection = normalizedFingerprintProjection(input);
10767
- return (0, import_node_crypto14.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
11854
+ return (0, import_node_crypto17.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
10768
11855
  }
10769
11856
 
10770
11857
  // src/core/network-compose-files.ts
10771
11858
  var import_node_child_process24 = require("child_process");
10772
- var import_node_crypto15 = require("crypto");
10773
- var import_node_fs27 = __toESM(require("fs"));
10774
- var import_node_path31 = __toESM(require("path"));
11859
+ var import_node_crypto18 = require("crypto");
11860
+ var import_node_fs31 = __toESM(require("fs"));
11861
+ var import_node_path36 = __toESM(require("path"));
10775
11862
  var import_yaml4 = __toESM(require("yaml"));
10776
11863
  var OVERLAY = "docker-compose.devrouter-network.yml";
10777
11864
  var MARKER = "# devrouter:managed workspace network";
10778
- var MAX_BYTES = 2 * 1024 * 1024;
10779
- function object2(value) {
11865
+ var MAX_BYTES2 = 2 * 1024 * 1024;
11866
+ function object3(value) {
10780
11867
  if (!value || typeof value !== "object" || Array.isArray(value))
10781
11868
  throw new Error("Network Compose evidence is malformed.");
10782
11869
  return value;
@@ -10784,20 +11871,20 @@ function object2(value) {
10784
11871
  function inspectNetworkComposeFiles(input) {
10785
11872
  const authoredNetworks = {};
10786
11873
  for (const file of input.plan.composeFiles) {
10787
- const stat = import_node_fs27.default.lstatSync(file);
10788
- if (!stat.isFile() || stat.size > MAX_BYTES)
11874
+ const stat = import_node_fs31.default.lstatSync(file);
11875
+ if (!stat.isFile() || stat.size > MAX_BYTES2)
10789
11876
  throw new Error("Network Compose source is unavailable or too large.");
10790
11877
  let source;
10791
11878
  try {
10792
- source = object2(import_yaml4.default.parse(import_node_fs27.default.readFileSync(file, "utf8")));
11879
+ source = object3(import_yaml4.default.parse(import_node_fs31.default.readFileSync(file, "utf8")));
10793
11880
  } catch {
10794
11881
  throw new Error("Network Compose source could not be parsed safely.");
10795
11882
  }
10796
11883
  if (source.networks !== void 0) {
10797
- for (const [name, declaration] of Object.entries(object2(source.networks))) {
11884
+ for (const [name, declaration] of Object.entries(object3(source.networks))) {
10798
11885
  authoredNetworks[name] = {
10799
11886
  ...authoredNetworks[name],
10800
- ...declaration === null ? {} : object2(declaration)
11887
+ ...declaration === null ? {} : object3(declaration)
10801
11888
  };
10802
11889
  }
10803
11890
  }
@@ -10825,13 +11912,13 @@ function inspectNetworkComposeFiles(input) {
10825
11912
  env,
10826
11913
  encoding: "utf8",
10827
11914
  timeout: 1e4,
10828
- maxBuffer: MAX_BYTES
11915
+ maxBuffer: MAX_BYTES2
10829
11916
  }
10830
11917
  );
10831
11918
  if (result.error || result.status !== 0)
10832
11919
  throw new Error("Complete network Compose evidence is unavailable.");
10833
11920
  try {
10834
- return { compose: object2(JSON.parse(result.stdout)), authoredNetworks };
11921
+ return { compose: object3(JSON.parse(result.stdout)), authoredNetworks };
10835
11922
  } catch {
10836
11923
  throw new Error("Network Compose evidence is malformed.");
10837
11924
  }
@@ -10839,7 +11926,7 @@ function inspectNetworkComposeFiles(input) {
10839
11926
  function prepareNetworkComposeFiles(plan, subnet) {
10840
11927
  const overlay = deriveComposeNetworkOverlay(subnet);
10841
11928
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
10842
- const overlayPath = import_node_path31.default.join(plan.composeDirectory, OVERLAY);
11929
+ const overlayPath = import_node_path36.default.join(plan.composeDirectory, OVERLAY);
10843
11930
  const ignored = (0, import_node_child_process24.spawnSync)("git", [
10844
11931
  "-C",
10845
11932
  plan.composeDirectory,
@@ -10851,12 +11938,12 @@ function prepareNetworkComposeFiles(plan, subnet) {
10851
11938
  ]);
10852
11939
  if (ignored.status !== 0)
10853
11940
  throw new Error("Generated network overlay must be ignored before allocation.");
10854
- const bytes = `${MARKER}
11941
+ const bytes2 = `${MARKER}
10855
11942
  ${import_yaml4.default.stringify(overlay.overlay)}`;
10856
11943
  const assertOwnership = () => {
10857
- if (!import_node_fs27.default.existsSync(overlayPath)) return;
10858
- const stat = import_node_fs27.default.lstatSync(overlayPath);
10859
- if (!stat.isFile() || stat.size > MAX_BYTES || import_node_fs27.default.readFileSync(overlayPath, "utf8") !== bytes)
11944
+ if (!import_node_fs31.default.existsSync(overlayPath)) return;
11945
+ const stat = import_node_fs31.default.lstatSync(overlayPath);
11946
+ if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs31.default.readFileSync(overlayPath, "utf8") !== bytes2)
10860
11947
  throw new Error("Retained network overlay changed; repair is required.");
10861
11948
  };
10862
11949
  assertOwnership();
@@ -10870,13 +11957,13 @@ ${JSON.stringify(extended.config, null, 2)}
10870
11957
  ...plan,
10871
11958
  contents,
10872
11959
  composeFiles: [...plan.composeFiles, overlayPath],
10873
- effectiveConfigSha256: (0, import_node_crypto15.createHash)("sha256").update(contents).digest("hex")
11960
+ effectiveConfigSha256: (0, import_node_crypto18.createHash)("sha256").update(contents).digest("hex")
10874
11961
  };
10875
11962
  return {
10876
11963
  plan: result,
10877
11964
  write: () => {
10878
11965
  assertOwnership();
10879
- writeFileAtomically(overlayPath, bytes);
11966
+ writeFileAtomically(overlayPath, bytes2);
10880
11967
  }
10881
11968
  };
10882
11969
  }
@@ -11188,7 +12275,7 @@ function createManagedNetworkSession(input) {
11188
12275
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
11189
12276
  );
11190
12277
  const authority = readNetworkOperationAuthority(identity2);
11191
- const fingerprint = (0, import_node_crypto16.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
12278
+ const fingerprint = (0, import_node_crypto19.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
11192
12279
  if (!saved) {
11193
12280
  if (policy.status !== "valid")
11194
12281
  throw new Error("New network allocation requires valid machine policy.");
@@ -11372,11 +12459,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
11372
12459
  var POLL_INTERVAL_MS = 1e3;
11373
12460
  function assertOverlay(container, repoPath) {
11374
12461
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
11375
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path32.default.join(repoPath, ".devcontainer"))) {
12462
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))) {
11376
12463
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
11377
12464
  }
11378
12465
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
11379
- const expectedOverlay = import_node_path32.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
12466
+ const expectedOverlay = import_node_path37.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
11380
12467
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
11381
12468
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
11382
12469
  }
@@ -11571,7 +12658,7 @@ function hasExactManagedComposeProject(repoPath, state) {
11571
12658
  }).some((container) => {
11572
12659
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
11573
12660
  return Boolean(
11574
- workingDir && sameWorkspacePath(workingDir, import_node_path32.default.join(repoPath, ".devcontainer"))
12661
+ workingDir && sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))
11575
12662
  );
11576
12663
  });
11577
12664
  } catch {
@@ -11955,7 +13042,7 @@ function resolvePrimaryTarget(repoPath) {
11955
13042
  }
11956
13043
  function isPrimaryCheckout(repoPath) {
11957
13044
  try {
11958
- return import_node_fs28.default.statSync(import_node_path32.default.join(repoPath, ".git")).isDirectory();
13045
+ return import_node_fs32.default.statSync(import_node_path37.default.join(repoPath, ".git")).isDirectory();
11959
13046
  } catch {
11960
13047
  return false;
11961
13048
  }
@@ -12018,8 +13105,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12018
13105
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
12019
13106
  let devpodId = target.devpodId;
12020
13107
  if (target.kind === "linked") {
12021
- const overlayPath = import_node_path32.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
12022
- if (!import_node_fs28.default.existsSync(overlayPath)) {
13108
+ const overlayPath = import_node_path37.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
13109
+ if (!import_node_fs32.default.existsSync(overlayPath)) {
12023
13110
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
12024
13111
  }
12025
13112
  }
@@ -12089,7 +13176,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12089
13176
  }
12090
13177
  const apps = proxyAppsFromConfig(runtime.config);
12091
13178
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
12092
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path32.default.basename(repoPath)) ?? "app";
13179
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path37.default.basename(repoPath)) ?? "app";
12093
13180
  for (const [index, app] of apps.entries()) {
12094
13181
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
12095
13182
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -12340,6 +13427,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12340
13427
  claimLifecycleEffect();
12341
13428
  writeManagedRuntimeState(state);
12342
13429
  capturedStopState = state;
13430
+ clearActiveLifecycleStartupWitness();
12343
13431
  };
12344
13432
  if (options.repair) capture();
12345
13433
  else
@@ -12908,9 +13996,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12908
13996
  }
12909
13997
  const repairLocked = () => {
12910
13998
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
12911
- import_node_fs28.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13999
+ import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12912
14000
  return withFileLock(
12913
- import_node_path32.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
14001
+ import_node_path37.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
12914
14002
  {
12915
14003
  activity: "Managed runtime repair",
12916
14004
  target: repoPath,
@@ -12982,6 +14070,7 @@ var lockHeld = false;
12982
14070
  var stopProjects = [];
12983
14071
  var stopBaselineState;
12984
14072
  var BUSY_LIFECYCLE_WAIT_MS = 30 * 60 * 1e3;
14073
+ var CAPACITY_CLIENT_WAIT_MS = 30 * 60 * 1e3;
12985
14074
  function matchesFence(record5, fence) {
12986
14075
  return Object.entries(reliabilityFence(record5.state)).every(
12987
14076
  ([key, value]) => value === fence[key]
@@ -12998,6 +14087,7 @@ function claimActiveLifecycleEffect() {
12998
14087
  if (!matchesFence(record5, worker.fence) || worker.kind !== "stop" && record5.worker?.id !== worker.workerId) {
12999
14088
  throw new Error("Lifecycle intent superseded this worker.");
13000
14089
  }
14090
+ if (worker.kind !== "stop") assertCapacityEffect(record5, worker.workerId, Date.now());
13001
14091
  if (record5.effectSequence === Number.MAX_SAFE_INTEGER)
13002
14092
  throw new Error("Lifecycle effect sequence exhausted.");
13003
14093
  record5.effectSequence += 1;
@@ -13052,6 +14142,13 @@ function reconcileDrained(record5) {
13052
14142
  record5.worker = null;
13053
14143
  }
13054
14144
  }
14145
+ function clearActiveLifecycleStartupWitness() {
14146
+ const request = activeWorker;
14147
+ if (!request || request.kind === "stop") return;
14148
+ const record5 = readReliabilityOperation(request.identity);
14149
+ if (record5?.startupWitness?.operationId !== request.operationId) return;
14150
+ clearStartupWitness(request.identity, reliabilityFence(record5.state));
14151
+ }
13055
14152
  async function acquireWorkerLock(repoPath, operation) {
13056
14153
  const deadline = Date.now() + 3e4;
13057
14154
  for (; ; ) {
@@ -13108,7 +14205,7 @@ async function executeLifecycleWorker(request, operation) {
13108
14205
  const owned = containers.filter(
13109
14206
  (container) => sameWorkspacePath(
13110
14207
  container.labels["com.docker.compose.project.working_dir"] ?? "",
13111
- import_node_path33.default.join(request.repoPath, ".devcontainer")
14208
+ import_node_path38.default.join(request.repoPath, ".devcontainer")
13112
14209
  )
13113
14210
  );
13114
14211
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -13134,7 +14231,7 @@ async function executeLifecycleWorker(request, operation) {
13134
14231
  }
13135
14232
  });
13136
14233
  }
13137
- function recordLifecycleOutcome(outcome) {
14234
+ function recordLifecycleOutcome(outcome, result) {
13138
14235
  const request = activeWorker;
13139
14236
  if (request?.kind !== "exec") return;
13140
14237
  updateReliabilityOperation(request.identity, (record5) => {
@@ -13152,27 +14249,40 @@ function recordLifecycleOutcome(outcome) {
13152
14249
  };
13153
14250
  stepRecord(record5, event);
13154
14251
  record5.outcome = { ...outcome, operationId: request.operationId };
14252
+ if (result !== void 0) record5.result = result;
13155
14253
  });
13156
14254
  }
13157
- function recordLifecycleCompletion(exitCode) {
14255
+ function recordLifecycleCompletion(exitCode, preparedProfile, result) {
13158
14256
  const request = activeWorker;
13159
14257
  if (!request || request.kind === "stop") return;
13160
14258
  updateReliabilityOperation(request.identity, (record5) => {
13161
14259
  if (!matchesFence(record5, request.fence)) return;
14260
+ if (preparedProfile !== void 0 && record5.enrollment) {
14261
+ if (request.kind !== "ensure" || record5.worker?.id !== request.workerId || record5.state.operation?.id !== request.operationId || record5.state.profile !== preparedProfile)
14262
+ throw new Error("Prepared profile does not match the active lifecycle worker.");
14263
+ record5.activeProfile = preparedProfile;
14264
+ record5.preparation = {
14265
+ operationId: request.operationId,
14266
+ profile: preparedProfile,
14267
+ fence: { ...request.fence }
14268
+ };
14269
+ }
13162
14270
  stepRecord(record5, {
13163
14271
  ...request.fence,
13164
14272
  type: "completion",
13165
14273
  operationId: request.operationId,
13166
14274
  exitCode
13167
14275
  });
14276
+ if (result !== void 0) record5.result = result;
13168
14277
  });
13169
14278
  }
13170
- function recordLifecycleUnknown() {
14279
+ function recordLifecycleUnknown(result) {
13171
14280
  const request = activeWorker;
13172
14281
  if (!request || request.kind === "stop") return;
13173
14282
  updateReliabilityOperation(request.identity, (record5) => {
13174
14283
  if (!matchesFence(record5, request.fence)) return;
13175
14284
  stepRecord(record5, { ...request.fence, type: "interrupted", operationId: request.operationId });
14285
+ if (result !== void 0) record5.result = result;
13176
14286
  });
13177
14287
  }
13178
14288
  function proveLifecycleStopped() {
@@ -13196,7 +14306,7 @@ function proveLifecycleStopped() {
13196
14306
  const remaining = inspectWorkspaceContainers().filter(
13197
14307
  (container) => sameWorkspacePath(
13198
14308
  container.labels["com.docker.compose.project.working_dir"] ?? "",
13199
- import_node_path33.default.join(request.repoPath, ".devcontainer")
14309
+ import_node_path38.default.join(request.repoPath, ".devcontainer")
13200
14310
  )
13201
14311
  );
13202
14312
  if (remaining.some(
@@ -13209,16 +14319,48 @@ function proveLifecycleStopped() {
13209
14319
  (route) => sameWorkspacePath(route.repoPath, request.repoPath)
13210
14320
  );
13211
14321
  if (routes.length) throw new Error("Workspace routes remain published after stop.");
13212
- updateReliabilityOperation(request.identity, (record5) => {
14322
+ const settlement = updateReliabilityOperation(request.identity, (record5) => {
13213
14323
  if (!matchesFence(record5, request.fence) || record5.worker)
13214
14324
  throw new Error("Stop proof was superseded or an earlier worker remains.");
14325
+ if (record5.version === 2) {
14326
+ if (record5.capacity) record5.capacity.validUntilMs = 0;
14327
+ return record5.state.environmentId;
14328
+ }
13215
14329
  stepRecord(record5, {
13216
14330
  ...request.fence,
13217
14331
  type: "stop-proof",
13218
14332
  workloadsStopped: true,
13219
14333
  routesRemoved: true
13220
14334
  });
14335
+ return void 0;
13221
14336
  });
14337
+ if (settlement) {
14338
+ const capacity = new CapacityStore(import_node_path38.default.join(DEVROUTER_HOME, "controller"));
14339
+ for (let attempt = 0; ; attempt++) {
14340
+ try {
14341
+ capacity.settleEnvironmentAfterStop(settlement, capacity.read().revision);
14342
+ break;
14343
+ } catch (error) {
14344
+ if (!(error instanceof CapacitySnapshotChangedError) || attempt >= 2) throw error;
14345
+ }
14346
+ }
14347
+ updateReliabilityOperation(request.identity, (record5) => {
14348
+ if (!matchesFence(record5, request.fence) || record5.worker || record5.state.environmentId !== settlement || record5.capacity && record5.capacity.validUntilMs !== 0)
14349
+ throw new Error("Capacity settlement was superseded before journal confirmation.");
14350
+ record5.capacity = null;
14351
+ if (record5.enrollment) {
14352
+ record5.phaseSettlement = null;
14353
+ record5.activeProfile = null;
14354
+ record5.preparation = null;
14355
+ }
14356
+ stepRecord(record5, {
14357
+ ...request.fence,
14358
+ type: "stop-proof",
14359
+ workloadsStopped: true,
14360
+ routesRemoved: true
14361
+ });
14362
+ });
14363
+ }
13222
14364
  };
13223
14365
  if (stopBaselineState)
13224
14366
  withMutationLock("Settle retained stop", stopBaselineState.repoPath, settle);
@@ -13354,7 +14496,7 @@ async function devpodExecOutcome(repoPath, command) {
13354
14496
  }
13355
14497
  assertRunningDevpod(devpod.id, repoPath);
13356
14498
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
13357
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto17.randomUUID)()}__:`;
14499
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto21.randomUUID)()}__:`;
13358
14500
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
13359
14501
  const literalCommand = command.map(quotePosixArg).join(" ");
13360
14502
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -13544,11 +14686,11 @@ async function devpodExecOutcome(repoPath, command) {
13544
14686
 
13545
14687
  // src/core/workspace-lifecycle.ts
13546
14688
  var import_node_child_process29 = require("child_process");
13547
- var import_node_fs30 = __toESM(require("fs"));
13548
- var import_node_path34 = __toESM(require("path"));
14689
+ var import_node_fs34 = __toESM(require("fs"));
14690
+ var import_node_path39 = __toESM(require("path"));
13549
14691
 
13550
14692
  // src/core/route-state.ts
13551
- var import_node_fs29 = __toESM(require("fs"));
14693
+ var import_node_fs33 = __toESM(require("fs"));
13552
14694
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
13553
14695
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
13554
14696
  }
@@ -13753,20 +14895,26 @@ process.once("message", async (message) => {
13753
14895
  request.command ?? [],
13754
14896
  request.retainedExecProof
13755
14897
  ) : await devpodExecOutcome(request.repoPath, request.command ?? []);
13756
- recordLifecycleOutcome(outcome);
14898
+ recordLifecycleOutcome(outcome, { ok: true, value: outcome });
13757
14899
  return outcome;
13758
14900
  }
13759
14901
  const ensured = await workspaceEnsure(request.repoPath, request.options);
13760
14902
  recordLifecycleCompletion(
13761
- ensured.applicationReadiness?.status === "application-error" ? 1 : 0
14903
+ ensured.applicationReadiness?.status === "application-error" ? 1 : 0,
14904
+ ensured.managedRuntime?.status === "ready" ? ensured.profile : void 0,
14905
+ { ok: true, value: ensured }
13762
14906
  );
13763
14907
  return ensured;
13764
14908
  });
13765
14909
  result = { ok: true, value };
13766
14910
  } catch (error) {
13767
14911
  try {
13768
- if (error instanceof ExecutionOutcomeError) recordLifecycleOutcome(error.outcome);
13769
- else recordLifecycleUnknown();
14912
+ const failure = {
14913
+ ok: false,
14914
+ message: error instanceof Error ? error.message : String(error)
14915
+ };
14916
+ if (error instanceof ExecutionOutcomeError) recordLifecycleOutcome(error.outcome, failure);
14917
+ else recordLifecycleUnknown(failure);
13770
14918
  } catch {
13771
14919
  }
13772
14920
  result = { ok: false, message: error instanceof Error ? error.message : String(error) };