@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.
Files changed (114) hide show
  1. package/README.md +24 -1
  2. package/bin/cli/commands/check.js +2 -2
  3. package/bin/cli/commands/doctor/checks.js +12 -12
  4. package/bin/cli/commands/doctor/index.js +2 -2
  5. package/bin/cli/commands/doctor/types.js +1 -1
  6. package/bin/cli/commands/history.js +12 -5
  7. package/bin/cli/commands/install-hooks.js +5 -5
  8. package/bin/cli/commands/materialize.js +2 -2
  9. package/bin/cli/commands/patch.js +142 -0
  10. package/bin/cli/commands/path.js +4 -4
  11. package/bin/cli/commands/query.js +54 -13
  12. package/bin/cli/commands/registry.js +4 -0
  13. package/bin/cli/commands/seek.js +17 -11
  14. package/bin/cli/commands/tree.js +230 -0
  15. package/bin/cli/commands/trust.js +3 -3
  16. package/bin/cli/commands/verify-audit.js +8 -7
  17. package/bin/cli/commands/view.js +6 -5
  18. package/bin/cli/infrastructure.js +26 -12
  19. package/bin/cli/shared.js +2 -2
  20. package/bin/cli/types.js +19 -8
  21. package/bin/presenters/index.js +35 -9
  22. package/bin/presenters/json.js +14 -12
  23. package/bin/presenters/text.js +155 -33
  24. package/index.d.ts +118 -22
  25. package/index.js +2 -0
  26. package/package.json +5 -3
  27. package/src/domain/WarpGraph.js +4 -1
  28. package/src/domain/crdt/ORSet.js +8 -8
  29. package/src/domain/errors/EmptyMessageError.js +2 -2
  30. package/src/domain/errors/ForkError.js +1 -1
  31. package/src/domain/errors/IndexError.js +1 -1
  32. package/src/domain/errors/OperationAbortedError.js +1 -1
  33. package/src/domain/errors/QueryError.js +1 -1
  34. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  35. package/src/domain/errors/ShardCorruptionError.js +2 -2
  36. package/src/domain/errors/ShardLoadError.js +2 -2
  37. package/src/domain/errors/ShardValidationError.js +4 -4
  38. package/src/domain/errors/StorageError.js +2 -2
  39. package/src/domain/errors/SyncError.js +1 -1
  40. package/src/domain/errors/TraversalError.js +1 -1
  41. package/src/domain/errors/TrustError.js +1 -1
  42. package/src/domain/errors/WarpError.js +2 -2
  43. package/src/domain/errors/WormholeError.js +1 -1
  44. package/src/domain/services/AuditReceiptService.js +6 -6
  45. package/src/domain/services/AuditVerifierService.js +52 -38
  46. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  47. package/src/domain/services/BitmapIndexReader.js +28 -19
  48. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  49. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  50. package/src/domain/services/CheckpointService.js +22 -3
  51. package/src/domain/services/CommitDagTraversalService.js +13 -13
  52. package/src/domain/services/DagPathFinding.js +7 -7
  53. package/src/domain/services/DagTopology.js +1 -1
  54. package/src/domain/services/DagTraversal.js +1 -1
  55. package/src/domain/services/HealthCheckService.js +1 -1
  56. package/src/domain/services/HookInstaller.js +1 -1
  57. package/src/domain/services/HttpSyncServer.js +92 -41
  58. package/src/domain/services/IndexRebuildService.js +7 -7
  59. package/src/domain/services/IndexStalenessChecker.js +4 -3
  60. package/src/domain/services/JoinReducer.js +26 -11
  61. package/src/domain/services/KeyCodec.js +7 -0
  62. package/src/domain/services/LogicalTraversal.js +1 -1
  63. package/src/domain/services/MessageCodecInternal.js +1 -1
  64. package/src/domain/services/MigrationService.js +1 -1
  65. package/src/domain/services/ObserverView.js +8 -8
  66. package/src/domain/services/PatchBuilderV2.js +96 -30
  67. package/src/domain/services/ProvenanceIndex.js +1 -1
  68. package/src/domain/services/ProvenancePayload.js +1 -1
  69. package/src/domain/services/QueryBuilder.js +3 -3
  70. package/src/domain/services/StateDiff.js +14 -11
  71. package/src/domain/services/StateSerializerV5.js +2 -2
  72. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  73. package/src/domain/services/SyncAuthService.js +3 -2
  74. package/src/domain/services/SyncProtocol.js +25 -11
  75. package/src/domain/services/TemporalQuery.js +9 -6
  76. package/src/domain/services/TranslationCost.js +7 -5
  77. package/src/domain/services/WormholeService.js +16 -7
  78. package/src/domain/trust/TrustCanonical.js +3 -3
  79. package/src/domain/trust/TrustEvaluator.js +18 -3
  80. package/src/domain/trust/TrustRecordService.js +30 -23
  81. package/src/domain/trust/TrustStateBuilder.js +21 -8
  82. package/src/domain/trust/canonical.js +6 -6
  83. package/src/domain/types/TickReceipt.js +1 -1
  84. package/src/domain/types/WarpErrors.js +45 -0
  85. package/src/domain/types/WarpOptions.js +29 -0
  86. package/src/domain/types/WarpPersistence.js +41 -0
  87. package/src/domain/types/WarpTypes.js +2 -2
  88. package/src/domain/types/WarpTypesV2.js +2 -2
  89. package/src/domain/utils/MinHeap.js +6 -5
  90. package/src/domain/utils/canonicalStringify.js +5 -4
  91. package/src/domain/utils/roaring.js +31 -5
  92. package/src/domain/warp/PatchSession.js +40 -18
  93. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  94. package/src/domain/warp/checkpoint.methods.js +5 -1
  95. package/src/domain/warp/fork.methods.js +2 -2
  96. package/src/domain/warp/materialize.methods.js +55 -5
  97. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  98. package/src/domain/warp/patch.methods.js +54 -29
  99. package/src/domain/warp/provenance.methods.js +5 -3
  100. package/src/domain/warp/query.methods.js +89 -6
  101. package/src/domain/warp/sync.methods.js +16 -11
  102. package/src/globals.d.ts +64 -0
  103. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  104. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  105. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  106. package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
  107. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  108. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  109. package/src/visualization/layouts/converters.js +2 -2
  110. package/src/visualization/layouts/elkAdapter.js +1 -1
  111. package/src/visualization/layouts/elkLayout.js +10 -7
  112. package/src/visualization/layouts/index.js +1 -1
  113. package/src/visualization/renderers/ascii/seek.js +16 -6
  114. 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, any> | null>;
20
- getEdgeProps(from: string, to: string, label: string): Promise<Record<string, any> | null>;
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<any>;
165
+ getStateSnapshot(): Promise<WarpStateV5 | null>;
23
166
  getNodes(): Promise<string[]>;
24
- getEdges(): Promise<Array<{ from: string; to: string; label: string; props: Record<string, any> }>>;
167
+ getEdges(): Promise<Array<{ from: string; to: string; label: string; props: Record<string, unknown> }>>;
25
168
  getPropertyCount(): Promise<number>;
26
- query(): any;
27
- observer(name: string, config: any): Promise<any>;
28
- translationCost(configA: any, configB: any): Promise<{ cost: number; breakdown: { nodeLoss: number; edgeLoss: number; propLoss: number } }>;
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: Function; onError?: Function; replay?: boolean }): { unsubscribe: () => void };
32
- watch(pattern: string, options: { onChange: Function; onError?: Function; poll?: number }): { unsubscribe: () => void };
33
- _notifySubscribers(diff: any, currentState: any): void;
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?: any): Promise<any>;
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<any>;
40
- _loadPatchBySha(sha: string): Promise<any>;
41
- _loadPatchesBySha(shas: string[]): Promise<Array<{ patch: any; sha: string }>>;
42
- _sortPatchesCausally(patches: any[]): any[];
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<{ fromSha: string; toSha: string; writerId: string; payload: any; patchCount: number }>;
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: any): Promise<void>;
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<any>;
55
- createSyncRequest(): Promise<any>;
56
- processSyncRequest(request: any): Promise<any>;
57
- applySyncResponse(response: any): any;
58
- syncNeeded(remoteFrontier: any): Promise<boolean>;
59
- syncWith(remote: any, options?: any): Promise<any>;
60
- serve(options?: any): Promise<any>;
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<any>;
66
- _loadPatchesSince(checkpoint: any): Promise<any[]>;
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: any): any;
70
- maybeRunGC(): any;
71
- runGC(): any;
72
- getGCMetrics(): any;
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: import('../types/WarpTypesV2.js').PatchV2; sha: string }>>;
79
- getWriterPatches(writerId: string, stopAtSha?: string | null): Promise<Array<{ patch: import('../types/WarpTypesV2.js').PatchV2; sha: string }>>;
80
- _onPatchCommitted(writerId: string, opts?: { patch?: any; sha?: string }): Promise<void>;
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?: any): Promise<Writer>;
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: any): any;
87
- _frontierEquals(a: any, b: any): boolean;
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?: any): Promise<any>;
91
- _materializeGraph(): Promise<any>;
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: any): any;
95
- _buildAdjacency(state: any): any;
96
- _setMaterializedState(state: any): Promise<{ state: any; stateHash: string; adjacency: any }>;
97
- _materializeWithCeiling(ceiling: any, collectReceipts: boolean, t0: number): Promise<any>;
98
- materializeAt(checkpointSha: string): Promise<any>;
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: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
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(/** @type {any} */ ({ // TODO(ts-cleanup): needs options type
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 {any} */ (patches), /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (checkpoint.state), { receipts: true })); // TODO(ts-cleanup): type patch array
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 {any} */ (patches), /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (checkpoint.state))); // TODO(ts-cleanup): type patch array
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 {any} */ (checkpoint).provenanceIndex; // TODO(ts-cleanup): type checkpoint cast
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 {any} */ (allPatches), undefined, { receipts: true })); // TODO(ts-cleanup): type patch array
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 {any} */ (allPatches))); // TODO(ts-cleanup): type patch array
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<{state: any, stateHash: string, adjacency: any}>}
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 {any} */ (allPatches), undefined, { receipts: true })); // TODO(ts-cleanup): type patch array
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 {any} */ (allPatches))); // TODO(ts-cleanup): type patch array
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: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
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
- if (!currentRefSha) {
93
- // First commit for this writer
94
- return { lamport: 1, parentSha: null };
95
- }
105
+ let ownTick = 0;
96
106
 
97
- // Read the current patch commit to get its lamport timestamp
98
- const commitMessage = await this._persistence.showNode(currentRefSha);
99
- const kind = detectMessageKind(commitMessage);
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
- if (kind !== 'patch') {
102
- // Writer ref doesn't point to a patch commit - treat as first commit
103
- return { lamport: 1, parentSha: currentRefSha };
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
- try {
107
- const patchInfo = decodePatchMessage(commitMessage);
108
- return { lamport: patchInfo.lamport + 1, parentSha: currentRefSha };
109
- } catch {
110
- // Malformed message - error with actionable message
111
- throw new Error(
112
- `Failed to parse lamport from writer ref ${writerRef}: ` +
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 {any} */ (committed), sha, true) // TODO(ts-cleanup): narrow patch 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 {any} */ (committed), sha); // TODO(ts-cleanup): narrow patch type to PatchV2
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: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
284
+ persistence,
262
285
  graphName: this._graphName,
263
286
  writerId: resolvedWriterId,
264
287
  versionVector: this._versionVector,
265
- getCurrentState: () => /** @type {any} */ (this._cachedState), // TODO(ts-cleanup): narrow port type
288
+ getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
266
289
  onDeleteWithData: this._onDeleteWithData,
267
- onCommitSuccess: (/** @type {any} */ opts) => this._onPatchCommitted(resolvedWriterId, opts), // TODO(ts-cleanup): type sync protocol
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: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
342
+ persistence: writerPersistence,
318
343
  graphName: this._graphName,
319
344
  writerId: freshWriterId,
320
345
  versionVector: this._versionVector,
321
- getCurrentState: () => /** @type {any} */ (this._cachedState), // TODO(ts-cleanup): narrow port type
346
+ getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
322
347
  onDeleteWithData: this._onDeleteWithData,
323
- onCommitSuccess: (/** @type {any} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts), // TODO(ts-cleanup): type sync protocol
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
  }