@volter/world-core 3.0.155 → 3.0.156

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 (101) hide show
  1. package/dist/generated/pack-facts.json +143 -12
  2. package/dist/inject.cjs +150 -86
  3. package/dist/src/actions.d.ts +15 -0
  4. package/dist/src/actions.js +82 -21
  5. package/dist/src/ancestry.d.ts +5 -4
  6. package/dist/src/ancestry.js +53 -28
  7. package/dist/src/changeset.js +1 -1
  8. package/dist/src/clickhouse/compression.d.ts +28 -0
  9. package/dist/src/clickhouse/compression.js +281 -0
  10. package/dist/src/clickhouse/index.d.ts +1 -0
  11. package/dist/src/clickhouse/index.js +1 -0
  12. package/dist/src/derived-core.d.ts +12 -1
  13. package/dist/src/derived-core.js +145 -11
  14. package/dist/src/derived.js +3 -0
  15. package/dist/src/fork.js +1 -1
  16. package/dist/src/grpc-wire.js +2 -1
  17. package/dist/src/h2.js +63 -10
  18. package/dist/src/head.d.ts +3 -0
  19. package/dist/src/head.js +9 -0
  20. package/dist/src/history.d.ts +1 -1
  21. package/dist/src/history.js +7 -7
  22. package/dist/src/index.d.ts +4 -1
  23. package/dist/src/index.js +5 -1
  24. package/dist/src/log.d.ts +23 -3
  25. package/dist/src/log.js +160 -37
  26. package/dist/src/managed-original-inputs.d.ts +101 -0
  27. package/dist/src/managed-original-inputs.js +125 -0
  28. package/dist/src/mongodb/client.d.ts +8 -0
  29. package/dist/src/mongodb/client.js +27 -0
  30. package/dist/src/mongodb/index.d.ts +2 -0
  31. package/dist/src/mongodb/index.js +5 -0
  32. package/dist/src/mongodb/stream.d.ts +74 -0
  33. package/dist/src/mongodb/stream.js +206 -0
  34. package/dist/src/observe.js +1 -1
  35. package/dist/src/pack-fetch.js +20 -8
  36. package/dist/src/packRegistry.d.ts +10 -14
  37. package/dist/src/packRegistry.js +2 -13
  38. package/dist/src/protocol.d.ts +13 -0
  39. package/dist/src/protocol.js +13 -0
  40. package/dist/src/redis/engine.d.ts +10 -4
  41. package/dist/src/redis/engine.js +103 -32
  42. package/dist/src/redis/member-storage.js +20 -8
  43. package/dist/src/redis/service-times.d.ts +25 -0
  44. package/dist/src/redis/service-times.js +87 -0
  45. package/dist/src/redis/stream.d.ts +2 -1
  46. package/dist/src/redis/stream.js +109 -20
  47. package/dist/src/runtime-state.d.ts +11 -0
  48. package/dist/src/runtime-state.js +13 -0
  49. package/dist/src/runtime.d.ts +4 -2
  50. package/dist/src/runtime.js +5 -2
  51. package/dist/src/serve-http.d.ts +2 -0
  52. package/dist/src/serve-http.js +21 -4
  53. package/dist/src/serve.d.ts +10 -0
  54. package/dist/src/serve.js +43 -3
  55. package/dist/src/storage.d.ts +2 -1
  56. package/dist/src/storage.js +6 -3
  57. package/dist/src/world-boot.d.ts +1 -0
  58. package/dist/src/world-boot.js +6 -0
  59. package/dist/src/world-clock.js +6 -2
  60. package/dist/src/world-store.d.ts +108 -7
  61. package/dist/src/world-store.js +883 -103
  62. package/dist/src/xml.d.ts +30 -0
  63. package/dist/src/xml.js +74 -0
  64. package/generated/pack-facts.json +143 -12
  65. package/inject.cjs +150 -86
  66. package/package.json +20 -1
  67. package/src/actions.ts +78 -21
  68. package/src/ancestry.ts +46 -24
  69. package/src/changeset.ts +1 -1
  70. package/src/clickhouse/compression.ts +172 -0
  71. package/src/clickhouse/index.ts +2 -0
  72. package/src/derived-core.ts +142 -11
  73. package/src/derived.ts +3 -0
  74. package/src/fork.ts +1 -1
  75. package/src/grpc-wire.ts +2 -1
  76. package/src/h2.ts +46 -5
  77. package/src/head.ts +9 -0
  78. package/src/history.ts +7 -7
  79. package/src/index.ts +6 -1
  80. package/src/log.ts +147 -32
  81. package/src/managed-original-inputs.ts +163 -0
  82. package/src/mongodb/client.ts +27 -0
  83. package/src/mongodb/index.ts +5 -0
  84. package/src/mongodb/stream.ts +213 -0
  85. package/src/observe.ts +1 -1
  86. package/src/pack-fetch.ts +20 -8
  87. package/src/packRegistry.ts +3 -15
  88. package/src/protocol.ts +13 -0
  89. package/src/redis/engine.ts +100 -57
  90. package/src/redis/member-storage.ts +17 -9
  91. package/src/redis/service-times.ts +81 -0
  92. package/src/redis/stream.ts +66 -14
  93. package/src/runtime-state.ts +14 -0
  94. package/src/runtime.ts +6 -1
  95. package/src/serve-http.ts +23 -4
  96. package/src/serve.ts +45 -3
  97. package/src/storage.ts +5 -3
  98. package/src/world-boot.ts +6 -0
  99. package/src/world-clock.ts +6 -2
  100. package/src/world-store.ts +704 -91
  101. package/src/xml.ts +71 -0
@@ -0,0 +1,11 @@
1
+ export { getActiveWorldStore, removeCarriedLocks } from './world-store.js';
2
+ export type { WorldStore } from './world-store.js';
3
+ export { stateDirName, withFileLock, worldPaths, worldStateRoot, WORLD_DATA_ENV } from './storage.js';
4
+ export { assertStateRemovable, withAncestryLock, withStateRemoval } from './ancestry.js';
5
+ export { readBranchMeta } from './log.js';
6
+ export { volterHome } from './volter-home.js';
7
+ export { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository, vendorOfPackModule } from './pack-paths.js';
8
+ export { PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from './protocol.js';
9
+ export { WORLD_BOOT_PATH } from './serve-http.js';
10
+ export { WORLD_ENV_NAMES_ENV } from './world-env.js';
11
+ export { beginPlaceholderPull, endPlaceholderPull, placeholderPullActive } from './placeholder-remote.js';
@@ -0,0 +1,13 @@
1
+ // The lifecycle's state/identity primitives. Re-export their canonical modules
2
+ // so the active store, locks and scopes are shared with the full runtime and packs.
3
+ // Booting an empty World does not require pack registration or derived engines.
4
+ export { getActiveWorldStore, removeCarriedLocks } from "./world-store.js";
5
+ export { stateDirName, withFileLock, worldPaths, worldStateRoot, WORLD_DATA_ENV } from "./storage.js";
6
+ export { assertStateRemovable, withAncestryLock, withStateRemoval } from "./ancestry.js";
7
+ export { readBranchMeta } from "./log.js";
8
+ export { volterHome } from "./volter-home.js";
9
+ export { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository, vendorOfPackModule } from "./pack-paths.js";
10
+ export { PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from "./protocol.js";
11
+ export { WORLD_BOOT_PATH } from "./serve-http.js";
12
+ export { WORLD_ENV_NAMES_ENV } from "./world-env.js";
13
+ export { beginPlaceholderPull, endPlaceholderPull, placeholderPullActive } from "./placeholder-remote.js";
@@ -38,7 +38,7 @@ export { SqlWorldStore } from './world-store-sql.js';
38
38
  export type { SqlExec } from './world-store-sql.js';
39
39
  export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, } from './blob-store.js';
40
40
  export type { BlobStore } from './blob-store.js';
41
- export { applyTwinWrite, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, type TwinRequestJournalCursor, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from './serve.js';
41
+ export { applyTwinWrite, applyTwinWrites, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, type TwinRequestJournalCursor, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from './serve.js';
42
42
  export type { AtomicTwinWriteDecision, TwinRequestJournalEntry, TwinResource, TwinWriteInput, TwinWriteResult, } from './serve.js';
43
43
  export { createTwinProxy } from './proxy.js';
44
44
  export type { TwinProxy, TwinProxyOptions, VendorRoute } from './proxy.js';
@@ -52,6 +52,7 @@ export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType,
52
52
  export { installedPackageDir, installedVendorPacks, packVendor, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from './pack-paths.js';
53
53
  export * as git from './git/index.js';
54
54
  export * as s3 from './s3/wire.js';
55
+ export * as xml from './xml.js';
55
56
  export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, placeholderPullMarkerPath, withPlaceholderPull, placeholderEventsFor } from './placeholder-remote.js';
56
57
  export { aliasesFrom, ownFields, subjectHistory, rebaseBranch, cutCheckpoint, appendParentEntry, wholeLog, branchEntries, branchLogPath, branchMetaPath, CHECKPOINT_EVERY, dropCheckpoint, foldEntries, landedCopy, landedIds, parentEntries, parentLogPath, position, readBranchMeta, readTree, toEntry, toEvent, unpushedEntries, writeBranchMeta } from './log.js';
57
58
  export type { BranchMeta, Entry, EntryOp, Receipt } from './log.js';
@@ -69,7 +70,7 @@ export { observeResource, observeResources, collectObservations, foldObservation
69
70
  export type { ObservedResource, ObserveReport, Observation } from './observe.js';
70
71
  export { readTreeMap, readParentTreeMap, treeStamp, treeChangesSince, originEntries, appendOriginEntry, originLogPath, isUrlParent, parentPosition, positionAt, splitsBatch, assertBatchBoundary } from './log.js';
71
72
  export { referenceField, registerReferences, packReferences, resolveReferences, type ReferenceDeclaration } from './references.js';
72
- export { serveHttp, serveStream, connectWebSocket, nodeBuiltin, WORLD_BOOT_PATH, type HttpServer, type ServeHttpOptions, type HttpHandler } from './serve-http.js';
73
+ export { serveHttp, serveStream, connectWebSocket, nodeBuiltin, servedHandlerOf, WORLD_BOOT_PATH, type HttpServer, type ServeHttpOptions, type HttpHandler } from './serve-http.js';
73
74
  export { fileResponse, contentTypeOf } from './file-response.js';
74
75
  export { bundleClient, filePathOf } from './client-bundle.js';
75
76
  export { brandTokensResponse } from './brand-tokens.js';
@@ -86,3 +87,4 @@ export { foldHistory } from './log.js';
86
87
  export { volterHome } from './volter-home.js';
87
88
  export type { MachinePool, MachineSpec, MachineProcess, MachineStatus, MachineFiles, MachineFileStat, GuestImage, LightPoolOptions } from './machines.js';
88
89
  export { boardFrameKey } from './board-keys.js';
90
+ export * as managedOriginalInputs from './managed-original-inputs.js';
@@ -55,7 +55,7 @@ export { SqlWorldStore } from "./world-store-sql.js";
55
55
  // The blob seam (runtime contract R11): byte storage behind byte-carrying handlers, so a
56
56
  // serverless namespace puts bytes in object storage while local worlds keep today's layout.
57
57
  export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, } from "./blob-store.js";
58
- export { applyTwinWrite, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from "./serve.js";
58
+ export { applyTwinWrite, applyTwinWrites, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from "./serve.js";
59
59
  export { createTwinProxy } from "./proxy.js";
60
60
  export { forkTwin, isFork, readForkMeta, } from "./fork.js";
61
61
  export { appendAction, appendTransactionCommit, checkPrecondition, confirmAction, listActions, listTransactionCommits, pendingActions, pushablePendingActions, isTwinBookkeeping, pendingTransactionCommits, projectResources, projectOwnerResources, OwnerStoreAmbiguousError, revertAction, TwinActionPreconditionError, subjectAliases, resolveSubjectId, runWithCorrelationId, currentCorrelationId } from "./actions.js";
@@ -73,6 +73,8 @@ export { installedPackageDir, installedVendorPacks, packVendor, packRepository,
73
73
  export * as git from "./git/index.js";
74
74
  // S3's wire (contract "S3's wire is a kernel library too"): its XML, CORS, lifecycle, delete batches, ETags, body bytes.
75
75
  export * as s3 from "./s3/wire.js";
76
+ // Generic strict XML grammar; vendor interpretation remains in its consumer.
77
+ export * as xml from "./xml.js";
76
78
  // The push arm: the kernel's transaction over a pack's one `perform<Name>Action` (contract section of that name).
77
79
  // The placeholder remote (contract section of that name): the seed is a pull, never a pending write.
78
80
  export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, placeholderPullMarkerPath, withPlaceholderPull, placeholderEventsFor } from "./placeholder-remote.js";
@@ -88,7 +90,7 @@ export { observeResource, observeResources, collectObservations, foldObservation
88
90
  export { readTreeMap, readParentTreeMap, treeStamp, treeChangesSince, originEntries, appendOriginEntry, originLogPath, isUrlParent, parentPosition, positionAt, splitsBatch, assertBatchBoundary } from "./log.js";
89
91
  export { referenceField, registerReferences, packReferences, resolveReferences } from "./references.js";
90
92
  // the HTTP server seam: the one place the serve path meets the runtime (Bun or Node)
91
- export { serveHttp, serveStream, connectWebSocket, nodeBuiltin, WORLD_BOOT_PATH } from "./serve-http.js";
93
+ export { serveHttp, serveStream, connectWebSocket, nodeBuiltin, servedHandlerOf, WORLD_BOOT_PATH } from "./serve-http.js";
92
94
  // two runtime-neutral helpers for a serve path: a file as a Response, a page's client bundle
93
95
  export { fileResponse, contentTypeOf } from "./file-response.js";
94
96
  export { bundleClient, filePathOf } from "./client-bundle.js";
@@ -104,3 +106,4 @@ export { captureHistory, captureParentHistory, historyChanges, historyAtInstant,
104
106
  export { foldHistory } from "./log.js";
105
107
  export { volterHome } from "./volter-home.js";
106
108
  export { boardFrameKey } from "./board-keys.js";
109
+ export * as managedOriginalInputs from "./managed-original-inputs.js";
@@ -45,6 +45,8 @@ export type HttpServer = {
45
45
  export declare function setServeDecorator(fn: ((options: ServeHttpOptions) => HttpHandler) | undefined): void;
46
46
  /** The path a World asks a service it booted, when that service's port answers, which boot it belongs to. */
47
47
  export declare const WORLD_BOOT_PATH = "/__volter/world-boot";
48
+ /** The handler serving `port` in this process, when a server made through the seam listens there. */
49
+ export declare function servedHandlerOf(port: number): HttpHandler | undefined;
48
50
  export declare function serveHttp(options: ServeHttpOptions): Promise<HttpServer>;
49
51
  /** A Node builtin without a static import — `process.getBuiltinModule` (Node 22.3+, Bun): what a
50
52
  * module that also rides into a browser bundle reaches `node:crypto` with. A bare `require()`
@@ -4,9 +4,10 @@
4
4
  // as a standard Request and the handler's Response written back. Async, because binding a port is
5
5
  // asynchronous on Node — Bun's synchronous bind is the special case.
6
6
  //
7
- // Nothing here runs at module scope, and `node:http` is reached through `process.getBuiltinModule`
7
+ // Nothing here runs at module scope (the served-handler registry is made on first use), and `node:http` is reached through `process.getBuiltinModule`
8
8
  // rather than a static import: this module rides into browser client bundles through the kernel's
9
9
  // entrypoint, where a static `node:` import is the 2026-09-06 class of break.
10
+ import { worldBootAnswer } from "./world-boot.js";
10
11
  /** One socket library at the host seam, shared by inbound upgrades and authenticated outbound relays. */
11
12
  const socketLibrary = () => import('ws');
12
13
  export async function connectWebSocket(url, headers, protocols = []) {
@@ -19,6 +20,22 @@ export function setServeDecorator(fn) { decorate = fn; }
19
20
  const bunRuntime = () => { const b = globalThis.Bun; return b && typeof b.serve === 'function' ? b : undefined; };
20
21
  /** The path a World asks a service it booted, when that service's port answers, which boot it belongs to. */
21
22
  export const WORLD_BOOT_PATH = '/__volter/world-boot';
23
+ /** Each listening server's own handler, by its port, as the seam serves it (the boot door answered, nothing journaled):
24
+ * the World's co-located host warms a twin in-process through it (world-runtime host.ts warmInProcess), so its warm-up is
25
+ * never a request on the wire nor in any journal. A server leaves it when it stops. Kept on globalThis under a registered
26
+ * symbol, as the reference registry is (references.ts), so a pack that resolved its own copy of this module shares it. */
27
+ const servedHandlers = () => (globalThis[Symbol.for('volter.servedHandlers')] ??= new Map());
28
+ /** The handler serving `port` in this process, when a server made through the seam listens there. */
29
+ export function servedHandlerOf(port) { return servedHandlers().get(port); }
30
+ /** A server's handler kept by its port until the server stops. */
31
+ function keepHandler(server, handler) {
32
+ const port = server.port;
33
+ if (!port)
34
+ return server;
35
+ servedHandlers().set(port, handler);
36
+ return { ...server, stop: async (closeActive) => { if (servedHandlers().get(port) === handler)
37
+ servedHandlers().delete(port); await server.stop(closeActive); } };
38
+ }
22
39
  export async function serveHttp(options) {
23
40
  // Inside a World every server made through the seam answers which boot it belongs to, so the World can tell its own
24
41
  // twin from a leftover World's that holds the same port. Outside a World nothing changes.
@@ -33,7 +50,7 @@ export async function serveHttp(options) {
33
50
  const worldBoot = globalThis.process?.env?.VOLTER_WORLD_BOOT_ID;
34
51
  if (worldBoot) {
35
52
  const served = options.fetch;
36
- options = { ...options, fetch: (request) => (new URL(request.url).pathname === WORLD_BOOT_PATH ? Response.json({ boot: worldBoot }) : served(request)) };
53
+ options = { ...options, fetch: (request) => (new URL(request.url).pathname === WORLD_BOOT_PATH ? worldBootAnswer(worldBoot) : served(request)) };
37
54
  }
38
55
  // loopback by default: an unnamed interface is never every interface
39
56
  const hostname = options.hostname ?? '127.0.0.1';
@@ -60,9 +77,9 @@ export async function serveHttp(options) {
60
77
  } : {}),
61
78
  });
62
79
  const port = server.port ?? 0;
63
- return { hostname, port, url: server.url instanceof URL ? server.url : new URL(`http://${urlHost}:${port}/`), stop: async (closeActive = true) => { await server.stop(closeActive); } };
80
+ return keepHandler({ hostname, port, url: server.url instanceof URL ? server.url : new URL(`http://${urlHost}:${port}/`), stop: async (closeActive = true) => { await server.stop(closeActive); } }, options.fetch);
64
81
  }
65
- return serveOnNode({ ...options, hostname, urlHost, fetch: decorate ? decorate(options) : options.fetch });
82
+ return keepHandler(await serveOnNode({ ...options, hostname, urlHost, fetch: decorate ? decorate(options) : options.fetch }), options.fetch);
66
83
  }
67
84
  /** A Node builtin without a static import — `process.getBuiltinModule` (Node 22.3+, Bun): what a
68
85
  * module that also rides into a browser bundle reaches `node:crypto` with. A bare `require()`
@@ -184,6 +184,16 @@ export declare function applyTwinWrite(service: string, write: TwinWriteInput, r
184
184
  result: TwinWriteResult;
185
185
  resource: TwinResource;
186
186
  }>;
187
+ /**
188
+ * Several local writes of one service, landed as `applyTwinWrite` lands each called in turn — the same actions, ids and
189
+ * order — under one lock and one append (actions.ts `appendActionOccurrences`). Writes that need the one-at-a-time
190
+ * path take it: a caller's identity (`idempotencyKey`/`actionId`), preconditions, or a head that performs each entry as
191
+ * it lands. An input is offloaded once per distinct value, as each write's own offload would name it.
192
+ */
193
+ export declare function applyTwinWrites(service: string, writes: readonly TwinWriteInput[], root?: string): Promise<Array<{
194
+ result: TwinWriteResult;
195
+ resource: TwinResource;
196
+ }>>;
187
197
  export declare function createTwinServer(options: {
188
198
  service: string;
189
199
  root?: string;
package/dist/src/serve.js CHANGED
@@ -12,12 +12,13 @@ import { parseExactJson } from "./exact-json.js";
12
12
  // reality). Forking is a separate data operation (fork.ts), never a serve mode.
13
13
  import { AsyncLocalStorage } from 'node:async_hooks';
14
14
  import { serveHttp, setServeDecorator, WORLD_BOOT_PATH } from "./serve-http.js";
15
- import { performAtHead } from "./head.js";
15
+ import { performAtHead, performsAtHeadNow } from "./head.js";
16
16
  import { withHeaders } from "./with-headers.js";
17
17
  import { createHash } from 'node:crypto';
18
18
  import { dirname, join } from 'node:path';
19
19
  import { hashFieldValue } from "./hash.js";
20
- import { appendActionIfAbsent, appendActionOccurrence, decideAndAppendAction, projectResources } from "./actions.js";
20
+ import { appendActionIfAbsent, appendActionOccurrence, appendActionOccurrences, decideAndAppendAction, projectResources } from "./actions.js";
21
+ import { readSubject } from "./log.js";
21
22
  import { observeWorldPaths, worldPaths } from "./storage.js";
22
23
  import { getActiveWorldStore } from "./world-store.js";
23
24
  import { getActiveBlobStore } from "./blob-store.js";
@@ -596,11 +597,50 @@ export async function applyTwinWrite(service, write, root) {
596
597
  return {
597
598
  result,
598
599
  get resource() {
599
- return resolved ??= projectResources(service, root).find((r) => r.type === write.subjectType && r.id === answerId)
600
+ return resolved ??= readSubject(service, write.subjectType, answerId, root)
600
601
  ?? { id: answerId, type: write.subjectType, updatedAt: landed.occurredAt, ...write.fields };
601
602
  },
602
603
  };
603
604
  }
605
+ /**
606
+ * Several local writes of one service, landed as `applyTwinWrite` lands each called in turn — the same actions, ids and
607
+ * order — under one lock and one append (actions.ts `appendActionOccurrences`). Writes that need the one-at-a-time
608
+ * path take it: a caller's identity (`idempotencyKey`/`actionId`), preconditions, or a head that performs each entry as
609
+ * it lands. An input is offloaded once per distinct value, as each write's own offload would name it.
610
+ */
611
+ export async function applyTwinWrites(service, writes, root) {
612
+ if (writes.length <= 1 || performsAtHeadNow(service, root) || writes.some((write) => write.idempotencyKey || write.actionId || write.preconditions?.length)) {
613
+ const out = [];
614
+ for (const write of writes)
615
+ out.push(await applyTwinWrite(service, write, root));
616
+ return out;
617
+ }
618
+ // Each distinct input offloaded once (S2 R17): `offloadInput` is content-addressed (a string's blob is named by its
619
+ // SHA-256), so the result is the one each write's own offload would give, and a command's writes, which share one
620
+ // recorded request, hash and look up its blob once instead of once per write.
621
+ const offloaded = new Map();
622
+ for (const write of writes)
623
+ if (write.input !== undefined && !offloaded.has(write.input))
624
+ offloaded.set(write.input, await offloadInput(service, write.input, root));
625
+ const committed = appendActionOccurrences(writes.map((write) => actionForTwinWrite(service, write.input === undefined ? write : { ...write, input: offloaded.get(write.input) })), root);
626
+ const out = [];
627
+ for (const [index, { appended, action: landed, placeholder }] of committed.entries()) {
628
+ const write = writes[index];
629
+ // the head does not perform here (`performsAtHeadNow` was false): this marks the request's deferred group as written
630
+ if (appended && !placeholder)
631
+ await performAtHead(service, landed, root);
632
+ const answerId = write.subjectId;
633
+ let resolved;
634
+ out.push({
635
+ result: { status: appended ? 'performed' : 'replayed', actionId: landed.id },
636
+ get resource() {
637
+ return resolved ??= readSubject(service, write.subjectType, answerId, root)
638
+ ?? { id: answerId, type: write.subjectType, updatedAt: landed.occurredAt, ...write.fields };
639
+ },
640
+ });
641
+ }
642
+ return out;
643
+ }
604
644
  export async function createTwinServer(options) {
605
645
  const readOnly = options.readOnly ?? false;
606
646
  const serveOptions = {
@@ -1,3 +1,4 @@
1
+ import { type WorldLockOptions } from './world-store.js';
1
2
  import type { AppendEventResult, GenericWorldState, WorldPaths, WorldReducer, WorldServiceEvent } from './types.js';
2
3
  /**
3
4
  * Name of the per-project state directory holding world data
@@ -60,7 +61,7 @@ export declare function twinLog(kind: string, details: Record<string, unknown>):
60
61
  * single-instance store it is a synchronous pass-through. Used to make read-then-append
61
62
  * critical sections atomic. Public so world-runtime can guard concurrent `upWorld`
62
63
  * claims of one instance dir with the SAME lock semantics the event log uses (TWIN-36). */
63
- export declare function withFileLock<T>(lockPath: string, fn: () => T): T;
64
+ export declare function withFileLock<T>(lockPath: string, fn: () => T, options?: WorldLockOptions): T;
64
65
  /** The parent log as observed rows: every entry the branch inherited or landed, as
65
66
  * observed rows (log.ts `toEvent`). A branched world sees its base world's logs to the branch
66
67
  * position first. */
@@ -3,7 +3,7 @@ import { dirname, isAbsolute, join, resolve } from 'node:path';
3
3
  import { withStateLock, withStateRemoval } from "./ancestry.js";
4
4
  import { appendParentEntry, toEntry } from "./log.js";
5
5
  import { GenericWorldStateSchema, WorldServiceEventSchema, } from "./schemas.js";
6
- import { getActiveWorldStore } from "./world-store.js";
6
+ import { getActiveWorldStore, storageLockScope, TREE_GENERATION } from "./world-store.js";
7
7
  import { refuseReadOnlyWrite } from "./request-scope.js";
8
8
  import { parentEntries, toEvent } from "./log.js";
9
9
  function nowIso() {
@@ -181,8 +181,8 @@ function appendJsonl(path, value) {
181
181
  * single-instance store it is a synchronous pass-through. Used to make read-then-append
182
182
  * critical sections atomic. Public so world-runtime can guard concurrent `upWorld`
183
183
  * claims of one instance dir with the SAME lock semantics the event log uses (TWIN-36). */
184
- export function withFileLock(lockPath, fn) {
185
- return withStateLock(lockPath, fn);
184
+ export function withFileLock(lockPath, fn, options = {}) {
185
+ return withStateLock(lockPath, fn, storageLockScope(options));
186
186
  }
187
187
  function writeJsonAtomic(path, value) {
188
188
  getActiveWorldStore().writeAtomic(path, `${JSON.stringify(value, null, 2)}\n`);
@@ -327,6 +327,9 @@ function isKnownServiceEntry(name) {
327
327
  // log.ts: the branch pointer and the checkpoint
328
328
  if (name === 'branch.json' || name === 'checkpoints')
329
329
  return true;
330
+ // world-store.ts: the generation a file store keeps beside a tree's files
331
+ if (name === TREE_GENERATION)
332
+ return true;
330
333
  return KNOWN_SERVICE_ENTRIES.has(name) || name.endsWith('.tmp') || /\.stale\.\d+\.\d+$/.test(name);
331
334
  }
332
335
  /** A service dir "looks like" one of ours when every entry in it is something
@@ -0,0 +1 @@
1
+ export declare function worldBootAnswer(boot: string): Response;
@@ -0,0 +1,6 @@
1
+ // One private boot answer for the kernel's HTTP and gRPC serve seams.
2
+ // Reading process identity happens on request; importing this helper has no side effects.
3
+ export function worldBootAnswer(boot) {
4
+ const pid = globalThis.process?.pid;
5
+ return Response.json({ boot, pid });
6
+ }
@@ -23,10 +23,14 @@ const machineNow = () => {
23
23
  * now); real wall-clock time otherwise. */
24
24
  export function worldNow() {
25
25
  const file = process.env[WORLD_CLOCK_ENV];
26
- if (file && getActiveWorldStore().exists(file)) {
26
+ // One read answers both whether the clock file is there and what it holds.
27
+ // Kept while the clock file's own facts stand (world-store.ts readKept): every operation asks the time.
28
+ const store = getActiveWorldStore();
29
+ const held = file ? (store.readKept ? store.readKept(file) : store.read(file)) : null;
30
+ if (held !== null) {
27
31
  // A configured-but-corrupt clock is a WORLD defect — parseClock fails loudly, never silently drifting back to
28
32
  // wall-clock (two services disagreeing about now breaks the world).
29
- const clock = clockForm.parseClock(getActiveWorldStore().read(file) ?? '');
33
+ const clock = clockForm.parseClock(held);
30
34
  return new Date(clockForm.clockNowMs(clock, machineNow())).toISOString();
31
35
  }
32
36
  return new Date(machineNow()).toISOString();
@@ -7,6 +7,17 @@ export type WorldStat = {
7
7
  isDirectory: boolean;
8
8
  isSymbolicLink?: boolean;
9
9
  };
10
+ /** Diagnostic context only; it does not change lock acquisition or stale-owner policy. */
11
+ export type WorldLockOptions = {
12
+ retainLiveOwner?: boolean;
13
+ operation?: string;
14
+ caller?: string;
15
+ };
16
+ /** Capture at the outermost lock helper, then carry the same caller through nested helpers. */
17
+ export declare function storageLockScope(options?: WorldLockOptions): WorldLockOptions & {
18
+ operation: string;
19
+ caller: string;
20
+ };
10
21
  /**
11
22
  * The synchronous persistence seam. Every path is an ABSOLUTE key the caller already
12
23
  * computed (via `worldPaths`/`worldStateRoot`); the store treats it as an opaque key,
@@ -51,13 +62,57 @@ export interface WorldStore {
51
62
  readRange?(path: string, start: number): string | null;
52
63
  /** Resolve filesystem aliases for durable ownership; absent on opaque-key stores. */
53
64
  canonicalPath?(path: string): string;
65
+ /** `read`, for a small file every operation needs and few change (a branch's pointer, the World's clock): the text
66
+ * kept from the last read while the path's own facts stand, so an unchanged file costs one check and no read.
67
+ * Optional: a store without it is read. */
68
+ readKept?(path: string): string | null;
69
+ /** The facts of the generation kept beside a tree's files in `dirPath` (TREE_FILES): one string that differs after
70
+ * any write of one of them through a store, by this process or another; `null` where the directory has none yet,
71
+ * which says nothing. Optional: a store without it is asked for each file's own facts. */
72
+ generation?(dirPath: string): string | null;
73
+ /** The generation in `dirPath` now, when exactly one write of a tree's file through a store has moved it since
74
+ * `before` (a generation this store answered for it); `null` when it cannot say so. A writer that read `before` just
75
+ * ahead of its own write, holding the directory's history lock, learns that its write was the only one (log.ts
76
+ * appendToBranchLog). Optional, beside `generation`. */
77
+ generationAfterOwnWrite?(dirPath: string, before: string): string | null;
54
78
  /** Run `fn` holding an exclusive lock on `lockPath`, releasing it afterwards. The fs
55
79
  * backend uses the historical cross-process reclaimable file lock; an in-memory,
56
80
  * single-instance backend is a synchronous pass-through. */
57
- withLock<T>(lockPath: string, fn: () => T, options?: {
58
- retainLiveOwner?: boolean;
59
- }): T;
81
+ withLock<T>(lockPath: string, fn: () => T, options?: WorldLockOptions): T;
60
82
  }
83
+ /** The files a service's tree is folded from, by name in its state directory (log.ts: the parent log, the branch log,
84
+ * the branch's pointer), and the generation a store keeps beside them. */
85
+ export declare const TREE_FILES: {
86
+ readonly parent: "events.jsonl";
87
+ readonly branch: "actions.jsonl";
88
+ readonly pointer: "branch.json";
89
+ };
90
+ export declare const TREE_GENERATION = "tree.generation";
91
+ /**
92
+ * A WORLD'S CARRIED LOCKS, REMOVED AT ITS OPEN (S2 K4). A World booted over its kept state (`up --keep-state`, a
93
+ * tab's restored archive) carries whatever lock files its last life left in its services' state directories. One whose
94
+ * holder record cannot be read is no holder's once its taker has gone, and a restore gives it the restore's file time,
95
+ * so the empty-record grace and the 60 s age ran from the restore: every waiter waited them, or gave up at 10 s (Twenty
96
+ * e946aa4c run 2: anthropic's projection.lock, "holder record unreadable", 10,086 ms, then its credentials 500 and the
97
+ * server 10 s late). A carried `.recovery` that is not empty and does not parse was never ended by R13's rule at all.
98
+ *
99
+ * A file is carried only when all of these hold: it is a world-core lock or its reclaim record (`*.lock`,
100
+ * `*.lock.recovery`) in a service's state directory (`<root>/<service>/<stateDir>/world`, never a yarn.lock or any
101
+ * project file); its text is empty or a holder record's beginning (`{"pid"`) that does not parse; and its file time is
102
+ * before `before`, the start of this boot's claim. A restore sets the time before the claim; a taker that is live now
103
+ * (a CLI's changeset rebase, a branch World keeping this one as its parent's storage, a two-call taker between its
104
+ * create and its record) made its file after it. Each removal is still a reclaimer's: a lock's under its `.recovery`,
105
+ * taken with its record (createRecorded), a held `.recovery` skipped; the file read again, renamed aside, the aside copy
106
+ * read again and linked back where it now holds a record (EEXIST there is the path taken since, left to its holder).
107
+ * A record that parses is kept, under its rules (a dead holder's, our own pid's, the 60 s age, which a restore resets:
108
+ * not this rule's). Answers the paths removed.
109
+ */
110
+ export declare function removeCarriedLocks(roots: readonly string[], options: {
111
+ stateDir: string;
112
+ before: number;
113
+ }): string[];
114
+ /** Name a bookkeeping phase within an existing acquisition; never acquire another lock. */
115
+ export declare function withStorageLockOperation<T>(path: string, operation: string, fn: () => T): T;
61
116
  /**
62
117
  * The default store: a thin, faithful pass-through to `node:fs`. Every method preserves
63
118
  * the exact behavior the kernel had before the seam existed — the durable append
@@ -66,6 +121,51 @@ export interface WorldStore {
66
121
  */
67
122
  export declare class FsWorldStore implements WorldStore {
68
123
  read(path: string): string | null;
124
+ /**
125
+ * A small file's text, kept while its own facts stand. THE GUARANTEE: the key is the path's own lstat (device,
126
+ * inode, size, modification and change times); this process's own writes through the store drop what is kept at
127
+ * once; another process's write is seen when any of those facts moves. That is the guarantee a tree's stamp
128
+ * already rests on for its logs (their size and modification time), with the inode and change time added: a
129
+ * replaced file (writeAtomic) is another inode, a file written in place has another time. What it cannot see is a
130
+ * rewrite to the same size on the same or a reused inode within one tick of the file system's clock, with no check
131
+ * between; the logs' stamp has the same limit. A link is read through, and not kept.
132
+ * A text read after a write that came between the lstat and the read is kept under the OLDER key, so the next
133
+ * check finds the facts moved and reads again: what is kept is never newer than its key, and never served past it.
134
+ */
135
+ private readonly kept;
136
+ readKept(path: string): string | null;
137
+ /**
138
+ * A TREE'S GENERATION: one file beside a tree's three files, a byte longer after every write of any of them, so a
139
+ * reader that has the tree's stamp asks ONE question to know it still stands (log.ts treeFacts), where it asked
140
+ * three, two of them of files a World with no parent does not have.
141
+ *
142
+ * WHAT MAKES IT SOUND. A writer appends a byte BEFORE its write and another AFTER it; a reader takes the
143
+ * generation's facts BEFORE it reads the files' own. The file only grows, so its size never repeats on one inode,
144
+ * and a replaced or removed file is another inode or none. So for any write W and any reader that kept a stamp S
145
+ * under generation G: if W's first byte is after G was read, the kept G is behind and S is not served; if it is
146
+ * before, then either W itself was before S's own reads (S holds it), or W's second byte is after G was read, and
147
+ * again G is behind. An append is one call the system orders among all writers: each byte is one append of its
148
+ * own, whole or not at all, on a POSIX filesystem and in a tab's one kernel (browser-substrate
149
+ * packages/node/src/kernel-virtual-filesystem.ts appendFileSync, an O_APPEND open and writeAll, which sends a write
150
+ * of at most DATA_BYTES as one SYS_write request the kernel serves whole; or, by a file step, its table's own
151
+ * O_APPEND open and write of the one byte, filesystem.ts appendFileBytes). The proof rests on that; log.ts
152
+ * appendToBranchLog's (`generationAfterOwnWrite`: one write is exactly two bytes) as well.
153
+ *
154
+ * WHERE THAT STOPS. (1) A writer that dies between its write and its second byte, with a reader's whole check
155
+ * between its first byte and its write: that reader keeps the older stamp until the next write of the tree. (2) A
156
+ * writer of these files that does not come through a store of this version (an older package, a copy made over a
157
+ * live directory by another program): the generation does not move. A directory made or removed whole has no
158
+ * generation or another's, which serves nothing kept. (3) A stamp that names files outside its own directory (a
159
+ * World with a parent: its view and its ancestors' logs) is never kept under a generation (log.ts).
160
+ */
161
+ generation(dirPath: string): string | null;
162
+ /** One write is `moved` twice, a byte each: the same file, those two bytes longer. */
163
+ generationAfterOwnWrite(dirPath: string, before: string): string | null;
164
+ /** One more byte on the generation beside `path`, where `path` is one of a tree's files. A directory that is not
165
+ * there has no reader to tell: the write that makes it is followed by the byte that makes its generation. */
166
+ private moved;
167
+ /** A write of `path`, with the generation beside it moved before and after where it is one of a tree's files. */
168
+ private writing;
69
169
  readLines(path: string): string[];
70
170
  readRange(path: string, start: number): string | null;
71
171
  append(path: string, data: string): void;
@@ -79,17 +179,18 @@ export declare class FsWorldStore implements WorldStore {
79
179
  remove(path: string): void;
80
180
  mkdir(dirPath: string): void;
81
181
  list(dirPath: string): string[];
182
+ /** The real path of each path already resolved: a state directory's does not move under a running process, and
183
+ * every history lock asked for it again. A path that does not exist yet is not kept. */
184
+ private readonly canonical;
82
185
  canonicalPath(path: string): string;
83
186
  stat(path: string): WorldStat | null;
84
187
  /** Run `fn` holding an exclusive cross-process file lock. The lockfile records
85
- * `{pid, hostname, at}`; a contender that finds a stale lock (dead pid or age >
188
+ * identity, operation, caller and any nested wait; a contender that finds a stale lock (dead pid or age >
86
189
  * LOCK_STALE_MS) reclaims it by atomically renaming it aside — so a crashed holder
87
190
  * can't wedge the world forever. Reclaim is race-safe: only the process that wins
88
191
  * the rename clears the stale inode, and the exclusive `wx` create still decides the
89
192
  * winner. (Moved verbatim from storage.ts `withFileLock`.) */
90
- withLock<T>(lockPath: string, fn: () => T, options?: {
91
- retainLiveOwner?: boolean;
92
- }): T;
193
+ withLock<T>(lockPath: string, fn: () => T, options?: WorldLockOptions): T;
93
194
  }
94
195
  /**
95
196
  * An in-memory store. Files are entries in a `Map<path, string>`; directories are