@volter/world-core 2.0.37 → 3.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 (205) hide show
  1. package/README.md +4 -5
  2. package/app-route.cjs +12 -6
  3. package/app-route.d.cts +1 -1
  4. package/dist/app-route.cjs +12 -6
  5. package/dist/app-route.d.cts +1 -1
  6. package/dist/generated/pack-facts.json +1410 -3069
  7. package/dist/inject.cjs +64 -9
  8. package/dist/pack-facts.cjs +44 -0
  9. package/dist/src/actions.d.ts +3 -3
  10. package/dist/src/actions.js +22 -16
  11. package/dist/src/ancestry.d.ts +14 -2
  12. package/dist/src/ancestry.js +92 -2
  13. package/dist/src/anthropic-wire.d.ts +39 -0
  14. package/dist/src/anthropic-wire.js +136 -0
  15. package/dist/src/bytes.d.ts +7 -0
  16. package/dist/src/bytes.js +35 -0
  17. package/dist/src/changeset.d.ts +1 -1
  18. package/dist/src/changeset.js +0 -0
  19. package/dist/src/clickhouse/index.d.ts +3 -0
  20. package/dist/src/clickhouse/index.js +6 -0
  21. package/dist/src/clickhouse/sql.d.ts +233 -0
  22. package/dist/src/clickhouse/sql.js +4329 -0
  23. package/dist/src/clickhouse/types.d.ts +18 -0
  24. package/dist/src/clickhouse/types.js +47 -0
  25. package/dist/src/clickhouse/values.d.ts +146 -0
  26. package/dist/src/clickhouse/values.js +858 -0
  27. package/dist/src/client-bundle.js +2 -3
  28. package/dist/src/cors.d.ts +15 -0
  29. package/dist/src/cors.js +31 -0
  30. package/dist/src/derived-core.d.ts +487 -24
  31. package/dist/src/derived-core.js +788 -144
  32. package/dist/src/derived-real.d.ts +13 -0
  33. package/dist/src/derived-real.js +518 -0
  34. package/dist/src/derived.d.ts +35 -1
  35. package/dist/src/derived.js +61 -9
  36. package/dist/src/emit.js +1 -2
  37. package/dist/src/events.d.ts +206 -0
  38. package/dist/src/events.js +341 -0
  39. package/dist/src/executor.d.ts +3 -0
  40. package/dist/src/executor.js +19 -2
  41. package/dist/src/file-response.d.ts +6 -0
  42. package/dist/src/file-response.js +30 -0
  43. package/dist/src/fork.js +3 -2
  44. package/dist/src/git/history.d.ts +7 -0
  45. package/dist/src/git/history.js +24 -0
  46. package/dist/src/git/index.d.ts +1 -0
  47. package/dist/src/git/index.js +1 -0
  48. package/dist/src/git/lfs.d.ts +28 -0
  49. package/dist/src/git/lfs.js +66 -0
  50. package/dist/src/git/objects.js +3 -8
  51. package/dist/src/git/smart-http.d.ts +3 -1
  52. package/dist/src/git/smart-http.js +67 -6
  53. package/dist/src/graphql-wire.d.ts +29 -0
  54. package/dist/src/graphql-wire.js +101 -0
  55. package/dist/src/grpc-wire.d.ts +67 -0
  56. package/dist/src/grpc-wire.js +170 -0
  57. package/dist/src/h2.d.ts +40 -0
  58. package/dist/src/h2.js +656 -0
  59. package/dist/src/head.d.ts +32 -3
  60. package/dist/src/head.js +161 -40
  61. package/dist/src/history.d.ts +1 -1
  62. package/dist/src/history.js +6 -6
  63. package/dist/src/hpack.json +1 -0
  64. package/dist/src/index.d.ts +64 -75
  65. package/dist/src/index.js +58 -101
  66. package/dist/src/log.js +28 -19
  67. package/dist/src/machines.d.ts +50 -0
  68. package/dist/src/machines.js +151 -0
  69. package/dist/src/managed-database.d.ts +86 -0
  70. package/dist/src/managed-database.js +283 -0
  71. package/dist/src/multipart.d.ts +11 -0
  72. package/dist/src/multipart.js +51 -0
  73. package/dist/src/observe.d.ts +15 -5
  74. package/dist/src/observe.js +23 -9
  75. package/dist/src/openai-wire.d.ts +108 -0
  76. package/dist/src/openai-wire.js +337 -0
  77. package/dist/src/pack-assets.d.ts +3 -4
  78. package/dist/src/pack-assets.js +15 -10
  79. package/dist/src/pack-fetch.d.ts +77 -0
  80. package/dist/src/pack-fetch.js +449 -0
  81. package/dist/src/pack-paths.d.ts +12 -0
  82. package/dist/src/pack-paths.js +86 -0
  83. package/dist/src/packRegistry.d.ts +69 -162
  84. package/dist/src/packRegistry.js +55 -20
  85. package/dist/src/people.d.ts +13 -0
  86. package/dist/src/people.js +18 -0
  87. package/dist/src/placeholder-image.d.ts +5 -0
  88. package/dist/src/placeholder-image.js +114 -0
  89. package/dist/src/protobuf.d.ts +28 -0
  90. package/dist/src/protobuf.js +332 -0
  91. package/dist/src/redis/engine.js +1 -1
  92. package/dist/src/request-scope.d.ts +1 -1
  93. package/dist/src/request-scope.js +6 -4
  94. package/dist/src/resource-blob.d.ts +5 -0
  95. package/dist/src/resource-blob.js +11 -0
  96. package/dist/src/runtime.d.ts +85 -0
  97. package/dist/src/runtime.js +104 -0
  98. package/dist/src/s3/wire.d.ts +60 -0
  99. package/dist/src/s3/wire.js +157 -0
  100. package/dist/src/scenario.d.ts +3 -0
  101. package/dist/src/scenario.js +2 -0
  102. package/dist/src/schema-sample.d.ts +1 -0
  103. package/dist/src/schema-sample.js +21 -0
  104. package/dist/src/sealed-box.d.ts +14 -0
  105. package/dist/src/sealed-box.js +225 -0
  106. package/dist/src/serve-http.d.ts +14 -0
  107. package/dist/src/serve-http.js +27 -3
  108. package/dist/src/serve.d.ts +6 -0
  109. package/dist/src/serve.js +69 -14
  110. package/dist/src/signing.d.ts +135 -0
  111. package/dist/src/signing.js +222 -0
  112. package/dist/src/sigv4.d.ts +48 -0
  113. package/dist/src/sigv4.js +167 -0
  114. package/dist/src/smtp.d.ts +16 -0
  115. package/dist/src/smtp.js +72 -0
  116. package/dist/src/sockets.d.ts +51 -0
  117. package/dist/src/sockets.js +90 -0
  118. package/dist/src/state-system.d.ts +1 -0
  119. package/dist/src/state-system.js +1 -1
  120. package/dist/src/storage.d.ts +1 -1
  121. package/dist/src/storage.js +3 -3
  122. package/dist/src/trace-context.js +1 -1
  123. package/dist/src/twin-fetch.d.ts +0 -7
  124. package/dist/src/twin-fetch.js +0 -14
  125. package/dist/src/vendor-call.d.ts +6 -0
  126. package/dist/src/vendor-call.js +41 -0
  127. package/dist/src/world-store.js +1 -1
  128. package/dist/vendor-hosts.cjs +36 -125
  129. package/dist/vendor-hosts.d.cts +8 -0
  130. package/generated/pack-facts.json +1410 -3069
  131. package/inject.cjs +64 -9
  132. package/pack-facts.cjs +44 -0
  133. package/package.json +17 -3
  134. package/src/actions.ts +23 -16
  135. package/src/ancestry.ts +74 -2
  136. package/src/anthropic-wire.ts +137 -0
  137. package/src/bytes.ts +42 -0
  138. package/src/changeset.ts +5 -5
  139. package/src/clickhouse/index.ts +6 -0
  140. package/src/clickhouse/sql.ts +3059 -0
  141. package/src/clickhouse/types.ts +44 -0
  142. package/src/clickhouse/values.ts +697 -0
  143. package/src/client-bundle.ts +2 -3
  144. package/src/cors.ts +34 -0
  145. package/src/derived-core.ts +1013 -146
  146. package/src/derived-real.ts +434 -0
  147. package/src/derived.ts +73 -3
  148. package/src/emit.ts +1 -2
  149. package/src/events.ts +449 -0
  150. package/src/executor.ts +24 -2
  151. package/src/file-response.ts +27 -0
  152. package/src/fork.ts +3 -2
  153. package/src/git/history.ts +19 -0
  154. package/src/git/index.ts +1 -0
  155. package/src/git/lfs.ts +67 -0
  156. package/src/git/objects.ts +3 -5
  157. package/src/git/smart-http.ts +56 -6
  158. package/src/graphql-wire.ts +106 -0
  159. package/src/grpc-wire.ts +159 -0
  160. package/src/h2.ts +627 -0
  161. package/src/head.ts +132 -41
  162. package/src/history.ts +6 -6
  163. package/src/hpack.json +1 -0
  164. package/src/index.ts +82 -329
  165. package/src/log.ts +27 -18
  166. package/src/machines.ts +151 -0
  167. package/src/managed-database.ts +299 -0
  168. package/src/multipart.ts +51 -0
  169. package/src/observe.ts +31 -15
  170. package/src/openai-wire.ts +371 -0
  171. package/src/pack-assets.ts +15 -11
  172. package/src/pack-fetch.ts +458 -0
  173. package/src/pack-paths.ts +72 -0
  174. package/src/packRegistry.ts +79 -167
  175. package/src/people.ts +31 -0
  176. package/src/placeholder-image.ts +88 -0
  177. package/src/protobuf.ts +251 -0
  178. package/src/redis/engine.ts +1 -1
  179. package/src/request-scope.ts +8 -4
  180. package/src/resource-blob.ts +13 -0
  181. package/src/runtime.ts +344 -0
  182. package/src/s3/wire.ts +172 -0
  183. package/src/scenario.ts +4 -0
  184. package/src/schema-sample.ts +24 -0
  185. package/src/sealed-box.ts +182 -0
  186. package/src/serve-http.ts +31 -3
  187. package/src/serve.ts +58 -14
  188. package/src/signing.ts +231 -0
  189. package/src/sigv4.ts +158 -0
  190. package/src/smtp.ts +76 -0
  191. package/src/sockets.ts +140 -0
  192. package/src/state-system.ts +2 -2
  193. package/src/storage.ts +3 -3
  194. package/src/trace-context.ts +1 -1
  195. package/src/twin-fetch.ts +0 -20
  196. package/src/vendor-call.ts +41 -0
  197. package/src/world-store.ts +1 -1
  198. package/vendor-hosts.cjs +36 -125
  199. package/vendor-hosts.d.cts +8 -0
  200. package/dist/src/mirror-shell.d.ts +0 -2
  201. package/dist/src/mirror-shell.js +0 -13
  202. package/dist/src/v1-removed.d.ts +0 -159
  203. package/dist/src/v1-removed.js +0 -124
  204. package/src/mirror-shell.ts +0 -15
  205. package/src/v1-removed.ts +0 -172
package/dist/inject.cjs CHANGED
@@ -13,8 +13,7 @@
13
13
  // e.g. STRIPE_TWIN_URL=http://127.0.0.1:12111 or a hosted skin URL
14
14
  // {twinsUrl}/{org}/{world}/{vendor} with VOLTER_TWINS_KEY set (R7c). Host claims come
15
15
  // from pack-facts at runtime — this file carries no per-vendor roster to rot.
16
- // AWS service-areas (DYNAMODB/TIMESTREAM/SESV2/SECRETSMANAGER/BEDROCK_TWIN_URL) point at
17
- // the consolidated `aws` twin, same as S3_TWIN_URL.
16
+ // The aws pack's service keys (S3_TWIN_URL, SECRETSMANAGER_TWIN_URL) both point at its one twin.
18
17
  // …or combined: TWIN_INJECT="stripe=http://127.0.0.1:12111,github=http://127.0.0.1:12120"
19
18
  //
20
19
  // It patches http/https `request`/`get` and global `fetch` at the process
@@ -44,6 +43,7 @@ const { createPublicKey, createVerify } = require('crypto');
44
43
  // serves it) has its own home, shared with the application route
45
44
  const { VENDOR_HOSTS, twinEnvStem, twinOrigins } = require('./vendor-hosts.cjs');
46
45
  const dns = require('dns');
46
+ const http2 = require('http2');
47
47
 
48
48
  function decodeBase64UrlJson(part) {
49
49
  try {
@@ -130,7 +130,7 @@ installClerkFastifyTwinShim();
130
130
  // twin that is attached AND whose URL variable is set; the first such pack wins.
131
131
  function installPrismaAdapterShim() {
132
132
  if (Module.__volterPrismaAdapterShimInstalled) return;
133
- const { packs } = require('./generated/pack-facts.json');
133
+ const { packs } = require('./pack-facts.cjs').packFacts();
134
134
  const candidates = Object.keys(packs).filter((vendor) => packs[vendor].prismaAdapter).sort();
135
135
  if (candidates.length === 0) return;
136
136
  Module.__volterPrismaAdapterShimInstalled = true;
@@ -225,7 +225,7 @@ function resolveTwin(hostname, map, pathname) {
225
225
  const base = worldBase();
226
226
  // a name a vendor's host rule gives a twin running here is that twin's, never an application's (a vendor with no
227
227
  // twin here can be one of the World's applications: the product itself)
228
- const app = appRouteFor(map[APP_INSTANCE], bare, map);
228
+ const app = appRouteFor(map[APP_INSTANCE], bare, map, claimedAppHosts(map));
229
229
  if (app && !isTwinOriginHost(bare) && (!base || new URL(base.origin).hostname !== bare)) {
230
230
  return { vendor: 'app', origin: app.origin };
231
231
  }
@@ -245,17 +245,18 @@ const { appRouteFor, appForwardHeaders, appRequestOptions, sessionCa } = require
245
245
  // to a vendor, so each claiming twin lists its own at its door, read here every five seconds from the moment the
246
246
  // injector installs (or, in a process that only resolves, the proxy, from its first resolve). A fetch that would be
247
247
  // refused while a first read is still in flight waits for it; a name connected moments ago is refused until the next
248
- // read, as a new DNS record takes a moment.
248
+ // read, as a new DNS record takes a moment. The door's `appHosts` are the names a hosting twin serves as the
249
+ // customer's own application (a domain added to a Vercel project): those route to the application, not the twin.
249
250
  const CLAIMERS = {};
250
251
  {
251
- const { packs } = require('./generated/pack-facts.json');
252
+ const { packs } = require('./pack-facts.cjs').packFacts();
252
253
  for (const vendor of Object.keys(packs)) if (packs[vendor].hostsClaimed) CLAIMERS[vendor] = packs[vendor].hostsClaimed.door;
253
254
  }
254
255
  const CLAIMS = new Map(); // cache: origin -> the hosts its twin last listed; refreshed by the poll below
255
256
  function claimsAt(origin, door) {
256
257
  const held = CLAIMS.get(origin);
257
258
  if (held) return held;
258
- const entry = { hosts: new Set(), loaded: false, ready: null };
259
+ const entry = { hosts: new Set(), appHosts: new Set(), loaded: false, ready: null };
259
260
  let settle;
260
261
  entry.ready = new Promise((resolve) => { settle = resolve; });
261
262
  CLAIMS.set(origin, entry);
@@ -266,7 +267,10 @@ function claimsAt(origin, door) {
266
267
  Promise.resolve()
267
268
  .then(() => f(`${origin}${door}`, { headers: attachTwinsKey({}) }))
268
269
  .then((r) => (r.ok ? r.json() : null))
269
- .then((body) => { if (body && Array.isArray(body.hosts)) entry.hosts = new Set(body.hosts.map((h) => String(h).toLowerCase())); })
270
+ .then((body) => {
271
+ if (body && Array.isArray(body.hosts)) entry.hosts = new Set(body.hosts.map((h) => String(h).toLowerCase()));
272
+ if (body) entry.appHosts = new Set(Array.isArray(body.appHosts) ? body.appHosts.map((h) => String(h).toLowerCase()) : []);
273
+ })
270
274
  .catch(() => {})
271
275
  .then(() => { entry.loaded = true; settle(); });
272
276
  };
@@ -277,6 +281,15 @@ function claimsAt(origin, door) {
277
281
  if (timer.unref) timer.unref();
278
282
  return entry;
279
283
  }
284
+ /** The names the claiming twins of this map serve as the application (their doors' `appHosts`), or null for none. */
285
+ function claimedAppHosts(map) {
286
+ let out = null;
287
+ for (const vendor of Object.keys(CLAIMERS)) {
288
+ if (!map[vendor]) continue;
289
+ for (const h of claimsAt(map[vendor], CLAIMERS[vendor]).appHosts) (out ??= new Set()).add(h);
290
+ }
291
+ return out;
292
+ }
280
293
  /** The first read of a claiming twin's door still in flight for this map, or null when every one has landed. */
281
294
  function claimsPending(map) {
282
295
  const pending = Object.keys(CLAIMERS).filter((v) => map[v]).map((v) => claimsAt(map[v], CLAIMERS[v])).filter((e) => !e.loaded);
@@ -737,7 +750,7 @@ function socketTargetHosts(args, isTls) {
737
750
 
738
751
  function endpointEnvFor(vendor) {
739
752
  try {
740
- const facts = require('./generated/pack-facts.json').packs[vendor];
753
+ const facts = require('./pack-facts.cjs').packFacts().packs[vendor];
741
754
  const endpoint = facts && facts.endpointEnv;
742
755
  if (!endpoint) return null;
743
756
  const templates = Object.keys(endpoint.templates || {});
@@ -868,6 +881,33 @@ function patchedConnect(originalConnect, isTls) {
868
881
  let MAP = {};
869
882
  let installed = false;
870
883
 
884
+ /** A request asking to switch protocols (a WebSocket's `Upgrade: websocket`): a vendor's socket a twin serves (Slack's
885
+ * Socket Mode link, Discord's Gateway), which the fetch-backed stand-in cannot carry. */
886
+ function isUpgrade(headers) {
887
+ for (const [k, v] of Object.entries(headers || {})) if (k.toLowerCase() === 'upgrade' && v) return true;
888
+ return false;
889
+ }
890
+
891
+ /** An upgrade to a twinned host, sent as a real request to the twin's own listener (its origin's scheme, host and port,
892
+ * the vendor's path under it), the vendor's host kept in `Host`: the client's upgrade, its socket and its frames are the
893
+ * twin's. The caller's own connection options (a TLS `createConnection`, an agent, a servername) name the vendor and
894
+ * are left behind. Where the evidence stops: a hosted World's front upgrades nothing yet, so this carries a local
895
+ * World's twins only. */
896
+ function upgradeToTwin(origin, url, method, headers, args, callback) {
897
+ const target = new URL(origin);
898
+ const given = args.find((a) => a && typeof a === 'object' && !(a instanceof URL)) || {};
899
+ const options = {};
900
+ for (const [k, v] of Object.entries(given)) {
901
+ if (!['protocol', 'host', 'hostname', 'port', 'path', 'agent', 'createConnection', 'servername', 'defaultPort', 'socketPath', 'headers', 'method', 'href', 'origin', 'search', 'pathname'].includes(k)) options[k] = v;
902
+ }
903
+ Object.assign(options, {
904
+ protocol: target.protocol, hostname: target.hostname, port: target.port || (target.protocol === 'https:' ? 443 : 80),
905
+ path: `${target.pathname.replace(/\/+$/, '')}${url.pathname}${url.search}`, method, headers,
906
+ });
907
+ const request = target.protocol === 'https:' ? ORIGINALS.httpsRequest : ORIGINALS.httpRequest;
908
+ return callback ? request(options, callback) : request(options);
909
+ }
910
+
871
911
  function patchedFactory(originalRequest, defaultProtocol) {
872
912
  return function patchedRequest(...args) {
873
913
  const { url, method, headers, callback } = describe(args, defaultProtocol);
@@ -879,6 +919,7 @@ function patchedFactory(originalRequest, defaultProtocol) {
879
919
  if (twin) {
880
920
  const fwdHeaders = Object.assign({}, headers);
881
921
  if (!fwdHeaders.host && !fwdHeaders.Host) fwdHeaders.host = url.host;
922
+ if (isUpgrade(headers)) return upgradeToTwin(twin.origin, url, method, fwdHeaders, args, callback);
882
923
  return fakeClientRequest(twin.origin, method, url.pathname + url.search, fwdHeaders, callback);
883
924
  }
884
925
  // No twin claims this path — but if configured twins claim the HOST, the path is an unclaimed
@@ -926,6 +967,7 @@ function install(mapOverride) {
926
967
  tlsConnect: tls.connect,
927
968
  dnsLookup: dns.lookup,
928
969
  dnsPromisesLookup: dns.promises.lookup,
970
+ http2Connect: http2.connect,
929
971
  };
930
972
 
931
973
  http.request = patchedFactory(ORIGINALS.httpRequest, 'http:');
@@ -937,6 +979,18 @@ function install(mapOverride) {
937
979
  tls.connect = patchedConnect(ORIGINALS.tlsConnect, true);
938
980
  dns.lookup = patchedLookup(ORIGINALS.dnsLookup);
939
981
  dns.promises.lookup = patchedPromisesLookup(ORIGINALS.dnsPromisesLookup);
982
+ // gRPC (architecture, "Other wires": gRPC): an HTTP/2 session to a host a twin serves (grpc-js on google-gax: TLS to
983
+ // webrisk.googleapis.com) connects to that twin's origin in plain text, where the kernel's HTTP/2 server answers it;
984
+ // the client carries on unchanged. Any other authority is the real one's.
985
+ http2.connect = function patchedHttp2Connect(authority, options, listener) {
986
+ let url = null;
987
+ try { url = new URL(typeof authority === 'string' ? authority : authority && authority.href ? authority.href : `https://${authority.hostname || authority.host}`); } catch { url = null; }
988
+ const twin = url && resolveTwin(url.hostname, MAP, '/');
989
+ if (!twin) return ORIGINALS.http2Connect.apply(this, arguments);
990
+ const cb = typeof options === 'function' ? options : listener;
991
+ const opts = typeof options === 'function' || !options ? {} : Object.fromEntries(Object.entries(options).filter(([k]) => !['secureContext', 'servername', 'ca', 'cert', 'key', 'checkServerIdentity', 'ALPNProtocols', 'createConnection', 'rejectUnauthorized'].includes(k)));
992
+ return ORIGINALS.http2Connect.call(this, new URL(twin.origin).origin, opts, cb);
993
+ };
940
994
 
941
995
  if (ORIGINALS.fetch) {
942
996
  const originalFetch = ORIGINALS.fetch;
@@ -1084,6 +1138,7 @@ function restore() {
1084
1138
  tls.connect = ORIGINALS.tlsConnect;
1085
1139
  dns.lookup = ORIGINALS.dnsLookup;
1086
1140
  dns.promises.lookup = ORIGINALS.dnsPromisesLookup;
1141
+ http2.connect = ORIGINALS.http2Connect;
1087
1142
  installed = false;
1088
1143
  MAP = {};
1089
1144
  }
@@ -0,0 +1,44 @@
1
+ // THE PACK FACTS a process routes by (docs/contributing/architecture.md, "The catalog: where twins come from"): the facts
2
+ // this kernel was built with (generated/pack-facts.json, compiled from the packs of its checkout), overlaid by the facts
3
+ // of the packs the World resolved from installed packages. Each released pack carries its own
4
+ // (`generated/pack-facts.json`), and the World's instance file (VOLTER_WORLD_INSTANCE, which every World process already
5
+ // has) names them as `packFacts`. A pack's own facts replace the built-in ones for its vendor. Plain CommonJS, no
6
+ // dependency: the injector preloads it. Read once per process.
7
+ 'use strict';
8
+ const fs = require('fs');
9
+
10
+ let cached;
11
+ function packFacts() {
12
+ if (cached) return cached;
13
+ const base = require('./generated/pack-facts.json');
14
+ const packs = Object.assign({}, base.packs);
15
+ // the vendors whose facts came from an installed pack (the host rules let those replace a hand entry)
16
+ const installed = [];
17
+ const instance = process.env.VOLTER_WORLD_INSTANCE;
18
+ if (instance) {
19
+ try {
20
+ const files = JSON.parse(fs.readFileSync(instance, 'utf8')).packFacts || [];
21
+ for (const file of files) {
22
+ try { const own = JSON.parse(fs.readFileSync(file, 'utf8')).packs || {}; Object.assign(packs, own); installed.push(...Object.keys(own)); } catch (_) { /* a pack without readable facts keeps the built-in ones */ }
23
+ }
24
+ } catch (_) { /* no instance yet: the built-in facts */ }
25
+ }
26
+ cached = Object.assign({}, base, { packs, installed });
27
+ return cached;
28
+ }
29
+
30
+ /** Facts files a process knows before any World instance names them (the runtime booting a World it has not written
31
+ * the instance of): merged as the instance's are, and into the host rules already built. */
32
+ function extendPackFacts(files) {
33
+ const facts = packFacts();
34
+ const added = {};
35
+ for (const file of files || []) {
36
+ try { Object.assign(added, JSON.parse(fs.readFileSync(file, 'utf8')).packs || {}); } catch (_) { /* the facts it has stand */ }
37
+ }
38
+ Object.assign(facts.packs, added);
39
+ // the host rules already built from the facts learn these packs too
40
+ require('./vendor-hosts.cjs').addPackHosts(added);
41
+ return facts;
42
+ }
43
+
44
+ module.exports = { packFacts, extendPackFacts };
@@ -124,7 +124,7 @@ export declare function appendActionIfAbsent(action: TwinAction, root?: string):
124
124
  * Append `build(n)` as an OCCURRENCE: the nth time this exact write has been made.
125
125
  *
126
126
  * A local vendor write is an occurrence, not a replay — two calls are two actions, even
127
- * byte-identical in the same instant (docs/contributing/adding-a-twin.md#5-build-on-the-shared-kernel--dont-reinvent,
127
+ * byte-identical in the same instant (docs/contributing/architecture.md, "The state kernel",
128
128
  * "A local write is an occurrence"). `appendActionIfAbsent` cannot express that: its identity is content, and
129
129
  * content provably cannot separate "the same request delivered twice" from "the same change made
130
130
  * twice". So a caller wanting at-most-once passes an explicit key and uses that function; a caller
@@ -205,8 +205,8 @@ export declare class OwnerStoreAmbiguousError extends Error {
205
205
  });
206
206
  }
207
207
  /**
208
- * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, xidentity's tokens
209
- * read by x, googleoauth's by googlecalendar; scripts/architecture.test.ts holds the declared reader-owner pairs): the
208
+ * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, googleoauth's tokens
209
+ * read by webrisk; the reader's manifest declares the pair, `ownerReads`): the
210
210
  * owner's tree, from wherever the World keeps the owner's store (`ownerStoreRoots`). Read only: the rows are frozen and
211
211
  * shared. It claims no journal identity (the twin answering is the reader).
212
212
  *
@@ -11,7 +11,7 @@
11
11
  //
12
12
  // Projection = observed mirror, then apply each `set` transaction in order,
13
13
  // skipping any transaction that was reverted or confirmed.
14
- import { withAncestryLock } from "./ancestry.js";
14
+ import { withHistoryLock } from "./ancestry.js";
15
15
  import { AsyncLocalStorage } from 'node:async_hooks';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { dirname, join } from 'node:path';
@@ -91,13 +91,18 @@ function actionIds(service, root) {
91
91
  memos.set(path, { key, ids });
92
92
  return ids;
93
93
  }
94
+ /** A service's projection lock, under its history lock: one directory's locks are taken history, projection,
95
+ * actions, events, in that order (architecture, the history lock). */
96
+ function withProjection(service, root, fn) {
97
+ return withHistoryLock(worldPaths(service, root).dir, () => withFileLock(projectionLock(service, root), fn));
98
+ }
94
99
  function appendActionRaw(action, root) {
95
100
  const path = actionsPath(action.service, root);
96
101
  getActiveWorldStore().mkdir(dirname(path));
97
102
  const memos = idMemoSlot(path);
98
103
  const held = memos.get(path);
99
104
  const current = held !== undefined && held.key === logKey(path);
100
- withAncestryLock(() => appendDurable(path, `${JSON.stringify(action)}\n`));
105
+ withHistoryLock(worldPaths(action.service, root).dir, () => appendDurable(path, `${JSON.stringify(action)}\n`));
101
106
  if (current) {
102
107
  held.ids.add(action.id);
103
108
  held.key = logKey(path);
@@ -144,9 +149,10 @@ function existingExactAction(action, root) {
144
149
  }
145
150
  return existing;
146
151
  }
147
- /** The request-scoped correlation id (D3): set by the kernel fetch adapter for the duration of one
148
- * handler call from the wire's `x-twins-request-id`; read by appendAction as the default. */
149
- // created on first use, never at import: a browser bundle of a mirror client carries this module and has
152
+ /** The request-scoped correlation id (D3): for the duration of one handler call, the id a pack fetch mints for the
153
+ * request (a caller's `x-twins-request-id` is recorded with each write as provenance, never as the group), or, under
154
+ * the kernel fetch adapter, the wire's `x-twins-request-id`; read by appendAction as the default. */
155
+ // created on first use, never at import: a browser bundle carrying the kernel carries this module and has
150
156
  // no AsyncLocalStorage (see serve.ts identitySlot)
151
157
  let correlationStore;
152
158
  const correlationScope = () => (correlationStore ??= new AsyncLocalStorage());
@@ -158,7 +164,7 @@ export function appendAction(action, root) {
158
164
  // compare-and-set, not an advisory validation: checking outside either lock would let another
159
165
  // writer invalidate it before this action lands. The shadow basis is stamped under the same
160
166
  // lock so the recorded merge base is the mirror the preconditions were checked against.
161
- return withFileLock(projectionLock(action.service, root), () => withFileLock(actionsLock(action.service, root), () => {
167
+ return withProjection(action.service, root, () => withFileLock(actionsLock(action.service, root), () => {
162
168
  const stamped = withCorrelationId(action);
163
169
  assertPreconditions(stamped, root);
164
170
  const placeholder = landIfPlaceholderPull(stamped, root);
@@ -174,7 +180,7 @@ export function appendAction(action, root) {
174
180
  * writes both land). A reused id with different content fails loudly. */
175
181
  export function appendActionIfAbsent(action, root) {
176
182
  refuseReadOnlyWrite(action.service);
177
- return withFileLock(projectionLock(action.service, root), () => withFileLock(actionsLock(action.service, root), () => {
183
+ return withProjection(action.service, root, () => withFileLock(actionsLock(action.service, root), () => {
178
184
  const identified = withCorrelationId(action);
179
185
  // An exact retry is already committed. Resolve it before re-evaluating author-time
180
186
  // preconditions against the state that first commit intentionally changed — and
@@ -195,7 +201,7 @@ export function appendActionIfAbsent(action, root) {
195
201
  * Append `build(n)` as an OCCURRENCE: the nth time this exact write has been made.
196
202
  *
197
203
  * A local vendor write is an occurrence, not a replay — two calls are two actions, even
198
- * byte-identical in the same instant (docs/contributing/adding-a-twin.md#5-build-on-the-shared-kernel--dont-reinvent,
204
+ * byte-identical in the same instant (docs/contributing/architecture.md, "The state kernel",
199
205
  * "A local write is an occurrence"). `appendActionIfAbsent` cannot express that: its identity is content, and
200
206
  * content provably cannot separate "the same request delivered twice" from "the same change made
201
207
  * twice". So a caller wanting at-most-once passes an explicit key and uses that function; a caller
@@ -209,7 +215,7 @@ export function appendActionIfAbsent(action, root) {
209
215
  */
210
216
  export function appendActionOccurrence(base, root) {
211
217
  refuseReadOnlyWrite(base.service);
212
- return withFileLock(projectionLock(base.service, root), () => withFileLock(actionsLock(base.service, root), () => {
218
+ return withProjection(base.service, root, () => withFileLock(actionsLock(base.service, root), () => {
213
219
  const stamped = occurrenceOf(base, actionIds(base.service, root), root);
214
220
  assertPreconditions(stamped, root);
215
221
  const placeholder = landIfPlaceholderPull(stamped, root);
@@ -247,7 +253,7 @@ export function occurrenceId(base, ordinal) {
247
253
  * appends before releasing the lock.
248
254
  */
249
255
  export function decideAndAppendAction(service, decide, root) {
250
- return withFileLock(projectionLock(service, root), () => withFileLock(actionsLock(service, root), () => {
256
+ return withProjection(service, root, () => withFileLock(actionsLock(service, root), () => {
251
257
  const resources = projectResources(service, root);
252
258
  const decision = decide(resources);
253
259
  if (decision.kind === 'skip')
@@ -368,8 +374,8 @@ function ownerIndex(owner, root) {
368
374
  return index;
369
375
  }
370
376
  /**
371
- * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, xidentity's tokens
372
- * read by x, googleoauth's by googlecalendar; scripts/architecture.test.ts holds the declared reader-owner pairs): the
377
+ * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, googleoauth's tokens
378
+ * read by webrisk; the reader's manifest declares the pair, `ownerReads`): the
373
379
  * owner's tree, from wherever the World keeps the owner's store (`ownerStoreRoots`). Read only: the rows are frozen and
374
380
  * shared. It claims no journal identity (the twin answering is the reader).
375
381
  *
@@ -424,13 +430,13 @@ export function confirmAction(opts) {
424
430
  ...(opts.additionalObservations ?? []).map((o) => landedCopy(base, receipt, { subject: o.subject, fields: o.fields })),
425
431
  landedCopy(base, receipt, { subject: opts.subject, fields: opts.fields, ...(opts.vendorSubjectId ? { vendorSubjectId: opts.vendorSubjectId } : {}) }),
426
432
  ];
427
- withFileLock(projectionLock(opts.service, opts.root), () => {
428
- withAncestryLock(() => withFileLock(eventsLockPath(paths), () => {
433
+ withProjection(opts.service, opts.root, () => {
434
+ withFileLock(eventsLockPath(paths), () => {
429
435
  const held = landedIds(parentEntries(opts.service, opts.root));
430
436
  for (const copy of copies)
431
437
  if (!held.has(copy.id))
432
438
  appendDurable(paths.events, `${JSON.stringify(copy)}\n`);
433
- }));
439
+ });
434
440
  dropCheckpoint(opts.service, opts.root);
435
441
  });
436
442
  const observedEventIds = copies.map((c) => c.id);
@@ -445,7 +451,7 @@ export function confirmAction(opts) {
445
451
  */
446
452
  export function revertAction(opts) {
447
453
  refuseReadOnlyWrite(opts.service);
448
- return withFileLock(projectionLock(opts.service, opts.root), () => withFileLock(actionsLock(opts.service, opts.root), () => {
454
+ return withProjection(opts.service, opts.root, () => withFileLock(actionsLock(opts.service, opts.root), () => {
449
455
  const all = listActions(opts.service, opts.root);
450
456
  const target = all.find((a) => a.id === opts.actionId);
451
457
  if (target === undefined)
@@ -1,7 +1,18 @@
1
1
  export declare const canonicalStatePath: (path: string) => string;
2
2
  /** Synchronous only; use exclusive acquisition without age-stealing a live owner.
3
- * The coordinator lives outside service, instance and hosted World deletion trees. */
3
+ * The coordinator lives outside service, instance and hosted World deletion trees. It is held only for ancestry
4
+ * bookkeeping (generations, pins, branch pointers, removal receipts) and the not-being-removed check: every World
5
+ * sharing the VOLTER_HOME waits on it, so no append, fold or caller's critical section runs inside it, and no state lock
6
+ * is taken inside it (`withStateLock` takes its lock first). */
4
7
  export declare function withAncestryLock<T>(fn: () => T): T;
8
+ /** Run `fn` holding the cross-process lock at `lockPath`, a lock of the directory it sits in: taken first, then the
9
+ * coordinator only to check that the directory is not being removed. Re-entrant within a process. */
10
+ export declare function withStateLock<T>(lockPath: string, fn: () => T): T;
11
+ /** A state directory's history lock: its appends, captures, observation batches and folds. A branch's is taken before
12
+ * its ancestors'. */
13
+ export declare function withHistoryLock<T>(directory: string, fn: () => T): T;
14
+ /** Several sibling directories' history locks at once (a World's services, a changeset's targets), in one order. */
15
+ export declare function withHistoryLocks<T>(directories: string[], fn: () => T): T;
5
16
  /** Create an identity only for an existing parent. A recreated path gets a new identity. */
6
17
  export declare function stateGeneration(dir: string): string;
7
18
  /** Must be called in the same critical section as child pointer publication. A crash before
@@ -18,5 +29,6 @@ export declare function assertNotBeingRemoved(path: string): void;
18
29
  /** force on scrub bypasses shape checking only, never another branch's ownership. The coordinator is held to decide
19
30
  * and record the removal (`check`, the caller's own precondition, runs there too) and to release it, not through the
20
31
  * deletion: every World on the machine shares the coordinator, and a large tree's deletion outlasts their writes'
21
- * patience. The receipt, written first, keeps the path from being pinned or written while it is deleted. */
32
+ * patience. The receipt, written first, keeps the path from being pinned or locked while it is deleted; the deletion
33
+ * then waits on each lock inside the tree in turn, so a holder that checked before the receipt finishes first. */
22
34
  export declare function withStateRemoval<T>(path: string, remove: () => T, check?: () => void): T;
@@ -10,7 +10,10 @@ export const canonicalStatePath = (path) => getActiveWorldStore().canonicalPath?
10
10
  const metaPath = (dir) => join(dir, 'ancestry.json');
11
11
  const inside = (base, path) => { const rel = relative(canonicalStatePath(base), canonicalStatePath(path)); return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !rel.startsWith(sep)); };
12
12
  /** Synchronous only; use exclusive acquisition without age-stealing a live owner.
13
- * The coordinator lives outside service, instance and hosted World deletion trees. */
13
+ * The coordinator lives outside service, instance and hosted World deletion trees. It is held only for ancestry
14
+ * bookkeeping (generations, pins, branch pointers, removal receipts) and the not-being-removed check: every World
15
+ * sharing the VOLTER_HOME waits on it, so no append, fold or caller's critical section runs inside it, and no state lock
16
+ * is taken inside it (`withStateLock` takes its lock first). */
14
17
  export function withAncestryLock(fn) {
15
18
  const store = getActiveWorldStore();
16
19
  const active = entered ??= new WeakSet();
@@ -28,6 +31,76 @@ export function withAncestryLock(fn) {
28
31
  }
29
32
  }, { retainLiveOwner: true });
30
33
  }
34
+ /** The state locks this process holds, per store: taken again inside one, a lock is already ours. */
35
+ let heldLocks;
36
+ const heldBy = (store) => { const map = heldLocks ??= new WeakMap(); let set = map.get(store); if (!set) {
37
+ set = new Set();
38
+ map.set(store, set);
39
+ } return set; };
40
+ /** Run `fn` holding the cross-process lock at `lockPath`, a lock of the directory it sits in: taken first, then the
41
+ * coordinator only to check that the directory is not being removed. Re-entrant within a process. */
42
+ export function withStateLock(lockPath, fn) {
43
+ const store = getActiveWorldStore();
44
+ const held = heldBy(store);
45
+ if (held.has(lockPath))
46
+ return fn();
47
+ if (entered?.has(store))
48
+ throw new Error(`State lock ${lockPath} taken inside the ancestry coordinator: a state lock is taken before it, never inside it`);
49
+ return store.withLock(lockPath, () => {
50
+ withAncestryLock(() => assertNotBeingRemoved(dirname(lockPath)));
51
+ held.add(lockPath);
52
+ try {
53
+ return fn();
54
+ }
55
+ finally {
56
+ held.delete(lockPath);
57
+ }
58
+ });
59
+ }
60
+ /** A state directory's history lock: its appends, captures, observation batches and folds. A branch's is taken before
61
+ * its ancestors'. */
62
+ export function withHistoryLock(directory, fn) {
63
+ return withStateLock(join(canonicalStatePath(directory), 'history.lock'), fn);
64
+ }
65
+ /** A lock file a holder has recorded itself in (`{pid, hostname}`, written before the holder runs anything): a file
66
+ * that only ends in `.lock` (a yarn.lock) is not one. */
67
+ function isHeldLock(text) {
68
+ try {
69
+ const holder = JSON.parse(text ?? '');
70
+ return Number.isInteger(holder.pid);
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /** Several sibling directories' history locks at once (a World's services, a changeset's targets), in one order. */
77
+ export function withHistoryLocks(directories, fn) {
78
+ const sorted = [...new Set(directories.map((d) => canonicalStatePath(d)))].sort();
79
+ const take = (i) => (i === sorted.length ? fn() : withHistoryLock(sorted[i], () => take(i + 1)));
80
+ return take(0);
81
+ }
82
+ /** The locks inside a tree a removal waits on before deleting it. */
83
+ function locksUnder(path) {
84
+ const store = getActiveWorldStore();
85
+ const out = [];
86
+ const walk = (dir) => {
87
+ for (const name of store.list(dir)) {
88
+ if (name === 'node_modules' || name === '.git')
89
+ continue;
90
+ const child = join(dir, name);
91
+ const stat = store.stat(child);
92
+ if (!stat || stat.isSymbolicLink)
93
+ continue;
94
+ if (stat.isDirectory)
95
+ walk(child);
96
+ else if (name.endsWith('.lock') && isHeldLock(store.read(child)))
97
+ out.push(child);
98
+ }
99
+ };
100
+ if (store.stat(path)?.isDirectory)
101
+ walk(path);
102
+ return out.sort();
103
+ }
31
104
  function metadata(dir) {
32
105
  const raw = getActiveWorldStore().read(metaPath(dir));
33
106
  if (raw === null)
@@ -209,7 +282,8 @@ export function assertNotBeingRemoved(path) {
209
282
  /** force on scrub bypasses shape checking only, never another branch's ownership. The coordinator is held to decide
210
283
  * and record the removal (`check`, the caller's own precondition, runs there too) and to release it, not through the
211
284
  * deletion: every World on the machine shares the coordinator, and a large tree's deletion outlasts their writes'
212
- * patience. The receipt, written first, keeps the path from being pinned or written while it is deleted. */
285
+ * patience. The receipt, written first, keeps the path from being pinned or locked while it is deleted; the deletion
286
+ * then waits on each lock inside the tree in turn, so a holder that checked before the receipt finishes first. */
213
287
  export function withStateRemoval(path, remove, check) {
214
288
  const store = getActiveWorldStore();
215
289
  const { removal, receipt } = withAncestryLock(() => {
@@ -225,6 +299,22 @@ export function withStateRemoval(path, remove, check) {
225
299
  beingRemoved = [...beingRemoved, removal.path];
226
300
  return { removal, receipt };
227
301
  });
302
+ // Each lock is waited on alone, taken and let go: a holder that checked before the receipt finishes, and a taker
303
+ // after it refuses once it holds the lock (withStateLock), so the removal never holds one lock while waiting on
304
+ // another and no lock order can deadlock it. A wait that fails leaves nothing deleted: the receipt is released.
305
+ const held = heldBy(store);
306
+ try {
307
+ for (const lock of locksUnder(path).filter((l) => !held.has(l)))
308
+ store.withLock(lock, () => undefined);
309
+ }
310
+ catch (error) {
311
+ withAncestryLock(() => {
312
+ if (store.exists(receipt))
313
+ store.remove(receipt);
314
+ beingRemoved = beingRemoved.filter((p) => p !== removal.path);
315
+ });
316
+ throw error;
317
+ }
228
318
  const result = remove();
229
319
  withAncestryLock(() => {
230
320
  if (store.exists(path))
@@ -0,0 +1,39 @@
1
+ import { type WireRequest, type WireTurn } from './openai-wire.js';
2
+ type Row = Record<string, unknown>;
3
+ /** A message's content as text: a string, or its text blocks (and the text of its tool results) joined. */
4
+ export declare const messagesText: (content: unknown) => string;
5
+ /** The last user message's text. */
6
+ export declare function messagesLastUserText(messages: Row[]): string;
7
+ /** The tool results the last message carries, by the names of the tool_use blocks they answer. */
8
+ export declare function messagesToolResults(messages: Row[]): string[];
9
+ /** A Messages request as a scenario reads it (the OpenAI wire's `WireRequest`): its model, its system and messages'
10
+ * text, its last user text, the tools it offers and the tool results it ends with. */
11
+ export declare function messagesWireRequest(body: Row): WireRequest;
12
+ /** The labeled stub for a Messages request: with tools offered (`tool_choice` not `none`) and the last message not a
13
+ * tool's result, it calls the tool the choice names, else the first, with input its schema admits; with a JSON schema
14
+ * for the output, it answers a value the schema admits; otherwise it echoes the last user message. `label` names the
15
+ * twin. */
16
+ export declare function messagesStubTurn(body: Row, label: string): WireTurn;
17
+ export type MessageShape = {
18
+ id: string;
19
+ model: string;
20
+ turn: WireTurn;
21
+ body: Row;
22
+ /** each tool_use block's id, and the thinking block's signature */
23
+ toolId: (i: number) => string;
24
+ signature?: (thinking: string) => string;
25
+ /** usage fields the vendor adds (service_tier, the prompt cache's counts) */
26
+ usage?: Row;
27
+ };
28
+ /** A message (`type: message`): a thinking block when the request turns thinking on, then the
29
+ * text, then the tool_use blocks; its stop reason, and its usage estimated from the request and the answer. A text
30
+ * longer than `max_tokens` is cut there and stops `max_tokens`. */
31
+ export declare function message(s: MessageShape): Row;
32
+ /** A message as the stream's events: message_start (its content empty), per block its start, deltas and stop (a ping
33
+ * after the first start), message_delta with the stop reason and the output usage, message_stop. `extra` adds fields
34
+ * a vendor puts on the two closing events (Merge's `routing`). */
35
+ export declare function messageSSE(m: Row, extra?: Row, options?: {
36
+ ping?: boolean;
37
+ completeToolInput?: boolean;
38
+ }): string;
39
+ export {};