@volter/world-core 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/app-route.cjs +154 -0
  4. package/app-route.d.cts +7 -0
  5. package/attach.cjs +80 -0
  6. package/dist/app-route.cjs +154 -0
  7. package/dist/app-route.d.cts +7 -0
  8. package/dist/attach.cjs +80 -0
  9. package/dist/generated/pack-facts.json +4306 -0
  10. package/dist/inject.cjs +1097 -0
  11. package/dist/network-policy.cjs +92 -0
  12. package/dist/network-policy.d.cts +10 -0
  13. package/dist/src/actions.d.ts +276 -0
  14. package/dist/src/actions.js +436 -0
  15. package/dist/src/ancestry.d.ts +22 -0
  16. package/dist/src/ancestry.js +238 -0
  17. package/dist/src/args.d.ts +3 -0
  18. package/dist/src/args.js +12 -0
  19. package/dist/src/blob-store.d.ts +55 -0
  20. package/dist/src/blob-store.js +186 -0
  21. package/dist/src/brand-tokens.d.ts +2 -0
  22. package/dist/src/brand-tokens.js +17 -0
  23. package/dist/src/changeset.d.ts +431 -0
  24. package/dist/src/changeset.js +0 -0
  25. package/dist/src/client-bundle.d.ts +1 -0
  26. package/dist/src/client-bundle.js +28 -0
  27. package/dist/src/credential.d.ts +38 -0
  28. package/dist/src/credential.js +114 -0
  29. package/dist/src/derived-core.d.ts +452 -0
  30. package/dist/src/derived-core.js +782 -0
  31. package/dist/src/derived.d.ts +84 -0
  32. package/dist/src/derived.js +122 -0
  33. package/dist/src/emit.d.ts +106 -0
  34. package/dist/src/emit.js +157 -0
  35. package/dist/src/executor.d.ts +120 -0
  36. package/dist/src/executor.js +387 -0
  37. package/dist/src/file-response.d.ts +3 -0
  38. package/dist/src/file-response.js +22 -0
  39. package/dist/src/fork.d.ts +26 -0
  40. package/dist/src/fork.js +68 -0
  41. package/dist/src/git/history.d.ts +36 -0
  42. package/dist/src/git/history.js +298 -0
  43. package/dist/src/git/index.d.ts +6 -0
  44. package/dist/src/git/index.js +6 -0
  45. package/dist/src/git/inflate.d.ts +11 -0
  46. package/dist/src/git/inflate.js +194 -0
  47. package/dist/src/git/objects.d.ts +64 -0
  48. package/dist/src/git/objects.js +161 -0
  49. package/dist/src/git/pack.d.ts +14 -0
  50. package/dist/src/git/pack.js +199 -0
  51. package/dist/src/git/refs.d.ts +19 -0
  52. package/dist/src/git/refs.js +35 -0
  53. package/dist/src/git/smart-http.d.ts +45 -0
  54. package/dist/src/git/smart-http.js +223 -0
  55. package/dist/src/hash.d.ts +38 -0
  56. package/dist/src/hash.js +48 -0
  57. package/dist/src/head.d.ts +140 -0
  58. package/dist/src/head.js +313 -0
  59. package/dist/src/history.d.ts +76 -0
  60. package/dist/src/history.js +322 -0
  61. package/dist/src/index.d.ts +73 -0
  62. package/dist/src/index.js +98 -0
  63. package/dist/src/lifecycle.d.ts +1 -0
  64. package/dist/src/lifecycle.js +8 -0
  65. package/dist/src/log.d.ts +254 -0
  66. package/dist/src/log.js +801 -0
  67. package/dist/src/mirror-shell.d.ts +2 -0
  68. package/dist/src/mirror-shell.js +13 -0
  69. package/dist/src/observe.d.ts +49 -0
  70. package/dist/src/observe.js +148 -0
  71. package/dist/src/pack-assets.d.ts +30 -0
  72. package/dist/src/pack-assets.js +88 -0
  73. package/dist/src/packRegistry.d.ts +374 -0
  74. package/dist/src/packRegistry.js +142 -0
  75. package/dist/src/placeholder-remote.d.ts +22 -0
  76. package/dist/src/placeholder-remote.js +86 -0
  77. package/dist/src/proxy.d.ts +25 -0
  78. package/dist/src/proxy.js +155 -0
  79. package/dist/src/rateBudget.d.ts +367 -0
  80. package/dist/src/rateBudget.js +925 -0
  81. package/dist/src/references.d.ts +18 -0
  82. package/dist/src/references.js +27 -0
  83. package/dist/src/remote-execute.d.ts +22 -0
  84. package/dist/src/remote-execute.js +1 -0
  85. package/dist/src/resource-blob.d.ts +10 -0
  86. package/dist/src/resource-blob.js +56 -0
  87. package/dist/src/scenario.d.ts +197 -0
  88. package/dist/src/scenario.js +425 -0
  89. package/dist/src/schemas.d.ts +78 -0
  90. package/dist/src/schemas.js +50 -0
  91. package/dist/src/serve-http.d.ts +48 -0
  92. package/dist/src/serve-http.js +340 -0
  93. package/dist/src/serve.d.ts +147 -0
  94. package/dist/src/serve.js +507 -0
  95. package/dist/src/shared-blob-index.d.ts +4 -0
  96. package/dist/src/shared-blob-index.js +126 -0
  97. package/dist/src/state-system.d.ts +70 -0
  98. package/dist/src/state-system.js +90 -0
  99. package/dist/src/storage.d.ts +101 -0
  100. package/dist/src/storage.js +337 -0
  101. package/dist/src/twin-fetch.d.ts +64 -0
  102. package/dist/src/twin-fetch.js +91 -0
  103. package/dist/src/types.d.ts +40 -0
  104. package/dist/src/types.js +1 -0
  105. package/dist/src/v1-removed.d.ts +159 -0
  106. package/dist/src/v1-removed.js +124 -0
  107. package/dist/src/volter-home.d.ts +5 -0
  108. package/dist/src/volter-home.js +10 -0
  109. package/dist/src/world-clock.d.ts +4 -0
  110. package/dist/src/world-clock.js +32 -0
  111. package/dist/src/world-env.d.ts +3 -0
  112. package/dist/src/world-env.js +22 -0
  113. package/dist/src/world-store-sql.d.ts +27 -0
  114. package/dist/src/world-store-sql.js +86 -0
  115. package/dist/src/world-store.d.ts +168 -0
  116. package/dist/src/world-store.js +475 -0
  117. package/dist/src/worldConfig.d.ts +9 -0
  118. package/dist/src/worldConfig.js +17 -0
  119. package/dist/stream-bridge.cjs +80 -0
  120. package/dist/vendor-hosts.cjs +200 -0
  121. package/generated/pack-facts.json +4306 -0
  122. package/inject.cjs +1097 -0
  123. package/network-policy.cjs +92 -0
  124. package/network-policy.d.cts +10 -0
  125. package/package.json +103 -0
  126. package/src/actions.ts +564 -0
  127. package/src/ancestry.ts +213 -0
  128. package/src/args.ts +14 -0
  129. package/src/blob-store.ts +185 -0
  130. package/src/brand-tokens.ts +17 -0
  131. package/src/changeset.ts +1032 -0
  132. package/src/client-bundle.ts +29 -0
  133. package/src/credential.ts +140 -0
  134. package/src/derived-core.ts +1004 -0
  135. package/src/derived.ts +176 -0
  136. package/src/emit.ts +242 -0
  137. package/src/executor.ts +431 -0
  138. package/src/file-response.ts +22 -0
  139. package/src/fork.ts +89 -0
  140. package/src/git/history.ts +177 -0
  141. package/src/git/index.ts +6 -0
  142. package/src/git/inflate.ts +125 -0
  143. package/src/git/objects.ts +110 -0
  144. package/src/git/pack.ts +105 -0
  145. package/src/git/refs.ts +25 -0
  146. package/src/git/smart-http.ts +149 -0
  147. package/src/hash.ts +66 -0
  148. package/src/head.ts +318 -0
  149. package/src/history.ts +246 -0
  150. package/src/index.ts +323 -0
  151. package/src/lifecycle.ts +8 -0
  152. package/src/log.ts +793 -0
  153. package/src/mirror-shell.ts +15 -0
  154. package/src/observe.ts +130 -0
  155. package/src/pack-assets.ts +81 -0
  156. package/src/packRegistry.ts +408 -0
  157. package/src/placeholder-remote.ts +81 -0
  158. package/src/proxy.ts +183 -0
  159. package/src/rateBudget.ts +1115 -0
  160. package/src/references.ts +46 -0
  161. package/src/remote-execute.ts +26 -0
  162. package/src/resource-blob.ts +57 -0
  163. package/src/scenario.ts +479 -0
  164. package/src/schemas.ts +56 -0
  165. package/src/serve-http.ts +299 -0
  166. package/src/serve.ts +618 -0
  167. package/src/shared-blob-index.ts +108 -0
  168. package/src/state-system.ts +115 -0
  169. package/src/storage.ts +407 -0
  170. package/src/twin-fetch.ts +147 -0
  171. package/src/types.ts +50 -0
  172. package/src/v1-removed.ts +172 -0
  173. package/src/volter-home.ts +11 -0
  174. package/src/world-clock.ts +33 -0
  175. package/src/world-env.ts +18 -0
  176. package/src/world-store-sql.ts +118 -0
  177. package/src/world-store.ts +572 -0
  178. package/src/worldConfig.ts +27 -0
  179. package/stream-bridge.cjs +80 -0
  180. package/vendor-hosts.cjs +200 -0
package/src/history.ts ADDED
@@ -0,0 +1,246 @@
1
+ // Immutable views over shared append-only segments. A position is meaningful within a view.
2
+ import { createHash } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { resolveReferences } from './references.ts';
5
+ import { canonicalJson } from './hash.ts';
6
+ import { canonicalStatePath, checkParent, commitParentPin, pinParent, stateGeneration, withAncestryLock } from './ancestry.ts';
7
+ import { getActiveWorldStore } from './world-store.ts';
8
+ import { worldPaths } from './storage.ts';
9
+ import { aliasesFrom, foldHistory, branchEntries, isUrlParent, readBranchMeta, toEntry, type Entry } from './log.ts';
10
+
11
+ export type HistoryLayout = { base?: HistoryLayout; parent: number; branch: number };
12
+ type Segment = { directory: string; generation: string; file: 'events.jsonl' | 'actions.jsonl' | 'origin.jsonl'; start: number; count: number; digest: string };
13
+ export type HistoryOrigin = { at: string; directory: string; generation: string; view: string; position: number; remoteView?: string; depth: number; incomplete?: boolean };
14
+ export type HistoryView = { version: 1; owner: { directory: string; generation: string }; layout: HistoryLayout; segments: Segment[]; origin?: HistoryOrigin };
15
+ export type HistoryReference = { view: string; position: number };
16
+ export function validateHistoryOrigin(origin: HistoryOrigin, layout?: HistoryLayout): void {
17
+ if (!origin || typeof origin.at !== 'string' || !origin.at || typeof origin.directory !== 'string' || !origin.directory || typeof origin.generation !== 'string' || !origin.generation || !/^[a-f0-9]{64}$/.test(origin.view) || (origin.remoteView !== undefined && !/^[a-f0-9]{64}$/.test(origin.remoteView)) || !Number.isSafeInteger(origin.position) || origin.position < 0 || !Number.isSafeInteger(origin.depth) || origin.depth < 0 || origin.depth > 128 || (origin.incomplete !== undefined && typeof origin.incomplete !== 'boolean')) throw new Error('Invalid tracked origin metadata');
18
+ if (layout) {
19
+ let layer = layout;
20
+ for (let i = 0; i < origin.depth; i++) { if (!layer.base) throw new Error('Tracked origin layer is absent from the history layout'); layer = layer.base; }
21
+ if (!origin.incomplete && historyLength(layer) !== origin.position) throw new Error('Tracked origin position does not match its history layer');
22
+ }
23
+ }
24
+ export const historyEntryTime = (entry: Entry): number => Date.parse(entry.receipt?.at ?? entry.occurredAt);
25
+ export const historyDigest = (value: unknown): string => createHash('sha256').update(canonicalJson(value)).digest('hex');
26
+ export function historyLength(layout: HistoryLayout, depth = 0): number {
27
+ if (!layout || depth > 128 || !Number.isSafeInteger(layout.parent) || layout.parent < 0 || !Number.isSafeInteger(layout.branch) || layout.branch < 0) throw new Error('Invalid history layout');
28
+ const length = (layout.base ? historyLength(layout.base, depth + 1) : 0) + layout.parent + layout.branch;
29
+ if (!Number.isSafeInteger(length)) throw new Error('History position exceeds the safe integer range');
30
+ return length;
31
+ }
32
+ const viewPath = (directory: string, id: string) => {
33
+ if (!/^[a-f0-9]{64}$/.test(id)) throw new Error('Invalid history view identity');
34
+ return join(directory, 'views', `${id}.json`);
35
+ };
36
+ function rows(directory: string, file: Segment['file']): Entry[] {
37
+ return getActiveWorldStore().readLines(join(directory, file)).filter(line => line.trim()).map(line => toEntry(JSON.parse(line)));
38
+ }
39
+ function sliceLayout(layout: HistoryLayout, at: number): HistoryLayout {
40
+ const base = layout.base ? historyLength(layout.base) : 0;
41
+ return { ...(layout.base ? { base: sliceLayout(layout.base, Math.min(at, base)) } : {}), parent: Math.min(Math.max(0, at - base), layout.parent), branch: Math.min(Math.max(0, at - base - layout.parent), layout.branch) };
42
+ }
43
+ export function readHistoryView(directory: string, id: string): HistoryView {
44
+ const raw = getActiveWorldStore().read(viewPath(directory, id));
45
+ if (raw === null) throw new Error(`Missing retained history view ${id}`);
46
+ const { dependencies: _dependencies, ...view } = JSON.parse(raw) as HistoryView & { dependencies?: unknown };
47
+ if (view.version !== 1 || historyDigest({ ...view, dependencies: _dependencies }) !== id) throw new Error(`Invalid retained history view ${id}`);
48
+ historyLength(view.layout);
49
+ if (view.origin) validateHistoryOrigin(view.origin, view.layout);
50
+ if (!view.owner || typeof view.owner.directory !== 'string' || typeof view.owner.generation !== 'string' || !Array.isArray(view.segments) || view.segments.some(s => typeof s.directory !== 'string' || typeof s.generation !== 'string' || !['events.jsonl', 'actions.jsonl', 'origin.jsonl'].includes(s.file) || !Number.isSafeInteger(s.start) || s.start < 0 || !Number.isSafeInteger(s.count) || s.count < 0 || !/^[a-f0-9]{64}$/.test(s.digest))) throw new Error('Invalid history segments');
51
+ return view;
52
+ }
53
+ export function historyEntries(view: HistoryView): Entry[] {
54
+ checkParent(view.owner.directory, view.owner.generation);
55
+ const entries = view.segments.flatMap(span => {
56
+ checkParent(span.directory, span.generation);
57
+ const entries = rows(span.directory, span.file).slice(span.start, span.start + span.count);
58
+ if (entries.length !== span.count || historyDigest(entries) !== span.digest) throw new Error(`Retained history prefix was changed: ${span.directory}/${span.file}`);
59
+ return entries;
60
+ });
61
+ if (entries.length !== historyLength(view.layout)) throw new Error('Invalid history view layout');
62
+ return entries;
63
+ }
64
+ export function historyPrefix(view: HistoryView, at: number): HistoryView {
65
+ if (!Number.isInteger(at) || at < 0 || at > historyLength(view.layout)) throw new Error(`Invalid history position ${at}`);
66
+ historyEntries(view);
67
+ let left = at;
68
+ const segments: Segment[] = [];
69
+ for (const span of view.segments) {
70
+ if (left === 0) break;
71
+ const count = Math.min(left, span.count);
72
+ segments.push(count === span.count ? span : { ...span, count, digest: historyDigest(rows(span.directory, span.file).slice(span.start, span.start + count)) });
73
+ left -= count;
74
+ }
75
+ return { ...view, layout: sliceLayout(view.layout, at), segments, ...(view.origin && at < view.origin.position ? { origin: { ...view.origin, incomplete: true } } : {}) };
76
+ }
77
+ export function saveHistoryView(directory: string, view: HistoryView): string {
78
+ return withAncestryLock(() => {
79
+ if (view.origin) validateHistoryOrigin(view.origin, view.layout);
80
+ const dependencies = [...new Map([view.owner, ...view.segments, ...(view.origin ? [view.origin] : [])].map(s => [s.directory, { directory: s.directory, generation: s.generation }])).values()];
81
+ const id = historyDigest({ ...view, dependencies }); const file = viewPath(directory, id);
82
+ if (getActiveWorldStore().exists(file)) return id;
83
+ for (const dependency of dependencies) pinParent(dependency.directory, file, dependency.generation);
84
+ getActiveWorldStore().writeAtomic(file, JSON.stringify({ ...view, dependencies }));
85
+ for (const dependency of dependencies) commitParentPin(dependency.directory, file);
86
+ return id;
87
+ });
88
+ }
89
+ /** What an inherited history's content stands on, as file facts: every segment file the view names
90
+ * (and the view document itself) and each named directory's ancestry record (its generation). A read cache keyed by this sees an
91
+ * ancestor's retained history truncated, replaced or its directory re-created, which the full read
92
+ * refuses (`historyEntries`), instead of serving a tree folded from the history it replaced. */
93
+ export function inheritedHistoryKey(service: string, root?: string): string {
94
+ const parent = readBranchMeta(service, root)?.parent;
95
+ if (!parent?.view) return '';
96
+ const store = getActiveWorldStore();
97
+ const fact = (path: string): string => { const st = store.stat(path); return st ? `${st.size}:${st.mtimeMs}` : '-'; };
98
+ const directory = parent.viewDirectory ?? (isUrlParent(parent.at) ? worldPaths(service, root).dir : worldPaths(service, parent.at).dir);
99
+ let raw: string | null; let at: string;
100
+ try { at = viewPath(directory, parent.view); raw = store.read(at); } catch { return 'invalid-view'; }
101
+ if (raw === null) return 'missing-view';
102
+ const view = JSON.parse(raw) as HistoryView;
103
+ const directories = new Set<string>([view.owner?.directory ?? '', ...(view.segments ?? []).map((sp) => sp.directory)]);
104
+ if (!isUrlParent(parent.at)) directories.add(worldPaths(service, parent.at).dir);
105
+ const files = [...new Set((view.segments ?? []).map((sp) => join(sp.directory, sp.file)))].sort();
106
+ return `${at}=${fact(at)};${files.map((f) => `${f}=${fact(f)}`).join(',')};${[...directories].sort().map((d) => `${d}=${fact(join(d, 'ancestry.json'))}`).join(',')}`;
107
+ }
108
+ export function inheritedHistory(service: string, root?: string): HistoryView | undefined {
109
+ const parent = readBranchMeta(service, root)?.parent;
110
+ if (!parent) return undefined;
111
+ if (!parent.view) throw new Error('Branch has no immutable history view; recreate this disposable World');
112
+ const directory = parent.viewDirectory ?? (isUrlParent(parent.at) ? worldPaths(service, root).dir : worldPaths(service, parent.at).dir);
113
+ if (parent.viewDirectory && canonicalStatePath(parent.viewDirectory) !== canonicalStatePath(worldPaths(service, root).dir)) throw new Error('Composed history view must belong to this branch');
114
+ if (!isUrlParent(parent.at)) checkParent(worldPaths(service, parent.at).dir, parent.generation);
115
+ return historyPrefix(readHistoryView(directory, parent.view), parent.position);
116
+ }
117
+ /** Capture under the same coordinator as append and complete observation batches. */
118
+ export function captureHistory(service: string, root?: string): { view: string; position: number; descriptor: HistoryView } {
119
+ return withAncestryLock(() => {
120
+ const directory = canonicalStatePath(worldPaths(service, root).dir);
121
+ getActiveWorldStore().mkdir(directory);
122
+ if (!getActiveWorldStore().exists(join(directory, 'history-format.json'))) getActiveWorldStore().writeAtomic(join(directory, 'history-format.json'), '{"version":1}');
123
+ const generation = stateGeneration(directory);
124
+ const inherited = inheritedHistory(service, root);
125
+ const parent = rows(directory, 'events.jsonl'); const branch = branchEntries(service, root);
126
+ const origin = readBranchMeta(service, root)?.origin;
127
+ const descriptor: HistoryView = { version: 1, owner: { directory, generation }, layout: { ...(inherited ? { base: inherited.layout } : {}), parent: parent.length, branch: branch.length }, segments: [...(inherited?.segments ?? [])], ...(origin ? { origin: { ...origin, depth: origin.depth + 1 } } : {}) };
128
+ for (const [file, entries] of [['events.jsonl', parent], ['actions.jsonl', branch]] as const) {
129
+ if (entries.length) descriptor.segments.push({ directory, generation, file, start: 0, count: entries.length, digest: historyDigest(entries) });
130
+ }
131
+ return { view: saveHistoryView(directory, descriptor), position: historyLength(descriptor.layout), descriptor };
132
+ });
133
+ }
134
+ export function originHead(service: string, root?: string): { view: string; remoteView: string } | null {
135
+ const raw = getActiveWorldStore().read(join(worldPaths(service, root).dir, 'origin-head.json'));
136
+ return raw === null ? null : JSON.parse(raw);
137
+ }
138
+ /** A complete remote view shares cached rows by content, never by mutable offsets or entry ID. */
139
+ export function publishOriginHistory(service: string, root: string, remoteView: string, layout: HistoryLayout, entries: Entry[]): { view: string; appended: number } {
140
+ return withAncestryLock(() => {
141
+ if (entries.length !== historyLength(layout)) throw new Error('Incomplete origin history view');
142
+ const directory = canonicalStatePath(worldPaths(service, root).dir); const store = getActiveWorldStore(); store.mkdir(directory);
143
+ if (!store.exists(join(directory, 'history-format.json'))) store.writeAtomic(join(directory, 'history-format.json'), '{"version":1}');
144
+ const generation = stateGeneration(directory); const cached = rows(directory, 'origin.jsonl');
145
+ const index = new Map(cached.map((entry, i) => [historyDigest(entry), i]));
146
+ const segments: Segment[] = []; let appended = 0;
147
+ for (const entry of entries) {
148
+ const key = historyDigest(entry); let start = index.get(key);
149
+ if (start === undefined) { start = cached.length; store.append(join(directory, 'origin.jsonl'), `${JSON.stringify(entry)}\n`); cached.push(entry); index.set(key, start); appended++; }
150
+ const last = segments.at(-1);
151
+ if (last && last.start + last.count === start) { last.count++; }
152
+ else segments.push({ directory, generation, file: 'origin.jsonl', start, count: 1, digest: historyDigest([entry]) });
153
+ }
154
+ for (const span of segments) span.digest = historyDigest(cached.slice(span.start, span.start + span.count));
155
+ const view = saveHistoryView(directory, { version: 1, owner: { directory, generation }, layout, segments });
156
+ store.writeAtomic(join(directory, 'origin-head.json'), JSON.stringify({ view, remoteView }));
157
+ return { view, appended };
158
+ });
159
+ }
160
+
161
+ /** A changeset binds the complete inherited view, including this root's observations. */
162
+ export function captureParentHistory(service: string, root?: string): HistoryReference {
163
+ return withAncestryLock(() => {
164
+ const head = captureHistory(service, root);
165
+ const position = head.position - head.descriptor.layout.branch;
166
+ return { view: saveHistoryView(worldPaths(service, root).dir, historyPrefix(head.descriptor, position)), position };
167
+ });
168
+ }
169
+ /** Compare evaluated views, so removals, reordered rows and layout changes count as drift. */
170
+ export function historyChanges(service: string, before: HistoryView, after: HistoryView): Entry[] {
171
+ const oldEntries = historyEntries(before); const newEntries = historyEntries(after);
172
+ const oldTree = foldHistory(oldEntries, before.layout, service); const newTree = foldHistory(newEntries, after.layout, service);
173
+ const aliases = aliasesFrom(newEntries); const changes: Entry[] = [];
174
+ for (const key of new Set([...oldTree.keys(), ...newTree.keys()])) {
175
+ const old = oldTree.get(key); const next = newTree.get(key) ?? (old && aliases.has(key) ? newTree.get(`${old.type}:${aliases.get(key)}`) : undefined);
176
+ const fields: Record<string, unknown> = {};
177
+ const oldFields = old ? resolveReferences(service, old.type, old.fields, aliases) : {};
178
+ for (const field of new Set([...Object.keys(oldFields), ...Object.keys(next?.fields ?? {})])) if (canonicalJson(oldFields[field]) !== canonicalJson(next?.fields[field])) fields[field] = next?.fields[field];
179
+ if (old && !next) fields.deleted = true;
180
+ if (!Object.keys(fields).length) continue;
181
+ const subject = old ?? next!;
182
+ changes.push({ id: `view-delta:${historyDigest({ key, fields })}`, service, op: 'set', subject: { type: subject.type, id: subject.id }, occurredAt: next?.updatedAt ?? old!.updatedAt, fields });
183
+ }
184
+ return changes;
185
+ }
186
+
187
+ /** A timestamp can select across layers; represent it as a view, never a misleading offset. */
188
+ export function historyAtInstant(service: string, instant: string, root?: string, selectedView?: string): HistoryReference {
189
+ return withAncestryLock(() => {
190
+ const stamp = Date.parse(instant); if (!Number.isFinite(stamp)) throw new Error('Invalid history instant');
191
+ const directory = worldPaths(service, root).dir;
192
+ const source = selectedView ? readHistoryView(directory, selectedView) : captureHistory(service, root).descriptor;
193
+ const entries = historyEntries(source); const keep = entries.map(e => historyEntryTime(e) <= stamp);
194
+ // A contiguous observation batch is one indivisible look.
195
+ for (let i = 0; i < entries.length;) {
196
+ let end = i + 1; while (entries[i]?.batch && end < entries.length && entries[end]?.batch === entries[i]?.batch) end++;
197
+ if (keep.slice(i, end).some(Boolean)) for (let j = i; j < end; j++) keep[j] = true;
198
+ i = end;
199
+ }
200
+ let offset = 0;
201
+ const layoutOf = (layout: HistoryLayout): HistoryLayout => {
202
+ const base = layout.base ? layoutOf(layout.base) : undefined;
203
+ const parent = keep.slice(offset, offset + layout.parent).filter(Boolean).length; offset += layout.parent;
204
+ const branch = keep.slice(offset, offset + layout.branch).filter(Boolean).length; offset += layout.branch;
205
+ return { ...(base ? { base } : {}), parent, branch };
206
+ };
207
+ const layout = layoutOf(source.layout); const segments: Segment[] = []; offset = 0;
208
+ for (const span of source.segments) {
209
+ for (let i = 0; i < span.count;) {
210
+ if (!keep[offset + i]) { i++; continue; }
211
+ const start = i; while (i < span.count && keep[offset + i]) i++;
212
+ segments.push({ ...span, start: span.start + start, count: i - start, digest: historyDigest(entries.slice(offset + start, offset + i)) });
213
+ }
214
+ offset += span.count;
215
+ }
216
+ const view = saveHistoryView(directory, { version: 1, owner: source.owner, layout, segments, ...(source.origin ? { origin: { ...source.origin, ...(keep.slice(0, source.origin.position).some(v => !v) ? { incomplete: true } : {}) } } : {}) });
217
+ return { view, position: historyLength(layout) };
218
+ });
219
+ }
220
+
221
+ /** Replace only the tracked origin layer; inherited local layers keep their exact projection order. */
222
+ export function replaceHistoryOrigin(previous: HistoryView, origin: HistoryOrigin, next: HistoryOrigin): HistoryView {
223
+ let layer = previous.layout;
224
+ for (let depth = 0; depth < origin.depth; depth++) {
225
+ if (!layer.base) throw new Error('Tracked origin layer is absent from the inherited view');
226
+ layer = layer.base;
227
+ }
228
+ const count = historyLength(layer);
229
+ const before = historyEntries(previous);
230
+ checkParent(origin.directory, origin.generation);
231
+ if (!origin.incomplete) {
232
+ const expected = historyPrefix(readHistoryView(origin.directory, origin.view), origin.position);
233
+ if (count !== origin.position || historyDigest(before.slice(0, count)) !== historyDigest(historyEntries(expected)) || historyDigest(layer) !== historyDigest(expected.layout)) throw new Error('Tracked origin does not match the inherited history layer');
234
+ }
235
+ checkParent(next.directory, next.generation);
236
+ const replacement = historyPrefix(readHistoryView(next.directory, next.view), next.position);
237
+ const suffix: Segment[] = [];
238
+ let skip = count;
239
+ for (const span of previous.segments) {
240
+ if (skip >= span.count) { skip -= span.count; continue; }
241
+ suffix.push(skip === 0 ? span : { ...span, start: span.start + skip, count: span.count - skip, digest: historyDigest(rows(span.directory, span.file).slice(span.start + skip, span.start + span.count)) });
242
+ skip = 0;
243
+ }
244
+ const layoutOf = (layout: HistoryLayout, depth: number): HistoryLayout => depth === 0 ? replacement.layout : { ...layout, base: layoutOf(layout.base!, depth - 1) };
245
+ return { ...previous, layout: layoutOf(previous.layout, origin.depth), segments: [...replacement.segments, ...suffix], origin: { ...next, depth: origin.depth } };
246
+ }
package/src/index.ts ADDED
@@ -0,0 +1,323 @@
1
+ // @volter/world-core — the shared state kernel and vendor-independent runtime libraries.
2
+ // Protocol-2 packs read the checkpointed tree and write through the head's state system.
3
+ // The kernel owns logs, branches, landing and receipts; each pack owns its vendor's wire
4
+ // and resource semantics. See docs/concepts/the-model.md and docs/contributing/architecture.md.
5
+ // Compatibility exports below are not a second state model. Refer to their implementations
6
+ // before using older operator helpers; removed v1 entry points fail explicitly.
7
+ // Conformance/validation tooling (capability + spec + recorded-diff + UI harnesses and
8
+ // the spec derivers) is NOT part of the runtime kernel — it lives in @volter/world-tooling,
9
+ // a dev dependency. A twin runs without it; only tests and the conformance scripts use it.
10
+ // Pack registry — vendor twins self-describe (TwinPack) so tooling discovers them.
11
+ export { clearRegistry, getPack, hasPack, listPacks, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from './packRegistry.ts';
12
+ export type { PackTransport, PullPosture, PullTrigger, PullVendor, RoundTripWrite, TwinPack } from './packRegistry.ts';
13
+ // The DELIVER verb (`emit`) — vendor-agnostic engine + CLI glue; packs declare a TwinEmitter
14
+ // (their event catalog, endpoints-from-state, and signed synthesis) on `TwinPack.emitter`.
15
+ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from './scenario.ts';
16
+ export { WORLD_CLOCK_ENV, worldNow } from './world-clock.ts';
17
+ export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.ts';
18
+ export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, twinPublicBase } from './twin-fetch.ts';
19
+ export { compileSurface, createDerivedFetch, matchOperation } from './derived.ts';
20
+ export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface } from './derived.ts';
21
+ export { bindSemantics, coreFor, crossCutting, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.ts';
22
+ export type { Actor, CoreScope, DerivedManifest, ErrorSpec, FieldRule, ResourceDecl, ScreenDecl, Semantics, SemanticsContext, StateField, Transition, TransitionObserver } from './derived-core.ts';
23
+ export type { RemoteExecute, RemoteExecuteRequest, RemoteExecuteResponse } from './remote-execute.ts';
24
+ export type { TwinFetchAdapterConfig, TwinFetchHandlerRequest, TwinFetchHandlerResult, TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.ts';
25
+ export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioExtractorSpec, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord, ScenarioStatus } from './scenario.ts';
26
+ export { emitTwinEvent, eventSubscriptionMatches, listEmittable, runEmitCli } from './emit.ts';
27
+ export type { EmitDeliveryResult, EmitEndpoint, EmitReport, EmittableEvent, SynthesizedDelivery, TwinEmitter } from './emit.ts';
28
+ // The vendor-agnostic CLIENT-SIDE RATE BUDGET — the fail-closed backstop a pack's guarded client
29
+ // routes every live vendor call through. The MECHANISM is here; the per-vendor ceiling/window/
30
+ // weights are DATA the pack declares (`TwinPack.rateBudget` / `declareRateBudget`). Exported so an
31
+ // operator can inspect spend (`snapshot`) and a caller can catch `RateBudgetError` by type; there
32
+ // is deliberately no export that disables the guard, and a vendor with no declaration falls back
33
+ // to `DEFAULT_RATE_BUDGET` rather than to no limit at all.
34
+ export {
35
+ DEFAULT_RATE_BUDGET,
36
+ MAX_RATE_BUDGET_CEILING,
37
+ MAX_RATE_BUDGET_WINDOW_MS,
38
+ MIN_RATE_BUDGET_WEIGHT,
39
+ MIN_RATE_BUDGET_WINDOW_MS,
40
+ RateBudget,
41
+ RateBudgetError,
42
+ declareRateBudget,
43
+ hasRateBudgetDeclaration,
44
+ listRateBudgets,
45
+ priceCall,
46
+ rateBudgetPath,
47
+ rateBudgetPolicy,
48
+ rateBudgetWeight,
49
+ resetToSeconds,
50
+ assertBudgetGuardIntact,
51
+ } from './rateBudget.ts';
52
+ export type {
53
+ RateBudgetDeclaration,
54
+ RateBudgetErrorKind,
55
+ RateBudgetOptions,
56
+ RateBudgetPolicy,
57
+ RateBudgetReservation,
58
+ RateBudgetSnapshot,
59
+ RateBudgetWeightRule,
60
+ } from './rateBudget.ts';
61
+ export {
62
+ GenericWorldStateSchema,
63
+ WorldActorSchema,
64
+ WorldExternalRefSchema,
65
+ WorldServiceEventSchema,
66
+ WorldSubjectSchema,
67
+ } from './schemas.ts';
68
+ // NOTE: source books (event→tracker-source mapping) moved OUT of the world — it was
69
+ // the last tracker coupling. It now lives at `@volter/tracker/world-source-books`,
70
+ // so `@volter/world-core` is a pure twin runtime.
71
+ export {
72
+ loadWorldConfig,
73
+ } from './worldConfig.ts';
74
+ export type {
75
+ WorldConfig,
76
+ WorldServiceConfig,
77
+ } from './worldConfig.ts';
78
+ export {
79
+ DELTA_TYPE_SUFFIX,
80
+ diffSubjectFields,
81
+ hashFieldValue,
82
+ subjectKey, deltaAfterFields } from './hash.ts';
83
+ export type {
84
+ FieldChange,
85
+ SubjectFields,
86
+ } from './hash.ts';
87
+ export {
88
+ appendDurable,
89
+ appendEvent,
90
+ createEvent,
91
+ emptyGenericState,
92
+ genericWorldReducer,
93
+ listEvents,
94
+ loadState,
95
+ projectionLockPath,
96
+ readJsonFile,
97
+ rebuildGenericState,
98
+ rebuildState,
99
+ // Scrub: delete pulled data at rest (TWIN-45) — the honest, plain-`rm` counterpart to
100
+ // sync pull's fold-into-the-log. See docs/concepts/data-and-keys.md for the full data-at-rest story.
101
+ scrubService,
102
+ scrubWorld,
103
+ stateDirName,
104
+ // Opt-in structured stderr logging (VOLTER_TWIN_LOG=1) for the audit trail. Public so a
105
+ // CONNECTOR can say on the same stream when the vendor handed it a truncated page — a
106
+ // silent shortfall is the one thing a pull must never pass off as the whole resource.
107
+ twinLog,
108
+ // The kernel's cross-process mutual-exclusion primitive (exclusive-create lockfile with
109
+ // stale-holder reclaim). Public so world-runtime can guard concurrent `upWorld` claims of
110
+ // one instance dir with the SAME lock semantics the event log uses (TWIN-36).
111
+ withFileLock,
112
+ worldPaths,
113
+ worldStateRoot,
114
+ } from './storage.ts';
115
+ export type {
116
+ AppendEventResult,
117
+ CommitQueuedEventsResult,
118
+ GenericWorldState,
119
+ EnqueueEventResult,
120
+ QueuedWorldServiceEvent,
121
+ WorldPaths,
122
+ WorldReducer,
123
+ WorldServiceEvent,
124
+ } from './types.ts';
125
+ export type { ScrubResult } from './storage.ts';
126
+ // The pluggable persistence seam: the sync WorldStore interface, its fs (default) and
127
+ // in-memory implementations, the active-store injection point, and the async
128
+ // hydrate/flush boundary a serverless (Durable Object / KV / redis) entry uses.
129
+ export {
130
+ FsWorldStore,
131
+ MemoryWorldStore,
132
+ getActiveWorldStore,
133
+ setActiveWorldStore,
134
+ withWorldStore,
135
+ hydrateInto,
136
+ flushFrom,
137
+ } from './world-store.ts';
138
+ export type { WorldStore, WorldStat, HydrationSource, HydrationSink } from './world-store.ts';
139
+ export type { CheckLoader } from './head.ts';
140
+ export { SqlWorldStore } from './world-store-sql.ts';
141
+ export type { SqlExec } from './world-store-sql.ts';
142
+ // The blob seam (runtime contract R11): byte storage behind byte-carrying handlers, so a
143
+ // serverless namespace puts bytes in object storage while local worlds keep today's layout.
144
+ export {
145
+ FsBlobStore,
146
+ MemoryBlobStore,
147
+ blobDigest,
148
+ getActiveBlobStore,
149
+ setActiveBlobStore,
150
+ withBlobStore,
151
+ readBlobRange,
152
+ } from './blob-store.ts';
153
+ export type { BlobStore } from './blob-store.ts';
154
+ export {
155
+ applyTwinWrite,
156
+ applyTwinWriteAtomic,
157
+ createTwinServer,
158
+ journalTwinRequest,
159
+ twinRequestCredentials,
160
+ readTwinRequestJournal,
161
+ resolveTwinRead,
162
+ twinRequestJournalEnabled,
163
+ twinRequestJournalPath,
164
+ twinResources,
165
+ } from './serve.ts';
166
+ export type {
167
+ AtomicTwinWriteDecision,
168
+ TwinRequestJournalEntry,
169
+ TwinResource,
170
+ TwinWriteInput,
171
+ TwinWriteResult,
172
+ } from './serve.ts';
173
+ export { createTwinProxy } from './proxy.ts';
174
+ export type { TwinProxy, TwinProxyOptions, VendorRoute } from './proxy.ts';
175
+ export {
176
+ forkTwin,
177
+ isFork,
178
+ readForkMeta,
179
+ } from './fork.ts';
180
+ export type { ForkMeta } from './fork.ts';
181
+ export {
182
+ appendAction,
183
+ appendTransactionCommit,
184
+ checkPrecondition,
185
+ confirmAction,
186
+ listActions,
187
+ listTransactionCommits,
188
+ pendingActions,
189
+ pushablePendingActions,
190
+ isTwinBookkeeping,
191
+ pendingTransactionCommits,
192
+ projectResources,
193
+ revertAction,
194
+ TwinActionPreconditionError, subjectAliases, resolveSubjectId, runWithCorrelationId, currentCorrelationId } from './actions.ts';
195
+ export type {
196
+ ActionProjection,
197
+ ProjectedDelivery,
198
+ ProjectedResource,
199
+ ProjectedResourcePatch,
200
+ ProjectedResourceRef,
201
+ TwinAction,
202
+ TwinActionOp,
203
+ TwinActionPrecondition,
204
+ TwinActionPreconditionOp,
205
+ TwinActionRevertSpec,
206
+ TwinTransactionCommit,
207
+ TwinTransactionCommitOp,
208
+ TwinTransactionPrecondition,
209
+ TwinTransactionRevertSpec,
210
+ } from './actions.ts';
211
+ // ── Operator control plane (R19/R20): remote refs, queue lifecycle, push ledger,
212
+ // apply leases, plan, status. (the twins architecture notes)
213
+ // The CHANGESET primitive + the ledger DIFF (docs/concepts/the-model.md v0) — "commit" and
214
+ // "diff" for operational reality: a cross-service base marker, the ledger delta since it, and
215
+ // the content-addressed, replayable changeset over that delta. State-level by construction
216
+ // (it reads the same per-service action ledgers `actions.ts` writes); `volter-world` supplies
217
+ // world discovery and thin verbs on top.
218
+ export {
219
+ approveChangeset,
220
+ assertMarkerBelongsTo,
221
+ assertSafeChangesetName,
222
+ assertValidVerifiers,
223
+ buildChangeset,
224
+ captureMarker,
225
+ changesetContentHash,
226
+ changesetHashMatches,
227
+ changesetReadiness,
228
+ CHANGESET_KIND,
229
+ diffLedgers,
230
+ formatApplication,
231
+ formatChangeset,
232
+ formatRebaseReport,
233
+ narrateActions,
234
+ narrationDrift,
235
+ rebaseChangeset,
236
+ formatChangesetStatus,
237
+ formatLedgerDelta,
238
+ formatReplayReport,
239
+ formatVerification,
240
+ MARKER_KIND,
241
+ normalizeChangeset,
242
+ parseVerifierExpression,
243
+ replayChangeset,
244
+ runChangesetVerifiers,
245
+ summarizeByVendor,
246
+ twinWriteShape,
247
+ withApplication,
248
+ withVerification,
249
+ worldBootMarker,
250
+ WORLD_BOOT_MARKER_ID,
251
+ } from './changeset.ts';
252
+ export type {
253
+ ApplyReceipt,
254
+ RebaseActionResult,
255
+ RebaseReport,
256
+ RebaseTarget,
257
+ Changeset,
258
+ ChangesetAction,
259
+ ChangesetApplication,
260
+ CompensationEntry,
261
+ ChangesetApproval,
262
+ ChangesetReadiness,
263
+ ChangesetVerification,
264
+ ChangesetVerifier,
265
+ ChangesetVerifierResult,
266
+ LedgerDelta,
267
+ LedgerPosition,
268
+ LedgerRef,
269
+ ReplayActionResult,
270
+ ReplayReport,
271
+ ReplayTarget,
272
+ VendorSummary,
273
+ WorldMarker,
274
+ } from './changeset.ts';
275
+ export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets, type PackAssetStore } from './pack-assets.ts';
276
+
277
+ // The git plane (contract "The git plane is a kernel library"): objects, packs, refs, smart HTTP.
278
+ export * as git from './git/index.ts';
279
+
280
+ // The push arm: the kernel's transaction over a pack's one `perform<Name>Action` (contract section of that name).
281
+
282
+ // The placeholder remote (contract section of that name): the seed is a pull, never a pending write.
283
+ export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, placeholderPullMarkerPath, withPlaceholderPull, placeholderEventsFor } from './placeholder-remote.ts';
284
+
285
+ export { ownFields, subjectHistory, rebaseBranch, cutCheckpoint, appendParentEntry, wholeLog, branchEntries, branchLogPath, branchMetaPath, CHECKPOINT_EVERY, dropCheckpoint, foldEntries, landedCopy, landedIds, parentEntries, parentLogPath, position, readBranchMeta, readTree, toEntry, toEvent, unpushedEntries, writeBranchMeta } from './log.ts';
286
+ export type { BranchMeta, Entry, EntryOp, Receipt } from './log.ts';
287
+ export { sealCredential, openSealedCredential, MemoryCredentialStorage } from './credential.ts';
288
+ export type { CredentialPayload, CredentialStorage, SealedCredential } from './credential.ts';
289
+ export { validateRemoteOrigin, buildRemoteExecute, type CredentialCustody } from './executor.ts';
290
+ export type { TwinAuthStrategy } from './executor.ts';
291
+ export { clearRoot, authStrategyFor, clearStateSystems, NO_SECRETS_CHECK, readRoot, registerAuthStrategy, registerStateSystem, rootPath, runChecks, stateSystemFor, writeRoot } from './state-system.ts';
292
+ export type { Check, CheckVerdict, DeployPolicy, RootConfig, StateSystemAdapters } from './state-system.ts';
293
+ export { observeAppends } from './actions.ts';
294
+ export { appendActionIfAbsent, appendActionOccurrence } from './actions.ts';
295
+
296
+ // PROTOCOL 2 — the head and the fold (company contract "The head", "Refresh is the kernel's fold")
297
+ export { answerVendorErrors, performEntries, performAtHead, deployableEntries, headOf, boundRoot, loadChecks, loadCheck, openRootCredential, resealRootCredential, rootCustody, sealingKey, setCheckLoader, setSealingKeySource, userKek, userKekPath, vendorOf, worldRootOf, RefusedWriteError, VendorWriteError, HeadError } from './head.ts';
298
+ export type { BoundRoot, DeployReport, Head, PerformAction, PerformContext, PushOutcome } from './head.ts';
299
+ export { observeResource, observeResources, collectObservations, foldObservations } from './observe.ts';
300
+ export type { ObservedResource, ObserveReport, Observation } from './observe.ts';
301
+ export { readTreeMap, readParentTreeMap, treeStamp, treeChangesSince, originEntries, appendOriginEntry, originLogPath, isUrlParent, parentPosition, positionAt, splitsBatch, assertBatchBoundary } from './log.ts';
302
+
303
+ // v1 left the kernel (contract "Just like Neon", 5): the names a protocol 1 pack still imports throw on
304
+ // their first call, so the catalog stays importable and a pack out of date fails loudly.
305
+ export * from './v1-removed.ts';
306
+ export { referenceField, registerReferences, packReferences, resolveReferences, type ReferenceDeclaration } from './references.ts';
307
+ // the HTTP server seam: the one place the serve path meets the runtime (Bun or Node)
308
+ export { serveHttp, nodeBuiltin, WORLD_BOOT_PATH, type HttpServer, type ServeHttpOptions, type HttpHandler } from './serve-http.ts';
309
+ // two runtime-neutral helpers for a pack's serve path: a file as a Response, a mirror's client bundle
310
+ export { fileResponse, contentTypeOf } from './file-response.ts';
311
+ export { bundleClient } from './client-bundle.ts';
312
+ // the brand's tokens and faces for a Volter page (the console, the site, the UI kit), fetched at build
313
+ export { brandTokensResponse } from './brand-tokens.ts';
314
+
315
+ export { readResourceBlob, readResourceBlobRange, resourceBlobSize, resourceChain } from './resource-blob.ts';
316
+ export { mirrorShellUnder } from './mirror-shell.ts';
317
+
318
+ export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withStateRemoval } from './ancestry.ts';
319
+
320
+ export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from './history.ts';
321
+ export type { HistoryView, HistoryLayout, HistoryReference, HistoryOrigin } from './history.ts';
322
+ export { foldHistory } from './log.ts';
323
+ export { volterHome } from './volter-home.ts';
@@ -0,0 +1,8 @@
1
+ // Bun busy-spins on a top-level promise that can never settle when no other event-loop handle
2
+ // remains. Twin CLIs use this timer-backed hold after starting their servers so an unexpectedly
3
+ // closed server cannot turn an otherwise idle process into a full-core loop.
4
+ const IDLE_SLEEP_MS = 86_400_000;
5
+
6
+ export async function keepProcessAlive(): Promise<never> {
7
+ while (true) await new Promise<void>(resolve => setTimeout(resolve, IDLE_SLEEP_MS));
8
+ }