@volter/world-core 2.0.0 → 2.0.1
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.
- package/app-route.cjs +95 -6
- package/app-route.d.cts +2 -1
- package/dist/app-route.cjs +95 -6
- package/dist/app-route.d.cts +2 -1
- package/dist/generated/pack-facts.json +127 -8
- package/dist/inject.cjs +46 -1
- package/dist/src/actions.d.ts +24 -0
- package/dist/src/actions.js +49 -2
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +4 -2
- package/dist/src/packRegistry.d.ts +8 -6
- package/dist/src/redis/engine.d.ts +308 -0
- package/dist/src/redis/engine.js +1663 -0
- package/dist/src/redis/index.d.ts +2 -0
- package/dist/src/redis/index.js +6 -0
- package/dist/src/redis/lua.d.ts +153 -0
- package/dist/src/redis/lua.js +1373 -0
- package/dist/src/storage.d.ts +22 -1
- package/dist/src/storage.js +55 -1
- package/dist/src/world-clock.d.ts +2 -2
- package/dist/src/world-clock.js +18 -17
- package/dist/vendor-hosts.cjs +2 -2
- package/dist/world-clock.cjs +40 -0
- package/dist/world-clock.d.cts +4 -0
- package/generated/pack-facts.json +127 -8
- package/inject.cjs +46 -1
- package/package.json +11 -1
- package/src/actions.ts +53 -2
- package/src/index.ts +5 -0
- package/src/packRegistry.ts +8 -6
- package/src/redis/engine.ts +1468 -0
- package/src/redis/index.ts +6 -0
- package/src/redis/lua.ts +1250 -0
- package/src/storage.ts +52 -2
- package/src/world-clock.ts +19 -16
- package/vendor-hosts.cjs +2 -2
- package/world-clock.cjs +40 -0
- package/world-clock.d.cts +4 -0
package/dist/src/storage.d.ts
CHANGED
|
@@ -7,8 +7,29 @@ import type { AppendEventResult, GenericWorldState, WorldPaths, WorldReducer, Wo
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function stateDirName(): string;
|
|
9
9
|
export declare function worldStateRoot(root?: string): string;
|
|
10
|
-
|
|
10
|
+
/** Install the observer; the one it replaces is returned (a test puts it back). */
|
|
11
|
+
export declare function observeWorldPaths(observer: ((service: string, root: string | undefined) => void) | undefined): ((service: string, root: string | undefined) => void) | undefined;
|
|
12
|
+
/** Run `fn` without its worldPaths calls reaching the identity observer. */
|
|
13
|
+
export declare function withoutIdentityClaim<T>(fn: () => T): T;
|
|
11
14
|
export declare function worldPaths(service: string, root?: string): WorldPaths;
|
|
15
|
+
/**
|
|
16
|
+
* THE WORLD'S DATA DIRECTORY, as the World runtime hands it to every service it starts. Each twin of a World runs on
|
|
17
|
+
* its own root, `<data>/<service id>` (world-runtime's startService and startColocatedServices), so one twin's
|
|
18
|
+
* `.volter/world/<service>` is not another's; `ownerStoreRoots` finds a store across them.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WORLD_DATA_ENV = "VOLTER_WORLD_DATA";
|
|
21
|
+
/**
|
|
22
|
+
* The roots that hold the store of `owner` (ANOTHER pack's, read by contract: architecture A3), for a twin on `root`:
|
|
23
|
+
* - `[root]` when `root` itself holds that store (both packs on one root: `volter world serve`, a test, a pack serving
|
|
24
|
+
* several services on its root, as aws does);
|
|
25
|
+
* - else, when `root` is one of a World's service roots (a directory directly under the World's data directory), every
|
|
26
|
+
* other service root there that holds it (usually one; two when the World runs the owning pack twice);
|
|
27
|
+
* - else `[]`: nothing is issued yet, or the twin runs outside a World.
|
|
28
|
+
* Only a declared cross-pack read resolves through here (`projectOwnerResources`); a pack's reads of its OWN store never
|
|
29
|
+
* leave its root, so a fresh twin never reads a sibling that happens to run the same pack. Not memoized: it is a few
|
|
30
|
+
* stats per service root, and a store that appears after the first read (the owner's first write) must be found.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ownerStoreRoots(owner: string, root?: string): string[];
|
|
12
33
|
/** Read + parse a whole-file JSON sidecar, citing the path on a parse failure (mirrors
|
|
13
34
|
* readJsonl's `${path}:...` error shape). Callers own existence semantics — this parses a
|
|
14
35
|
* file that is expected to exist; guard with existsSync first when absence is allowed. */
|
package/dist/src/storage.js
CHANGED
|
@@ -40,12 +40,29 @@ function assertServiceName(service) {
|
|
|
40
40
|
* Nothing else may use this: it is a one-slot observability hook, never a control seam.
|
|
41
41
|
*/
|
|
42
42
|
let worldPathsObserver;
|
|
43
|
+
/** Install the observer; the one it replaces is returned (a test puts it back). */
|
|
43
44
|
export function observeWorldPaths(observer) {
|
|
45
|
+
const previous = worldPathsObserver;
|
|
44
46
|
worldPathsObserver = observer;
|
|
47
|
+
return previous;
|
|
48
|
+
}
|
|
49
|
+
/** While > 0, a read of ANOTHER pack's store is in progress (`projectOwnerResources`): that is not the request's twin
|
|
50
|
+
* saying who it is, so it claims no journal identity. */
|
|
51
|
+
let identityQuiet = 0;
|
|
52
|
+
/** Run `fn` without its worldPaths calls reaching the identity observer. */
|
|
53
|
+
export function withoutIdentityClaim(fn) {
|
|
54
|
+
identityQuiet++;
|
|
55
|
+
try {
|
|
56
|
+
return fn();
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
identityQuiet--;
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
export function worldPaths(service, root) {
|
|
47
63
|
const safeService = assertServiceName(service);
|
|
48
|
-
|
|
64
|
+
if (identityQuiet === 0)
|
|
65
|
+
worldPathsObserver?.(safeService, root);
|
|
49
66
|
const resolvedRoot = projectRoot(root);
|
|
50
67
|
const dir = join(worldStateRoot(root), safeService);
|
|
51
68
|
return {
|
|
@@ -60,6 +77,43 @@ export function worldPaths(service, root) {
|
|
|
60
77
|
eventQueue: join(dir, 'event-queue.jsonl'),
|
|
61
78
|
};
|
|
62
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* THE WORLD'S DATA DIRECTORY, as the World runtime hands it to every service it starts. Each twin of a World runs on
|
|
82
|
+
* its own root, `<data>/<service id>` (world-runtime's startService and startColocatedServices), so one twin's
|
|
83
|
+
* `.volter/world/<service>` is not another's; `ownerStoreRoots` finds a store across them.
|
|
84
|
+
*/
|
|
85
|
+
export const WORLD_DATA_ENV = 'VOLTER_WORLD_DATA';
|
|
86
|
+
/** Does `root` hold `service`'s store: its log or its branch record (what a write or a fork leaves; a read leaves
|
|
87
|
+
* neither)? The paths are built without worldPaths, so a probe claims no journal identity. */
|
|
88
|
+
function holdsStore(service, root) {
|
|
89
|
+
const dir = join(worldStateRoot(root), service);
|
|
90
|
+
const store = getActiveWorldStore();
|
|
91
|
+
return ['events.jsonl', 'actions.jsonl', 'branch.json'].some((file) => store.exists(join(dir, file)));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The roots that hold the store of `owner` (ANOTHER pack's, read by contract: architecture A3), for a twin on `root`:
|
|
95
|
+
* - `[root]` when `root` itself holds that store (both packs on one root: `volter world serve`, a test, a pack serving
|
|
96
|
+
* several services on its root, as aws does);
|
|
97
|
+
* - else, when `root` is one of a World's service roots (a directory directly under the World's data directory), every
|
|
98
|
+
* other service root there that holds it (usually one; two when the World runs the owning pack twice);
|
|
99
|
+
* - else `[]`: nothing is issued yet, or the twin runs outside a World.
|
|
100
|
+
* Only a declared cross-pack read resolves through here (`projectOwnerResources`); a pack's reads of its OWN store never
|
|
101
|
+
* leave its root, so a fresh twin never reads a sibling that happens to run the same pack. Not memoized: it is a few
|
|
102
|
+
* stats per service root, and a store that appears after the first read (the owner's first write) must be found.
|
|
103
|
+
*/
|
|
104
|
+
export function ownerStoreRoots(owner, root) {
|
|
105
|
+
const safeOwner = assertServiceName(owner);
|
|
106
|
+
const own = projectRoot(root);
|
|
107
|
+
if (holdsStore(safeOwner, own))
|
|
108
|
+
return [own];
|
|
109
|
+
const data = typeof process === 'undefined' ? undefined : process.env[WORLD_DATA_ENV];
|
|
110
|
+
if (!data || dirname(own) !== resolve(data))
|
|
111
|
+
return [];
|
|
112
|
+
return getActiveWorldStore().list(dirname(own))
|
|
113
|
+
.map((name) => join(dirname(own), name))
|
|
114
|
+
.filter((candidate) => candidate !== own && holdsStore(safeOwner, candidate))
|
|
115
|
+
.sort();
|
|
116
|
+
}
|
|
63
117
|
function readJsonl(path) {
|
|
64
118
|
const rows = [];
|
|
65
119
|
for (const [index, line] of getActiveWorldStore().readLines(path).entries()) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const WORLD_CLOCK_ENV = "TWIN_WORLD_CLOCK_FILE";
|
|
2
|
-
/** The world's "now": the
|
|
3
|
-
*
|
|
2
|
+
/** The world's "now": the clock file's time when one is configured (its frozen instant, or a running clock's time
|
|
3
|
+
* now); real wall-clock time otherwise. */
|
|
4
4
|
export declare function worldNow(): string;
|
package/dist/src/world-clock.js
CHANGED
|
@@ -6,27 +6,28 @@
|
|
|
6
6
|
// is the operator door (set / advance / show). No file, or no env → real wall-clock time,
|
|
7
7
|
// exactly the pre-clock behavior; a world only has scripted time when its operator says so.
|
|
8
8
|
//
|
|
9
|
-
// File format
|
|
10
|
-
// instant
|
|
11
|
-
// a
|
|
12
|
-
//
|
|
13
|
-
// hydrated serverless namespace must carry its frozen instant with it (runtime contract R11–R14
|
|
14
|
-
// — a MemoryWorldStore world that read the clock off the host filesystem would silently
|
|
15
|
-
// serve the HOST's time, the exact two-services-disagreeing-about-now defect below).
|
|
9
|
+
// File format (world-clock.cjs, the one home of it, shared with the injector that gives an application process the
|
|
10
|
+
// same time): a FROZEN instant, one ISO-8601 line, every read returning exactly that instant; or a RUNNING clock,
|
|
11
|
+
// `<instant> from <wall instant>`, the World's time at a moment of the machine's, running on at the machine's pace (a
|
|
12
|
+
// World serving an application). Moving time is an explicit operator act either way, never a background drift.
|
|
16
13
|
import { getActiveWorldStore } from "./world-store.js";
|
|
14
|
+
import clockForm from '../world-clock.cjs';
|
|
17
15
|
export const WORLD_CLOCK_ENV = 'TWIN_WORLD_CLOCK_FILE';
|
|
18
|
-
/** The
|
|
19
|
-
*
|
|
16
|
+
/** The machine's own time: the real Date.now even where the injector gives the process the World's time (inject.cjs
|
|
17
|
+
* leaves the original under this symbol), so a running clock's offset is applied once. */
|
|
18
|
+
const machineNow = () => {
|
|
19
|
+
const real = globalThis[Symbol.for('volter.machineDateNow')];
|
|
20
|
+
return typeof real === 'function' ? real() : Date.now();
|
|
21
|
+
};
|
|
22
|
+
/** The world's "now": the clock file's time when one is configured (its frozen instant, or a running clock's time
|
|
23
|
+
* now); real wall-clock time otherwise. */
|
|
20
24
|
export function worldNow() {
|
|
21
25
|
const file = process.env[WORLD_CLOCK_ENV];
|
|
22
26
|
if (file && getActiveWorldStore().exists(file)) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// A configured-but-corrupt clock is a WORLD defect — fail loudly, never silently drift
|
|
28
|
-
// back to wall-clock (two services disagreeing about now breaks the world).
|
|
29
|
-
throw new Error(`world clock: ${file} does not contain a parseable ISO-8601 instant (got ${JSON.stringify(raw.slice(0, 40))})`);
|
|
27
|
+
// A configured-but-corrupt clock is a WORLD defect — parseClock fails loudly, never silently drifting back to
|
|
28
|
+
// wall-clock (two services disagreeing about now breaks the world).
|
|
29
|
+
const clock = clockForm.parseClock(getActiveWorldStore().read(file) ?? '');
|
|
30
|
+
return new Date(clockForm.clockNowMs(clock, machineNow())).toISOString();
|
|
30
31
|
}
|
|
31
|
-
return new Date().toISOString();
|
|
32
|
+
return new Date(machineNow()).toISOString();
|
|
32
33
|
}
|
package/dist/vendor-hosts.cjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
//
|
|
23
23
|
// THE SHARED-HOST RULE (Cal.com incident, 2026-08): a shared-host predicate must claim ONLY the
|
|
24
24
|
// paths its twin actually serves — never "the rest of the host". Claiming the remainder is how
|
|
25
|
-
// every `@googleapis/calendar` call (
|
|
25
|
+
// every `@googleapis/calendar` call (all of its methods default to www.googleapis.com) was routed
|
|
26
26
|
// into the googleauth/gemini twin and answered with a plausible Google-shaped 404: a mis-route
|
|
27
27
|
// that fails OPEN and PLAUSIBLE, strictly worse than a coverage gap. An UNCLAIMED path on a
|
|
28
28
|
// claimed host is refused LOUDLY instead (see unclaimedTwinnedHostPathMessage below): the error
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* Exactly the routes gemini-twin.ts §googleauth models: `POST /token` (the oauth2.googleapis.com
|
|
33
33
|
* path, also answered on the legacy host) and `POST /oauth2/v4/token` (the legacy
|
|
34
34
|
* www.googleapis.com token path older google-auth-library versions default to). NOTHING else:
|
|
35
|
-
* /calendar/v3
|
|
35
|
+
* /calendar/v3/* (the googlecalendar pack's), /drive/v3/*, … are other Google products, and
|
|
36
36
|
* claiming them would mis-route those SDKs into an auth twin that answers with vendor-shaped
|
|
37
37
|
* 404s (the Cal.com incident).
|
|
38
38
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// THE WORLD CLOCK'S FORM — the one home of how a clock file reads (dependency-free, side-effect free), shared by the
|
|
3
|
+
// twins' worldNow() (src/world-clock.ts, reading through the active WorldStore) and the injector, which gives an
|
|
4
|
+
// application process the same time (inject.cjs, WORLD TIME).
|
|
5
|
+
//
|
|
6
|
+
// A clock file holds one line in one of two forms:
|
|
7
|
+
// · FROZEN: `<ISO-8601 instant>` — every read answers exactly that instant (scripted time: lives, seeds, a story);
|
|
8
|
+
// · RUNNING: `<ISO-8601 instant> from <ISO-8601 wall instant>` — the World's time was <instant> when the machine's was
|
|
9
|
+
// <wall instant>, and runs on at the machine's pace from there: a World serving an application moves time this way
|
|
10
|
+
// (`volter-world clock <world> shift <N s|m|h|d>`), since a frozen instant would stop the application's time.
|
|
11
|
+
// No file: the machine's time.
|
|
12
|
+
|
|
13
|
+
/** The clock a file's text states, or throws on text that states neither form. */
|
|
14
|
+
function parseClock(raw) {
|
|
15
|
+
const text = String(raw ?? '').trim();
|
|
16
|
+
const m = /^(\S+)\s+from\s+(\S+)$/.exec(text);
|
|
17
|
+
if (m) {
|
|
18
|
+
const at = Date.parse(m[1]);
|
|
19
|
+
const since = Date.parse(m[2]);
|
|
20
|
+
if (!Number.isNaN(at) && !Number.isNaN(since)) return { kind: 'running', at, since };
|
|
21
|
+
} else {
|
|
22
|
+
const at = Date.parse(text);
|
|
23
|
+
if (!Number.isNaN(at)) return { kind: 'frozen', at };
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`world clock: expected an ISO-8601 instant, or "<instant> from <instant>", got ${JSON.stringify(text.slice(0, 60))}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The World's time in ms for a parsed clock, at the machine's time `wallMs`. */
|
|
29
|
+
function clockNowMs(clock, wallMs) {
|
|
30
|
+
return clock.kind === 'running' ? clock.at + (wallMs - clock.since) : clock.at;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A clock's text: frozen at `atMs`, or running from `atMs` as of the machine's `sinceMs`. */
|
|
34
|
+
function formatClock(clock) {
|
|
35
|
+
return clock.kind === 'running'
|
|
36
|
+
? `${new Date(clock.at).toISOString()} from ${new Date(clock.since).toISOString()}`
|
|
37
|
+
: new Date(clock.at).toISOString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { parseClock, clockNowMs, formatClock };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type WorldClock = { kind: 'frozen'; at: number } | { kind: 'running'; at: number; since: number };
|
|
2
|
+
export function parseClock(raw: string | null | undefined): WorldClock;
|
|
3
|
+
export function clockNowMs(clock: WorldClock, wallMs: number): number;
|
|
4
|
+
export function formatClock(clock: WorldClock): string;
|
|
@@ -714,6 +714,9 @@
|
|
|
714
714
|
"hosts": [
|
|
715
715
|
{
|
|
716
716
|
"host": "api.cohere.com"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"host": "api.cohere.ai"
|
|
717
720
|
}
|
|
718
721
|
],
|
|
719
722
|
"archetype": "generative",
|
|
@@ -728,7 +731,7 @@
|
|
|
728
731
|
"embed_job",
|
|
729
732
|
"token"
|
|
730
733
|
],
|
|
731
|
-
"endpointEnvNone": "neither official SDK reads a base-URL env var: cohere-ai@8.1.0 takes the override as the `baseUrl`/`environment` CONSTRUCTOR option and reads only CO_API_KEY from the environment (auth/BearerAuthProvider.js), and @ai-sdk/cohere@4.0.35 takes `options.baseURL` and reads only COHERE_API_KEY. Interception is host-based on api.cohere.com; inventing a COHERE_BASE_URL nothing reads would make `covers` claim a world it does not cover.",
|
|
734
|
+
"endpointEnvNone": "neither official SDK reads a base-URL env var: cohere-ai@8.1.0 takes the override as the `baseUrl`/`environment` CONSTRUCTOR option and reads only CO_API_KEY from the environment (auth/BearerAuthProvider.js), and @ai-sdk/cohere@4.0.35 takes `options.baseURL` and reads only COHERE_API_KEY. Interception is host-based on api.cohere.com and api.cohere.ai; inventing a COHERE_BASE_URL nothing reads would make `covers` claim a world it does not cover.",
|
|
732
735
|
"serveExport": "createCohereTwinServer"
|
|
733
736
|
},
|
|
734
737
|
"currencyapi": {
|
|
@@ -963,6 +966,10 @@
|
|
|
963
966
|
"host": "discord.com",
|
|
964
967
|
"pathPattern": "^/api/"
|
|
965
968
|
},
|
|
969
|
+
{
|
|
970
|
+
"host": "discord.com",
|
|
971
|
+
"pathPattern": "^/(oauth2/authorize|login)/?$"
|
|
972
|
+
},
|
|
966
973
|
{
|
|
967
974
|
"host": "gateway.discord.gg"
|
|
968
975
|
},
|
|
@@ -1399,6 +1406,10 @@
|
|
|
1399
1406
|
"host": "github.com",
|
|
1400
1407
|
"pathPattern": "^/login/oauth/(authorize|access_token)$"
|
|
1401
1408
|
},
|
|
1409
|
+
{
|
|
1410
|
+
"host": "github.com",
|
|
1411
|
+
"pathPattern": "^/(login|session|sessions/two-factor(/app)?)$"
|
|
1412
|
+
},
|
|
1402
1413
|
{
|
|
1403
1414
|
"host": "github.com",
|
|
1404
1415
|
"pathPattern": "^/apps/[^/]+/installations/new$"
|
|
@@ -1470,6 +1481,37 @@
|
|
|
1470
1481
|
"endpointEnvNone": "no Google Ads client exposes an HTTP base-URL override: google-ads-api builds the host into the request literally, google-ads-python's `endpoint` is a gRPC host:port for its transport channel, and Dub hardcodes the host in a raw fetch. Interception is host-based.",
|
|
1471
1482
|
"serveExport": "createGoogleAdsTwinServer"
|
|
1472
1483
|
},
|
|
1484
|
+
"googlecalendar": {
|
|
1485
|
+
"adoption": {
|
|
1486
|
+
"sdks": [
|
|
1487
|
+
"@googleapis/calendar"
|
|
1488
|
+
],
|
|
1489
|
+
"pypi": [],
|
|
1490
|
+
"envStems": [
|
|
1491
|
+
"GOOGLECALENDAR"
|
|
1492
|
+
]
|
|
1493
|
+
},
|
|
1494
|
+
"hosts": [
|
|
1495
|
+
{
|
|
1496
|
+
"host": "www.googleapis.com",
|
|
1497
|
+
"pathPattern": "^/(batch/)?calendar/v3(/|$)"
|
|
1498
|
+
}
|
|
1499
|
+
],
|
|
1500
|
+
"archetype": "crud",
|
|
1501
|
+
"protocol": {
|
|
1502
|
+
"declared": "2",
|
|
1503
|
+
"major": 2,
|
|
1504
|
+
"standing": "current"
|
|
1505
|
+
},
|
|
1506
|
+
"transport": "rest",
|
|
1507
|
+
"resources": [
|
|
1508
|
+
"Calendar",
|
|
1509
|
+
"CalendarListEntry",
|
|
1510
|
+
"Event"
|
|
1511
|
+
],
|
|
1512
|
+
"endpointEnvNone": "@googleapis/calendar and googleapis read no base-URL variable: the root URL is the Discovery document's `https://www.googleapis.com/` unless the caller passes `rootUrl` (Inbox Zero reads its own GOOGLE_BASE_URL for that); interception is the host rule above.",
|
|
1513
|
+
"serveExport": "createGooglecalendarTwinServer"
|
|
1514
|
+
},
|
|
1473
1515
|
"googlemaps": {
|
|
1474
1516
|
"adoption": {
|
|
1475
1517
|
"pypi": [
|
|
@@ -2148,6 +2190,27 @@
|
|
|
2148
2190
|
],
|
|
2149
2191
|
"serveExport": "createMixpanelTwinServer"
|
|
2150
2192
|
},
|
|
2193
|
+
"mongodb": {
|
|
2194
|
+
"adoption": {
|
|
2195
|
+
"pypi": [],
|
|
2196
|
+
"sdks": []
|
|
2197
|
+
},
|
|
2198
|
+
"hostsNone": "RAW-PROTOCOL pack — there is no host to map. MongoDB clients speak the wire protocol over a raw TCP socket to whatever host the connection string names, which the injector (http/fetch patching) cannot see; a World reaches this twin through the connection string itself (MONGO_URI and its kin), which World-managed infrastructure injects.",
|
|
2199
|
+
"archetype": "crud",
|
|
2200
|
+
"protocol": {
|
|
2201
|
+
"declared": "2",
|
|
2202
|
+
"major": 2,
|
|
2203
|
+
"standing": "current"
|
|
2204
|
+
},
|
|
2205
|
+
"transport": "raw-tcp",
|
|
2206
|
+
"resources": [
|
|
2207
|
+
"collection",
|
|
2208
|
+
"index",
|
|
2209
|
+
"document"
|
|
2210
|
+
],
|
|
2211
|
+
"endpointEnvNone": "no vendor base-URL variable: the application reads its own connection-string variable (MONGO_URI, MONGODB_URL, DATABASE_URL, …), and World-managed infrastructure (`volter-world init` → `volter-world-infra`) injects that variable pointing at this twin's listener.",
|
|
2212
|
+
"serveExport": "createMongodbTwinServer"
|
|
2213
|
+
},
|
|
2151
2214
|
"moonshot": {
|
|
2152
2215
|
"adoption": {
|
|
2153
2216
|
"pypi": [],
|
|
@@ -2816,6 +2879,26 @@
|
|
|
2816
2879
|
"endpointEnvNone": "reddit clients take no base-URL environment variable (snoowrap and PRAW build https://oauth.reddit.com and https://www.reddit.com URLs from constants or explicit constructor options), so a World reaches them through the hosts above; inventing a REDDIT_BASE_URL the app never reads would report coverage the app does not have.",
|
|
2817
2880
|
"serveExport": "createRedditTwinServer"
|
|
2818
2881
|
},
|
|
2882
|
+
"redis": {
|
|
2883
|
+
"adoption": {},
|
|
2884
|
+
"hostsNone": "RAW-PROTOCOL pack — there is no host to map. Redis's protocol runs over a TCP socket the injector (which patches http/fetch) never sees, and a Redis has no vendor hostname: it is wherever the app's REDIS_URL points. The World points it here: init classifies REDIS_URL as managed infrastructure and the managed redis service is this twin.",
|
|
2885
|
+
"archetype": "crud",
|
|
2886
|
+
"protocol": {
|
|
2887
|
+
"major": 1,
|
|
2888
|
+
"standing": "deprecated"
|
|
2889
|
+
},
|
|
2890
|
+
"transport": "raw-tcp",
|
|
2891
|
+
"resources": [
|
|
2892
|
+
"key",
|
|
2893
|
+
"field",
|
|
2894
|
+
"member",
|
|
2895
|
+
"scored",
|
|
2896
|
+
"entry",
|
|
2897
|
+
"script"
|
|
2898
|
+
],
|
|
2899
|
+
"endpointEnvNone": "Redis clients read no base-URL env of their own: the app passes its REDIS_URL (or host/port) to ioredis or node-redis itself, and init emits that REDIS_URL for the managed redis service (world-runtime/src/init.ts), not as a twin endpoint env.",
|
|
2900
|
+
"serveExport": "createRedisTwinServer"
|
|
2901
|
+
},
|
|
2819
2902
|
"replicate": {
|
|
2820
2903
|
"adoption": {
|
|
2821
2904
|
"pypi": [
|
|
@@ -3358,11 +3441,11 @@
|
|
|
3358
3441
|
},
|
|
3359
3442
|
{
|
|
3360
3443
|
"host": "connect.stripe.com",
|
|
3361
|
-
"pathPattern": "^/setup/"
|
|
3444
|
+
"pathPattern": "^/setup/|^/oauth/(authorize|token|deauthorize)/?$"
|
|
3362
3445
|
},
|
|
3363
3446
|
{
|
|
3364
3447
|
"host": "dashboard.stripe.com",
|
|
3365
|
-
"pathPattern": "^/settings/public
|
|
3448
|
+
"pathPattern": "^/settings/public/?$|^/(test/)?settings/connect(/onboarding-options/oauth)?/?$"
|
|
3366
3449
|
},
|
|
3367
3450
|
{
|
|
3368
3451
|
"host": "verify.stripe.com",
|
|
@@ -3561,6 +3644,38 @@
|
|
|
3561
3644
|
"endpointEnvNone": "the @tavily/core SDK exposes no base-URL env var — it reads only TAVILY_API_KEY/TAVILY_HTTP(S)_PROXY/TAVILY_ORG_ID/TAVILY_PROJECT from process.env; base-URL override is the constructor option apiBaseURL (dist/index.mjs post()/get(): `apiBaseURL || BASE_URL`), so interception is the hosts entry above.",
|
|
3562
3645
|
"serveExport": "createTavilyTwinServer"
|
|
3563
3646
|
},
|
|
3647
|
+
"temporal": {
|
|
3648
|
+
"adoption": {
|
|
3649
|
+
"sdks": [
|
|
3650
|
+
"@temporalio/client",
|
|
3651
|
+
"@temporalio/worker",
|
|
3652
|
+
"@temporalio/workflow",
|
|
3653
|
+
"@temporalio/activity"
|
|
3654
|
+
],
|
|
3655
|
+
"scopes": [
|
|
3656
|
+
"@temporalio/"
|
|
3657
|
+
],
|
|
3658
|
+
"pypi": [
|
|
3659
|
+
"temporalio"
|
|
3660
|
+
],
|
|
3661
|
+
"envStems": [
|
|
3662
|
+
"TEMPORAL"
|
|
3663
|
+
]
|
|
3664
|
+
},
|
|
3665
|
+
"hostsNone": "RAW-PROTOCOL pack — there is no host to map. The Temporal SDKs dial the frontend address they are configured with (Connection.connect / NativeConnection.connect `address`, conventionally TEMPORAL_ADDRESS) over gRPC, which the http/fetch injector never sees. Interception is the app-read address: the World injects TEMPORAL_ADDRESS=<host>:<port> at this twin (APP_READ_ENDPOINT_ENV in world-runtime/src/init.ts).",
|
|
3666
|
+
"archetype": "engine-control",
|
|
3667
|
+
"protocol": {
|
|
3668
|
+
"declared": "2",
|
|
3669
|
+
"major": 2,
|
|
3670
|
+
"standing": "current"
|
|
3671
|
+
},
|
|
3672
|
+
"transport": "raw-tcp",
|
|
3673
|
+
"resources": [
|
|
3674
|
+
"workflow-execution",
|
|
3675
|
+
"namespace"
|
|
3676
|
+
],
|
|
3677
|
+
"serveExport": "createTemporalTwinServer"
|
|
3678
|
+
},
|
|
3564
3679
|
"tiktok": {
|
|
3565
3680
|
"adoption": {
|
|
3566
3681
|
"sdks": [],
|
|
@@ -4154,11 +4269,11 @@
|
|
|
4154
4269
|
"hosts": [
|
|
4155
4270
|
{
|
|
4156
4271
|
"host": "api.x.com",
|
|
4157
|
-
"pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/"
|
|
4272
|
+
"pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/(?!assets/consent\\.(?:js|css)$)"
|
|
4158
4273
|
},
|
|
4159
4274
|
{
|
|
4160
4275
|
"host": "api.twitter.com",
|
|
4161
|
-
"pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/"
|
|
4276
|
+
"pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/(?!assets/consent\\.(?:js|css)$)"
|
|
4162
4277
|
}
|
|
4163
4278
|
],
|
|
4164
4279
|
"archetype": "crud",
|
|
@@ -4241,11 +4356,11 @@
|
|
|
4241
4356
|
},
|
|
4242
4357
|
{
|
|
4243
4358
|
"host": "api.x.com",
|
|
4244
|
-
"pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)
|
|
4359
|
+
"pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$|^/oauth/(?:request_token|authorize|authenticate|access_token)/?$|^/1\\.1/oauth/invalidate_token(?:\\.json)?/?$|^/_twin/assets/consent\\.(?:js|css)$"
|
|
4245
4360
|
},
|
|
4246
4361
|
{
|
|
4247
4362
|
"host": "api.twitter.com",
|
|
4248
|
-
"pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)
|
|
4363
|
+
"pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$|^/oauth/(?:request_token|authorize|authenticate|access_token)/?$|^/1\\.1/oauth/invalidate_token(?:\\.json)?/?$|^/_twin/assets/consent\\.(?:js|css)$"
|
|
4249
4364
|
}
|
|
4250
4365
|
],
|
|
4251
4366
|
"archetype": "crud",
|
|
@@ -4263,7 +4378,11 @@
|
|
|
4263
4378
|
"access_token",
|
|
4264
4379
|
"refresh_token",
|
|
4265
4380
|
"grant",
|
|
4266
|
-
"rate_window"
|
|
4381
|
+
"rate_window",
|
|
4382
|
+
"oauth1_app",
|
|
4383
|
+
"oauth1_request_token",
|
|
4384
|
+
"oauth1_token",
|
|
4385
|
+
"oauth1_grant"
|
|
4267
4386
|
],
|
|
4268
4387
|
"serveExport": "createXIdentityTwinServer"
|
|
4269
4388
|
},
|
package/inject.cjs
CHANGED
|
@@ -678,7 +678,7 @@ function fakeClientRequest(origin, method, fullPath, headers, callback) {
|
|
|
678
678
|
|
|
679
679
|
// --- unclaimed path on a twinned shared host: refuse LOUDLY, never mis-route -----------------
|
|
680
680
|
// The Cal.com incident class: a host two vendors share (www.googleapis.com) is twinned, the
|
|
681
|
-
// request's PATH belongs to neither twin (`/
|
|
681
|
+
// request's PATH belongs to neither twin (`/drive/v3/*` — Google Drive has no pack; `/calendar/v3/*` was the case until the googlecalendar pack), and
|
|
682
682
|
// the old catch-all matcher answered it from the WRONG pack with a vendor-shaped 404: fails open
|
|
683
683
|
// AND plausible. The operator declared this host virtualized, so the traffic must not leak to the
|
|
684
684
|
// real vendor either. The only honest outcome is a refusal that NAMES the situation.
|
|
@@ -1092,6 +1092,51 @@ function restore() {
|
|
|
1092
1092
|
|
|
1093
1093
|
module.exports = { install, restore, readMap, resolveTwin, twinUrlVendor, twinnedHostVendors, unclaimedTwinnedHostPathMessage, appForwardHeaders, VENDOR_HOSTS };
|
|
1094
1094
|
|
|
1095
|
+
// WORLD TIME: an application process keeps the World's time, as its twins do (worldNow(), world-clock.ts). A World's
|
|
1096
|
+
// clock (TWIN_WORLD_CLOCK_FILE, one of world-clock.cjs's two forms) is read at most once a second, so a clock moved
|
|
1097
|
+
// while the application runs applies within a second; with no clock set, the process keeps the machine's time.
|
|
1098
|
+
// `Date` is the machine's own, wrapped so its `now()` and argument-less construction read the World's time; every other use
|
|
1099
|
+
// (a parsed date, `Date.UTC`, arithmetic, timers, `performance.now()`) is unchanged. The machine's own `Date.now` is
|
|
1100
|
+
// left under a global symbol, so the kernel's worldNow() applies a running clock's offset once in a process like this.
|
|
1101
|
+
// Without this, time moved in a World reaches its twins and not its application: a token the twins sign by the
|
|
1102
|
+
// World's time is refused by the application's (dub-stress findings row 107).
|
|
1103
|
+
function installWorldTime() {
|
|
1104
|
+
const file = process.env.TWIN_WORLD_CLOCK_FILE;
|
|
1105
|
+
if (!file || globalThis[Symbol.for('volter.machineDateNow')]) return;
|
|
1106
|
+
const fs = require('fs');
|
|
1107
|
+
const { parseClock, clockNowMs } = require('./world-clock.cjs');
|
|
1108
|
+
const MachineDate = Date;
|
|
1109
|
+
const machineNow = MachineDate.now.bind(MachineDate);
|
|
1110
|
+
let held = { checkedAt: -Infinity, mtimeMs: -1, clock: null };
|
|
1111
|
+
const clock = () => {
|
|
1112
|
+
const wall = machineNow();
|
|
1113
|
+
if (wall - held.checkedAt < 1000) return held.clock;
|
|
1114
|
+
held.checkedAt = wall;
|
|
1115
|
+
try {
|
|
1116
|
+
const mtimeMs = fs.statSync(file).mtimeMs;
|
|
1117
|
+
if (mtimeMs !== held.mtimeMs) held = { checkedAt: wall, mtimeMs, clock: parseClock(fs.readFileSync(file, 'utf8')) };
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
// no clock: the machine's time; a clock caught mid-write keeps the last one read until the next look
|
|
1120
|
+
if (error && error.code === 'ENOENT') held = { checkedAt: wall, mtimeMs: -1, clock: null };
|
|
1121
|
+
}
|
|
1122
|
+
return held.clock;
|
|
1123
|
+
};
|
|
1124
|
+
const worldMs = () => { const c = clock(); return c ? clockNowMs(c, machineNow()) : machineNow(); };
|
|
1125
|
+
// the machine's own Date, wrapped rather than subclassed, so identity holds everywhere: dates Node makes itself
|
|
1126
|
+
// (a file's mtime, structuredClone, a worker's message) are `instanceof Date`, `d.constructor === Date` stays true,
|
|
1127
|
+
// and Date.prototype is the machine's; only now() and argument-less construction read the World's time
|
|
1128
|
+
const WorldDate = new Proxy(MachineDate, {
|
|
1129
|
+
construct: (target, args, newTarget) => Reflect.construct(target, args.length ? args : [worldMs()], newTarget === WorldDate ? target : newTarget),
|
|
1130
|
+
// called as a function, Date() answers the current time's string, as the machine's does
|
|
1131
|
+
apply: () => new MachineDate(worldMs()).toString(),
|
|
1132
|
+
});
|
|
1133
|
+
Object.defineProperty(globalThis, Symbol.for('volter.machineDateNow'), { value: machineNow, enumerable: false });
|
|
1134
|
+
MachineDate.now = worldMs;
|
|
1135
|
+
Object.defineProperty(MachineDate.prototype, 'constructor', { value: WorldDate, writable: true, configurable: true, enumerable: false });
|
|
1136
|
+
globalThis.Date = WorldDate;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1095
1139
|
// Auto-install as a preload (`--require`); strict refusal also applies without twin URLs.
|
|
1096
1140
|
install();
|
|
1141
|
+
installWorldTime();
|
|
1097
1142
|
installPrismaAdapterShim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"network-policy.d.cts",
|
|
30
30
|
"app-route.cjs",
|
|
31
31
|
"vendor-hosts.cjs",
|
|
32
|
+
"world-clock.cjs",
|
|
33
|
+
"world-clock.d.cts",
|
|
32
34
|
"app-route.d.cts",
|
|
33
35
|
"generated",
|
|
34
36
|
"src",
|
|
@@ -73,10 +75,18 @@
|
|
|
73
75
|
"default": "./app-route.cjs"
|
|
74
76
|
},
|
|
75
77
|
"./vendor-hosts": "./vendor-hosts.cjs",
|
|
78
|
+
"./world-clock": {
|
|
79
|
+
"types": "./world-clock.d.cts",
|
|
80
|
+
"default": "./world-clock.cjs"
|
|
81
|
+
},
|
|
76
82
|
"./generated/pack-facts.json": "./generated/pack-facts.json",
|
|
77
83
|
"./lifecycle": {
|
|
78
84
|
"types": "./dist/src/lifecycle.d.ts",
|
|
79
85
|
"default": "./dist/src/lifecycle.js"
|
|
86
|
+
},
|
|
87
|
+
"./redis": {
|
|
88
|
+
"types": "./dist/src/redis/index.d.ts",
|
|
89
|
+
"default": "./dist/src/redis/index.js"
|
|
80
90
|
}
|
|
81
91
|
},
|
|
82
92
|
"scripts": {
|
package/src/actions.ts
CHANGED
|
@@ -17,9 +17,9 @@ import { randomUUID } from 'node:crypto';
|
|
|
17
17
|
import { dirname, join } from 'node:path';
|
|
18
18
|
import { canonicalJson, hashFieldValue, subjectKey } from './hash.ts';
|
|
19
19
|
import type { SubjectFields } from './hash.ts';
|
|
20
|
-
import { appendDurable, eventsLockPath, projectionLockPath, twinLog, withFileLock, worldPaths } from './storage.ts';
|
|
20
|
+
import { appendDurable, eventsLockPath, ownerStoreRoots, projectionLockPath, twinLog, withFileLock, withoutIdentityClaim, worldPaths } from './storage.ts';
|
|
21
21
|
import { landAsPlaceholder, placeholderPullActive } from './placeholder-remote.ts';
|
|
22
|
-
import { aliasesFrom, dropCheckpoint, landedCopy, landedIds, parentEntries, readTree, type Receipt } from './log.ts';
|
|
22
|
+
import { aliasesFrom, dropCheckpoint, landedCopy, landedIds, parentEntries, readTree, treeStamp, type Receipt } from './log.ts';
|
|
23
23
|
import { WorldServiceEventSchema } from './schemas.ts';
|
|
24
24
|
import { getActiveWorldStore } from './world-store.ts';
|
|
25
25
|
import type { WorldServiceEvent } from './types.ts';
|
|
@@ -439,6 +439,57 @@ export function projectResources(service: string, root?: string, opts: { until?:
|
|
|
439
439
|
return readTree(service, root, opts);
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
/** A read of another pack's store that cannot choose: the subject it looks for (or, with none named, the store itself)
|
|
443
|
+
* is held by more than one service of the World. A reader answers it as the vendor answers a credential it cannot
|
|
444
|
+
* resolve; it is never a server error. */
|
|
445
|
+
export class OwnerStoreAmbiguousError extends Error {
|
|
446
|
+
constructor(owner: string, roots: string[], subject?: { type: string; id: string }) {
|
|
447
|
+
super(`${subject ? `${subject.type} ${subject.id} of ` : ''}${owner}'s store is held by more than one service of this World (${roots.join(', ')}); a cross-pack read cannot choose between them`);
|
|
448
|
+
this.name = 'OwnerStoreAmbiguousError';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** One owner store as a cross-pack reader sees it: its rows (frozen, shared by every read until the store changes) and
|
|
453
|
+
* an index by `type:id`. Memoized per (owner, root) on the store's `treeStamp`, so a reader resolving a token on every
|
|
454
|
+
* request folds and copies the owner's tree only when the owner has written since. */
|
|
455
|
+
type OwnerIndex = { stamp: string; rows: readonly TwinResource[]; byKey: Map<string, TwinResource> };
|
|
456
|
+
const ownerIndexes = new Map<string, OwnerIndex>();
|
|
457
|
+
function ownerIndex(owner: string, root: string): OwnerIndex {
|
|
458
|
+
const key = `${owner}\0${root}`;
|
|
459
|
+
const stamp = treeStamp(owner, root);
|
|
460
|
+
const held = ownerIndexes.get(key);
|
|
461
|
+
if (held && held.stamp === stamp) return held;
|
|
462
|
+
const rows = readTree(owner, root).map((r) => Object.freeze(r));
|
|
463
|
+
const index: OwnerIndex = { stamp, rows: Object.freeze(rows), byKey: new Map(rows.map((r) => [`${r.type}:${r.id}`, r])) };
|
|
464
|
+
ownerIndexes.set(key, index);
|
|
465
|
+
return index;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, xidentity's tokens
|
|
470
|
+
* read by x, googleoauth's by googlecalendar; scripts/architecture.test.ts holds the declared reader-owner pairs): the
|
|
471
|
+
* owner's tree, from wherever the World keeps the owner's store (`ownerStoreRoots`). Read only: the rows are frozen and
|
|
472
|
+
* shared. It claims no journal identity (the twin answering is the reader).
|
|
473
|
+
*
|
|
474
|
+
* With `subject`, the store that holds that subject (the token a request presents): `[]` when none does, and
|
|
475
|
+
* `OwnerStoreAmbiguousError` only when more than one does, so a second service running the owning pack never breaks a
|
|
476
|
+
* lookup of what only one of them issued. Without it, the one store there is, and `OwnerStoreAmbiguousError` when the
|
|
477
|
+
* World holds two.
|
|
478
|
+
*/
|
|
479
|
+
export function projectOwnerResources(owner: string, root?: string, subject?: { type: string; id: string }): readonly TwinResource[] {
|
|
480
|
+
return withoutIdentityClaim(() => {
|
|
481
|
+
const roots = ownerStoreRoots(owner, root);
|
|
482
|
+
if (!subject) {
|
|
483
|
+
if (roots.length > 1) throw new OwnerStoreAmbiguousError(owner, roots);
|
|
484
|
+
return roots[0] ? ownerIndex(owner, roots[0]).rows : [];
|
|
485
|
+
}
|
|
486
|
+
const key = `${subject.type}:${subject.id}`;
|
|
487
|
+
const holding = roots.filter((r) => ownerIndex(owner, r).byKey.has(key));
|
|
488
|
+
if (holding.length > 1) throw new OwnerStoreAmbiguousError(owner, holding, subject);
|
|
489
|
+
return holding[0] ? ownerIndex(owner, holding[0]).rows : [];
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
442
493
|
/** The local → vendor id aliases the landed copies carry: a read by the id a caller was handed before
|
|
443
494
|
* its write was performed resolves to the row the vendor now owns. */
|
|
444
495
|
export function subjectAliases(service: string, root?: string): Map<string, string> {
|
package/src/index.ts
CHANGED
|
@@ -111,6 +111,9 @@ export {
|
|
|
111
111
|
withFileLock,
|
|
112
112
|
worldPaths,
|
|
113
113
|
worldStateRoot,
|
|
114
|
+
// Where another pack's store is in a World (architecture A3), and the variable the runtime names its data directory in.
|
|
115
|
+
ownerStoreRoots,
|
|
116
|
+
WORLD_DATA_ENV,
|
|
114
117
|
} from './storage.ts';
|
|
115
118
|
export type {
|
|
116
119
|
AppendEventResult,
|
|
@@ -190,6 +193,8 @@ export {
|
|
|
190
193
|
isTwinBookkeeping,
|
|
191
194
|
pendingTransactionCommits,
|
|
192
195
|
projectResources,
|
|
196
|
+
projectOwnerResources,
|
|
197
|
+
OwnerStoreAmbiguousError,
|
|
193
198
|
revertAction,
|
|
194
199
|
TwinActionPreconditionError, subjectAliases, resolveSubjectId, runWithCorrelationId, currentCorrelationId } from './actions.ts';
|
|
195
200
|
export type {
|