@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,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";
@@ -36,9 +36,9 @@ export type { WorldStore, WorldStat, HydrationSource, HydrationSink } from './wo
36
36
  export type { CheckLoader } from './head.js';
37
37
  export { SqlWorldStore } from './world-store-sql.js';
38
38
  export type { SqlExec } from './world-store-sql.js';
39
- export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, } from './blob-store.js';
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';
39
+ export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, streamChunks, writeBlobStream, } from './blob-store.js';
40
+ export type { BlobStore, BlobWritten } from './blob-store.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';
@@ -78,7 +79,7 @@ export { corsHeaders, withCors, type CorsDecl } from './cors.js';
78
79
  export { setInProcessTwins, vendorFetch, VendorUnreachableError, type InProcessTwin } from './vendor-call.js';
79
80
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver, type DatabaseDriver, type DatabaseSession } from './managed-database.js';
80
81
  export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.js';
81
- export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.js';
82
+ export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob, writeResourceBlobStream } from './resource-blob.js';
82
83
  export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from './ancestry.js';
83
84
  export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from './history.js';
84
85
  export type { HistoryView, HistoryLayout, HistoryReference, HistoryOrigin } from './history.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';
@@ -54,8 +54,8 @@ export { FsWorldStore, MemoryWorldStore, getActiveWorldStore, setActiveWorldStor
54
54
  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
- 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";
57
+ export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, streamChunks, writeBlobStream, } from "./blob-store.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";
@@ -98,9 +100,10 @@ export { multipartBoundary, multipartParts, multipartEnvelope, inferMultipartBou
98
100
  export { corsHeaders, withCors } from "./cors.js";
99
101
  export { setInProcessTwins, vendorFetch, VendorUnreachableError } from "./vendor-call.js";
100
102
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver } from "./managed-database.js";
101
- export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from "./resource-blob.js";
103
+ export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob, writeResourceBlobStream } from "./resource-blob.js";
102
104
  export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from "./ancestry.js";
103
105
  export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from "./history.js";
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';
@@ -43,6 +60,37 @@ export async function serveHttp(options) {
43
60
  // the global `Bun.serve` — the request journal's wrap of it (serve.ts) applies to packs and to
44
61
  // this seam alike, so the seam never decorates twice on Bun
45
62
  const upgrade = options.upgrade;
63
+ // the sockets open now, by the seam's own count: Bun keeps a socket its server closed (a refused key's 1008, from
64
+ // `open` or later) in `pendingWebSockets` after its close handler ran, and `stop()`'s promise then never settles
65
+ // (measured on Bun 1.3.11, macOS arm64, alone: a Bun.serve whose open() calls ws.close(1008) leaves
66
+ // pendingWebSockets at 1 with its close handler called once, and an awaited stop(true) had not settled after 3 s;
67
+ // unawaited, the listener closed and the process used 4 ms of CPU over the next 2 s; a client-initiated close
68
+ // stops in 0 ms). Conform's life hung on it after OpenAI's keyless Realtime step.
69
+ // and the requests whose handlers have not answered: a stop that drops connections (closeActive) aborts each one's
70
+ // signal, and Bun's stop still waits for the handler to settle, which a handler that never answers never does
71
+ // (measured on Bun 1.3.11, macOS arm64, alone: a Bun.serve whose fetch never resolves, its request's signal aborted
72
+ // by stop(true), pendingRequests 1, the stop unsettled after 3 s). Conform's client check hung on a pack served with
73
+ // a scenario that answered nothing. Node's path below reports such a handler as cleanup and resolves; so does this one.
74
+ const live = new Set();
75
+ const inflight = new Set();
76
+ const served = options.fetch;
77
+ const tracked = (request, ...rest) => {
78
+ inflight.add(request);
79
+ let answer;
80
+ try {
81
+ answer = served(request, ...rest);
82
+ }
83
+ catch (error) {
84
+ inflight.delete(request);
85
+ throw error;
86
+ }
87
+ if (!(answer instanceof Promise)) {
88
+ inflight.delete(request);
89
+ return answer;
90
+ }
91
+ return answer.finally(() => { inflight.delete(request); });
92
+ };
93
+ options = { ...options, fetch: tracked };
46
94
  const server = bun.serve({ ...options, hostname, port: options.port ?? 0,
47
95
  ...(upgrade ? {
48
96
  fetch: (request, server) => {
@@ -53,16 +101,35 @@ export async function serveHttp(options) {
53
101
  return options.fetch(request);
54
102
  },
55
103
  websocket: {
56
- open: (peer) => upgrade.open(peer, peer.data.request),
104
+ open: (peer) => { live.add(peer); return upgrade.open(peer, peer.data.request); },
57
105
  message: (peer, data) => upgrade.message(peer, data),
58
- close: upgrade.close,
106
+ close: (peer, ...rest) => { live.delete(peer); return upgrade.close(peer, ...rest); },
59
107
  },
60
108
  } : {}),
61
109
  });
62
110
  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); } };
111
+ return keepHandler({ hostname, port, url: server.url instanceof URL ? server.url : new URL(`http://${urlHost}:${port}/`), stop: async (closeActive = true) => {
112
+ const stopping = Promise.resolve(server.stop(closeActive));
113
+ // stop closes the sockets still open (closeActive), each a close Bun may then keep counting, and drops the
114
+ // connections of handlers still running: once every socket the seam opened has closed and every handler left is
115
+ // one whose request was dropped, Bun's stop would never settle on them (above), and the seam has stopped
116
+ let settled = false;
117
+ stopping.then(() => { settled = true; }, () => { settled = true; });
118
+ while (!settled) {
119
+ const orphaned = [...inflight].every((request) => request.signal.aborted);
120
+ const counted = (server.pendingWebSockets ?? 0) > 0;
121
+ if (live.size === 0 && orphaned && (counted || (closeActive && inflight.size > 0))) {
122
+ stopping.catch(() => { });
123
+ if (inflight.size)
124
+ console.error(`serveHttp: forced stop left pending handler cleanup: ${inflight.size} request(s)`);
125
+ return;
126
+ }
127
+ await new Promise((resolve) => setTimeout(resolve, 10));
128
+ }
129
+ await stopping;
130
+ } }, options.fetch);
64
131
  }
65
- return serveOnNode({ ...options, hostname, urlHost, fetch: decorate ? decorate(options) : options.fetch });
132
+ return keepHandler(await serveOnNode({ ...options, hostname, urlHost, fetch: decorate ? decorate(options) : options.fetch }), options.fetch);
66
133
  }
67
134
  /** A Node builtin without a static import — `process.getBuiltinModule` (Node 22.3+, Bun): what a
68
135
  * 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 = {
@@ -44,8 +44,11 @@ export type SocketEngine<C> = {
44
44
  };
45
45
  /** The open sessions of one socket of the vendor in a World (a handler reaches them as `ctx.sockets(id)`). */
46
46
  export declare function openSessions(service: string, root: string | undefined, socket: string): SocketSession[];
47
+ /** What a session awaits that the World's clock has run out (a life's `wait` past a relay's timeout), rejected as the
48
+ * machine's clock would reject it; called as the vendor serves a request ("A request answered by a session"). */
49
+ export declare function expireSocketWaits(service: string, root: string | undefined): void;
47
50
  /** Offer a write to every open session of the vendor, each in its turn after what it is already doing. Queued, never
48
51
  * awaited: the write may be one a session's own frame made, which that session finishes before it is told. */
49
52
  export declare function socketWrite(service: string, root: string | undefined, write: EventWrite): void;
50
53
  /** The upgrade the serve seam takes: a request to a declared socket's path (and host) opens a session of its engine. */
51
- export declare function socketUpgrade<C>(service: string, root: string | undefined, decls: ReadonlyArray<SocketDecl>, engines: Record<string, SocketEngine<C>>, context: (request: Request, decl: SocketDecl) => Promise<C>, hostOf: (request: Request) => string, pathOf?: (request: Request) => string, decide?: (asked: unknown) => Promise<ScenarioServed | undefined>): WebSocketUpgrade;
54
+ export declare function socketUpgrade<C>(service: string, root: string | undefined, decls: ReadonlyArray<SocketDecl>, engines: Record<string, SocketEngine<C>>, context: (request: Request, decl: SocketDecl, params: Record<string, string>) => Promise<C>, hostOf: (request: Request) => string, pathOf?: (request: Request) => string, decide?: (asked: unknown) => Promise<ScenarioServed | undefined>): WebSocketUpgrade;
@@ -1,3 +1,12 @@
1
+ // A vendor's persistent wire (architecture, "Other wires: sockets"): a WebSocket its clients hold open, on which the
2
+ // vendor sends what happens as it happens (Discord's Gateway: a bot identifies, is told the guilds it is in, and is sent
3
+ // each message, member and interaction as it lands). The manifest declares the socket (`sockets`); the pack's engine
4
+ // (`semantics/sockets.ts`, one export per socket id) speaks its protocol: what a session is sent when it opens, what it
5
+ // answers each frame, and which frames a write of the World sends it. The kernel holds the open sessions, serves the
6
+ // upgrade through the serve seam, and offers every write to every open session of the vendor, after the write's
7
+ // webhooks. A session's own counters (a sequence number, a heartbeat) are the connection's, never the World's state.
8
+ // Nothing here runs at module scope beyond the empty registry.
9
+ import { worldNow } from "./world-clock.js";
1
10
  const sessions = new Map();
2
11
  /** A World's next session number, per World (its service and root): a session's id is the World's, never the
3
12
  * process's, so a life replayed on a fresh World numbers its sessions as it did the first time. */
@@ -7,6 +16,21 @@ const keyOf = (service, root) => `${service}\0${root ?? ''}`;
7
16
  export function openSessions(service, root, socket) {
8
17
  return [...(sessions.get(keyOf(service, root)) ?? [])].filter((o) => o.session.socket === socket).map((o) => o.session);
9
18
  }
19
+ /** What a session awaits that the World's clock has run out (a life's `wait` past a relay's timeout), rejected as the
20
+ * machine's clock would reject it; called as the vendor serves a request ("A request answered by a session"). */
21
+ export function expireSocketWaits(service, root) {
22
+ const open = sessions.get(keyOf(service, root));
23
+ if (!open?.size)
24
+ return;
25
+ const now = Date.parse(worldNow());
26
+ for (const o of open)
27
+ for (const [k, p] of o.pending)
28
+ if (p.due <= now) {
29
+ clearTimeout(p.timer);
30
+ o.pending.delete(k);
31
+ p.reject(new Error('no reply in time'));
32
+ }
33
+ }
10
34
  /** Offer a write to every open session of the vendor, each in its turn after what it is already doing. Queued, never
11
35
  * awaited: the write may be one a session's own frame made, which that session finishes before it is told. */
12
36
  export function socketWrite(service, root, write) {
@@ -20,12 +44,41 @@ export function socketWrite(service, root, write) {
20
44
  o.queue = o.queue.then(async () => engine.write(o.session, write, await o.context())).catch((error) => o.session.close(1011, String(error.message ?? error).slice(0, 120)));
21
45
  }
22
46
  }
47
+ /** A declared socket path's parameters for a request's path, or undefined when it does not match: a `{name}` segment
48
+ * matches one segment, as an operation's path parameter does (architecture, sockets). */
49
+ function socketParams(declared, path) {
50
+ const names = [];
51
+ const pattern = (declared.replace(/\/+$/, '') || '/').split(/(\{[^}/]+\})/).map((part) => {
52
+ const name = /^\{([^}/]+)\}$/.exec(part)?.[1];
53
+ if (name === undefined)
54
+ return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
+ names.push(name);
56
+ return '([^/]+)';
57
+ }).join('');
58
+ const hit = new RegExp(`^${pattern}$`).exec(path);
59
+ if (!hit)
60
+ return undefined;
61
+ try {
62
+ return Object.fromEntries(names.map((name, i) => [name, decodeURIComponent(hit[i + 1])]));
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
23
68
  /** The upgrade the serve seam takes: a request to a declared socket's path (and host) opens a session of its engine. */
24
69
  export function socketUpgrade(service, root, decls, engines, context, hostOf, pathOf = (request) => new URL(request.url).pathname, decide = async () => undefined) {
25
- const declFor = (request) => decls.find((d) => {
70
+ const matchFor = (request) => {
26
71
  const path = pathOf(request).replace(/\/+$/, '') || '/';
27
- return (d.path.replace(/\/+$/, '') || '/') === path && (!d.host || d.host.toLowerCase() === hostOf(request)) && engines[d.id] !== undefined;
28
- });
72
+ for (const d of decls) {
73
+ if ((d.host && d.host.toLowerCase() !== hostOf(request)) || engines[d.id] === undefined)
74
+ continue;
75
+ const params = socketParams(d.path, path);
76
+ if (params)
77
+ return { decl: d, params };
78
+ }
79
+ return undefined;
80
+ };
81
+ const declFor = (request) => matchFor(request)?.decl;
29
82
  const byPeer = new WeakMap();
30
83
  const key = keyOf(service, root);
31
84
  return {
@@ -38,7 +91,7 @@ export function socketUpgrade(service, root, decls, engines, context, hostOf, pa
38
91
  return declared ? offered.find((p) => declared.includes(p)) : offered[0];
39
92
  },
40
93
  open: (peer, request) => {
41
- const decl = declFor(request);
94
+ const { decl, params } = matchFor(request);
42
95
  const engine = engines[decl.id];
43
96
  const pending = new Map();
44
97
  const session = {
@@ -47,7 +100,7 @@ export function socketUpgrade(service, root, decls, engines, context, hostOf, pa
47
100
  close: (code, reason) => { peer.close(code, reason); },
48
101
  expect: (key, ms) => new Promise((resolve, reject) => {
49
102
  const timer = setTimeout(() => { pending.delete(key); reject(new Error('no reply in time')); }, ms);
50
- pending.set(key, { resolve, reject, timer });
103
+ pending.set(key, { resolve, reject, timer, due: Date.parse(worldNow()) + ms });
51
104
  }),
52
105
  fulfil: (key, value) => {
53
106
  const p = pending.get(key);
@@ -60,7 +113,7 @@ export function socketUpgrade(service, root, decls, engines, context, hostOf, pa
60
113
  },
61
114
  decide,
62
115
  };
63
- const o = { session, engine, context: () => context(request.clone(), decl), queue: Promise.resolve(), pending };
116
+ const o = { session, engine, context: () => context(request.clone(), decl, params), queue: Promise.resolve(), pending };
64
117
  byPeer.set(peer, o);
65
118
  (sessions.get(key) ?? sessions.set(key, new Set()).get(key)).add(o);
66
119
  if (engine.open)
@@ -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
@@ -34,6 +34,52 @@ async function worldTwins() {
34
34
  }
35
35
  return { ...twinOrigins(env), ...twinOrigins(instanceEnv) };
36
36
  }
37
+ /** The hosts each claiming twin last listed at its `hostsClaimed` door, by the door's address, and when; read again
38
+ * after CLAIMS_TTL_MS or for a host it lacks, as the injector polls the doors every five seconds (inject.cjs, "HOSTS A TWIN CLAIMS WHILE IT
39
+ * RUNS"). */
40
+ const claimsRead = new Map();
41
+ const CLAIMS_TTL_MS = 5_000;
42
+ /** The hosts a twin claims at its door: in process (a journey walk's companion) or at its address, within the injector's
43
+ * first-read deadline (inject.cjs CLAIMS_WAIT_MS, 1500 ms, measured beside it); a door that does not answer claims
44
+ * nothing. */
45
+ async function claimedHosts(at, ask, host) {
46
+ const held = claimsRead.get(at);
47
+ // a host the held list lacks is asked for again once, as the injector refreshes before it refuses: a domain claimed a
48
+ // moment ago is found
49
+ if (held && Date.now() - held.at < CLAIMS_TTL_MS && held.hosts.has(host))
50
+ return held.hosts;
51
+ let hosts = new Set();
52
+ try {
53
+ const r = await ask();
54
+ const body = r.ok ? await r.json() : null;
55
+ hosts = new Set((body?.hosts ?? []).map((h) => String(h).toLowerCase()));
56
+ }
57
+ catch { /* an unanswered door claims nothing */ }
58
+ claimsRead.set(at, { hosts, at: Date.now() });
59
+ return hosts;
60
+ }
61
+ /** The twin that claims a host at its `hostsClaimed` door (an R2 bucket's custom domain, a Worker's), as the injector
62
+ * routes it: a twin this process answers first, the World's twins after. */
63
+ async function claimingTwin(target, twins) {
64
+ const { packFacts } = await import('../pack-facts.cjs');
65
+ const host = target.hostname.toLowerCase();
66
+ const key = process.env.VOLTER_TWINS_KEY;
67
+ const doors = Object.entries(packFacts().packs).flatMap(([vendor, facts]) => (facts.hostsClaimed?.door ? [[vendor, facts.hostsClaimed.door]] : []));
68
+ for (const [vendor, door] of doors) {
69
+ const twin = inProcessTwins[vendor];
70
+ if (twin && (await claimedHosts(`in-process:${vendor}${door}`, async () => twin(new Request(`http://${vendor}.twin${door}`)), host)).has(host))
71
+ return { vendor, local: true };
72
+ }
73
+ for (const [vendor, door] of doors) {
74
+ const origin = twins[vendor];
75
+ if (origin === undefined)
76
+ continue;
77
+ const at = `${origin.replace(/\/$/, '')}${door}`;
78
+ if ((await claimedHosts(at, () => fetch(at, { headers: key ? { 'x-twins-key': key } : {}, signal: AbortSignal.timeout(1500) }), host)).has(host))
79
+ return { vendor, local: false };
80
+ }
81
+ return undefined;
82
+ }
37
83
  /** One URL answered only by a World-held application or vendor twin: the fetch's application stand-in first (its answer
38
84
  * is the application's; `'unreachable'` leaves no application to reach, the World's twins still reachable). */
39
85
  async function worldResponse(target, init, standIn) {
@@ -61,9 +107,21 @@ async function worldResponse(target, init, standIn) {
61
107
  headers.set('x-twins-key', key);
62
108
  return fetch(`${twins[vendor].replace(/\/$/, '')}${target.pathname}${target.search}`, { ...init, headers, redirect: 'manual' });
63
109
  }
110
+ // the application's own hosts before any twin's claimed ones: a call to the application asks no door
64
111
  const { appDestination, appFetch } = await import('../app-route.cjs');
65
112
  if (scripted !== 'unreachable' && appDestination(target))
66
113
  return appFetch(target, { ...init, redirect: 'manual' });
114
+ const claimer = await claimingTwin(target, twins);
115
+ if (claimer !== undefined) {
116
+ const headers = new Headers(init.headers);
117
+ headers.set('x-volter-twin-original-host', target.host);
118
+ if (claimer.local)
119
+ return inProcessTwins[claimer.vendor](new Request(target, { ...init, headers, redirect: 'manual' }));
120
+ const key = process.env.VOLTER_TWINS_KEY;
121
+ if (key && !headers.has('x-twins-key'))
122
+ headers.set('x-twins-key', key);
123
+ return fetch(`${twins[claimer.vendor].replace(/\/$/, '')}${target.pathname}${target.search}`, { ...init, headers, redirect: 'manual' });
124
+ }
67
125
  throw new VendorUnreachableError(`${target.host}${target.pathname}: this World holds no application or vendor twin at this URL`);
68
126
  }
69
127
  /** A vendor URL (or the World's application), including redirects, always answered inside the World; `standIn` is the
@@ -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();