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