@volter/world-runtime 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/known-external-services.json +0 -8
  2. package/dist/src/app-url.js +1 -1
  3. package/dist/src/branch.js +79 -2
  4. package/dist/src/catalog.js +1 -1
  5. package/dist/src/cli.js +50 -22
  6. package/dist/src/console-apart.d.ts +1 -0
  7. package/dist/src/console-apart.js +7 -0
  8. package/dist/src/covers.js +4 -2
  9. package/dist/src/fixture-env.d.ts +3 -0
  10. package/dist/src/fixture-env.js +24 -0
  11. package/dist/src/host-worker.js +2 -9
  12. package/dist/src/host.js +2 -9
  13. package/dist/src/import-module.d.ts +1 -0
  14. package/dist/src/import-module.js +16 -0
  15. package/dist/src/index.d.ts +7 -2
  16. package/dist/src/index.js +4 -1
  17. package/dist/src/infra-cli.js +61 -12
  18. package/dist/src/init.d.ts +1 -1
  19. package/dist/src/init.js +34 -5
  20. package/dist/src/local-branches.d.ts +37 -0
  21. package/dist/src/local-branches.js +193 -0
  22. package/dist/src/pglite-backing.d.ts +8 -0
  23. package/dist/src/pglite-backing.js +121 -35
  24. package/dist/src/pglite-host.mjs +520 -14
  25. package/dist/src/prerequisites.js +1 -1
  26. package/dist/src/process-groups.js +1 -1
  27. package/dist/src/redirect-proxy.d.ts +1 -1
  28. package/dist/src/redirect-proxy.js +4 -4
  29. package/dist/src/redis-backing.d.ts +8 -0
  30. package/dist/src/redis-backing.js +120 -0
  31. package/dist/src/root.d.ts +23 -0
  32. package/dist/src/root.js +22 -10
  33. package/dist/src/run-task.js +1 -1
  34. package/dist/src/runtime.d.ts +1 -0
  35. package/dist/src/runtime.js +37 -20
  36. package/dist/src/schema.d.ts +5 -0
  37. package/dist/src/schema.js +10 -1
  38. package/dist/src/served-world.d.ts +196 -9
  39. package/dist/src/served-world.js +847 -103
  40. package/dist/src/service-recorder.js +1 -1
  41. package/dist/src/storage-capacity.js +2 -2
  42. package/dist/src/up-task.js +1 -1
  43. package/dist/src/world-origins.d.ts +13 -0
  44. package/dist/src/world-origins.js +37 -0
  45. package/dist/src/world-view.d.ts +25 -0
  46. package/dist/src/world-view.js +110 -0
  47. package/known-external-services.json +0 -8
  48. package/package.json +10 -4
  49. package/src/app-url.ts +1 -1
  50. package/src/branch.ts +66 -2
  51. package/src/catalog.ts +1 -1
  52. package/src/cli.ts +43 -21
  53. package/src/console-apart.ts +7 -1
  54. package/src/covers.ts +4 -2
  55. package/src/fixture-env.ts +25 -0
  56. package/src/host-worker.ts +2 -1
  57. package/src/host.ts +2 -1
  58. package/src/import-module.ts +9 -0
  59. package/src/index.ts +7 -2
  60. package/src/infra-cli.ts +56 -12
  61. package/src/init.ts +34 -5
  62. package/src/local-branches.ts +173 -0
  63. package/src/pglite-backing.ts +112 -36
  64. package/src/pglite-host.mjs +520 -14
  65. package/src/prerequisites.ts +1 -1
  66. package/src/process-groups.ts +1 -1
  67. package/src/redirect-proxy.ts +4 -4
  68. package/src/redis-backing.ts +107 -0
  69. package/src/root.ts +27 -2
  70. package/src/run-task.ts +1 -1
  71. package/src/runtime.ts +38 -20
  72. package/src/schema.ts +11 -1
  73. package/src/served-world.ts +762 -85
  74. package/src/service-recorder.ts +1 -1
  75. package/src/storage-capacity.ts +2 -2
  76. package/src/up-task.ts +1 -1
  77. package/src/world-origins.ts +38 -0
  78. package/src/world-view.ts +100 -0
@@ -1,5 +1,6 @@
1
1
  import { type HistoryReference } from '@volter/world-core';
2
2
  import { type TwinStreamConnection, type TwinStreamSink } from '@volter/world-core';
3
+ import { type TrustedIssuer } from '@volter/world-access';
3
4
  import { type Changeset, type Receipt } from '@volter/world-core';
4
5
  import { rootForControlRoot } from './root.js';
5
6
  import type { WorldInstance } from './schema.js';
@@ -29,6 +30,11 @@ export type ServedWorld = {
29
30
  console: string | null;
30
31
  stop: () => Promise<void>;
31
32
  };
33
+ /** Where a World keeps its browser sessions (WorldDoors): a host that rotates the tokens without the doors at hand
34
+ * clears it itself. */
35
+ export declare function sessionsFile(worldRoot: string): string;
36
+ /** Where a World keeps its named keys (their hashes, never a key): `.volter/keys.json`. */
37
+ export declare function keysFile(worldRoot: string): string;
32
38
  /** The served name: `bare.name` (`acme/team`), else `<id>/<id>`. */
33
39
  export declare function servedName(worldRoot: string, configRef: string): string;
34
40
  /** A world MOUNTED for serving: its doors, its tokens, and a boot that writes the serve record once the
@@ -53,7 +59,11 @@ export type MountedWorld = {
53
59
  stop: () => Promise<void>;
54
60
  };
55
61
  export declare function mountWorld(name: string, opts?: {
56
- root?: string;
62
+ passIssuers?: TrustedIssuer[];
63
+ root?: string; /** the host's branches of this World, by its served name */
64
+ branches?: (served: string) => BranchDoors | undefined; /** the origin a browser reaches this World at, when the host gives each World one */
65
+ browserOrigin?: (served: string) => string | null; /** served on this machine's loopback: its own pages need no token (DoorHost.localTrust) */
66
+ localTrust?: boolean;
57
67
  }): Promise<MountedWorld>;
58
68
  /** Boot the world (state kept) and serve it on its own port. Announces and returns after boot; `stop` downs the world. */
59
69
  export declare function serveWorld(name: string, opts?: {
@@ -99,7 +109,78 @@ export type DoorHost = {
99
109
  /** Where this host reads a twin's scenario (the handlers document a generative twin serves from,
100
110
  * re-read on every request), or null when the twin takes none here. */
101
111
  scenarioPath?: (vendor: string) => string | null;
112
+ /** A twin's mirror as its pack builds it: `index.html` (the shell) or `assets/<file>`, or null when
113
+ * the twin has none here. Every host answers the same mount (`/<served>/<vendor>/mirror/`); only
114
+ * where the files come from differs (built at serve under Bun, prebuilt under Node, static assets
115
+ * on Cloudflare). */
116
+ mirror?: (vendor: string, file: string) => Promise<Response | null>;
117
+ /** Whether the twin has a mirror on this host, without building it. */
118
+ hasMirror?: (vendor: string) => boolean | Promise<boolean>;
119
+ /** The account and token a twin's screens open signed in as, resolved by its pack from the twin's state at `root`
120
+ * for the account the World's config names (`signIn.as`); null when the pack cannot, or has no such account. */
121
+ mirrorSignIn?: (vendor: string, as: string, ctx: {
122
+ root: string;
123
+ twin: (path: string, init?: RequestInit) => Promise<Response>;
124
+ }) => Promise<{
125
+ account: string;
126
+ token: string;
127
+ } | null>;
128
+ /** Where this World's clock lives in its store (the file every twin reads `worldNow()` from). */
129
+ clockPath?: () => string;
130
+ /** The origin a browser reaches this World at (`http://<world>--<org>.localhost:<port>`), when the
131
+ * host gives each World one: its session lives only there (docs/contributing/architecture.md,
132
+ * "Viewing a World"). Null, or absent, on a host whose Worlds share one origin. */
133
+ browserOrigin?: () => string | null;
134
+ /** The origin another World this host serves (a branch of this one) is reached at, where it has one of its own. */
135
+ originOf?: (served: string) => string | null;
136
+ /** This World is served on this machine's loopback, for the person at it: a page of this World, reached by a
137
+ * loopback name, is given the World's session without a token (`localSession`). A remote host leaves it absent,
138
+ * and its pages sign in with a token. */
139
+ localTrust?: () => boolean;
140
+ /** The platforms whose passes open this World (docs/contributing/architecture.md, "The hosted product"): a person a
141
+ * trusted platform signed a pass for is given a session of the pass's scope. Absent, or empty, no pass opens it. */
142
+ passIssuers?: () => TrustedIssuer[];
143
+ /** This World's branches, where the host can make them (a branch is another World, so making one
144
+ * is the host's act); absent, the branches doors answer 404. */
145
+ branches?: BranchDoors;
146
+ };
147
+ /** A branch as its parent lists it. */
148
+ export type BranchRow = {
149
+ name: string;
150
+ from: string;
151
+ at: {
152
+ instant?: string;
153
+ live?: boolean;
154
+ label?: string;
155
+ } | null;
156
+ createdAt: string;
157
+ expiresAt: string | null;
102
158
  };
159
+ /** What a host does for a World's branches doors. `create` makes a World cloned from this one at
160
+ * `at` (its history cut at that instant), removed when `ttlSeconds` runs out; `origin` is where the
161
+ * request reached this World (a host whose branches clone over its public URL uses it). */
162
+ export type BranchDoors = {
163
+ list: () => Promise<BranchRow[]>;
164
+ /** `parentKey`: the key the branch reaches this World with (never this World's own token), made for whoever asked */
165
+ create: (at: {
166
+ instant?: string;
167
+ live?: boolean;
168
+ label?: string;
169
+ }, ttlSeconds: number | null, origin: string, parentKey?: string) => Promise<{
170
+ name: string;
171
+ token: string;
172
+ readToken: string;
173
+ expiresAt: string | null;
174
+ }>;
175
+ remove: (name: string) => Promise<boolean>;
176
+ };
177
+ /** A World's browser-origin label, `<world>--<org>`, or null when its names cannot make one (a DNS
178
+ * label: lowercase letters, digits and single hyphens, 63 at most). A host maps a label back by the
179
+ * Worlds it serves, never by parsing it. */
180
+ export declare function worldOriginLabel(served: string): string | null;
181
+ /** The header the doors set on a read-scope request to a twin: the twin refuses the caller's writes
182
+ * and still makes the vendor's own moves. It only restricts, so a twin trusts it without a token. */
183
+ export declare const READ_ONLY_HEADER = "x-volter-read-only";
103
184
  export declare class WorldDoors {
104
185
  readonly name: string;
105
186
  readonly worldRoot: string;
@@ -112,6 +193,46 @@ export declare class WorldDoors {
112
193
  constructor(name: string, worldRoot: string, configRef: string, served: string, host: DoorHost, token: string, readToken: string);
113
194
  /** New tokens; a request presenting the old ones is refused from the next call on. */
114
195
  retoken(token: string, readToken: string): void;
196
+ /** BROWSER SESSIONS: the session cookie carries an opaque id, never a token, so a leaked cookie is one session and
197
+ * not the World's write token; rotating the tokens ends every session. Kept in the World's store, so a restart
198
+ * keeps them; each lasts 30 days from its opening. */
199
+ private sessionsPath;
200
+ private held;
201
+ private sessions;
202
+ private keepSessions;
203
+ /** A new session of `scope`: its id, the cookie's value. A token's lasts 30 days; a person's from a pass, `who`,
204
+ * lasts as long as the pass's platform says (twelve hours), so a person removed there loses it within the day. */
205
+ private openSession;
206
+ /** NAMED KEYS: what an app or a script holds (docs/contributing/architecture.md, "The hosted product"), each named
207
+ * when made, shown once, kept only as its hash, and revoked alone; the World's two tokens stay its first keys. */
208
+ private heldKeys;
209
+ private keys;
210
+ private keepKeys;
211
+ /** When a key was last used, to the minute (a key in constant use is not a write per request). */
212
+ private touchKey;
213
+ /** `GET|POST /-/<world>/keys`, `DELETE /-/<world>/keys/<id>`, `DELETE /-/<world>/keys?person=<sub>`: named keys are
214
+ * managed with write access by the World's token or a person's session, never by a key (a key cannot make a key
215
+ * that outlives it). Each records who made it and whom it is for; the token may give one an end and a person, and
216
+ * revoke every key made by or for a person (their leaving the org). */
217
+ /** The person a key is for: whom it was made for, else the person whose session made it. */
218
+ private keyPerson;
219
+ /** Revoke the keys `pick` names, and what they reach: the branches whose parent key goes, and the branches (with their
220
+ * keys) a revoked key made. Answers the revoked keys' ids. */
221
+ private revokeKeys;
222
+ private keysDoor;
223
+ /** Passes this World has already spent: each opens one session. */
224
+ private readonly spent;
225
+ /** A PASS opens a session: a person a trusted platform signed a pass for, into this World, at its own origin, asked
226
+ * from its own page (the console reads the pass from its address's fragment and posts it here). */
227
+ /** The org its host records as this World's owner (`<state>/owner`, written at its making or its claim), or null. */
228
+ private recordedOwner;
229
+ private passSession;
230
+ /** A named key that still opens this World: not revoked, not expired. */
231
+ private liveKey;
232
+ /** The named key a request presents, with the end of its life. */
233
+ private presentedKey;
234
+ private endSession;
235
+ private endSessions;
115
236
  /** The world's twins, by vendor, once booted. */
116
237
  twins(): string[];
117
238
  private up;
@@ -119,8 +240,29 @@ export declare class WorldDoors {
119
240
  * the injector's `x-twins-key` (a zero-edit app keeps its SDK's own Authorization), `Bearer <token>`,
120
241
  * GitHub's `token <token>`, or the password half of `Basic` (Jira's email:token). */
121
242
  private scope;
122
- /** The World token the request presented, in whichever form, and the scope it opens. */
123
- private match;
243
+ /** The request's scope, whether it came by a token the caller presented or by a browser's session
244
+ * (the cookie `POST …/session` set), and the token that opened it. Every form presented is a
245
+ * candidate: a mirror page sends the VENDOR's bearer (the token its sign-in holds) beside the World's
246
+ * session cookie, and the cookie is what opens the World. With a browser origin of its own
247
+ * (`browserOrigin`), a World honours its session only on that origin: a session cookie on any other
248
+ * (a host's shared origin, path-addressed) opens nothing. */
249
+ /** What this World issued, for world-access to decide a grant against: its two tokens, its named keys, its sessions. */
250
+ private issued;
251
+ private credential;
252
+ /** The session cookie's name where `url` reached this World, or null where a session opens nothing:
253
+ * on the World's own origin a host-only cookie (`__Host-` over https); without an origin of its own,
254
+ * the path-scoped cookie a shared origin has always carried. */
255
+ private sessionCookieName;
256
+ /** The session cookie for `url`, set to `value`: host-only over the whole origin on the World's own
257
+ * origin (a `__Host-` cookie must be), scoped to the World's paths on a shared one. Null where a
258
+ * session opens nothing (another World's origin). */
259
+ private sessionCookie;
260
+ /** A browser's session opens the World only to the World's own pages: a request the session cookie carries must
261
+ * say `Sec-Fetch-Site: same-origin` (a browser sets it; a page cannot), or be a read the person navigated to
262
+ * (`none`: a typed or bookmarked address). Another page of the same site (a sibling World's origin shares its
263
+ * suffix) is refused its reads too, so a twin that echoes the caller's Origin with credentials cannot hand one
264
+ * World's data to another World's page; a request with no header fails closed. Null when it may pass. */
265
+ private crossOriginSession;
124
266
  /** Open one connection on a byte-stream door for the holder of the World's (write) token; null when
125
267
  * the token is not the World's or the World has no such stream. */
126
268
  openStream(token: string, id: string, sink: TwinStreamSink, peer: string): TwinStreamConnection | null;
@@ -129,14 +271,24 @@ export declare class WorldDoors {
129
271
  private controlRoot;
130
272
  private stateOf;
131
273
  handle(request: Request): Promise<Response>;
274
+ /** THE MIRROR MOUNT: `/<served>/<vendor>/mirror/` and `…/mirror/assets/<file>`, keyless and GET only —
275
+ * the pack's own shell and client. The shell's <base> is the twin's place in this World, so the
276
+ * mirror reads and writes the World's wire with the browser's session (`POST …/session`); any
277
+ * other path under mirror/ is one of the mirror's own routes (a reloaded deep link) and gets the
278
+ * shell. A link is not a twin and has no mirror. */
279
+ private mirror;
280
+ /** A vendor's screens' sign-in (`signIn.as`), once per account every twelve hours while this World is served (rotating the
281
+ * tokens forgets them): the pack finds the account and mints its credential through the twin's own doors; a failure
282
+ * is not kept, so the next page asks again. An account or credential that is not plain printable text is refused. */
283
+ private readonly signIns;
284
+ private readonly signInAt;
285
+ private signedIn;
286
+ /** Whether a twin enforces a read-scope request itself (its manifest names `requestScopes: ['read']`):
287
+ * only then may the read token reach it with anything but GET or HEAD. Asked once per twin. */
288
+ private readonly readEnforced;
289
+ private enforcesRead;
132
290
  /** The vendor wire: `/<org>/<world>/<vendor>/<rest>` → the twin's own URL. */
133
291
  private wire;
134
- /** Whether the twin's pack ships a mirror (its vendor's UI over this World's state). */
135
- private hasMirror;
136
- /** THE MIRROR MOUNT, as the hosted World serves it (apps/cloud supervisor.ts): `/<org>/<world>/<vendor>/mirror/`
137
- * and `…/mirror/assets/*`, keyless, GET only — the pack's own shell and bundle. The shell's <base> is the
138
- * twin's place under this World, so its reads go to the keyed wire with the browser's World session. */
139
- private mirror;
140
292
  private linksPath;
141
293
  /** The World's links: name → the vendor origin it forwards to. */
142
294
  links(): Record<string, {
@@ -151,6 +303,41 @@ export declare class WorldDoors {
151
303
  private observedAt;
152
304
  /** The world's own doors under `/-/<org>/<world>/`. */
153
305
  private door;
306
+ /** A browser session for this World: the presented token as an HttpOnly cookie scoped to the World's
307
+ * paths, so a page served under it (a twin's mirror) reads the wire as the token's holder, in the
308
+ * token's scope. */
309
+ /** THE LOCAL SESSION: a World served on this machine's loopback never asks the person at it for a token. Any
310
+ * website can send requests to 127.0.0.1, so what stands in for the token is proof the request is this World's own
311
+ * page, as Vite and Jupyter check their dev servers: the Host is a loopback name (a rebound DNS name is not, so DNS
312
+ * rebinding fails), the browser's Origin is exactly this origin (another site's page cannot claim it, so CSRF
313
+ * fails), and the fetch says it is same-origin. A process on this machine can claim all three: a local World
314
+ * trusts this machine's user, as a local dev server does. The session is the write token's. A World with no origin
315
+ * of its own (a shared origin, where every World's pages are one origin) is not trusted so: its page asks for a
316
+ * token. */
317
+ private localPage;
318
+ private localSession;
319
+ private session;
320
+ /** THE CLOCK: the frozen instant every twin stamps from, kept in the World's store. It moves only
321
+ * forward once the World has entries (a twin's catch-up stamps each move at its due time, so a
322
+ * clock set back would put new entries before old ones); the past is reached by branching. */
323
+ private clockDoor;
324
+ /** THE BRANCHES DOORS: list (read), make one as of an instant, remove one (write). The host makes
325
+ * and removes them; a host that makes none answers 404, and a view offers no "as of". */
326
+ private branchesDoor;
327
+ /** The newest entry's instant across the World's twins (ms), or null when it has none. */
328
+ private newestEntry;
329
+ /** THE MAP: every twin and what it holds, by resource type, and whether it has a mirror. */
330
+ private map;
331
+ /** THE TIMELINE: the entries of every twin's log merged, newest first, by each entry's instant, then
332
+ * twin, then position. The World keeps no order across twins finer than its clock, and this claims
333
+ * none. `before` is the cursor the previous page answered; `twin` and `trace` narrow it. */
334
+ private timeline;
335
+ /** Each twin's timeline rows, kept until its logs change: a poll of an unchanged World reads no log
336
+ * (a twin is read again only when the size or time of its parent log, branch log or branch record
337
+ * moved), and the merged order is sorted again only when a twin was. */
338
+ private readonly timelineByTwin;
339
+ private timelineMerged;
340
+ private timelineRows;
154
341
  /** PUSH: see `landChangeset` — the door hands the body to it. */
155
342
  private push;
156
343
  }