@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
package/inject.cjs CHANGED
@@ -146,32 +146,61 @@ function installPrismaAdapterShim() {
146
146
  if (candidates.length === 0) return;
147
147
  Module.__volterPrismaAdapterShimInstalled = true;
148
148
  const path = require('path');
149
- const originalLoad = Module._load;
150
- Module._load = function volterPrismaLoad(request, parent, isMain) {
151
- const exports = originalLoad.apply(this, arguments);
152
- if (request !== '@prisma/client' || !exports || typeof exports.PrismaClient !== 'function' || exports.PrismaClient.__volterAdapted) return exports;
149
+ // The package's own entry (its `main`, `default.js`; `index.js` before Prisma 5), patched as it loads (PACKAGE ENTRIES).
150
+ patchPackageEntry(/[\\/]node_modules[\\/]@prisma[\\/]client[\\/](default|index)\.js$/, (module) => {
151
+ const exports = module.exports;
152
+ if (!exports || typeof exports.PrismaClient !== 'function' || exports.PrismaClient.__volterAdapted) return;
153
153
  const vendor = candidates.find((candidate) => MAP[candidate] && process.env[packs[candidate].prismaAdapter.urlEnv]);
154
- if (!vendor) return exports;
154
+ if (!vendor) return;
155
155
  const facts = packs[vendor].prismaAdapter;
156
- // From the requiring module first (a library that constructs the client), then the
157
- // application itself, whose dependency the adapter is under pnpm's isolated layout.
158
- const from = parent && parent.filename ? path.dirname(parent.filename) : process.cwd();
159
- let adapterModule;
160
- try {
161
- adapterModule = originalLoad.call(this, require.resolve(facts.adapter, { paths: [from, process.cwd()] }), parent, false);
162
- } catch {
156
+ // THE ADAPTER IS FOUND WHERE THE CLIENT IS USED, AT ITS FIRST CONSTRUCTION WITHOUT ONE (review of the World 2209
157
+ // integration, 75f6a1dd). The entry's own parent is the first module that loaded it. For a copy cached before this
158
+ // prelude ran, that is not the library that later constructs the client, and an early miss there was final.
159
+ // At each construction without an adapter, until one is found, these places are asked in turn:
160
+ // - the directory of each CommonJS module that lists the entry among its children, most recently loaded first, by
161
+ // Module._cache's insertion order, with the entry's module.parent last of them. Node's updateChildren records a
162
+ // cached require too, so a consumer that requires the client later is found at the next construction;
163
+ // - the main module's directory and that of process.argv[1];
164
+ // - the working directory.
165
+ // An ES module that imports the client is never its parent or one of the cached modules that list it, so an ESM
166
+ // consumer is reached only through those last places. A found adapter is kept, and said once if a miss was said
167
+ // before it. A place that missed is not asked again (`missedPlaces`): a new consumer brings a new place, and no
168
+ // resolve is repeated. A miss is said once, and the construction is left as it was. Module._cache is read at each
169
+ // construction without an adapter until one is found, a walk of the cache's entries and no file system work.
170
+ let Adapter;
171
+ let missSaid = false;
172
+ const missedPlaces = new Set();
173
+ const adapterFor = () => {
174
+ if (Adapter) return Adapter;
175
+ const consumers = [module.parent, ...Object.values(Module._cache).filter((other) => other && other !== module.parent && Array.isArray(other.children) && other.children.includes(module))]
176
+ .filter((consumer) => consumer && consumer.filename).reverse();
177
+ // Each place with the module it is asked for, so the adapter loads as that module's child (Node's bookkeeping
178
+ // of parent and children only; resolution is by `paths`).
179
+ const places = new Map();
180
+ for (const consumer of consumers) if (!places.has(path.dirname(consumer.filename))) places.set(path.dirname(consumer.filename), consumer);
181
+ if (require.main && require.main.filename && !places.has(path.dirname(require.main.filename))) places.set(path.dirname(require.main.filename), require.main);
182
+ if (typeof process.argv[1] === 'string' && process.argv[1] && !places.has(path.dirname(path.resolve(process.argv[1])))) places.set(path.dirname(path.resolve(process.argv[1])), null);
183
+ if (!places.has(process.cwd())) places.set(process.cwd(), null);
184
+ let reason = `${facts.adapter} is not installed in this application`;
185
+ for (const [from, consumer] of places) {
186
+ if (missedPlaces.has(from)) continue;
187
+ let loaded;
188
+ try { loaded = Module._load(require.resolve(facts.adapter, { paths: [from] }), consumer, false); }
189
+ catch { missedPlaces.add(from); continue; }
190
+ const found = loaded && loaded[facts.export];
191
+ if (typeof found === 'function') {
192
+ Adapter = found;
193
+ if (missSaid) process.stderr.write(`[twin-inject] ${vendor}: ${facts.adapter} found from ${from}; a Prisma client constructed without an adapter from now on gets it\n`);
194
+ return Adapter;
195
+ }
196
+ missedPlaces.add(from);
197
+ reason = `${facts.adapter} exports no ${facts.export}`;
198
+ }
163
199
  // Left as it was, said once: on the client engine Prisma refuses the construction itself;
164
200
  // on the library engine the client reaches the twin over the vendor's own wire.
165
- process.stderr.write(`[twin-inject] ${vendor}: ${facts.adapter} is not installed in this application, so a Prisma client constructed without an adapter is left as it was\n`);
166
- exports.PrismaClient.__volterAdapted = true;
167
- return exports;
168
- }
169
- const Adapter = adapterModule && adapterModule[facts.export];
170
- if (typeof Adapter !== 'function') {
171
- process.stderr.write(`[twin-inject] ${vendor}: ${facts.adapter} exports no ${facts.export}; a Prisma client constructed without an adapter is left as it was\n`);
172
- exports.PrismaClient.__volterAdapted = true;
173
- return exports;
174
- }
201
+ if (!missSaid) { missSaid = true; process.stderr.write(`[twin-inject] ${vendor}: ${reason}, so a Prisma client constructed without an adapter is left as it was until it is found\n`); }
202
+ return undefined;
203
+ };
175
204
  const Base = exports.PrismaClient;
176
205
  class PrismaClient extends Base {
177
206
  constructor(options) {
@@ -180,16 +209,18 @@ function installPrismaAdapterShim() {
180
209
  // decision. The URL is the application's where it names one (its host is twinned by the
181
210
  // injector's own host rules), else the endpoint template's variable.
182
211
  if ('adapter' in given) { super(given); return; }
212
+ const Found = adapterFor();
213
+ // Left as it was: Prisma is given exactly what the application passed (`new PrismaClient()` is no options).
214
+ if (!Found) { super(...arguments); return; }
183
215
  const named = given.datasources && typeof given.datasources === 'object'
184
216
  ? Object.values(given.datasources).map((source) => source && source.url).find((value) => typeof value === 'string') : undefined;
185
217
  const url = given.datasourceUrl || named || process.env[facts.urlEnv];
186
- super({ ...given, adapter: new Adapter({ url, fetch: (...args) => globalThis.fetch(...args) }) });
218
+ super({ ...given, adapter: new Found({ url, fetch: (...args) => globalThis.fetch(...args) }) });
187
219
  }
188
220
  }
189
221
  PrismaClient.__volterAdapted = true;
190
222
  exports.PrismaClient = PrismaClient;
191
- return exports;
192
- };
223
+ });
193
224
  }
194
225
 
195
226
  // The exact inverse of readMap's env lookup: which vendor (if any) would the injector read
@@ -647,50 +678,77 @@ function routedTwin(url) {
647
678
  }
648
679
  // Imported Undici APIs may hold a dispatcher obtained before the global view was installed.
649
680
  // Rewrite their public HTTP doors as well; an independent connector still faces the socket guards.
650
- let UNDICI_LOAD = null;
681
+ // PACKAGE ENTRIES, TAKEN WHEN THEY LOAD (S2): the preludes that act on one package (undici, @prisma/client, miniflare)
682
+ // wrapped Module._load and ran on every load of the process to compare one name (LibreChat's server: 20,228 loads,
683
+ // ~20 ms in the tab, none of it in native's path), and a wrapped Module._load kept the tab's engine on its slower
684
+ // replaced-load route for every require. Each is now a patch of the package's entry module, made when that file is
685
+ // loaded (Module._extensions['.js'], once per new module): the entry is loaded once per copy (every version, every
686
+ // root, late or early) and cached, so the exports the patch leaves are what every later require of that copy is
687
+ // answered, and an `import` of it loads it the same way. A copy loaded before this prelude ran is patched where it is
688
+ // cached: its exports object is patched in place, so later requires and imports, and a module that holds that exports
689
+ // object, see the patch; only a binding taken from it before the prelude ran (a destructured PrismaClient, an ES
690
+ // module's namespace already linked) keeps the original. Known limits: a '.js' handler installed later that does not call the one it replaced hides the entries
691
+ // loaded through it; an entry outside a node_modules directory (a linked checkout of the package) is not recognized.
692
+ const ENTRY_PATCHES = [];
693
+ let ENTRY_LOAD = null;
694
+ function patchPackageEntry(pattern, patch) {
695
+ ENTRY_PATCHES.push({ pattern, patch });
696
+ for (const [filename, module] of Object.entries(Module._cache)) {
697
+ if (pattern.test(filename) && module && module.loaded) patch(module, filename);
698
+ }
699
+ if (ENTRY_LOAD) return;
700
+ const original = ENTRY_LOAD = Module._extensions['.js'];
701
+ Module._extensions['.js'] = function volterPackageEntry(module, filename) {
702
+ const result = original.apply(this, arguments);
703
+ for (const entry of ENTRY_PATCHES) if (entry.pattern.test(filename)) entry.patch(module, filename);
704
+ return result;
705
+ };
706
+ }
707
+
708
+ let UNDICI_ROUTED = false;
651
709
  function routeImportedUndici() {
652
- if (UNDICI_LOAD) return;
653
- const original = UNDICI_LOAD = Module._load;
654
- const seen = new WeakMap();
655
- Module._load = function volterUndiciLoad(request, parent, isMain) {
656
- const exports = original.apply(this, arguments);
657
- if (request !== 'undici' || !exports || typeof exports.request !== 'function') return exports;
658
- if (seen.has(exports)) return seen.get(exports);
659
- const view = Object.assign({}, exports);
660
- view.fetch = function routedUndiciFetch(input, init) {
661
- if (!installed) return exports.fetch(input, init);
662
- let url; try { url = new URL(typeof input === 'object' && input?.url ? input.url : String(input)); } catch {}
663
- const twin = routedTwin(url);
664
- // Local control planes may use their own Request class and dispatcher. Preserve both;
665
- // connections still pass the socket guards, and claimed vendor hops still enter routing.
666
- if (url && isLocalOrPrivateHost(url.hostname) && (!twin || twin.vendor === 'app')) return exports.fetch(input, init);
667
- if (input && typeof input === 'object' && typeof input.url === 'string' && !(input instanceof globalThis.Request)) {
668
- const options = {};
669
- for (const name of ['method', 'signal', 'redirect', 'cache', 'credentials', 'integrity', 'keepalive', 'referrer', 'referrerPolicy']) {
670
- if (input[name] !== undefined) options[name] = input[name];
671
- }
672
- options.headers = Array.from(input.headers);
673
- if (input.body !== null && input.body !== undefined) { options.body = input.body; options.duplex = 'half'; }
674
- return globalThis.fetch(input.url, { ...options, ...init });
710
+ if (UNDICI_ROUTED) return;
711
+ UNDICI_ROUTED = true;
712
+ patchPackageEntry(/[\\/]node_modules[\\/]undici[\\/]index\.js$/, (module) => {
713
+ if (module.exports && typeof module.exports.request === 'function' && !ROUTED_UNDICI.has(module.exports)) module.exports = routedUndici(module.exports);
714
+ });
715
+ }
716
+ const ROUTED_UNDICI = new WeakSet();
717
+ function routedUndici(exports) {
718
+ const view = Object.assign({}, exports);
719
+ view.fetch = function routedUndiciFetch(input, init) {
720
+ if (!installed) return exports.fetch(input, init);
721
+ let url; try { url = new URL(typeof input === 'object' && input?.url ? input.url : String(input)); } catch {}
722
+ const twin = routedTwin(url);
723
+ // Local control planes may use their own Request class and dispatcher. Preserve both;
724
+ // connections still pass the socket guards, and claimed vendor hops still enter routing.
725
+ if (url && isLocalOrPrivateHost(url.hostname) && (!twin || twin.vendor === 'app')) return exports.fetch(input, init);
726
+ if (input && typeof input === 'object' && typeof input.url === 'string' && !(input instanceof globalThis.Request)) {
727
+ const options = {};
728
+ for (const name of ['method', 'signal', 'redirect', 'cache', 'credentials', 'integrity', 'keepalive', 'referrer', 'referrerPolicy']) {
729
+ if (input[name] !== undefined) options[name] = input[name];
675
730
  }
676
- return globalThis.fetch(input, init);
677
- };
678
- view.request = function routedUndiciRequest(input, options, callback) {
679
- if (!installed) return exports.request.apply(exports, arguments);
680
- const opts = typeof options === 'function' ? {} : options ?? {};
681
- let url; try { url = new URL(String(input)); if (opts.path !== undefined) url = new URL(url.origin + String(opts.path)); } catch {}
682
- const twin = routedTwin(url);
683
- if (!twin || twin.vendor === 'app') return exports.request.apply(exports, arguments);
684
- const headers = undiciHeaderObject(opts.headers);
685
- headers.host = url.host; headers['x-volter-twin-original-host'] = url.host;
686
- const key = twinsKey(); if (key) headers['x-twins-key'] = key;
687
- const target = twin.origin.replace(/\/$/, '') + url.pathname + url.search;
688
- const { path: _path, ...requestOptions } = opts; // target already carries the effective path and endpoint prefix
689
- return exports.request(target, { ...requestOptions, headers, maxRedirections: 0 }, typeof options === 'function' ? options : callback);
690
- };
691
- seen.set(exports, view);
692
- return view;
731
+ options.headers = Array.from(input.headers);
732
+ if (input.body !== null && input.body !== undefined) { options.body = input.body; options.duplex = 'half'; }
733
+ return globalThis.fetch(input.url, { ...options, ...init });
734
+ }
735
+ return globalThis.fetch(input, init);
736
+ };
737
+ view.request = function routedUndiciRequest(input, options, callback) {
738
+ if (!installed) return exports.request.apply(exports, arguments);
739
+ const opts = typeof options === 'function' ? {} : options ?? {};
740
+ let url; try { url = new URL(String(input)); if (opts.path !== undefined) url = new URL(url.origin + String(opts.path)); } catch {}
741
+ const twin = routedTwin(url);
742
+ if (!twin || twin.vendor === 'app') return exports.request.apply(exports, arguments);
743
+ const headers = undiciHeaderObject(opts.headers);
744
+ headers.host = url.host; headers['x-volter-twin-original-host'] = url.host;
745
+ const key = twinsKey(); if (key) headers['x-twins-key'] = key;
746
+ const target = twin.origin.replace(/\/$/, '') + url.pathname + url.search;
747
+ const { path: _path, ...requestOptions } = opts; // target already carries the effective path and endpoint prefix
748
+ return exports.request(target, { ...requestOptions, headers, maxRedirections: 0 }, typeof options === 'function' ? options : callback);
693
749
  };
750
+ ROUTED_UNDICI.add(view);
751
+ return view;
694
752
  }
695
753
  function unkeyUndiciDispatches() {
696
754
  if (!ORIGINAL_DEFINE_PROPERTY) return;
@@ -1712,12 +1770,25 @@ function installWorldTime() {
1712
1770
  const worldMs = () => { const c = clock(); return c ? clockNowMs(c, machineNow()) : machineNow(); };
1713
1771
  // the machine's own Date, wrapped rather than subclassed, so identity holds everywhere: dates Node makes itself
1714
1772
  // (a file's mtime, structuredClone, a worker's message) are `instanceof Date`, `d.constructor === Date` stays true,
1715
- // and Date.prototype is the machine's; only now() and argument-less construction read the World's time
1716
- const WorldDate = new Proxy(MachineDate, {
1717
- construct: (target, args, newTarget) => Reflect.construct(target, args.length ? args : [worldMs()], newTarget === WorldDate ? target : newTarget),
1773
+ // and Date.prototype is the machine's; only now() and argument-less construction read the World's time. A bound
1774
+ // function, not a Proxy: every `new Date(x)` of the app (a driver decoding dates) ran the Proxy's construct trap,
1775
+ // ~15 ms of self time on LibreChat's server start in the tab (S2). Bound, so Function.prototype.toString of Date
1776
+ // says "function () { [native code] }", as the Proxy's did; a bound function constructs its target with the target
1777
+ // as new.target, and a subclass's own new.target passes through. Its prototype is the machine's; its own keys are
1778
+ // the machine's Date's (length, name, prototype, now, parse, UTC), `now` the World's.
1779
+ const worldDate = function Date(...args) {
1718
1780
  // called as a function, Date() answers the current time's string, as the machine's does
1719
- apply: () => new MachineDate(worldMs()).toString(),
1720
- });
1781
+ if (new.target === undefined) return new MachineDate(worldMs()).toString();
1782
+ return Reflect.construct(MachineDate, args.length ? args : [worldMs()], new.target === worldDate ? MachineDate : new.target);
1783
+ };
1784
+ worldDate.prototype = MachineDate.prototype;
1785
+ const WorldDate = worldDate.bind(undefined);
1786
+ Object.defineProperty(WorldDate, 'name', { value: 'Date', configurable: true });
1787
+ Object.defineProperty(WorldDate, 'length', { value: MachineDate.length, configurable: true });
1788
+ Object.defineProperty(WorldDate, 'prototype', { value: MachineDate.prototype });
1789
+ for (const [key, value] of [['now', worldMs], ['parse', MachineDate.parse], ['UTC', MachineDate.UTC]]) {
1790
+ Object.defineProperty(WorldDate, key, { value, writable: true, configurable: true, enumerable: false });
1791
+ }
1721
1792
  Object.defineProperty(globalThis, Symbol.for('volter.machineDateNow'), { value: machineNow, enumerable: false });
1722
1793
  MachineDate.now = worldMs;
1723
1794
  Object.defineProperty(MachineDate.prototype, 'constructor', { value: WorldDate, writable: true, configurable: true, enumerable: false });
@@ -1780,17 +1851,12 @@ function installMiniflareOutbound() {
1780
1851
  };
1781
1852
  return Array.isArray(options.workers) ? { ...options, workers: options.workers.map(worker) } : worker(options);
1782
1853
  };
1783
- const originalLoad = Module._load;
1784
- const seen = new WeakMap();
1785
- Module._load = function volterMiniflareLoad(request, parent) {
1786
- const exports = originalLoad.apply(this, arguments);
1787
- if (request !== 'miniflare' || !exports || typeof exports.Miniflare !== 'function' || exports.Miniflare.__volterRouted) return exports;
1788
- if (seen.has(exports)) return seen.get(exports);
1789
- if (!outbound) {
1790
- let at = null;
1791
- try { at = Module._resolveFilename(request, parent); } catch { at = null; }
1792
- outbound = outboundFor(at, exports.fetch);
1793
- }
1854
+ // The package's own entry (miniflare's `main`, dist/src/index.js), patched as it loads (PACKAGE ENTRIES).
1855
+ patchPackageEntry(/[\\/]node_modules[\\/]miniflare[\\/]dist[\\/]src[\\/]index\.js$/, (module, filename) => {
1856
+ const exports = module.exports;
1857
+ if (!exports || typeof exports.Miniflare !== 'function' || exports.Miniflare.__volterRouted) return;
1858
+ // the entry is the file `require('miniflare')` resolved to: its undici is the one beside it
1859
+ if (!outbound) outbound = outboundFor(filename, exports.fetch);
1794
1860
  const Original = exports.Miniflare;
1795
1861
  class Miniflare extends Original {
1796
1862
  constructor(options) { super(routed(options)); }
@@ -1799,10 +1865,8 @@ function installMiniflareOutbound() {
1799
1865
  Miniflare.__volterRouted = true;
1800
1866
  // miniflare's build exports `Miniflare` as a getter that can be neither assigned nor redefined: its importer is given
1801
1867
  // a view of the module whose `Miniflare` is this one, the same view each time it is required.
1802
- const view = new Proxy(exports, { get: (target, key, receiver) => (key === 'Miniflare' ? Miniflare : Reflect.get(target, key, receiver)) });
1803
- seen.set(exports, view);
1804
- return view;
1805
- };
1868
+ module.exports = new Proxy(exports, { get: (target, key, receiver) => (key === 'Miniflare' ? Miniflare : Reflect.get(target, key, receiver)) });
1869
+ });
1806
1870
  }
1807
1871
 
1808
1872
  // Auto-install as a preload (`--require`); strict refusal also applies without twin URLs.
@@ -140,6 +140,16 @@ function allowsWorldNetworkEgress(policy, value, method = 'GET') {
140
140
  return granted(policy.egress, to) || (policy.phase === 'build' && policy.publicReads === true && PUBLIC_BUILD_READS.includes(to.url.origin));
141
141
  }
142
142
 
143
+ /** Whether the config grants this request by name (`egress` for a read, `writes` for any method): the grant that takes a
144
+ * build World's request past a twinned host's twin to its real origin. The unlisted public build reads are not one:
145
+ * on a twinned host the twin answers them (architecture, "Reads are granted generously, writes narrowly"). */
146
+ function explicitlyGrantsWorldNetworkEgress(policy, value, method = 'GET') {
147
+ const to = destination(value, true);
148
+ if (!to) return false;
149
+ if (granted(policy.writes ?? [], to)) return true;
150
+ return READ_METHODS.includes(String(method).toUpperCase()) && granted(policy.egress, to);
151
+ }
152
+
143
153
  function worldNetworkUsesPassthrough(policy, value) {
144
154
  const to = destination(value, true);
145
155
  if (!to || policy.phase !== 'build') return false;
@@ -222,4 +232,4 @@ function worldEgressRefusal(value, env = process.env, method = 'GET') {
222
232
  return answer.outcome === 'refuse' ? `${answer.reason} (${refusalSide(answer)})` : null;
223
233
  }
224
234
 
225
- module.exports = { worldAttachmentNetworkEnv, worldStrictEgress, worldGrantFromEnv, PUBLIC_BUILD_READS, READ_METHODS, intersectWorldNetwork, worldNetworkNeedsPathInspection, worldNetworkUsesPassthrough, isWorldInternalHost, worldEgressRefusal, WORLD_NETWORK_POLICY_ENV, normalizeWorldNetwork, createWorldNetworkPolicy, parseWorldNetworkPolicy, worldNetworkPolicyFromEnv, allowsWorldNetworkEgress };
235
+ module.exports = { worldAttachmentNetworkEnv, worldStrictEgress, worldGrantFromEnv, PUBLIC_BUILD_READS, READ_METHODS, intersectWorldNetwork, worldNetworkNeedsPathInspection, worldNetworkUsesPassthrough, isWorldInternalHost, worldEgressRefusal, WORLD_NETWORK_POLICY_ENV, normalizeWorldNetwork, createWorldNetworkPolicy, parseWorldNetworkPolicy, worldNetworkPolicyFromEnv, allowsWorldNetworkEgress, explicitlyGrantsWorldNetworkEgress };
@@ -10,6 +10,7 @@ export function worldNetworkPolicyFromEnv(env: Readonly<Record<string, string |
10
10
  export const READ_METHODS: readonly string[];
11
11
  export const PUBLIC_BUILD_READS: readonly string[];
12
12
  export function allowsWorldNetworkEgress(policy: WorldNetworkPolicy, value: string | URL, method?: string): boolean;
13
+ export function explicitlyGrantsWorldNetworkEgress(policy: WorldNetworkPolicy, value: string | URL, method?: string): boolean;
13
14
  export function isWorldInternalHost(hostname: string): boolean;
14
15
  export function worldEgressRefusal(value: string | URL, env?: Readonly<Record<string, string | undefined>>, method?: string): string | null;
15
16
  export function worldNetworkNeedsPathInspection(policy: WorldNetworkPolicy, value: string | URL): boolean;
@@ -0,0 +1,3 @@
1
+ export type PackFacts = { packs: Record<string, Record<string, unknown>>; installed: string[]; [key: string]: unknown };
2
+ export function packFacts(): PackFacts;
3
+ export function extendPackFacts(files: string[] | undefined): PackFacts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.155",
3
+ "version": "3.0.157",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
@@ -25,6 +25,8 @@
25
25
  "attach.cjs",
26
26
  "stream-bridge.cjs",
27
27
  "http-bridge.cjs",
28
+ "required-credential-transport.cjs",
29
+ "required-credential-transport.d.cts",
28
30
  "network-policy.cjs",
29
31
  "network-policy.d.cts",
30
32
  "capability-decision.cjs",
@@ -34,6 +36,7 @@
34
36
  "host-routing.cjs",
35
37
  "host-routing.d.cts",
36
38
  "pack-facts.cjs",
39
+ "pack-facts.d.cts",
37
40
  "vendor-hosts.d.cts",
38
41
  "world-clock.cjs",
39
42
  "world-clock.d.cts",
@@ -89,6 +92,10 @@
89
92
  "./attach": "./attach.cjs",
90
93
  "./stream-bridge": "./stream-bridge.cjs",
91
94
  "./http-bridge": "./http-bridge.cjs",
95
+ "./required-credential-transport": {
96
+ "types": "./required-credential-transport.d.cts",
97
+ "default": "./required-credential-transport.cjs"
98
+ },
92
99
  "./network-policy": {
93
100
  "types": "./network-policy.d.cts",
94
101
  "default": "./network-policy.cjs"
@@ -128,9 +135,25 @@
128
135
  "types": "./dist/src/live/index.d.ts",
129
136
  "default": "./dist/src/live/index.js"
130
137
  },
138
+ "./mongodb": {
139
+ "types": "./dist/src/mongodb/index.d.ts",
140
+ "default": "./dist/src/mongodb/index.js"
141
+ },
131
142
  "./clickhouse": {
132
143
  "types": "./dist/src/clickhouse/index.d.ts",
133
144
  "default": "./dist/src/clickhouse/index.js"
145
+ },
146
+ "./runtime-state": {
147
+ "types": "./dist/src/runtime-state.d.ts",
148
+ "default": "./dist/src/runtime-state.js"
149
+ },
150
+ "./browser-state": {
151
+ "types": "./dist/src/browser-state.d.ts",
152
+ "default": "./dist/src/browser-state.js"
153
+ },
154
+ "./managed-original-inputs": {
155
+ "types": "./dist/src/managed-original-inputs.d.ts",
156
+ "default": "./dist/src/managed-original-inputs.js"
134
157
  }
135
158
  },
136
159
  "scripts": {
@@ -156,5 +179,8 @@
156
179
  },
157
180
  "engines": {
158
181
  "node": ">=22.3"
182
+ },
183
+ "optionalDependencies": {
184
+ "@xmldom/xmldom": "0.9.12"
159
185
  }
160
186
  }
@@ -0,0 +1,29 @@
1
+ // Metadata-only admission for native machine backings. Imports start no process and read no file.
2
+ 'use strict';
3
+
4
+ exports.refuseRequiredMachineTransport = function refuseRequiredMachineTransport(source, transport) {
5
+ const path = source.VOLTER_WORLD_INSTANCE;
6
+ if (!path) {
7
+ if (source.VOLTER_WORLD) throw new Error('World machine execution has no retained credential declaration');
8
+ return; // No registered World declaration is asserted by the standalone provider.
9
+ }
10
+ const instance = JSON.parse(require('node:fs').readFileSync(path, 'utf8'));
11
+ if (!instance || typeof instance !== 'object' || Array.isArray(instance)
12
+ || !instance.env || instance.env.VOLTER_WORLD_INSTANCE !== path || instance.env.VOLTER_WORLD !== source.VOLTER_WORLD
13
+ || !Object.hasOwn(instance, 'requiredCredentials')) throw new Error('World machine boot lacks its own retained credential declaration');
14
+ const record = instance.requiredCredentials;
15
+ if (!record || typeof record !== 'object' || Array.isArray(record)
16
+ || Object.keys(record).some(key => !['version', 'owners', 'valuesSha256'].includes(key))
17
+ || !Object.hasOwn(record, 'version') || record.version !== 1 || !Object.hasOwn(record, 'owners')
18
+ || !record.owners || typeof record.owners !== 'object' || Array.isArray(record.owners))
19
+ throw new Error('World machine credential declaration has an unsupported version or shape');
20
+ if (Object.keys(record.owners).length) {
21
+ const error = new Error(`${transport} does not carry required credential bindings`);
22
+ error.code = 'WORLD_MACHINE_CAPABILITY_GAP';
23
+ throw error;
24
+ }
25
+ if (Object.hasOwn(record, 'valuesSha256')) {
26
+ const empty = require('node:crypto').createHash('sha256').update('[]').digest('hex');
27
+ if (record.valuesSha256 !== empty) throw new Error('World machine empty credential declaration changed');
28
+ }
29
+ };
@@ -0,0 +1,3 @@
1
+ /** Refuse a registered World's required declarations before a backing with no credential transport acts.
2
+ * Reads only the actual instance named by the provider's own environment; standalone providers assert no World proof. */
3
+ export declare function refuseRequiredMachineTransport(source: Readonly<Record<string, string | undefined>>, transport: string): void;
package/src/actions.ts CHANGED
@@ -12,7 +12,7 @@ import { parseExactJson } from './exact-json.ts';
12
12
  //
13
13
  // Projection = observed mirror, then apply each `set` transaction in order,
14
14
  // skipping any transaction that was reverted or confirmed.
15
- import { withHistoryLock } from './ancestry.ts';
15
+ import { canonicalStatePath, withHistoryLock } from './ancestry.ts';
16
16
  import { AsyncLocalStorage } from 'node:async_hooks';
17
17
  import { randomUUID } from 'node:crypto';
18
18
  import { dirname, join } from 'node:path';
@@ -20,10 +20,10 @@ import { canonicalJson, hashFieldValue, subjectKey } from './hash.ts';
20
20
  import type { SubjectFields } from './hash.ts';
21
21
  import { appendDurable, eventsLockPath, ownerStoreRoots, projectionLockPath, twinLog, withFileLock, withoutIdentityClaim, worldPaths } from './storage.ts';
22
22
  import { landAsPlaceholder, placeholderPullActive } from './placeholder-remote.ts';
23
- import { aliasesFrom, dropCheckpoint, landedCopy, landedIds, parentEntries, readTree, treeStamp, type Receipt } from './log.ts';
23
+ import { aliasesFrom, appendToBranchLog, dropCheckpoint, landedCopy, landedIds, parentEntries, readTree, treeStamp, type Receipt } from './log.ts';
24
24
  import { WorldServiceEventSchema } from './schemas.ts';
25
25
  import { inVendorMove, refuseReadOnlyWrite } from './request-scope.ts';
26
- import { getActiveWorldStore } from './world-store.ts';
26
+ import { getActiveWorldStore, storageLockScope, type WorldLockOptions } from './world-store.ts';
27
27
  import { currentTraceparent, traceparentForDelivery } from './trace-context.ts';
28
28
  import type { WorldServiceEvent } from './types.ts';
29
29
  import type { TwinResource } from './serve.ts';
@@ -124,8 +124,11 @@ export class TwinActionPreconditionError extends Error {
124
124
  function actionsPath(service: string, root?: string): string {
125
125
  return join(dirname(worldPaths(service, root).events), 'actions.jsonl');
126
126
  }
127
- const actionsLock = (service: string, root?: string): string => `${actionsPath(service, root)}.lock`;
128
- const projectionLock = (service: string, root?: string): string => projectionLockPath(worldPaths(service, root));
127
+ // Canonical, as the history lock's path is (ancestry.ts withHistoryLock), so a take inside it is seen as nested in the same
128
+ // directory whatever the path it was asked by (/var or /private/var). The file is the same one, so a take by another
129
+ // process through the other spelling still excludes this one.
130
+ const actionsLock = (service: string, root?: string): string => join(canonicalStatePath(worldPaths(service, root).dir), 'actions.jsonl.lock');
131
+ const projectionLock = (service: string, root?: string): string => { const paths = worldPaths(service, root); return projectionLockPath({ ...paths, dir: canonicalStatePath(paths.dir) }); };
129
132
 
130
133
  /** Every appended action carries a correlationId — generate one when the caller
131
134
  * hasn't supplied it, so downstream joins (push ledger, logs) always have an id
@@ -186,20 +189,33 @@ function actionIds(service: string, root?: string): ReadonlySet<string> {
186
189
 
187
190
  /** A service's projection lock, under its history lock: one directory's locks are taken history, projection,
188
191
  * actions, events, in that order (architecture, the history lock). */
189
- function withProjection<T>(service: string, root: string | undefined, fn: () => T): T {
190
- return withHistoryLock(worldPaths(service, root).dir, () => withFileLock(projectionLock(service, root), fn));
192
+ function withProjection<T>(service: string, root: string | undefined, fn: () => T, options: WorldLockOptions = {}): T {
193
+ const scope = storageLockScope(options);
194
+ return withHistoryLock(worldPaths(service, root).dir, () => withFileLock(projectionLock(service, root), fn, scope), scope);
191
195
  }
192
196
 
193
197
  function appendActionRaw(action: TwinAction, root?: string): void {
194
- const path = actionsPath(action.service, root);
195
- getActiveWorldStore().mkdir(dirname(path));
198
+ appendActionsRaw([action], root);
199
+ }
200
+
201
+ /** One service's actions appended as ONE store append, in order: the same lines `appendActionRaw` writes one at a time. */
202
+ function appendActionsRaw(actions: readonly TwinAction[], root?: string): void {
203
+ if (!actions.length) return;
204
+ const service = actions[0]!.service;
205
+ const path = actionsPath(service, root);
206
+ // No mkdir: every append is made holding the projection lock (withProjection), whose file is in this same directory
207
+ // and was made there by the take (S2 R17 review).
196
208
  const memos = idMemoSlot(path);
197
209
  const held = memos.get(path);
198
210
  const current = held !== undefined && held.key === logKey(path);
199
- withHistoryLock(worldPaths(action.service, root).dir, () => appendDurable(path, `${JSON.stringify(action)}\n`));
200
- if (current) { held.ids.add(action.id); held.key = logKey(path); } else memos.delete(path);
201
- twinLog('action.append', { service: action.service, id: action.id, op: action.op, correlationId: action.correlationId });
202
- appendObserver?.(action, root);
211
+ const data = actions.map((action) => `${JSON.stringify(action)}\n`).join('');
212
+ // the log is the branch log a read's tree is folded from: the append extends that memo too (log.ts appendToBranchLog)
213
+ const after = withHistoryLock(worldPaths(service, root).dir, () => appendToBranchLog(service, root, data, () => appendDurable(path, data)), { operation: 'action append' });
214
+ if (current) { for (const action of actions) held.ids.add(action.id); held.key = after ?? logKey(path); } else memos.delete(path);
215
+ for (const action of actions) {
216
+ twinLog('action.append', { service: action.service, id: action.id, op: action.op, correlationId: action.correlationId });
217
+ appendObserver?.(action, root);
218
+ }
203
219
  }
204
220
 
205
221
  /** ONE observer of appended entries (state-system.ts: the runtime's `deploy: auto` hook). Called
@@ -264,7 +280,7 @@ export function appendAction(action: TwinAction, root?: string): TwinAction {
264
280
  if (placeholder) return placeholder;
265
281
  appendActionRaw(stamped, root);
266
282
  return stamped;
267
- }));
283
+ }, { operation: 'action append' }), { operation: 'action append' });
268
284
  }
269
285
 
270
286
  /** Append `action` only if no exact action with the same id already exists — the whole
@@ -286,7 +302,7 @@ export function appendActionIfAbsent(action: TwinAction, root?: string): { actio
286
302
  if (placeholder) return { action: placeholder, appended: true, placeholder: true };
287
303
  appendActionRaw(stamped, root);
288
304
  return { action: stamped, appended: true };
289
- }));
305
+ }, { operation: 'action append' }), { operation: 'action append' });
290
306
  }
291
307
 
292
308
  /**
@@ -314,12 +330,53 @@ export function appendActionOccurrence(base: TwinAction, root?: string): { actio
314
330
  if (placeholder) return { action: placeholder, appended: true, placeholder: true };
315
331
  appendActionRaw(stamped, root);
316
332
  return { action: stamped, appended: true };
317
- }));
333
+ }, { operation: 'action append' }), { operation: 'action append' });
334
+ }
335
+
336
+ /**
337
+ * Append several writes of ONE service as occurrences, in order, under one projection + actions lock and as one store
338
+ * append: each lands with the id, ordinal and correlation `appendActionOccurrence` would give it called once per write
339
+ * in the same order (a write's ordinal counts the batch's earlier writes as taken). A Redis run's flush made one
340
+ * append per changed member and key, each taking the history, projection and actions locks and appending alone, one
341
+ * after another under the twin's single command queue (S2 R19: Twenty's queue scripts spent up to 2.1 s of a 2 s
342
+ * window in flush while 38 commands waited). A write carrying preconditions is not taken here: each is checked against
343
+ * the state the earlier ones made, which only the one-at-a-time path sees. While a placeholder pull is open a write
344
+ * lands as observed events, as `appendActionOccurrence`'s does.
345
+ */
346
+ export function appendActionOccurrences(bases: readonly TwinAction[], root?: string): Array<{ action: TwinAction; appended: boolean; placeholder?: true }> {
347
+ if (!bases.length) return [];
348
+ const service = bases[0]!.service;
349
+ if (bases.some((base) => base.service !== service)) throw new Error(`appendActionOccurrences: one service per batch, got ${[...new Set(bases.map((base) => base.service))].join(', ')}`);
350
+ if (bases.some((base) => base.preconditions?.length)) throw new Error('appendActionOccurrences: a write with preconditions is appended alone');
351
+ refuseReadOnlyWrite(service);
352
+ return withProjection(service, root, () => withFileLock(actionsLock(service, root), () => {
353
+ const logged = actionIds(service, root);
354
+ const batch = new Set<string>();
355
+ const taken = { has: (id: string) => batch.has(id) || logged.has(id) };
356
+ const landed: Array<{ action: TwinAction; appended: boolean; placeholder?: true }> = [];
357
+ let unwritten: TwinAction[] = [];
358
+ for (const base of bases) {
359
+ const stamped = occurrenceOf(base, taken, root);
360
+ const placeholder = landIfPlaceholderPull(stamped, root);
361
+ if (placeholder) {
362
+ // no action row: its id is not taken, as it is not in the log the one-at-a-time path reads
363
+ // the lines before it are written first, so the log and the pull keep the writes' order
364
+ appendActionsRaw(unwritten, root); unwritten = [];
365
+ landed.push({ action: placeholder, appended: true, placeholder: true });
366
+ continue;
367
+ }
368
+ batch.add(stamped.id);
369
+ unwritten.push(stamped);
370
+ landed.push({ action: stamped, appended: true });
371
+ }
372
+ appendActionsRaw(unwritten, root);
373
+ return landed;
374
+ }, { operation: 'action append' }), { operation: 'action append' });
318
375
  }
319
376
 
320
377
  /** The occurrence rule, shared by both appenders that use it — the ordinal AND the merge base.
321
378
  * Pure over (base, the log as it stands): the caller holds the lock. */
322
- function occurrenceOf(base: TwinAction, taken: ReadonlySet<string>, root?: string): TwinAction {
379
+ function occurrenceOf(base: TwinAction, taken: Pick<ReadonlySet<string>, 'has'>, root?: string): TwinAction {
323
380
  {
324
381
  let ordinal = 0;
325
382
  while (taken.has(occurrenceId(base.id, ordinal))) ordinal += 1;
@@ -393,7 +450,7 @@ export function decideAndAppendAction<T>(
393
450
  if (placeholder) return { value: decision.value, action: placeholder, appended: true, placeholder: true };
394
451
  appendActionRaw(stamped, root);
395
452
  return { value: decision.value, action: stamped, appended: true };
396
- }));
453
+ }, { operation: 'action decision' }), { operation: 'action decision' });
397
454
  }
398
455
 
399
456
  export function appendTransactionCommit(commit: TwinTransactionCommit, root?: string): TwinTransactionCommit {
@@ -565,9 +622,9 @@ export function confirmAction(opts: {
565
622
  withFileLock(eventsLockPath(paths), () => {
566
623
  const held = landedIds(parentEntries(opts.service, opts.root));
567
624
  for (const copy of copies) if (!held.has(copy.id)) appendDurable(paths.events, `${JSON.stringify(copy)}\n`);
568
- });
625
+ }, { operation: 'action confirmation' });
569
626
  dropCheckpoint(opts.service, opts.root);
570
- });
627
+ }, { operation: 'action confirmation' });
571
628
  const observedEventIds = copies.map((c) => c.id);
572
629
  return { observedEventId: observedEventIds.at(-1)!, observedEventIds };
573
630
  }
@@ -607,7 +664,7 @@ export function revertAction(opts: { service: string; actionId: string; occurred
607
664
  });
608
665
  appendActionRaw(revert, opts.root);
609
666
  return { status: 'reverted', revertId: revert.id };
610
- }));
667
+ }, { operation: 'action revert' }), { operation: 'action revert' });
611
668
  }
612
669
 
613
670
  /** The LOCAL OVERLAY: pending actions (set, not reverted, not yet confirmed) — the divergence