@volter/world-core 3.0.161 → 3.0.162

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.
@@ -1,3 +1,5 @@
1
+ import { storageTenant } from "./request-scope.js";
2
+ import { isWorldAccountName } from "./world-account-name.js";
1
3
  import { parseExactJson, compareExactNumbers, exactInteger, isExactIntegerValue } from "./exact-json.js";
2
4
  // THE DERIVED CORE — what a derived pack does for an operation it declares no handler for
3
5
  // (docs/contributing/architecture.md, "Protocol 3"). The generated surface says what the
@@ -235,7 +237,13 @@ function notFound(manifest, object, id, param) {
235
237
  return vendorError(manifest, { status: nf.status, message: String(fill(message, { object, id })), ...(code ? { code } : {}), ...(nf.kind ? { kind: nf.kind } : {}), ...(param ? { param } : {}) });
236
238
  }
237
239
  // ── the tree ─────────────────────────────────────────────────────────────────────────────────
238
- export const storedType = (m, resource) => m.resources[resource]?.storedAs ?? resource;
240
+ const TENANT_TYPE = '::account:';
241
+ const accountScope = (m) => m.tenant?.storage === 'namespaced' ? storageTenant(m.service) : undefined;
242
+ const scopeLabel = (m, label) => { const account = accountScope(m); return account && account !== 'default' ? `account:${account}:${label}` : label; };
243
+ export const storedType = (m, resource) => {
244
+ const base = m.resources[resource]?.storedAs ?? resource, account = accountScope(m);
245
+ return account && account !== 'default' && !m.resources[resource]?.shared ? `${base}${TENANT_TYPE}${encodeURIComponent(account)}` : base;
246
+ };
239
247
  // A service's resources by type, folded once per World state (the tree's stamp; any write moves it), not on every read:
240
248
  // a read of one resource type projected the service's whole tree, so a pack whose tree also holds many subjects of
241
249
  // other types (PlanetScale's SQL rows beside its management records) paid for all of them on each read. A read gets its
@@ -254,9 +262,15 @@ export function resourcesOfType(service, type, root) {
254
262
  if (held && delta) {
255
263
  // the index keeps the tree's order: a subject updated where it stands stays there, one new to the tree (or made
256
264
  // again after a delete) goes to the end in the order the tree holds it
257
- for (const k of [...delta.removed, ...delta.appended])
258
- held.byType.get(k.slice(0, k.indexOf(':')))?.delete(k);
265
+ for (const k of [...delta.removed, ...delta.appended]) {
266
+ const type = held.typeByKey.get(k);
267
+ if (type !== undefined)
268
+ held.byType.get(type)?.delete(k);
269
+ held.typeByKey.delete(k);
270
+ }
259
271
  const byKey = new Map(delta.changed.map((r) => [`${r.type}:${r.id}`, r]));
272
+ for (const [k, r] of byKey)
273
+ held.typeByKey.set(k, r.type);
260
274
  for (const [k, r] of byKey)
261
275
  if (!delta.appended.includes(k))
262
276
  (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)).set(k, r);
@@ -273,7 +287,7 @@ export function resourcesOfType(service, type, root) {
273
287
  const byType = new Map();
274
288
  for (const r of twinResources(service, root))
275
289
  (byType.get(r.type) ?? byType.set(r.type, new Map()).get(r.type)).set(`${r.type}:${r.id}`, r);
276
- held = { stamp, byType };
290
+ held = { stamp, byType, typeByKey: new Map([...byType].flatMap(([type, rows]) => [...rows.keys()].map(key => [key, type]))) };
277
291
  indexes.set(key, held);
278
292
  }
279
293
  }
@@ -282,7 +296,9 @@ export function resourcesOfType(service, type, root) {
282
296
  function stored(m, resource, root, opts = {}) {
283
297
  const type = storedType(m, resource);
284
298
  const when = Object.entries(m.resources[resource]?.readableWhen ?? {});
285
- return resourcesOfType(m.service, type, root).filter((r) => (opts.withDeleted || r.deleted !== true) && when.every(([k, v]) => r[k] === v));
299
+ const rows = m.tenant?.storage === 'namespaced' && accountScope(m) === undefined && !m.resources[resource]?.shared
300
+ ? twinResources(m.service, root).filter(row => row.type === type || row.type.startsWith(`${type}${TENANT_TYPE}`)) : resourcesOfType(m.service, type, root);
301
+ return rows.filter((r) => (opts.withDeleted || r.deleted !== true) && when.every(([k, v]) => r[k] === v));
286
302
  }
287
303
  /** The subject an item operation names: the manifest's key over the path parameters, or the last one. */
288
304
  function subjectOf(m, resource, call) {
@@ -317,7 +333,7 @@ function missingParent(m, resource, call, root, tenant) {
317
333
  const id = parentValue(parent, call.params);
318
334
  const where = Object.entries(parent.where ?? {}).map(([field, template]) => [field, String(fill(template, call.params))]);
319
335
  const found = (r) => (where.length ? where.every(([field, v]) => String(parentField(r, field)) === v) : r.id === id)
320
- && (!m.tenant || m.resources[parent.resource]?.shared || r[m.tenant.field] === tenant);
336
+ && (!m.tenant || m.tenant.storage === 'namespaced' || m.resources[parent.resource]?.shared || r[m.tenant.field] === tenant);
321
337
  return id !== undefined && stored(m, parent.resource, root, { withDeleted: parent.allowDeleted === true }).some(found) ? undefined : notFound(m, parent.resource, String(id), parent.param ?? parent.params?.at(-1));
322
338
  }
323
339
  /** The vendor's view of a stored subject: its own fields, bookkeeping (`_`) left out. */
@@ -430,14 +446,33 @@ function worldSeed(service, root, at) {
430
446
  if (pending)
431
447
  return pending;
432
448
  const made = (async () => {
433
- const value = Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');
434
- // the World's own bookkeeping, made on first use whatever the request: a read-only one lands it too (a vendor's move)
435
- await runAsVendorMove(() => applyTwinWrite(service, { operation: 'world_seed.record', subjectType: SEED, subjectId: 'seed', fields: { value }, occurredAt: at, actor: { kind: 'system' } }, root));
449
+ // The action lock also joins first asks from several renderers/processes to the same seed.
450
+ const { value } = await runAsVendorMove(() => applyTwinWriteAtomic(service, snapshot => {
451
+ const existing = snapshot.find(row => row.type === SEED && row.id === 'seed');
452
+ if (typeof existing?.value === 'string')
453
+ return { kind: 'skip', value: existing.value };
454
+ const value = Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');
455
+ return { kind: 'write', value, write: { operation: 'world_seed.record', subjectType: SEED, subjectId: 'seed', fields: { value }, occurredAt: at, actor: { kind: 'system' } } };
456
+ }, root));
436
457
  return value;
437
458
  })().finally(() => seeding.delete(key));
438
459
  seeding.set(key, made);
439
460
  return made;
440
461
  }
462
+ /** World-owned generated values use the same private seed and log as pack secrets. */
463
+ export async function worldSecret(service, root, label, at = new Date().toISOString()) {
464
+ return hmac(await worldSeed(service, root, at), label, 'base64url');
465
+ }
466
+ /** The same retained account primitive for pack doors and private native twin fronts. */
467
+ export async function worldAccount(service, root, id = 'default', at = worldNow(), provisioning = false) {
468
+ if (!isWorldAccountName(id))
469
+ throw new Error('Credential account must be a name of at most 128 letters, digits, dots, underscores or hyphens');
470
+ await applyTwinWriteAtomic(service, (snapshot) => {
471
+ const held = snapshot.find(row => row.type === '_world_account' && row.id === id);
472
+ return held ? { kind: 'skip', value: id } : { kind: 'write', value: id, write: { operation: 'world_account.create', subjectType: '_world_account', subjectId: id, fields: { name: id }, ...(provisioning ? { input: { activity: 'provisioning' } } : {}), occurredAt: at, actor: { kind: 'system' } } };
473
+ }, root);
474
+ return id;
475
+ }
441
476
  /** A World's signing key for a label (ctx.signingKey): made once from the platform's random source, then read; the
442
477
  * making is one per service store and label in the process, as the seed's is. */
443
478
  const SIGNING_KEY = '_world_signing_key';
@@ -472,6 +507,13 @@ function worldSigningKey(service, root, at, label, alg) {
472
507
  keying.set(key, made);
473
508
  return made;
474
509
  }
510
+ /** Vendor ids remain unique across its accounts, including public links that carry an id instead of a key. */
511
+ function mintRows(m, resource, root, snapshot) {
512
+ const base = m.resources[resource]?.storedAs ?? resource;
513
+ if (m.tenant?.storage === 'namespaced')
514
+ return (snapshot ?? twinResources(m.service, root)).filter(row => row.type === base || row.type.startsWith(`${base}${TENANT_TYPE}`));
515
+ return snapshot ? snapshot.filter(row => row.type === base) : resourcesOfType(m.service, base, root);
516
+ }
475
517
  function mintId(m, resource, root, at, snapshot) {
476
518
  if (!m.resources[resource])
477
519
  throw new Error(`mint: ${resource} is not a resource ${m.vendor}'s manifest declares`);
@@ -495,7 +537,7 @@ function mintId(m, resource, root, at, snapshot) {
495
537
  const width = Number(digits[1]);
496
538
  if (!Number.isInteger(width) || width < 1 || width > 77)
497
539
  throw new Error('mint: decimal width must be between 1 and 77');
498
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
540
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
499
541
  const floor = 10n ** BigInt(width - 1);
500
542
  for (let n = held.size + 1;; n++) {
501
543
  const id = String(floor + BigInt('0x' + sha256(`${m.service}:${resource}:${n}`)) % (9n * floor));
@@ -518,7 +560,7 @@ function mintId(m, resource, root, at, snapshot) {
518
560
  const pattern = new RegExp(`^${escapeRe(String(fill(head, { prefix })))}(\\d+)${escapeRe(String(fill(tail, { prefix })))}$`);
519
561
  let max = 0n;
520
562
  // the one type, from the index that follows writes (a scan of the whole World per create grew with it)
521
- for (const r of (snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root))) {
563
+ for (const r of mintRows(m, resource, root, snapshot)) {
522
564
  const hit = pattern.exec(r.id);
523
565
  if (hit && BigInt(hit[1]) > max)
524
566
  max = BigInt(hit[1]);
@@ -533,13 +575,13 @@ function mintId(m, resource, root, at, snapshot) {
533
575
  * and its attachment) must not meet. */
534
576
  const lastSnowflake = new Map();
535
577
  function mintSnowflake(m, epoch, root, at, snapshot) {
536
- const ms = BigInt(Date.parse(at ?? '') || 0);
578
+ const ms = BigInt(Date.parse(at) || 0);
537
579
  const base = ms > epoch ? (ms - epoch) << 22n : 0n;
538
580
  let max = -1n;
539
581
  for (const [name, decl] of Object.entries(m.resources)) {
540
582
  if (!/^\{snowflake:\d+\}$/.test(decl.ids ?? m.ids.template))
541
583
  continue;
542
- for (const r of (snapshot ? snapshot.filter((r) => r.type === storedType(m, name)) : resourcesOfType(m.service, storedType(m, name), root)))
584
+ for (const r of mintRows(m, name, root, snapshot))
543
585
  if (/^\d+$/.test(r.id)) {
544
586
  const v = BigInt(r.id);
545
587
  if (v > max)
@@ -555,9 +597,9 @@ function mintSnowflake(m, epoch, root, at, snapshot) {
555
597
  }
556
598
  /** The next KSUID for a resource (Clerk's and Svix's ids, `ksuidFrom`): the World's instant, then the type's mint
557
599
  * count, so a frozen clock still lists them as made; past any the World already holds. */
558
- function mintKsuid(m, resource, at, root, prefix = '', snapshot, width = 27) {
559
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
560
- const seconds = Math.floor((Date.parse(at ?? '') || 0) / 1000);
600
+ function mintKsuid(m, resource, at, root, prefix, snapshot, width) {
601
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
602
+ const seconds = Math.floor((Date.parse(at) || 0) / 1000);
561
603
  for (let n = held.size + 1;; n++) {
562
604
  const id = `${prefix}${ksuidFrom(seconds, n, `${m.service}:${resource}:${n}`, width)}`;
563
605
  if (!held.has(id))
@@ -566,8 +608,8 @@ function mintKsuid(m, resource, at, root, prefix = '', snapshot, width = 27) {
566
608
  }
567
609
  /** The next id of `count` lowercase letters for a resource, derived from the resource and the count before it, past
568
610
  * any the World already holds. */
569
- function mintLetters(m, resource, count, root, prefix = '', snapshot) {
570
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
611
+ function mintLetters(m, resource, count, root, prefix, snapshot) {
612
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
571
613
  for (let n = held.size + 1;; n++) {
572
614
  const id = `${prefix}${lettersFrom(`${m.service}:${resource}:${n}`, count)}`;
573
615
  if (!held.has(id))
@@ -576,8 +618,8 @@ function mintLetters(m, resource, count, root, prefix = '', snapshot) {
576
618
  }
577
619
  /** The next hex id for a resource: N hex digits derived from the resource and the count before it, past any the World
578
620
  * already holds (a Cloudflare id, 32). */
579
- function mintHex(m, resource, count, root, prefix = '', snapshot) {
580
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
621
+ function mintHex(m, resource, count, root, prefix, snapshot) {
622
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
581
623
  for (let n = held.size + 1;; n++) {
582
624
  const id = `${prefix}${sha256(`${m.service}:${resource}:${n}`).slice(0, count)}`;
583
625
  if (!held.has(id))
@@ -587,8 +629,7 @@ function mintHex(m, resource, count, root, prefix = '', snapshot) {
587
629
  /** The next UUID for a resource: a version-4-shaped UUID derived from the resource and the count before it, past any
588
630
  * the World already holds. */
589
631
  function mintUuid(m, resource, root, snapshot) {
590
- const type = storedType(m, resource);
591
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === type) : resourcesOfType(m.service, type, root)).map((r) => r.id));
632
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
592
633
  for (let n = held.size + 1;; n++) {
593
634
  const id = uuidFrom(`${m.service}:${resource}:${n}`);
594
635
  if (!held.has(id))
@@ -849,14 +890,15 @@ async function finishWrite(m, call, resource, row, result, operation, params, ro
849
890
  const request = requestOfCall.get(call) ?? call.request;
850
891
  const context = () => contextFor(m, { ...call, request: request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
851
892
  if (m.onWrite)
852
- await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
893
+ await m.onWrite({ operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
853
894
  if (m.events) {
854
- const events = m.events;
855
- const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
895
+ const endpoint = (kind) => ({ ...kind, storedAs: storedType(m, kind.storedAs), ...(kind.joins ? { joins: Object.fromEntries(Object.entries(kind.joins).map(([name, join]) => [name, { ...join, storedAs: storedType(m, join.storedAs) }])) } : {}) });
896
+ const events = { ...m.events, ...(m.events.store ? { store: storedType(m, m.events.store) } : {}), endpoints: 'storedAs' in m.events.endpoints ? endpoint(m.events.endpoints) : m.events.endpoints.map(endpoint) };
897
+ const write = { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
856
898
  await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined);
857
899
  }
858
900
  if (m.sockets?.length)
859
- socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
901
+ socketWrite(m.service, root, { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
860
902
  // a read held until the World writes (held-reads.ts) hears it
861
903
  heldReadsWrite(m.service, root, storedType(m, resource));
862
904
  // the subject's id as it stands after the write: the vendor's, when a live head minted one
@@ -1018,8 +1060,8 @@ function adoptedPath(m, call, root) {
1018
1060
  /** A call's fields with each declared reference that names an adopted subject by its local id moved to the adopted
1019
1061
  * id. A call (a handler's operation) need not say which subject type it writes, so every reference the pack declares
1020
1062
  * is tried: a field is rewritten only when its value is a local id the alias map holds for the reference's target. */
1021
- function adoptedInCall(service, fields, root) {
1022
- const refs = packReferences(service);
1063
+ function adoptedInCall(m, fields, root) {
1064
+ const service = m.service, refs = packReferences(service);
1023
1065
  if (refs.length === 0)
1024
1066
  return fields;
1025
1067
  const aliases = aliasesNow(service, root);
@@ -1028,14 +1070,14 @@ function adoptedInCall(service, fields, root) {
1028
1070
  let out = fields;
1029
1071
  for (const ref of refs) {
1030
1072
  const local = ref.key(out);
1031
- const adopted = local === undefined ? undefined : aliases.get(`${ref.to}:${local}`);
1073
+ const adopted = local === undefined ? undefined : aliases.get(`${storedType(m, ref.to)}:${local}`);
1032
1074
  if (adopted !== undefined && adopted !== local)
1033
1075
  out = { ...out, ...ref.adopt(out, adopted) };
1034
1076
  }
1035
1077
  return out;
1036
1078
  }
1037
1079
  async function boundaryParams(m, call, root) {
1038
- return adoptedInCall(m.service, await readParams(m, call.request, call.operation), root);
1080
+ return adoptedInCall(m, await readParams(m, call.request, call.operation), root);
1039
1081
  }
1040
1082
  /** The refusal a subject's fields would draw from its resource's `unique` rules: another live subject (not `except`)
1041
1083
  * already holds every field of a rule, each field given. */
@@ -1237,7 +1279,7 @@ export async function serveCore(m, call, scope = {}) {
1237
1279
  const parent = parentOfCall(decl, call);
1238
1280
  const parentId = parent ? parentValue(parent, call.params) : undefined;
1239
1281
  // the caller's tenant, when the vendor has tenancy and the resource is not shared by every tenant
1240
- const tenantField = m.tenant && !decl.shared ? m.tenant.field : undefined;
1282
+ const tenantField = m.tenant?.storage !== 'namespaced' && m.tenant && !decl.shared ? m.tenant.field : undefined;
1241
1283
  const tenant = tenantField && scope.tenant ? await scope.tenant(await contextFor(m, { ...call, request: call.request.clone() }, scope)) : undefined;
1242
1284
  if (tenantField && tenant === undefined)
1243
1285
  return { unmodeled: `the caller names no tenant (semantics/tenant.ts) for ${resource}` };
@@ -1433,7 +1475,7 @@ export async function contextFor(m, call, scope) {
1433
1475
  sentOf.set(call, { ...sentRequest(m, asAsked, parts ? Object.fromEntries(parts.filter((p) => p.filename === null).map((p) => [p.name, new TextDecoder().decode(p.body)])) : raw ?? body), ...(parts ? { parts } : {}), ...(raw ? { type: call.request.headers.get('content-type') ?? undefined } : {}) });
1434
1476
  // a handler reading the body itself sees the adopted id too (the boundary's one resolution, below)
1435
1477
  if (body && typeof body === 'object' && !Array.isArray(body))
1436
- body = adoptedInCall(m.service, body, root);
1478
+ body = adoptedInCall(m, body, root);
1437
1479
  // every read of the body is of a copy, so the handler's own `call.request` and the core's answer (`core()`) each
1438
1480
  // still find it unread, whichever of them reads it first
1439
1481
  const pristine = call.request.clone();
@@ -1450,6 +1492,13 @@ export async function contextFor(m, call, scope) {
1450
1492
  const rawResources = new WeakMap();
1451
1493
  const rawRow = (resource, row) => {
1452
1494
  const name = m.resources[resource] ? resource : Object.keys(m.resources).find((name) => storedType(m, name) === row.type);
1495
+ if (m.tenant?.storage === 'namespaced') {
1496
+ const [base, encoded] = row.type.split(TENANT_TYPE);
1497
+ const owner = m.resources[name ?? resource]?.shared && row[m.tenant.field] !== undefined ? row[m.tenant.field] : encoded ? decodeURIComponent(encoded) : 'default';
1498
+ row = copyResource(row);
1499
+ row.type = base;
1500
+ row[m.tenant.field] = owner;
1501
+ }
1453
1502
  rawResources.set(row, name ?? resource);
1454
1503
  return row;
1455
1504
  };
@@ -1468,7 +1517,7 @@ export async function contextFor(m, call, scope) {
1468
1517
  git: (name) => {
1469
1518
  if (!/^[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.-]+)*$/.test(name) || name.split('/').some((seg) => seg === '.' || seg === '..'))
1470
1519
  throw new Error(`semantics: ${name} is not a git repository name`);
1471
- const plane = `${worldPaths(m.service, root).dir}/git`;
1520
+ const plane = `${worldPaths(m.service, root).dir}/git${accountScope(m) && accountScope(m) !== 'default' ? `/accounts/${encodeURIComponent(accountScope(m))}` : ''}`;
1472
1521
  return { store: new GitObjectStore(getActiveBlobStore(), `${plane}/repos/${name}`), refs: new GitRefs(getActiveWorldStore(), `${plane}/repos/${name}`) };
1473
1522
  },
1474
1523
  mail: (route, mail, headers) => sendMail(route, mail, headers),
@@ -1508,17 +1557,21 @@ export async function contextFor(m, call, scope) {
1508
1557
  return value;
1509
1558
  },
1510
1559
  occurredAt: at,
1511
- secret: async (label) => hmac(await worldSeed(m.service, root, at), label, 'base64url'),
1560
+ secret: async (label) => hmac(await worldSeed(m.service, root, at), scopeLabel(m, label), 'base64url'),
1561
+ account: async (field = 'account') => {
1562
+ const fields = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
1563
+ return worldAccount(m.service, root, fields[field] ?? 'default', at, call.request.headers.get('x-volter-world-activity') === 'provisioning');
1564
+ },
1512
1565
  record: async (type, fields, id, operation) => {
1513
1566
  if (!type.startsWith('_'))
1514
1567
  throw new Error(`semantics: ${type} is not a bookkeeping type (bookkeeping types start with _)`);
1515
1568
  // a bookkeeping subject a resource counts (`counts` naming it: sentry's issue users) keeps that count as any
1516
1569
  // child does, the parent it left recounted too
1517
- const keeps = counted(m, type);
1570
+ const keeps = counted(m, type), physicalType = storedType(m, type);
1518
1571
  if (id !== undefined) {
1519
1572
  const before = operation ? stateBeforeWrite(m, type, id, root) : undefined;
1520
- const prior = keeps ? resourcesOfType(m.service, type, root).find((r) => r.id === id) : undefined;
1521
- const { resource: row, result } = await applyTwinWrite(m.service, { operation: operation ?? `${type.slice(1)}.record`, subjectType: type, subjectId: id, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } }, root);
1573
+ const prior = keeps ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === id) : undefined;
1574
+ const { resource: row, result } = await applyTwinWrite(m.service, { operation: operation ?? `${type.slice(1)}.record`, subjectType: physicalType, subjectId: id, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } }, root);
1522
1575
  if (operation)
1523
1576
  observeStateWrite(m, result, before, root, stateChecks);
1524
1577
  if (keeps)
@@ -1531,7 +1584,7 @@ export async function contextFor(m, call, scope) {
1531
1584
  const prefix = `${type.slice(1)}_`;
1532
1585
  let max = 0n;
1533
1586
  for (const r of resources) {
1534
- if (r.type !== type)
1587
+ if (r.type !== physicalType)
1535
1588
  continue;
1536
1589
  const digits = r.id.startsWith(prefix) ? r.id.slice(prefix.length) : '';
1537
1590
  if (/^\d+$/.test(digits) && BigInt(digits) > max)
@@ -1539,21 +1592,21 @@ export async function contextFor(m, call, scope) {
1539
1592
  }
1540
1593
  return `${prefix}${max + 1n}`;
1541
1594
  })();
1542
- return { kind: 'write', value: subject, write: { operation: operation ?? `${type.slice(1)}.record`, subjectType: type, subjectId: subject, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } } };
1543
- }, root, () => resourcesOfType(m.service, type, root));
1595
+ return { kind: 'write', value: subject, write: { operation: operation ?? `${type.slice(1)}.record`, subjectType: physicalType, subjectId: subject, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } } };
1596
+ }, root, () => resourcesOfType(m.service, physicalType, root));
1544
1597
  if (operation && result)
1545
1598
  observeStateWrite(m, result, undefined, root, stateChecks);
1546
- const made = keeps && value !== undefined ? resourcesOfType(m.service, type, root).find((r) => r.id === value) : undefined;
1599
+ const made = keeps && value !== undefined ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === value) : undefined;
1547
1600
  if (made)
1548
1601
  await keepCounts(m, call, type, made, params, root, at);
1549
1602
  return value;
1550
1603
  },
1551
1604
  get machines() {
1552
1605
  const enrolled = resourcesOfType(m.service, MACHINE_POOL, root).find((r) => r.id === 'pool');
1553
- return guardedMachinePool(poolFor(m.service, root, enrolled?.kind ?? 'none', enrolled?.httpReplies, { images: enrolled?.images }), m.service, ['retrieve', 'list'].includes(call.operation.class) || (m.reads?.includes(call.operation.id) ?? false));
1606
+ return guardedMachinePool(poolFor(m.service, root, enrolled?.kind ?? 'none', enrolled?.httpReplies), m.service, ['retrieve', 'list'].includes(call.operation.class) || (m.reads?.includes(call.operation.id) ?? false));
1554
1607
  },
1555
- signingKey: (label, alg) => worldSigningKey(m.service, root, at, label, alg ?? 'RS256'),
1556
- heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, label, alg ?? 'RS256'),
1608
+ signingKey: (label, alg) => worldSigningKey(m.service, root, at, scopeLabel(m, label), alg ?? 'RS256'),
1609
+ heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, scopeLabel(m, label), alg ?? 'RS256'),
1557
1610
  create: (resource, fields, operation) => atomicWrite(m, call, resource, (snapshot) => {
1558
1611
  const id = mintId(m, resource, root, at, snapshot);
1559
1612
  return { id, fields: typeof fields === 'function' ? fields(id) : fields };
@@ -1566,7 +1619,7 @@ export async function contextFor(m, call, scope) {
1566
1619
  throw new Error('createUnique requires declared unique keys');
1567
1620
  let existing;
1568
1621
  const row = await atomicWrite(m, call, resource, (snapshot) => {
1569
- const id = mintId(m, resource, root, at, snapshot);
1622
+ const id = options?.id ?? mintId(m, resource, root, at, snapshot);
1570
1623
  const selected = typeof fields === 'function' ? fields(id) : fields;
1571
1624
  const hit = snapshot.find((r) => r.type === storedType(m, resource) && r.deleted !== true && rules.some((rule) => rule.fields.every((f) => selected[f] !== undefined && view(m, resource, r)[f] === selected[f])));
1572
1625
  if (hit) {
@@ -1612,7 +1665,17 @@ export async function contextFor(m, call, scope) {
1612
1665
  ok: (fields) => Response.json({ ...(m.success ?? {}), ...fields }),
1613
1666
  row: (resource, id, opts) => { const row = stored(m, resource, root, opts).find((r) => r.id === id); return row ? rawRow(resource, row) : undefined; },
1614
1667
  rowsRaw: (resource, opts) => stored(m, resource, root, opts).map((row) => rawRow(resource, row)),
1615
- tree: () => twinResources(m.service, root),
1668
+ tree: () => {
1669
+ const rows = twinResources(m.service, root);
1670
+ if (m.tenant?.storage !== 'namespaced')
1671
+ return rows;
1672
+ const account = accountScope(m);
1673
+ return rows.filter(row => {
1674
+ const [base, encoded] = row.type.split(TENANT_TYPE);
1675
+ const name = Object.keys(m.resources).find(name => (m.resources[name]?.storedAs ?? name) === base) ?? base;
1676
+ return account === undefined || m.resources[name]?.shared || (encoded ? decodeURIComponent(encoded) : 'default') === account;
1677
+ }).map(row => rawRow(row.type.split(TENANT_TYPE)[0], row));
1678
+ },
1616
1679
  history: (resource, id) => subjectHistory(m.service, { type: storedType(m, resource), id }, root).map((e) => ({ ...(e.operation ? { operation: e.operation } : {}), ...(e.fields ? { fields: e.fields } : {}), ...(e.occurredAt ? { occurredAt: e.occurredAt } : {}) })),
1617
1680
  raw: (body, init = {}) => new Response(body, { status: init.status ?? 200, headers: init.headers ?? {} }),
1618
1681
  atomically: async (decide) => {
@@ -1667,17 +1730,17 @@ export async function contextFor(m, call, scope) {
1667
1730
  engine: scope.database === undefined ? unboundDatabase(m.service) : managedDatabase(scope.database, { readOnly: scope.readOnly === true || isReadOnlyRequest(call.request) }),
1668
1731
  redis: (commands, options) => execRedisRun(commands, {
1669
1732
  root, occurredAt: at, service: m.service, readOnly: scope.readOnly === true || isReadOnlyRequest(call.request), dialect: options.dialect,
1670
- ...(options.database === undefined ? {} : { database: options.database }),
1733
+ ...(accountScope(m) && accountScope(m) !== 'default' ? { database: scopeLabel(m, options.database ?? 'default') } : options.database === undefined ? {} : { database: options.database }),
1671
1734
  ...(options.transaction === undefined ? {} : { transaction: options.transaction }),
1672
1735
  }),
1673
- live: liveAccess({ rows: (type) => resourcesOfType(m.service, type, root), blob: (key) => readResourceBlob(m.service, key, root) }, call.request),
1736
+ live: liveAccess({ rows: (type) => stored(m, type, root).map(row => rawRow(type, row)), blob: (key) => readResourceBlob(m.service, scopeLabel(m, key), root) }, call.request),
1674
1737
  blobs: {
1675
- put: (key, bytes) => writeResourceBlob(m.service, key, bytes, root),
1676
- putStream: (key, body, accept) => writeResourceBlobStream(m.service, key, body, accept, root),
1677
- get: (key) => readResourceBlob(m.service, key, root),
1678
- remove: (key) => removeResourceBlob(m.service, key, root),
1679
- size: (key) => resourceBlobSize(m.service, key, root),
1680
- range: (key, start, endInclusive) => readResourceBlobRange(m.service, key, start, endInclusive, root),
1738
+ put: (key, bytes) => writeResourceBlob(m.service, scopeLabel(m, key), bytes, root),
1739
+ putStream: (key, body, accept) => writeResourceBlobStream(m.service, scopeLabel(m, key), body, accept, root),
1740
+ get: (key) => readResourceBlob(m.service, scopeLabel(m, key), root),
1741
+ remove: (key) => removeResourceBlob(m.service, scopeLabel(m, key), root),
1742
+ size: (key) => resourceBlobSize(m.service, scopeLabel(m, key), root),
1743
+ range: (key, start, endInclusive) => readResourceBlobRange(m.service, scopeLabel(m, key), start, endInclusive, root),
1681
1744
  },
1682
1745
  merge: (resource, prior, next) => mergeField(m.resources[resource]?.update, prior, next),
1683
1746
  conflict: (resource, fields, except) => {
@@ -2005,7 +2068,7 @@ export function authRefusal(m, request, root) {
2005
2068
  const held = m.auth.held;
2006
2069
  if (held && !held.standing?.includes(key)) {
2007
2070
  const hash = sha256(key);
2008
- const holds = resourcesOfType(m.service, held.storedAs, root).some((r) => r.deleted !== true && r[held.hashField] === hash && (held.unless === undefined || r[held.unless] !== true));
2071
+ const holds = stored(m, held.storedAs, root).some((r) => r.deleted !== true && r[held.hashField] === hash && (held.unless === undefined || r[held.unless] !== true));
2009
2072
  if (!holds)
2010
2073
  return vendorError(errorManifest, invalid);
2011
2074
  }
@@ -2101,7 +2164,7 @@ export function crossCutting(m, opts = {}) {
2101
2164
  const url = new URL(request.url);
2102
2165
  const signature = hashFieldValue({ path: url.pathname + url.search, body: await bodyTextOf(request.clone()) });
2103
2166
  const id = `idem_${hex(key)}`;
2104
- const prior = twinResources(m.service, opts.root).find((r) => r.type === m.idempotency.storedAs && r.id === id);
2167
+ const prior = twinResources(m.service, opts.root).find((r) => r.type === storedType(m, m.idempotency.storedAs) && r.id === id);
2105
2168
  if (prior) {
2106
2169
  if (m.idempotency.conflict && prior.requestHash !== undefined && prior.requestHash !== signature)
2107
2170
  return vendorError(m, m.idempotency.conflict);
@@ -2117,7 +2180,7 @@ export function crossCutting(m, opts = {}) {
2117
2180
  return response;
2118
2181
  const contentType = response.headers.get('content-type') ?? 'application/json';
2119
2182
  const text = await response.clone().text();
2120
- await applyTwinWrite(m.service, { operation: 'idempotency.record', subjectType: m.idempotency.storedAs, subjectId: id, fields: { response: { status: response.status, text, contentType }, requestHash: signature }, occurredAt: (opts.clock ?? worldNow)(), actor: { kind: 'system' } }, opts.root);
2183
+ await applyTwinWrite(m.service, { operation: 'idempotency.record', subjectType: storedType(m, m.idempotency.storedAs), subjectId: id, fields: { response: { status: response.status, text, contentType }, requestHash: signature }, occurredAt: (opts.clock ?? worldNow)(), actor: { kind: 'system' } }, opts.root);
2121
2184
  return response;
2122
2185
  }
2123
2186
  }
@@ -47,8 +47,8 @@ export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from './twin-fetch.j
47
47
  export { statefulTwinManifest, twinManifest } from './scenario.js';
48
48
  export { assetContentType, packAsset } from './pack-assets.js';
49
49
  export { bytesResponse, contentTypeOf, fileResponse } from './file-response.js';
50
- export { certificateOf, digest, digestBytes, equalSecrets, oauth1BaseString, oauth1Encode, oauth1Header, oauth1Signature, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, base62From, md5, publicKeyOf, sha256, signWith, SIGNING_ALGORITHMS, uuidFrom, verifyWith } from './signing.js';
51
- export type { HandlerCrypto, Jwk, Jwks, JwtVerdict, SigningKey } from './signing.js';
50
+ export { certificateOf, digest, digestBytes, equalSecrets, oauth1BaseString, oauth1Encode, oauth1Header, oauth1Signature, hmac, jwks, jwtDecode, jwtSign, jwtSignClaims, jwtVerify, jwtVerifySignature, keyId, lettersFrom, base62From, md5, publicKeyOf, sha256, signWith, SIGNING_ALGORITHMS, uuidFrom, verifyWith } from './signing.js';
51
+ export type { HandlerCrypto, Jwk, Jwks, JwtDecodeOptions, JwtSignatureOptions, JwtVerdict, JwtVerificationKey, SigningKey } from './signing.js';
52
52
  export { blobDigest } from './blob-store.js';
53
53
  export { gunzip, gzip } from './bytes.js';
54
54
  export { ownFields } from './log.js';
@@ -81,3 +81,5 @@ export { readHostPort } from './host-port.js';
81
81
  export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost, type BrowserCookie, type BrowserState, type BrowserStorageState, type BrowserContext, type BrowserIndexedDB } from './browser-state.js';
82
82
  export * as managedOriginalInputs from './managed-original-inputs.js';
83
83
  export * as managedGoPrograms from './managed-go-programs.js';
84
+ export { worldSecret, worldAccount } from './derived-core.js';
85
+ export { isWorldAccountName } from './world-account-name.js';
package/dist/src/index.js CHANGED
@@ -37,7 +37,7 @@ export { statefulTwinManifest, twinManifest } from "./scenario.js";
37
37
  export { assetContentType, packAsset } from "./pack-assets.js";
38
38
  export { bytesResponse, contentTypeOf, fileResponse } from "./file-response.js";
39
39
  // ── the kernel's deterministic helpers ─────────────────────────────────────────────────────────
40
- export { certificateOf, digest, digestBytes, equalSecrets, oauth1BaseString, oauth1Encode, oauth1Header, oauth1Signature, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, base62From, md5, publicKeyOf, sha256, signWith, SIGNING_ALGORITHMS, uuidFrom, verifyWith } from "./signing.js";
40
+ export { certificateOf, digest, digestBytes, equalSecrets, oauth1BaseString, oauth1Encode, oauth1Header, oauth1Signature, hmac, jwks, jwtDecode, jwtSign, jwtSignClaims, jwtVerify, jwtVerifySignature, keyId, lettersFrom, base62From, md5, publicKeyOf, sha256, signWith, SIGNING_ALGORITHMS, uuidFrom, verifyWith } from "./signing.js";
41
41
  export { blobDigest } from "./blob-store.js";
42
42
  // compressed bytes a wire carries (a SARIF upload), over web streams
43
43
  export { gunzip, gzip } from "./bytes.js";
@@ -73,3 +73,5 @@ export { readHostPort } from "./host-port.js";
73
73
  export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost } from "./browser-state.js";
74
74
  export * as managedOriginalInputs from "./managed-original-inputs.js";
75
75
  export * as managedGoPrograms from "./managed-go-programs.js";
76
+ export { worldSecret, worldAccount } from "./derived-core.js";
77
+ export { isWorldAccountName } from "./world-account-name.js";
@@ -17,18 +17,6 @@ export type MachineSpec = {
17
17
  signal?: AbortSignal;
18
18
  startTimeoutMs?: number;
19
19
  };
20
- export type GuestImage = {
21
- context: string;
22
- dockerfile?: string;
23
- runtime?: 'node' | 'bun' | 'python' | 'deno';
24
- command?: string[];
25
- cwd?: string;
26
- env?: Record<string, string>;
27
- prepared?: boolean;
28
- };
29
- export type LightPoolOptions = {
30
- images?: Record<string, GuestImage>;
31
- };
32
20
  export type MachineRun = {
33
21
  ports: Array<{
34
22
  internal: number;
@@ -85,7 +73,7 @@ export type MachineHttpReply = {
85
73
  body?: string;
86
74
  };
87
75
  export type MachinePool = {
88
- readonly kind: 'docker' | 'none' | 'fake' | 'light';
76
+ readonly kind: 'docker' | 'none' | 'fake';
89
77
  /** Optional capability admission, before vendor resource creation; starts no process. */
90
78
  admitRun?(spec: MachineSpec): Promise<void>;
91
79
  /** runs the image; a failure (no container runtime, an image that will not start) rejects with the runtime's message */
@@ -115,10 +103,10 @@ export type MachinePool = {
115
103
  /** A declared compute capability gap, distinct from a failed admitted guest. */
116
104
  export declare function isMachineCapabilityGap(error: unknown): boolean;
117
105
  export declare const MACHINE_POOL = "_machine_pool";
118
- export declare const POOL_KINDS: readonly ["docker", "none", "fake", "light"];
106
+ export declare const POOL_KINDS: readonly ["docker", "none", "fake"];
119
107
  /** The pool of a kind, for a World (`world`: the mark its containers carry). */
120
- export declare function machinePool(kind: MachinePool['kind'], world?: string, httpReplies?: readonly MachineHttpReply[], light?: LightPoolOptions, root?: string): MachinePool;
121
- export declare function poolFor(service: string, root: string | undefined, kind: MachinePool['kind'], httpReplies?: readonly MachineHttpReply[], light?: LightPoolOptions): MachinePool;
108
+ export declare function machinePool(kind: MachinePool['kind'], world?: string, httpReplies?: readonly MachineHttpReply[]): MachinePool;
109
+ export declare function poolFor(service: string, root: string | undefined, kind: MachinePool['kind'], httpReplies?: readonly MachineHttpReply[]): MachinePool;
122
110
  /** Re-enrolling a verification pool starts a fresh fake execution record. */
123
111
  export declare function resetFakePool(service: string, root: string | undefined): void;
124
112
  /** Every machine mutation shares the tree's refusal seam. HTTP/RPC reads must be explicitly
@@ -1,17 +1,9 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
1
  // The World's machine pool (architecture, "Other wires: … machines"): what runs a vendor's customer's image when the
10
2
  // vendor is one that runs images (Fly's Machines). It is a platform provider enrolled through the kernel's door
11
3
  // `POST /_twin/machine-pool`, recorded in the pack's own tree; a handler reaches it as `ctx.machines`. `docker` runs
12
4
  // each machine as a local container with its internal ports published on loopback; `none` runs nothing; `fake`
13
5
  // records what it was asked (capability verification). Nothing here runs at module scope, and `node:child_process`
14
- // is reached only when a compute pool is asked to act. The light pool delegates to the host's Substrate adapter.
6
+ // is reached only when a compute pool is asked to act.
15
7
  import { refuseReadOnlyWrite } from "./request-scope.js";
16
8
  import { nodeBuiltin } from "./serve-http.js";
17
9
  import { refuseRequiredMachineTransport } from '../required-credential-transport.cjs';
@@ -20,33 +12,7 @@ export function isMachineCapabilityGap(error) {
20
12
  return typeof error === 'object' && error !== null && 'code' in error && error.code === 'WORLD_MACHINE_CAPABILITY_GAP';
21
13
  }
22
14
  export const MACHINE_POOL = '_machine_pool';
23
- export const POOL_KINDS = ['docker', 'none', 'fake', 'light'];
24
- /** The host owns provider bootstrapping. Browser/kernel imports stay inert, and a missing
25
- * host provider refuses rather than falling back to metadata-only success. */
26
- function lightPool(world, root, options) {
27
- let ready;
28
- const load = () => ready ??= (async () => {
29
- const entry = typeof process === 'undefined' ? undefined : process.env.VOLTER_WORLD_GUEST_RUNNER;
30
- if (!entry)
31
- throw new Error('light guest runner is unavailable: run this twin inside a World');
32
- const provider = await import(__rewriteRelativeImportExtension(entry));
33
- return provider.createLightGuestPool({ world, root, ...options });
34
- })();
35
- return {
36
- kind: 'light',
37
- admitRun: async (spec) => { await (await load()).admitRun?.(spec); },
38
- run: async (spec) => (await load()).run(spec),
39
- exec: async (name, spec) => (await load()).exec(name, spec),
40
- request: async (name, port, request) => (await load()).request(name, port, request),
41
- status: async (name) => (await load()).status(name),
42
- ports: async (name) => (await load()).ports(name),
43
- files: async (name) => (await load()).files(name),
44
- pause: async (name) => (await load()).pause(name),
45
- resume: async (name) => (await load()).resume(name),
46
- stop: async (name) => (await load()).stop(name),
47
- remove: async (name) => (await load()).remove(name),
48
- };
49
- }
15
+ export const POOL_KINDS = ['docker', 'none', 'fake'];
50
16
  /** A container's name for a machine: the World's prefix, the World's own mark (a machine id repeats across Worlds and
51
17
  * branches, which mint alike), then the vendor's machine name made safe for docker. */
52
18
  const containerName = (world, name) => `volter-world-machine-${world}-${name.replace(/[^A-Za-z0-9_.-]/g, '-')}`;
@@ -66,8 +32,8 @@ function dockerPool(world) {
66
32
  const state = JSON.parse(await docker(['inspect', '--format', '{{json .State}}', containerName(world, name)]));
67
33
  return state.Running ? { state: 'started' } : { state: 'exited', exitCode: state.ExitCode, ...(state.Error ? { reason: state.Error } : {}) };
68
34
  },
69
- async ports() { throw new Error('port discovery requires the light guest backing'); },
70
- async files() { throw new Error('file access requires the light guest backing'); },
35
+ async ports() { throw new Error('port discovery is unavailable'); },
36
+ async files() { throw new Error('file access is unavailable'); },
71
37
  async run(spec) {
72
38
  refuseRequiredMachineTransport(process.env, 'Docker machine application transport');
73
39
  const name = containerName(world, spec.name);
@@ -175,9 +141,9 @@ function dockerPool(world) {
175
141
  };
176
142
  }
177
143
  /** The pool of a kind, for a World (`world`: the mark its containers carry). */
178
- export function machinePool(kind, world = 'world', httpReplies = [], light = {}, root) {
179
- if (kind === 'light')
180
- return lightPool(world, root, light);
144
+ export function machinePool(kind, world = 'world', httpReplies = []) {
145
+ if (!POOL_KINDS.includes(kind))
146
+ throw new Error(`kind must be one of ${POOL_KINDS.join(', ')}`);
181
147
  if (kind === 'docker')
182
148
  return dockerPool(world);
183
149
  if (kind === 'fake') {
@@ -231,15 +197,15 @@ export function machinePool(kind, world = 'world', httpReplies = [], light = {},
231
197
  }
232
198
  /** A vendor's pool in a World, one per World and kind for the life of the process (a fake pool keeps what it was asked). */
233
199
  const pools = new Map();
234
- export function poolFor(service, root, kind, httpReplies = [], light = {}) {
235
- const key = `${service}\0${root ?? ''}\0${kind}${kind === 'light' ? JSON.stringify(light) : ''}`;
200
+ export function poolFor(service, root, kind, httpReplies = []) {
201
+ const key = `${service}\0${root ?? ''}\0${kind}`;
236
202
  let pool = pools.get(key);
237
203
  if (!pool) {
238
204
  // the World's mark: a short digest of the vendor and the World's tree, so two Worlds' machines never share a name
239
205
  let h = 0;
240
206
  for (const ch of `${service}:${root ?? ''}`)
241
207
  h = (Math.imul(h, 31) + ch.charCodeAt(0)) >>> 0;
242
- pool = machinePool(kind, h.toString(16).padStart(8, '0'), httpReplies, light, root);
208
+ pool = machinePool(kind, h.toString(16).padStart(8, '0'), httpReplies);
243
209
  pools.set(key, pool);
244
210
  }
245
211
  return pool;