@volter/world-runtime 2.0.38 → 2.0.40

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.
@@ -268,6 +268,10 @@ export declare class WorldDoors {
268
268
  /** What this World issued, for world-access to decide a grant against: its two tokens, its named keys, its sessions. */
269
269
  private issued;
270
270
  private credential;
271
+ /** Who made a request, as this World names it: `key:<name>` for a named key, `person:<who>` for a browser's session
272
+ * (`person` when it names no one), `token` / `token:read` for the World's own tokens; null when it presented nothing
273
+ * this World issued. */
274
+ private callerOf;
271
275
  /** The session cookie's name where `url` reached this World, or null where a session opens nothing:
272
276
  * on the World's own origin a host-only cookie (`__Host-` over https); without an origin of its own,
273
277
  * the path-scoped cookie a shared origin has always carried. */
@@ -352,6 +356,10 @@ export declare class WorldDoors {
352
356
  * twin, then position. The World keeps no order across twins finer than its clock, and this claims
353
357
  * none. `before` is the cursor the previous page answered; `twin` and `trace` narrow it. */
354
358
  private timeline;
359
+ /** Each twin's callers by the World's span, from its request journal: its newest CALLER_SEGMENTS once, then from
360
+ * where the last read stopped (readTwinRequestJournalFrom follows a rotation). The newest CALLERS_KEPT are kept. */
361
+ private readonly callersByTwin;
362
+ private callersOf;
355
363
  /** Each twin's timeline rows, kept until its logs change: a poll of an unchanged World reads no log
356
364
  * (a twin is read again only when the size or time of its parent log, branch log or branch record
357
365
  * moved), and the merged order is sorted again only when a twin was. */
@@ -26,7 +26,7 @@ import { getActiveWorldStore, withAncestryLock, captureHistory, historyAtInstant
26
26
  // to `.volter/token` and `.volter/token.read` (0600).
27
27
  // One writer per world by construction: the doors run in this process, serialized per twin.
28
28
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
29
- import { assertSafeChangesetName, blobDigest, destinationOf, openSealedAt, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, mirrorShellUnder, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, validateRemoteOrigin } from '@volter/world-core';
29
+ import { CALLER_HEADER, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, assertSafeChangesetName, blobDigest, destinationOf, openSealedAt, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, mirrorShellUnder, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, validateRemoteOrigin } from '@volter/world-core';
30
30
  import { APP_READ_ENDPOINT_ENV } from "./init.js";
31
31
  import { grantOf, presentedSecrets, sameSecret, spentPasses, verifyPass } from '@volter/world-access';
32
32
  import { createHash } from 'node:crypto';
@@ -369,6 +369,11 @@ function traceIdOf(traceparent) {
369
369
  const m = typeof traceparent === 'string' ? /^[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(traceparent) : null;
370
370
  return m && !/^0+$/.test(m[1]) ? m[1] : null;
371
371
  }
372
+ const CALLERS_KEPT = 50_000;
373
+ /** The closed journal segments a World reads for callers when it first answers a timeline (~1 MB each): older entries show no caller. */
374
+ const CALLER_SEGMENTS = 8;
375
+ /** A W3C traceparent's parent-id (the span the World minted for the request), or null. */
376
+ function spanOf(traceparent) { return parseTraceparent(traceparent)?.parentId ?? null; }
372
377
  /** Timeline order, newest first: by the entry's instant, then twin, then position. */
373
378
  function newerFirst(a, b) {
374
379
  return b.at.localeCompare(a.at) || b.twin.localeCompare(a.twin) || b.position - a.position;
@@ -653,6 +658,22 @@ export class WorldDoors {
653
658
  return { scope: grant.scope, via: 'session', token: null, session: grant.session, ...(grant.who ? { who: grant.who } : {}) };
654
659
  return { scope: grant.scope, via: grant.via === 'key' ? 'key' : 'token', token: grant.presented ?? null };
655
660
  }
661
+ /** Who made a request, as this World names it: `key:<name>` for a named key, `person:<who>` for a browser's session
662
+ * (`person` when it names no one), `token` / `token:read` for the World's own tokens; null when it presented nothing
663
+ * this World issued. */
664
+ callerOf(request) {
665
+ const cred = this.credential(request);
666
+ if (cred.scope === null)
667
+ return null;
668
+ if (cred.via === 'session')
669
+ return cred.who ? `person:${cred.who}` : 'person';
670
+ if (cred.via === 'key') {
671
+ const token = cred.token;
672
+ const held = token ? this.keys().find((k) => sameSecret(keyHash(token), k.hash)) : undefined;
673
+ return held ? `key:${held.name}` : 'key';
674
+ }
675
+ return cred.scope === 'read' ? 'token:read' : 'token';
676
+ }
656
677
  /** The session cookie's name where `url` reached this World, or null where a session opens nothing:
657
678
  * on the World's own origin a host-only cookie (`__Host-` over https); without an origin of its own,
658
679
  * the path-scoped cookie a shared origin has always carried. */
@@ -872,6 +893,14 @@ export class WorldDoors {
872
893
  headers.delete(TOKEN_HEADER);
873
894
  headers.delete(TWINS_KEY_HEADER);
874
895
  headers.delete('host');
896
+ // who made it is the World's word, never the caller's: the twin's request journal records it. The World is a hop of
897
+ // the request's trace: the caller's trace-id goes on (or a new one), under a parent-id the World mints, which the
898
+ // request's log entries carry, so each entry is joined to its caller by an id no caller chose
899
+ headers.delete(CALLER_HEADER);
900
+ const who = this.callerOf(request);
901
+ if (who)
902
+ headers.set(CALLER_HEADER, who);
903
+ headers.set('traceparent', traceparentForDelivery(headers.get('traceparent')) ?? newTraceparent());
875
904
  // at a twin that enforces the read scope, every request of the read token is marked read-only (a GET
876
905
  // that would write for its caller is refused too); at any other, its GET passes as it always has
877
906
  if (enforced)
@@ -942,7 +971,7 @@ export class WorldDoors {
942
971
  // what leaves for the vendor before the seal is applied: no World token in any header it can arrive
943
972
  // under (an SDK's own `Authorization: Bearer` included), no cookie, no routing headers of this World
944
973
  const caller = new Headers(request.headers);
945
- for (const h of [TOKEN_HEADER, TWINS_KEY_HEADER, TWIN_PREFIX_HEADER, 'host', 'x-forwarded-host', 'x-forwarded-proto', 'cookie'])
974
+ for (const h of [TOKEN_HEADER, TWINS_KEY_HEADER, TWIN_PREFIX_HEADER, CALLER_HEADER, 'host', 'x-forwarded-host', 'x-forwarded-proto', 'cookie'])
946
975
  caller.delete(h);
947
976
  // a secret this World minted (`tok_…`, live, revoked or expired) or one that opens it never leaves
948
977
  const authorization = caller.get('authorization');
@@ -953,7 +982,10 @@ export class WorldDoors {
953
982
  // name, with the credential shape of what the caller sent on. As an audit device does, the request is
954
983
  // written before the real credential is used, and a request whose audit cannot be written is refused.
955
984
  const credentials = twinRequestCredentials(caller, url);
956
- if (!journalTwinRequest(name, { phase: 'request', method: request.method, path: `/${rest}`, status: 0, credentials }, this.controlRoot(name))) {
985
+ const who = this.callerOf(request);
986
+ const trace = parseTraceparent(request.headers.get('traceparent'))?.traceId;
987
+ const by = { ...(who ? { caller: who } : {}), ...(trace ? { trace } : {}) };
988
+ if (!journalTwinRequest(name, { phase: 'request', method: request.method, path: `/${rest}`, status: 0, credentials, ...by }, this.controlRoot(name))) {
957
989
  return Response.json({ error: `the ${name} link's audit could not be written: nothing was sent` }, { status: 503 });
958
990
  }
959
991
  const startedAt = Date.now();
@@ -964,7 +996,7 @@ export class WorldDoors {
964
996
  return answer;
965
997
  }
966
998
  finally {
967
- journalTwinRequest(name, { method: request.method, path: `/${rest}`, status, ms: Date.now() - startedAt, credentials }, this.controlRoot(name));
999
+ journalTwinRequest(name, { method: request.method, path: `/${rest}`, status, ms: Date.now() - startedAt, credentials, ...by }, this.controlRoot(name));
968
1000
  }
969
1001
  }
970
1002
  async forwardSealed(request, caller, url, rest, name, origin) {
@@ -1705,7 +1737,33 @@ export class WorldDoors {
1705
1737
  const shown = page.slice(0, limit);
1706
1738
  const last = shown[shown.length - 1];
1707
1739
  const next = more && last ? Buffer.from(JSON.stringify([last.entry.occurredAt, last.twin, last.position])).toString('base64url') : null;
1708
- return Response.json({ world: this.served, entries: shown, next });
1740
+ // who made each entry: the request it was written in (the World's span on it) as the twin's journal recorded it
1741
+ const entries = shown.map((r) => { const by = this.callersOf(r.twin).get(spanOf(r.entry.traceparent) ?? ''); return by ? { ...r, caller: by } : r; });
1742
+ return Response.json({ world: this.served, entries, next });
1743
+ }
1744
+ /** Each twin's callers by the World's span, from its request journal: its newest CALLER_SEGMENTS once, then from
1745
+ * where the last read stopped (readTwinRequestJournalFrom follows a rotation). The newest CALLERS_KEPT are kept. */
1746
+ callersByTwin = new Map();
1747
+ callersOf(twin) {
1748
+ const root = this.controlRoot(twin);
1749
+ let held = this.callersByTwin.get(twin);
1750
+ if (!held || held.root !== root) {
1751
+ held = { root, cursor: null, callers: new Map() };
1752
+ this.callersByTwin.set(twin, held);
1753
+ }
1754
+ const read = readTwinRequestJournalFrom(twin, root, held.cursor, { segments: CALLER_SEGMENTS });
1755
+ for (const e of read.entries)
1756
+ if (e.span && e.caller) {
1757
+ held.callers.delete(e.span);
1758
+ held.callers.set(e.span, e.caller);
1759
+ }
1760
+ held.cursor = read.cursor;
1761
+ for (const span of held.callers.keys()) {
1762
+ if (held.callers.size <= CALLERS_KEPT)
1763
+ break;
1764
+ held.callers.delete(span);
1765
+ }
1766
+ return held.callers;
1709
1767
  }
1710
1768
  /** Each twin's timeline rows, kept until its logs change: a poll of an unchanged World reads no log
1711
1769
  * (a twin is read again only when the size or time of its parent log, branch log or branch record
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "2.0.38",
3
+ "version": "2.0.40",
4
4
  "description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
5
5
  "keywords": [
6
6
  "twin",
@@ -67,14 +67,14 @@
67
67
  "node": ">=22.3"
68
68
  },
69
69
  "peerDependencies": {
70
- "@volter/world-core": "2.0.21",
70
+ "@volter/world-core": "2.0.22",
71
71
  "@volter/world-console": "2.0.13"
72
72
  },
73
73
  "dependencies": {
74
74
  "@electric-sql/pglite": "0.5.8",
75
75
  "@electric-sql/pglite-pgvector": "0.0.9",
76
76
  "@volter/world-access": "2.0.2",
77
- "@volter/world-core": "2.0.21",
77
+ "@volter/world-core": "2.0.22",
78
78
  "pg-gateway": "0.3.0-beta.4",
79
79
  "smol-toml": "^1.8.0"
80
80
  },
@@ -84,6 +84,6 @@
84
84
  }
85
85
  },
86
86
  "optionalDependencies": {
87
- "@volter/twin-mongodb": "0.1.21"
87
+ "@volter/twin-mongodb": "0.1.22"
88
88
  }
89
89
  }
@@ -26,7 +26,7 @@ import { getActiveWorldStore, withAncestryLock, captureHistory, historyAtInstant
26
26
  // to `.volter/token` and `.volter/token.read` (0600).
27
27
  // One writer per world by construction: the doors run in this process, serialized per twin.
28
28
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync , rmSync} from 'node:fs';
29
- import { assertSafeChangesetName, blobDigest, destinationOf, openSealedAt, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, mirrorShellUnder, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink } from '@volter/world-core';
29
+ import { CALLER_HEADER, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, type TwinRequestJournalCursor, assertSafeChangesetName, blobDigest, destinationOf, openSealedAt, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, mirrorShellUnder, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink } from '@volter/world-core';
30
30
  import { APP_READ_ENDPOINT_ENV } from './init.ts';
31
31
  import { grantOf, presentedSecrets, sameSecret, spentPasses, verifyPass, type TrustedIssuer, type WorldCredentials } from '@volter/world-access';
32
32
  import { createHash } from 'node:crypto';
@@ -380,6 +380,11 @@ function traceIdOf(traceparent: unknown): string | null {
380
380
  return m && !/^0+$/.test(m[1]!) ? m[1]! : null;
381
381
  }
382
382
  type TimelineRow = { twin: string; position: number; entry: Entry };
383
+ const CALLERS_KEPT = 50_000;
384
+ /** The closed journal segments a World reads for callers when it first answers a timeline (~1 MB each): older entries show no caller. */
385
+ const CALLER_SEGMENTS = 8;
386
+ /** A W3C traceparent's parent-id (the span the World minted for the request), or null. */
387
+ function spanOf(traceparent: unknown): string | null { return parseTraceparent(traceparent)?.parentId ?? null; }
383
388
  /** Timeline order, newest first: by the entry's instant, then twin, then position. */
384
389
  function newerFirst(a: { at: string; twin: string; position: number }, b: { at: string; twin: string; position: number }): number {
385
390
  return b.at.localeCompare(a.at) || b.twin.localeCompare(a.twin) || b.position - a.position;
@@ -590,6 +595,16 @@ export class WorldDoors {
590
595
  if (grant.via === 'session') return { scope: grant.scope, via: 'session', token: null, session: grant.session!, ...(grant.who ? { who: grant.who } : {}) };
591
596
  return { scope: grant.scope, via: grant.via === 'key' ? 'key' : 'token', token: grant.presented ?? null };
592
597
  }
598
+ /** Who made a request, as this World names it: `key:<name>` for a named key, `person:<who>` for a browser's session
599
+ * (`person` when it names no one), `token` / `token:read` for the World's own tokens; null when it presented nothing
600
+ * this World issued. */
601
+ private callerOf(request: Request): string | null {
602
+ const cred = this.credential(request);
603
+ if (cred.scope === null) return null;
604
+ if (cred.via === 'session') return cred.who ? `person:${cred.who}` : 'person';
605
+ if (cred.via === 'key') { const token = cred.token; const held = token ? this.keys().find((k) => sameSecret(keyHash(token), k.hash)) : undefined; return held ? `key:${held.name}` : 'key'; }
606
+ return cred.scope === 'read' ? 'token:read' : 'token';
607
+ }
593
608
  /** The session cookie's name where `url` reached this World, or null where a session opens nothing:
594
609
  * on the World's own origin a host-only cookie (`__Host-` over https); without an origin of its own,
595
610
  * the path-scoped cookie a shared origin has always carried. */
@@ -781,6 +796,12 @@ export class WorldDoors {
781
796
  const enforced = scope === 'read' && (await this.enforcesRead(vendor!, twin, url.origin));
782
797
  if (scope === 'read' && !reads && !enforced) return Response.json({ error: 'the read token opens only reads' }, { status: 403 });
783
798
  const headers = new Headers(request.headers); headers.delete(TOKEN_HEADER); headers.delete(TWINS_KEY_HEADER); headers.delete('host');
799
+ // who made it is the World's word, never the caller's: the twin's request journal records it. The World is a hop of
800
+ // the request's trace: the caller's trace-id goes on (or a new one), under a parent-id the World mints, which the
801
+ // request's log entries carry, so each entry is joined to its caller by an id no caller chose
802
+ headers.delete(CALLER_HEADER);
803
+ const who = this.callerOf(request); if (who) headers.set(CALLER_HEADER, who);
804
+ headers.set('traceparent', traceparentForDelivery(headers.get('traceparent')) ?? newTraceparent());
784
805
  // at a twin that enforces the read scope, every request of the read token is marked read-only (a GET
785
806
  // that would write for its caller is refused too); at any other, its GET passes as it always has
786
807
  if (enforced) headers.set(READ_ONLY_HEADER, '1');
@@ -843,7 +864,7 @@ export class WorldDoors {
843
864
  // what leaves for the vendor before the seal is applied: no World token in any header it can arrive
844
865
  // under (an SDK's own `Authorization: Bearer` included), no cookie, no routing headers of this World
845
866
  const caller = new Headers(request.headers);
846
- for (const h of [TOKEN_HEADER, TWINS_KEY_HEADER, TWIN_PREFIX_HEADER, 'host', 'x-forwarded-host', 'x-forwarded-proto', 'cookie']) caller.delete(h);
867
+ for (const h of [TOKEN_HEADER, TWINS_KEY_HEADER, TWIN_PREFIX_HEADER, CALLER_HEADER, 'host', 'x-forwarded-host', 'x-forwarded-proto', 'cookie']) caller.delete(h);
847
868
  // a secret this World minted (`tok_…`, live, revoked or expired) or one that opens it never leaves
848
869
  const authorization = caller.get('authorization');
849
870
  const offered = authorization ? presentedSecrets(new Headers({ authorization }), { token: TOKEN_HEADER, twinsKey: TWINS_KEY_HEADER }) : [];
@@ -852,7 +873,9 @@ export class WorldDoors {
852
873
  // name, with the credential shape of what the caller sent on. As an audit device does, the request is
853
874
  // written before the real credential is used, and a request whose audit cannot be written is refused.
854
875
  const credentials = twinRequestCredentials(caller, url);
855
- if (!journalTwinRequest(name, { phase: 'request', method: request.method, path: `/${rest}`, status: 0, credentials }, this.controlRoot(name))) {
876
+ const who = this.callerOf(request); const trace = parseTraceparent(request.headers.get('traceparent'))?.traceId;
877
+ const by = { ...(who ? { caller: who } : {}), ...(trace ? { trace } : {}) };
878
+ if (!journalTwinRequest(name, { phase: 'request', method: request.method, path: `/${rest}`, status: 0, credentials, ...by }, this.controlRoot(name))) {
856
879
  return Response.json({ error: `the ${name} link's audit could not be written: nothing was sent` }, { status: 503 });
857
880
  }
858
881
  const startedAt = Date.now(); let status = 500;
@@ -861,7 +884,7 @@ export class WorldDoors {
861
884
  status = answer.status;
862
885
  return answer;
863
886
  } finally {
864
- journalTwinRequest(name, { method: request.method, path: `/${rest}`, status, ms: Date.now() - startedAt, credentials }, this.controlRoot(name));
887
+ journalTwinRequest(name, { method: request.method, path: `/${rest}`, status, ms: Date.now() - startedAt, credentials, ...by }, this.controlRoot(name));
865
888
  }
866
889
  }
867
890
 
@@ -1442,7 +1465,23 @@ export class WorldDoors {
1442
1465
  const more = page.length > limit; const shown = page.slice(0, limit);
1443
1466
  const last = shown[shown.length - 1];
1444
1467
  const next = more && last ? Buffer.from(JSON.stringify([last.entry.occurredAt, last.twin, last.position])).toString('base64url') : null;
1445
- return Response.json({ world: this.served, entries: shown, next });
1468
+ // who made each entry: the request it was written in (the World's span on it) as the twin's journal recorded it
1469
+ const entries = shown.map((r) => { const by = this.callersOf(r.twin).get(spanOf(r.entry.traceparent) ?? ''); return by ? { ...r, caller: by } : r; });
1470
+ return Response.json({ world: this.served, entries, next });
1471
+ }
1472
+
1473
+ /** Each twin's callers by the World's span, from its request journal: its newest CALLER_SEGMENTS once, then from
1474
+ * where the last read stopped (readTwinRequestJournalFrom follows a rotation). The newest CALLERS_KEPT are kept. */
1475
+ private readonly callersByTwin = new Map<string, { root: string; cursor: TwinRequestJournalCursor | null; callers: Map<string, string> }>();
1476
+ private callersOf(twin: string): Map<string, string> {
1477
+ const root = this.controlRoot(twin);
1478
+ let held = this.callersByTwin.get(twin);
1479
+ if (!held || held.root !== root) { held = { root, cursor: null, callers: new Map() }; this.callersByTwin.set(twin, held); }
1480
+ const read = readTwinRequestJournalFrom(twin, root, held.cursor, { segments: CALLER_SEGMENTS });
1481
+ for (const e of read.entries) if (e.span && e.caller) { held.callers.delete(e.span); held.callers.set(e.span, e.caller); }
1482
+ held.cursor = read.cursor;
1483
+ for (const span of held.callers.keys()) { if (held.callers.size <= CALLERS_KEPT) break; held.callers.delete(span); }
1484
+ return held.callers;
1446
1485
  }
1447
1486
 
1448
1487
  /** Each twin's timeline rows, kept until its logs change: a poll of an unchanged World reads no log