@volter/world-core 3.0.155 → 3.0.157

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 (150) hide show
  1. package/attach.cjs +2 -0
  2. package/capability-decision.cjs +6 -4
  3. package/dist/attach.cjs +2 -0
  4. package/dist/capability-decision.cjs +6 -4
  5. package/dist/generated/pack-facts.json +204 -4
  6. package/dist/inject.cjs +150 -86
  7. package/dist/network-policy.cjs +11 -1
  8. package/dist/network-policy.d.cts +1 -0
  9. package/dist/pack-facts.d.cts +3 -0
  10. package/dist/required-credential-transport.cjs +29 -0
  11. package/dist/required-credential-transport.d.cts +3 -0
  12. package/dist/src/actions.d.ts +15 -0
  13. package/dist/src/actions.js +82 -21
  14. package/dist/src/ancestry.d.ts +5 -4
  15. package/dist/src/ancestry.js +53 -28
  16. package/dist/src/blob-store.d.ts +16 -0
  17. package/dist/src/blob-store.js +76 -1
  18. package/dist/src/changeset.js +1 -1
  19. package/dist/src/clickhouse/compression.d.ts +28 -0
  20. package/dist/src/clickhouse/compression.js +281 -0
  21. package/dist/src/clickhouse/index.d.ts +1 -0
  22. package/dist/src/clickhouse/index.js +1 -0
  23. package/dist/src/derived-core.d.ts +84 -7
  24. package/dist/src/derived-core.js +336 -61
  25. package/dist/src/derived-real.js +97 -15
  26. package/dist/src/derived.d.ts +5 -0
  27. package/dist/src/derived.js +3 -0
  28. package/dist/src/executor.d.ts +7 -0
  29. package/dist/src/executor.js +15 -1
  30. package/dist/src/fork.js +1 -1
  31. package/dist/src/git/lfs.d.ts +13 -3
  32. package/dist/src/git/lfs.js +41 -9
  33. package/dist/src/grpc-wire.js +2 -1
  34. package/dist/src/h2.js +63 -10
  35. package/dist/src/head.d.ts +5 -0
  36. package/dist/src/head.js +15 -0
  37. package/dist/src/history.d.ts +1 -1
  38. package/dist/src/history.js +7 -7
  39. package/dist/src/index.d.ts +5 -1
  40. package/dist/src/index.js +6 -1
  41. package/dist/src/live/ingest.js +9 -13
  42. package/dist/src/live/rtmp.js +7 -5
  43. package/dist/src/live/segmenter.js +7 -1
  44. package/dist/src/log.d.ts +23 -3
  45. package/dist/src/log.js +160 -37
  46. package/dist/src/machines.js +5 -1
  47. package/dist/src/managed-go-programs.d.ts +55 -0
  48. package/dist/src/managed-go-programs.js +133 -0
  49. package/dist/src/managed-original-inputs.d.ts +101 -0
  50. package/dist/src/managed-original-inputs.js +125 -0
  51. package/dist/src/mongodb/client.d.ts +8 -0
  52. package/dist/src/mongodb/client.js +27 -0
  53. package/dist/src/mongodb/index.d.ts +2 -0
  54. package/dist/src/mongodb/index.js +5 -0
  55. package/dist/src/mongodb/stream.d.ts +97 -0
  56. package/dist/src/mongodb/stream.js +206 -0
  57. package/dist/src/observe.js +1 -1
  58. package/dist/src/pack-fetch.d.ts +6 -1
  59. package/dist/src/pack-fetch.js +69 -14
  60. package/dist/src/pack-paths.d.ts +2 -1
  61. package/dist/src/pack-paths.js +6 -1
  62. package/dist/src/packRegistry.d.ts +10 -14
  63. package/dist/src/packRegistry.js +2 -13
  64. package/dist/src/protocol.d.ts +13 -0
  65. package/dist/src/protocol.js +13 -0
  66. package/dist/src/redis/engine.d.ts +10 -4
  67. package/dist/src/redis/engine.js +103 -32
  68. package/dist/src/redis/member-storage.js +20 -8
  69. package/dist/src/redis/service-times.d.ts +25 -0
  70. package/dist/src/redis/service-times.js +87 -0
  71. package/dist/src/redis/stream.d.ts +2 -1
  72. package/dist/src/redis/stream.js +109 -20
  73. package/dist/src/resource-blob.d.ts +4 -0
  74. package/dist/src/resource-blob.js +8 -1
  75. package/dist/src/runtime-state.d.ts +11 -0
  76. package/dist/src/runtime-state.js +13 -0
  77. package/dist/src/runtime.d.ts +7 -5
  78. package/dist/src/runtime.js +7 -4
  79. package/dist/src/serve-http.d.ts +2 -0
  80. package/dist/src/serve-http.js +73 -6
  81. package/dist/src/serve.d.ts +10 -0
  82. package/dist/src/serve.js +43 -3
  83. package/dist/src/sockets.d.ts +4 -1
  84. package/dist/src/sockets.js +59 -6
  85. package/dist/src/storage.d.ts +2 -1
  86. package/dist/src/storage.js +6 -3
  87. package/dist/src/vendor-call.js +58 -0
  88. package/dist/src/world-boot.d.ts +1 -0
  89. package/dist/src/world-boot.js +6 -0
  90. package/dist/src/world-clock.js +6 -2
  91. package/dist/src/world-store.d.ts +122 -7
  92. package/dist/src/world-store.js +959 -103
  93. package/dist/src/xml.d.ts +30 -0
  94. package/dist/src/xml.js +74 -0
  95. package/generated/pack-facts.json +204 -4
  96. package/inject.cjs +150 -86
  97. package/network-policy.cjs +11 -1
  98. package/network-policy.d.cts +1 -0
  99. package/pack-facts.d.cts +3 -0
  100. package/package.json +27 -1
  101. package/required-credential-transport.cjs +29 -0
  102. package/required-credential-transport.d.cts +3 -0
  103. package/src/actions.ts +78 -21
  104. package/src/ancestry.ts +46 -24
  105. package/src/blob-store.ts +59 -1
  106. package/src/changeset.ts +1 -1
  107. package/src/clickhouse/compression.ts +172 -0
  108. package/src/clickhouse/index.ts +2 -0
  109. package/src/derived-core.ts +356 -61
  110. package/src/derived-real.ts +80 -14
  111. package/src/derived.ts +9 -1
  112. package/src/executor.ts +17 -3
  113. package/src/fork.ts +1 -1
  114. package/src/git/lfs.ts +40 -9
  115. package/src/grpc-wire.ts +2 -1
  116. package/src/h2.ts +46 -5
  117. package/src/head.ts +15 -0
  118. package/src/history.ts +7 -7
  119. package/src/index.ts +7 -1
  120. package/src/live/ingest.ts +7 -3
  121. package/src/live/rtmp.ts +7 -5
  122. package/src/live/segmenter.ts +7 -1
  123. package/src/log.ts +147 -32
  124. package/src/machines.ts +5 -1
  125. package/src/managed-go-programs.ts +146 -0
  126. package/src/managed-original-inputs.ts +163 -0
  127. package/src/mongodb/client.ts +27 -0
  128. package/src/mongodb/index.ts +5 -0
  129. package/src/mongodb/stream.ts +223 -0
  130. package/src/observe.ts +1 -1
  131. package/src/pack-fetch.ts +66 -16
  132. package/src/pack-paths.ts +5 -1
  133. package/src/packRegistry.ts +3 -15
  134. package/src/protocol.ts +13 -0
  135. package/src/redis/engine.ts +100 -57
  136. package/src/redis/member-storage.ts +17 -9
  137. package/src/redis/service-times.ts +81 -0
  138. package/src/redis/stream.ts +66 -14
  139. package/src/resource-blob.ts +9 -1
  140. package/src/runtime-state.ts +14 -0
  141. package/src/runtime.ts +10 -3
  142. package/src/serve-http.ts +65 -6
  143. package/src/serve.ts +45 -3
  144. package/src/sockets.ts +39 -8
  145. package/src/storage.ts +5 -3
  146. package/src/vendor-call.ts +54 -0
  147. package/src/world-boot.ts +6 -0
  148. package/src/world-clock.ts +6 -2
  149. package/src/world-store.ts +768 -91
  150. package/src/xml.ts +71 -0
@@ -0,0 +1,206 @@
1
+ const TIMED = Symbol('mongodb timed reply');
2
+ /**
3
+ * `reply` after `afterMs` (0: at once); with `everyMs`, again each `everyMs` until the peer closes, where the sender
4
+ * allowed more than one reply. The two differ in the vendor's own answers: a hello naming the server's current
5
+ * topology is first answered after the wait, one naming another process at once, and both then repeat at the wait.
6
+ */
7
+ export const timedReply = (reply, afterMs, everyMs) => ({ [TIMED]: true, reply, afterMs, ...(everyMs !== undefined ? { everyMs } : {}) });
8
+ const isTimed = (answer) => answer[TIMED] === true;
9
+ export const MongoOp = { REPLY: 1, QUERY: 2004, MSG: 2013 };
10
+ /** OP_MSG's flag bits. */
11
+ export const MongoMsgFlag = { CHECKSUM_PRESENT: 1 << 0, MORE_TO_COME: 1 << 1, EXHAUST_ALLOWED: 1 << 16 };
12
+ const HEADER = 16;
13
+ const dec = new TextDecoder();
14
+ const enc = new TextEncoder();
15
+ const view = (bytes) => new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
16
+ function concat(parts) {
17
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
18
+ let at = 0;
19
+ for (const p of parts) {
20
+ out.set(p, at);
21
+ at += p.length;
22
+ }
23
+ return out;
24
+ }
25
+ function header(length, requestId, responseTo, opcode) {
26
+ const out = new Uint8Array(HEADER), v = view(out);
27
+ v.setInt32(0, length, true);
28
+ v.setInt32(4, requestId, true);
29
+ v.setInt32(8, responseTo, true);
30
+ v.setInt32(12, opcode, true);
31
+ return out;
32
+ }
33
+ /** An OP_MSG message carrying one document as its body section. */
34
+ export function encodeMsg(codec, document, requestId, responseTo, flags = 0) {
35
+ const body = codec.serialize(document), bits = new Uint8Array(5);
36
+ view(bits).setUint32(0, flags, true);
37
+ return concat([header(HEADER + 4 + 1 + body.length, requestId, responseTo, MongoOp.MSG), bits, body]);
38
+ }
39
+ /** The command of an OP_MSG message: its body section, with each document sequence as the field it names. */
40
+ export function decodeMsg(codec, message) {
41
+ const v = view(message), flags = v.getUint32(HEADER, true), end = message.length - (flags & MongoMsgFlag.CHECKSUM_PRESENT ? 4 : 0);
42
+ let document;
43
+ const sequences = {};
44
+ for (let at = HEADER + 4; at < end;) {
45
+ const kind = message[at];
46
+ at += 1;
47
+ if (kind === 0) {
48
+ const size = v.getInt32(at, true);
49
+ if (size < 5 || at + size > end)
50
+ throw new Error('OP_MSG body section runs past the message');
51
+ document = codec.deserialize(message.subarray(at, at + size));
52
+ at += size;
53
+ }
54
+ else if (kind === 1) {
55
+ const size = v.getInt32(at, true), stop = at + size;
56
+ if (size < 4 || stop > end)
57
+ throw new Error('OP_MSG document sequence runs past the message');
58
+ const nameEnd = message.indexOf(0, at + 4);
59
+ if (nameEnd < 0 || nameEnd >= stop)
60
+ throw new Error('OP_MSG document sequence names no field');
61
+ const name = dec.decode(message.subarray(at + 4, nameEnd)), documents = [];
62
+ for (let cursor = nameEnd + 1; cursor < stop;) {
63
+ const length = v.getInt32(cursor, true);
64
+ if (length < 5 || cursor + length > stop)
65
+ throw new Error('OP_MSG document sequence holds a document that runs past it');
66
+ documents.push(codec.deserialize(message.subarray(cursor, cursor + length)));
67
+ cursor += length;
68
+ }
69
+ sequences[name] = documents;
70
+ at = stop;
71
+ }
72
+ else
73
+ throw new Error(`OP_MSG section kind ${kind} is unknown`);
74
+ }
75
+ if (!document)
76
+ throw new Error('OP_MSG carries no body section');
77
+ return { flags, document: { ...document, ...sequences } };
78
+ }
79
+ /** MongoDB's wire, served: each command document answered by the pack, in arrival order, on one connection. */
80
+ export function createMongoStream(options) {
81
+ const limit = options.maxMessageSizeBytes ?? 48_000_000;
82
+ let nextResponse = 0;
83
+ return (sink, peer) => {
84
+ const session = options.open(peer);
85
+ let buffer = new Uint8Array(0);
86
+ let closed = false;
87
+ let chain = Promise.resolve();
88
+ // A reply being held or repeated (a timed reply): its timer, and the messages that arrived meanwhile. A
89
+ // connection serves one command at a time, so nothing else is answered until a held reply is sent, and nothing
90
+ // at all while a repeating one runs (recording:hello-awaitable-exhaust-then-command.json: a ping sent into the
91
+ // stream has no reply). The timer is not work: `settled` does not wait for it, it does not keep the process
92
+ // alive (unref), and a close from either side clears it at once.
93
+ let held;
94
+ const parked = [];
95
+ const release = () => { if (held !== undefined) {
96
+ clearTimeout(held);
97
+ held = undefined;
98
+ } parked.length = 0; };
99
+ const hold = (ms, then) => {
100
+ held = setTimeout(() => { held = undefined; if (!closed)
101
+ then(); }, Math.max(0, ms));
102
+ held.unref?.();
103
+ };
104
+ const end = (reason) => {
105
+ if (closed)
106
+ return;
107
+ closed = true;
108
+ release();
109
+ (options.ended ?? ((why, from) => console.error(`mongodb wire: ended the connection from ${from}: ${why}`)))(reason, peer);
110
+ options.close?.(session);
111
+ sink.end();
112
+ };
113
+ const answer = async (message) => {
114
+ const v = view(message), requestId = v.getInt32(4, true), opcode = v.getInt32(12, true);
115
+ if (opcode === MongoOp.MSG) {
116
+ const { flags, document } = decodeMsg(options.codec, message);
117
+ const database = typeof document.$db === 'string' ? document.$db : 'admin';
118
+ const exhaustAllowed = (flags & MongoMsgFlag.EXHAUST_ALLOWED) !== 0;
119
+ const answered = await options.command(session, database, document, { exhaustAllowed });
120
+ // the sender waits for no reply to a message it marked moreToCome
121
+ if (flags & MongoMsgFlag.MORE_TO_COME)
122
+ return;
123
+ if (!isTimed(answered)) {
124
+ sink.write(encodeMsg(options.codec, answered, ++nextResponse, requestId));
125
+ return;
126
+ }
127
+ if (answered.everyMs === undefined || !exhaustAllowed) {
128
+ // held once: one reply after the wait, with no flag; then what arrived meanwhile is answered in order
129
+ // source: recording:hello-awaitable-no-exhaust.json "received"
130
+ hold(answered.afterMs, () => {
131
+ sink.write(encodeMsg(options.codec, answered.reply, ++nextResponse, requestId));
132
+ for (const message of parked.splice(0))
133
+ take(message);
134
+ });
135
+ return;
136
+ }
137
+ // repeated: each reply marked moreToCome; the first answers the request, each later one answers this
138
+ // wire's own previous reply, until the peer closes
139
+ // source: recording:hello-awaitable-exhaust.json "received"
140
+ let answers = requestId;
141
+ const again = () => {
142
+ const id = ++nextResponse;
143
+ sink.write(encodeMsg(options.codec, answered.reply, id, answers, MongoMsgFlag.MORE_TO_COME));
144
+ answers = id;
145
+ hold(answered.everyMs, again);
146
+ };
147
+ // source: recording:hello-stale-process-exhaust.json "received" (the first at once, the rest at the wait)
148
+ hold(answered.afterMs, again);
149
+ return;
150
+ }
151
+ if (opcode === MongoOp.QUERY) {
152
+ // the legacy form a driver's first hello may take: a query on `<database>.$cmd` whose document is the command
153
+ const nameEnd = message.indexOf(0, HEADER + 4);
154
+ if (nameEnd < 0)
155
+ throw new Error('OP_QUERY names no collection');
156
+ const namespace = dec.decode(message.subarray(HEADER + 4, nameEnd));
157
+ if (!namespace.endsWith('.$cmd'))
158
+ throw new Error(`OP_QUERY on ${namespace}: only a command on <database>.$cmd is served`);
159
+ const command = options.codec.deserialize(message.subarray(nameEnd + 1 + 8));
160
+ // the legacy form has no flags of OP_MSG's: nothing is held or repeated for it
161
+ const legacy = await options.command(session, namespace.slice(0, -'.$cmd'.length), command, { exhaustAllowed: false });
162
+ const reply = options.codec.serialize(isTimed(legacy) ? legacy.reply : legacy);
163
+ // OP_REPLY: responseFlags (AwaitCapable), cursorID 0, startingFrom 0, numberReturned 1, then the document
164
+ const fixed = new Uint8Array(20), f = view(fixed);
165
+ f.setInt32(0, 8, true);
166
+ f.setInt32(16, 1, true);
167
+ sink.write(concat([header(HEADER + 20 + reply.length, ++nextResponse, requestId, MongoOp.REPLY), fixed, reply]));
168
+ return;
169
+ }
170
+ throw new Error(`opcode ${opcode} is not served`);
171
+ };
172
+ // One message, in arrival order: parked while a reply is held, answered otherwise.
173
+ const take = (message) => {
174
+ chain = chain.then(() => (closed ? undefined : held !== undefined ? void parked.push(message) : answer(message))).catch((error) => end(error instanceof Error ? error.message : String(error)));
175
+ };
176
+ return {
177
+ data(chunk) {
178
+ if (closed)
179
+ return;
180
+ buffer = buffer.length ? concat([buffer, chunk]) : chunk;
181
+ while (buffer.length >= 4) {
182
+ const length = view(buffer).getInt32(0, true);
183
+ // where the citation stops: what the vendor's server does with a malformed or oversized message is not
184
+ // read; this ends the connection, which no driver's well-formed message reaches
185
+ if (length < HEADER || length > limit) {
186
+ end(`a message of ${length} bytes (accepted: ${HEADER} to ${limit})`);
187
+ return;
188
+ }
189
+ if (buffer.length < length)
190
+ break;
191
+ const message = buffer.subarray(0, length);
192
+ buffer = buffer.subarray(length);
193
+ take(message.slice());
194
+ }
195
+ },
196
+ settled: () => chain,
197
+ close: () => { if (!closed) {
198
+ closed = true;
199
+ release();
200
+ options.close?.(session);
201
+ } },
202
+ };
203
+ };
204
+ }
205
+ /** The bytes of a C string, for a client's OP_QUERY. */
206
+ export const cstring = (text) => concat([enc.encode(text), new Uint8Array(1)]);
@@ -162,5 +162,5 @@ function foldResources(service, resources, opts) {
162
162
  }
163
163
  }
164
164
  return report;
165
- });
165
+ }, { operation: 'observation batch' });
166
166
  }
@@ -1,4 +1,4 @@
1
- import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
1
+ import { type DerivedManifest, type Handler, type HandlerContext, type ScreenDecl, type VendorManifest } from './derived-core.js';
2
2
  import { type DerivedFetch, type DerivedSurface, type BoardFrame } from './derived.js';
3
3
  import { type PackScenarioAdapter } from './scenario.js';
4
4
  import type { ApplicationStandIn } from './events.js';
@@ -86,5 +86,10 @@ export type PackFetchOptions = {
86
86
  /** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
87
87
  application?: ApplicationStandIn;
88
88
  };
89
+ /** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
90
+ * that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
91
+ * page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
92
+ * over. Undefined when nothing is asked; the path (with no `show`) otherwise. */
93
+ export declare function shownRecord(url: URL, workspaces: ReadonlyArray<ScreenDecl>, params: Record<string, string>): string | undefined;
89
94
  export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
90
95
  export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): DerivedFetch;
@@ -1,4 +1,5 @@
1
1
  import { parseExactJson } from "./exact-json.js";
2
+ import { redisServiceTimes } from "./redis/service-times.js";
2
3
  import { readHostPort } from "./host-port.js";
3
4
  // A Protocol 3 pack's whole HTTP surface, served by the kernel from the pack's declarations (docs/contributing/
4
5
  // architecture.md, "The pack contract: the kernel's root"): the discovery door (`GET /twin`, the manifest's
@@ -22,7 +23,7 @@ import { withCors } from "./cors.js";
22
23
  import { contentAddressOf } from "./content-route.js";
23
24
  import { getPack } from "./packRegistry.js";
24
25
  import { hostRules } from '../host-routing.cjs';
25
- import { socketUpgrade } from "./sockets.js";
26
+ import { expireSocketWaits, socketUpgrade } from "./sockets.js";
26
27
  import { liveIngest } from "./live/ingest.js";
27
28
  import { readResourceBlob, removeResourceBlob, writeResourceBlob } from "./resource-blob.js";
28
29
  import { resourcesOfType } from "./derived-core.js";
@@ -70,6 +71,32 @@ function hostMatcher(pattern) {
70
71
  }
71
72
  /** The host a request is for: the vendor host the World's injector names, else its own. */
72
73
  const hostOf = (request) => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
74
+ /** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
75
+ * that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
76
+ * page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
77
+ * over. Undefined when nothing is asked; the path (with no `show`) otherwise. */
78
+ export function shownRecord(url, workspaces, params) {
79
+ const asked = /^([^:]+):(.+)$/.exec(url.searchParams.get('show') ?? '');
80
+ if (!asked)
81
+ return undefined;
82
+ const [, type, id] = asked;
83
+ const fill = (path, extra) => {
84
+ let unfilled = false;
85
+ const filled = path.replace(/\{([^}]+)\}|<([^>]+)>/g, (_, a, b) => {
86
+ const v = { ...params, ...extra }[(a ?? b)];
87
+ if (v === undefined || v === '') {
88
+ unfilled = true;
89
+ return '';
90
+ }
91
+ return encodeURIComponent(v);
92
+ });
93
+ return unfilled ? undefined : filled;
94
+ };
95
+ const shows = workspaces.filter((s) => s.shows?.type === type).map((s) => fill(s.path, { [s.shows.param]: id })).find((p) => p !== undefined);
96
+ const lists = workspaces.filter((s) => s.lists?.includes(type)).map((s) => fill(s.path, {})).find((p) => p !== undefined);
97
+ const home = workspaces.find((s) => s.path.replace(/\/+$/, '') === '') ? '/' : undefined;
98
+ return shows ?? lists ?? home ?? url.pathname;
99
+ }
73
100
  /** The answer for a path no operation, door or screen serves, and for an operation nothing models: the manifest's
74
101
  * `gap`, its `{method}` and `{path}` filled, else the manifest's not-found refusal. */
75
102
  function gapAnswer(m, request, otherMethod = false) {
@@ -124,6 +151,16 @@ async function encoded(m, request, res) {
124
151
  /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
125
152
  /** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
126
153
  const doorPath = (path) => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
154
+ /** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
155
+ * `mergeSlashes`); one with none is itself. */
156
+ function slashesMerged(request) {
157
+ const url = new URL(request.url);
158
+ const merged = url.pathname.replace(/\/{2,}/g, '/');
159
+ if (merged === url.pathname)
160
+ return request;
161
+ url.pathname = merged;
162
+ return new Request(url, request);
163
+ }
127
164
  export function createPackFetch(parts, options = {}) {
128
165
  const m = parts.manifest;
129
166
  const scope = {
@@ -257,7 +294,7 @@ export function createPackFetch(parts, options = {}) {
257
294
  }
258
295
  };
259
296
  // a context the kernel opens for what no surface operation names: a door, a screen, the clock, the board
260
- const open = (request, id, method, path, params) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params }, scope);
297
+ const open = (request, id, method, path, params, streamed) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params, ...(streamed ? { streamed } : {}) }, scope);
261
298
  const advanceClock = async (request) => {
262
299
  if (parts.clock)
263
300
  await runAsVendorMove(async () => parts.clock(await open(new Request(request.url, m.tenant ? { headers: request.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
@@ -369,7 +406,7 @@ export function createPackFetch(parts, options = {}) {
369
406
  return refusedReadOnly();
370
407
  if (parts.clock)
371
408
  await runAsVendorMove(async () => parts.clock(await open(new Request(asked.url, m.tenant ? { headers: asked.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
372
- return hit.s.answer(await open(asked, hit.s.decl.id, method, hit.s.decl.path, { ...(hit.s.host(host)?.params ?? {}), ...hit.hit.params }));
409
+ return hit.s.answer(await open(asked, hit.s.decl.id, method, hit.s.decl.path, { ...(hit.s.host(host)?.params ?? {}), ...hit.hit.params }, true));
373
410
  };
374
411
  /** A request as the kernel reads it: the headers only the kernel names taken off, its body decoded (undefined when it
375
412
  * does not decode as its Content-Encoding says). */
@@ -392,7 +429,9 @@ export function createPackFetch(parts, options = {}) {
392
429
  const readable = await prepared(arrived);
393
430
  return readable instanceof Response ? readable : readable ? atContentHost(readable) : undecodable(arrived);
394
431
  };
395
- const fetch = async (arrived) => {
432
+ const fetch = async (received) => {
433
+ const arrived = m.mergeSlashes ? slashesMerged(received) : received;
434
+ redisServiceTimes.lap('1 request made by the stream');
396
435
  // the discovery door is the pack's own, whichever lane would take its path
397
436
  const asked = new URL(arrived.url).pathname.replace(/\/+$/, '') || '/';
398
437
  const discovery = arrived.method === 'GET' && m.discovery !== undefined && (asked === '/twin' || asked === '/twin/scenario');
@@ -427,6 +466,7 @@ export function createPackFetch(parts, options = {}) {
427
466
  const url = new URL(request.url);
428
467
  const path = url.pathname.replace(/\/+$/, '') || '/';
429
468
  const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
469
+ redisServiceTimes.lap('2 content and lane routes, decoding, routing');
430
470
  if (request.method === 'GET' && path === '/twin' && m.discovery) {
431
471
  const d = m.discovery;
432
472
  const discoveryDoors = [...(m.doors ?? []), ...(m.machines ? [{ id: 'machine-pool', method: 'POST', path: '/_twin/machine-pool', note: 'Enroll the World execution pool: { kind: light | docker | none | fake, images?: { image: { context, command?, runtime? } } }.' }] : [])];
@@ -466,6 +506,9 @@ export function createPackFetch(parts, options = {}) {
466
506
  // the vendor's own moves, not the caller's: its context carries none of the request's headers (a Stripe-Account
467
507
  // header would make a payout time makes the caller's), but a tenant's, when the vendor keeps tenants apart
468
508
  await advanceClock(request);
509
+ // a reply a session awaits that the World's clock has run out (sockets.ts)
510
+ if (m.sockets?.length)
511
+ expireSocketWaits(m.service, options.root);
469
512
  if (path.startsWith('/_twin/')) {
470
513
  // the World's machine pool, enrolled by the kernel for a vendor that runs images (machines.ts)
471
514
  if (m.machines && path === '/_twin/machine-pool' && request.method === 'POST') {
@@ -523,9 +566,12 @@ export function createPackFetch(parts, options = {}) {
523
566
  ?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
524
567
  ?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
525
568
  if (byPath) {
569
+ const asked = byPath.s.decl.kind === 'workspace' && request.method === 'GET' ? shownRecord(url, screens.filter((x) => x.decl.kind === 'workspace').map((x) => x.decl), { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }) : undefined;
570
+ if (asked !== undefined && asked !== path)
571
+ return new Response(null, { status: 302, headers: { location: `${(request.headers.get(TWIN_PREFIX_HEADER) ?? '').replace(/\/+$/, '')}${asked}` } });
526
572
  if (readOnly && request.method !== 'GET' && request.method !== 'HEAD')
527
573
  return refusedReadOnly();
528
- return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }));
574
+ return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }, true));
529
575
  }
530
576
  for (const route of m.machineRoutes ?? []) {
531
577
  const host = request.headers.get('x-volter-twin-original-host') ?? new URL(request.url).hostname;
@@ -549,12 +595,19 @@ export function createPackFetch(parts, options = {}) {
549
595
  return encoded(m, request, await decided(request));
550
596
  // The front reads a context before forwarding (possibly with new headers). Materialize a transport body before
551
597
  // those independent reads, rather than chaining tees of the incoming stream through the front and the scenario.
598
+ redisServiceTimes.lap('3 doors, screens and machine routes passed');
552
599
  const bytes = request.method === 'GET' || request.method === 'HEAD' ? undefined : await request.arrayBuffer();
553
600
  const buffered = new Request(request.url, { method: request.method, headers: request.headers, signal: request.signal, ...(bytes ? { body: bytes } : {}) });
554
601
  const matched = matchOperation(routes, buffered.method, url.pathname, url.searchParams, buffered.headers);
555
602
  const operation = matched?.operation ?? { id: 'unmatched', method: buffered.method, path: url.pathname, class: buffered.method === 'GET' ? 'retrieve' : 'action' };
603
+ redisServiceTimes.lap('4 body buffered, operation matched');
556
604
  const ctx = await contextFor(m, { request: buffered.clone(), operation, params: matched?.params ?? pathParams(routes, url.pathname) }, scope);
557
- return encoded(m, buffered, await parts.around(ctx, (rewritten) => decided(rewritten ?? buffered)));
605
+ redisServiceTimes.lap('5 the around context opened');
606
+ const answered = await parts.around(ctx, (rewritten) => decided(rewritten ?? buffered));
607
+ redisServiceTimes.lap('6d the handler, and the around after it');
608
+ const sent = await encoded(m, buffered, answered);
609
+ redisServiceTimes.lap('7 answer encoded');
610
+ return sent;
558
611
  };
559
612
  // the request's id is the correlation of every entry it appends (runtime contract D3), minted here, always: it is the
560
613
  // group a perform sends as one call (the real-system adapters, "Perform"). Under `auto` the group is performed once
@@ -609,7 +662,7 @@ export function createPackFetch(parts, options = {}) {
609
662
  for (const d of m.sockets ?? [])
610
663
  if (!parts.sockets?.[d.id])
611
664
  throw new Error(`${m.vendor}: the manifest declares the socket ${d.id}, and semantics/sockets.ts exports no ${d.id}`);
612
- const ownUpgrade = m.sockets?.length ? socketUpgrade(m.service, options.root, m.sockets, parts.sockets, (request, d) => open(request, `socket:${d.id}`, 'GET', d.path, {}), hostOf, (request) => new URL(routed(request).url).pathname,
665
+ const ownUpgrade = m.sockets?.length ? socketUpgrade(m.service, options.root, m.sockets, parts.sockets, (request, d, params) => open(request, `socket:${d.id}`, 'GET', d.path, params), hostOf, (request) => new URL(routed(request).url).pathname,
613
666
  // a turn a session speaks (a voice agent's reply) is the World's scenario's, as a turn over HTTP is
614
667
  async (asked) => engineOf()?.serve(asked)) : undefined;
615
668
  const upgrade = laneUpgrades(parts.lanes ?? {}, ownUpgrade);
@@ -739,9 +792,17 @@ export function createVendorFetch(manifest, lanes) {
739
792
  const ingest = Object.values(lanes).find((l) => l.fetch.ingest)?.fetch.ingest;
740
793
  const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
741
794
  return Object.assign(async (request) => {
795
+ const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
796
+ // the discovery door is answered before any lane's clock moves, as a pack's own is (createPackFetch): reading what
797
+ // the twin is moves no vendor clock
798
+ if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
799
+ // the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
800
+ const screens = lanesWorkspaces(lanes);
801
+ const laneDiscovery = await laneDoors(lanes, request, '/twin');
802
+ return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}), ...(Object.keys(laneDiscovery).length ? { lanes: laneDiscovery } : {}) });
803
+ }
742
804
  for (const part of Object.values(lanes))
743
805
  await part.fetch.advanceClock?.(request);
744
- const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
745
806
  // the vendor's board is the frames of the lanes that lay one out, each drawn when any lane's screen takes its url
746
807
  if (request.method === 'GET' && path === '/twin/board') {
747
808
  const frames = [];
@@ -758,12 +819,6 @@ export function createVendorFetch(manifest, lanes) {
758
819
  const draws = (url) => Object.values(lanes).some((l) => l.fetch.draws?.(url) ?? false);
759
820
  return Response.json({ frames: frames.map((f) => ({ ...f, drawn: draws(f.url) })) });
760
821
  }
761
- if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
762
- // the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
763
- const screens = lanesWorkspaces(lanes);
764
- const laneDiscovery = await laneDoors(lanes, request, '/twin');
765
- return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}), ...(Object.keys(laneDiscovery).length ? { lanes: laneDiscovery } : {}) });
766
- }
767
822
  if (request.method === 'GET' && path === '/twin/scenario') {
768
823
  const laneScenarios = await laneDoors(lanes, request, '/twin/scenario');
769
824
  if (Object.keys(laneScenarios).length)
@@ -8,7 +8,8 @@ export declare function installedVendorPacks(from: string, options?: {
8
8
  }): Map<string, string>;
9
9
  /** Node's nearest installed package, including linked packages and hoisted workspace dependencies. */
10
10
  export declare function installedPackageDir(from: string, name: string): string | undefined;
11
- /** A pack repository itself, or the P3 repository beside a platform checkout. Applications do not acquire a
11
+ /** A pack repository itself, or a platform checkout's P3 repository: the one `VOLTER_PACKS` names (a worktree's own
12
+ * pinned packs, as the repository's scripts read it), else the one beside it. Applications do not acquire a
12
13
  * sibling checkout merely because one happens to be beside them. */
13
14
  export declare function packRepository(from: string): string | undefined;
14
15
  /** Resolve one vendor's package directory for host-side reads (assets, infrastructure launchers). */
@@ -121,7 +121,8 @@ export function installedPackageDir(from, name) {
121
121
  dir = up;
122
122
  }
123
123
  }
124
- /** A pack repository itself, or the P3 repository beside a platform checkout. Applications do not acquire a
124
+ /** A pack repository itself, or a platform checkout's P3 repository: the one `VOLTER_PACKS` names (a worktree's own
125
+ * pinned packs, as the repository's scripts read it), else the one beside it. Applications do not acquire a
125
126
  * sibling checkout merely because one happens to be beside them. */
126
127
  export function packRepository(from) {
127
128
  let dir = resolve(from);
@@ -129,6 +130,10 @@ export function packRepository(from) {
129
130
  if (packageName(dir) === 'twin-packs-p3')
130
131
  return dir;
131
132
  if (packageName(join(dir, 'packages', 'world-core')) === '@volter/world-core') {
133
+ const env = globalThis.process?.env?.VOLTER_PACKS;
134
+ const named = env ? resolve(env) : undefined;
135
+ if (named && packageName(named) === 'twin-packs-p3')
136
+ return named;
132
137
  const sibling = join(dirname(dir), 'twin-packs-p3');
133
138
  if (packageName(sibling) === 'twin-packs-p3')
134
139
  return sibling;
@@ -5,6 +5,7 @@ import type { DerivedManifest } from './derived-core.js';
5
5
  import type { DerivedSurface } from './derived.js';
6
6
  import type { TwinEmitter } from './emit.js';
7
7
  import { type StateSystemAdapters } from './state-system.js';
8
+ export { PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from './protocol.js';
8
9
  /**
9
10
  * How a pack's clients ADDRESS it. The first three are HTTP API styles; `raw-tcp` is the
10
11
  * RAW-PROTOCOL class — a pack whose clients speak their protocol directly over a TCP socket
@@ -37,19 +38,6 @@ export type PackTransport = 'rest' | 'graphql' | 'web-api' | 'raw-tcp';
37
38
  * seam (fly's runtime, github's git plane, supabase's external stack).
38
39
  * 'proxy' — the vendor itself is a forwarding plane (tunnel).
39
40
  */
40
- /** THE PLATFORM PROTOCOL VERSION (runtime contract R16): `major.minor`. The major names the
41
- * descriptor shape, the factory signature, the doors and the adapter contract a package is
42
- * written against, and the build form it is made in (the derived pack); a change that removes or
43
- * alters a required shape bumps it, an additive change bumps minor. A package declares the major
44
- * it targets (`protocol`), and the kernel serves its own major and no other: a package that
45
- * declares another, or none, is refused. */
46
- export declare const PROTOCOL_VERSION = "3.0";
47
- export declare const PROTOCOL_MAJOR: number;
48
- /** Where a package's declared protocol stands against this kernel: `current`, or `refused`. */
49
- export declare function protocolStanding(declared: string | undefined): {
50
- major: number;
51
- standing: 'current' | 'refused';
52
- };
53
41
  export declare const PACK_ARCHETYPES: readonly ["crud", "generative", "signed-protocol", "engine-control", "proxy"];
54
42
  export type PackArchetype = (typeof PACK_ARCHETYPES)[number];
55
43
  /**
@@ -223,9 +211,17 @@ export type TwinPack = {
223
211
  urlEnv: string;
224
212
  };
225
213
  managedService?: {
226
- kind: 'redis' | 'mongodb';
214
+ kind: string;
227
215
  scheme: string;
228
216
  note: string;
217
+ configurationArg?: '--configuration';
218
+ originalInputs?: {
219
+ version: 1;
220
+ manifestArg: '--original-inputs';
221
+ rootArg: '--original-root';
222
+ digestArg: '--original-inputs-sha256';
223
+ paths: string[];
224
+ };
229
225
  };
230
226
  managedDatabase?: {
231
227
  kind: 'postgres';
@@ -11,6 +11,8 @@ import { declareRateBudget } from "./rateBudget.js";
11
11
  import { referenceField, registerReferences } from "./references.js";
12
12
  import { deriveStateSystem, deriveVendorStateSystem } from "./derived-real.js";
13
13
  import { registerAuthStrategy, registerStateSystem } from "./state-system.js";
14
+ import { PROTOCOL_VERSION, protocolStanding } from "./protocol.js";
15
+ export { PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from "./protocol.js";
14
16
  /**
15
17
  * The pack's SERVE-FAMILY — the axis the invariant matrix keys strictness off, DECLARED
16
18
  * because it is the pack's own claim about what kind of thing it is (a heuristic over file
@@ -26,19 +28,6 @@ import { registerAuthStrategy, registerStateSystem } from "./state-system.js";
26
28
  * seam (fly's runtime, github's git plane, supabase's external stack).
27
29
  * 'proxy' — the vendor itself is a forwarding plane (tunnel).
28
30
  */
29
- /** THE PLATFORM PROTOCOL VERSION (runtime contract R16): `major.minor`. The major names the
30
- * descriptor shape, the factory signature, the doors and the adapter contract a package is
31
- * written against, and the build form it is made in (the derived pack); a change that removes or
32
- * alters a required shape bumps it, an additive change bumps minor. A package declares the major
33
- * it targets (`protocol`), and the kernel serves its own major and no other: a package that
34
- * declares another, or none, is refused. */
35
- export const PROTOCOL_VERSION = '3.0';
36
- export const PROTOCOL_MAJOR = Number(PROTOCOL_VERSION.split('.')[0]);
37
- /** Where a package's declared protocol stands against this kernel: `current`, or `refused`. */
38
- export function protocolStanding(declared) {
39
- const major = declared === undefined ? Number.NaN : Number(String(declared).split('.')[0]);
40
- return major === PROTOCOL_MAJOR ? { major, standing: 'current' } : { major, standing: 'refused' };
41
- }
42
31
  export const PACK_ARCHETYPES = ['crud', 'generative', 'signed-protocol', 'engine-control', 'proxy'];
43
32
  /** The posture to assume for a pack that doesn't declare one. Conservative on purpose. */
44
33
  export const DEFAULT_PULL_POSTURE = 'on-demand';
@@ -0,0 +1,13 @@
1
+ /** THE PLATFORM PROTOCOL VERSION (runtime contract R16): `major.minor`. The major names the
2
+ * descriptor shape, the factory signature, the doors and the adapter contract a package is
3
+ * written against, and the build form it is made in (the derived pack); a change that removes or
4
+ * alters a required shape bumps it, an additive change bumps minor. A package declares the major
5
+ * it targets (`protocol`), and the kernel serves its own major and no other: a package that
6
+ * declares another, or none, is refused. */
7
+ export declare const PROTOCOL_VERSION = "3.0";
8
+ export declare const PROTOCOL_MAJOR: number;
9
+ /** Where a package's declared protocol stands against this kernel: `current`, or `refused`. */
10
+ export declare function protocolStanding(declared: string | undefined): {
11
+ major: number;
12
+ standing: 'current' | 'refused';
13
+ };
@@ -0,0 +1,13 @@
1
+ /** THE PLATFORM PROTOCOL VERSION (runtime contract R16): `major.minor`. The major names the
2
+ * descriptor shape, the factory signature, the doors and the adapter contract a package is
3
+ * written against, and the build form it is made in (the derived pack); a change that removes or
4
+ * alters a required shape bumps it, an additive change bumps minor. A package declares the major
5
+ * it targets (`protocol`), and the kernel serves its own major and no other: a package that
6
+ * declares another, or none, is refused. */
7
+ export const PROTOCOL_VERSION = '3.0';
8
+ export const PROTOCOL_MAJOR = Number(PROTOCOL_VERSION.split('.')[0]);
9
+ /** Where a package's declared protocol stands against this kernel: `current`, or `refused`. */
10
+ export function protocolStanding(declared) {
11
+ const major = declared === undefined ? Number.NaN : Number(String(declared).split('.')[0]);
12
+ return major === PROTOCOL_MAJOR ? { major, standing: 'current' } : { major, standing: 'refused' };
13
+ }
@@ -45,9 +45,9 @@ type StoredZSet = Array<[string, string]>;
45
45
  export type StreamEntries = Array<[string, HashPairs]>;
46
46
  /** A point-in-time copy of a run's whole mutable state, for script rollback. */
47
47
  type Snapshot = {
48
- rows: Map<string, KeyRow>;
48
+ rows: OverlayState<KeyRow>;
49
49
  touched: Map<string, string>;
50
- scripts: Map<string, string>;
50
+ scripts: OverlayState<string>;
51
51
  touchedScripts: Map<string, string>;
52
52
  };
53
53
  export type KeyRow = {
@@ -168,12 +168,17 @@ export type KeyspaceImage = {
168
168
  scripts: Map<string, string>;
169
169
  revs: Map<string, number>;
170
170
  };
171
+ declare const GONE: unique symbol;
172
+ type OverlayState<V> = {
173
+ own: Map<string, V | typeof GONE>;
174
+ moved: Set<string>;
175
+ };
171
176
  /**
172
177
  * A synchronous, in-memory image of the keyspace for the duration of ONE request.
173
178
  *
174
179
  * Seeded from the keyspace's image (the tree folded by `projectResources`, memoized per World state — the kernel IS
175
180
  * the source of truth); every mutation records the key name in `touched`, and the flush writes exactly those keys
176
- * back. This run's maps are its own copies: nothing a run changes reaches the image or a later request except
181
+ * back. This run owns its overlays: nothing a run changes reaches the image or a later request except
177
182
  * through the tree.
178
183
  */
179
184
  export declare class KeySpace {
@@ -185,7 +190,7 @@ export declare class KeySpace {
185
190
  private readonly touchedScripts;
186
191
  /** name → the last write ordinal seen, INCLUDING for keys currently deleted or expired. */
187
192
  private readonly revs;
188
- /** The keyspace as the tree held it when this run began: shared, never changed in place. */
193
+ /** The shared base maps are patched by the next image(); a KeySpace must finish reading its base before it yields. */
189
194
  readonly image: KeyspaceImage;
190
195
  constructor(ctx: RedisContext, nowMs: number);
191
196
  /**
@@ -323,5 +328,6 @@ export declare function execRedisRun(commands: string[][], ctx: RedisContext): P
323
328
  items: RunItem[];
324
329
  wrote: boolean;
325
330
  instant: number;
331
+ touched: string[];
326
332
  }>;
327
333
  export {};