@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/src/storage.ts
CHANGED
|
@@ -59,13 +59,25 @@ function assertServiceName(service: string): string {
|
|
|
59
59
|
*/
|
|
60
60
|
let worldPathsObserver: ((service: string, root: string | undefined) => void) | undefined;
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
/** Install the observer; the one it replaces is returned (a test puts it back). */
|
|
63
|
+
export function observeWorldPaths(observer: ((service: string, root: string | undefined) => void) | undefined): ((service: string, root: string | undefined) => void) | undefined {
|
|
64
|
+
const previous = worldPathsObserver;
|
|
63
65
|
worldPathsObserver = observer;
|
|
66
|
+
return previous;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** While > 0, a read of ANOTHER pack's store is in progress (`projectOwnerResources`): that is not the request's twin
|
|
70
|
+
* saying who it is, so it claims no journal identity. */
|
|
71
|
+
let identityQuiet = 0;
|
|
72
|
+
/** Run `fn` without its worldPaths calls reaching the identity observer. */
|
|
73
|
+
export function withoutIdentityClaim<T>(fn: () => T): T {
|
|
74
|
+
identityQuiet++;
|
|
75
|
+
try { return fn(); } finally { identityQuiet--; }
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export function worldPaths(service: string, root?: string): WorldPaths {
|
|
67
79
|
const safeService = assertServiceName(service);
|
|
68
|
-
worldPathsObserver?.(safeService, root);
|
|
80
|
+
if (identityQuiet === 0) worldPathsObserver?.(safeService, root);
|
|
69
81
|
const resolvedRoot = projectRoot(root);
|
|
70
82
|
const dir = join(worldStateRoot(root), safeService);
|
|
71
83
|
return {
|
|
@@ -81,6 +93,44 @@ export function worldPaths(service: string, root?: string): WorldPaths {
|
|
|
81
93
|
};
|
|
82
94
|
}
|
|
83
95
|
|
|
96
|
+
/**
|
|
97
|
+
* THE WORLD'S DATA DIRECTORY, as the World runtime hands it to every service it starts. Each twin of a World runs on
|
|
98
|
+
* its own root, `<data>/<service id>` (world-runtime's startService and startColocatedServices), so one twin's
|
|
99
|
+
* `.volter/world/<service>` is not another's; `ownerStoreRoots` finds a store across them.
|
|
100
|
+
*/
|
|
101
|
+
export const WORLD_DATA_ENV = 'VOLTER_WORLD_DATA';
|
|
102
|
+
|
|
103
|
+
/** Does `root` hold `service`'s store: its log or its branch record (what a write or a fork leaves; a read leaves
|
|
104
|
+
* neither)? The paths are built without worldPaths, so a probe claims no journal identity. */
|
|
105
|
+
function holdsStore(service: string, root: string): boolean {
|
|
106
|
+
const dir = join(worldStateRoot(root), service);
|
|
107
|
+
const store = getActiveWorldStore();
|
|
108
|
+
return ['events.jsonl', 'actions.jsonl', 'branch.json'].some((file) => store.exists(join(dir, file)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The roots that hold the store of `owner` (ANOTHER pack's, read by contract: architecture A3), for a twin on `root`:
|
|
113
|
+
* - `[root]` when `root` itself holds that store (both packs on one root: `volter world serve`, a test, a pack serving
|
|
114
|
+
* several services on its root, as aws does);
|
|
115
|
+
* - else, when `root` is one of a World's service roots (a directory directly under the World's data directory), every
|
|
116
|
+
* other service root there that holds it (usually one; two when the World runs the owning pack twice);
|
|
117
|
+
* - else `[]`: nothing is issued yet, or the twin runs outside a World.
|
|
118
|
+
* Only a declared cross-pack read resolves through here (`projectOwnerResources`); a pack's reads of its OWN store never
|
|
119
|
+
* leave its root, so a fresh twin never reads a sibling that happens to run the same pack. Not memoized: it is a few
|
|
120
|
+
* stats per service root, and a store that appears after the first read (the owner's first write) must be found.
|
|
121
|
+
*/
|
|
122
|
+
export function ownerStoreRoots(owner: string, root?: string): string[] {
|
|
123
|
+
const safeOwner = assertServiceName(owner);
|
|
124
|
+
const own = projectRoot(root);
|
|
125
|
+
if (holdsStore(safeOwner, own)) return [own];
|
|
126
|
+
const data = typeof process === 'undefined' ? undefined : process.env[WORLD_DATA_ENV];
|
|
127
|
+
if (!data || dirname(own) !== resolve(data)) return [];
|
|
128
|
+
return getActiveWorldStore().list(dirname(own))
|
|
129
|
+
.map((name) => join(dirname(own), name))
|
|
130
|
+
.filter((candidate) => candidate !== own && holdsStore(safeOwner, candidate))
|
|
131
|
+
.sort();
|
|
132
|
+
}
|
|
133
|
+
|
|
84
134
|
function readJsonl<T>(path: string): T[] {
|
|
85
135
|
const rows: T[] = [];
|
|
86
136
|
for (const [index, line] of getActiveWorldStore().readLines(path).entries()) {
|
package/src/world-clock.ts
CHANGED
|
@@ -6,28 +6,31 @@
|
|
|
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.ts';
|
|
14
|
+
import clockForm from '../world-clock.cjs';
|
|
17
15
|
|
|
18
16
|
export const WORLD_CLOCK_ENV = 'TWIN_WORLD_CLOCK_FILE';
|
|
19
17
|
|
|
20
|
-
/** The
|
|
21
|
-
*
|
|
18
|
+
/** The machine's own time: the real Date.now even where the injector gives the process the World's time (inject.cjs
|
|
19
|
+
* leaves the original under this symbol), so a running clock's offset is applied once. */
|
|
20
|
+
const machineNow = (): number => {
|
|
21
|
+
const real = (globalThis as Record<symbol, unknown>)[Symbol.for('volter.machineDateNow')];
|
|
22
|
+
return typeof real === 'function' ? (real as () => number)() : Date.now();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** The world's "now": the clock file's time when one is configured (its frozen instant, or a running clock's time
|
|
26
|
+
* now); real wall-clock time otherwise. */
|
|
22
27
|
export function worldNow(): string {
|
|
23
28
|
const file = process.env[WORLD_CLOCK_ENV];
|
|
24
29
|
if (file && getActiveWorldStore().exists(file)) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// back to wall-clock (two services disagreeing about now breaks the world).
|
|
30
|
-
throw new Error(`world clock: ${file} does not contain a parseable ISO-8601 instant (got ${JSON.stringify(raw.slice(0, 40))})`);
|
|
30
|
+
// A configured-but-corrupt clock is a WORLD defect — parseClock fails loudly, never silently drifting back to
|
|
31
|
+
// wall-clock (two services disagreeing about now breaks the world).
|
|
32
|
+
const clock = clockForm.parseClock(getActiveWorldStore().read(file) ?? '');
|
|
33
|
+
return new Date(clockForm.clockNowMs(clock, machineNow())).toISOString();
|
|
31
34
|
}
|
|
32
|
-
return new Date().toISOString();
|
|
35
|
+
return new Date(machineNow()).toISOString();
|
|
33
36
|
}
|
package/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
|
*/
|
package/world-clock.cjs
ADDED
|
@@ -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;
|