@volter/world-core 3.0.161 → 3.0.163

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
@@ -35,6 +37,7 @@ import { vendorFetch } from "./vendor-call.js";
35
37
  import { packReferences } from "./references.js";
36
38
  import { twinPublicBase, withRequestScopes } from "./twin-fetch.js";
37
39
  import { inVendorMove, isReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
40
+ import { getPack } from "./packRegistry.js";
38
41
  import { bodyTextOf, checkJsonBody, takeParsedBody } from "./derived.js";
39
42
  /** What a move to `to` stores in the field: a boolean field's value is a boolean, and a derived field's
40
43
  * value is written by the move's effects (a state name is not a timestamp), so it stores nothing itself. */
@@ -235,7 +238,13 @@ function notFound(manifest, object, id, param) {
235
238
  return vendorError(manifest, { status: nf.status, message: String(fill(message, { object, id })), ...(code ? { code } : {}), ...(nf.kind ? { kind: nf.kind } : {}), ...(param ? { param } : {}) });
236
239
  }
237
240
  // ── the tree ─────────────────────────────────────────────────────────────────────────────────
238
- export const storedType = (m, resource) => m.resources[resource]?.storedAs ?? resource;
241
+ const TENANT_TYPE = '::account:';
242
+ const accountScope = (m) => m.tenant?.storage === 'namespaced' ? storageTenant(m.service) : undefined;
243
+ const scopeLabel = (m, label) => { const account = accountScope(m); return account && account !== 'default' ? `account:${account}:${label}` : label; };
244
+ export const storedType = (m, resource) => {
245
+ const base = m.resources[resource]?.storedAs ?? resource, account = accountScope(m);
246
+ return account && account !== 'default' && !m.resources[resource]?.shared ? `${base}${TENANT_TYPE}${encodeURIComponent(account)}` : base;
247
+ };
239
248
  // A service's resources by type, folded once per World state (the tree's stamp; any write moves it), not on every read:
240
249
  // a read of one resource type projected the service's whole tree, so a pack whose tree also holds many subjects of
241
250
  // other types (PlanetScale's SQL rows beside its management records) paid for all of them on each read. A read gets its
@@ -254,9 +263,15 @@ export function resourcesOfType(service, type, root) {
254
263
  if (held && delta) {
255
264
  // the index keeps the tree's order: a subject updated where it stands stays there, one new to the tree (or made
256
265
  // 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);
266
+ for (const k of [...delta.removed, ...delta.appended]) {
267
+ const type = held.typeByKey.get(k);
268
+ if (type !== undefined)
269
+ held.byType.get(type)?.delete(k);
270
+ held.typeByKey.delete(k);
271
+ }
259
272
  const byKey = new Map(delta.changed.map((r) => [`${r.type}:${r.id}`, r]));
273
+ for (const [k, r] of byKey)
274
+ held.typeByKey.set(k, r.type);
260
275
  for (const [k, r] of byKey)
261
276
  if (!delta.appended.includes(k))
262
277
  (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)).set(k, r);
@@ -273,7 +288,7 @@ export function resourcesOfType(service, type, root) {
273
288
  const byType = new Map();
274
289
  for (const r of twinResources(service, root))
275
290
  (byType.get(r.type) ?? byType.set(r.type, new Map()).get(r.type)).set(`${r.type}:${r.id}`, r);
276
- held = { stamp, byType };
291
+ held = { stamp, byType, typeByKey: new Map([...byType].flatMap(([type, rows]) => [...rows.keys()].map(key => [key, type]))) };
277
292
  indexes.set(key, held);
278
293
  }
279
294
  }
@@ -282,7 +297,9 @@ export function resourcesOfType(service, type, root) {
282
297
  function stored(m, resource, root, opts = {}) {
283
298
  const type = storedType(m, resource);
284
299
  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));
300
+ const rows = m.tenant?.storage === 'namespaced' && accountScope(m) === undefined && !m.resources[resource]?.shared
301
+ ? twinResources(m.service, root).filter(row => row.type === type || row.type.startsWith(`${type}${TENANT_TYPE}`)) : resourcesOfType(m.service, type, root);
302
+ return rows.filter((r) => (opts.withDeleted || r.deleted !== true) && when.every(([k, v]) => r[k] === v));
286
303
  }
287
304
  /** The subject an item operation names: the manifest's key over the path parameters, or the last one. */
288
305
  function subjectOf(m, resource, call) {
@@ -317,7 +334,7 @@ function missingParent(m, resource, call, root, tenant) {
317
334
  const id = parentValue(parent, call.params);
318
335
  const where = Object.entries(parent.where ?? {}).map(([field, template]) => [field, String(fill(template, call.params))]);
319
336
  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);
337
+ && (!m.tenant || m.tenant.storage === 'namespaced' || m.resources[parent.resource]?.shared || r[m.tenant.field] === tenant);
321
338
  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
339
  }
323
340
  /** The vendor's view of a stored subject: its own fields, bookkeeping (`_`) left out. */
@@ -430,14 +447,84 @@ function worldSeed(service, root, at) {
430
447
  if (pending)
431
448
  return pending;
432
449
  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));
450
+ // The action lock also joins first asks from several renderers/processes to the same seed.
451
+ const { value } = await runAsVendorMove(() => applyTwinWriteAtomic(service, snapshot => {
452
+ const existing = snapshot.find(row => row.type === SEED && row.id === 'seed');
453
+ if (typeof existing?.value === 'string')
454
+ return { kind: 'skip', value: existing.value };
455
+ const value = Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');
456
+ return { kind: 'write', value, write: { operation: 'world_seed.record', subjectType: SEED, subjectId: 'seed', fields: { value }, occurredAt: at, actor: { kind: 'system' } } };
457
+ }, root));
436
458
  return value;
437
459
  })().finally(() => seeding.delete(key));
438
460
  seeding.set(key, made);
439
461
  return made;
440
462
  }
463
+ /** World-owned generated values use the same private seed and log as pack secrets. */
464
+ export async function worldSecret(service, root, label, at = new Date().toISOString()) {
465
+ return hmac(await worldSeed(service, root, at), label, 'base64url');
466
+ }
467
+ /** The same retained account primitive for pack doors and private native twin fronts. */
468
+ export async function worldAccount(service, root, id = 'default', at = worldNow(), provisioning = false) {
469
+ if (!isWorldAccountName(id))
470
+ throw new Error('Credential account must be a name of at most 128 letters, digits, dots, underscores or hyphens');
471
+ await applyTwinWriteAtomic(service, (snapshot) => {
472
+ const held = snapshot.find(row => row.type === '_world_account' && row.id === id);
473
+ 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' } } };
474
+ }, root);
475
+ return id;
476
+ }
477
+ function nativeAccountLink(row, field) {
478
+ if (!row || row.deleted === true)
479
+ return undefined;
480
+ const fields = ownFields(row);
481
+ const value = field === undefined ? fields : fields[field];
482
+ if (!value || typeof value !== 'object' || Array.isArray(value))
483
+ return undefined;
484
+ const link = value;
485
+ return typeof link.resource === 'string' && typeof link.id === 'string' ? { resource: link.resource, id: link.id } : undefined;
486
+ }
487
+ const sameAccountLink = (a, b) => a?.resource === b?.resource && a?.id === b?.id;
488
+ /** Native creation and reconstruction run outside the action lock; only the retained link is compared and written. */
489
+ async function retainNativeAccountLink(service, root, at, call, type, id, field, resource, make, read) {
490
+ for (;;) {
491
+ const before = nativeAccountLink(resourcesOfType(service, type, root).find((row) => row.id === id), field);
492
+ const held = before && await read(before);
493
+ if (held !== undefined)
494
+ return held;
495
+ const made = { resource, id: await make() };
496
+ if (await read(made) === undefined)
497
+ throw new Error(`Credential ${type}/${id}: native ${resource}/${made.id} is unavailable after creation`);
498
+ const { value } = await applyTwinWriteAtomic(service, (snapshot) => {
499
+ const current = nativeAccountLink(snapshot.find((row) => row.type === type && row.id === id), field);
500
+ if (!sameAccountLink(current, before))
501
+ return { kind: 'skip', value: current };
502
+ return {
503
+ kind: 'write', value: made,
504
+ write: {
505
+ operation: `${type.slice(1)}.link`, subjectType: type, subjectId: id,
506
+ fields: field === undefined ? made : { name: id, [field]: made },
507
+ ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' },
508
+ },
509
+ };
510
+ }, root);
511
+ const linked = value && await read(value);
512
+ if (linked !== undefined)
513
+ return linked;
514
+ }
515
+ }
516
+ function vendorName(label, rule, { suffixed = false, identity = label } = {}) {
517
+ const allowed = new RegExp(rule.allowed.source, rule.allowed.flags.replace(/[gy]/g, ''));
518
+ const separator = rule.separator ?? '-';
519
+ if ([...separator].length !== 1 || !allowed.test(separator))
520
+ throw new Error('Credential name separator must be one allowed character');
521
+ const first = rule.first && new RegExp(rule.first.source, rule.first.flags.replace(/[gy]/g, ''));
522
+ let name = [...(rule.lower ? label.toLowerCase() : label)].map((letter) => allowed.test(letter) ? letter : separator).join('');
523
+ name = name.replace(new RegExp(`${escapeRe(separator)}+`, 'g'), separator);
524
+ if (first && !first.test(name[0] ?? ''))
525
+ name = `w${separator}${name}`;
526
+ return name !== label || name.length > rule.max || suffixed ? `${name.slice(0, Math.max(0, rule.max - 9))}${separator}${sha256(identity).slice(0, 8)}` : name;
527
+ }
441
528
  /** A World's signing key for a label (ctx.signingKey): made once from the platform's random source, then read; the
442
529
  * making is one per service store and label in the process, as the seed's is. */
443
530
  const SIGNING_KEY = '_world_signing_key';
@@ -472,6 +559,13 @@ function worldSigningKey(service, root, at, label, alg) {
472
559
  keying.set(key, made);
473
560
  return made;
474
561
  }
562
+ /** Vendor ids remain unique across its accounts, including public links that carry an id instead of a key. */
563
+ function mintRows(m, resource, root, snapshot) {
564
+ const base = m.resources[resource]?.storedAs ?? resource;
565
+ if (m.tenant?.storage === 'namespaced')
566
+ return (snapshot ?? twinResources(m.service, root)).filter(row => row.type === base || row.type.startsWith(`${base}${TENANT_TYPE}`));
567
+ return snapshot ? snapshot.filter(row => row.type === base) : resourcesOfType(m.service, base, root);
568
+ }
475
569
  function mintId(m, resource, root, at, snapshot) {
476
570
  if (!m.resources[resource])
477
571
  throw new Error(`mint: ${resource} is not a resource ${m.vendor}'s manifest declares`);
@@ -495,7 +589,7 @@ function mintId(m, resource, root, at, snapshot) {
495
589
  const width = Number(digits[1]);
496
590
  if (!Number.isInteger(width) || width < 1 || width > 77)
497
591
  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));
592
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
499
593
  const floor = 10n ** BigInt(width - 1);
500
594
  for (let n = held.size + 1;; n++) {
501
595
  const id = String(floor + BigInt('0x' + sha256(`${m.service}:${resource}:${n}`)) % (9n * floor));
@@ -518,7 +612,7 @@ function mintId(m, resource, root, at, snapshot) {
518
612
  const pattern = new RegExp(`^${escapeRe(String(fill(head, { prefix })))}(\\d+)${escapeRe(String(fill(tail, { prefix })))}$`);
519
613
  let max = 0n;
520
614
  // 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))) {
615
+ for (const r of mintRows(m, resource, root, snapshot)) {
522
616
  const hit = pattern.exec(r.id);
523
617
  if (hit && BigInt(hit[1]) > max)
524
618
  max = BigInt(hit[1]);
@@ -533,13 +627,13 @@ function mintId(m, resource, root, at, snapshot) {
533
627
  * and its attachment) must not meet. */
534
628
  const lastSnowflake = new Map();
535
629
  function mintSnowflake(m, epoch, root, at, snapshot) {
536
- const ms = BigInt(Date.parse(at ?? '') || 0);
630
+ const ms = BigInt(Date.parse(at) || 0);
537
631
  const base = ms > epoch ? (ms - epoch) << 22n : 0n;
538
632
  let max = -1n;
539
633
  for (const [name, decl] of Object.entries(m.resources)) {
540
634
  if (!/^\{snowflake:\d+\}$/.test(decl.ids ?? m.ids.template))
541
635
  continue;
542
- for (const r of (snapshot ? snapshot.filter((r) => r.type === storedType(m, name)) : resourcesOfType(m.service, storedType(m, name), root)))
636
+ for (const r of mintRows(m, name, root, snapshot))
543
637
  if (/^\d+$/.test(r.id)) {
544
638
  const v = BigInt(r.id);
545
639
  if (v > max)
@@ -555,9 +649,9 @@ function mintSnowflake(m, epoch, root, at, snapshot) {
555
649
  }
556
650
  /** The next KSUID for a resource (Clerk's and Svix's ids, `ksuidFrom`): the World's instant, then the type's mint
557
651
  * 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);
652
+ function mintKsuid(m, resource, at, root, prefix, snapshot, width) {
653
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
654
+ const seconds = Math.floor((Date.parse(at) || 0) / 1000);
561
655
  for (let n = held.size + 1;; n++) {
562
656
  const id = `${prefix}${ksuidFrom(seconds, n, `${m.service}:${resource}:${n}`, width)}`;
563
657
  if (!held.has(id))
@@ -566,8 +660,8 @@ function mintKsuid(m, resource, at, root, prefix = '', snapshot, width = 27) {
566
660
  }
567
661
  /** The next id of `count` lowercase letters for a resource, derived from the resource and the count before it, past
568
662
  * 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));
663
+ function mintLetters(m, resource, count, root, prefix, snapshot) {
664
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
571
665
  for (let n = held.size + 1;; n++) {
572
666
  const id = `${prefix}${lettersFrom(`${m.service}:${resource}:${n}`, count)}`;
573
667
  if (!held.has(id))
@@ -576,8 +670,8 @@ function mintLetters(m, resource, count, root, prefix = '', snapshot) {
576
670
  }
577
671
  /** The next hex id for a resource: N hex digits derived from the resource and the count before it, past any the World
578
672
  * 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));
673
+ function mintHex(m, resource, count, root, prefix, snapshot) {
674
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
581
675
  for (let n = held.size + 1;; n++) {
582
676
  const id = `${prefix}${sha256(`${m.service}:${resource}:${n}`).slice(0, count)}`;
583
677
  if (!held.has(id))
@@ -587,8 +681,7 @@ function mintHex(m, resource, count, root, prefix = '', snapshot) {
587
681
  /** The next UUID for a resource: a version-4-shaped UUID derived from the resource and the count before it, past any
588
682
  * the World already holds. */
589
683
  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));
684
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
592
685
  for (let n = held.size + 1;; n++) {
593
686
  const id = uuidFrom(`${m.service}:${resource}:${n}`);
594
687
  if (!held.has(id))
@@ -849,14 +942,15 @@ async function finishWrite(m, call, resource, row, result, operation, params, ro
849
942
  const request = requestOfCall.get(call) ?? call.request;
850
943
  const context = () => contextFor(m, { ...call, request: request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
851
944
  if (m.onWrite)
852
- await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
945
+ await m.onWrite({ operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
853
946
  if (m.events) {
854
- const events = m.events;
855
- const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
947
+ 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) }])) } : {}) });
948
+ 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) };
949
+ const write = { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
856
950
  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
951
  }
858
952
  if (m.sockets?.length)
859
- socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
953
+ socketWrite(m.service, root, { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
860
954
  // a read held until the World writes (held-reads.ts) hears it
861
955
  heldReadsWrite(m.service, root, storedType(m, resource));
862
956
  // the subject's id as it stands after the write: the vendor's, when a live head minted one
@@ -1018,8 +1112,8 @@ function adoptedPath(m, call, root) {
1018
1112
  /** A call's fields with each declared reference that names an adopted subject by its local id moved to the adopted
1019
1113
  * id. A call (a handler's operation) need not say which subject type it writes, so every reference the pack declares
1020
1114
  * 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);
1115
+ function adoptedInCall(m, fields, root) {
1116
+ const service = m.service, refs = packReferences(service);
1023
1117
  if (refs.length === 0)
1024
1118
  return fields;
1025
1119
  const aliases = aliasesNow(service, root);
@@ -1028,14 +1122,14 @@ function adoptedInCall(service, fields, root) {
1028
1122
  let out = fields;
1029
1123
  for (const ref of refs) {
1030
1124
  const local = ref.key(out);
1031
- const adopted = local === undefined ? undefined : aliases.get(`${ref.to}:${local}`);
1125
+ const adopted = local === undefined ? undefined : aliases.get(`${storedType(m, ref.to)}:${local}`);
1032
1126
  if (adopted !== undefined && adopted !== local)
1033
1127
  out = { ...out, ...ref.adopt(out, adopted) };
1034
1128
  }
1035
1129
  return out;
1036
1130
  }
1037
1131
  async function boundaryParams(m, call, root) {
1038
- return adoptedInCall(m.service, await readParams(m, call.request, call.operation), root);
1132
+ return adoptedInCall(m, await readParams(m, call.request, call.operation), root);
1039
1133
  }
1040
1134
  /** The refusal a subject's fields would draw from its resource's `unique` rules: another live subject (not `except`)
1041
1135
  * already holds every field of a rule, each field given. */
@@ -1237,7 +1331,7 @@ export async function serveCore(m, call, scope = {}) {
1237
1331
  const parent = parentOfCall(decl, call);
1238
1332
  const parentId = parent ? parentValue(parent, call.params) : undefined;
1239
1333
  // 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;
1334
+ const tenantField = m.tenant?.storage !== 'namespaced' && m.tenant && !decl.shared ? m.tenant.field : undefined;
1241
1335
  const tenant = tenantField && scope.tenant ? await scope.tenant(await contextFor(m, { ...call, request: call.request.clone() }, scope)) : undefined;
1242
1336
  if (tenantField && tenant === undefined)
1243
1337
  return { unmodeled: `the caller names no tenant (semantics/tenant.ts) for ${resource}` };
@@ -1433,7 +1527,7 @@ export async function contextFor(m, call, scope) {
1433
1527
  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
1528
  // a handler reading the body itself sees the adopted id too (the boundary's one resolution, below)
1435
1529
  if (body && typeof body === 'object' && !Array.isArray(body))
1436
- body = adoptedInCall(m.service, body, root);
1530
+ body = adoptedInCall(m, body, root);
1437
1531
  // every read of the body is of a copy, so the handler's own `call.request` and the core's answer (`core()`) each
1438
1532
  // still find it unread, whichever of them reads it first
1439
1533
  const pristine = call.request.clone();
@@ -1447,12 +1541,25 @@ export async function contextFor(m, call, scope) {
1447
1541
  });
1448
1542
  const paths = expandPaths(m, params);
1449
1543
  const stateChecks = new Map();
1544
+ const credentialDoor = m.descriptor?.credentialDoor ?? getPack(m.vendor)?.credentialDoor;
1545
+ const credentialFields = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
1450
1546
  const rawResources = new WeakMap();
1451
1547
  const rawRow = (resource, row) => {
1452
1548
  const name = m.resources[resource] ? resource : Object.keys(m.resources).find((name) => storedType(m, name) === row.type);
1549
+ if (m.tenant?.storage === 'namespaced') {
1550
+ const [base, encoded] = row.type.split(TENANT_TYPE);
1551
+ const owner = m.resources[name ?? resource]?.shared && row[m.tenant.field] !== undefined ? row[m.tenant.field] : encoded ? decodeURIComponent(encoded) : 'default';
1552
+ row = copyResource(row);
1553
+ row.type = base;
1554
+ row[m.tenant.field] = owner;
1555
+ }
1453
1556
  rawResources.set(row, name ?? resource);
1454
1557
  return row;
1455
1558
  };
1559
+ const liveNativeRow = (link) => {
1560
+ const row = context.row(link.resource, link.id);
1561
+ return row && row.deleted !== true ? row : undefined;
1562
+ };
1456
1563
  const context = {
1457
1564
  call,
1458
1565
  params,
@@ -1468,7 +1575,7 @@ export async function contextFor(m, call, scope) {
1468
1575
  git: (name) => {
1469
1576
  if (!/^[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.-]+)*$/.test(name) || name.split('/').some((seg) => seg === '.' || seg === '..'))
1470
1577
  throw new Error(`semantics: ${name} is not a git repository name`);
1471
- const plane = `${worldPaths(m.service, root).dir}/git`;
1578
+ const plane = `${worldPaths(m.service, root).dir}/git${accountScope(m) && accountScope(m) !== 'default' ? `/accounts/${encodeURIComponent(accountScope(m))}` : ''}`;
1472
1579
  return { store: new GitObjectStore(getActiveBlobStore(), `${plane}/repos/${name}`), refs: new GitRefs(getActiveWorldStore(), `${plane}/repos/${name}`) };
1473
1580
  },
1474
1581
  mail: (route, mail, headers) => sendMail(route, mail, headers),
@@ -1508,17 +1615,50 @@ export async function contextFor(m, call, scope) {
1508
1615
  return value;
1509
1616
  },
1510
1617
  occurredAt: at,
1511
- secret: async (label) => hmac(await worldSeed(m.service, root, at), label, 'base64url'),
1618
+ secret: async (label) => hmac(await worldSeed(m.service, root, at), scopeLabel(m, label), 'base64url'),
1619
+ account: async (field = credentialDoor?.account?.field ?? 'account') => {
1620
+ return worldAccount(m.service, root, credentialFields[field] ?? 'default', at, call.request.headers.get('x-volter-world-activity') === 'provisioning');
1621
+ },
1622
+ organization: async () => {
1623
+ const id = await context.account();
1624
+ const row = projectOwnerResources('_world', root, { type: '_world_organization', id }).find((row) => row.type === '_world_organization' && row.id === id && row.deleted !== true);
1625
+ return row ? { id, label: row.label, members: [...row.members] } : { id, label: id, members: ['owner@world.test'] };
1626
+ },
1627
+ deployment: () => {
1628
+ const id = credentialFields[credentialDoor?.deployment?.field ?? 'deployment'] ?? '';
1629
+ if (id !== '' && !isWorldAccountName(id))
1630
+ throw new Error('Credential deployment must be a name of at most 128 letters, digits, dots, underscores or hyphens');
1631
+ return id;
1632
+ },
1633
+ vendorName,
1634
+ tenant: async (resource, make) => {
1635
+ const id = await context.account();
1636
+ return retainNativeAccountLink(m.service, root, at, call, '_world_account', id, 'tenant', resource, async () => make(await context.organization()), async (link) => liveNativeRow(link));
1637
+ },
1638
+ deploymentObject: async (kind, resource, make) => {
1639
+ const organization = await context.account(), deployment = context.deployment();
1640
+ return retainNativeAccountLink(m.service, root, at, call, '_world_deployment_object', `${organization}/${deployment}/${kind}`, undefined, resource, () => make(deployment), async (link) => liveNativeRow(link));
1641
+ },
1642
+ accountKey: async (purpose, resource, key) => {
1643
+ const organization = await context.account(), deployment = context.deployment();
1644
+ return retainNativeAccountLink(m.service, root, at, call, '_world_account_key', `${organization}/${deployment}/${purpose}`, undefined, resource, () => key.make(), async (link) => {
1645
+ const row = liveNativeRow(link);
1646
+ if (!row)
1647
+ return undefined;
1648
+ const secret = await key.read(row);
1649
+ return secret === undefined ? undefined : { id: link.id, secret };
1650
+ });
1651
+ },
1512
1652
  record: async (type, fields, id, operation) => {
1513
1653
  if (!type.startsWith('_'))
1514
1654
  throw new Error(`semantics: ${type} is not a bookkeeping type (bookkeeping types start with _)`);
1515
1655
  // a bookkeeping subject a resource counts (`counts` naming it: sentry's issue users) keeps that count as any
1516
1656
  // child does, the parent it left recounted too
1517
- const keeps = counted(m, type);
1657
+ const keeps = counted(m, type), physicalType = storedType(m, type);
1518
1658
  if (id !== undefined) {
1519
1659
  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);
1660
+ const prior = keeps ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === id) : undefined;
1661
+ 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
1662
  if (operation)
1523
1663
  observeStateWrite(m, result, before, root, stateChecks);
1524
1664
  if (keeps)
@@ -1531,7 +1671,7 @@ export async function contextFor(m, call, scope) {
1531
1671
  const prefix = `${type.slice(1)}_`;
1532
1672
  let max = 0n;
1533
1673
  for (const r of resources) {
1534
- if (r.type !== type)
1674
+ if (r.type !== physicalType)
1535
1675
  continue;
1536
1676
  const digits = r.id.startsWith(prefix) ? r.id.slice(prefix.length) : '';
1537
1677
  if (/^\d+$/.test(digits) && BigInt(digits) > max)
@@ -1539,21 +1679,21 @@ export async function contextFor(m, call, scope) {
1539
1679
  }
1540
1680
  return `${prefix}${max + 1n}`;
1541
1681
  })();
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));
1682
+ return { kind: 'write', value: subject, write: { operation: operation ?? `${type.slice(1)}.record`, subjectType: physicalType, subjectId: subject, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } } };
1683
+ }, root, () => resourcesOfType(m.service, physicalType, root));
1544
1684
  if (operation && result)
1545
1685
  observeStateWrite(m, result, undefined, root, stateChecks);
1546
- const made = keeps && value !== undefined ? resourcesOfType(m.service, type, root).find((r) => r.id === value) : undefined;
1686
+ const made = keeps && value !== undefined ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === value) : undefined;
1547
1687
  if (made)
1548
1688
  await keepCounts(m, call, type, made, params, root, at);
1549
1689
  return value;
1550
1690
  },
1551
1691
  get machines() {
1552
1692
  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));
1693
+ 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
1694
  },
1555
- signingKey: (label, alg) => worldSigningKey(m.service, root, at, label, alg ?? 'RS256'),
1556
- heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, label, alg ?? 'RS256'),
1695
+ signingKey: (label, alg) => worldSigningKey(m.service, root, at, scopeLabel(m, label), alg ?? 'RS256'),
1696
+ heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, scopeLabel(m, label), alg ?? 'RS256'),
1557
1697
  create: (resource, fields, operation) => atomicWrite(m, call, resource, (snapshot) => {
1558
1698
  const id = mintId(m, resource, root, at, snapshot);
1559
1699
  return { id, fields: typeof fields === 'function' ? fields(id) : fields };
@@ -1566,9 +1706,14 @@ export async function contextFor(m, call, scope) {
1566
1706
  throw new Error('createUnique requires declared unique keys');
1567
1707
  let existing;
1568
1708
  const row = await atomicWrite(m, call, resource, (snapshot) => {
1569
- const id = mintId(m, resource, root, at, snapshot);
1709
+ const id = options?.id ?? mintId(m, resource, root, at, snapshot);
1570
1710
  const selected = typeof fields === 'function' ? fields(id) : fields;
1571
- 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])));
1711
+ const hit = snapshot.find((r) => {
1712
+ if (r.type !== storedType(m, resource) || r.deleted === true)
1713
+ return false;
1714
+ const fields = { [m.resources[resource]?.idAs ?? 'id']: r.id, ...ownFields(r) };
1715
+ return rules.some((rule) => rule.fields.every((field) => selected[field] !== undefined && fields[field] === selected[field]));
1716
+ });
1572
1717
  if (hit) {
1573
1718
  existing = view(m, resource, hit);
1574
1719
  return undefined;
@@ -1608,11 +1753,21 @@ export async function contextFor(m, call, scope) {
1608
1753
  },
1609
1754
  writeDetailed: (resource, id, fields, operation) => writeDetailed(m, call, resource, id, fields, operation, params, root, at, stateChecks),
1610
1755
  resolve: (resource, id) => resolveSubjectId(m.service, storedType(m, resource), id, root),
1611
- storedType: (resource) => storedType(m, resource),
1756
+ storedType: (resource) => m.resources[resource]?.storedAs ?? resource,
1612
1757
  ok: (fields) => Response.json({ ...(m.success ?? {}), ...fields }),
1613
1758
  row: (resource, id, opts) => { const row = stored(m, resource, root, opts).find((r) => r.id === id); return row ? rawRow(resource, row) : undefined; },
1614
1759
  rowsRaw: (resource, opts) => stored(m, resource, root, opts).map((row) => rawRow(resource, row)),
1615
- tree: () => twinResources(m.service, root),
1760
+ tree: () => {
1761
+ const rows = twinResources(m.service, root);
1762
+ if (m.tenant?.storage !== 'namespaced')
1763
+ return rows;
1764
+ const account = accountScope(m);
1765
+ return rows.filter(row => {
1766
+ const [base, encoded] = row.type.split(TENANT_TYPE);
1767
+ const name = Object.keys(m.resources).find(name => (m.resources[name]?.storedAs ?? name) === base) ?? base;
1768
+ return account === undefined || m.resources[name]?.shared || (encoded ? decodeURIComponent(encoded) : 'default') === account;
1769
+ }).map(row => rawRow(row.type.split(TENANT_TYPE)[0], row));
1770
+ },
1616
1771
  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
1772
  raw: (body, init = {}) => new Response(body, { status: init.status ?? 200, headers: init.headers ?? {} }),
1618
1773
  atomically: async (decide) => {
@@ -1667,17 +1822,17 @@ export async function contextFor(m, call, scope) {
1667
1822
  engine: scope.database === undefined ? unboundDatabase(m.service) : managedDatabase(scope.database, { readOnly: scope.readOnly === true || isReadOnlyRequest(call.request) }),
1668
1823
  redis: (commands, options) => execRedisRun(commands, {
1669
1824
  root, occurredAt: at, service: m.service, readOnly: scope.readOnly === true || isReadOnlyRequest(call.request), dialect: options.dialect,
1670
- ...(options.database === undefined ? {} : { database: options.database }),
1825
+ ...(accountScope(m) && accountScope(m) !== 'default' ? { database: scopeLabel(m, options.database ?? 'default') } : options.database === undefined ? {} : { database: options.database }),
1671
1826
  ...(options.transaction === undefined ? {} : { transaction: options.transaction }),
1672
1827
  }),
1673
- live: liveAccess({ rows: (type) => resourcesOfType(m.service, type, root), blob: (key) => readResourceBlob(m.service, key, root) }, call.request),
1828
+ 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
1829
  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),
1830
+ put: (key, bytes) => writeResourceBlob(m.service, scopeLabel(m, key), bytes, root),
1831
+ putStream: (key, body, accept) => writeResourceBlobStream(m.service, scopeLabel(m, key), body, accept, root),
1832
+ get: (key) => readResourceBlob(m.service, scopeLabel(m, key), root),
1833
+ remove: (key) => removeResourceBlob(m.service, scopeLabel(m, key), root),
1834
+ size: (key) => resourceBlobSize(m.service, scopeLabel(m, key), root),
1835
+ range: (key, start, endInclusive) => readResourceBlobRange(m.service, scopeLabel(m, key), start, endInclusive, root),
1681
1836
  },
1682
1837
  merge: (resource, prior, next) => mergeField(m.resources[resource]?.update, prior, next),
1683
1838
  conflict: (resource, fields, except) => {
@@ -1719,10 +1874,12 @@ const BATCH_REFUSED_READS = ['get', 'rows', 'tree', 'history', 'find', 'list', '
1719
1874
  /** The members that write, or wait on writes, or answer as another context: each lands the staged writes first. */
1720
1875
  const BATCH_LANDING_MEMBERS = ['create', 'createUnique', 'change', 'accumulate', 'writeDetailed', 'record', 'atomically', 'remove', 'issue', 'at', 'over', 'core', 'awaitWrite', 'awaitCondition',
1721
1876
  // a World's seed and signing keys are written when first asked (worldSeed, worldSigningKey, as a vendor's move)
1722
- 'secret', 'signingKey'];
1877
+ 'secret', 'signingKey', 'account', 'organization', 'tenant', 'deploymentObject', 'accountKey'];
1723
1878
  /** `ctx.batch` over a context whose writes may be grouped (see `batchable`). */
1724
1879
  function bindBatch(m, call, context, params, root, at, rawRow) {
1725
1880
  let staged;
1881
+ // A callback's native object must land before the helper writes its retained link.
1882
+ let nativeAccountWrites = 0;
1726
1883
  // each staged subject as it will stand, by `type:id`, in the order first written
1727
1884
  const subjects = new Map();
1728
1885
  const land = async () => {
@@ -1738,7 +1895,19 @@ function bindBatch(m, call, context, params, root, at, rawRow) {
1738
1895
  const plain = ctx[name];
1739
1896
  if (typeof plain !== 'function')
1740
1897
  continue;
1741
- ctx[name] = async (...args) => { await land(); return plain(...args); };
1898
+ ctx[name] = async (...args) => {
1899
+ await land();
1900
+ const native = name === 'tenant' || name === 'deploymentObject' || name === 'accountKey';
1901
+ if (native)
1902
+ nativeAccountWrites++;
1903
+ try {
1904
+ return await plain(...args);
1905
+ }
1906
+ finally {
1907
+ if (native)
1908
+ nativeAccountWrites--;
1909
+ }
1910
+ };
1742
1911
  }
1743
1912
  for (const name of BATCH_REFUSED_READS) {
1744
1913
  const plain = ctx[name];
@@ -1753,7 +1922,7 @@ function bindBatch(m, call, context, params, root, at, rawRow) {
1753
1922
  const plainWrite = context.write, plainRow = context.row, plainRowsRaw = context.rowsRaw;
1754
1923
  context.write = async (resource, id, fields, operation) => {
1755
1924
  // a vendor's own move (ctx.asVendor) is the clock's write, recorded as the system's: it lands alone, in its place
1756
- if (!staged || inVendorMove()) {
1925
+ if (!staged || nativeAccountWrites > 0 || inVendorMove()) {
1757
1926
  await land();
1758
1927
  return plainWrite(resource, id, fields, operation);
1759
1928
  }
@@ -2005,7 +2174,7 @@ export function authRefusal(m, request, root) {
2005
2174
  const held = m.auth.held;
2006
2175
  if (held && !held.standing?.includes(key)) {
2007
2176
  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));
2177
+ const holds = stored(m, held.storedAs, root).some((r) => r.deleted !== true && r[held.hashField] === hash && (held.unless === undefined || r[held.unless] !== true));
2009
2178
  if (!holds)
2010
2179
  return vendorError(errorManifest, invalid);
2011
2180
  }
@@ -2101,7 +2270,7 @@ export function crossCutting(m, opts = {}) {
2101
2270
  const url = new URL(request.url);
2102
2271
  const signature = hashFieldValue({ path: url.pathname + url.search, body: await bodyTextOf(request.clone()) });
2103
2272
  const id = `idem_${hex(key)}`;
2104
- const prior = twinResources(m.service, opts.root).find((r) => r.type === m.idempotency.storedAs && r.id === id);
2273
+ const prior = twinResources(m.service, opts.root).find((r) => r.type === storedType(m, m.idempotency.storedAs) && r.id === id);
2105
2274
  if (prior) {
2106
2275
  if (m.idempotency.conflict && prior.requestHash !== undefined && prior.requestHash !== signature)
2107
2276
  return vendorError(m, m.idempotency.conflict);
@@ -2117,7 +2286,7 @@ export function crossCutting(m, opts = {}) {
2117
2286
  return response;
2118
2287
  const contentType = response.headers.get('content-type') ?? 'application/json';
2119
2288
  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);
2289
+ 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
2290
  return response;
2122
2291
  }
2123
2292
  }
@@ -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";