@impz/testing 6.6.1 → 6.7.1

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.
Files changed (2) hide show
  1. package/dist/index.js +641 -529
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -18923,12 +18923,12 @@ var BACKUP_TREE = {
18923
18923
  buildImageDir: (digest) => join("images", toDigestHex(digest))
18924
18924
  };
18925
18925
 
18926
- // packages/daemon/testing/start-test-impd.ts
18926
+ // packages/daemon/src/test-utils/start-test-impd.ts
18927
18927
  import { copyFileSync as copyFileSync5, mkdirSync as mkdirSync31, mkdtempSync, writeFileSync as writeFileSync16 } from "fs";
18928
18928
  import { rm as rm2 } from "fs/promises";
18929
18929
  import { tmpdir } from "os";
18930
18930
 
18931
- // packages/test-utils/src/claim-free-ports.ts
18931
+ // packages/shared/src/test-utils/claim-free-ports.ts
18932
18932
  import { onTestFinished } from "bun:test";
18933
18933
  import { randomInt } from "crypto";
18934
18934
  import { existsSync, readFileSync } from "fs";
@@ -18991,449 +18991,13 @@ function readBusyPorts() {
18991
18991
  return busy;
18992
18992
  }
18993
18993
 
18994
- // packages/daemon/testing/start-test-impd.ts
18994
+ // packages/daemon/src/test-utils/start-test-impd.ts
18995
18995
  import { createORPCClient as createORPCClient2 } from "@orpc/client";
18996
18996
  import { RPCLink } from "@orpc/client/fetch";
18997
18997
  import invariant7 from "tiny-invariant";
18998
18998
 
18999
18999
  // packages/daemon/src/config.ts
19000
19000
  import { join as join4 } from "path";
19001
- import * as z53 from "zod";
19002
- // packages/shared/src/format-bytes.ts
19003
- var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
19004
- function formatBytes(bytes) {
19005
- let value = bytes;
19006
- let unit = 0;
19007
- while (value >= 1024 && unit < UNITS.length - 1) {
19008
- value /= 1024;
19009
- unit += 1;
19010
- }
19011
- const digits = unit === 0 || value >= 100 ? 0 : 1;
19012
- return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
19013
- }
19014
- // packages/shared/src/merge-bytes.ts
19015
- function mergeBytes(chunks) {
19016
- const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
19017
- let offset = 0;
19018
- for (const chunk of chunks) {
19019
- merged.set(chunk, offset);
19020
- offset += chunk.byteLength;
19021
- }
19022
- return merged;
19023
- }
19024
- // packages/shared/src/to-plain-octets.ts
19025
- var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
19026
- function toPlainOctets(entry) {
19027
- const groups = DOTTED_ENTRY.exec(entry)?.groups;
19028
- if (groups === undefined) {
19029
- return entry;
19030
- }
19031
- const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
19032
- return `${octets.join(".")}${groups["prefix"] ?? ""}`;
19033
- }
19034
- // packages/daemon/src/host-net/net/addressing.ts
19035
- import { IPv4 as IPv43 } from "ipaddr.js";
19036
-
19037
- // packages/daemon/src/host-net/net/addressing6.ts
19038
- import { IPv6 as IPv62 } from "ipaddr.js";
19039
-
19040
- // packages/daemon/src/host-net/net/parse-address.ts
19041
- import { IPv4, IPv6 } from "ipaddr.js";
19042
- function parseAddress(text) {
19043
- if (IPv4.isValidFourPartDecimal(text)) {
19044
- return IPv4.parse(text);
19045
- }
19046
- if (text.includes("%")) {
19047
- return null;
19048
- }
19049
- const hex = toHexGroups(text);
19050
- return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
19051
- }
19052
- function toHexGroups(text) {
19053
- const split = text.lastIndexOf(":");
19054
- const tail = text.slice(split + 1);
19055
- if (!tail.includes(".")) {
19056
- return text;
19057
- }
19058
- if (!IPv4.isValidFourPartDecimal(tail)) {
19059
- return null;
19060
- }
19061
- const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
19062
- return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
19063
- }
19064
- function toGroup(high, low) {
19065
- return (high * 256 + low).toString(16);
19066
- }
19067
-
19068
- // packages/daemon/src/host-net/net/parse-cidr.ts
19069
- import { IPv4 as IPv42 } from "ipaddr.js";
19070
- function parseCidr(text) {
19071
- const [addressText = "", prefix, ...rest] = text.split("/");
19072
- const address = parseAddress(addressText);
19073
- if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
19074
- return null;
19075
- }
19076
- const maxBits = address instanceof IPv42 ? 32 : 128;
19077
- const bits = prefix === undefined ? maxBits : Number(prefix);
19078
- return bits > maxBits ? null : { address, bits };
19079
- }
19080
-
19081
- // packages/daemon/src/host-net/net/addressing6.ts
19082
- var GROUPS = 8;
19083
- var GROUP_BITS = 16n;
19084
- var PREFIX_LENGTH = 64;
19085
- var GATEWAY_IP6 = "fe80::1";
19086
- function parseIpv6(text) {
19087
- const address = parseAddress(text);
19088
- return address instanceof IPv62 ? toBigint(address) : null;
19089
- }
19090
- function formatIpv6(address) {
19091
- const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
19092
- return new IPv62(groups).toRFC5952String();
19093
- }
19094
- function formatCidr6(text) {
19095
- const parsed = parseCidr(text);
19096
- if (parsed === null || !(parsed.address instanceof IPv62)) {
19097
- return null;
19098
- }
19099
- const hostBits = BigInt(128 - parsed.bits);
19100
- const network = toBigint(parsed.address) >> hostBits << hostBits;
19101
- return `${formatIpv6(network)}/${String(parsed.bits)}`;
19102
- }
19103
- function parsePrefix64(cidr) {
19104
- const parsed = parseCidr(cidr);
19105
- const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
19106
- if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
19107
- return null;
19108
- }
19109
- return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
19110
- }
19111
- function buildUlaPrefix(random) {
19112
- if (random.length < 5) {
19113
- throw new Error("a ULA global ID needs 5 random bytes");
19114
- }
19115
- const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
19116
- const network = (0xfdn << 40n) + globalId << 80n;
19117
- return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
19118
- }
19119
- function findMappedIpv4(address) {
19120
- const parsed = parseAddress(address);
19121
- return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
19122
- }
19123
- function deriveGuestIp6(prefix, guestIpv4) {
19124
- return formatIpv6(prefix.network + BigInt(guestIpv4));
19125
- }
19126
- function toBigint(address) {
19127
- return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
19128
- }
19129
-
19130
- // packages/daemon/src/host-net/net/addressing.ts
19131
- var SLOT_SIZE = 4;
19132
- var SLOT_PREFIX_LENGTH = 30;
19133
- function parseSubnet(cidr) {
19134
- const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
19135
- if (parsed === null || !(parsed.address instanceof IPv43)) {
19136
- throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
19137
- }
19138
- if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
19139
- throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
19140
- }
19141
- const network = toNumber(parsed.address);
19142
- if (network % 2 ** (32 - parsed.bits) !== 0) {
19143
- throw new Error(`subnet has host bits set: ${cidr}`);
19144
- }
19145
- return { network, prefixLength: parsed.bits };
19146
- }
19147
- var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
19148
- var TAILNET_SIZE = 2 ** 22;
19149
- function isTailnetOverlap(subnet) {
19150
- const start = subnet.network;
19151
- const end = start + 2 ** (32 - subnet.prefixLength);
19152
- return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
19153
- }
19154
- function countSlots(subnet) {
19155
- return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
19156
- }
19157
- function deriveSlotAddress(slot, plan) {
19158
- if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
19159
- throw new RangeError(`slot ${String(slot)} is outside the subnet`);
19160
- }
19161
- const base = plan.subnet.network + slot * SLOT_SIZE;
19162
- const guest = base + 2;
19163
- return {
19164
- slot,
19165
- tap: `imp${String(slot)}`,
19166
- hostIp: formatIpv4(base + 1),
19167
- guestIp: formatIpv4(guest),
19168
- prefixLength: SLOT_PREFIX_LENGTH,
19169
- netmask: "255.255.255.252",
19170
- guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
19171
- hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
19172
- guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
19173
- tailnetPort: plan.portBase + slot
19174
- };
19175
- }
19176
- function parseIpv4(text, allowMapped = false) {
19177
- const address = parseAddress(text);
19178
- if (address instanceof IPv43) {
19179
- return toNumber(address);
19180
- }
19181
- return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
19182
- }
19183
- function findPeerSlot(peer, local, subnet) {
19184
- const peerIp = parseIpv4(peer, true);
19185
- const localIp = parseIpv4(local, true);
19186
- if (peerIp === null || localIp === null) {
19187
- return null;
19188
- }
19189
- const offset = peerIp - subnet.network;
19190
- if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
19191
- return null;
19192
- }
19193
- return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
19194
- }
19195
- function findGuestSlot(address, subnet) {
19196
- const ip = parseIpv4(address, true);
19197
- const offset = ip === null ? -1 : ip - subnet.network;
19198
- if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
19199
- return null;
19200
- }
19201
- return (offset - 2) / SLOT_SIZE;
19202
- }
19203
- function formatSubnet(subnet) {
19204
- return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
19205
- }
19206
- function formatCidr4(text) {
19207
- const parsed = parseCidr(toPlainOctets(text));
19208
- if (parsed === null || !(parsed.address instanceof IPv43)) {
19209
- return null;
19210
- }
19211
- const ip = toNumber(parsed.address);
19212
- return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
19213
- }
19214
- function toNumber(address) {
19215
- return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
19216
- }
19217
- function formatIpv4(address) {
19218
- return splitOctets(address).join(".");
19219
- }
19220
- function splitOctets(address) {
19221
- return [
19222
- Math.floor(address / 2 ** 24) % 256,
19223
- Math.floor(address / 2 ** 16) % 256,
19224
- Math.floor(address / 2 ** 8) % 256,
19225
- address % 256
19226
- ];
19227
- }
19228
- function toHexByte(octet) {
19229
- return octet.toString(16).padStart(2, "0");
19230
- }
19231
-
19232
- // packages/daemon/src/host-net/net/ipv6-plan.ts
19233
- import { randomBytes } from "crypto";
19234
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
19235
- import { dirname, join as join2 } from "path";
19236
-
19237
- // packages/daemon/src/platform/process/get-error-message.ts
19238
- function getErrorMessage(error) {
19239
- return error instanceof Error ? error.message : String(error);
19240
- }
19241
-
19242
- // packages/daemon/src/platform/process/get-impd-logger.ts
19243
- import { getLogger } from "@logtape/logtape";
19244
- function getImpdLogger(area) {
19245
- return getLogger(["impd", area]);
19246
- }
19247
-
19248
- // packages/daemon/src/platform/process/run-command.ts
19249
- async function runCommand(argv, options = {}) {
19250
- const child = Bun.spawn([...argv], {
19251
- stdin: "ignore",
19252
- stdout: "pipe",
19253
- stderr: "pipe",
19254
- env: options.env === undefined ? process.env : { ...options.env },
19255
- ...options.signal !== undefined && { signal: options.signal }
19256
- });
19257
- const [stdout, stderr, exitCode] = await Promise.all([
19258
- new Response(child.stdout).text(),
19259
- new Response(child.stderr).text(),
19260
- child.exited
19261
- ]);
19262
- return { exitCode, stdout, stderr };
19263
- }
19264
- async function runChecked(argv, options = {}, run = runCommand) {
19265
- const result = await run(argv, options);
19266
- if (result.exitCode !== 0) {
19267
- throw buildExitError(argv, result);
19268
- }
19269
- return result.stdout;
19270
- }
19271
- function buildExitError(argv, result) {
19272
- const output = result.stderr.trim() || (result.stdout ?? "").trim();
19273
- return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
19274
- }
19275
-
19276
- // packages/daemon/src/host-net/net/nat66-ruleset.ts
19277
- var NAT66_TABLE = "ip6 imp_nat66";
19278
- function buildNat66Ruleset(prefix, uplink) {
19279
- return [
19280
- `table ${NAT66_TABLE} {}`,
19281
- `delete table ${NAT66_TABLE}`,
19282
- `table ${NAT66_TABLE} {`,
19283
- " chain postrouting {",
19284
- " type nat hook postrouting priority srcnat; policy accept;",
19285
- ` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
19286
- " }",
19287
- "}",
19288
- ""
19289
- ].join(`
19290
- `);
19291
- }
19292
- function buildNat66Removal() {
19293
- return `table ${NAT66_TABLE} {}
19294
- delete table ${NAT66_TABLE}
19295
- `;
19296
- }
19297
-
19298
- // packages/daemon/src/host-net/net/ipv6-plan.ts
19299
- function parseIpv6Setting(text) {
19300
- if (text === "auto" || text === "off") {
19301
- return { kind: text };
19302
- }
19303
- const prefix = parsePrefix64(text);
19304
- if (prefix === null) {
19305
- throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
19306
- }
19307
- return { kind: "routed", prefix };
19308
- }
19309
- async function resolveIpv6Plan(setting, deps) {
19310
- const stopIpv6 = async (reason) => {
19311
- await removeNat66(deps);
19312
- getImpdLogger("ipv6").info("off ({reason})", { reason });
19313
- return null;
19314
- };
19315
- if (setting.kind === "off") {
19316
- return stopIpv6("IMP_SUBNET6=off");
19317
- }
19318
- const uplink = await deps.readDefaultRoute();
19319
- if (setting.kind === "auto" && uplink === null) {
19320
- return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
19321
- }
19322
- const missing = await deps.checkHostRules();
19323
- if (missing !== null) {
19324
- return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
19325
- }
19326
- if (setting.kind === "routed") {
19327
- await removeNat66(deps);
19328
- getImpdLogger("ipv6").info("{text}, routed{route}", {
19329
- text: setting.prefix.text,
19330
- route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
19331
- });
19332
- return { prefix: setting.prefix, nat66: false, uplink };
19333
- }
19334
- const prefix = deps.readUlaPrefix();
19335
- try {
19336
- await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
19337
- } catch (error) {
19338
- return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
19339
- }
19340
- getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
19341
- text: prefix.text,
19342
- uplink: uplink ?? ""
19343
- });
19344
- return { prefix, nat66: true, uplink };
19345
- }
19346
- async function removeNat66(deps) {
19347
- try {
19348
- await deps.runNft(buildNat66Removal());
19349
- } catch {}
19350
- }
19351
- var HOST_RULES6 = [
19352
- ["filter", "-A INPUT -i imp+ -j DROP"],
19353
- ["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
19354
- ["filter", "-A FORWARD -o imp+ -j DROP"],
19355
- ["filter", "-A FORWARD -i imp+ -j DROP"],
19356
- ["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
19357
- ];
19358
- var HOST_SYSCTLS6 = [
19359
- ["default/accept_ra", "0"],
19360
- ["default/accept_redirects", "0"],
19361
- ["all/forwarding", "1"]
19362
- ];
19363
- async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
19364
- for (const table of ["filter", "raw"]) {
19365
- const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
19366
- const rules = new Set(result.stdout.split(`
19367
- `).map((line) => line.trim()));
19368
- const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
19369
- if (result.exitCode !== 0) {
19370
- return `ip6tables -t ${table}: ${result.stderr.trim()}`;
19371
- }
19372
- if (missing !== undefined) {
19373
- return `no ip6tables -t ${table} ${missing[1]}`;
19374
- }
19375
- }
19376
- for (const [key, wanted] of HOST_SYSCTLS6) {
19377
- const value = readSysctl(join2(procSys, key));
19378
- if (value !== wanted) {
19379
- return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
19380
- }
19381
- }
19382
- return null;
19383
- }
19384
- function readSysctl(path) {
19385
- try {
19386
- return readFileSync2(path, "utf8").trim();
19387
- } catch {
19388
- return null;
19389
- }
19390
- }
19391
- function readOrCreateUlaPrefix(path) {
19392
- if (existsSync2(path)) {
19393
- const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
19394
- if (prefix !== null) {
19395
- return prefix;
19396
- }
19397
- }
19398
- const prefix = buildUlaPrefix(randomBytes(5));
19399
- mkdirSync2(dirname(path), { recursive: true });
19400
- writeFileSync(path, `${prefix.text}
19401
- `);
19402
- return prefix;
19403
- }
19404
-
19405
- // packages/daemon/src/host-net/net/range-matcher.ts
19406
- import { IPv4 as IPv44 } from "ipaddr.js";
19407
- function buildRangeMatcher(ranges) {
19408
- const ranges4 = [];
19409
- const ranges6 = [];
19410
- for (const range of ranges) {
19411
- const cidr = parseCidr(range);
19412
- if (cidr === null) {
19413
- throw new Error(`not an address range: ${range}`);
19414
- }
19415
- if (cidr.address instanceof IPv44) {
19416
- ranges4.push([cidr.address, cidr.bits]);
19417
- } else {
19418
- ranges6.push([cidr.address, cidr.bits]);
19419
- }
19420
- }
19421
- const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
19422
- const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
19423
- return (address) => {
19424
- const ip = parseAddress(address);
19425
- if (ip === null) {
19426
- return false;
19427
- }
19428
- if (ip instanceof IPv44) {
19429
- return isIn4(ip);
19430
- }
19431
- return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
19432
- };
19433
- }
19434
-
19435
- // packages/daemon/src/identity/auth/parse-tailnet-rules.ts
19436
- import * as z48 from "zod";
19437
19001
  // packages/api/src/schemas/lease-schema.ts
19438
19002
  import * as z2 from "zod";
19439
19003
 
@@ -21266,6 +20830,443 @@ function hasScope(granted, needed) {
21266
20830
  const neededRank = ScopeSchema.options.indexOf(needed);
21267
20831
  return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
21268
20832
  }
20833
+ // packages/daemon/src/config.ts
20834
+ import * as z53 from "zod";
20835
+ // packages/shared/src/format-bytes.ts
20836
+ var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
20837
+ function formatBytes(bytes) {
20838
+ let value = bytes;
20839
+ let unit = 0;
20840
+ while (value >= 1024 && unit < UNITS.length - 1) {
20841
+ value /= 1024;
20842
+ unit += 1;
20843
+ }
20844
+ const digits = unit === 0 || value >= 100 ? 0 : 1;
20845
+ return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
20846
+ }
20847
+ // packages/shared/src/get-error-message.ts
20848
+ function getErrorMessage(error) {
20849
+ return error instanceof Error ? error.message : String(error);
20850
+ }
20851
+ // packages/shared/src/merge-bytes.ts
20852
+ function mergeBytes(chunks) {
20853
+ const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
20854
+ let offset = 0;
20855
+ for (const chunk of chunks) {
20856
+ merged.set(chunk, offset);
20857
+ offset += chunk.byteLength;
20858
+ }
20859
+ return merged;
20860
+ }
20861
+ // packages/shared/src/to-plain-octets.ts
20862
+ var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
20863
+ function toPlainOctets(entry) {
20864
+ const groups = DOTTED_ENTRY.exec(entry)?.groups;
20865
+ if (groups === undefined) {
20866
+ return entry;
20867
+ }
20868
+ const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
20869
+ return `${octets.join(".")}${groups["prefix"] ?? ""}`;
20870
+ }
20871
+ // packages/daemon/src/host-net/net/addressing.ts
20872
+ import { IPv4 as IPv43 } from "ipaddr.js";
20873
+
20874
+ // packages/daemon/src/host-net/net/addressing6.ts
20875
+ import { IPv6 as IPv62 } from "ipaddr.js";
20876
+
20877
+ // packages/daemon/src/host-net/net/parse-address.ts
20878
+ import { IPv4, IPv6 } from "ipaddr.js";
20879
+ function parseAddress(text) {
20880
+ if (IPv4.isValidFourPartDecimal(text)) {
20881
+ return IPv4.parse(text);
20882
+ }
20883
+ if (text.includes("%")) {
20884
+ return null;
20885
+ }
20886
+ const hex = toHexGroups(text);
20887
+ return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
20888
+ }
20889
+ function toHexGroups(text) {
20890
+ const split = text.lastIndexOf(":");
20891
+ const tail = text.slice(split + 1);
20892
+ if (!tail.includes(".")) {
20893
+ return text;
20894
+ }
20895
+ if (!IPv4.isValidFourPartDecimal(tail)) {
20896
+ return null;
20897
+ }
20898
+ const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
20899
+ return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
20900
+ }
20901
+ function toGroup(high, low) {
20902
+ return (high * 256 + low).toString(16);
20903
+ }
20904
+
20905
+ // packages/daemon/src/host-net/net/parse-cidr.ts
20906
+ import { IPv4 as IPv42 } from "ipaddr.js";
20907
+ function parseCidr(text) {
20908
+ const [addressText = "", prefix, ...rest] = text.split("/");
20909
+ const address = parseAddress(addressText);
20910
+ if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
20911
+ return null;
20912
+ }
20913
+ const maxBits = address instanceof IPv42 ? 32 : 128;
20914
+ const bits = prefix === undefined ? maxBits : Number(prefix);
20915
+ return bits > maxBits ? null : { address, bits };
20916
+ }
20917
+
20918
+ // packages/daemon/src/host-net/net/addressing6.ts
20919
+ var GROUPS = 8;
20920
+ var GROUP_BITS = 16n;
20921
+ var PREFIX_LENGTH = 64;
20922
+ var GATEWAY_IP6 = "fe80::1";
20923
+ function parseIpv6(text) {
20924
+ const address = parseAddress(text);
20925
+ return address instanceof IPv62 ? toBigint(address) : null;
20926
+ }
20927
+ function formatIpv6(address) {
20928
+ const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
20929
+ return new IPv62(groups).toRFC5952String();
20930
+ }
20931
+ function formatCidr6(text) {
20932
+ const parsed = parseCidr(text);
20933
+ if (parsed === null || !(parsed.address instanceof IPv62)) {
20934
+ return null;
20935
+ }
20936
+ const hostBits = BigInt(128 - parsed.bits);
20937
+ const network = toBigint(parsed.address) >> hostBits << hostBits;
20938
+ return `${formatIpv6(network)}/${String(parsed.bits)}`;
20939
+ }
20940
+ function parsePrefix64(cidr) {
20941
+ const parsed = parseCidr(cidr);
20942
+ const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
20943
+ if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
20944
+ return null;
20945
+ }
20946
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20947
+ }
20948
+ function buildUlaPrefix(random) {
20949
+ if (random.length < 5) {
20950
+ throw new Error("a ULA global ID needs 5 random bytes");
20951
+ }
20952
+ const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
20953
+ const network = (0xfdn << 40n) + globalId << 80n;
20954
+ return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
20955
+ }
20956
+ function findMappedIpv4(address) {
20957
+ const parsed = parseAddress(address);
20958
+ return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
20959
+ }
20960
+ function deriveGuestIp6(prefix, guestIpv4) {
20961
+ return formatIpv6(prefix.network + BigInt(guestIpv4));
20962
+ }
20963
+ function toBigint(address) {
20964
+ return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
20965
+ }
20966
+
20967
+ // packages/daemon/src/host-net/net/addressing.ts
20968
+ var SLOT_SIZE = 4;
20969
+ var SLOT_PREFIX_LENGTH = 30;
20970
+ function parseSubnet(cidr) {
20971
+ const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
20972
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
20973
+ throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
20974
+ }
20975
+ if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
20976
+ throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
20977
+ }
20978
+ const network = toNumber(parsed.address);
20979
+ if (network % 2 ** (32 - parsed.bits) !== 0) {
20980
+ throw new Error(`subnet has host bits set: ${cidr}`);
20981
+ }
20982
+ return { network, prefixLength: parsed.bits };
20983
+ }
20984
+ var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
20985
+ var TAILNET_SIZE = 2 ** 22;
20986
+ function isTailnetOverlap(subnet) {
20987
+ const start = subnet.network;
20988
+ const end = start + 2 ** (32 - subnet.prefixLength);
20989
+ return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
20990
+ }
20991
+ function countSlots(subnet) {
20992
+ return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
20993
+ }
20994
+ function deriveSlotAddress(slot, plan) {
20995
+ if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
20996
+ throw new RangeError(`slot ${String(slot)} is outside the subnet`);
20997
+ }
20998
+ const base = plan.subnet.network + slot * SLOT_SIZE;
20999
+ const guest = base + 2;
21000
+ return {
21001
+ slot,
21002
+ tap: `imp${String(slot)}`,
21003
+ hostIp: formatIpv4(base + 1),
21004
+ guestIp: formatIpv4(guest),
21005
+ prefixLength: SLOT_PREFIX_LENGTH,
21006
+ netmask: "255.255.255.252",
21007
+ guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
21008
+ hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
21009
+ guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
21010
+ tailnetPort: plan.portBase + slot
21011
+ };
21012
+ }
21013
+ function parseIpv4(text, allowMapped = false) {
21014
+ const address = parseAddress(text);
21015
+ if (address instanceof IPv43) {
21016
+ return toNumber(address);
21017
+ }
21018
+ return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
21019
+ }
21020
+ function findPeerSlot(peer, local, subnet) {
21021
+ const peerIp = parseIpv4(peer, true);
21022
+ const localIp = parseIpv4(local, true);
21023
+ if (peerIp === null || localIp === null) {
21024
+ return null;
21025
+ }
21026
+ const offset = peerIp - subnet.network;
21027
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21028
+ return null;
21029
+ }
21030
+ return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
21031
+ }
21032
+ function findGuestSlot(address, subnet) {
21033
+ const ip = parseIpv4(address, true);
21034
+ const offset = ip === null ? -1 : ip - subnet.network;
21035
+ if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
21036
+ return null;
21037
+ }
21038
+ return (offset - 2) / SLOT_SIZE;
21039
+ }
21040
+ function formatSubnet(subnet) {
21041
+ return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
21042
+ }
21043
+ function formatCidr4(text) {
21044
+ const parsed = parseCidr(toPlainOctets(text));
21045
+ if (parsed === null || !(parsed.address instanceof IPv43)) {
21046
+ return null;
21047
+ }
21048
+ const ip = toNumber(parsed.address);
21049
+ return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
21050
+ }
21051
+ function toNumber(address) {
21052
+ return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
21053
+ }
21054
+ function formatIpv4(address) {
21055
+ return splitOctets(address).join(".");
21056
+ }
21057
+ function splitOctets(address) {
21058
+ return [
21059
+ Math.floor(address / 2 ** 24) % 256,
21060
+ Math.floor(address / 2 ** 16) % 256,
21061
+ Math.floor(address / 2 ** 8) % 256,
21062
+ address % 256
21063
+ ];
21064
+ }
21065
+ function toHexByte(octet) {
21066
+ return octet.toString(16).padStart(2, "0");
21067
+ }
21068
+
21069
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21070
+ import { randomBytes } from "crypto";
21071
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
21072
+ import { dirname, join as join2 } from "path";
21073
+
21074
+ // packages/daemon/src/platform/process/get-impd-logger.ts
21075
+ import { getLogger } from "@logtape/logtape";
21076
+ function getImpdLogger(area) {
21077
+ return getLogger(["impd", area]);
21078
+ }
21079
+
21080
+ // packages/daemon/src/platform/process/run-command.ts
21081
+ async function runCommand(argv, options = {}) {
21082
+ const child = Bun.spawn([...argv], {
21083
+ stdin: "ignore",
21084
+ stdout: "pipe",
21085
+ stderr: "pipe",
21086
+ env: options.env === undefined ? process.env : { ...options.env },
21087
+ ...options.signal !== undefined && { signal: options.signal }
21088
+ });
21089
+ const [stdout, stderr, exitCode] = await Promise.all([
21090
+ new Response(child.stdout).text(),
21091
+ new Response(child.stderr).text(),
21092
+ child.exited
21093
+ ]);
21094
+ return { exitCode, stdout, stderr };
21095
+ }
21096
+ async function runChecked(argv, options = {}, run = runCommand) {
21097
+ const result = await run(argv, options);
21098
+ if (result.exitCode !== 0) {
21099
+ throw buildExitError(argv, result);
21100
+ }
21101
+ return result.stdout;
21102
+ }
21103
+ function buildExitError(argv, result) {
21104
+ const output = result.stderr.trim() || (result.stdout ?? "").trim();
21105
+ return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
21106
+ }
21107
+
21108
+ // packages/daemon/src/host-net/net/nat66-ruleset.ts
21109
+ var NAT66_TABLE = "ip6 imp_nat66";
21110
+ function buildNat66Ruleset(prefix, uplink) {
21111
+ return [
21112
+ `table ${NAT66_TABLE} {}`,
21113
+ `delete table ${NAT66_TABLE}`,
21114
+ `table ${NAT66_TABLE} {`,
21115
+ " chain postrouting {",
21116
+ " type nat hook postrouting priority srcnat; policy accept;",
21117
+ ` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
21118
+ " }",
21119
+ "}",
21120
+ ""
21121
+ ].join(`
21122
+ `);
21123
+ }
21124
+ function buildNat66Removal() {
21125
+ return `table ${NAT66_TABLE} {}
21126
+ delete table ${NAT66_TABLE}
21127
+ `;
21128
+ }
21129
+
21130
+ // packages/daemon/src/host-net/net/ipv6-plan.ts
21131
+ function parseIpv6Setting(text) {
21132
+ if (text === "auto" || text === "off") {
21133
+ return { kind: text };
21134
+ }
21135
+ const prefix = parsePrefix64(text);
21136
+ if (prefix === null) {
21137
+ throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
21138
+ }
21139
+ return { kind: "routed", prefix };
21140
+ }
21141
+ async function resolveIpv6Plan(setting, deps) {
21142
+ const stopIpv6 = async (reason) => {
21143
+ await removeNat66(deps);
21144
+ getImpdLogger("ipv6").info("off ({reason})", { reason });
21145
+ return null;
21146
+ };
21147
+ if (setting.kind === "off") {
21148
+ return stopIpv6("IMP_SUBNET6=off");
21149
+ }
21150
+ const uplink = await deps.readDefaultRoute();
21151
+ if (setting.kind === "auto" && uplink === null) {
21152
+ return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
21153
+ }
21154
+ const missing = await deps.checkHostRules();
21155
+ if (missing !== null) {
21156
+ return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
21157
+ }
21158
+ if (setting.kind === "routed") {
21159
+ await removeNat66(deps);
21160
+ getImpdLogger("ipv6").info("{text}, routed{route}", {
21161
+ text: setting.prefix.text,
21162
+ route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
21163
+ });
21164
+ return { prefix: setting.prefix, nat66: false, uplink };
21165
+ }
21166
+ const prefix = deps.readUlaPrefix();
21167
+ try {
21168
+ await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
21169
+ } catch (error) {
21170
+ return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
21171
+ }
21172
+ getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
21173
+ text: prefix.text,
21174
+ uplink: uplink ?? ""
21175
+ });
21176
+ return { prefix, nat66: true, uplink };
21177
+ }
21178
+ async function removeNat66(deps) {
21179
+ try {
21180
+ await deps.runNft(buildNat66Removal());
21181
+ } catch {}
21182
+ }
21183
+ var HOST_RULES6 = [
21184
+ ["filter", "-A INPUT -i imp+ -j DROP"],
21185
+ ["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
21186
+ ["filter", "-A FORWARD -o imp+ -j DROP"],
21187
+ ["filter", "-A FORWARD -i imp+ -j DROP"],
21188
+ ["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
21189
+ ];
21190
+ var HOST_SYSCTLS6 = [
21191
+ ["default/accept_ra", "0"],
21192
+ ["default/accept_redirects", "0"],
21193
+ ["all/forwarding", "1"]
21194
+ ];
21195
+ async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
21196
+ for (const table of ["filter", "raw"]) {
21197
+ const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
21198
+ const rules = new Set(result.stdout.split(`
21199
+ `).map((line) => line.trim()));
21200
+ const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
21201
+ if (result.exitCode !== 0) {
21202
+ return `ip6tables -t ${table}: ${result.stderr.trim()}`;
21203
+ }
21204
+ if (missing !== undefined) {
21205
+ return `no ip6tables -t ${table} ${missing[1]}`;
21206
+ }
21207
+ }
21208
+ for (const [key, wanted] of HOST_SYSCTLS6) {
21209
+ const value = readSysctl(join2(procSys, key));
21210
+ if (value !== wanted) {
21211
+ return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
21212
+ }
21213
+ }
21214
+ return null;
21215
+ }
21216
+ function readSysctl(path) {
21217
+ try {
21218
+ return readFileSync2(path, "utf8").trim();
21219
+ } catch {
21220
+ return null;
21221
+ }
21222
+ }
21223
+ function readOrCreateUlaPrefix(path) {
21224
+ if (existsSync2(path)) {
21225
+ const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
21226
+ if (prefix !== null) {
21227
+ return prefix;
21228
+ }
21229
+ }
21230
+ const prefix = buildUlaPrefix(randomBytes(5));
21231
+ mkdirSync2(dirname(path), { recursive: true });
21232
+ writeFileSync(path, `${prefix.text}
21233
+ `);
21234
+ return prefix;
21235
+ }
21236
+
21237
+ // packages/daemon/src/host-net/net/range-matcher.ts
21238
+ import { IPv4 as IPv44 } from "ipaddr.js";
21239
+ function buildRangeMatcher(ranges) {
21240
+ const ranges4 = [];
21241
+ const ranges6 = [];
21242
+ for (const range of ranges) {
21243
+ const cidr = parseCidr(range);
21244
+ if (cidr === null) {
21245
+ throw new Error(`not an address range: ${range}`);
21246
+ }
21247
+ if (cidr.address instanceof IPv44) {
21248
+ ranges4.push([cidr.address, cidr.bits]);
21249
+ } else {
21250
+ ranges6.push([cidr.address, cidr.bits]);
21251
+ }
21252
+ }
21253
+ const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
21254
+ const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
21255
+ return (address) => {
21256
+ const ip = parseAddress(address);
21257
+ if (ip === null) {
21258
+ return false;
21259
+ }
21260
+ if (ip instanceof IPv44) {
21261
+ return isIn4(ip);
21262
+ }
21263
+ return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
21264
+ };
21265
+ }
21266
+
21267
+ // packages/daemon/src/identity/auth/parse-tailnet-rules.ts
21268
+ import * as z48 from "zod";
21269
+
21269
21270
  // packages/daemon/src/identity/auth/tailnet-identity.ts
21270
21271
  import * as z47 from "zod";
21271
21272
 
@@ -21703,6 +21704,8 @@ var EnvSchema = z53.object({
21703
21704
  IMP_EGRESS_DENY: z53.string().default(""),
21704
21705
  IMP_HOST_ADDRESSES: z53.string().default(""),
21705
21706
  IMP_RAM_BUDGET_MIB: CountSchema3.default(16384),
21707
+ IMP_PRIORITY_RAM_MIB: CountSchema3.optional(),
21708
+ IMP_PRIORITY_IMPS: z53.string().transform(splitList).pipe(z53.array(ImpPatternSchema)).optional(),
21706
21709
  IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
21707
21710
  IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
21708
21711
  IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
@@ -21867,6 +21870,7 @@ function loadConfig(env) {
21867
21870
  hostAddresses,
21868
21871
  brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
21869
21872
  ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
21873
+ priorityRam: parsePriorityRam(parsed),
21870
21874
  idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
21871
21875
  idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
21872
21876
  memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
@@ -21923,6 +21927,20 @@ function loadConfig(env) {
21923
21927
  warnings: listHttpsWarnings(parsed)
21924
21928
  };
21925
21929
  }
21930
+ function parsePriorityRam(parsed) {
21931
+ const mib = parsed.IMP_PRIORITY_RAM_MIB;
21932
+ const patterns = parsed.IMP_PRIORITY_IMPS;
21933
+ if (mib === undefined && patterns === undefined) {
21934
+ return null;
21935
+ }
21936
+ if (mib === undefined || patterns === undefined) {
21937
+ throw new Error("IMP_PRIORITY_RAM_MIB and IMP_PRIORITY_IMPS go together; set both or neither");
21938
+ }
21939
+ if (mib >= parsed.IMP_RAM_BUDGET_MIB) {
21940
+ throw new Error(`IMP_PRIORITY_RAM_MIB ${String(mib)} leaves no room for other imps in the ${String(parsed.IMP_RAM_BUDGET_MIB)} MiB IMP_RAM_BUDGET_MIB`);
21941
+ }
21942
+ return { mib, patterns };
21943
+ }
21926
21944
  function parseTestRegistry(parsed) {
21927
21945
  const registry = parsed.IMP_BUILD_TEST_REGISTRY;
21928
21946
  const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
@@ -21972,7 +21990,7 @@ import { join as join48 } from "path";
21972
21990
  // packages/daemon/package.json
21973
21991
  var package_default = {
21974
21992
  name: "@impz/daemon",
21975
- version: "6.6.1",
21993
+ version: "6.7.1",
21976
21994
  private: true,
21977
21995
  description: "impd: the imp host daemon",
21978
21996
  type: "module",
@@ -21980,13 +21998,9 @@ var package_default = {
21980
21998
  ".": "./src/index.ts",
21981
21999
  "./read-text": "./src/platform/process/read-text.ts",
21982
22000
  "./real-clock": "./src/platform/process/real-clock.ts",
21983
- "./testing": "./testing/index.ts",
21984
- "./testing/build-stub-clock": "./testing/platform/build-stub-clock.ts",
21985
- "./testing/data-layout": "./src/vm/storage/data-layout.ts",
21986
- "./testing/pebble-images": "./testing/network/pebble-images.ts",
21987
- "./testing/start-stub-agent": "./testing/vm/start-stub-agent.ts",
21988
- "./testing/start-test-impd": "./testing/start-test-impd.ts",
21989
- "./testing/test-token": "./testing/test-token.ts",
22001
+ "./test-utils": "./src/test-utils/index.ts",
22002
+ "./test-utils/data-layout": "./src/vm/storage/data-layout.ts",
22003
+ "./test-utils/*": "./src/test-utils/*.ts",
21990
22004
  "./package.json": "./package.json"
21991
22005
  },
21992
22006
  dependencies: {
@@ -22029,7 +22043,8 @@ var package_default = {
22029
22043
  },
22030
22044
  devDependencies: {
22031
22045
  "@faker-js/faker": "catalog:",
22032
- "@impz/test-utils": "workspace:*",
22046
+ "@impz/cli": "workspace:*",
22047
+ "@impz/e2e-harness": "workspace:*",
22033
22048
  "@modelcontextprotocol/client": "catalog:",
22034
22049
  "@msw/data": "catalog:",
22035
22050
  "@types/dns-packet": "catalog:",
@@ -25007,7 +25022,7 @@ class RamBudgetError extends ImpdError {
25007
25022
  budgetMib: shortfall.budgetMib,
25008
25023
  usedMib: shortfall.usedMib,
25009
25024
  requestedMib: shortfall.requestedMib,
25010
- neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
25025
+ neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
25011
25026
  protected: [],
25012
25027
  protectedHidden: shortfall.protected.length
25013
25028
  }
@@ -25027,25 +25042,35 @@ function buildRamBudgetError(shortfall) {
25027
25042
  const used = String(shortfall.usedMib);
25028
25043
  const budget = String(shortfall.budgetMib);
25029
25044
  const requested = String(shortfall.requestedMib);
25030
- return new RamBudgetError(`not enough RAM: ${used} of ${budget} MiB in use, ${requested} MiB requested, and no idle imp left to sleep`, shortfall, "ram");
25045
+ const kept = formatKept(shortfall.kept);
25046
+ return new RamBudgetError(`not enough RAM: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested, and no idle imp left to sleep`, shortfall, "ram");
25031
25047
  }
25032
25048
  function buildOrdinaryBudgetError(shortfall) {
25033
25049
  const used = String(shortfall.usedMib);
25034
25050
  const budget = String(shortfall.budgetMib);
25035
25051
  const requested = String(shortfall.requestedMib);
25036
- return new RamBudgetError(`not enough RAM outside the huge page pool: ${used} of ${budget} MiB in use, ${requested} MiB requested on 4 KiB pages, and no idle imp left to sleep`, shortfall, "ordinary");
25052
+ const kept = formatKept(shortfall.kept);
25053
+ return new RamBudgetError(`not enough RAM outside the huge page pool: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested on 4 KiB pages, and no idle imp left to sleep`, shortfall, "ordinary");
25037
25054
  }
25038
25055
  function buildHugePoolError(shortfall) {
25039
25056
  const committed = String(shortfall.committedMib);
25040
25057
  const total = String(shortfall.totalMib);
25041
25058
  const requested = String(shortfall.requestedMib);
25042
- return new RamBudgetError(`not enough 2 MiB pages: ${committed} of the host's ${total} MiB pool in use or promised to awake imps, ${requested} MiB requested`, {
25059
+ const kept = formatKept(shortfall.kept);
25060
+ return new RamBudgetError(`not enough 2 MiB pages: ${committed} of the host's ${total} MiB pool in use or promised to awake imps${kept}, ${requested} MiB requested`, {
25043
25061
  budgetMib: shortfall.totalMib,
25044
25062
  usedMib: shortfall.committedMib,
25045
25063
  requestedMib: shortfall.requestedMib,
25046
- protected: []
25064
+ protected: [],
25065
+ ...shortfall.kept !== undefined && { kept: shortfall.kept }
25047
25066
  }, "pool");
25048
25067
  }
25068
+ function formatKept(kept) {
25069
+ if (kept === undefined || kept.mib === 0) {
25070
+ return "";
25071
+ }
25072
+ return ` and ${String(kept.mib)} MiB kept for priority imps (${kept.patterns.join(", ")})`;
25073
+ }
25049
25074
  function buildNeededMib(usedMib, requestedMib, budgetMib) {
25050
25075
  return Math.max(0, usedMib + requestedMib - budgetMib);
25051
25076
  }
@@ -26317,7 +26342,11 @@ async function sendAgentRequest(vsockPath, request, timeoutMs = 5000) {
26317
26342
  const connection = await openAgentConnection(vsockPath, timeoutMs);
26318
26343
  try {
26319
26344
  sendRequestFrame(connection, request);
26320
- const frame = await readFrameWithin(connection, timeoutMs);
26345
+ const frame = await readFrameWithin(connection, {
26346
+ call: `${request.op} response`,
26347
+ vsockPath,
26348
+ timeoutMs
26349
+ });
26321
26350
  if (frame?.type !== FRAME_TYPES.response) {
26322
26351
  throw new Error(`agent ${request.op}: no response`);
26323
26352
  }
@@ -26379,8 +26408,8 @@ async function sendThaw(vsockPath) {
26379
26408
  const response = await sendAgentRequest(vsockPath, { op: "thaw" });
26380
26409
  decodeAgentMessage(OKSchema, response);
26381
26410
  }
26382
- function readFrameWithin(connection, timeoutMs) {
26383
- return requireWithin(connection.next(), timeoutMs, () => new Error(`agent did not answer within ${String(timeoutMs)} ms`));
26411
+ function readFrameWithin(connection, wait) {
26412
+ return requireWithin(connection.next(), wait.timeoutMs, () => new Error(`agent at ${wait.vsockPath} sent no ${wait.call} within ${String(wait.timeoutMs)} ms`));
26384
26413
  }
26385
26414
 
26386
26415
  // packages/daemon/src/imps/imps/consistent-disk.ts
@@ -33480,7 +33509,11 @@ async function openStream(vsockPath, request, startTimeoutMs) {
33480
33509
  let output;
33481
33510
  try {
33482
33511
  sendRequestFrame(connection, request);
33483
- const first = await readFrameWithin(connection, startTimeoutMs);
33512
+ const first = await readFrameWithin(connection, {
33513
+ call: `${request.op} start`,
33514
+ vsockPath,
33515
+ timeoutMs: startTimeoutMs
33516
+ });
33484
33517
  if (first === null) {
33485
33518
  throw new Error("agent closed the exec connection before the process started");
33486
33519
  }
@@ -37693,6 +37726,61 @@ function buildBudgets(budgetMib, pool) {
37693
37726
  };
37694
37727
  }
37695
37728
 
37729
+ // packages/daemon/src/imp-ops/governor/build-usage-totals.ts
37730
+ function buildUsageTotals(input) {
37731
+ const reserved = new Map(input.reservations.map((reservation) => [reservation.id, reservation]));
37732
+ const byImp = new Map;
37733
+ let usedMib = input.lockedMib;
37734
+ let effectiveMib = input.lockedMib + input.headroomMib;
37735
+ let ordinaryUsedMib = input.lockedMib;
37736
+ let ordinaryEffectiveMib = input.lockedMib + input.headroomMib;
37737
+ const priority = { ramMib: 0, ordinaryMib: 0, poolMib: 0 };
37738
+ for (const [index, imp] of input.awake.entries()) {
37739
+ const reading = input.readings[index] ?? null;
37740
+ const measured = reading?.ramMib ?? 0;
37741
+ const effective = Math.max(measured, reserved.get(imp.id)?.mib ?? 0);
37742
+ const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
37743
+ byImp.set(imp.id, effective);
37744
+ usedMib += measured;
37745
+ effectiveMib += effective;
37746
+ ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
37747
+ ordinaryEffectiveMib += ordinary;
37748
+ if (imp.isPriority) {
37749
+ priority.ramMib += effective;
37750
+ priority.ordinaryMib += ordinary;
37751
+ priority.poolMib += imp.hugePagesMib;
37752
+ }
37753
+ }
37754
+ for (const reservation of input.reservations) {
37755
+ if (!byImp.has(reservation.id)) {
37756
+ const ordinary = reservation.isOnPool ? 0 : reservation.mib;
37757
+ effectiveMib += reservation.mib;
37758
+ ordinaryEffectiveMib += ordinary;
37759
+ if (reservation.isPriority) {
37760
+ priority.ramMib += reservation.mib;
37761
+ priority.ordinaryMib += ordinary;
37762
+ }
37763
+ }
37764
+ }
37765
+ for (const pending of input.pendingPool) {
37766
+ if (pending.isPriority && !byImp.has(pending.id)) {
37767
+ priority.poolMib += pending.mib;
37768
+ }
37769
+ }
37770
+ return {
37771
+ byImp,
37772
+ usedMib,
37773
+ effectiveMib,
37774
+ ordinaryUsedMib,
37775
+ ordinaryEffectiveMib,
37776
+ kept: {
37777
+ ramMib: Math.max(0, input.priorityMib - priority.ramMib),
37778
+ ordinaryMib: Math.max(0, input.priorityMib - priority.ordinaryMib),
37779
+ poolMib: Math.max(0, input.priorityMib - priority.poolMib)
37780
+ }
37781
+ };
37782
+ }
37783
+
37696
37784
  // packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
37697
37785
  function buildHugePoolAdmission(input) {
37698
37786
  const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
@@ -37700,21 +37788,22 @@ function buildHugePoolAdmission(input) {
37700
37788
  const totalMib = input.pool?.totalMib ?? 0;
37701
37789
  const freeMib = input.pool?.freeMib ?? 0;
37702
37790
  const promisedMib = growthMib + pendingMib;
37703
- if (promisedMib + input.requestedMib <= freeMib) {
37791
+ const wantedMib = promisedMib + input.keptMib + input.requestedMib;
37792
+ if (wantedMib <= freeMib) {
37704
37793
  return { admitted: true };
37705
37794
  }
37706
37795
  return {
37707
37796
  admitted: false,
37708
37797
  totalMib,
37709
37798
  committedMib: totalMib - freeMib + promisedMib,
37710
- neededMib: promisedMib + input.requestedMib - freeMib
37799
+ neededMib: wantedMib - freeMib
37711
37800
  };
37712
37801
  }
37713
37802
 
37714
37803
  // packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
37715
37804
  function makeCheckHugePool(deps) {
37716
37805
  const pendingHugePages = deps.pendingHugePages;
37717
- return async (request, hugePagesMib) => {
37806
+ return async (request, hugePagesMib, kept) => {
37718
37807
  const listed = await deps.listAwake();
37719
37808
  const awake = listed.filter((imp) => imp.id !== request.id);
37720
37809
  for (const imp of awake) {
@@ -37730,7 +37819,8 @@ function makeCheckHugePool(deps) {
37730
37819
  heldMib: held[index] ?? 0
37731
37820
  })),
37732
37821
  pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
37733
- requestedMib: hugePagesMib
37822
+ requestedMib: hugePagesMib,
37823
+ keptMib: kept?.mib ?? 0
37734
37824
  });
37735
37825
  if (poolAdmission.admitted) {
37736
37826
  return;
@@ -37746,7 +37836,8 @@ function makeCheckHugePool(deps) {
37746
37836
  throw buildHugePoolError({
37747
37837
  totalMib: poolAdmission.totalMib,
37748
37838
  committedMib: poolAdmission.committedMib,
37749
- requestedMib: hugePagesMib
37839
+ requestedMib: hugePagesMib,
37840
+ ...kept !== null && { kept }
37750
37841
  });
37751
37842
  };
37752
37843
  }
@@ -37771,6 +37862,17 @@ function pickShortfall(used, budgets, requestedMib) {
37771
37862
  } : ram;
37772
37863
  }
37773
37864
 
37865
+ // packages/daemon/src/imp-ops/governor/pick-effective-shortfall.ts
37866
+ function pickEffectiveShortfall(usage, budgets, requestedMib, isPriority) {
37867
+ const kept = isPriority ? { ramMib: 0, ordinaryMib: 0 } : usage.kept;
37868
+ const shortfall = pickShortfall({
37869
+ ramMib: usage.effectiveMib + kept.ramMib,
37870
+ ordinaryMib: usage.ordinaryEffectiveMib + kept.ordinaryMib
37871
+ }, budgets, requestedMib);
37872
+ const keptMib = shortfall.budget === "ordinary" ? kept.ordinaryMib : kept.ramMib;
37873
+ return { ...shortfall, usedMib: shortfall.usedMib - keptMib, keptMib };
37874
+ }
37875
+
37774
37876
  // packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
37775
37877
  function pickSleepVictims(candidates, needMib) {
37776
37878
  if (needMib <= 0) {
@@ -37798,8 +37900,12 @@ function createRamGovernor(deps) {
37798
37900
  const admission = createPrioritySemaphore(1);
37799
37901
  const reservations = new Map;
37800
37902
  const now = deps.now ?? Date.now;
37903
+ const priority = deps.priority ?? null;
37904
+ const isPriority = (name) => priority !== null && name !== null && isImpAllowed(priority.patterns, name);
37905
+ const buildKept = (mib) => priority === null || mib === 0 ? null : { mib, patterns: priority.patterns };
37801
37906
  let stuckOver = false;
37802
37907
  const pendingHugePages = new Map;
37908
+ const admittedPriority = new Set;
37803
37909
  const emitDecision = (decision) => {
37804
37910
  deps.events?.publish({
37805
37911
  v: EVENT_VERSION,
@@ -37830,6 +37936,11 @@ function createRamGovernor(deps) {
37830
37936
  if ((request.hugePagesMib ?? 0) > 0) {
37831
37937
  pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
37832
37938
  }
37939
+ if (isPriority(request.name)) {
37940
+ admittedPriority.add(request.id);
37941
+ } else {
37942
+ admittedPriority.delete(request.id);
37943
+ }
37833
37944
  emitAdmission(request, "admitted", usedMib);
37834
37945
  };
37835
37946
  const readReservation = (id) => {
@@ -37860,45 +37971,27 @@ function createRamGovernor(deps) {
37860
37971
  const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
37861
37972
  const listed = await deps.listAwake();
37862
37973
  const awake = listed.filter((imp) => imp.id !== excludeId);
37863
- const byImp = new Map;
37864
37974
  const lockedMib = deps.readLockedMib?.() ?? 0;
37865
- let usedMib = lockedMib;
37866
- let effectiveMib = lockedMib;
37867
- let ordinaryUsedMib = lockedMib;
37868
- let ordinaryEffectiveMib = lockedMib;
37869
37975
  const readings = await Promise.all(awake.map((imp) => readVm(imp.pid, imp.apiSocket)));
37870
- for (const [index, imp] of awake.entries()) {
37871
- const reading = readings[index] ?? null;
37872
- const measured = reading?.ramMib ?? 0;
37873
- const effective = Math.max(measured, readReservation(imp.id));
37874
- const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
37875
- byImp.set(imp.id, effective);
37876
- usedMib += measured;
37877
- effectiveMib += effective;
37878
- ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
37879
- ordinaryEffectiveMib += ordinary;
37880
- }
37881
- for (const id of reservations.keys()) {
37882
- if (id !== excludeId && !byImp.has(id)) {
37883
- effectiveMib += readReservation(id);
37884
- if (!pendingHugePages.has(id)) {
37885
- ordinaryEffectiveMib += readReservation(id);
37886
- }
37887
- }
37888
- }
37976
+ const live = [...reservations.keys()].filter((id) => id !== excludeId).map((id) => ({
37977
+ id,
37978
+ mib: readReservation(id),
37979
+ isPriority: admittedPriority.has(id),
37980
+ isOnPool: pendingHugePages.has(id)
37981
+ }));
37982
+ const pendingPool = [...pendingHugePages].filter(([id]) => id !== excludeId).map(([id, mib]) => ({ id, mib, isPriority: admittedPriority.has(id) }));
37889
37983
  const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
37890
37984
  const headroomMib = await headroom;
37891
- effectiveMib += headroomMib;
37892
- ordinaryEffectiveMib += headroomMib;
37893
- return {
37894
- awake,
37895
- byImp,
37896
- usedMib,
37897
- effectiveMib,
37898
- ordinaryUsedMib,
37899
- ordinaryEffectiveMib,
37900
- headroomMib
37901
- };
37985
+ const totals = buildUsageTotals({
37986
+ awake: awake.map((imp) => ({ ...imp, isPriority: isPriority(imp.name) })),
37987
+ readings,
37988
+ reservations: live,
37989
+ pendingPool,
37990
+ lockedMib,
37991
+ headroomMib,
37992
+ priorityMib: priority?.mib ?? 0
37993
+ });
37994
+ return { awake, headroomMib, ...totals };
37902
37995
  };
37903
37996
  const readFreeRoom = async (excludeId, findShortfall) => {
37904
37997
  const usage = await readEffectiveUsage(excludeId);
@@ -37926,7 +38019,7 @@ function createRamGovernor(deps) {
37926
38019
  const shortfall = options.findShortfall(usage);
37927
38020
  const missingMib = shortfall.missingMib;
37928
38021
  const isOrdinary = shortfall.budget === "ordinary";
37929
- const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0);
38022
+ const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0).filter((imp) => !options.sparesPriority || !isPriority(imp.name));
37930
38023
  if (missingMib <= 0) {
37931
38024
  return {
37932
38025
  fits: true,
@@ -38011,20 +38104,23 @@ function createRamGovernor(deps) {
38011
38104
  requestedMib: request.memoryMib
38012
38105
  });
38013
38106
  }
38107
+ const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38108
+ const isPriorityRequest = isPriority(request.name);
38014
38109
  if (hugePagesMib > 0) {
38015
- await checkHugePool(request, hugePagesMib);
38110
+ const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
38111
+ await checkHugePool(request, hugePagesMib, kept);
38016
38112
  }
38017
- const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
38018
- if (pickEffectiveShortfall(bound, budgets, request.reserveMib).missingMib <= 0) {
38113
+ const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
38114
+ if (findReserveShortfall(bound).missingMib <= 0) {
38019
38115
  recordAdmission(request, bound.effectiveMib);
38020
38116
  return;
38021
38117
  }
38022
- const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
38023
38118
  const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
38024
38119
  excludeId: request.id,
38025
38120
  reason: `to make room for ${request.name ?? request.id}`,
38026
38121
  whenShort: "giveUp",
38027
- findShortfall: findReserveShortfall
38122
+ findShortfall: findReserveShortfall,
38123
+ sparesPriority: !isPriorityRequest
38028
38124
  });
38029
38125
  const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
38030
38126
  if (!room.fits && diskFull !== null) {
@@ -38034,8 +38130,9 @@ function createRamGovernor(deps) {
38034
38130
  const usage = await readEffectiveUsage(request.id);
38035
38131
  const shortfall = findReserveShortfall(usage);
38036
38132
  const isOrdinary = shortfall.budget === "ordinary";
38133
+ const kept = buildKept(shortfall.keptMib);
38037
38134
  emitAdmission(request, "refused", shortfall.usedMib, {
38038
- neededMib: buildNeededMib(shortfall.usedMib, request.reserveMib, shortfall.budgetMib),
38135
+ neededMib: Math.max(0, shortfall.missingMib),
38039
38136
  protectedCount: room.protected.length,
38040
38137
  ...isOrdinary && { trigger: ORDINARY_TRIGGER }
38041
38138
  });
@@ -38043,7 +38140,8 @@ function createRamGovernor(deps) {
38043
38140
  budgetMib: shortfall.budgetMib,
38044
38141
  usedMib: shortfall.usedMib,
38045
38142
  requestedMib: request.reserveMib,
38046
- protected: room.protected
38143
+ protected: room.protected,
38144
+ ...kept !== null && { kept }
38047
38145
  };
38048
38146
  throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
38049
38147
  }
@@ -38052,6 +38150,7 @@ function createRamGovernor(deps) {
38052
38150
  release: (id) => {
38053
38151
  reservations.delete(id);
38054
38152
  pendingHugePages.delete(id);
38153
+ admittedPriority.delete(id);
38055
38154
  },
38056
38155
  admitGrow: (request) => admission.run("background", async () => {
38057
38156
  const listed = await deps.listAwake();
@@ -38059,15 +38158,17 @@ function createRamGovernor(deps) {
38059
38158
  return false;
38060
38159
  }
38061
38160
  const budgets = readBudgets();
38161
+ const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
38062
38162
  const room = await makeRoom({
38063
38163
  excludeId: null,
38064
38164
  reason: `to grow ${request.name}`,
38065
38165
  whenShort: "giveUp",
38066
- findShortfall: (usage) => pickEffectiveShortfall(usage, budgets, request.mib),
38067
- protectedId: request.id
38166
+ findShortfall: findGrowShortfall,
38167
+ protectedId: request.id,
38168
+ sparesPriority: !isPriority(request.name)
38068
38169
  });
38069
38170
  const usage = await readEffectiveUsage(null);
38070
- const shortfall = pickEffectiveShortfall(usage, budgets, request.mib);
38171
+ const shortfall = findGrowShortfall(usage);
38071
38172
  const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
38072
38173
  emitDecision({
38073
38174
  decision: fits ? "admitted" : "refused",
@@ -38116,7 +38217,8 @@ function createRamGovernor(deps) {
38116
38217
  excludeId: null,
38117
38218
  reason: ENFORCE_TRIGGER,
38118
38219
  whenShort: "sleepAll",
38119
- findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0)
38220
+ findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0),
38221
+ sparesPriority: false
38120
38222
  });
38121
38223
  const over = `over budget by ${String(room.missingMib)} MiB`;
38122
38224
  if (room.fits) {
@@ -38134,9 +38236,6 @@ function createRamGovernor(deps) {
38134
38236
  })
38135
38237
  };
38136
38238
  }
38137
- function pickEffectiveShortfall(usage, budgets, requestedMib) {
38138
- return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
38139
- }
38140
38239
  function toTaskPriority(request) {
38141
38240
  return request.isBackground === true ? "background" : "foreground";
38142
38241
  }
@@ -41333,7 +41432,11 @@ async function openRelayStream(vsockPath, request, feature, answerTimeoutMs) {
41333
41432
  const connection = await openAgentConnection(vsockPath);
41334
41433
  try {
41335
41434
  sendRequestFrame(connection, request);
41336
- const answer = await readFrameWithin(connection, answerTimeoutMs);
41435
+ const answer = await readFrameWithin(connection, {
41436
+ call: `${request.op} answer`,
41437
+ vsockPath,
41438
+ timeoutMs: answerTimeoutMs
41439
+ });
41337
41440
  if (answer?.type !== FRAME_TYPES.response) {
41338
41441
  throw new Error(`agent closed the ${request.op} connection before it answered`);
41339
41442
  }
@@ -41379,12 +41482,16 @@ function buildListenRequest(spec) {
41379
41482
  }
41380
41483
  return { op: "listen", network: "tcp", address: `127.0.0.1:${String(spec.port)}` };
41381
41484
  }
41382
- async function openListener(vsockPath, spec) {
41485
+ async function openListener(vsockPath, spec, answerTimeoutMs = ANSWER_TIMEOUT_MS) {
41383
41486
  const feature = spec.network === "ssh-agent" ? "agent-forwarding" : "reverse-forward";
41384
41487
  const connection = await openAgentConnection(vsockPath);
41385
41488
  try {
41386
41489
  sendRequestFrame(connection, buildListenRequest(spec));
41387
- const answer = await readFrameWithin(connection, ANSWER_TIMEOUT_MS);
41490
+ const answer = await readFrameWithin(connection, {
41491
+ call: "listener answer",
41492
+ vsockPath,
41493
+ timeoutMs: answerTimeoutMs
41494
+ });
41388
41495
  if (answer?.type !== FRAME_TYPES.response) {
41389
41496
  throw new Error("agent closed the listen connection before it answered");
41390
41497
  }
@@ -42286,7 +42393,7 @@ async function sendServicesRestart(vsockPath, service) {
42286
42393
  const response = await sendAgentRequest(vsockPath, { op: "services.restart", service }, STOP_TIMEOUT_MS).catch(handleUnknownOp("services"));
42287
42394
  decodeAgentMessage(OKSchema, response);
42288
42395
  }
42289
- async function openServiceLogStream(vsockPath, request) {
42396
+ async function openServiceLogStream(vsockPath, request, startTimeoutMs = LOGS_START_TIMEOUT_MS) {
42290
42397
  const connection = await openAgentConnection(vsockPath);
42291
42398
  try {
42292
42399
  sendRequestFrame(connection, {
@@ -42296,7 +42403,11 @@ async function openServiceLogStream(vsockPath, request) {
42296
42403
  follow: request.follow,
42297
42404
  ...request.cursor !== undefined && { cursor: request.cursor }
42298
42405
  });
42299
- const first = await readFrameWithin(connection, LOGS_START_TIMEOUT_MS);
42406
+ const first = await readFrameWithin(connection, {
42407
+ call: "services.logs start",
42408
+ vsockPath,
42409
+ timeoutMs: startTimeoutMs
42410
+ });
42300
42411
  if (first === null) {
42301
42412
  throw new Error("agent closed the log connection before it answered");
42302
42413
  }
@@ -45622,6 +45733,7 @@ function createImpdImps(config, deps, parts) {
45622
45733
  });
45623
45734
  const governor = createRamGovernor({
45624
45735
  budgetMib: config.ramBudgetMib,
45736
+ priority: config.priorityRam,
45625
45737
  listAwake: async () => {
45626
45738
  const listed = await listImps(deps.db);
45627
45739
  return listed.flatMap((imp) => {
@@ -58516,12 +58628,12 @@ async function setupDefaultImage(config, images) {
58516
58628
  }
58517
58629
  }
58518
58630
 
58519
- // packages/daemon/testing/network/read-no-tailscale.ts
58631
+ // packages/daemon/src/test-utils/network/read-no-tailscale.ts
58520
58632
  function readNoTailscale() {
58521
58633
  return Promise.resolve({ state: null, hostname: null, dnsName: null, ip: null, ips: [] });
58522
58634
  }
58523
58635
 
58524
- // packages/daemon/testing/platform/build-stub-ticker-timer.ts
58636
+ // packages/daemon/src/test-utils/platform/build-stub-ticker-timer.ts
58525
58637
  function buildStubTickerTimer() {
58526
58638
  const pending = new Map;
58527
58639
  const holdRun = (label, run, durationMs) => {
@@ -58547,12 +58659,12 @@ function buildStubTickerTimer() {
58547
58659
  };
58548
58660
  }
58549
58661
 
58550
- // packages/daemon/testing/platform/format-run-prefix.ts
58662
+ // packages/daemon/src/test-utils/platform/format-run-prefix.ts
58551
58663
  function formatRunPrefix(owner) {
58552
58664
  return `pid${String(owner.pid)}-ns${owner.namespace}-`;
58553
58665
  }
58554
58666
 
58555
- // packages/daemon/testing/platform/open-migrated-copy.ts
58667
+ // packages/daemon/src/test-utils/platform/open-migrated-copy.ts
58556
58668
  import { Database as Database4 } from "bun:sqlite";
58557
58669
  var TEMPLATE = {};
58558
58670
  function openMigratedCopy() {
@@ -58567,14 +58679,14 @@ async function serializeMigratedDatabase() {
58567
58679
  return bytes;
58568
58680
  }
58569
58681
 
58570
- // packages/daemon/testing/platform/pick-impd-port.ts
58682
+ // packages/daemon/src/test-utils/platform/pick-impd-port.ts
58571
58683
  var FIRST_PORT = 10100;
58572
58684
  var PORT_SPAN = 9900;
58573
58685
  function pickImpdPort(draw) {
58574
58686
  return FIRST_PORT + Math.floor(draw * PORT_SPAN);
58575
58687
  }
58576
58688
 
58577
- // packages/daemon/testing/platform/read-run-owner.ts
58689
+ // packages/daemon/src/test-utils/platform/read-run-owner.ts
58578
58690
  import { readlinkSync as readlinkSync2 } from "fs";
58579
58691
  function readRunOwner() {
58580
58692
  return {
@@ -58583,7 +58695,7 @@ function readRunOwner() {
58583
58695
  };
58584
58696
  }
58585
58697
 
58586
- // packages/daemon/testing/platform/start-impd-log-capture.ts
58698
+ // packages/daemon/src/test-utils/platform/start-impd-log-capture.ts
58587
58699
  import { resetSync } from "@logtape/logtape";
58588
58700
 
58589
58701
  // packages/daemon/src/platform/process/setup-logging.ts
@@ -58616,7 +58728,7 @@ function renderLogLine(record) {
58616
58728
  `;
58617
58729
  }
58618
58730
 
58619
- // packages/daemon/testing/platform/start-impd-log-capture.ts
58731
+ // packages/daemon/src/test-utils/platform/start-impd-log-capture.ts
58620
58732
  var listeners = new Set;
58621
58733
  function startImpdLogCapture(onLine) {
58622
58734
  if (listeners.size === 0) {
@@ -58634,10 +58746,10 @@ function startImpdLogCapture(onLine) {
58634
58746
  };
58635
58747
  }
58636
58748
 
58637
- // packages/daemon/testing/test-token.ts
58749
+ // packages/daemon/src/test-utils/test-token.ts
58638
58750
  var TEST_TOKEN = "test-token";
58639
58751
 
58640
- // packages/daemon/testing/vm/build-stub-vmm.ts
58752
+ // packages/daemon/src/test-utils/vm/build-stub-vmm.ts
58641
58753
  import { existsSync as existsSync33, mkdirSync as mkdirSync30, renameSync as renameSync8, writeFileSync as writeFileSync15 } from "fs";
58642
58754
  var STUB_AGENT_VERSION = "0.1.0";
58643
58755
  var IMPD_OWNER = { uid: process.getuid?.() ?? 0, cgroup: null };
@@ -59041,7 +59153,7 @@ log tail`);
59041
59153
  };
59042
59154
  }
59043
59155
 
59044
- // packages/daemon/testing/start-test-impd.ts
59156
+ // packages/daemon/src/test-utils/start-test-impd.ts
59045
59157
  var FAKE_VM_RAM_MIB = 300;
59046
59158
  var FAKE_VM_RSS_MIB = 340;
59047
59159
  var TEST_DRIVE = "d1".repeat(32);
@@ -59384,7 +59496,7 @@ function buildTestIdentity(dataDir, drive) {
59384
59496
  import { randomBytes as randomBytes12 } from "crypto";
59385
59497
  import { Duplex } from "stream";
59386
59498
 
59387
- // packages/daemon/testing/vm/start-stub-agent.ts
59499
+ // packages/daemon/src/test-utils/vm/start-stub-agent.ts
59388
59500
  import { onTestFinished as onTestFinished2 } from "bun:test";
59389
59501
  import { mkdirSync as mkdirSync32 } from "fs";
59390
59502
  import { createServer as createServer3 } from "net";