@volter/world-core 2.0.0

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 (180) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/app-route.cjs +154 -0
  4. package/app-route.d.cts +7 -0
  5. package/attach.cjs +80 -0
  6. package/dist/app-route.cjs +154 -0
  7. package/dist/app-route.d.cts +7 -0
  8. package/dist/attach.cjs +80 -0
  9. package/dist/generated/pack-facts.json +4306 -0
  10. package/dist/inject.cjs +1097 -0
  11. package/dist/network-policy.cjs +92 -0
  12. package/dist/network-policy.d.cts +10 -0
  13. package/dist/src/actions.d.ts +276 -0
  14. package/dist/src/actions.js +436 -0
  15. package/dist/src/ancestry.d.ts +22 -0
  16. package/dist/src/ancestry.js +238 -0
  17. package/dist/src/args.d.ts +3 -0
  18. package/dist/src/args.js +12 -0
  19. package/dist/src/blob-store.d.ts +55 -0
  20. package/dist/src/blob-store.js +186 -0
  21. package/dist/src/brand-tokens.d.ts +2 -0
  22. package/dist/src/brand-tokens.js +17 -0
  23. package/dist/src/changeset.d.ts +431 -0
  24. package/dist/src/changeset.js +0 -0
  25. package/dist/src/client-bundle.d.ts +1 -0
  26. package/dist/src/client-bundle.js +28 -0
  27. package/dist/src/credential.d.ts +38 -0
  28. package/dist/src/credential.js +114 -0
  29. package/dist/src/derived-core.d.ts +452 -0
  30. package/dist/src/derived-core.js +782 -0
  31. package/dist/src/derived.d.ts +84 -0
  32. package/dist/src/derived.js +122 -0
  33. package/dist/src/emit.d.ts +106 -0
  34. package/dist/src/emit.js +157 -0
  35. package/dist/src/executor.d.ts +120 -0
  36. package/dist/src/executor.js +387 -0
  37. package/dist/src/file-response.d.ts +3 -0
  38. package/dist/src/file-response.js +22 -0
  39. package/dist/src/fork.d.ts +26 -0
  40. package/dist/src/fork.js +68 -0
  41. package/dist/src/git/history.d.ts +36 -0
  42. package/dist/src/git/history.js +298 -0
  43. package/dist/src/git/index.d.ts +6 -0
  44. package/dist/src/git/index.js +6 -0
  45. package/dist/src/git/inflate.d.ts +11 -0
  46. package/dist/src/git/inflate.js +194 -0
  47. package/dist/src/git/objects.d.ts +64 -0
  48. package/dist/src/git/objects.js +161 -0
  49. package/dist/src/git/pack.d.ts +14 -0
  50. package/dist/src/git/pack.js +199 -0
  51. package/dist/src/git/refs.d.ts +19 -0
  52. package/dist/src/git/refs.js +35 -0
  53. package/dist/src/git/smart-http.d.ts +45 -0
  54. package/dist/src/git/smart-http.js +223 -0
  55. package/dist/src/hash.d.ts +38 -0
  56. package/dist/src/hash.js +48 -0
  57. package/dist/src/head.d.ts +140 -0
  58. package/dist/src/head.js +313 -0
  59. package/dist/src/history.d.ts +76 -0
  60. package/dist/src/history.js +322 -0
  61. package/dist/src/index.d.ts +73 -0
  62. package/dist/src/index.js +98 -0
  63. package/dist/src/lifecycle.d.ts +1 -0
  64. package/dist/src/lifecycle.js +8 -0
  65. package/dist/src/log.d.ts +254 -0
  66. package/dist/src/log.js +801 -0
  67. package/dist/src/mirror-shell.d.ts +2 -0
  68. package/dist/src/mirror-shell.js +13 -0
  69. package/dist/src/observe.d.ts +49 -0
  70. package/dist/src/observe.js +148 -0
  71. package/dist/src/pack-assets.d.ts +30 -0
  72. package/dist/src/pack-assets.js +88 -0
  73. package/dist/src/packRegistry.d.ts +374 -0
  74. package/dist/src/packRegistry.js +142 -0
  75. package/dist/src/placeholder-remote.d.ts +22 -0
  76. package/dist/src/placeholder-remote.js +86 -0
  77. package/dist/src/proxy.d.ts +25 -0
  78. package/dist/src/proxy.js +155 -0
  79. package/dist/src/rateBudget.d.ts +367 -0
  80. package/dist/src/rateBudget.js +925 -0
  81. package/dist/src/references.d.ts +18 -0
  82. package/dist/src/references.js +27 -0
  83. package/dist/src/remote-execute.d.ts +22 -0
  84. package/dist/src/remote-execute.js +1 -0
  85. package/dist/src/resource-blob.d.ts +10 -0
  86. package/dist/src/resource-blob.js +56 -0
  87. package/dist/src/scenario.d.ts +197 -0
  88. package/dist/src/scenario.js +425 -0
  89. package/dist/src/schemas.d.ts +78 -0
  90. package/dist/src/schemas.js +50 -0
  91. package/dist/src/serve-http.d.ts +48 -0
  92. package/dist/src/serve-http.js +340 -0
  93. package/dist/src/serve.d.ts +147 -0
  94. package/dist/src/serve.js +507 -0
  95. package/dist/src/shared-blob-index.d.ts +4 -0
  96. package/dist/src/shared-blob-index.js +126 -0
  97. package/dist/src/state-system.d.ts +70 -0
  98. package/dist/src/state-system.js +90 -0
  99. package/dist/src/storage.d.ts +101 -0
  100. package/dist/src/storage.js +337 -0
  101. package/dist/src/twin-fetch.d.ts +64 -0
  102. package/dist/src/twin-fetch.js +91 -0
  103. package/dist/src/types.d.ts +40 -0
  104. package/dist/src/types.js +1 -0
  105. package/dist/src/v1-removed.d.ts +159 -0
  106. package/dist/src/v1-removed.js +124 -0
  107. package/dist/src/volter-home.d.ts +5 -0
  108. package/dist/src/volter-home.js +10 -0
  109. package/dist/src/world-clock.d.ts +4 -0
  110. package/dist/src/world-clock.js +32 -0
  111. package/dist/src/world-env.d.ts +3 -0
  112. package/dist/src/world-env.js +22 -0
  113. package/dist/src/world-store-sql.d.ts +27 -0
  114. package/dist/src/world-store-sql.js +86 -0
  115. package/dist/src/world-store.d.ts +168 -0
  116. package/dist/src/world-store.js +475 -0
  117. package/dist/src/worldConfig.d.ts +9 -0
  118. package/dist/src/worldConfig.js +17 -0
  119. package/dist/stream-bridge.cjs +80 -0
  120. package/dist/vendor-hosts.cjs +200 -0
  121. package/generated/pack-facts.json +4306 -0
  122. package/inject.cjs +1097 -0
  123. package/network-policy.cjs +92 -0
  124. package/network-policy.d.cts +10 -0
  125. package/package.json +103 -0
  126. package/src/actions.ts +564 -0
  127. package/src/ancestry.ts +213 -0
  128. package/src/args.ts +14 -0
  129. package/src/blob-store.ts +185 -0
  130. package/src/brand-tokens.ts +17 -0
  131. package/src/changeset.ts +1032 -0
  132. package/src/client-bundle.ts +29 -0
  133. package/src/credential.ts +140 -0
  134. package/src/derived-core.ts +1004 -0
  135. package/src/derived.ts +176 -0
  136. package/src/emit.ts +242 -0
  137. package/src/executor.ts +431 -0
  138. package/src/file-response.ts +22 -0
  139. package/src/fork.ts +89 -0
  140. package/src/git/history.ts +177 -0
  141. package/src/git/index.ts +6 -0
  142. package/src/git/inflate.ts +125 -0
  143. package/src/git/objects.ts +110 -0
  144. package/src/git/pack.ts +105 -0
  145. package/src/git/refs.ts +25 -0
  146. package/src/git/smart-http.ts +149 -0
  147. package/src/hash.ts +66 -0
  148. package/src/head.ts +318 -0
  149. package/src/history.ts +246 -0
  150. package/src/index.ts +323 -0
  151. package/src/lifecycle.ts +8 -0
  152. package/src/log.ts +793 -0
  153. package/src/mirror-shell.ts +15 -0
  154. package/src/observe.ts +130 -0
  155. package/src/pack-assets.ts +81 -0
  156. package/src/packRegistry.ts +408 -0
  157. package/src/placeholder-remote.ts +81 -0
  158. package/src/proxy.ts +183 -0
  159. package/src/rateBudget.ts +1115 -0
  160. package/src/references.ts +46 -0
  161. package/src/remote-execute.ts +26 -0
  162. package/src/resource-blob.ts +57 -0
  163. package/src/scenario.ts +479 -0
  164. package/src/schemas.ts +56 -0
  165. package/src/serve-http.ts +299 -0
  166. package/src/serve.ts +618 -0
  167. package/src/shared-blob-index.ts +108 -0
  168. package/src/state-system.ts +115 -0
  169. package/src/storage.ts +407 -0
  170. package/src/twin-fetch.ts +147 -0
  171. package/src/types.ts +50 -0
  172. package/src/v1-removed.ts +172 -0
  173. package/src/volter-home.ts +11 -0
  174. package/src/world-clock.ts +33 -0
  175. package/src/world-env.ts +18 -0
  176. package/src/world-store-sql.ts +118 -0
  177. package/src/world-store.ts +572 -0
  178. package/src/worldConfig.ts +27 -0
  179. package/stream-bridge.cjs +80 -0
  180. package/vendor-hosts.cjs +200 -0
package/src/serve.ts ADDED
@@ -0,0 +1,618 @@
1
+ // Twin serve layer (the twins architecture notes, increment 1): answer vendor-shaped
2
+ // READS from the local event-sourced state — the "local twin" read path. An app
3
+ // or agent points at this instead of the real vendor; it responds from local
4
+ // state with the real service unreachable.
5
+ //
6
+ // There is ONE twin, not a set of "modes". Pulling reality, writing locally, and
7
+ // forking are operations on the same substrate (events + local actions + the
8
+ // projection over them), not exclusive modes — see the architecture doc, "a twin
9
+ // is a repo". The only serve-time policy here is `readOnly`: a twin accepts local
10
+ // writes (as actions) unless you start it read-only (a pure mirror of pulled
11
+ // reality). Forking is a separate data operation (fork.ts), never a serve mode.
12
+ import { AsyncLocalStorage } from 'node:async_hooks';
13
+ import { serveHttp, setServeDecorator, WORLD_BOOT_PATH, type HttpHandler } from './serve-http.ts';
14
+ import { performAtHead } from './head.ts';
15
+ import { createHash } from 'node:crypto';
16
+ import { dirname, join } from 'node:path';
17
+ import type { SubjectFields } from './hash.ts';
18
+ import { hashFieldValue } from './hash.ts';
19
+ import { appendActionIfAbsent, appendActionOccurrence, decideAndAppendAction, projectResources } from './actions.ts';
20
+ import type { ActionProjection, TwinAction, TwinActionPrecondition } from './actions.ts';
21
+ import { observeWorldPaths, worldPaths } from './storage.ts';
22
+ import { getActiveWorldStore } from './world-store.ts';
23
+
24
+ // ── the request journal: the INSPECT half of the read path ─────────────────────────────────────
25
+ // `actions.jsonl` records MUTATIONS only, so a twin's READS are invisible to `volter-world tail`
26
+ // — an empty-catalog GET that 404s leaves no trace, and "did the app even ask?" is unanswerable.
27
+ // The journal is the minimal honest answer: an OPT-IN (VOLTER_TWIN_REQUEST_JOURNAL=1) per-twin
28
+ // `requests.jsonl` beside `actions.jsonl`, one line per served HTTP request. Size-capped: at ~1 MB
29
+ // the file rotates once to `requests.jsonl.1` (previous rotation overwritten), so an enabled
30
+ // journal can never grow unbounded. `volter-world tail --requests` merges these lines live.
31
+ //
32
+ // WHAT A LINE CARRIES. `{at, method, path, status}` — the shape — plus, when the request presented
33
+ // one, the CREDENTIAL SHAPE: which header/query-param NAMES carried a credential-looking value,
34
+ // each with a truncated sha256 FINGERPRINT of the value. Never the value, never the rest of the
35
+ // query string, never a body. That is what makes the journal answer the question a twin exists to
36
+ // answer and could not: *which credential arrived, in which header?* Two different fingerprints
37
+ // under `dd-api-key` and `dd-application-key` say "two distinct keys arrived, in their own named
38
+ // headers"; the same fingerprint on two rows says "the same value came back"; a missing name says
39
+ // the caller sent nothing. Reading a fingerprint back to its secret is not possible — it is
40
+ // sha256, truncated — so the file stays safe to paste into an issue.
41
+ //
42
+ // The query string used to be dropped whole (it carries tokens), which is precisely why a
43
+ // query-placed key was invisible: the journal now names the credential PARAMS and fingerprints
44
+ // them, and still records `path` without its query.
45
+ //
46
+ // A fingerprint is not a vault: it is sha256 of the value, so a LOW-entropy credential (`"test"`)
47
+ // is guessable from its fingerprint by anyone who guesses the value first. It is exactly the right
48
+ // strength for its job — telling two credentials apart, and recognising one across requests.
49
+ //
50
+ // WHERE IT IS WIRED. Not per-pack. Every vendor pack mounts its own `Bun.serve({fetch})` inside a
51
+ // `create<Vendor>TwinServer({port, root, readOnly})` factory — there is no shared middleware, but
52
+ // there IS a shared CONSTRUCTOR, so `installTwinRequestJournal()` (below, called once at module
53
+ // load) wraps `Bun.serve` and journals every twin's fetch handler. The pack writes no journaling
54
+ // line at all; the four packs that used to call `journalTwinRequest` themselves no longer do.
55
+ // Measured coverage: 66 of the 67 pack HTTP servers journal with zero pack-side code. The one that
56
+ // does not is STRUCTURAL, not missed — `smtp` is a raw-TCP `Bun.listen` mail listener with no HTTP
57
+ // surface. Anything that mounts an HTTP twin through `Bun.serve` is covered by
58
+ // construction; anything that does not is a second transport and would need its own seam.
59
+
60
+ export type TwinCredentialShape = {
61
+ /** header name (lowercased) or query-param name the credential arrived under. */
62
+ name: string;
63
+ /** where it sat on the request. */
64
+ in: 'header' | 'query';
65
+ /** the auth SCHEME when the value carried one (`Bearer`, `Basic`, `AWS4-HMAC-SHA256`). A scheme
66
+ * is not a secret, and it is what distinguishes "a bearer token arrived" from "a named vendor
67
+ * header arrived" — the exact confusion this journal was added to end. */
68
+ scheme?: string;
69
+ /** `sha256:<16 hex>` of the value (of the credential part when a scheme was present). Stable
70
+ * across processes and runs, so equal fingerprints mean equal values; non-reversible. */
71
+ fp?: string;
72
+ /** the name arrived with an EMPTY value — the "the SDK sent the header but no key" case. */
73
+ empty?: true;
74
+ };
75
+
76
+ export type TwinRequestJournalEntry = {
77
+ at?: string;
78
+ method: string;
79
+ path: string;
80
+ status: number;
81
+ /** the serve's wall time in milliseconds (layer 8: a world's response-time report) */
82
+ ms?: number;
83
+ /** credential-looking headers/query params that arrived, named + fingerprinted, never quoted. */
84
+ credentials?: TwinCredentialShape[];
85
+ };
86
+
87
+ /** The journal read back: every entry kept for a service, oldest first (an empty list when the journal is off or empty). */
88
+ export function readTwinRequestJournal(service: string, root?: string): TwinRequestJournalEntry[] {
89
+ const path = twinRequestJournalPath(service, root);
90
+ const out: TwinRequestJournalEntry[] = [];
91
+ for (const file of [`${path}.1`, path]) {
92
+ const text = getActiveWorldStore().read(file); if (!text) continue;
93
+ for (const line of text.split('\n')) { if (!line) continue; try { out.push(JSON.parse(line) as TwinRequestJournalEntry); } catch { /* a torn line at rotation */ } }
94
+ }
95
+ return out;
96
+ }
97
+
98
+ /** ~1 MB cap before a one-deep rotation — small enough to never matter on disk, large enough for
99
+ * tens of thousands of entries (a shape line is ~80 bytes). */
100
+ const REQUEST_JOURNAL_MAX_BYTES = 1_000_000;
101
+
102
+ /**
103
+ * Does this header/param NAME look like it carries a credential? Deliberately a generic word
104
+ * predicate, never a vendor table: the kernel must not learn that Datadog calls its keys
105
+ * `DD-API-KEY` or that Postmark calls its tokens `X-Postmark-Server-Token`. Both fall out of
106
+ * `key`/`token` as name segments, and so does every vendor that has not been written yet.
107
+ */
108
+ const CREDENTIAL_NAME = /(?:^|[-_.])(?:auth|authorization|token|jwt|key|apikey|secret|credential|credentials|signature|sig|password|passwd|pwd|session|sessionid|cookie|access[-_]?token|refresh[-_]?token)(?:[-_.]|$)/i;
109
+
110
+ /** Auth schemes recognised in a value even when the NAME says nothing (`Bearer …` in `x-custom`). */
111
+ const CREDENTIAL_SCHEME = /^(Bearer|Basic|Digest|Token|Negotiate|NTLM|OAuth|Signature|Hawk|AWS4-HMAC-SHA256|SharedKey|SharedKeyLite|GoogleLogin)\s+(\S[\s\S]*)$/i;
112
+
113
+ /** Non-reversible, stable fingerprint. 64 bits of sha256 — enough that two distinct credentials
114
+ * never collide in a journal, far too little to walk back to a real key. */
115
+ export function credentialFingerprint(value: string): string {
116
+ return `sha256:${createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 16)}`;
117
+ }
118
+
119
+ function credentialShapeFor(name: string, value: string, where: 'header' | 'query'): TwinCredentialShape | undefined {
120
+ const scheme = CREDENTIAL_SCHEME.exec(value);
121
+ if (!scheme && !CREDENTIAL_NAME.test(name)) return undefined;
122
+ const lower = name.toLowerCase();
123
+ if (value === '') return { name: lower, in: where, empty: true };
124
+ if (scheme) return { name: lower, in: where, scheme: scheme[1]!, fp: credentialFingerprint(scheme[2]!) };
125
+ return { name: lower, in: where, fp: credentialFingerprint(value) };
126
+ }
127
+
128
+ /**
129
+ * The credential SHAPE of one request: every credential-looking header and query param, named and
130
+ * fingerprinted, sorted for stable diffing. Values never leave this function.
131
+ */
132
+ export function twinRequestCredentials(headers: Headers | Record<string, string>, url?: string | URL): TwinCredentialShape[] {
133
+ const shapes: TwinCredentialShape[] = [];
134
+ const seen = new Set<string>();
135
+ const add = (name: string, value: string, where: 'header' | 'query') => {
136
+ const shape = credentialShapeFor(name, value, where);
137
+ if (!shape) return;
138
+ const key = `${shape.in}:${shape.name}`;
139
+ if (seen.has(key)) return;
140
+ seen.add(key);
141
+ shapes.push(shape);
142
+ };
143
+ try {
144
+ if (typeof (headers as Headers).forEach === 'function') {
145
+ (headers as Headers).forEach((value, name) => add(name, value, 'header'));
146
+ } else {
147
+ for (const [name, value] of Object.entries(headers as Record<string, string>)) add(name, String(value), 'header');
148
+ }
149
+ } catch { /* a malformed header bag must not fail a serve */ }
150
+ try {
151
+ if (url !== undefined) {
152
+ const search = typeof url === 'string' ? new URL(url, 'http://twin.invalid').search : url.search;
153
+ for (const [name, value] of new URLSearchParams(search)) add(name, value, 'query');
154
+ }
155
+ } catch { /* an unparseable URL must not fail a serve */ }
156
+ return shapes.sort((a, b) => (a.in === b.in ? a.name.localeCompare(b.name) : a.in.localeCompare(b.in)));
157
+ }
158
+
159
+ export function twinRequestJournalEnabled(env: Record<string, string | undefined> = process.env): boolean {
160
+ return env.VOLTER_TWIN_REQUEST_JOURNAL === '1';
161
+ }
162
+
163
+ /** Where a service's request journal lives: beside its `actions.jsonl`. */
164
+ export function twinRequestJournalPath(service: string, root?: string): string {
165
+ return join(dirname(worldPaths(service, root).events), 'requests.jsonl');
166
+ }
167
+
168
+ /**
169
+ * Append one served request to the journal — a no-op unless VOLTER_TWIN_REQUEST_JOURNAL=1, so
170
+ * the default path does zero I/O. Never throws: an observability append must not fail a serve.
171
+ */
172
+ export function journalTwinRequest(service: string, entry: TwinRequestJournalEntry, root?: string): void {
173
+ if (!twinRequestJournalEnabled()) return;
174
+ try {
175
+ const store = getActiveWorldStore();
176
+ const path = twinRequestJournalPath(service, root);
177
+ store.mkdir(dirname(path));
178
+ const size = store.stat(path)?.size ?? 0;
179
+ if (size >= REQUEST_JOURNAL_MAX_BYTES) {
180
+ // one-deep rotation: the previous overflow is overwritten, the live file starts fresh.
181
+ store.writeAtomic(`${path}.1`, store.read(path) ?? '');
182
+ store.write(path, '');
183
+ }
184
+ const line = JSON.stringify({
185
+ at: entry.at ?? new Date().toISOString(),
186
+ method: entry.method.toUpperCase(),
187
+ path: entry.path.split('?')[0], // the query STRING never lands; its credential params do, named + fingerprinted
188
+ status: entry.status,
189
+ ...(typeof entry.ms === 'number' ? { ms: entry.ms } : {}),
190
+ ...(entry.credentials?.length ? { credentials: entry.credentials } : {}),
191
+ });
192
+ store.append(path, `${line}\n`);
193
+ } catch {
194
+ // journaling is best-effort observability; the serve path must not care
195
+ }
196
+ }
197
+
198
+ // ── how a wrapped `Bun.serve` learns which twin it is ──────────────────────────────────────────
199
+ // `Bun.serve({port, fetch})` carries neither the vendor id nor the state root — both live in the
200
+ // factory's closure. They are recoverable without touching a single pack, because handling a
201
+ // request makes the pack call the kernel, and every kernel state path funnels through
202
+ // `worldPaths(service, root)` (storage.ts). Observing the FIRST such call a server makes is the
203
+ // twin naming itself: `datadog` under `/tmp/world/data/datadog`. It is cached per server, so only
204
+ // the first request pays for it. A server whose first request touches no state falls back to the
205
+ // call site (`packages/twin/<vendor>/…` or `@volter/twin-<vendor>`) and the process's `--root`
206
+ // argument, which is how the world runtime launches every twin.
207
+
208
+ export const TWIN_JOURNAL_IDENTITY = Symbol.for('volter.twin.requestJournal.identity');
209
+ const JOURNAL_SHIM_INSTALLED = Symbol.for('volter.twin.requestJournal.installed');
210
+
211
+ export type TwinJournalIdentity = { service: string; root?: string };
212
+
213
+ /** One capture slot per in-flight request, so two twins co-located in ONE process (the shared
214
+ * host) can never read each other's identity off a racing sibling's first request.
215
+ *
216
+ * LAZY ON PURPOSE, and this is load-bearing rather than style. Mirror-UI packs import their own
217
+ * server module into the BROWSER bundle and rely on Bun tree-shaking the server-only half away
218
+ * (see any `*-mirror-ui.ts` header). A module-scope `new AsyncLocalStorage()` is a side effect,
219
+ * so it survives tree-shaking and lands in the client — where `node:async_hooks` does not
220
+ * resolve, `new` throws at bundle evaluation, and the React app never mounts. That took out
221
+ * every mirror UI in the estate at once. Nothing in this module may run at module scope. */
222
+ let identityCapture: AsyncLocalStorage<{ identity?: TwinJournalIdentity }> | undefined;
223
+
224
+ function identitySlot(): AsyncLocalStorage<{ identity?: TwinJournalIdentity }> {
225
+ identityCapture ??= new AsyncLocalStorage<{ identity?: TwinJournalIdentity }>();
226
+ return identityCapture;
227
+ }
228
+
229
+ function observeIdentityFromWorldPaths(): void {
230
+ observeWorldPaths((service, root) => {
231
+ const slot = identityCapture?.getStore();
232
+ if (!slot || slot.identity !== undefined) return;
233
+ slot.identity = root === undefined ? { service } : { service, root };
234
+ });
235
+ }
236
+
237
+ /** The `--root DIR` a twin was launched with (the world runtime's spawn contract), if any. */
238
+ function rootFromArgv(argv: readonly string[] = process.argv): string | undefined {
239
+ const index = argv.indexOf('--root');
240
+ const value = index >= 0 ? argv[index + 1] : undefined;
241
+ return value && !value.startsWith('--') ? value : undefined;
242
+ }
243
+
244
+ /** The vendor a `Bun.serve` call site belongs to, from its module path. Infra packages are not
245
+ * vendors, so they are skipped: whoever called THEM is the twin. */
246
+ const INFRA_PACKAGES = new Set(['world-runtime', 'world-tooling', 'world-attach', 'world-browser-assets', 'world-host']);
247
+
248
+ export function vendorFromStack(stack: string | undefined): string | undefined {
249
+ if (!stack) return undefined;
250
+ const pattern = /(?:packages[\\/]twin[\\/]|@volter[\\/]twin-)([A-Za-z0-9_-]+)/g;
251
+ for (const match of stack.matchAll(pattern)) {
252
+ const name = match[1]!;
253
+ if (!INFRA_PACKAGES.has(name)) return name;
254
+ }
255
+ return undefined;
256
+ }
257
+
258
+
259
+
260
+ /**
261
+ * Wrap `Bun.serve` so EVERY twin's HTTP surface journals, with no per-pack line. Idempotent
262
+ * (a `Symbol.for` marker survives a second copy of this module) and a strict pass-through when
263
+ * VOLTER_TWIN_REQUEST_JOURNAL is not `1` — the default path adds one function call per request
264
+ * and does zero I/O. Called once at module load, below; exported so a test can assert it.
265
+ *
266
+ * A pack that knows its own identity may declare it by putting `{service, root}` on the serve
267
+ * options under `TWIN_JOURNAL_IDENTITY` (a symbol key Bun's option reader ignores). Nothing in
268
+ * the estate needs to: `createTwinServer` is the only caller, because it is the one server whose
269
+ * service is an argument rather than a fact about the pack.
270
+ */
271
+ export function installTwinRequestJournal(): boolean {
272
+ // a browser bundle (a mirror UI carrying the kernel): nothing to wrap, nothing to touch
273
+ if (typeof window !== 'undefined' || typeof document !== 'undefined') return false;
274
+ const bun = (globalThis as { Bun?: { serve?: unknown } }).Bun;
275
+ const original = bun && typeof bun.serve === 'function' ? bun.serve as (...args: unknown[]) => { port?: number } : undefined;
276
+ if (original && (original as unknown as Record<symbol, unknown>)[JOURNAL_SHIM_INSTALLED]) return true;
277
+ // Registered HERE, not at module scope, for the same reason the capture slot is lazy: a
278
+ // module-scope registration is a side effect that survives tree-shaking into the client.
279
+ observeIdentityFromWorldPaths();
280
+ // every server made through the seam (serve-http.ts) journals — on Node this is the only wrap
281
+ setServeDecorator((options) => journalingFetch(options as unknown as Record<string | symbol, unknown> & { fetch: (...a: unknown[]) => unknown }, vendorFromStack(new Error().stack)) as HttpHandler);
282
+ if (!original) return false; // Node: no global to wrap; the seam carries the journal
283
+
284
+ const wrapped = function serve(this: unknown, options: unknown, ...rest: unknown[]) {
285
+ const config = options as (Record<string | symbol, unknown> & { fetch?: (...a: unknown[]) => unknown }) | undefined;
286
+ if (!config || typeof config.fetch !== 'function') return original.apply(this, [options, ...rest]);
287
+ // Captured HERE, at the `Bun.serve` call, because this is the only moment the pack's own
288
+ // module is on the stack — by the time a request is served, Bun is the caller.
289
+ const callSiteVendor = (config[TWIN_JOURNAL_IDENTITY] as TwinJournalIdentity | undefined) ? undefined : vendorFromStack(new Error().stack);
290
+ return original.apply(this, [{ ...config, fetch: journalingFetch(config as Record<string | symbol, unknown> & { fetch: (...a: unknown[]) => unknown }, callSiteVendor) }, ...rest]);
291
+ };
292
+ Object.defineProperty(wrapped, JOURNAL_SHIM_INSTALLED, { value: true });
293
+ (bun as { serve: unknown }).serve = wrapped;
294
+ return true;
295
+ }
296
+
297
+ /** The journaling form of a server's fetch handler: identity resolved once per SERVER (declared
298
+ * under TWIN_JOURNAL_IDENTITY, else observed from the first state-touching request, else the
299
+ * call-site vendor + `--root`), every request journaled with its method, path and status. */
300
+ function journalingFetch(config: Record<string | symbol, unknown> & { fetch: (...a: unknown[]) => unknown }, callSiteVendor: string | undefined): (request: Request, server?: unknown) => unknown {
301
+ if (config.twinRequestJournal === false) return config.fetch;
302
+ {
303
+ const declared = config[TWIN_JOURNAL_IDENTITY] as TwinJournalIdentity | undefined;
304
+ // Resolved once per SERVER, not per request: a twin's identity is a constant of the server.
305
+ let identity: TwinJournalIdentity | undefined = declared;
306
+ const inner = config.fetch as (this: unknown, request: Request, server: unknown) => unknown;
307
+ const journaling = async function (this: unknown, request: Request, server: unknown) {
308
+ // The World's own boot probe (serve-http.ts, WORLD_BOOT_PATH) is not the app's traffic: never journaled.
309
+ if (!twinRequestJournalEnabled() || new URL(request.url).pathname === WORLD_BOOT_PATH) return inner.call(this, request, server);
310
+ const slot: { identity?: TwinJournalIdentity } = {};
311
+ let status = 500; const startedAt = Date.now();
312
+ try {
313
+ const response = (await identitySlot().run(slot, () => inner.call(this, request, server))) as Response | undefined;
314
+ if (response instanceof Response) status = response.status;
315
+ else if (response === undefined) return response; // upgraded (websocket) — nothing served
316
+ return response;
317
+ } finally {
318
+ // Only an OBSERVED identity is cached: it is the twin's own words. A fallback is
319
+ // per-request, so the first request that touches state upgrades every later one instead
320
+ // of locking a guessed root in for the life of the server.
321
+ identity ??= slot.identity;
322
+ const resolved = identity ?? fallbackIdentity(callSiteVendor);
323
+ if (resolved) {
324
+ const url = new URL(request.url);
325
+ journalTwinRequest(resolved.service, {
326
+ method: request.method,
327
+ path: url.pathname,
328
+ status,
329
+ ms: Date.now() - startedAt,
330
+ credentials: twinRequestCredentials(request.headers, url),
331
+ }, resolved.root);
332
+ }
333
+ }
334
+ };
335
+ return journaling;
336
+ }
337
+ }
338
+
339
+ /** Identity for a server whose first request touched no state: the vendor that owns the calling
340
+ * module, plus the `--root` the world runtime launched this twin with. */
341
+ function fallbackIdentity(callSiteVendor: string | undefined): TwinJournalIdentity | undefined {
342
+ const service = process.env.VOLTER_TWIN_JOURNAL_SERVICE || callSiteVendor;
343
+ if (!service || !/^[A-Za-z0-9_-]+$/.test(service)) return undefined;
344
+ const root = rootFromArgv();
345
+ return root === undefined ? { service } : { service, root };
346
+ }
347
+
348
+ // One call, at module load. `@volter/world-core`'s entrypoint re-exports this module, and every pack
349
+ // that serves imports the kernel, so wrapping here is what makes the journal a property of the
350
+ // PRODUCT rather than of the four packs that once remembered to call it.
351
+ //
352
+ // It is a no-op wherever `Bun.serve` is absent, which is what keeps it safe in a browser bundle:
353
+ // off-Bun it returns before touching `node:async_hooks`, `process.argv`, or the storage hook.
354
+ installTwinRequestJournal();
355
+
356
+ // A subject rendered as a vendor resource: its folded current fields + identity.
357
+ export type TwinResource = { id: string; type: string; updatedAt: string } & Record<string, unknown>;
358
+
359
+ // The twin's current view = the OBSERVED mirror (events.jsonl) with the local
360
+ // ACTION log (actions.jsonl) projected over it (R18). Observed facts and local
361
+ // actions are kept separate; this is the single projected read model.
362
+ export function twinResources(service: string, root?: string): TwinResource[] {
363
+ return projectResources(service, root);
364
+ }
365
+
366
+ // Result of a local twin write — it is an ACTION, not an egress/real write.
367
+ export type TwinWriteResult = { status: 'performed' | 'replayed'; actionId: string; /** the id the vendor minted when the head performed the write */ externalId?: string; /** what the vendor answered when the head performed the write, as the adapter returned it */ vendorData?: unknown };
368
+
369
+ export type TwinWriteInput = {
370
+ operation: string;
371
+ provider?: string;
372
+ subjectType: string;
373
+ subjectId: string;
374
+ fields: SubjectFields;
375
+ input?: Record<string, unknown>;
376
+ projection?: ActionProjection;
377
+ occurredAt?: string;
378
+ actor?: { kind: 'agent' | 'human' | 'bot' | 'system'; id?: string };
379
+ preconditions?: TwinActionPrecondition[];
380
+ correlationId?: string;
381
+ uniqueness?: string;
382
+ /** AT-MOST-ONCE, opt in. A local vendor write is an OCCURRENCE by default: two calls are two
383
+ * actions, even byte-identical in the same instant. Pass a caller-supplied key when a re-issue
384
+ * of the SAME request must collapse onto the first. Without a key the kernel cannot tell a retry
385
+ * from a repeat, and guessing from content is what silently dropped writes.
386
+ *
387
+ * SCOPE, stated plainly because an earlier version of this comment overclaimed: the key dedupes
388
+ * on (service, operation, subjectId, key). It therefore helps only when the CALLER supplies the
389
+ * subject id. A create whose id the twin mints gets a fresh id per call and is NOT deduped by
390
+ * this — which is the commonest shape a vendor's own idempotency header covers — so a pack
391
+ * modelling such a header for creates keeps doing it itself, by request signature and stored
392
+ * response (the payments pack in this catalog does). Reuse of a key with a DIFFERENT payload
393
+ * raises a conflict, which the generic twin server answers as a 400 (2026-09-04). */
394
+ idempotencyKey?: string;
395
+ /** The EXACT identity of an action being re-materialized, when the caller already has one —
396
+ * replay reproducing a source changeset's actions into a target world. Distinct from
397
+ * `idempotencyKey`, which is a REQUEST key the kernel derives an id from: this IS the id, so a
398
+ * replayed action keeps the identity it had in the source world. Implies at-most-once, because
399
+ * identity equality is what at-most-once means. Not for packs. */
400
+ actionId?: string;
401
+ };
402
+
403
+ export type AtomicTwinWriteDecision<T> =
404
+ | { kind: 'skip'; value: T }
405
+ | { kind: 'write'; value: T; write: TwinWriteInput };
406
+
407
+ function actionForTwinWrite(service: string, write: TwinWriteInput): TwinAction {
408
+ const occurredAt = write.occurredAt ?? new Date().toISOString();
409
+ // Idempotency: dedup a RE-ISSUED IDENTICAL write only. The key includes a hash of the write
410
+ // CONTENT, not just the timestamp. `undefined` keys are omitted by canonicalJson, preserving
411
+ // every pre-input/projection action id for existing packs.
412
+ const contentHash = hashFieldValue({ operation: write.operation, subjectId: write.subjectId, fields: write.fields, input: write.input, projection: write.projection });
413
+ // A caller-supplied identity MAY carry an occurrence ordinal, and must: `actionId`'s one
414
+ // legitimate caller is replay, and a replayed changeset that contains a REPEAT carries the
415
+ // source's `<base>#1` verbatim. A guard here that rejected ordinal suffixes — added to stop a
416
+ // pack squatting slot #5 — threw on exactly that replay, and nothing had tested "replay a
417
+ // repeat" (found 2026-09-04 in review). The squat it guarded against is a misuse of a
418
+ // replay-only field, is LOUD on collision, and corrupts nothing; breaking the CI primitive to
419
+ // prevent it was the wrong trade.
420
+ const actionId = write.actionId
421
+ ?? (write.idempotencyKey
422
+ ? `twin:${service}:${write.operation}:${write.subjectId}:idem:${write.idempotencyKey}`
423
+ : `twin:${service}:${write.operation}:${write.subjectId}:${occurredAt}:${contentHash}${write.uniqueness ? `:${write.uniqueness}` : ''}`);
424
+ return {
425
+ id: actionId,
426
+ service,
427
+ op: 'set',
428
+ operation: write.operation,
429
+ subject: { type: write.subjectType, id: write.subjectId },
430
+ occurredAt,
431
+ ...(write.actor ? { actor: write.actor } : {}),
432
+ ...(write.preconditions?.length ? { preconditions: write.preconditions } : {}),
433
+ ...(write.correlationId ? { correlationId: write.correlationId } : {}),
434
+ ...(write.idempotencyKey ? { idempotencyKey: write.idempotencyKey } : {}),
435
+ ...(write.input ? { input: write.input } : {}),
436
+ fields: write.fields,
437
+ ...(write.projection ? { projection: write.projection } : {}),
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Decide a state-dependent local write and append it under the same cross-process action lock.
443
+ * Use this when acceptance or the new fields depend on current projected state; a caller-side
444
+ * read followed by `applyTwinWrite` is not atomic across processes.
445
+ */
446
+ export async function applyTwinWriteAtomic<T>(
447
+ service: string,
448
+ prepare: (resources: readonly TwinResource[]) => AtomicTwinWriteDecision<T>,
449
+ root?: string,
450
+ ): Promise<{ value: T; result?: TwinWriteResult }> {
451
+ // Same ruling as applyTwinWrite: a local write is an OCCURRENCE unless the caller supplied
452
+ // identity. The mode rides on the decision because only the callback knows which write it chose.
453
+ const committed = decideAndAppendAction(service, (resources) => {
454
+ const decision = prepare(resources);
455
+ if (decision.kind === 'skip') return { kind: 'skip', value: decision.value };
456
+ return {
457
+ kind: 'append',
458
+ value: decision.value,
459
+ action: actionForTwinWrite(service, decision.write),
460
+ identity: decision.write.idempotencyKey || decision.write.actionId ? 'caller' : 'occurrence',
461
+ };
462
+ }, root);
463
+ // as applyTwinWrite: a seed's write is the placeholder's default data, never performed
464
+ const head = committed.action && committed.appended && !committed.placeholder ? await performAtHead(service, committed.action, root) : { performed: false };
465
+ return {
466
+ value: committed.value,
467
+ ...(committed.action ? { result: { status: committed.appended ? 'performed' : 'replayed', actionId: committed.action.id, ...(head.externalId ? { externalId: head.externalId } : {}) } } : {}),
468
+ };
469
+ }
470
+
471
+ // Resolve a read request against the twin's resources. Returns the matched
472
+ // resource(s) + an HTTP-ish status, deterministically from state.
473
+ // GET / -> { service, mode, resourceTypes, count }
474
+ // GET /<type> -> list of resources of that type
475
+ // GET /<type>/<id> -> one resource (id may be url-encoded)
476
+ export function resolveTwinRead(
477
+ service: string,
478
+ pathname: string,
479
+ opts: { root?: string } = {},
480
+ ): { status: number; body: unknown } {
481
+ const resources = twinResources(service, opts.root);
482
+ const parts = pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
483
+
484
+ if (parts.length === 0) {
485
+ const types = [...new Set(resources.map((r) => r.type))].sort();
486
+ return { status: 200, body: { service, resourceTypes: types, count: resources.length } };
487
+ }
488
+ const type = parts[0]!;
489
+ const ofType = resources.filter((r) => r.type === type);
490
+ if (parts.length === 1) {
491
+ return { status: 200, body: { type, count: ofType.length, items: ofType } };
492
+ }
493
+ const id = decodeURIComponent(parts.slice(1).join('/'));
494
+ const found = ofType.find((r) => r.id === id);
495
+ if (!found) return { status: 404, body: { error: 'not_found', type, id } };
496
+ return { status: 200, body: found };
497
+ }
498
+
499
+ // Simulator/fork-mode write: the twin ACCEPTS a write as a LOCAL ACTION appended
500
+ // to the action log (R18) — NOT an observed event and NOT an egress/real write.
501
+ // State is the mirror with this action projected over it, so a subsequent read
502
+ // returns the change. The observed event log is untouched; the real service is
503
+ // provably untouched (no network I/O, no egress). Pushing the action to the real
504
+ // vendor is a separate, explicit step that records egress + confirms the action.
505
+ export async function applyTwinWrite(
506
+ service: string,
507
+ write: TwinWriteInput,
508
+ root?: string,
509
+ ): Promise<{ result: TwinWriteResult; resource: TwinResource }> {
510
+ const action = actionForTwinWrite(service, write);
511
+ // A LOCAL VENDOR WRITE IS AN OCCURRENCE. Two calls are two actions, even byte-identical in the
512
+ // same instant, and the ordinal in the action id says which occurrence this is. The kernel used
513
+ // to derive identity from (content + occurredAt millisecond) and drop a repeat as `replayed`,
514
+ // which silently lost any write returning a subject to a value it previously held — permanently,
515
+ // under a pinned world clock, where every write in a world shares one instant. That default cost
516
+ // github, slack and jira live defects while the recipe told each pack to defend itself with a
517
+ // per-write ordinal and a fifth of the catalog opted out via `uniqueness`. A default every pack
518
+ // must remember to defend against is not a default. See docs/contributing/adding-a-twin.md
519
+ // #5-build-on-the-shared-kernel--dont-reinvent, "A local write is an occurrence".
520
+ //
521
+ // AT-MOST-ONCE is opt in, by KEY: `idempotencyKey` makes the id a function of the caller's own
522
+ // request identity, so a re-issue collapses onto the first and answers `replayed`. Content can
523
+ // never decide this — it cannot separate the same request delivered twice from the same change
524
+ // made twice — which is why the kernel no longer tries.
525
+ //
526
+ // `uniqueness` is subsumed and still honoured: it was the escape hatch packs reached for when
527
+ // the default was backwards (a billable AI completion that must ledger every call), and with
528
+ // occurrence semantics it is simply redundant rather than wrong.
529
+ //
530
+ // correlationId (D3): a caller-supplied request-scoped id lands on the action row; the appenders
531
+ // generate one when omitted, so it's never missing.
532
+ const committed = write.idempotencyKey || write.actionId
533
+ ? appendActionIfAbsent(action, root)
534
+ : appendActionOccurrence(action, root);
535
+ // The COMMITTED action's id, not the one computed before the lock: an occurrence past the first
536
+ // carries an ordinal, and a caller told the base id would be naming an action that is not there.
537
+ const { appended, action: landed } = committed;
538
+ // THE HEAD (head.ts): a twin whose root says `auto` performs the entry now, before the app is
539
+ // answered; the vendor's id, when it minted one, is the id the answer carries.
540
+ // a write landed as the placeholder's default data (a seed) is a pull, never a commit: the head is not asked
541
+ const head = appended && !committed.placeholder ? await performAtHead(service, landed, root) : { performed: false };
542
+ const answerId = head.externalId ?? write.subjectId;
543
+ const result: TwinWriteResult = { status: appended ? 'performed' : 'replayed', actionId: landed.id, ...(head.externalId ? { externalId: head.externalId } : {}), ...(head.data !== undefined ? { vendorData: head.data } : {}) };
544
+ // THE WRITTEN RESOURCE, read when a caller asks for it. Projecting the whole tree after every write to answer it
545
+ // made each write O(tree) and a batch of N writes O(N x tree), whether or not the caller used the answer (a Timestream
546
+ // seed of a member's wearable history stalled its twin for minutes). Resolved by (type, id): an id alone is ambiguous
547
+ // when two resource TYPES share it.
548
+ let resolved: TwinResource | undefined;
549
+ return {
550
+ result,
551
+ get resource(): TwinResource {
552
+ return resolved ??= projectResources(service, root).find((r) => r.type === write.subjectType && r.id === answerId)
553
+ ?? ({ id: answerId, type: write.subjectType, updatedAt: landed.occurredAt, ...write.fields } as TwinResource);
554
+ },
555
+ };
556
+ }
557
+
558
+ export async function createTwinServer(options: { service: string; root?: string; port?: number; readOnly?: boolean }): Promise<{ port: number; stop: () => void }> {
559
+ const readOnly = options.readOnly ?? false;
560
+ const serveOptions = {
561
+ port: options.port ?? 0,
562
+ idleTimeout: 60,
563
+ async fetch(request: Request) {
564
+ const url = new URL(request.url);
565
+ const json = (status: number, body: unknown) =>
566
+ new Response(JSON.stringify(body, null, 2), { status, headers: { 'content-type': 'application/json' } });
567
+ if (request.method === 'GET') {
568
+ // Re-read per request so a concurrently-syncing twin serves fresh state.
569
+ const { status, body } = resolveTwinRead(options.service, url.pathname, { root: options.root });
570
+ return json(status, body);
571
+ }
572
+ // Writes are accepted as local actions unless this twin was started read-only.
573
+ if (readOnly) return json(405, { error: 'read_only', hint: 'this twin was started read-only; omit readOnly to accept writes' });
574
+ const parts = url.pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
575
+ if (parts.length < 2) return json(400, { error: 'write_needs_type_and_id', hint: 'POST /<type>/<id> with a JSON body of fields' });
576
+ let fields: SubjectFields;
577
+ try { fields = (await request.json()) as SubjectFields; } catch { return json(400, { error: 'invalid_json_body' }); }
578
+ // AT-MOST-ONCE OVER HTTP, the way vendors spell it. A local write is an occurrence, so an
579
+ // identical repeat POST is a second action and answers 201 — which is the truth, and a change
580
+ // from the old content-dedupe behaviour. A caller that means "the same request again" sends
581
+ // `Idempotency-Key`, exactly as Stripe and others define it, and gets 200 `replayed`. Without
582
+ // this the `replayed` arm below was unreachable on the kernel's own write endpoint.
583
+ const idempotencyKey = request.headers.get('idempotency-key') ?? undefined;
584
+ let committed: Awaited<ReturnType<typeof applyTwinWrite>>;
585
+ try {
586
+ committed = await applyTwinWrite(options.service, {
587
+ operation: `${request.method.toLowerCase()}.${parts[0]}`,
588
+ subjectType: parts[0]!,
589
+ subjectId: decodeURIComponent(parts.slice(1).join('/')),
590
+ fields,
591
+ actor: { kind: 'agent' },
592
+ ...(idempotencyKey ? { idempotencyKey } : {}),
593
+ }, options.root);
594
+ } catch (e) {
595
+ // A key REUSED WITH A DIFFERENT PAYLOAD is the caller's error, and the vendors that have
596
+ // this header say so with a 400 and an `idempotency_error` code. The kernel raises it as a
597
+ // conflicting-duplicate throw, which this server was letting escape as a 500 HTML page —
598
+ // found 2026-09-04 in review, one day after the header was wired. Anything else is still
599
+ // a genuine failure and still propagates.
600
+ if (idempotencyKey && e instanceof Error && e.message.startsWith('Conflicting duplicate twin action')) {
601
+ return json(400, { error: 'idempotency_error', message: `Keys for idempotent requests can only be used with the same parameters they were first used with. Try using a key other than '${idempotencyKey}' if you meant to execute a different request.` });
602
+ }
603
+ throw e;
604
+ }
605
+ const { result, resource } = committed;
606
+ return json(result.status === 'replayed' ? 200 : 201, { status: result.status, resource });
607
+ },
608
+ };
609
+ // This generic server's twin is an ARGUMENT, not a fact about a pack, so it NAMES ITSELF for the
610
+ // request journal rather than being recognised from its call site. Every vendor pack is journaled
611
+ // without declaring anything (installTwinRequestJournal, above); this is the lone declaration.
612
+ Object.defineProperty(serveOptions, TWIN_JOURNAL_IDENTITY, {
613
+ value: { service: options.service, ...(options.root !== undefined ? { root: options.root } : {}) },
614
+ enumerable: true,
615
+ });
616
+ const server = await serveHttp(serveOptions);
617
+ return { port: server.port, stop: () => { void server.stop(true); } };
618
+ }