@git-stunts/git-warp 11.2.1 → 11.5.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.
- package/README.md +24 -1
- package/bin/cli/commands/check.js +2 -2
- package/bin/cli/commands/doctor/checks.js +12 -12
- package/bin/cli/commands/doctor/index.js +2 -2
- package/bin/cli/commands/doctor/types.js +1 -1
- package/bin/cli/commands/history.js +12 -5
- package/bin/cli/commands/install-hooks.js +5 -5
- package/bin/cli/commands/materialize.js +2 -2
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +4 -4
- package/bin/cli/commands/query.js +54 -13
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/seek.js +17 -11
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +3 -3
- package/bin/cli/commands/verify-audit.js +8 -7
- package/bin/cli/commands/view.js +6 -5
- package/bin/cli/infrastructure.js +26 -12
- package/bin/cli/shared.js +2 -2
- package/bin/cli/types.js +19 -8
- package/bin/presenters/index.js +35 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +155 -33
- package/index.d.ts +118 -22
- package/index.js +2 -0
- package/package.json +5 -3
- package/src/domain/WarpGraph.js +4 -1
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +1 -1
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +1 -1
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/services/AuditReceiptService.js +6 -6
- package/src/domain/services/AuditVerifierService.js +52 -38
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +22 -3
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +92 -41
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +26 -11
- package/src/domain/services/KeyCodec.js +7 -0
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +1 -1
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +96 -30
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +3 -2
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +3 -3
- package/src/domain/trust/TrustEvaluator.js +18 -3
- package/src/domain/trust/TrustRecordService.js +30 -23
- package/src/domain/trust/TrustStateBuilder.js +21 -8
- package/src/domain/trust/canonical.js +6 -6
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +40 -18
- package/src/domain/warp/_wiredMethods.d.ts +199 -45
- package/src/domain/warp/checkpoint.methods.js +5 -1
- package/src/domain/warp/fork.methods.js +2 -2
- package/src/domain/warp/materialize.methods.js +55 -5
- package/src/domain/warp/materializeAdvanced.methods.js +15 -4
- package/src/domain/warp/patch.methods.js +54 -29
- package/src/domain/warp/provenance.methods.js +5 -3
- package/src/domain/warp/query.methods.js +89 -6
- package/src/domain/warp/sync.methods.js +16 -11
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- package/src/visualization/renderers/svg/index.js +1 -1
|
@@ -5,10 +5,153 @@
|
|
|
5
5
|
* via wireWarpMethods(). This declaration file makes them visible to tsc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
9
|
-
|
|
10
8
|
import type { PatchBuilderV2 } from '../services/PatchBuilderV2.js';
|
|
11
9
|
import type { Writer } from './Writer.js';
|
|
10
|
+
import type { WarpStateV5 } from '../services/JoinReducer.js';
|
|
11
|
+
import type { PatchV2 } from '../types/WarpTypesV2.js';
|
|
12
|
+
import type { StateDiffResult } from '../services/StateDiff.js';
|
|
13
|
+
import type { TickReceipt } from '../types/TickReceipt.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Observer configuration for view creation and translation cost.
|
|
17
|
+
*/
|
|
18
|
+
interface ObserverConfig {
|
|
19
|
+
match: string;
|
|
20
|
+
expose?: string[];
|
|
21
|
+
redact?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Translation cost result.
|
|
26
|
+
*/
|
|
27
|
+
interface TranslationCostResult {
|
|
28
|
+
cost: number;
|
|
29
|
+
breakdown: { nodeLoss: number; edgeLoss: number; propLoss: number };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Lightweight status snapshot.
|
|
34
|
+
*/
|
|
35
|
+
interface WarpGraphStatus {
|
|
36
|
+
cachedState: 'fresh' | 'stale' | 'none';
|
|
37
|
+
patchesSinceCheckpoint: number;
|
|
38
|
+
tombstoneRatio: number;
|
|
39
|
+
writers: number;
|
|
40
|
+
frontier: Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sync request message.
|
|
45
|
+
*/
|
|
46
|
+
interface SyncRequest {
|
|
47
|
+
type: 'sync-request';
|
|
48
|
+
frontier: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sync response message.
|
|
53
|
+
*/
|
|
54
|
+
interface SyncResponse {
|
|
55
|
+
type: 'sync-response';
|
|
56
|
+
frontier: Record<string, string>;
|
|
57
|
+
patches: Array<{ writerId: string; sha: string; patch: unknown }>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result of applySyncResponse().
|
|
62
|
+
*/
|
|
63
|
+
interface ApplySyncResult {
|
|
64
|
+
state: WarpStateV5;
|
|
65
|
+
frontier: Map<string, number>;
|
|
66
|
+
applied: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sync options for syncWith().
|
|
71
|
+
*/
|
|
72
|
+
interface SyncWithOptions {
|
|
73
|
+
path?: string;
|
|
74
|
+
retries?: number;
|
|
75
|
+
baseDelayMs?: number;
|
|
76
|
+
maxDelayMs?: number;
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
onStatus?: (event: {
|
|
80
|
+
type: string;
|
|
81
|
+
attempt: number;
|
|
82
|
+
durationMs?: number;
|
|
83
|
+
status?: number;
|
|
84
|
+
error?: Error;
|
|
85
|
+
}) => void;
|
|
86
|
+
auth?: { secret: string; keyId?: string };
|
|
87
|
+
/** Auto-materialize after sync; when true, result includes `state` */
|
|
88
|
+
materialize?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* GC execution result.
|
|
93
|
+
*/
|
|
94
|
+
interface GCExecuteResult {
|
|
95
|
+
nodesCompacted: number;
|
|
96
|
+
edgesCompacted: number;
|
|
97
|
+
tombstonesRemoved: number;
|
|
98
|
+
durationMs: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* GC metrics.
|
|
103
|
+
*/
|
|
104
|
+
interface GCMetrics {
|
|
105
|
+
nodeCount: number;
|
|
106
|
+
edgeCount: number;
|
|
107
|
+
tombstoneCount: number;
|
|
108
|
+
tombstoneRatio: number;
|
|
109
|
+
patchesSinceCompaction: number;
|
|
110
|
+
lastCompactionTime: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Result of maybeRunGC().
|
|
115
|
+
*/
|
|
116
|
+
interface MaybeGCResult {
|
|
117
|
+
ran: boolean;
|
|
118
|
+
result: GCExecuteResult | null;
|
|
119
|
+
reasons: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Join receipt from CRDT merge.
|
|
124
|
+
*/
|
|
125
|
+
interface JoinReceipt {
|
|
126
|
+
nodesAdded: number;
|
|
127
|
+
nodesRemoved: number;
|
|
128
|
+
edgesAdded: number;
|
|
129
|
+
edgesRemoved: number;
|
|
130
|
+
propsChanged: number;
|
|
131
|
+
frontierMerged: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Wormhole edge.
|
|
136
|
+
*/
|
|
137
|
+
interface WormholeEdge {
|
|
138
|
+
fromSha: string;
|
|
139
|
+
toSha: string;
|
|
140
|
+
writerId: string;
|
|
141
|
+
payload: unknown;
|
|
142
|
+
patchCount: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Checkpoint data returned by _loadLatestCheckpoint.
|
|
147
|
+
*/
|
|
148
|
+
interface CheckpointData {
|
|
149
|
+
state: WarpStateV5;
|
|
150
|
+
frontier: Map<string, string>;
|
|
151
|
+
stateHash: string;
|
|
152
|
+
schema: number;
|
|
153
|
+
provenanceIndex?: unknown;
|
|
154
|
+
}
|
|
12
155
|
|
|
13
156
|
export {};
|
|
14
157
|
|
|
@@ -16,85 +159,96 @@ declare module '../WarpGraph.js' {
|
|
|
16
159
|
export default interface WarpGraph {
|
|
17
160
|
// ── query.methods.js ──────────────────────────────────────────────────
|
|
18
161
|
hasNode(nodeId: string): Promise<boolean>;
|
|
19
|
-
getNodeProps(nodeId: string): Promise<Map<string,
|
|
20
|
-
getEdgeProps(from: string, to: string, label: string): Promise<Record<string,
|
|
162
|
+
getNodeProps(nodeId: string): Promise<Map<string, unknown> | null>;
|
|
163
|
+
getEdgeProps(from: string, to: string, label: string): Promise<Record<string, unknown> | null>;
|
|
21
164
|
neighbors(nodeId: string, direction?: 'outgoing' | 'incoming' | 'both', edgeLabel?: string): Promise<Array<{ nodeId: string; label: string; direction: 'outgoing' | 'incoming' }>>;
|
|
22
|
-
getStateSnapshot(): Promise<
|
|
165
|
+
getStateSnapshot(): Promise<WarpStateV5 | null>;
|
|
23
166
|
getNodes(): Promise<string[]>;
|
|
24
|
-
getEdges(): Promise<Array<{ from: string; to: string; label: string; props: Record<string,
|
|
167
|
+
getEdges(): Promise<Array<{ from: string; to: string; label: string; props: Record<string, unknown> }>>;
|
|
25
168
|
getPropertyCount(): Promise<number>;
|
|
26
|
-
query():
|
|
27
|
-
observer(name: string, config:
|
|
28
|
-
translationCost(configA:
|
|
169
|
+
query(): import('../services/QueryBuilder.js').default;
|
|
170
|
+
observer(name: string, config: ObserverConfig): Promise<import('../services/ObserverView.js').default>;
|
|
171
|
+
translationCost(configA: ObserverConfig, configB: ObserverConfig): Promise<TranslationCostResult>;
|
|
29
172
|
|
|
30
173
|
// ── subscribe.methods.js ──────────────────────────────────────────────
|
|
31
|
-
subscribe(options: { onChange:
|
|
32
|
-
watch(pattern: string, options: { onChange:
|
|
33
|
-
_notifySubscribers(diff:
|
|
174
|
+
subscribe(options: { onChange: (diff: StateDiffResult) => void; onError?: (error: Error) => void; replay?: boolean }): { unsubscribe: () => void };
|
|
175
|
+
watch(pattern: string, options: { onChange: (diff: StateDiffResult) => void; onError?: (error: Error) => void; poll?: number }): { unsubscribe: () => void };
|
|
176
|
+
_notifySubscribers(diff: StateDiffResult, currentState: WarpStateV5): void;
|
|
34
177
|
|
|
35
178
|
// ── provenance.methods.js ─────────────────────────────────────────────
|
|
36
179
|
patchesFor(entityId: string): Promise<string[]>;
|
|
37
|
-
materializeSlice(nodeId: string, options?:
|
|
180
|
+
materializeSlice(nodeId: string, options?: { receipts?: boolean }): Promise<{ state: WarpStateV5; patchCount: number; receipts?: TickReceipt[] }>;
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
|
|
38
182
|
_computeBackwardCone(nodeId: string): Promise<Map<string, any>>;
|
|
39
|
-
loadPatchBySha(sha: string): Promise<
|
|
40
|
-
_loadPatchBySha(sha: string): Promise<
|
|
41
|
-
_loadPatchesBySha(shas: string[]): Promise<Array<{ patch:
|
|
42
|
-
|
|
183
|
+
loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
|
|
184
|
+
_loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
|
|
185
|
+
_loadPatchesBySha(shas: string[]): Promise<Array<{ patch: PatchV2; sha: string }>>;
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
|
|
187
|
+
_sortPatchesCausally(patches: Array<{ patch: any; sha: string }>): Array<{ patch: any; sha: string }>;
|
|
43
188
|
|
|
44
189
|
// ── fork.methods.js ───────────────────────────────────────────────────
|
|
45
190
|
fork(options: { from: string; at: string; forkName?: string; forkWriterId?: string }): Promise<WarpGraph>;
|
|
46
|
-
createWormhole(fromSha: string, toSha: string): Promise<
|
|
191
|
+
createWormhole(fromSha: string, toSha: string): Promise<WormholeEdge>;
|
|
47
192
|
_isAncestor(ancestorSha: string, descendantSha: string): Promise<boolean>;
|
|
48
193
|
_relationToCheckpointHead(ckHead: string, incomingSha: string): Promise<string>;
|
|
49
|
-
_validatePatchAgainstCheckpoint(writerId: string, incomingSha: string, checkpoint:
|
|
194
|
+
_validatePatchAgainstCheckpoint(writerId: string, incomingSha: string, checkpoint: unknown): Promise<void>;
|
|
50
195
|
|
|
51
196
|
// ── sync.methods.js ───────────────────────────────────────────────────
|
|
52
197
|
getFrontier(): Promise<Map<string, string>>;
|
|
53
198
|
hasFrontierChanged(): Promise<boolean>;
|
|
54
|
-
status(): Promise<
|
|
55
|
-
createSyncRequest(): Promise<
|
|
56
|
-
processSyncRequest(request:
|
|
57
|
-
applySyncResponse(response:
|
|
58
|
-
syncNeeded(remoteFrontier:
|
|
59
|
-
syncWith(remote:
|
|
60
|
-
serve(options
|
|
199
|
+
status(): Promise<WarpGraphStatus>;
|
|
200
|
+
createSyncRequest(): Promise<SyncRequest>;
|
|
201
|
+
processSyncRequest(request: SyncRequest): Promise<SyncResponse>;
|
|
202
|
+
applySyncResponse(response: SyncResponse): ApplySyncResult;
|
|
203
|
+
syncNeeded(remoteFrontier: Map<string, string>): Promise<boolean>;
|
|
204
|
+
syncWith(remote: string | WarpGraph, options?: SyncWithOptions): Promise<{ applied: number; attempts: number; state?: WarpStateV5 }>;
|
|
205
|
+
serve(options: {
|
|
206
|
+
port: number;
|
|
207
|
+
host?: string;
|
|
208
|
+
path?: string;
|
|
209
|
+
maxRequestBytes?: number;
|
|
210
|
+
httpPort: unknown;
|
|
211
|
+
auth?: unknown;
|
|
212
|
+
allowedWriters?: string[];
|
|
213
|
+
}): Promise<{ close(): Promise<void>; url: string }>;
|
|
61
214
|
|
|
62
215
|
// ── checkpoint.methods.js ─────────────────────────────────────────────
|
|
63
216
|
createCheckpoint(): Promise<string>;
|
|
64
217
|
syncCoverage(): Promise<void>;
|
|
65
|
-
_loadLatestCheckpoint(): Promise<
|
|
66
|
-
_loadPatchesSince(checkpoint:
|
|
218
|
+
_loadLatestCheckpoint(): Promise<CheckpointData | null>;
|
|
219
|
+
_loadPatchesSince(checkpoint: CheckpointData): Promise<Array<{ patch: PatchV2; sha: string }>>;
|
|
67
220
|
_validateMigrationBoundary(): Promise<void>;
|
|
68
221
|
_hasSchema1Patches(): Promise<boolean>;
|
|
69
|
-
_maybeRunGC(state:
|
|
70
|
-
maybeRunGC():
|
|
71
|
-
runGC():
|
|
72
|
-
getGCMetrics():
|
|
222
|
+
_maybeRunGC(state: WarpStateV5): void;
|
|
223
|
+
maybeRunGC(): MaybeGCResult;
|
|
224
|
+
runGC(): GCExecuteResult;
|
|
225
|
+
getGCMetrics(): GCMetrics | null;
|
|
73
226
|
|
|
74
227
|
// ── patch.methods.js ──────────────────────────────────────────────────
|
|
75
228
|
createPatch(): Promise<PatchBuilderV2>;
|
|
76
229
|
patch(build: (p: PatchBuilderV2) => void | Promise<void>): Promise<string>;
|
|
77
230
|
_nextLamport(): Promise<{ lamport: number; parentSha: string | null }>;
|
|
78
|
-
_loadWriterPatches(writerId: string, stopAtSha?: string | null): Promise<Array<{ patch:
|
|
79
|
-
getWriterPatches(writerId: string, stopAtSha?: string | null): Promise<Array<{ patch:
|
|
80
|
-
_onPatchCommitted(writerId: string, opts?: { patch?:
|
|
231
|
+
_loadWriterPatches(writerId: string, stopAtSha?: string | null): Promise<Array<{ patch: PatchV2; sha: string }>>;
|
|
232
|
+
getWriterPatches(writerId: string, stopAtSha?: string | null): Promise<Array<{ patch: PatchV2; sha: string }>>;
|
|
233
|
+
_onPatchCommitted(writerId: string, opts?: { patch?: PatchV2; sha?: string }): Promise<void>;
|
|
81
234
|
writer(writerId?: string): Promise<Writer>;
|
|
82
|
-
createWriter(opts?:
|
|
235
|
+
createWriter(opts?: { persist?: 'config' | 'none'; alias?: string }): Promise<Writer>;
|
|
83
236
|
_ensureFreshState(): Promise<void>;
|
|
84
237
|
discoverWriters(): Promise<string[]>;
|
|
85
238
|
discoverTicks(): Promise<{ ticks: number[]; maxTick: number; perWriter: Map<string, { ticks: number[]; tipSha: string | null; tickShas: Record<number, string> }> }>;
|
|
86
|
-
join(otherState:
|
|
87
|
-
_frontierEquals(a:
|
|
239
|
+
join(otherState: WarpStateV5): { state: WarpStateV5; receipt: JoinReceipt };
|
|
240
|
+
_frontierEquals(a: Map<string, number>, b: Map<string, number>): boolean;
|
|
88
241
|
|
|
89
242
|
// ── materialize.methods.js ────────────────────────────────────────────
|
|
90
|
-
materialize(options?:
|
|
91
|
-
|
|
243
|
+
materialize(options: { receipts: true; ceiling?: number | null }): Promise<{ state: WarpStateV5; receipts: TickReceipt[] }>;
|
|
244
|
+
materialize(options?: { receipts?: false; ceiling?: number | null }): Promise<WarpStateV5>;
|
|
245
|
+
_materializeGraph(): Promise<{ state: WarpStateV5; stateHash: string; adjacency: unknown }>;
|
|
92
246
|
|
|
93
247
|
// ── materializeAdvanced.methods.js ────────────────────────────────────
|
|
94
|
-
_resolveCeiling(options
|
|
95
|
-
_buildAdjacency(state:
|
|
96
|
-
_setMaterializedState(state:
|
|
97
|
-
_materializeWithCeiling(ceiling:
|
|
98
|
-
materializeAt(checkpointSha: string): Promise<
|
|
248
|
+
_resolveCeiling(options?: { ceiling?: number | null }): number | null;
|
|
249
|
+
_buildAdjacency(state: WarpStateV5): { outgoing: Map<string, Array<{ neighborId: string; label: string }>>; incoming: Map<string, Array<{ neighborId: string; label: string }>> };
|
|
250
|
+
_setMaterializedState(state: WarpStateV5): Promise<{ state: WarpStateV5; stateHash: string; adjacency: unknown }>;
|
|
251
|
+
_materializeWithCeiling(ceiling: number, collectReceipts: boolean, t0: number): Promise<WarpStateV5 | { state: WarpStateV5; receipts: TickReceipt[] }>;
|
|
252
|
+
materializeAt(checkpointSha: string): Promise<WarpStateV5>;
|
|
99
253
|
}
|
|
100
254
|
}
|
|
@@ -16,6 +16,8 @@ import { shouldRunGC, executeGC } from '../services/GCPolicy.js';
|
|
|
16
16
|
import { collectGCMetrics } from '../services/GCMetrics.js';
|
|
17
17
|
import { computeAppliedVV } from '../services/CheckpointSerializerV5.js';
|
|
18
18
|
|
|
19
|
+
/** @typedef {import('../types/WarpPersistence.js').CorePersistence} CorePersistence */
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* Creates a checkpoint of the current graph state.
|
|
21
23
|
*
|
|
@@ -59,8 +61,10 @@ export async function createCheckpoint() {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// 4. Call CheckpointService.create() with provenance index if available
|
|
64
|
+
/** @type {CorePersistence} */
|
|
65
|
+
const persistence = this._persistence;
|
|
62
66
|
const checkpointSha = await createCheckpointCommit({
|
|
63
|
-
persistence
|
|
67
|
+
persistence,
|
|
64
68
|
graphName: this._graphName,
|
|
65
69
|
state,
|
|
66
70
|
frontier,
|
|
@@ -201,13 +201,13 @@ export async function createWormhole(fromSha, toSha) {
|
|
|
201
201
|
const t0 = this._clock.now();
|
|
202
202
|
|
|
203
203
|
try {
|
|
204
|
-
const wormhole = await createWormholeImpl(
|
|
204
|
+
const wormhole = await createWormholeImpl({
|
|
205
205
|
persistence: this._persistence,
|
|
206
206
|
graphName: this._graphName,
|
|
207
207
|
fromSha,
|
|
208
208
|
toSha,
|
|
209
209
|
codec: this._codec,
|
|
210
|
-
})
|
|
210
|
+
});
|
|
211
211
|
|
|
212
212
|
this._logTiming('createWormhole', t0, {
|
|
213
213
|
metrics: `${wormhole.patchCount} patches from=${fromSha.slice(0, 7)} to=${toSha.slice(0, 7)}`,
|
|
@@ -9,6 +9,47 @@
|
|
|
9
9
|
import { reduceV5, createEmptyStateV5, cloneStateV5 } from '../services/JoinReducer.js';
|
|
10
10
|
import { ProvenanceIndex } from '../services/ProvenanceIndex.js';
|
|
11
11
|
import { diffStates, isEmptyDiff } from '../services/StateDiff.js';
|
|
12
|
+
import { decodePatchMessage, detectMessageKind } from '../services/WarpMessageCodec.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scans the checkpoint frontier's tip commits for the maximum observed Lamport tick.
|
|
16
|
+
* Updates `graph._maxObservedLamport` in-place; best-effort (skips unreadable commits).
|
|
17
|
+
*
|
|
18
|
+
* @param {import('../WarpGraph.js').default} graph
|
|
19
|
+
* @param {Map<string, string>} frontier
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
22
|
+
async function scanFrontierForMaxLamport(graph, frontier) {
|
|
23
|
+
for (const tipSha of frontier.values()) {
|
|
24
|
+
try {
|
|
25
|
+
const msg = await graph._persistence.showNode(tipSha);
|
|
26
|
+
if (detectMessageKind(msg) === 'patch') {
|
|
27
|
+
const { lamport } = decodePatchMessage(msg);
|
|
28
|
+
if (lamport > graph._maxObservedLamport) {
|
|
29
|
+
graph._maxObservedLamport = lamport;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// best-effort: skip unreadable frontier commits
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Scans a list of patch entries for the maximum observed Lamport tick.
|
|
40
|
+
* Updates `graph._maxObservedLamport` in-place.
|
|
41
|
+
*
|
|
42
|
+
* @param {import('../WarpGraph.js').default} graph
|
|
43
|
+
* @param {Array<{patch: {lamport?: number}}>} patches
|
|
44
|
+
*/
|
|
45
|
+
function scanPatchesForMaxLamport(graph, patches) {
|
|
46
|
+
for (const { patch } of patches) {
|
|
47
|
+
const tick = patch.lamport ?? 0;
|
|
48
|
+
if (tick > graph._maxObservedLamport) {
|
|
49
|
+
graph._maxObservedLamport = tick;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
12
53
|
|
|
13
54
|
/**
|
|
14
55
|
* Materializes the current graph state.
|
|
@@ -63,17 +104,24 @@ export async function materialize(options) {
|
|
|
63
104
|
// If checkpoint exists, use incremental materialization
|
|
64
105
|
if (checkpoint?.schema === 2 || checkpoint?.schema === 3) {
|
|
65
106
|
const patches = await this._loadPatchesSince(checkpoint);
|
|
107
|
+
// Update max observed Lamport so _nextLamport() issues globally-monotonic ticks.
|
|
108
|
+
// Read the checkpoint frontier's tip commit messages to capture the pre-checkpoint max,
|
|
109
|
+
// then scan the incremental patches for anything newer.
|
|
110
|
+
if (checkpoint.frontier instanceof Map) {
|
|
111
|
+
await scanFrontierForMaxLamport(this, checkpoint.frontier);
|
|
112
|
+
}
|
|
113
|
+
scanPatchesForMaxLamport(this, patches);
|
|
66
114
|
if (collectReceipts) {
|
|
67
|
-
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {
|
|
115
|
+
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (patches), checkpoint.state, { receipts: true }));
|
|
68
116
|
state = result.state;
|
|
69
117
|
receipts = result.receipts;
|
|
70
118
|
} else {
|
|
71
|
-
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {
|
|
119
|
+
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (patches), checkpoint.state));
|
|
72
120
|
}
|
|
73
121
|
patchCount = patches.length;
|
|
74
122
|
|
|
75
123
|
// Build provenance index: start from checkpoint index if present, then add new patches
|
|
76
|
-
const ckPI = /** @type {
|
|
124
|
+
const ckPI = /** @type {{provenanceIndex?: import('../services/ProvenanceIndex.js').ProvenanceIndex}} */ (checkpoint).provenanceIndex;
|
|
77
125
|
this._provenanceIndex = ckPI
|
|
78
126
|
? ckPI.clone()
|
|
79
127
|
: new ProvenanceIndex();
|
|
@@ -109,13 +157,15 @@ export async function materialize(options) {
|
|
|
109
157
|
receipts = [];
|
|
110
158
|
}
|
|
111
159
|
} else {
|
|
160
|
+
// Update max observed Lamport from all loaded patches.
|
|
161
|
+
scanPatchesForMaxLamport(this, allPatches);
|
|
112
162
|
// 5. Reduce all patches to state
|
|
113
163
|
if (collectReceipts) {
|
|
114
|
-
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {
|
|
164
|
+
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (allPatches), undefined, { receipts: true }));
|
|
115
165
|
state = result.state;
|
|
116
166
|
receipts = result.receipts;
|
|
117
167
|
} else {
|
|
118
|
-
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {
|
|
168
|
+
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (allPatches)));
|
|
119
169
|
}
|
|
120
170
|
patchCount = allPatches.length;
|
|
121
171
|
|
|
@@ -18,6 +18,15 @@ import { serializeFullStateV5, deserializeFullStateV5 } from '../services/Checkp
|
|
|
18
18
|
import { buildSeekCacheKey } from '../utils/seekCacheKey.js';
|
|
19
19
|
import { materializeIncremental } from '../services/CheckpointService.js';
|
|
20
20
|
import { createFrontier, updateFrontier } from '../services/Frontier.js';
|
|
21
|
+
|
|
22
|
+
/** @typedef {import('../types/WarpPersistence.js').CorePersistence} CorePersistence */
|
|
23
|
+
/** @typedef {import('../services/JoinReducer.js').WarpStateV5} WarpStateV5 */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{ outgoing: Map<string, Array<{neighborId: string, label: string}>>, incoming: Map<string, Array<{neighborId: string, label: string}>> }} AdjacencyMap
|
|
27
|
+
* @typedef {{ state: WarpStateV5, stateHash: string, adjacency: AdjacencyMap }} MaterializedResult
|
|
28
|
+
*/
|
|
29
|
+
|
|
21
30
|
import { buildWriterRef } from '../utils/RefLayout.js';
|
|
22
31
|
import { decodePatchMessage, detectMessageKind } from '../services/WarpMessageCodec.js';
|
|
23
32
|
|
|
@@ -97,7 +106,7 @@ export function _buildAdjacency(state) {
|
|
|
97
106
|
*
|
|
98
107
|
* @this {import('../WarpGraph.js').default}
|
|
99
108
|
* @param {import('../services/JoinReducer.js').WarpStateV5} state
|
|
100
|
-
* @returns {Promise<
|
|
109
|
+
* @returns {Promise<MaterializedResult>}
|
|
101
110
|
* @private
|
|
102
111
|
*/
|
|
103
112
|
export async function _setMaterializedState(state) {
|
|
@@ -226,11 +235,11 @@ export async function _materializeWithCeiling(ceiling, collectReceipts, t0) {
|
|
|
226
235
|
receipts = [];
|
|
227
236
|
}
|
|
228
237
|
} else if (collectReceipts) {
|
|
229
|
-
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {
|
|
238
|
+
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (allPatches), undefined, { receipts: true }));
|
|
230
239
|
state = result.state;
|
|
231
240
|
receipts = result.receipts;
|
|
232
241
|
} else {
|
|
233
|
-
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {
|
|
242
|
+
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ (allPatches)));
|
|
234
243
|
}
|
|
235
244
|
|
|
236
245
|
this._provenanceIndex = new ProvenanceIndex();
|
|
@@ -326,8 +335,10 @@ export async function materializeAt(checkpointSha) {
|
|
|
326
335
|
};
|
|
327
336
|
|
|
328
337
|
// 4. Call materializeIncremental with the checkpoint and target frontier
|
|
338
|
+
/** @type {CorePersistence} */
|
|
339
|
+
const persistence = this._persistence;
|
|
329
340
|
const state = await materializeIncremental({
|
|
330
|
-
persistence
|
|
341
|
+
persistence,
|
|
331
342
|
graphName: this._graphName,
|
|
332
343
|
checkpointSha,
|
|
333
344
|
targetFrontier,
|
|
@@ -18,9 +18,19 @@ import { decodePatchMessage, detectMessageKind } from '../services/WarpMessageCo
|
|
|
18
18
|
import { Writer } from './Writer.js';
|
|
19
19
|
import { generateWriterId, resolveWriterId } from '../utils/WriterId.js';
|
|
20
20
|
|
|
21
|
+
/** @typedef {import('../types/WarpPersistence.js').CorePersistence} CorePersistence */
|
|
22
|
+
|
|
21
23
|
/**
|
|
22
24
|
* Creates a new PatchBuilderV2 for this graph.
|
|
23
25
|
*
|
|
26
|
+
* In multi-writer scenarios, call `materialize()` (or a query method that
|
|
27
|
+
* auto-materializes) before creating a patch so that `_maxObservedLamport`
|
|
28
|
+
* reflects all known writers. Without this, `_nextLamport()` still produces
|
|
29
|
+
* locally-monotonic ticks (`Math.max(ownTick, _maxObservedLamport) + 1`),
|
|
30
|
+
* and `PatchBuilderV2.commit()` re-reads the writer's own ref at commit
|
|
31
|
+
* time, so correctness is preserved — but the tick may be lower than
|
|
32
|
+
* necessary, losing LWW tiebreakers against other writers.
|
|
33
|
+
*
|
|
24
34
|
* @this {import('../WarpGraph.js').default}
|
|
25
35
|
* @returns {Promise<PatchBuilderV2>} A new patch builder
|
|
26
36
|
*/
|
|
@@ -52,6 +62,9 @@ export async function createPatch() {
|
|
|
52
62
|
* Not reentrant: calling `graph.patch()` inside a callback throws.
|
|
53
63
|
* Use `createPatch()` directly for advanced multi-patch workflows.
|
|
54
64
|
*
|
|
65
|
+
* **Multi-writer note:** call `materialize()` before `patch()` so that
|
|
66
|
+
* `_maxObservedLamport` is up-to-date. See `createPatch()` for details.
|
|
67
|
+
*
|
|
55
68
|
* @this {import('../WarpGraph.js').default}
|
|
56
69
|
* @param {(p: PatchBuilderV2) => void | Promise<void>} build - Callback that adds operations to the patch
|
|
57
70
|
* @returns {Promise<string>} The commit SHA of the new patch
|
|
@@ -89,30 +102,35 @@ export async function _nextLamport() {
|
|
|
89
102
|
const writerRef = buildWriterRef(this._graphName, this._writerId);
|
|
90
103
|
const currentRefSha = await this._persistence.readRef(writerRef);
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
// First commit for this writer
|
|
94
|
-
return { lamport: 1, parentSha: null };
|
|
95
|
-
}
|
|
105
|
+
let ownTick = 0;
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
if (currentRefSha) {
|
|
108
|
+
// Read the current patch commit to get its lamport timestamp
|
|
109
|
+
const commitMessage = await this._persistence.showNode(currentRefSha);
|
|
110
|
+
const kind = detectMessageKind(commitMessage);
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
if (kind === 'patch') {
|
|
113
|
+
try {
|
|
114
|
+
const patchInfo = decodePatchMessage(commitMessage);
|
|
115
|
+
ownTick = patchInfo.lamport;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Failed to parse lamport from writer ref ${writerRef}: ` +
|
|
119
|
+
`commit ${currentRefSha} has invalid patch message format`,
|
|
120
|
+
{ cause: err }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Non-patch ref: ownTick stays 0 (fresh start), falls through to standard return.
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`commit ${currentRefSha} has invalid patch message format`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
127
|
+
// Standard Lamport clock rule: next tick = max(own chain, globally observed max) + 1.
|
|
128
|
+
// _maxObservedLamport is updated during materialize() and after each commit, so this
|
|
129
|
+
// is O(1) — no additional git reads required at commit time.
|
|
130
|
+
return {
|
|
131
|
+
lamport: Math.max(ownTick, this._maxObservedLamport) + 1,
|
|
132
|
+
parentSha: currentRefSha ?? null,
|
|
133
|
+
};
|
|
116
134
|
}
|
|
117
135
|
|
|
118
136
|
/**
|
|
@@ -193,19 +211,22 @@ export async function getWriterPatches(writerId, stopAtSha = null) {
|
|
|
193
211
|
*/
|
|
194
212
|
export async function _onPatchCommitted(writerId, { patch: committed, sha } = {}) {
|
|
195
213
|
vvIncrement(this._versionVector, writerId);
|
|
214
|
+
// Keep _maxObservedLamport up to date so _nextLamport() issues globally-monotonic ticks.
|
|
215
|
+
if (committed?.lamport !== undefined && committed.lamport > this._maxObservedLamport) {
|
|
216
|
+
this._maxObservedLamport = committed.lamport;
|
|
217
|
+
}
|
|
196
218
|
this._patchesSinceCheckpoint++;
|
|
197
219
|
// Eager re-materialize: apply the just-committed patch to cached state
|
|
198
220
|
// Only when the cache is clean — applying a patch to stale state would be incorrect
|
|
199
221
|
if (this._cachedState && !this._stateDirty && committed && sha) {
|
|
200
222
|
let tickReceipt = null;
|
|
201
223
|
if (this._auditService) {
|
|
202
|
-
// TODO(ts-cleanup): narrow joinPatch return + patch type to PatchV2
|
|
203
224
|
const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}} */ (
|
|
204
|
-
joinPatch(this._cachedState, /** @type {
|
|
225
|
+
joinPatch(this._cachedState, /** @type {Parameters<typeof joinPatch>[1]} */ (committed), sha, true)
|
|
205
226
|
);
|
|
206
227
|
tickReceipt = result.receipt;
|
|
207
228
|
} else {
|
|
208
|
-
joinPatch(this._cachedState, /** @type {
|
|
229
|
+
joinPatch(this._cachedState, /** @type {Parameters<typeof joinPatch>[1]} */ (committed), sha);
|
|
209
230
|
}
|
|
210
231
|
await this._setMaterializedState(this._cachedState);
|
|
211
232
|
// Update provenance index with new patch
|
|
@@ -257,14 +278,16 @@ export async function writer(writerId) {
|
|
|
257
278
|
configSet,
|
|
258
279
|
});
|
|
259
280
|
|
|
281
|
+
/** @type {CorePersistence} */
|
|
282
|
+
const persistence = this._persistence;
|
|
260
283
|
return new Writer({
|
|
261
|
-
persistence
|
|
284
|
+
persistence,
|
|
262
285
|
graphName: this._graphName,
|
|
263
286
|
writerId: resolvedWriterId,
|
|
264
287
|
versionVector: this._versionVector,
|
|
265
|
-
getCurrentState: () => /** @type {
|
|
288
|
+
getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
|
|
266
289
|
onDeleteWithData: this._onDeleteWithData,
|
|
267
|
-
onCommitSuccess: (/** @type {
|
|
290
|
+
onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts))),
|
|
268
291
|
codec: this._codec,
|
|
269
292
|
});
|
|
270
293
|
}
|
|
@@ -313,14 +336,16 @@ export async function createWriter(opts = {}) {
|
|
|
313
336
|
await this._persistence.configSet(configKey, freshWriterId);
|
|
314
337
|
}
|
|
315
338
|
|
|
339
|
+
/** @type {CorePersistence} */
|
|
340
|
+
const writerPersistence = this._persistence;
|
|
316
341
|
return new Writer({
|
|
317
|
-
persistence:
|
|
342
|
+
persistence: writerPersistence,
|
|
318
343
|
graphName: this._graphName,
|
|
319
344
|
writerId: freshWriterId,
|
|
320
345
|
versionVector: this._versionVector,
|
|
321
|
-
getCurrentState: () => /** @type {
|
|
346
|
+
getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
|
|
322
347
|
onDeleteWithData: this._onDeleteWithData,
|
|
323
|
-
onCommitSuccess: (/** @type {
|
|
348
|
+
onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts))),
|
|
324
349
|
codec: this._codec,
|
|
325
350
|
});
|
|
326
351
|
}
|