@volter/world-core 3.0.163 → 3.0.165

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 (74) hide show
  1. package/dist/generated/pack-facts.json +44 -3660
  2. package/dist/pack-facts.cjs +4 -4
  3. package/dist/src/browser-session.d.ts +7 -1
  4. package/dist/src/browser-session.js +21 -10
  5. package/dist/src/browser.d.ts +4 -0
  6. package/dist/src/browser.js +10 -0
  7. package/dist/src/derived-core.d.ts +46 -6
  8. package/dist/src/derived-core.js +60 -13
  9. package/dist/src/derived-real.js +159 -5
  10. package/dist/src/derived.d.ts +1 -1
  11. package/dist/src/events.d.ts +6 -3
  12. package/dist/src/events.js +52 -7
  13. package/dist/src/executor.d.ts +2 -2
  14. package/dist/src/executor.js +13 -0
  15. package/dist/src/file-response.d.ts +6 -2
  16. package/dist/src/file-response.js +29 -5
  17. package/dist/src/git/index.d.ts +1 -0
  18. package/dist/src/git/index.js +1 -0
  19. package/dist/src/git/smart-http-client.d.ts +28 -0
  20. package/dist/src/git/smart-http-client.js +95 -0
  21. package/dist/src/git/smart-http.d.ts +3 -0
  22. package/dist/src/git/smart-http.js +9 -0
  23. package/dist/src/head.d.ts +16 -1
  24. package/dist/src/head.js +52 -7
  25. package/dist/src/html.d.ts +29 -0
  26. package/dist/src/html.js +64 -0
  27. package/dist/src/index.d.ts +5 -2
  28. package/dist/src/index.js +5 -1
  29. package/dist/src/managed-database.d.ts +4 -2
  30. package/dist/src/managed-database.js +1 -0
  31. package/dist/src/media-header.d.ts +34 -0
  32. package/dist/src/media-header.js +129 -0
  33. package/dist/src/pack-fetch.d.ts +24 -3
  34. package/dist/src/pack-fetch.js +155 -96
  35. package/dist/src/pack-paths.d.ts +2 -3
  36. package/dist/src/pack-paths.js +3 -13
  37. package/dist/src/packRegistry.d.ts +17 -1
  38. package/dist/src/request-body.d.ts +2 -3
  39. package/dist/src/request-body.js +2 -3
  40. package/dist/src/runtime.d.ts +4 -1
  41. package/dist/src/runtime.js +5 -1
  42. package/dist/src/serve-http.d.ts +2 -2
  43. package/dist/src/serve-http.js +104 -19
  44. package/dist/src/smtp-server.d.ts +78 -0
  45. package/dist/src/smtp-server.js +160 -0
  46. package/dist/src/smtp.d.ts +29 -11
  47. package/dist/src/smtp.js +221 -52
  48. package/generated/pack-facts.json +44 -3660
  49. package/pack-facts.cjs +4 -4
  50. package/package.json +4 -2
  51. package/src/browser-session.ts +17 -5
  52. package/src/browser.ts +10 -0
  53. package/src/derived-core.ts +77 -19
  54. package/src/derived-real.ts +131 -4
  55. package/src/derived.ts +2 -2
  56. package/src/events.ts +54 -9
  57. package/src/executor.ts +13 -2
  58. package/src/file-response.ts +28 -6
  59. package/src/git/index.ts +1 -0
  60. package/src/git/smart-http-client.ts +99 -0
  61. package/src/git/smart-http.ts +7 -0
  62. package/src/head.ts +37 -6
  63. package/src/html.ts +72 -0
  64. package/src/index.ts +6 -2
  65. package/src/managed-database.ts +4 -3
  66. package/src/media-header.ts +108 -0
  67. package/src/pack-fetch.ts +142 -87
  68. package/src/pack-paths.ts +3 -11
  69. package/src/packRegistry.ts +9 -1
  70. package/src/request-body.ts +2 -3
  71. package/src/runtime.ts +5 -1
  72. package/src/serve-http.ts +81 -19
  73. package/src/smtp-server.ts +166 -0
  74. package/src/smtp.ts +199 -53
package/src/pack-fetch.ts CHANGED
@@ -19,7 +19,9 @@ import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from './
19
19
  import { parseScenarioDocument, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest, type PackScenarioAdapter } from './scenario.ts';
20
20
  import { getActiveWorldStore } from './world-store.ts';
21
21
  import { runWithCorrelationId } from './actions.ts';
22
- import { deferPerform, performRequestGroup, RefusedWriteError, VendorWriteError } from './head.ts';
22
+ import { deferPerform, deployableEntries, performRequestGroup, RefusedWriteError, VendorWriteError } from './head.ts';
23
+ import { FLUSH, ZERO_SHA, pktLine, readPktLines } from './git/smart-http.ts';
24
+ import { concat } from './git/objects.ts';
23
25
  import { withCors } from './cors.ts';
24
26
  import type { ApplicationStandIn } from './events.ts';
25
27
  import { contentAddressOf } from './content-route.ts';
@@ -32,7 +34,7 @@ import { expireSocketWaits, socketUpgrade, type SocketEngine } from './sockets.t
32
34
  import { liveIngest } from './live/ingest.ts';
33
35
  import type { LiveEngine } from './live/types.ts';
34
36
  import { readResourceBlob, removeResourceBlob, writeResourceBlob } from './resource-blob.ts';
35
- import { resourcesOfType } from './derived-core.ts';
37
+ import { gitRepositoryOf, resourcesOfType } from './derived-core.ts';
36
38
  import { worldNow } from './world-clock.ts';
37
39
  import type { WebSocketUpgrade, WebSocketPeer } from './serve-http.ts';
38
40
  import { MACHINE_POOL, POOL_KINDS, resetFakePool } from './machines.ts';
@@ -105,7 +107,7 @@ export type LanePart = {
105
107
  * World's scenario file for a model vendor's turns (`scenarioPath`, the runtime's `--scenario`). */
106
108
  export type PackFetchOptions = {
107
109
  root?: string; readOnly?: boolean; clock?: () => string; database?: string; scenarioPath?: string;
108
- /** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
110
+ /** the application a walk stands in for: this fetch's requests and declared events are answered by it */
109
111
  application?: ApplicationStandIn;
110
112
  };
111
113
 
@@ -143,7 +145,7 @@ function hostMatcher(pattern: string): (host: string) => Match | undefined {
143
145
  };
144
146
  }
145
147
  /** The host a request is for: the vendor host the World's injector names, else its own. */
146
- const hostOf = (request: Request): string => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
148
+ export const hostOf = (request: Request): string => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
147
149
 
148
150
  /** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
149
151
  * that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
@@ -218,13 +220,26 @@ async function encoded(m: DerivedManifest, request: Request, res: Response): Pro
218
220
  return new Response(buffered, { status: res.status, statusText: res.statusText, headers });
219
221
  }
220
222
 
221
- /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
222
223
  /** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
223
224
  const doorPath = (path: string): string => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
224
225
 
226
+ /** A declared World door, matched by the same normalized path for serving and response ownership. */
227
+ export function worldDoorMatcher<Door extends { method: string; path: string }>(doors: ReadonlyArray<Door>): (method: string, path: string) => { door: Door; params: Record<string, string> } | undefined {
228
+ const routes = doors.map((door) => ({ door, method: door.method.toUpperCase(), matches: pathMatcher(doorPath(door.path), false) }));
229
+ return (method, path) => {
230
+ const at = path.replace(/\/+$/, '') || '/';
231
+ for (const route of routes) {
232
+ if (route.method !== method.toUpperCase()) continue;
233
+ const hit = route.matches(at);
234
+ if (hit) return { door: route.door, params: hit.params };
235
+ }
236
+ return undefined;
237
+ };
238
+ }
239
+
225
240
  /** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
226
241
  * `mergeSlashes`); one with none is itself. */
227
- function slashesMerged(request: Request): Request {
242
+ export function slashesMerged(request: Request): Request {
228
243
  const url = new URL(request.url);
229
244
  const merged = url.pathname.replace(/\/{2,}/g, '/');
230
245
  if (merged === url.pathname) return request;
@@ -232,6 +247,58 @@ function slashesMerged(request: Request): Request {
232
247
  return new Request(url, request);
233
248
  }
234
249
 
250
+ /** The selected unit's request transformation, shared by serving and response judgment. */
251
+ export function unitRequestRouter(m: Pick<DerivedManifest, 'pathPrefix' | 'basePathAlias' | 'pathFromHost' | 'hostParams' | 'headerParams'>, surface: DerivedSurface, routes: ReturnType<typeof compileSurface>): (request: Request) => Request {
252
+ const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
253
+ const base = surface.basePath ?? '';
254
+ const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
255
+ const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
256
+ const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
257
+ // each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
258
+ // segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
259
+ // keeps another's slashes, nor a label that only part of a segment holds
260
+ const spanningNames = new Set(surface.spanning ?? []);
261
+ const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
262
+ const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
263
+ return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
264
+ })));
265
+ /** A path as the vendor routes it: the manifest's path prefix taken off, the spec's base in an alias's place, a host's
266
+ * path label (pathFromHost) put in front. */
267
+ const rewritten = (pathname: string, host: string): string => {
268
+ let path = pathname;
269
+ if (prefix) path = path.replace(prefix, '') || '/';
270
+ // a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
271
+ if (baseAlias && !(path === base || path.startsWith(`${base}/`))) path = path.replace(baseAlias, base);
272
+ const label = hostPath?.exec(host)?.[1];
273
+ if (label) path = `/${label}${path === '/' ? '' : path}`;
274
+ return path;
275
+ };
276
+ /** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
277
+ * through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
278
+ * slashes; then rewritten. */
279
+ return (request: Request): Request => {
280
+ const url = new URL(request.url);
281
+ const host = hostOf(request);
282
+ const kept = rewritten(url.pathname, host);
283
+ const matched = matchOperation(routes, request.method, kept);
284
+ const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
285
+ const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
286
+ // a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
287
+ // `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
288
+ const groups = { ...(prefix?.exec(source)?.groups ?? {}), ...(hostParams?.exec(host)?.groups ?? {}), ...Object.fromEntries(Object.entries(m.headerParams ?? {}).flatMap(([param, header]) => { const value = request.headers.get(header); return value === null ? [] : [[param, value]]; })) };
289
+ const path = spanning ? kept : rewritten(source, host);
290
+ if (path === url.pathname && !Object.keys(groups).length) return request;
291
+ // the path the client sent, which a signature over the request (SigV4's canonical URI) covers
292
+ const headers = new Headers(request.headers);
293
+ // the path as first sent: a front before this (an attach bridge) may have named the one its client signed
294
+ if (!headers.has(ORIGINAL_PATH_HEADER)) headers.set(ORIGINAL_PATH_HEADER, url.pathname);
295
+ if (Object.keys(groups).length) headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
296
+ url.pathname = path;
297
+ return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) } as RequestInit);
298
+ };
299
+ }
300
+
301
+ /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
235
302
  export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}): DerivedFetch {
236
303
  const m = parts.manifest;
237
304
  const scope: CoreScope = {
@@ -322,11 +389,8 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
322
389
  if (decision.kind === 'fault') return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
323
390
  return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, scenarioDecisionHeader(decision)]])));
324
391
  };
325
- const doors = (m.doors ?? []).map((d) => {
326
- const answer = parts.doors?.[d.id];
327
- if (!answer) throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
328
- return { decl: d, path: pathMatcher(doorPath(d.path), false), answer };
329
- });
392
+ for (const d of m.doors ?? []) if (!parts.doors?.[d.id]) throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
393
+ const door = worldDoorMatcher(m.doors ?? []);
330
394
  const screens = (m.screens ?? []).filter((s) => s.status === 'done' && parts.screens?.[s.id]).map((s) => {
331
395
  const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
332
396
  return { decl: s, host: (h: string) => hosts.map((match) => match(h)).find(Boolean), path: pathMatcher(s.path, true), answer: parts.screens![s.id]! };
@@ -355,53 +419,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
355
419
  };
356
420
  const refusedReadOnly = (): Response => vendorError(m, m.readOnly);
357
421
 
358
- const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
359
- const base = surface.basePath ?? '';
360
- const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
361
- const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
362
- const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
363
- // each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
364
- // segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
365
- // keeps another's slashes, nor a label that only part of a segment holds
366
- const spanningNames = new Set(surface.spanning ?? []);
367
- const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
368
- const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
369
- return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
370
- })));
371
- /** A path as the vendor routes it: the manifest's path prefix taken off, the spec's base in an alias's place, a host's
372
- * path label (pathFromHost) put in front. */
373
- const rewritten = (pathname: string, host: string): string => {
374
- let path = pathname;
375
- if (prefix) path = path.replace(prefix, '') || '/';
376
- // a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
377
- if (baseAlias && !(path === base || path.startsWith(`${base}/`))) path = path.replace(baseAlias, base);
378
- const label = hostPath?.exec(host)?.[1];
379
- if (label) path = `/${label}${path === '/' ? '' : path}`;
380
- return path;
381
- };
382
- /** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
383
- * through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
384
- * slashes; then rewritten. */
385
- const routed = (request: Request): Request => {
386
- const url = new URL(request.url);
387
- const host = hostOf(request);
388
- const kept = rewritten(url.pathname, host);
389
- const matched = matchOperation(routes, request.method, kept);
390
- const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
391
- const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
392
- // a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
393
- // `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
394
- const groups = { ...(prefix?.exec(source)?.groups ?? {}), ...(hostParams?.exec(host)?.groups ?? {}), ...Object.fromEntries(Object.entries(m.headerParams ?? {}).flatMap(([param, header]) => { const value = request.headers.get(header); return value === null ? [] : [[param, value]]; })) };
395
- const path = spanning ? kept : rewritten(source, host);
396
- if (path === url.pathname && !Object.keys(groups).length) return request;
397
- // the path the client sent, which a signature over the request (SigV4's canonical URI) covers
398
- const headers = new Headers(request.headers);
399
- // the path as first sent: a front before this (an attach bridge) may have named the one its client signed
400
- if (!headers.has(ORIGINAL_PATH_HEADER)) headers.set(ORIGINAL_PATH_HEADER, url.pathname);
401
- if (Object.keys(groups).length) headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
402
- url.pathname = path;
403
- return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) } as RequestInit);
404
- };
422
+ const routed = unitRequestRouter(m, surface, routes);
405
423
  const rootServes = (method: string, path: string): boolean => Boolean(matchOperation(routes, method, path));
406
424
  const lane = m.lanes ? laneRouter(m.vendor, m.lanes, parts.lanes ?? {}, rootServes) : undefined;
407
425
  // Inspect lane-owned World doors directly, without routing them back through the vendor gateway.
@@ -488,7 +506,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
488
506
  const own = await answerContent(arrived);
489
507
  if (own) return own;
490
508
  // Root World doors belong to the pack at every routed host, including a lane's host.
491
- const rootDoor = doors.some((d) => d.decl.method.toUpperCase() === arrived.method && d.path(asked));
509
+ const rootDoor = door(arrived.method, asked);
492
510
  const toLane = discovery || rootDoor ? undefined : lane?.(arrived);
493
511
  if (toLane) return toLane(arrived);
494
512
  const readable = await prepared(arrived);
@@ -551,12 +569,11 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
551
569
  if (kind === 'fake') resetFakePool(m.service, options.root);
552
570
  return Response.json({ kind }, { status: 201 });
553
571
  }
554
- for (const d of doors) {
555
- if (d.decl.method.toUpperCase() !== request.method) continue;
556
- const hit = d.path(path);
557
- if (!hit) continue;
572
+ const hit = door(request.method, path);
573
+ if (hit) {
558
574
  if (readOnly && request.method !== 'GET') return refusedReadOnly();
559
- return d.answer(await open(request, d.decl.id, d.decl.method, d.decl.path, hit.params));
575
+ const d = hit.door;
576
+ return parts.doors![d.id]!(await open(request, d.id, d.method, d.path, hit.params));
560
577
  }
561
578
  return gapAnswer(m, request);
562
579
  }
@@ -628,7 +645,38 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
628
645
  const id = `req_${globalThis.crypto.randomUUID()}`;
629
646
  const { value: answer, appended } = await runWithCorrelationId(id, () => deferPerform(id, () => fetch(request)));
630
647
  if (!appended) return answer;
648
+ // a receive-pack's pushes (derived-core.ts pushedInput): what the World's refs held before and after each
649
+ const pushes = m.git ? deployableEntries(m.service, options.root).filter((e) => e.correlationId === id && e.input?.git).map((e) => ({ actionId: e.id, ...(e.input!.git as { name: string; ref: string; before: string; after: string }) })) : [];
631
650
  const performed = await runWithCorrelationId(id, () => performRequestGroup(m.service, id, options.root));
651
+ // under `auto`, what the root did is what the World keeps (ADR 0031): a ref it refused goes back to its value before
652
+ // the push, by compare-and-set under the ref lock (a later push built on it keeps its value), its push and effects
653
+ // are reverted (head.ts), and the client hears git's own report, `ok` or `ng` for each ref
654
+ if (pushes.length) {
655
+ const whole = performed instanceof RefusedWriteError ? performed.reason : performed instanceof VendorWriteError ? performed.message : undefined;
656
+ const refused = new Map<string, string>();
657
+ for (const p of pushes) {
658
+ const reason = whole ?? (performed as { refused?: Array<{ actionId: string; reason: string }> }).refused?.find((r) => r.actionId === p.actionId)?.reason;
659
+ if (reason !== undefined) refused.set(p.actionId, reason);
660
+ }
661
+ if (refused.size) {
662
+ for (const p of [...pushes].reverse()) {
663
+ if (!refused.has(p.actionId)) continue;
664
+ const { refs } = gitRepositoryOf(m, p.name, options.root);
665
+ refs.withLock(() => {
666
+ if ((refs.get(p.ref) ?? ZERO_SHA) !== p.after) return;
667
+ if (p.before === ZERO_SHA) refs.delete(p.ref); else refs.set(p.ref, p.before);
668
+ });
669
+ }
670
+ const ng = new Map(pushes.filter((p) => refused.has(p.actionId)).map((p) => [p.ref, refused.get(p.actionId)!.replace(/\s+/g, ' ')]));
671
+ const report = concat(...readPktLines(new Uint8Array(await answer.arrayBuffer())).lines.map((line) => {
672
+ const ref = line.startsWith('ok ') ? line.slice(3).trim() : undefined;
673
+ return pktLine(ref !== undefined && ng.has(ref) ? `ng ${ref} ${ng.get(ref)}\n` : line);
674
+ }), FLUSH);
675
+ const headers = new Headers(answer.headers);
676
+ headers.delete('content-length');
677
+ return new Response(report as unknown as BodyInit, { status: answer.status, headers });
678
+ }
679
+ }
632
680
  // the vendor's refusal, as its status and words (perform names them `<status>: <message>`), in the vendor's error body
633
681
  if (performed instanceof RefusedWriteError) { const said = /^(\d{3}): ([\s\S]*)$/.exec(performed.reason); return vendorError(m, { status: said ? Number(said[1]) : 400, message: said ? said[2]! : performed.reason }); }
634
682
  if (performed instanceof VendorWriteError) return vendorError(m, { status: 502, message: performed.message });
@@ -717,30 +765,16 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
717
765
  return derivedRequestScopes(m, Object.assign(served, { owners, ownersByUnit, serves, advanceClock: async (request: Request) => runWithStorageTenant(m.service, await selectedAccount(request), () => advanceClock(request)), workspaces, boards: Boolean(parts.board), maxRequestBodySize: m.descriptor?.maxRequestBodySize, draws: drawsUrl, contentHosts, ...(upgrade ? { upgrade } : {}), ...(ingest ? { ingest } : {}) }), options.readOnly);
718
766
  }
719
767
 
720
- /** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
721
- * route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
722
- function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LanePart>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => ((request: Request) => Promise<Response>) | undefined {
723
- const served = (name: string): ((request: Request) => Promise<Response>) => {
724
- const part = lanes[name];
725
- if (!part) throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
726
- // the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
727
- const named = (request: Request): Promise<Response> => {
728
- const headers = new Headers(request.headers);
729
- headers.set(LANE_HEADER, name);
730
- return part.fetch(withHeaders(request, headers));
731
- };
732
- return decl.cors ? withCors(decl.cors, named) : named;
733
- };
734
- const doors = Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({
735
- name, method: d.method.toUpperCase(), matches: pathMatcher(d.path.startsWith('/_twin/') ? d.path : `/_twin/${d.path.replace(/^\//, '')}`, false),
736
- })));
768
+ /** The lane selected by the vendor gateway, shared by serving and response judgment. */
769
+ export function laneSelector(decl: LanesDecl, lanes: Record<string, { manifest: LanePart['manifest']; fetch: Pick<LanePart['fetch'], 'serves' | 'contentHosts'> }>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => string | undefined {
770
+ const door = worldDoorMatcher(Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({ ...d, name }))));
737
771
  const laneRoutes = decl.routes.map((r) => {
738
772
  const at = r.path?.replace(/\/+$/, '');
739
773
  const host = r.host !== undefined ? new RegExp(r.host, 'i') : undefined;
740
774
  const except = (r.exceptHosts ?? []).map((h) => h.toLowerCase());
741
775
  const own = lanes[r.lane]?.fetch.serves;
742
776
  return {
743
- lane: served(r.lane),
777
+ lane: r.lane,
744
778
  holds: (request: Request, path: string, name: string): boolean =>
745
779
  (at === undefined || path === at || path.startsWith(`${at}/`))
746
780
  && (host === undefined || host.test(name))
@@ -749,21 +783,42 @@ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LaneP
749
783
  && !(r.unlessOnlyRoot && rootServes(request.method, path) && !(own?.(request.method, path) ?? false)),
750
784
  };
751
785
  });
752
- const fallback = decl.default !== undefined ? served(decl.default) : undefined;
753
786
  return (request) => {
754
787
  // `<base>/@<host>/<path>` at a World's place, for a host a lane's built content screen names, goes to that lane, before
755
788
  // any lane is picked by the request's host or path: its own fetch answers it by its content route, under the vendor's
756
789
  // host rules (architecture, "A page at a World's place")
757
790
  const address = request.headers.has(TWIN_PREFIX_HEADER) ? contentAddressOf(new URL(request.url).pathname) : undefined;
758
791
  const content = address && Object.keys(lanes).find((l) => lanes[l]!.fetch.contentHosts?.includes(address.host));
759
- if (content) return served(content);
792
+ if (content) return content;
760
793
  const path = new URL(request.url).pathname.replace(/\/{2,}/g, '/');
761
794
  if (path.startsWith('/_twin/')) {
762
- const door = doors.find((d) => d.method === request.method && d.matches(path));
763
- if (door) return served(door.name);
795
+ const hit = door(request.method, path);
796
+ if (hit) return hit.door.name;
764
797
  }
765
798
  const name = hostOf(request);
766
- return laneRoutes.find((r) => r.holds(request, path, name))?.lane ?? fallback;
799
+ return laneRoutes.find((r) => r.holds(request, path, name))?.lane ?? decl.default;
800
+ };
801
+ }
802
+
803
+ /** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
804
+ * route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
805
+ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LanePart>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => ((request: Request) => Promise<Response>) | undefined {
806
+ const served = (name: string): ((request: Request) => Promise<Response>) => {
807
+ const part = lanes[name];
808
+ if (!part) throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
809
+ // the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
810
+ const named = (request: Request): Promise<Response> => {
811
+ const headers = new Headers(request.headers);
812
+ headers.set(LANE_HEADER, name);
813
+ return part.fetch(withHeaders(request, headers));
814
+ };
815
+ return decl.cors ? withCors(decl.cors, named) : named;
816
+ };
817
+ const selected = laneSelector(decl, lanes, rootServes);
818
+ const handlers = new Map([...new Set([...decl.routes.map((route) => route.lane), ...(decl.default === undefined ? [] : [decl.default]), ...Object.keys(lanes)])].map((name) => [name, served(name)]));
819
+ return (request) => {
820
+ const name = selected(request);
821
+ return name === undefined ? undefined : handlers.get(name);
767
822
  };
768
823
  }
769
824
 
package/src/pack-paths.ts CHANGED
@@ -102,20 +102,12 @@ export function installedPackageDir(from: string, name: string): string | undefi
102
102
  }
103
103
  }
104
104
 
105
- /** A pack repository itself, or a platform checkout's P3 repository: the one `VOLTER_PACKS` names (a worktree's own
106
- * pinned packs, as the repository's scripts read it), else the one beside it. Applications do not acquire a
107
- * sibling checkout merely because one happens to be beside them. */
105
+ /** The pack repository a World is rooted in, when it is one (ADR 0034: a platform checkout finds no pack repository
106
+ * beside it; a pack reaches any other World as an installed or linked package). */
108
107
  export function packRepository(from: string): string | undefined {
109
108
  let dir = resolve(from);
110
109
  for (;;) {
111
110
  if (packageName(dir) === 'twin-packs-p3') return dir;
112
- if (packageName(join(dir, 'packages', 'world-core')) === '@volter/world-core') {
113
- const env = globalThis.process?.env?.VOLTER_PACKS;
114
- const named = env ? resolve(env) : undefined;
115
- if (named && packageName(named) === 'twin-packs-p3') return named;
116
- const sibling = join(dirname(dir), 'twin-packs-p3');
117
- if (packageName(sibling) === 'twin-packs-p3') return sibling;
118
- }
119
111
  const up = dirname(dir);
120
112
  if (up === dir) return undefined;
121
113
  dir = up;
@@ -125,7 +117,7 @@ export function packRepository(from: string): string | undefined {
125
117
  /** Resolve one vendor's package directory for host-side reads (assets, infrastructure launchers). */
126
118
  export function vendorPackageDir(from: string, vendor: string): string | undefined {
127
119
  if (!/^[a-z][a-z0-9-]*$/.test(vendor)) return undefined;
128
- const installed = installedVendorPacks(from).get(vendor);
120
+ const installed = installedVendorPacks(from, { vendors: [vendor] }).get(vendor);
129
121
  const repo = packRepository(from);
130
122
  const checkout = repo === undefined ? undefined : join(repo, vendor);
131
123
  if (!checkout || packageName(checkout) !== `@volter/twin-${vendor}` || !isDerivedPack(checkout)) return installed;
@@ -166,13 +166,16 @@ export async function pullOnSchedule<T>(vendor: PullVendor, pull: () => Promise<
166
166
  * matches and no exclude does. Validated by scripts/pack-facts.ts: keys are `[a-z0-9-]+`, a
167
167
  * foreign key is never another pack's vendor id, belongs to one pack, and must appear in the
168
168
  * pack's `adoption.worldIds` (the world wires `<KEY>_TWIN_URL` from there); a pack must claim its
169
- * own vendor id too; no key is exclude-only; `exclude` is `true` or absent.
169
+ * own vendor id too; no key is exclude-only; `exclude` is `true` or absent. `basic` is a vendor fact for the executor: on
170
+ * a URL the rule names, the host takes the sealed token as HTTP Basic's password under this user (a vendor's git over
171
+ * HTTP whose API takes the same token as a bearer; ADR 0031).
170
172
  */
171
173
  export type HostRule = ({ host: string } | { suffix: string } | { hostPattern: string }) & {
172
174
  pathPattern?: string;
173
175
  demandPathPattern?: string | false;
174
176
  key?: string;
175
177
  exclude?: true;
178
+ basic?: string;
176
179
  };
177
180
 
178
181
  export type RoundTripWrite = { method: string; path: string; body?: unknown; headers?: Record<string, string> };
@@ -234,6 +237,11 @@ export type TwinPack = {
234
237
  managedService?: { kind: string; scheme: string; note: string; configurationArg?: '--configuration'; originalInputs?: { version: 1; manifestArg: '--original-inputs'; rootArg: '--original-root'; digestArg: '--original-inputs-sha256'; paths: string[] } };
235
238
  managedDatabase?: { kind: 'postgres'; arg: string; user: string; database: string; clock: 'wall'; credentialField: string; note: string };
236
239
  sidePorts?: Array<{ name: string; option: string; flag: string; path?: string; scheme?: string; note: string }>;
240
+ /** The vendor's own requests a World's generated seed makes for what its world.json declares (ADR 0033 §3), each a URL
241
+ * at the vendor's own host, defined by the twin's own screen form for that act: its sign-up, and its domain screen's
242
+ * add and verify with `record`, the one `/_twin/` stand-in, for a record at the domain's DNS host; `settles` is the
243
+ * World-clock time the vendor takes to settle a verified domain (`10m`). */
244
+ seed?: { signUp?: string; domain?: { add: string; record: string; verify: string; settles: string } };
237
245
  stateSystem?: StateSystemAdapters;
238
246
  /** The pack has no vendor-backed half, and why (copied from the manifest by packOf). */
239
247
  vendorBacked?: { none: string };
@@ -1,8 +1,7 @@
1
- /** Compressed-request output only: Node's plain requests remain uncapped; Bun retains its own plain-body limit.
2
- * The default is Bun's existing largest plain body (128 MiB), so both runtimes decode the same.
1
+ /** The shared HTTP transport and compressed-request output retain Bun's 128 MiB plain-body default.
3
2
  * Source: bun-types/serve.d.ts, ServeOptions.maxRequestBodySize (@default 1024 * 1024 * 128),
4
3
  * https://github.com/oven-sh/bun/blob/main/packages/bun-types/serve.d.ts .
5
- * A pack's descriptor.maxRequestBodySize overrides decoded output as it does Bun's plain-body limit. */
4
+ * A pack's descriptor.maxRequestBodySize overrides both the transport and decoded-output limits. */
6
5
  export const DEFAULT_MAX_DECODED_REQUEST_BODY_SIZE = 128 * 1024 * 1024;
7
6
 
8
7
  export class RequestBodyTooLargeError extends Error {
package/src/runtime.ts CHANGED
@@ -16,11 +16,12 @@ export { ORIGINAL_PATH_HEADER } from './sigv4.ts';
16
16
  export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.ts';
17
17
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.ts';
18
18
  export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from './derived.ts';
19
+ export { hostOf, laneSelector, slashesMerged, unitRequestRouter, worldDoorMatcher } from './pack-fetch.ts';
19
20
  export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.ts';
20
21
  export type { Traceparent } from './trace-context.ts';
21
22
  export type { DerivedCall, DerivedCoreOutcome, DerivedDispatch, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.ts';
22
23
  export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.ts';
23
- export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from './events.ts';
24
+ export { deliverEvents, fillEnvelope, setDeliveryObserver, settleEventDeliveries, signEvent, signEventAll, verifyEvent } from './events.ts';
24
25
  export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtSignClaims, jwtVerify, jwtVerifySignature, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.ts';
25
26
  export type { HandlerCrypto, Jwk, Jwks, JwtDecodeOptions, JwtSignatureOptions, JwtVerdict, JwtVerificationKey, SigningKey } from './signing.ts';
26
27
  export type { EventDelivery, EventRender, EventScheme, EventsDecl, EventTransport, EventWrite } from './events.ts';
@@ -306,6 +307,8 @@ export * as git from './git/index.ts';
306
307
  export * as s3 from './s3/wire.ts';
307
308
  // Generic strict XML grammar; vendor interpretation remains in its consumer.
308
309
  export * as xml from './xml.ts';
310
+ // Generic WHATWG HTML grammar; what a pack keeps of received HTML remains the pack's policy.
311
+ export * as html from './html.ts';
309
312
 
310
313
  // The push arm: the kernel's transaction over a pack's one `perform<Name>Action` (contract section of that name).
311
314
 
@@ -342,6 +345,7 @@ export { brandTokensResponse } from './brand-tokens.ts';
342
345
  export { multipartBoundary, multipartParts, multipartEnvelope, inferMultipartBoundary, multipartDelimiterPattern, type MultipartPart, type MultipartFramedPart, type MultipartEnvelope } from './multipart.ts';
343
346
  export { corsHeaders, withCors, type CorsDecl } from './cors.ts';
344
347
  export { setInProcessTwins, vendorFetch, VendorUnreachableError, type InProcessTwin } from './vendor-call.ts';
348
+ export { setInProcessMailServers } from './smtp.ts';
345
349
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver, type DatabaseDriver, type DatabaseSession } from './managed-database.ts';
346
350
  export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.ts';
347
351
  export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob, writeResourceBlobStream } from './resource-blob.ts';