@devrouter/cli 0.0.66 → 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
  }
@@ -1188,6 +1210,17 @@ function inspectDevsyRuntimeStatus(devsyId) {
1188
1210
  if (result.status !== 0 || result.error) return "unknown";
1189
1211
  return parseDevsyRuntimeStatus(result.stdout, devsyId);
1190
1212
  }
1213
+ function inspectDevsyRuntimeAbsence(devsyId) {
1214
+ const result = (0, import_node_child_process5.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
1215
+ encoding: "utf-8",
1216
+ timeout: 1e4
1217
+ });
1218
+ if (!result.error && result.status === 0) {
1219
+ return parseDevsyRuntimeStatus(result.stdout, devsyId) === "not-found";
1220
+ }
1221
+ const output = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n");
1222
+ return /workspace not found/i.test(output);
1223
+ }
1191
1224
  function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
1192
1225
  const idOwners = workspaces.filter((workspace) => workspace.id === devsyId);
1193
1226
  const pathOwners = workspaces.filter(
@@ -1987,6 +2020,7 @@ function requireReadyDevsyAgent(options = {}) {
1987
2020
  // src/core/managed-devsy-stop.ts
1988
2021
  var import_node_os4 = __toESM(require("os"));
1989
2022
  var import_node_path20 = __toESM(require("path"));
2023
+ var import_node_util4 = require("util");
1990
2024
 
1991
2025
  // src/core/devcontainer-profile.ts
1992
2026
  var import_node_child_process10 = require("child_process");
@@ -2461,7 +2495,7 @@ function stopExactManagedService(containerId, service, options) {
2461
2495
  var import_node_fs10 = __toESM(require("fs"));
2462
2496
  var import_node_path12 = __toESM(require("path"));
2463
2497
  function proveManagedComposePopulation(options) {
2464
- const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers } = options;
2498
+ const { plan, repoPath, composeProject, providerRoot, featureDirectory, containers } = options;
2465
2499
  const services = /* @__PURE__ */ new Set();
2466
2500
  for (const container of containers) {
2467
2501
  const service = container.labels["com.docker.compose.service"] ?? "";
@@ -2479,7 +2513,7 @@ function proveManagedComposePopulation(options) {
2479
2513
  for (const file of files.slice(plan.composeFiles.length)) {
2480
2514
  if (import_node_path12.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2481
2515
  import_node_path12.default.basename(file)
2482
- ) || 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))) {
2483
2517
  throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
2484
2518
  }
2485
2519
  }
@@ -3208,8 +3242,8 @@ function managedRunnerId(uid, providerId) {
3208
3242
  if (uid === void 0) {
3209
3243
  return providerId;
3210
3244
  }
3211
- const bytes = Buffer.byteLength(uid);
3212
- return bytes === 16 || bytes === 40 ? uid : providerId;
3245
+ const bytes2 = Buffer.byteLength(uid);
3246
+ return bytes2 === 16 || bytes2 === 40 ? uid : providerId;
3213
3247
  }
3214
3248
  function identityOf(value) {
3215
3249
  return {
@@ -3297,7 +3331,7 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
3297
3331
  repoPath: state.repoPath,
3298
3332
  composeProject: state.composeProject,
3299
3333
  featureDirectory,
3300
- devsyRoot,
3334
+ providerRoot: devsyRoot,
3301
3335
  containers
3302
3336
  });
3303
3337
  if (primary.id !== primaryId)
@@ -3401,7 +3435,7 @@ function proveReplacementStopAbsence(state, baseline, replacement) {
3401
3435
  const newRunner = managedRunnerId(before.uid, baseline.providerId);
3402
3436
  const runners = oldRunner === newRunner ? [oldRunner] : [oldRunner, newRunner];
3403
3437
  const assertEmptyPopulation = () => {
3404
- 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))
3405
3439
  throw new Error("Replacement stop observed a remaining workspace population.");
3406
3440
  };
3407
3441
  assertEmptyPopulation();
@@ -3484,6 +3518,7 @@ function managedStopRouteReferences(state) {
3484
3518
  }
3485
3519
 
3486
3520
  // src/core/repo-config.ts
3521
+ var import_node_crypto7 = require("crypto");
3487
3522
  var import_node_fs14 = __toESM(require("fs"));
3488
3523
  var import_node_path18 = __toESM(require("path"));
3489
3524
  var import_yaml3 = __toESM(require("yaml"));
@@ -3846,10 +3881,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
3846
3881
  }
3847
3882
  const now = (/* @__PURE__ */ new Date()).toISOString();
3848
3883
  const replacements = inputs.map((input) => {
3849
- const id3 = buildHostRouteId(input.repoPath, input.name);
3884
+ const id5 = buildHostRouteId(input.repoPath, input.name);
3850
3885
  return hostRouteFromInput(
3851
3886
  input,
3852
- routes.find((route) => route.id === id3),
3887
+ routes.find((route) => route.id === id5),
3853
3888
  now
3854
3889
  );
3855
3890
  });
@@ -3891,6 +3926,10 @@ var VALID_HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-
3891
3926
  var DEVROUTER_VERSION_RE = /^\d+\.\d+\.\d+$/;
3892
3927
  var VALID_ENV_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/i;
3893
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;
3894
3933
  var UPSTREAM_TEMPLATE_RE = /^(?:\$\{WORKSPACE\}|[a-zA-Z0-9._-])+:\d{1,5}$/;
3895
3934
  function assertUpstreamSpec(upstream, label) {
3896
3935
  if (upstream.includes(WORKSPACE_PLACEHOLDER)) {
@@ -4446,11 +4485,265 @@ function parseManagedRuntime(value, configPath) {
4446
4485
  processes
4447
4486
  };
4448
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
+ }
4449
4733
  function parseConfig(raw, configPath) {
4450
4734
  const root = ensureObject(raw, configPath);
4451
4735
  ensureAllowedKeys(
4452
4736
  root,
4453
- ["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
+ ],
4454
4747
  configPath
4455
4748
  );
4456
4749
  const version = toIntegerOrThrow(root.version, `${configPath}.version`);
@@ -4522,6 +4815,7 @@ function parseConfig(raw, configPath) {
4522
4815
  }
4523
4816
  const managedRuntime = parseManagedRuntime(root.managedRuntime, configPath);
4524
4817
  const profiles = parseProfiles(root.profiles, configPath, apps, managedRuntime);
4818
+ const capacity = parseCapacityEstimates(root.capacity, configPath, profiles);
4525
4819
  return {
4526
4820
  version: 1,
4527
4821
  ...devrouter ? { devrouter } : {},
@@ -4529,6 +4823,7 @@ function parseConfig(raw, configPath) {
4529
4823
  ...secretManager ? { secretManager } : {},
4530
4824
  ...managedRuntime ? { managedRuntime } : {},
4531
4825
  ...profiles ? { profiles } : {},
4826
+ ...capacity ? { capacity } : {},
4532
4827
  apps
4533
4828
  };
4534
4829
  }
@@ -4863,7 +5158,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs14.default.rea
4863
5158
  const config = parseConfig(parsed ?? {}, configPath);
4864
5159
  const requiredVersion = config.devrouter?.version;
4865
5160
  if (requiredVersion && !hasWarnedVersionMismatch) {
4866
- const cliVersion = true ? "0.0.66" : "0.0.0-dev";
5161
+ const cliVersion = true ? "0.0.68" : "0.0.0-dev";
4867
5162
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4868
5163
  hasWarnedVersionMismatch = true;
4869
5164
  process.stderr.write(
@@ -5042,7 +5337,7 @@ async function ensureNetwork(name) {
5042
5337
 
5043
5338
  // src/core/tls.ts
5044
5339
  var import_node_child_process13 = require("child_process");
5045
- var import_node_crypto7 = require("crypto");
5340
+ var import_node_crypto8 = require("crypto");
5046
5341
  var import_node_fs15 = __toESM(require("fs"));
5047
5342
  var import_node_path19 = __toESM(require("path"));
5048
5343
  var DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
@@ -5112,7 +5407,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
5112
5407
  return normalizeUniqueHosts(names);
5113
5408
  }
5114
5409
  function parseCertificateDnsHosts(pem) {
5115
- const certificate = new import_node_crypto7.X509Certificate(pem);
5410
+ const certificate = new import_node_crypto8.X509Certificate(pem);
5116
5411
  const subjectAltName = certificate.subjectAltName ?? "";
5117
5412
  if (subjectAltName.length === 0) {
5118
5413
  return [];
@@ -5418,6 +5713,66 @@ function stopRetainedManagedDevsyWorkspace(options) {
5418
5713
  }
5419
5714
  return owner.workspace;
5420
5715
  }
5716
+ function registrationStatus() {
5717
+ resetWorkspaceRuntimeCaches();
5718
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
5719
+ throw new Error("Managed stop provider selection changed.");
5720
+ }
5721
+ return inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
5722
+ }
5723
+ function proveAbsentRegistrationStop() {
5724
+ const observe = () => {
5725
+ const owner = registrationStatus();
5726
+ if (owner.status === "conflict") throw new Error(owner.reason);
5727
+ if (owner.status !== "absent") {
5728
+ throw new Error("Managed stop absence proof requires the registration to remain absent.");
5729
+ }
5730
+ if (listDevpodWorkspacesRaw({ allowMissingExecutable: true }).some(
5731
+ (entry) => entry.id === devsyId || sameWorkspacePath(entry.source.localFolder, repoPath)
5732
+ )) {
5733
+ throw new Error(
5734
+ "Managed stop absence proof requires both provider registrations to remain absent."
5735
+ );
5736
+ }
5737
+ if (!inspectDevsyRuntimeAbsence(devsyId)) {
5738
+ throw new Error(
5739
+ "Managed stop absence proof requires Devsy to report the runtime not-found."
5740
+ );
5741
+ }
5742
+ if (inspectManagedStopContainers(state.composeProject).length !== 0) {
5743
+ throw new Error("Managed stop absence proof observed a remaining compose population.");
5744
+ }
5745
+ if (inspectProviderRunnerContainers(resolveManagedStopEndpoint(), devsyId).length !== 0) {
5746
+ throw new Error("Managed stop absence proof observed a remaining runner population.");
5747
+ }
5748
+ const owned = inspectWorkspaceContainers().filter(
5749
+ (container) => sameWorkspacePath(
5750
+ container.labels["com.docker.compose.project.working_dir"] ?? "",
5751
+ import_node_path20.default.join(repoPath, ".devcontainer")
5752
+ )
5753
+ );
5754
+ if (owned.length !== 0) {
5755
+ throw new Error("Managed stop absence proof observed a remaining workspace container.");
5756
+ }
5757
+ if (workspace) {
5758
+ const record5 = readWorkspaceOwnership(repoPath, workspace);
5759
+ if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present") {
5760
+ throw new Error("Managed stop workspace ownership changed during absence proof.");
5761
+ }
5762
+ }
5763
+ if (!(0, import_node_util4.isDeepStrictEqual)(readManagedRuntimeState(repoPath, workspace), state)) {
5764
+ throw new Error("Managed stop retained state changed during absence proof.");
5765
+ }
5766
+ };
5767
+ observe();
5768
+ observe();
5769
+ }
5770
+ const initialOwner = registrationStatus();
5771
+ if (initialOwner.status === "conflict") throw new Error(initialOwner.reason);
5772
+ if (initialOwner.status === "absent") {
5773
+ proveAbsentRegistrationStop();
5774
+ return "proven-absent";
5775
+ }
5421
5776
  const context = registration2().context;
5422
5777
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
5423
5778
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
@@ -5461,7 +5816,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
5461
5816
  repoPath,
5462
5817
  composeProject: state.composeProject,
5463
5818
  featureDirectory,
5464
- devsyRoot,
5819
+ providerRoot: devsyRoot,
5465
5820
  containers
5466
5821
  });
5467
5822
  if (previous && (!sameSet(
@@ -5561,12 +5916,12 @@ function networkProviderEnvironment(binding, source) {
5561
5916
  }
5562
5917
 
5563
5918
  // src/core/network-claim-lookup.ts
5564
- var import_node_crypto9 = require("crypto");
5919
+ var import_node_crypto10 = require("crypto");
5565
5920
  var import_node_fs18 = __toESM(require("fs"));
5566
5921
  var import_node_path23 = __toESM(require("path"));
5567
5922
 
5568
5923
  // src/core/network-claims.ts
5569
- var import_node_crypto8 = require("crypto");
5924
+ var import_node_crypto9 = require("crypto");
5570
5925
  var import_node_fs17 = __toESM(require("fs"));
5571
5926
  var import_node_path22 = __toESM(require("path"));
5572
5927
 
@@ -6756,7 +7111,7 @@ function assertDirectory(directory) {
6756
7111
  }
6757
7112
  }
6758
7113
  function hashKey(value) {
6759
- return (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
7114
+ return (0, import_node_crypto9.createHash)("sha256").update(value).digest("hex");
6760
7115
  }
6761
7116
  function cloneFence(fence) {
6762
7117
  return { ...fence };
@@ -6784,7 +7139,7 @@ function claimError(code, message) {
6784
7139
  function networkOwnerKey(repoPath) {
6785
7140
  if (!import_node_path23.default.isAbsolute(repoPath) || import_node_path23.default.resolve(repoPath) !== repoPath)
6786
7141
  throw new Error("Invalid network owner path.");
6787
- return (0, import_node_crypto9.createHash)("sha256").update(repoPath).digest("hex");
7142
+ return (0, import_node_crypto10.createHash)("sha256").update(repoPath).digest("hex");
6788
7143
  }
6789
7144
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6790
7145
  let names;
@@ -6809,22 +7164,22 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6809
7164
  );
6810
7165
  try {
6811
7166
  if (!import_node_fs18.default.fstatSync(descriptor).isFile()) throw new Error();
6812
- const bytes = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
7167
+ const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
6813
7168
  let size = 0;
6814
- while (size < bytes.length) {
6815
- 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);
6816
7171
  if (!count) break;
6817
7172
  size += count;
6818
7173
  }
6819
7174
  if (size > NETWORK_CLAIMS_MAX_FILE_BYTES) throw new Error();
6820
- value = JSON.parse(bytes.subarray(0, size).toString("utf8"));
7175
+ value = JSON.parse(bytes2.subarray(0, size).toString("utf8"));
6821
7176
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
6822
7177
  } catch {
6823
7178
  throw new Error("Retained network claim could not be read safely.");
6824
7179
  } finally {
6825
7180
  import_node_fs18.default.closeSync(descriptor);
6826
7181
  }
6827
- 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)
6828
7183
  throw new Error("Retained network daemon identity is invalid.");
6829
7184
  const claim2 = readNetworkClaim({
6830
7185
  daemonId: value.daemonId,
@@ -6841,7 +7196,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
6841
7196
 
6842
7197
  // src/core/network-provider-inspect.ts
6843
7198
  var import_node_child_process14 = require("child_process");
6844
- var import_node_crypto10 = require("crypto");
7199
+ var import_node_crypto11 = require("crypto");
6845
7200
  function object(value) {
6846
7201
  if (!value || typeof value !== "object" || Array.isArray(value))
6847
7202
  throw new Error("Invalid provider evidence.");
@@ -6861,7 +7216,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
6861
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 !== "") {
6862
7217
  throw new Error("Docker provider definition is not qualified for network allocation.");
6863
7218
  }
6864
- 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");
6865
7220
  }
6866
7221
  function read(provider, args) {
6867
7222
  const result = (0, import_node_child_process14.spawnSync)(provider, args, {
@@ -6923,9 +7278,9 @@ function inspectNetworkProviderBinding(input) {
6923
7278
  const option = (key, localOnly = false) => {
6924
7279
  const value = localOnly ? stored[key] : stored[key] ?? settings[key];
6925
7280
  if (value === void 0) return null;
6926
- const text3 = object(value).value;
6927
- if (typeof text3 !== "string") throw new Error();
6928
- return text3 || null;
7281
+ const text4 = object(value).value;
7282
+ if (typeof text4 !== "string") throw new Error();
7283
+ return text4 || null;
6929
7284
  };
6930
7285
  if (workspaceProvider && workspaceProvider.name !== "docker") throw new Error();
6931
7286
  if (option("DOCKER_ELEVATION") && option("DOCKER_ELEVATION") !== "none") throw new Error();
@@ -7125,12 +7480,12 @@ function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
7125
7480
  function assertDevsyTarget(devsyId, repoPath) {
7126
7481
  const workspaces = listDevsyWorkspaces();
7127
7482
  const existing = selectDevsyWorkspace(workspaces, repoPath);
7128
- const id3 = devsyId ?? existing?.id;
7129
- if (id3) {
7130
- const before = inspectDevsyWorkspaceOwnership(workspaces, id3, repoPath);
7483
+ const id5 = devsyId ?? existing?.id;
7484
+ if (id5) {
7485
+ const before = inspectDevsyWorkspaceOwnership(workspaces, id5, repoPath);
7131
7486
  if (before.status === "conflict") throw new Error(before.reason);
7132
7487
  }
7133
- return id3;
7488
+ return id5;
7134
7489
  }
7135
7490
  async function startDevsyWorkspace(options) {
7136
7491
  const activity = options.recreate ? "Devsy recreate" : "Devsy start";
@@ -7242,7 +7597,572 @@ var ExecutionOutcomeError = class extends Error {
7242
7597
  };
7243
7598
 
7244
7599
  // src/core/reliability-lifecycle.ts
7245
- 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
+ };
7246
8166
 
7247
8167
  // src/core/reliability-contract.ts
7248
8168
  var RELIABILITY_CONTRACT_VERSION = 2;
@@ -7396,6 +8316,7 @@ function assertReliabilityEvent(value) {
7396
8316
  case "launched":
7397
8317
  case "interrupted":
7398
8318
  case "not-started":
8319
+ case "settle":
7399
8320
  valid = isReliabilityId(value.operationId);
7400
8321
  break;
7401
8322
  case "completion":
@@ -7465,8 +8386,13 @@ function transition(state, outcome, effects = []) {
7465
8386
  assertReliabilityState(state);
7466
8387
  return { state, effects, outcome };
7467
8388
  }
7468
- function unchanged(state, outcome) {
7469
- return transition(cloneState(state), outcome);
8389
+ function unchanged(state, outcome, reason2) {
8390
+ const result = transition(cloneState(state), outcome);
8391
+ if (reason2) result.reason = reason2;
8392
+ return result;
8393
+ }
8394
+ function blocked(state, reason2) {
8395
+ return unchanged(state, "blocked", reason2);
7470
8396
  }
7471
8397
  function advance(value) {
7472
8398
  return value === Number.MAX_SAFE_INTEGER ? null : value + 1;
@@ -7491,7 +8417,10 @@ function updateRunningPhase(state) {
7491
8417
  }
7492
8418
  function handleRequest(state, event) {
7493
8419
  if (state.executionPolicy === "manual" && event.mode === "start")
7494
- return unchanged(state, "blocked");
8420
+ return blocked(
8421
+ state,
8422
+ "start requests are not admitted under manual execution policy; use the ensure/stop commands."
8423
+ );
7495
8424
  const existing = state.requests.find((request) => request.key === event.key);
7496
8425
  if (existing) {
7497
8426
  const consumer = state.consumers.find((candidate) => candidate.id === existing.consumerId);
@@ -7558,10 +8487,10 @@ function handleRequest(state, event) {
7558
8487
  }
7559
8488
  function canExecAfterInterruptedEnsure(state) {
7560
8489
  const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
7561
- return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
8490
+ const quiescent = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
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));
7562
8492
  }
7563
8493
  function handleOperationRequest(state, event) {
7564
- if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
7565
8494
  const previous = state.operationHistory.find((entry) => entry.key === event.key);
7566
8495
  if (previous)
7567
8496
  return unchanged(
@@ -7570,26 +8499,42 @@ function handleOperationRequest(state, event) {
7570
8499
  );
7571
8500
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
7572
8501
  return unchanged(state, "conflict");
7573
- if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
8502
+ if (state.executionPolicy !== "manual" && state.operationHistory.length >= RELIABILITY_MAX_ITEMS)
7574
8503
  return unchanged(state, "blocked");
8504
+ if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
8505
+ return blocked(
8506
+ state,
8507
+ "the latest ensure is INTERRUPTED and drained; exec requires recovering it first (run ensure, or exec with recovery proof)."
8508
+ );
7575
8509
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
7576
8510
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
7577
8511
  if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
7578
- return unchanged(state, "blocked");
8512
+ return blocked(
8513
+ state,
8514
+ `operation ${state.operation.id} is ${state.operation.status} (drained=${state.operation.drained}) and no stop proof, ensure reconciliation, or exec recovery supersedes it.`
8515
+ );
7579
8516
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
7580
- return unchanged(state, "blocked");
8517
+ return blocked(
8518
+ state,
8519
+ `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish.`
8520
+ );
7581
8521
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
7582
- return unchanged(state, "blocked");
7583
- const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
8522
+ return blocked(
8523
+ state,
8524
+ `exec requires a running workspace (runtimeRunning=${event.runtimeRunning}, desired='${state.desired}').`
8525
+ );
8526
+ const rollover = state.executionPolicy === "manual" && state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
7584
8527
  let retired = -1;
7585
8528
  if (rollover) {
7586
- if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
7587
- return unchanged(state, "blocked");
7588
8529
  const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
7589
8530
  retired = state.operationHistory.findIndex(
7590
- (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
8531
+ (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED", "INTERRUPTED"].includes(entry.status)
7591
8532
  );
7592
- if (retired === -1) return unchanged(state, "blocked");
8533
+ if (retired === -1)
8534
+ return blocked(
8535
+ state,
8536
+ `journal holds ${state.operationHistory.length} entries and every retirable entry is the current operation or the latest ensure.`
8537
+ );
7593
8538
  }
7594
8539
  if (state.desired !== "running" || rollover) {
7595
8540
  const revision = advance(state.intentRevision);
@@ -7600,6 +8545,7 @@ function handleOperationRequest(state, event) {
7600
8545
  state.desired = "running";
7601
8546
  state.phase = "queued";
7602
8547
  state.profile = event.profile;
8548
+ if (state.executionPolicy === "capacity-managed") state.admission = "waiting";
7603
8549
  state.stopProof = { workloadsStopped: false, routesRemoved: false };
7604
8550
  state.consumers = [cloneConsumer(event.consumer)];
7605
8551
  state.requests = [
@@ -7625,7 +8571,11 @@ function handleOperationRequest(state, event) {
7625
8571
  profile: event.profile,
7626
8572
  consumer: cloneConsumer(event.consumer)
7627
8573
  });
7628
- return transition(state, "accepted");
8574
+ return transition(
8575
+ state,
8576
+ "accepted",
8577
+ state.executionPolicy === "capacity-managed" ? [effect(state, "request-admission", event.operationId)] : []
8578
+ );
7629
8579
  }
7630
8580
  function handleAdmission(state, event) {
7631
8581
  if (state.executionPolicy === "manual" || state.desired !== "running" && state.desired !== "parked-for-capacity" || state.requests.length === 0) {
@@ -7644,7 +8594,10 @@ function handleDispatch(state) {
7644
8594
  if (state.operation?.drained || state.operation?.status !== "NOT_STARTED")
7645
8595
  return unchanged(state, "stale");
7646
8596
  if (state.desired !== "running" || state.executionPolicy === "capacity-managed" && state.admission !== "admitted" || state.consumers.length === 0) {
7647
- return unchanged(state, "blocked");
8597
+ return blocked(
8598
+ state,
8599
+ `dispatch requires desired 'running' with a registered consumer (desired='${state.desired}', consumers=${state.consumers.length}).`
8600
+ );
7648
8601
  }
7649
8602
  const corrective = state.phase === "recovering" && state.incident !== null;
7650
8603
  state.operation = { ...state.operation, status: "DISPATCH_PENDING", exitCode: null };
@@ -7659,7 +8612,10 @@ function handleDispatchPersisted(state, event) {
7659
8612
  if (state.desired !== "running" || state.executionPolicy === "capacity-managed" && (state.admission !== "admitted" || !state.chargeHeld)) {
7660
8613
  state.operation = setUnknown(state.operation);
7661
8614
  state.chargeHeld = state.executionPolicy === "capacity-managed";
7662
- return transition(state, "blocked");
8615
+ return blocked(
8616
+ state,
8617
+ `dispatch persistence requires desired 'running' (desired='${state.desired}'); the operation outcome is unknown.`
8618
+ );
7663
8619
  }
7664
8620
  if (state.phase === "recovering" && state.incident !== null && state.incident.correctiveActionsTaken >= state.incident.actionLimit) {
7665
8621
  return unchanged(state, "blocked");
@@ -7707,6 +8663,22 @@ function handleInterrupted(state, event) {
7707
8663
  if (state.desired === "running") state.phase = "recovering";
7708
8664
  return transition(state, "accepted");
7709
8665
  }
8666
+ function handleSettle(state, event) {
8667
+ if (state.executionPolicy !== "manual") return unchanged(state, "stale");
8668
+ if (!state.operation || state.operation.id !== event.operationId)
8669
+ return unchanged(state, "stale");
8670
+ if (state.operation.drained && state.operation.status === "INTERRUPTED")
8671
+ return unchanged(state, "joined");
8672
+ if (state.operation.status === "COMPLETED") return unchanged(state, "joined");
8673
+ state.operation = {
8674
+ ...state.operation,
8675
+ status: "INTERRUPTED",
8676
+ drained: true,
8677
+ exitCode: null
8678
+ };
8679
+ if (state.desired === "running") state.phase = "recovering";
8680
+ return transition(state, "accepted");
8681
+ }
7710
8682
  function handleObservation(state, event, nowMs) {
7711
8683
  if (state.desired !== "running") return unchanged(state, "stale");
7712
8684
  const previous = state.observations.find(
@@ -7716,7 +8688,10 @@ function handleObservation(state, event, nowMs) {
7716
8688
  return unchanged(state, "stale");
7717
8689
  }
7718
8690
  if (!previous && state.observations.length >= RELIABILITY_MAX_ITEMS)
7719
- return unchanged(state, "blocked");
8691
+ return blocked(
8692
+ state,
8693
+ `observation set holds ${state.observations.length} distinct capabilities; observations repeat per capability and never retire.`
8694
+ );
7720
8695
  state.observations = state.observations.filter(
7721
8696
  (observation) => observation.capability !== event.observation.capability
7722
8697
  );
@@ -7725,7 +8700,7 @@ function handleObservation(state, event, nowMs) {
7725
8700
  return transition(state, "accepted");
7726
8701
  }
7727
8702
  function handleStop(state) {
7728
- if (state.desired === "stopped-by-user" && state.intentRevision > 0)
8703
+ if (state.desired === "stopped-by-user" && state.phase === "stopping")
7729
8704
  return unchanged(state, "joined");
7730
8705
  const intentRevision = advance(state.intentRevision);
7731
8706
  if (intentRevision === null) return unchanged(state, "blocked");
@@ -7744,7 +8719,10 @@ function handleStop(state) {
7744
8719
  function handleStopProof(state, event) {
7745
8720
  if (state.desired === "running") return unchanged(state, "stale");
7746
8721
  if (state.executionPolicy === "manual" && state.operation && !state.operation.drained && event.workloadsStopped && event.routesRemoved)
7747
- return unchanged(state, "blocked");
8722
+ return blocked(
8723
+ state,
8724
+ `stop proof cannot settle while operation ${state.operation.id} is not drained; wait for its worker or settle the journal.`
8725
+ );
7748
8726
  const previous = { ...state.stopProof };
7749
8727
  if (previous.workloadsStopped === event.workloadsStopped && previous.routesRemoved === event.routesRemoved)
7750
8728
  return unchanged(state, "joined");
@@ -7923,6 +8901,8 @@ function applyReliabilityEvent(state, event, nowMs) {
7923
8901
  return handleCompletion(next, event);
7924
8902
  case "interrupted":
7925
8903
  return handleInterrupted(next, event);
8904
+ case "settle":
8905
+ return handleSettle(next, event);
7926
8906
  case "observation":
7927
8907
  return handleObservation(next, event, nowMs);
7928
8908
  case "stop-proof":
@@ -7942,7 +8922,7 @@ function stepReliability(state, event, nowMs) {
7942
8922
  if (!isReliabilityCounter(nowMs) || nowMs < state.observationsAfterMs)
7943
8923
  throw new Error("Invalid reliability clock.");
7944
8924
  const result = applyReliabilityEvent(state, event, nowMs);
7945
- if (result.outcome === "accepted" && result.state.executionPolicy === "manual" && result.state.operation) {
8925
+ if (result.outcome === "accepted" && result.state.operation) {
7946
8926
  const operation = result.state.operation;
7947
8927
  result.state.operationHistory = result.state.operationHistory.map(
7948
8928
  (entry) => entry.id === operation.id ? { ...entry, ...operation } : entry
@@ -7955,34 +8935,206 @@ function stepReliability(state, event, nowMs) {
7955
8935
  }
7956
8936
 
7957
8937
  // src/core/reliability-operation-store.ts
7958
- var import_node_crypto11 = require("crypto");
7959
- var import_node_fs20 = __toESM(require("fs"));
7960
- 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");
8942
+ function installedCliVersion() {
8943
+ return true ? "0.0.68" : "0.0.0-dev";
8944
+ }
8945
+ function compareCliVersions(left, right) {
8946
+ const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
8947
+ const leftParts = parts(left);
8948
+ const rightParts = parts(right);
8949
+ for (let index = 0; index < 3; index += 1) {
8950
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
8951
+ if (difference !== 0) return difference;
8952
+ }
8953
+ return 0;
8954
+ }
7961
8955
  var MAX_RECORD_BYTES = 1048576;
8956
+ var MAX_RELIABILITY_JOURNALS = 256;
8957
+ var MAX_RELIABILITY_DIRECTORY_ENTRIES = MAX_RELIABILITY_JOURNALS * 2;
7962
8958
  function identityKey(identity2) {
7963
- 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)) {
7964
8960
  throw new Error("Invalid reliability workspace identity.");
7965
8961
  }
7966
- 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");
7967
8963
  }
7968
8964
  function reliabilityOperationPath(identity2) {
7969
- 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`);
7970
8966
  }
7971
8967
  function keys(value, expected) {
7972
8968
  if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
7973
8969
  throw new Error("Reliability record contains unsupported fields.");
7974
8970
  }
7975
8971
  }
7976
- function validate(record5, identity2) {
7977
- keys(record5, ["version", "identity", "revision", "state", "worker", "effectSequence", "outcome"]);
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) {
9062
+ keys(record5, [
9063
+ "version",
9064
+ "identity",
9065
+ "revision",
9066
+ "state",
9067
+ "worker",
9068
+ "effectSequence",
9069
+ "outcome",
9070
+ "result",
9071
+ "writtenByVersion",
9072
+ ...record5.version === 2 ? [
9073
+ "capacity",
9074
+ "enrollment",
9075
+ "activeProfile",
9076
+ "preparation",
9077
+ "phaseSettlement",
9078
+ "startupWitness"
9079
+ ] : []
9080
+ ]);
9081
+ if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
9082
+ throw new Error(
9083
+ `Reliability record was last written by devrouter ${record5.writtenByVersion}, which is newer than the installed ${installedCliVersion()}; upgrade devrouter (devrouter upgrade) before running lifecycle commands.`
9084
+ );
9085
+ }
7978
9086
  keys(record5.identity, ["repoPath", "workspace", "provider"]);
7979
- 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)) {
7980
9088
  throw new Error("Reliability record has invalid version, ownership, or counters.");
7981
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
+ }
7982
9113
  const state = record5.state;
7983
9114
  assertReliabilityState(state);
7984
- if (state.environmentId !== identityKey(identity2) || state.executionPolicy !== "manual") {
7985
- 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.");
7986
9138
  }
7987
9139
  keys(state, [
7988
9140
  "contractVersion",
@@ -8017,6 +9169,52 @@ function validate(record5, identity2) {
8017
9169
  keys(entry, ["key", "mode", "consumerId", "operationId", "profile", "intentRevision"]);
8018
9170
  for (const entry of state.observations)
8019
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
+ }
8020
9218
  if (state.operation) {
8021
9219
  const current = state.operationHistory.find((entry) => entry.id === state.operation?.id);
8022
9220
  if (!current || operationKeys.some(
@@ -8033,6 +9231,12 @@ function validate(record5, identity2) {
8033
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))) {
8034
9232
  throw new Error("Reliability execution outcome is invalid.");
8035
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
+ }
8036
9240
  }
8037
9241
  if (record5.worker !== null) {
8038
9242
  keys(record5.worker, ["id", "operationId", "pid", "birth"]);
@@ -8041,37 +9245,59 @@ function validate(record5, identity2) {
8041
9245
  }
8042
9246
  }
8043
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
+ }
8044
9267
  function readReliabilityOperation(identity2) {
8045
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) {
8046
9276
  let descriptor;
8047
9277
  try {
8048
- descriptor = import_node_fs20.default.openSync(
9278
+ descriptor = import_node_fs24.default.openSync(
8049
9279
  file,
8050
- 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
8051
9281
  );
8052
9282
  } catch (error) {
8053
9283
  if (error.code === "ENOENT") return void 0;
8054
9284
  throw error;
8055
9285
  }
8056
9286
  try {
8057
- const stat = import_node_fs20.default.fstatSync(descriptor);
8058
- if (!stat.isFile() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
8059
- throw new Error("Reliability record is not a bounded private file.");
8060
- }
8061
- const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
8062
- const count = import_node_fs20.default.readSync(descriptor, bytes, 0, bytes.length, 0);
8063
- if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
8064
- const record5 = JSON.parse(
8065
- bytes.subarray(0, count).toString("utf8")
8066
- );
8067
- validate(record5, identity2);
8068
- 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"));
8069
9295
  } finally {
8070
- import_node_fs20.default.closeSync(descriptor);
9296
+ import_node_fs24.default.closeSync(descriptor);
8071
9297
  }
8072
9298
  }
8073
9299
  function persist(record5) {
8074
- validate(record5, record5.identity);
9300
+ validate2(record5, record5.identity);
8075
9301
  const contents = `${JSON.stringify(record5)}
8076
9302
  `;
8077
9303
  if (Buffer.byteLength(contents) > MAX_RECORD_BYTES)
@@ -8082,23 +9308,23 @@ function persist(record5) {
8082
9308
  } catch (error) {
8083
9309
  const reread = readReliabilityOperation(record5.identity);
8084
9310
  if (!reread || JSON.stringify(reread) !== JSON.stringify(record5)) throw error;
8085
- 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);
8086
9312
  try {
8087
- import_node_fs20.default.fsyncSync(descriptor);
9313
+ import_node_fs24.default.fsyncSync(descriptor);
8088
9314
  } finally {
8089
- import_node_fs20.default.closeSync(descriptor);
9315
+ import_node_fs24.default.closeSync(descriptor);
8090
9316
  }
8091
- 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");
8092
9318
  try {
8093
- import_node_fs20.default.fsyncSync(directory);
9319
+ import_node_fs24.default.fsyncSync(directory);
8094
9320
  } finally {
8095
- import_node_fs20.default.closeSync(directory);
9321
+ import_node_fs24.default.closeSync(directory);
8096
9322
  }
8097
9323
  }
8098
9324
  }
8099
9325
  function updateReliabilityOperation(identity2, operation) {
8100
9326
  const file = reliabilityOperationPath(identity2);
8101
- 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 });
8102
9328
  return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
8103
9329
  const record5 = readReliabilityOperation(identity2) ?? {
8104
9330
  version: 1,
@@ -8111,22 +9337,37 @@ function updateReliabilityOperation(identity2, operation) {
8111
9337
  };
8112
9338
  if (record5.revision === Number.MAX_SAFE_INTEGER)
8113
9339
  throw new Error("Reliability record revision is exhausted.");
9340
+ const previousVersion = record5.version;
9341
+ const previousEnrollment = record5.enrollment && { ...record5.enrollment };
8114
9342
  const result = operation(record5);
8115
9343
  if (result && typeof result.then === "function") {
8116
9344
  throw new Error("Reliability record updates must be synchronous.");
8117
9345
  }
8118
- validate(record5, identity2);
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.");
9348
+ record5.writtenByVersion = installedCliVersion();
9349
+ validate2(record5, identity2);
8119
9350
  record5.revision += 1;
8120
9351
  persist(record5);
8121
9352
  return result;
8122
9353
  });
8123
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
+ }
8124
9364
 
8125
9365
  // src/core/reliability-worker.ts
8126
9366
  var import_node_child_process16 = require("child_process");
8127
- var import_node_crypto12 = require("crypto");
8128
- var import_node_fs21 = __toESM(require("fs"));
8129
- 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;
8130
9371
  function workerGroupAbsent(pid) {
8131
9372
  try {
8132
9373
  process.kill(-pid, 0);
@@ -8138,10 +9379,10 @@ function workerGroupAbsent(pid) {
8138
9379
 
8139
9380
  // src/core/traefik-route-health.ts
8140
9381
  var import_node_child_process17 = require("child_process");
8141
- var import_node_fs22 = __toESM(require("fs"));
8142
- var import_node_path27 = __toESM(require("path"));
9382
+ var import_node_fs26 = __toESM(require("fs"));
9383
+ var import_node_path32 = __toESM(require("path"));
8143
9384
  var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
8144
- 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");
8145
9386
  var ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
8146
9387
  var DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
8147
9388
  var DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -8410,7 +9651,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
8410
9651
  throw new Error(
8411
9652
  "Traefik routes did not reload; shared router restart is disabled for this operation."
8412
9653
  );
8413
- import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9654
+ import_node_fs26.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8414
9655
  return withFileLock(
8415
9656
  ROUTER_RELOAD_LOCK_FILE,
8416
9657
  {
@@ -8462,19 +9703,19 @@ function assertTraefikRoutesRemoved(routes) {
8462
9703
 
8463
9704
  // src/core/workspace-ensure.ts
8464
9705
  var import_node_child_process26 = require("child_process");
8465
- var import_node_fs28 = __toESM(require("fs"));
8466
- var import_node_path32 = __toESM(require("path"));
9706
+ var import_node_fs32 = __toESM(require("fs"));
9707
+ var import_node_path37 = __toESM(require("path"));
8467
9708
 
8468
9709
  // src/core/devpod-mutation.ts
8469
9710
  var import_node_child_process18 = require("child_process");
8470
- var import_node_fs23 = __toESM(require("fs"));
8471
- var import_node_path28 = __toESM(require("path"));
8472
- 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");
8473
9714
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
8474
9715
  var DevpodStartPostconditionError = class extends Error {
8475
9716
  };
8476
9717
  function withMutationLock2(activity, target, operation) {
8477
- import_node_fs23.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9718
+ import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8478
9719
  return withFileLockSync(
8479
9720
  DEVPOD_MUTATION_LOCK_FILE,
8480
9721
  {
@@ -8813,7 +10054,7 @@ function transportFailure() {
8813
10054
 
8814
10055
  // src/core/managed-host-preparation.ts
8815
10056
  var import_node_child_process20 = require("child_process");
8816
- var import_node_fs24 = __toESM(require("fs"));
10057
+ var import_node_fs28 = __toESM(require("fs"));
8817
10058
  var PREPARATION_TIMEOUT_MS = 6e4;
8818
10059
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
8819
10060
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -8856,7 +10097,7 @@ function runPreparationCommand(argv, cwd) {
8856
10097
  }
8857
10098
  function readConfigBytes(configPath) {
8858
10099
  try {
8859
- return import_node_fs24.default.readFileSync(configPath);
10100
+ return import_node_fs28.default.readFileSync(configPath);
8860
10101
  } catch {
8861
10102
  return void 0;
8862
10103
  }
@@ -8891,9 +10132,9 @@ async function runManagedHostPreparation(repoPath, config) {
8891
10132
 
8892
10133
  // src/core/managed-post-start.ts
8893
10134
  var import_node_child_process21 = require("child_process");
8894
- var import_node_crypto13 = require("crypto");
8895
- var import_node_fs25 = __toESM(require("fs"));
8896
- 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"));
8897
10138
  var MANAGED_MARKER = "devrouter:managed devcontainer";
8898
10139
  var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
8899
10140
  var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
@@ -8906,7 +10147,7 @@ function renderDeliveryScript(targetPath) {
8906
10147
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
8907
10148
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
8908
10149
  }
8909
- const temporaryPrefix = import_node_path29.default.posix.basename(targetPath);
10150
+ const temporaryPrefix = import_node_path34.default.posix.basename(targetPath);
8910
10151
  return `set -eu
8911
10152
  umask 077
8912
10153
  runtime_root=/tmp/devrouter
@@ -8941,32 +10182,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
8941
10182
  }
8942
10183
  }
8943
10184
  function readRegularFileBytes(filePath) {
8944
- if (!import_node_fs25.default.existsSync(filePath) || !import_node_fs25.default.lstatSync(filePath).isFile()) return void 0;
8945
- 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);
8946
10187
  }
8947
10188
  function readRegularFile(filePath) {
8948
10189
  return readRegularFileBytes(filePath)?.toString("utf-8");
8949
10190
  }
8950
10191
  function adapterFingerprint(adapter) {
8951
- return (0, import_node_crypto13.createHash)("sha256").update(adapter).digest("hex");
10192
+ return (0, import_node_crypto16.createHash)("sha256").update(adapter).digest("hex");
8952
10193
  }
8953
10194
  function resolveProcessHelperPath() {
8954
10195
  const candidates = [
8955
- import_node_path29.default.resolve(__dirname, "..", "bin", "devrouter-process"),
8956
- 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")
8957
10198
  ];
8958
- const helperPath = candidates.find((candidate) => import_node_fs25.default.existsSync(candidate));
10199
+ const helperPath = candidates.find((candidate) => import_node_fs29.default.existsSync(candidate));
8959
10200
  if (!helperPath) {
8960
10201
  throw new Error("Could not locate the packaged devrouter-process helper.");
8961
10202
  }
8962
10203
  return helperPath;
8963
10204
  }
8964
10205
  function resolveManagedPostStartPlan(repoPath) {
8965
- const adapterPath = import_node_path29.default.join(repoPath, MANAGED_ADAPTER_PATH);
10206
+ const adapterPath = import_node_path34.default.join(repoPath, MANAGED_ADAPTER_PATH);
8966
10207
  const adapterBytes = readRegularFileBytes(adapterPath);
8967
10208
  const adapterText = adapterBytes?.toString("utf-8");
8968
- const dockerfilePath = import_node_path29.default.join(repoPath, ".devcontainer", "Dockerfile");
8969
- 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");
8970
10211
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
8971
10212
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
8972
10213
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -9007,7 +10248,7 @@ function resolveManagedPostStartPlan(repoPath) {
9007
10248
  }
9008
10249
  function runManagedPostStart(options) {
9009
10250
  if (options.plan.kind !== "runtime") return;
9010
- const helper = import_node_fs25.default.readFileSync(resolveProcessHelperPath());
10251
+ const helper = import_node_fs29.default.readFileSync(resolveProcessHelperPath());
9011
10252
  deliverRuntimeFile(
9012
10253
  options.container.id,
9013
10254
  RUNTIME_HELPER_PATH,
@@ -9556,14 +10797,14 @@ function record4(value) {
9556
10797
  }
9557
10798
  return value;
9558
10799
  }
9559
- function text2(value) {
10800
+ function text3(value) {
9560
10801
  if (typeof value !== "string" || !value || value.includes("\0")) {
9561
10802
  throw new NetworkInventoryError("Docker network inventory contains an invalid identity.");
9562
10803
  }
9563
10804
  return value;
9564
10805
  }
9565
- function id2(value) {
9566
- const result = text2(value);
10806
+ function id4(value) {
10807
+ const result = text3(value);
9567
10808
  if (!FULL_ID.test(result))
9568
10809
  throw new NetworkInventoryError("Docker inventory requires full object IDs.");
9569
10810
  return result;
@@ -9573,7 +10814,7 @@ function rows(output) {
9573
10814
  return trimmed ? trimmed.split(/\r?\n/).map((line) => JSON.parse(line)) : [];
9574
10815
  }
9575
10816
  function ids(output) {
9576
- const values = output.trim() ? output.trim().split(/\r?\n/).map(id2) : [];
10817
+ const values = output.trim() ? output.trim().split(/\r?\n/).map(id4) : [];
9577
10818
  if (values.length > MAX_IDS || new Set(values).size !== values.length) {
9578
10819
  throw new NetworkInventoryError("Docker network inventory has duplicate or too many objects.");
9579
10820
  }
@@ -9619,7 +10860,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9619
10860
  read2(["info", "--format", '{"id":{{json .ID}},"pools":{{json .DefaultAddressPools}}}'])
9620
10861
  )
9621
10862
  );
9622
- result.daemonId = text2(info.id);
10863
+ result.daemonId = text3(info.id);
9623
10864
  if (options.expectedDaemonId && result.daemonId !== options.expectedDaemonId) {
9624
10865
  throw new NetworkInventoryError("Docker daemon identity changed before network inspection.");
9625
10866
  }
@@ -9627,7 +10868,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9627
10868
  throw new NetworkInventoryError("Docker default pools are unavailable.");
9628
10869
  result.pools = info.pools.map((value) => {
9629
10870
  const pool = record4(value);
9630
- const base = text2(pool.Base);
10871
+ const base = text3(pool.Base);
9631
10872
  if (!Number.isSafeInteger(pool.Size) || Number(pool.Size) < 0 || Number(pool.Size) > 128) {
9632
10873
  throw new NetworkInventoryError("Docker default pool allocation size is invalid.");
9633
10874
  }
@@ -9642,11 +10883,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9642
10883
  throw new NetworkInventoryError("Docker network IPAM or endpoint evidence is unavailable.");
9643
10884
  }
9644
10885
  return {
9645
- id: id2(network.id),
9646
- name: text2(network.name),
9647
- driver: text2(network.driver),
10886
+ id: id4(network.id),
10887
+ name: text3(network.name),
10888
+ driver: text3(network.driver),
9648
10889
  subnets: (network.ipam === null ? [] : network.ipam).map(
9649
- (entry) => text2(record4(entry).Subnet)
10890
+ (entry) => text3(record4(entry).Subnet)
9650
10891
  ),
9651
10892
  activeEndpoints: Number(network.activeEndpoints),
9652
10893
  retainedContainerIds: [],
@@ -9662,11 +10903,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9662
10903
  const inspected = [];
9663
10904
  for (const value of containers) {
9664
10905
  const container = record4(value);
9665
- const containerId = id2(container.id);
10906
+ const containerId = id4(container.id);
9666
10907
  inspected.push(containerId);
9667
10908
  for (const membership of Object.values(record4(container.networks))) {
9668
10909
  const reference = record4(membership);
9669
- const networkId = id2(reference.NetworkID);
10910
+ const networkId = id4(reference.NetworkID);
9670
10911
  const network = byId.get(networkId);
9671
10912
  if (!network)
9672
10913
  throw new NetworkInventoryError("A retained container references an unobserved network.");
@@ -9684,7 +10925,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9684
10925
  "Docker object population changed during network inspection."
9685
10926
  );
9686
10927
  }
9687
- const finalId = text2(JSON.parse(read2(["info", "--format", "{{json .ID}}"])));
10928
+ const finalId = text3(JSON.parse(read2(["info", "--format", "{{json .ID}}"])));
9688
10929
  if (finalId !== result.daemonId)
9689
10930
  throw new NetworkInventoryError("Docker daemon identity changed during inspection.");
9690
10931
  result.status = "complete";
@@ -9697,36 +10938,36 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
9697
10938
 
9698
10939
  // src/core/network-routes.ts
9699
10940
  var import_node_child_process23 = require("child_process");
9700
- var import_node_net2 = require("net");
10941
+ var import_node_net3 = require("net");
9701
10942
 
9702
10943
  // src/core/network-local-daemon.ts
9703
- var import_node_fs26 = __toESM(require("fs"));
9704
- var import_node_path30 = __toESM(require("path"));
10944
+ var import_node_fs30 = __toESM(require("fs"));
10945
+ var import_node_path35 = __toESM(require("path"));
9705
10946
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
9706
10947
  const deadline = Date.now() + 2e3;
9707
10948
  try {
9708
10949
  if (!endpoint.startsWith("unix:///")) return false;
9709
10950
  const socket = endpoint.slice("unix://".length);
9710
- 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");
9711
10952
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
9712
10953
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
9713
10954
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
9714
10955
  const inode = `socket:[${rows2[0][6]}]`;
9715
- const ownNamespace = import_node_fs26.default.readlinkSync(import_node_path30.default.join(procRoot, "self/ns/net"));
9716
- 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));
9717
10958
  if (pids.length > 4096) return false;
9718
10959
  for (const pid of pids) {
9719
10960
  if (Date.now() > deadline) return false;
9720
10961
  try {
9721
- const root = import_node_path30.default.join(procRoot, pid);
9722
- if (import_node_path30.default.basename(import_node_fs26.default.readlinkSync(import_node_path30.default.join(root, "exe"))) !== "dockerd") continue;
9723
- if (import_node_fs26.default.readlinkSync(import_node_path30.default.join(root, "ns/net")) !== ownNamespace) continue;
9724
- 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"));
9725
10966
  if (descriptors.length > 4096) continue;
9726
10967
  if (descriptors.some((fd) => {
9727
10968
  if (Date.now() > deadline) return false;
9728
10969
  try {
9729
- 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;
9730
10971
  } catch {
9731
10972
  return false;
9732
10973
  }
@@ -9752,7 +10993,7 @@ function parseLinuxNetworkRoutes(output) {
9752
10993
  const value = row;
9753
10994
  if (typeof value.dst !== "string") throw new Error();
9754
10995
  if (value.dst === "default") continue;
9755
- 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;
9756
10997
  const cidr = parseIPv4Cidr(value.dst.includes("/") ? value.dst : `${value.dst}/32`);
9757
10998
  if (!cidr || typeof value.dev !== "string" || !value.dev) throw new Error();
9758
10999
  routes.push({ cidr, interface: value.dev, source: "host" });
@@ -9801,10 +11042,10 @@ function inspectLegacyNetworkCapacity(input) {
9801
11042
  }
9802
11043
 
9803
11044
  // src/core/network-managed.ts
9804
- var import_node_crypto16 = require("crypto");
11045
+ var import_node_crypto19 = require("crypto");
9805
11046
 
9806
11047
  // src/core/network-compose.ts
9807
- var import_node_crypto14 = require("crypto");
11048
+ var import_node_crypto17 = require("crypto");
9808
11049
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
9809
11050
  function isRecord4(value) {
9810
11051
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -10610,19 +11851,19 @@ function stableValue(value) {
10610
11851
  }
10611
11852
  function fingerprintComposeNetworkModel(input) {
10612
11853
  const projection = normalizedFingerprintProjection(input);
10613
- 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");
10614
11855
  }
10615
11856
 
10616
11857
  // src/core/network-compose-files.ts
10617
11858
  var import_node_child_process24 = require("child_process");
10618
- var import_node_crypto15 = require("crypto");
10619
- var import_node_fs27 = __toESM(require("fs"));
10620
- 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"));
10621
11862
  var import_yaml4 = __toESM(require("yaml"));
10622
11863
  var OVERLAY = "docker-compose.devrouter-network.yml";
10623
11864
  var MARKER = "# devrouter:managed workspace network";
10624
- var MAX_BYTES = 2 * 1024 * 1024;
10625
- function object2(value) {
11865
+ var MAX_BYTES2 = 2 * 1024 * 1024;
11866
+ function object3(value) {
10626
11867
  if (!value || typeof value !== "object" || Array.isArray(value))
10627
11868
  throw new Error("Network Compose evidence is malformed.");
10628
11869
  return value;
@@ -10630,20 +11871,20 @@ function object2(value) {
10630
11871
  function inspectNetworkComposeFiles(input) {
10631
11872
  const authoredNetworks = {};
10632
11873
  for (const file of input.plan.composeFiles) {
10633
- const stat = import_node_fs27.default.lstatSync(file);
10634
- if (!stat.isFile() || stat.size > MAX_BYTES)
11874
+ const stat = import_node_fs31.default.lstatSync(file);
11875
+ if (!stat.isFile() || stat.size > MAX_BYTES2)
10635
11876
  throw new Error("Network Compose source is unavailable or too large.");
10636
11877
  let source;
10637
11878
  try {
10638
- 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")));
10639
11880
  } catch {
10640
11881
  throw new Error("Network Compose source could not be parsed safely.");
10641
11882
  }
10642
11883
  if (source.networks !== void 0) {
10643
- for (const [name, declaration] of Object.entries(object2(source.networks))) {
11884
+ for (const [name, declaration] of Object.entries(object3(source.networks))) {
10644
11885
  authoredNetworks[name] = {
10645
11886
  ...authoredNetworks[name],
10646
- ...declaration === null ? {} : object2(declaration)
11887
+ ...declaration === null ? {} : object3(declaration)
10647
11888
  };
10648
11889
  }
10649
11890
  }
@@ -10671,13 +11912,13 @@ function inspectNetworkComposeFiles(input) {
10671
11912
  env,
10672
11913
  encoding: "utf8",
10673
11914
  timeout: 1e4,
10674
- maxBuffer: MAX_BYTES
11915
+ maxBuffer: MAX_BYTES2
10675
11916
  }
10676
11917
  );
10677
11918
  if (result.error || result.status !== 0)
10678
11919
  throw new Error("Complete network Compose evidence is unavailable.");
10679
11920
  try {
10680
- return { compose: object2(JSON.parse(result.stdout)), authoredNetworks };
11921
+ return { compose: object3(JSON.parse(result.stdout)), authoredNetworks };
10681
11922
  } catch {
10682
11923
  throw new Error("Network Compose evidence is malformed.");
10683
11924
  }
@@ -10685,7 +11926,7 @@ function inspectNetworkComposeFiles(input) {
10685
11926
  function prepareNetworkComposeFiles(plan, subnet) {
10686
11927
  const overlay = deriveComposeNetworkOverlay(subnet);
10687
11928
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
10688
- const overlayPath = import_node_path31.default.join(plan.composeDirectory, OVERLAY);
11929
+ const overlayPath = import_node_path36.default.join(plan.composeDirectory, OVERLAY);
10689
11930
  const ignored = (0, import_node_child_process24.spawnSync)("git", [
10690
11931
  "-C",
10691
11932
  plan.composeDirectory,
@@ -10697,12 +11938,12 @@ function prepareNetworkComposeFiles(plan, subnet) {
10697
11938
  ]);
10698
11939
  if (ignored.status !== 0)
10699
11940
  throw new Error("Generated network overlay must be ignored before allocation.");
10700
- const bytes = `${MARKER}
11941
+ const bytes2 = `${MARKER}
10701
11942
  ${import_yaml4.default.stringify(overlay.overlay)}`;
10702
11943
  const assertOwnership = () => {
10703
- if (!import_node_fs27.default.existsSync(overlayPath)) return;
10704
- const stat = import_node_fs27.default.lstatSync(overlayPath);
10705
- 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)
10706
11947
  throw new Error("Retained network overlay changed; repair is required.");
10707
11948
  };
10708
11949
  assertOwnership();
@@ -10716,13 +11957,13 @@ ${JSON.stringify(extended.config, null, 2)}
10716
11957
  ...plan,
10717
11958
  contents,
10718
11959
  composeFiles: [...plan.composeFiles, overlayPath],
10719
- effectiveConfigSha256: (0, import_node_crypto15.createHash)("sha256").update(contents).digest("hex")
11960
+ effectiveConfigSha256: (0, import_node_crypto18.createHash)("sha256").update(contents).digest("hex")
10720
11961
  };
10721
11962
  return {
10722
11963
  plan: result,
10723
11964
  write: () => {
10724
11965
  assertOwnership();
10725
- writeFileAtomically(overlayPath, bytes);
11966
+ writeFileAtomically(overlayPath, bytes2);
10726
11967
  }
10727
11968
  };
10728
11969
  }
@@ -11034,7 +12275,7 @@ function createManagedNetworkSession(input) {
11034
12275
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
11035
12276
  );
11036
12277
  const authority = readNetworkOperationAuthority(identity2);
11037
- 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");
11038
12279
  if (!saved) {
11039
12280
  if (policy.status !== "valid")
11040
12281
  throw new Error("New network allocation requires valid machine policy.");
@@ -11218,11 +12459,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
11218
12459
  var POLL_INTERVAL_MS = 1e3;
11219
12460
  function assertOverlay(container, repoPath) {
11220
12461
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
11221
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path32.default.join(repoPath, ".devcontainer"))) {
12462
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))) {
11222
12463
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
11223
12464
  }
11224
12465
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
11225
- const expectedOverlay = import_node_path32.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
12466
+ const expectedOverlay = import_node_path37.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
11226
12467
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
11227
12468
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
11228
12469
  }
@@ -11417,7 +12658,7 @@ function hasExactManagedComposeProject(repoPath, state) {
11417
12658
  }).some((container) => {
11418
12659
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
11419
12660
  return Boolean(
11420
- workingDir && sameWorkspacePath(workingDir, import_node_path32.default.join(repoPath, ".devcontainer"))
12661
+ workingDir && sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))
11421
12662
  );
11422
12663
  });
11423
12664
  } catch {
@@ -11801,7 +13042,7 @@ function resolvePrimaryTarget(repoPath) {
11801
13042
  }
11802
13043
  function isPrimaryCheckout(repoPath) {
11803
13044
  try {
11804
- 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();
11805
13046
  } catch {
11806
13047
  return false;
11807
13048
  }
@@ -11864,8 +13105,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
11864
13105
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
11865
13106
  let devpodId = target.devpodId;
11866
13107
  if (target.kind === "linked") {
11867
- const overlayPath = import_node_path32.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
11868
- 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)) {
11869
13110
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
11870
13111
  }
11871
13112
  }
@@ -11935,7 +13176,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
11935
13176
  }
11936
13177
  const apps = proxyAppsFromConfig(runtime.config);
11937
13178
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
11938
- 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";
11939
13180
  for (const [index, app] of apps.entries()) {
11940
13181
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
11941
13182
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -12186,6 +13427,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12186
13427
  claimLifecycleEffect();
12187
13428
  writeManagedRuntimeState(state);
12188
13429
  capturedStopState = state;
13430
+ clearActiveLifecycleStartupWitness();
12189
13431
  };
12190
13432
  if (options.repair) capture();
12191
13433
  else
@@ -12754,9 +13996,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
12754
13996
  }
12755
13997
  const repairLocked = () => {
12756
13998
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
12757
- import_node_fs28.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13999
+ import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12758
14000
  return withFileLock(
12759
- import_node_path32.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
14001
+ import_node_path37.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
12760
14002
  {
12761
14003
  activity: "Managed runtime repair",
12762
14004
  target: repoPath,
@@ -12828,6 +14070,7 @@ var lockHeld = false;
12828
14070
  var stopProjects = [];
12829
14071
  var stopBaselineState;
12830
14072
  var BUSY_LIFECYCLE_WAIT_MS = 30 * 60 * 1e3;
14073
+ var CAPACITY_CLIENT_WAIT_MS = 30 * 60 * 1e3;
12831
14074
  function matchesFence(record5, fence) {
12832
14075
  return Object.entries(reliabilityFence(record5.state)).every(
12833
14076
  ([key, value]) => value === fence[key]
@@ -12844,6 +14087,7 @@ function claimActiveLifecycleEffect() {
12844
14087
  if (!matchesFence(record5, worker.fence) || worker.kind !== "stop" && record5.worker?.id !== worker.workerId) {
12845
14088
  throw new Error("Lifecycle intent superseded this worker.");
12846
14089
  }
14090
+ if (worker.kind !== "stop") assertCapacityEffect(record5, worker.workerId, Date.now());
12847
14091
  if (record5.effectSequence === Number.MAX_SAFE_INTEGER)
12848
14092
  throw new Error("Lifecycle effect sequence exhausted.");
12849
14093
  record5.effectSequence += 1;
@@ -12861,7 +14105,9 @@ async function withLifecycleOperationLock(repoPath, operation) {
12861
14105
  function stepRecord(record5, event) {
12862
14106
  const transition2 = stepReliability(record5.state, event, Date.now());
12863
14107
  if (!["accepted", "joined"].includes(transition2.outcome))
12864
- throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
14108
+ throw new Error(
14109
+ `Lifecycle transition is ${transition2.outcome}.${transition2.reason ? ` ${transition2.reason}` : ""}`
14110
+ );
12865
14111
  record5.state = transition2.state;
12866
14112
  }
12867
14113
  function reconcileDrained(record5) {
@@ -12896,6 +14142,13 @@ function reconcileDrained(record5) {
12896
14142
  record5.worker = null;
12897
14143
  }
12898
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
+ }
12899
14152
  async function acquireWorkerLock(repoPath, operation) {
12900
14153
  const deadline = Date.now() + 3e4;
12901
14154
  for (; ; ) {
@@ -12952,7 +14205,7 @@ async function executeLifecycleWorker(request, operation) {
12952
14205
  const owned = containers.filter(
12953
14206
  (container) => sameWorkspacePath(
12954
14207
  container.labels["com.docker.compose.project.working_dir"] ?? "",
12955
- import_node_path33.default.join(request.repoPath, ".devcontainer")
14208
+ import_node_path38.default.join(request.repoPath, ".devcontainer")
12956
14209
  )
12957
14210
  );
12958
14211
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -12978,7 +14231,7 @@ async function executeLifecycleWorker(request, operation) {
12978
14231
  }
12979
14232
  });
12980
14233
  }
12981
- function recordLifecycleOutcome(outcome) {
14234
+ function recordLifecycleOutcome(outcome, result) {
12982
14235
  const request = activeWorker;
12983
14236
  if (request?.kind !== "exec") return;
12984
14237
  updateReliabilityOperation(request.identity, (record5) => {
@@ -12996,27 +14249,40 @@ function recordLifecycleOutcome(outcome) {
12996
14249
  };
12997
14250
  stepRecord(record5, event);
12998
14251
  record5.outcome = { ...outcome, operationId: request.operationId };
14252
+ if (result !== void 0) record5.result = result;
12999
14253
  });
13000
14254
  }
13001
- function recordLifecycleCompletion(exitCode) {
14255
+ function recordLifecycleCompletion(exitCode, preparedProfile, result) {
13002
14256
  const request = activeWorker;
13003
14257
  if (!request || request.kind === "stop") return;
13004
14258
  updateReliabilityOperation(request.identity, (record5) => {
13005
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
+ }
13006
14270
  stepRecord(record5, {
13007
14271
  ...request.fence,
13008
14272
  type: "completion",
13009
14273
  operationId: request.operationId,
13010
14274
  exitCode
13011
14275
  });
14276
+ if (result !== void 0) record5.result = result;
13012
14277
  });
13013
14278
  }
13014
- function recordLifecycleUnknown() {
14279
+ function recordLifecycleUnknown(result) {
13015
14280
  const request = activeWorker;
13016
14281
  if (!request || request.kind === "stop") return;
13017
14282
  updateReliabilityOperation(request.identity, (record5) => {
13018
14283
  if (!matchesFence(record5, request.fence)) return;
13019
14284
  stepRecord(record5, { ...request.fence, type: "interrupted", operationId: request.operationId });
14285
+ if (result !== void 0) record5.result = result;
13020
14286
  });
13021
14287
  }
13022
14288
  function proveLifecycleStopped() {
@@ -13040,7 +14306,7 @@ function proveLifecycleStopped() {
13040
14306
  const remaining = inspectWorkspaceContainers().filter(
13041
14307
  (container) => sameWorkspacePath(
13042
14308
  container.labels["com.docker.compose.project.working_dir"] ?? "",
13043
- import_node_path33.default.join(request.repoPath, ".devcontainer")
14309
+ import_node_path38.default.join(request.repoPath, ".devcontainer")
13044
14310
  )
13045
14311
  );
13046
14312
  if (remaining.some(
@@ -13053,16 +14319,48 @@ function proveLifecycleStopped() {
13053
14319
  (route) => sameWorkspacePath(route.repoPath, request.repoPath)
13054
14320
  );
13055
14321
  if (routes.length) throw new Error("Workspace routes remain published after stop.");
13056
- updateReliabilityOperation(request.identity, (record5) => {
14322
+ const settlement = updateReliabilityOperation(request.identity, (record5) => {
13057
14323
  if (!matchesFence(record5, request.fence) || record5.worker)
13058
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
+ }
13059
14329
  stepRecord(record5, {
13060
14330
  ...request.fence,
13061
14331
  type: "stop-proof",
13062
14332
  workloadsStopped: true,
13063
14333
  routesRemoved: true
13064
14334
  });
14335
+ return void 0;
13065
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
+ }
13066
14364
  };
13067
14365
  if (stopBaselineState)
13068
14366
  withMutationLock("Settle retained stop", stopBaselineState.repoPath, settle);
@@ -13198,7 +14496,7 @@ async function devpodExecOutcome(repoPath, command) {
13198
14496
  }
13199
14497
  assertRunningDevpod(devpod.id, repoPath);
13200
14498
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
13201
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto17.randomUUID)()}__:`;
14499
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto21.randomUUID)()}__:`;
13202
14500
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
13203
14501
  const literalCommand = command.map(quotePosixArg).join(" ");
13204
14502
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -13388,11 +14686,11 @@ async function devpodExecOutcome(repoPath, command) {
13388
14686
 
13389
14687
  // src/core/workspace-lifecycle.ts
13390
14688
  var import_node_child_process29 = require("child_process");
13391
- var import_node_fs30 = __toESM(require("fs"));
13392
- var import_node_path34 = __toESM(require("path"));
14689
+ var import_node_fs34 = __toESM(require("fs"));
14690
+ var import_node_path39 = __toESM(require("path"));
13393
14691
 
13394
14692
  // src/core/route-state.ts
13395
- var import_node_fs29 = __toESM(require("fs"));
14693
+ var import_node_fs33 = __toESM(require("fs"));
13396
14694
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
13397
14695
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
13398
14696
  }
@@ -13597,20 +14895,26 @@ process.once("message", async (message) => {
13597
14895
  request.command ?? [],
13598
14896
  request.retainedExecProof
13599
14897
  ) : await devpodExecOutcome(request.repoPath, request.command ?? []);
13600
- recordLifecycleOutcome(outcome);
14898
+ recordLifecycleOutcome(outcome, { ok: true, value: outcome });
13601
14899
  return outcome;
13602
14900
  }
13603
14901
  const ensured = await workspaceEnsure(request.repoPath, request.options);
13604
14902
  recordLifecycleCompletion(
13605
- 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 }
13606
14906
  );
13607
14907
  return ensured;
13608
14908
  });
13609
14909
  result = { ok: true, value };
13610
14910
  } catch (error) {
13611
14911
  try {
13612
- if (error instanceof ExecutionOutcomeError) recordLifecycleOutcome(error.outcome);
13613
- 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);
13614
14918
  } catch {
13615
14919
  }
13616
14920
  result = { ok: false, message: error instanceof Error ? error.message : String(error) };