@git-stunts/git-warp 10.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +16 -0
  3. package/README.md +480 -0
  4. package/SECURITY.md +30 -0
  5. package/bin/git-warp +24 -0
  6. package/bin/warp-graph.js +1574 -0
  7. package/index.d.ts +2366 -0
  8. package/index.js +180 -0
  9. package/package.json +129 -0
  10. package/scripts/install-git-warp.sh +258 -0
  11. package/scripts/uninstall-git-warp.sh +139 -0
  12. package/src/domain/WarpGraph.js +3157 -0
  13. package/src/domain/crdt/Dot.js +160 -0
  14. package/src/domain/crdt/LWW.js +154 -0
  15. package/src/domain/crdt/ORSet.js +371 -0
  16. package/src/domain/crdt/VersionVector.js +222 -0
  17. package/src/domain/entities/GraphNode.js +60 -0
  18. package/src/domain/errors/EmptyMessageError.js +47 -0
  19. package/src/domain/errors/ForkError.js +30 -0
  20. package/src/domain/errors/IndexError.js +23 -0
  21. package/src/domain/errors/OperationAbortedError.js +22 -0
  22. package/src/domain/errors/QueryError.js +39 -0
  23. package/src/domain/errors/SchemaUnsupportedError.js +17 -0
  24. package/src/domain/errors/ShardCorruptionError.js +56 -0
  25. package/src/domain/errors/ShardLoadError.js +57 -0
  26. package/src/domain/errors/ShardValidationError.js +61 -0
  27. package/src/domain/errors/StorageError.js +57 -0
  28. package/src/domain/errors/SyncError.js +30 -0
  29. package/src/domain/errors/TraversalError.js +23 -0
  30. package/src/domain/errors/WarpError.js +31 -0
  31. package/src/domain/errors/WormholeError.js +28 -0
  32. package/src/domain/errors/WriterError.js +39 -0
  33. package/src/domain/errors/index.js +21 -0
  34. package/src/domain/services/AnchorMessageCodec.js +99 -0
  35. package/src/domain/services/BitmapIndexBuilder.js +225 -0
  36. package/src/domain/services/BitmapIndexReader.js +435 -0
  37. package/src/domain/services/BoundaryTransitionRecord.js +463 -0
  38. package/src/domain/services/CheckpointMessageCodec.js +147 -0
  39. package/src/domain/services/CheckpointSerializerV5.js +281 -0
  40. package/src/domain/services/CheckpointService.js +384 -0
  41. package/src/domain/services/CommitDagTraversalService.js +156 -0
  42. package/src/domain/services/DagPathFinding.js +712 -0
  43. package/src/domain/services/DagTopology.js +239 -0
  44. package/src/domain/services/DagTraversal.js +245 -0
  45. package/src/domain/services/Frontier.js +108 -0
  46. package/src/domain/services/GCMetrics.js +101 -0
  47. package/src/domain/services/GCPolicy.js +122 -0
  48. package/src/domain/services/GitLogParser.js +205 -0
  49. package/src/domain/services/HealthCheckService.js +246 -0
  50. package/src/domain/services/HookInstaller.js +326 -0
  51. package/src/domain/services/HttpSyncServer.js +262 -0
  52. package/src/domain/services/IndexRebuildService.js +426 -0
  53. package/src/domain/services/IndexStalenessChecker.js +103 -0
  54. package/src/domain/services/JoinReducer.js +582 -0
  55. package/src/domain/services/KeyCodec.js +113 -0
  56. package/src/domain/services/LegacyAnchorDetector.js +67 -0
  57. package/src/domain/services/LogicalTraversal.js +351 -0
  58. package/src/domain/services/MessageCodecInternal.js +132 -0
  59. package/src/domain/services/MessageSchemaDetector.js +145 -0
  60. package/src/domain/services/MigrationService.js +55 -0
  61. package/src/domain/services/ObserverView.js +265 -0
  62. package/src/domain/services/PatchBuilderV2.js +669 -0
  63. package/src/domain/services/PatchMessageCodec.js +140 -0
  64. package/src/domain/services/ProvenanceIndex.js +337 -0
  65. package/src/domain/services/ProvenancePayload.js +242 -0
  66. package/src/domain/services/QueryBuilder.js +835 -0
  67. package/src/domain/services/StateDiff.js +300 -0
  68. package/src/domain/services/StateSerializerV5.js +156 -0
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +709 -0
  70. package/src/domain/services/SyncProtocol.js +593 -0
  71. package/src/domain/services/TemporalQuery.js +201 -0
  72. package/src/domain/services/TranslationCost.js +221 -0
  73. package/src/domain/services/TraversalService.js +8 -0
  74. package/src/domain/services/WarpMessageCodec.js +29 -0
  75. package/src/domain/services/WarpStateIndexBuilder.js +127 -0
  76. package/src/domain/services/WormholeService.js +353 -0
  77. package/src/domain/types/TickReceipt.js +285 -0
  78. package/src/domain/types/WarpTypes.js +209 -0
  79. package/src/domain/types/WarpTypesV2.js +200 -0
  80. package/src/domain/utils/CachedValue.js +140 -0
  81. package/src/domain/utils/EventId.js +89 -0
  82. package/src/domain/utils/LRUCache.js +112 -0
  83. package/src/domain/utils/MinHeap.js +114 -0
  84. package/src/domain/utils/RefLayout.js +280 -0
  85. package/src/domain/utils/WriterId.js +205 -0
  86. package/src/domain/utils/cancellation.js +33 -0
  87. package/src/domain/utils/canonicalStringify.js +42 -0
  88. package/src/domain/utils/defaultClock.js +20 -0
  89. package/src/domain/utils/defaultCodec.js +51 -0
  90. package/src/domain/utils/nullLogger.js +21 -0
  91. package/src/domain/utils/roaring.js +181 -0
  92. package/src/domain/utils/shardVersion.js +9 -0
  93. package/src/domain/warp/PatchSession.js +217 -0
  94. package/src/domain/warp/Writer.js +181 -0
  95. package/src/hooks/post-merge.sh +60 -0
  96. package/src/infrastructure/adapters/BunHttpAdapter.js +225 -0
  97. package/src/infrastructure/adapters/ClockAdapter.js +57 -0
  98. package/src/infrastructure/adapters/ConsoleLogger.js +150 -0
  99. package/src/infrastructure/adapters/DenoHttpAdapter.js +230 -0
  100. package/src/infrastructure/adapters/GitGraphAdapter.js +787 -0
  101. package/src/infrastructure/adapters/GlobalClockAdapter.js +5 -0
  102. package/src/infrastructure/adapters/NoOpLogger.js +62 -0
  103. package/src/infrastructure/adapters/NodeCryptoAdapter.js +32 -0
  104. package/src/infrastructure/adapters/NodeHttpAdapter.js +98 -0
  105. package/src/infrastructure/adapters/PerformanceClockAdapter.js +5 -0
  106. package/src/infrastructure/adapters/WebCryptoAdapter.js +121 -0
  107. package/src/infrastructure/codecs/CborCodec.js +384 -0
  108. package/src/ports/BlobPort.js +30 -0
  109. package/src/ports/ClockPort.js +25 -0
  110. package/src/ports/CodecPort.js +25 -0
  111. package/src/ports/CommitPort.js +114 -0
  112. package/src/ports/ConfigPort.js +31 -0
  113. package/src/ports/CryptoPort.js +38 -0
  114. package/src/ports/GraphPersistencePort.js +57 -0
  115. package/src/ports/HttpServerPort.js +25 -0
  116. package/src/ports/IndexStoragePort.js +39 -0
  117. package/src/ports/LoggerPort.js +68 -0
  118. package/src/ports/RefPort.js +51 -0
  119. package/src/ports/TreePort.js +51 -0
  120. package/src/visualization/index.js +26 -0
  121. package/src/visualization/layouts/converters.js +75 -0
  122. package/src/visualization/layouts/elkAdapter.js +86 -0
  123. package/src/visualization/layouts/elkLayout.js +95 -0
  124. package/src/visualization/layouts/index.js +29 -0
  125. package/src/visualization/renderers/ascii/box.js +16 -0
  126. package/src/visualization/renderers/ascii/check.js +271 -0
  127. package/src/visualization/renderers/ascii/colors.js +13 -0
  128. package/src/visualization/renderers/ascii/formatters.js +73 -0
  129. package/src/visualization/renderers/ascii/graph.js +344 -0
  130. package/src/visualization/renderers/ascii/history.js +335 -0
  131. package/src/visualization/renderers/ascii/index.js +14 -0
  132. package/src/visualization/renderers/ascii/info.js +245 -0
  133. package/src/visualization/renderers/ascii/materialize.js +255 -0
  134. package/src/visualization/renderers/ascii/path.js +240 -0
  135. package/src/visualization/renderers/ascii/progress.js +32 -0
  136. package/src/visualization/renderers/ascii/symbols.js +33 -0
  137. package/src/visualization/renderers/ascii/table.js +19 -0
  138. package/src/visualization/renderers/browser/index.js +1 -0
  139. package/src/visualization/renderers/svg/index.js +159 -0
  140. package/src/visualization/utils/ansi.js +14 -0
  141. package/src/visualization/utils/time.js +40 -0
  142. package/src/visualization/utils/truncate.js +40 -0
  143. package/src/visualization/utils/unicode.js +52 -0
package/README.md ADDED
@@ -0,0 +1,480 @@
1
+ # @git-stunts/git-warp
2
+
3
+ [![CI](https://github.com/git-stunts/git-warp/actions/workflows/ci.yml/badge.svg)](https://github.com/git-stunts/git-warp/actions/workflows/ci.yml)
4
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+ [![npm version](https://badge.fury.io/js/%40git-stunts%2Fgit-warp.svg)](https://www.npmjs.com/package/@git-stunts/git-warp)
6
+
7
+ A multi-writer graph database that uses Git commits as its storage substrate. Graph state is stored as commits pointing to the empty tree (`4b825dc...`), making the data invisible to normal Git workflows while inheriting Git's content-addressing, cryptographic integrity, and distributed replication.
8
+
9
+ Writers collaborate without coordination using CRDTs (OR-Set for nodes/edges, LWW registers for properties). Every writer maintains an independent patch chain; materialization deterministically merges all writers into a single consistent view.
10
+
11
+ ```bash
12
+ npm install @git-stunts/git-warp @git-stunts/plumbing
13
+ ```
14
+
15
+ For a comprehensive walkthrough — from setup to advanced features — see the [Guide](docs/GUIDE.md).
16
+
17
+ ## Quick Start
18
+
19
+ ```javascript
20
+ import GitPlumbing from '@git-stunts/plumbing';
21
+ import WarpGraph, { GitGraphAdapter } from '@git-stunts/git-warp';
22
+
23
+ const plumbing = new GitPlumbing({ cwd: './my-repo' });
24
+ const persistence = new GitGraphAdapter({ plumbing });
25
+
26
+ const graph = await WarpGraph.open({
27
+ persistence,
28
+ graphName: 'demo',
29
+ writerId: 'writer-1',
30
+ autoMaterialize: true, // auto-materialize on query
31
+ });
32
+
33
+ // Write data using the patch builder
34
+ await (await graph.createPatch())
35
+ .addNode('user:alice')
36
+ .setProperty('user:alice', 'name', 'Alice')
37
+ .setProperty('user:alice', 'role', 'admin')
38
+ .addNode('user:bob')
39
+ .setProperty('user:bob', 'name', 'Bob')
40
+ .addEdge('user:alice', 'user:bob', 'manages')
41
+ .setEdgeProperty('user:alice', 'user:bob', 'manages', 'since', '2024')
42
+ .commit();
43
+
44
+ // Query the graph
45
+ const result = await graph.query()
46
+ .match('user:*')
47
+ .outgoing('manages')
48
+ .run();
49
+ ```
50
+
51
+ ## How It Works
52
+
53
+ Each writer creates **patches**: atomic batches of graph operations (add/remove nodes, add/remove edges, set properties). Patches are serialized as CBOR-encoded Git commit messages pointing to the empty tree, forming a per-writer chain under `refs/warp/<graphName>/writers/<writerId>`.
54
+
55
+ **Materialization** replays all patches from all writers, applying CRDT merge semantics:
56
+
57
+ - **Nodes and edges** use an Observed-Remove Set (OR-Set). An add wins over a concurrent remove unless the remove has observed the specific add event.
58
+ - **Properties** use Last-Write-Wins (LWW) registers, ordered by Lamport timestamp, then writer ID, then patch SHA.
59
+ - **Version vectors** track causality across writers, ensuring deterministic convergence regardless of patch arrival order.
60
+
61
+ **Checkpoints** snapshot materialized state into a single commit for fast incremental recovery. Subsequent materializations only need to replay patches created after the checkpoint.
62
+
63
+ ## Multi-Writer Collaboration
64
+
65
+ Writers operate independently on the same Git repository. Sync happens through standard Git transport (push/pull) or the built-in HTTP sync protocol.
66
+
67
+ ```javascript
68
+ // Writer A (on machine A)
69
+ const graphA = await WarpGraph.open({
70
+ persistence: persistenceA,
71
+ graphName: 'shared',
72
+ writerId: 'alice',
73
+ });
74
+
75
+ await (await graphA.createPatch())
76
+ .addNode('doc:1')
77
+ .setProperty('doc:1', 'title', 'Draft')
78
+ .commit();
79
+
80
+ // Writer B (on machine B)
81
+ const graphB = await WarpGraph.open({
82
+ persistence: persistenceB,
83
+ graphName: 'shared',
84
+ writerId: 'bob',
85
+ });
86
+
87
+ await (await graphB.createPatch())
88
+ .addNode('doc:2')
89
+ .setProperty('doc:2', 'title', 'Notes')
90
+ .commit();
91
+
92
+ // After git push/pull, materialize merges both writers
93
+ const state = await graphA.materialize();
94
+ await graphA.hasNode('doc:1'); // true
95
+ await graphA.hasNode('doc:2'); // true
96
+ ```
97
+
98
+ ### HTTP Sync
99
+
100
+ ```javascript
101
+ // Start a sync server
102
+ const server = await graphB.serve({ port: 3000 });
103
+
104
+ // Sync from another instance
105
+ await graphA.syncWith('http://localhost:3000/sync');
106
+
107
+ await server.close();
108
+ ```
109
+
110
+ ### Direct Sync
111
+
112
+ ```javascript
113
+ // Sync two in-process instances directly
114
+ await graphA.syncWith(graphB);
115
+ ```
116
+
117
+ ## Querying
118
+
119
+ Query methods require materialized state. Either call `materialize()` first, or pass `autoMaterialize: true` to `WarpGraph.open()` to handle this automatically.
120
+
121
+ ### Simple Queries
122
+
123
+ ```javascript
124
+ await graph.materialize();
125
+
126
+ await graph.getNodes(); // ['user:alice', 'user:bob']
127
+ await graph.hasNode('user:alice'); // true
128
+ await graph.getNodeProps('user:alice'); // Map { 'name' => 'Alice', 'role' => 'admin' }
129
+ await graph.neighbors('user:alice', 'outgoing'); // [{ nodeId: 'user:bob', label: 'manages', direction: 'outgoing' }]
130
+ await graph.getEdges(); // [{ from: 'user:alice', to: 'user:bob', label: 'manages', props: {} }]
131
+ await graph.getEdgeProps('user:alice', 'user:bob', 'manages'); // { weight: 0.9 } or null
132
+ ```
133
+
134
+ ### Fluent Query Builder
135
+
136
+ ```javascript
137
+ const result = await graph.query()
138
+ .match('user:*') // glob pattern matching
139
+ .outgoing('manages') // traverse outgoing edges with label
140
+ .select(['id', 'props']) // select fields
141
+ .run();
142
+ ```
143
+
144
+ #### Object shorthand in `where()`
145
+
146
+ Filter nodes by property equality using plain objects. Multiple properties = AND semantics.
147
+
148
+ ```javascript
149
+ // Object shorthand — strict equality on primitive values
150
+ const admins = await graph.query()
151
+ .match('user:*')
152
+ .where({ role: 'admin', active: true })
153
+ .run();
154
+
155
+ // Chain object and function filters
156
+ const seniorAdmins = await graph.query()
157
+ .match('user:*')
158
+ .where({ role: 'admin' })
159
+ .where(({ props }) => props.age >= 30)
160
+ .run();
161
+ ```
162
+
163
+ #### Multi-hop traversal
164
+
165
+ Traverse multiple hops in a single call with `depth`. Default is `[1, 1]` (single hop).
166
+
167
+ ```javascript
168
+ // Depth 2: return only hop-2 neighbors
169
+ const grandchildren = await graph.query()
170
+ .match('org:root')
171
+ .outgoing('child', { depth: 2 })
172
+ .run();
173
+
174
+ // Range [1, 3]: return neighbors at hops 1, 2, and 3
175
+ const reachable = await graph.query()
176
+ .match('node:a')
177
+ .outgoing('next', { depth: [1, 3] })
178
+ .run();
179
+
180
+ // Depth [0, 2]: include the start set (self) plus hops 1 and 2
181
+ const selfAndNeighbors = await graph.query()
182
+ .match('node:a')
183
+ .outgoing('next', { depth: [0, 2] })
184
+ .run();
185
+
186
+ // Incoming edges work the same way
187
+ const ancestors = await graph.query()
188
+ .match('node:leaf')
189
+ .incoming('child', { depth: [1, 5] })
190
+ .run();
191
+ ```
192
+
193
+ #### Aggregation
194
+
195
+ Compute count, sum, avg, min, max over matched nodes. This is a terminal operation — `select()`, `outgoing()`, and `incoming()` cannot follow `aggregate()`.
196
+
197
+ ```javascript
198
+ const stats = await graph.query()
199
+ .match('order:*')
200
+ .where({ status: 'paid' })
201
+ .aggregate({ count: true, sum: 'props.total', avg: 'props.total' })
202
+ .run();
203
+
204
+ // stats = { stateHash: '...', count: 12, sum: 1450, avg: 120.83 }
205
+ ```
206
+
207
+ Non-numeric property values are silently skipped during aggregation.
208
+
209
+ ### Path Finding
210
+
211
+ ```javascript
212
+ const result = await graph.traverse.shortestPath('user:alice', 'user:bob', {
213
+ dir: 'outgoing',
214
+ labelFilter: 'manages',
215
+ maxDepth: 10,
216
+ });
217
+
218
+ if (result.found) {
219
+ console.log(result.path); // ['user:alice', 'user:bob']
220
+ console.log(result.length); // 1
221
+ }
222
+ ```
223
+
224
+ ## Subscriptions & Reactivity
225
+
226
+ React to graph changes without polling. Handlers are called after `materialize()` when state has changed.
227
+
228
+ ### Subscribe to All Changes
229
+
230
+ ```javascript
231
+ const { unsubscribe } = graph.subscribe({
232
+ onChange: (diff) => {
233
+ console.log('Nodes added:', diff.nodes.added);
234
+ console.log('Nodes removed:', diff.nodes.removed);
235
+ console.log('Edges added:', diff.edges.added);
236
+ console.log('Props changed:', diff.props.set);
237
+ },
238
+ onError: (err) => console.error('Handler error:', err),
239
+ replay: true, // immediately fire with current state
240
+ });
241
+
242
+ // Make changes and materialize to trigger handlers
243
+ await (await graph.createPatch()).addNode('user:charlie').commit();
244
+ await graph.materialize(); // onChange fires with the diff
245
+
246
+ unsubscribe(); // stop receiving updates
247
+ ```
248
+
249
+ ### Watch with Pattern Filtering
250
+
251
+ Only receive changes for nodes matching a glob pattern:
252
+
253
+ ```javascript
254
+ const { unsubscribe } = graph.watch('user:*', {
255
+ onChange: (diff) => {
256
+ // Only includes user:* nodes, their edges, and their properties
257
+ console.log('User changes:', diff);
258
+ },
259
+ poll: 5000, // optional: check for remote changes every 5s
260
+ });
261
+ ```
262
+
263
+ When `poll` is set, the watcher periodically calls `hasFrontierChanged()` and auto-materializes if remote changes are detected.
264
+
265
+ ## Observer Views
266
+
267
+ Project the graph through filtered lenses for access control, data minimization, or multi-tenant isolation (Paper IV).
268
+
269
+ ```javascript
270
+ // Create an observer that only sees user:* nodes, with sensitive fields hidden
271
+ const view = await graph.observer('publicApi', {
272
+ match: 'user:*',
273
+ redact: ['ssn', 'password'],
274
+ });
275
+
276
+ const users = await view.getNodes(); // only user:* nodes
277
+ const props = await view.getNodeProps('user:alice'); // Map without ssn/password
278
+ const result = await view.query().match('user:*').where({ role: 'admin' }).run();
279
+
280
+ // Measure information loss between two observer perspectives
281
+ const { cost, breakdown } = await graph.translationCost(
282
+ { match: '*' }, // full view
283
+ { match: 'user:*', redact: ['ssn'] }, // restricted view
284
+ );
285
+ // cost ∈ [0, 1] — 0 = identical views, 1 = completely disjoint
286
+ ```
287
+
288
+ ## Temporal Queries
289
+
290
+ CTL*-style temporal operators over patch history (Paper IV).
291
+
292
+ ```javascript
293
+ // Was this node always in 'active' status?
294
+ const alwaysActive = await graph.temporal.always(
295
+ 'user:alice',
296
+ (snapshot) => snapshot.props.status === 'active',
297
+ { since: 0 },
298
+ );
299
+
300
+ // Was this PR ever merged?
301
+ const wasMerged = await graph.temporal.eventually(
302
+ 'pr:42',
303
+ (snapshot) => snapshot.props.status === 'merged',
304
+ );
305
+ ```
306
+
307
+ ## Patch Operations
308
+
309
+ The patch builder supports seven operations:
310
+
311
+ ```javascript
312
+ const sha = await (await graph.createPatch())
313
+ .addNode('n1') // create a node
314
+ .removeNode('n1') // tombstone a node
315
+ .addEdge('n1', 'n2', 'label') // create a directed edge
316
+ .removeEdge('n1', 'n2', 'label') // tombstone an edge
317
+ .setProperty('n1', 'key', 'value') // set a node property (LWW)
318
+ .setEdgeProperty('n1', 'n2', 'label', 'weight', 0.8) // set an edge property (LWW)
319
+ .commit(); // commit as a single atomic patch
320
+ ```
321
+
322
+ Each `commit()` creates one Git commit containing all the operations, advances the writer's Lamport clock, and updates the writer's ref via compare-and-swap.
323
+
324
+ ### Writer API
325
+
326
+ For repeated writes, the Writer API is more convenient:
327
+
328
+ ```javascript
329
+ const writer = await graph.writer();
330
+
331
+ await writer.commitPatch(p => {
332
+ p.addNode('item:1');
333
+ p.setProperty('item:1', 'status', 'active');
334
+ });
335
+ ```
336
+
337
+ ## Checkpoints and Garbage Collection
338
+
339
+ ```javascript
340
+ // Checkpoint current state for fast future materialization
341
+ await graph.materialize();
342
+ await graph.createCheckpoint();
343
+
344
+ // GC removes tombstones when safe
345
+ const metrics = graph.getGCMetrics();
346
+ const { ran, result } = graph.maybeRunGC();
347
+
348
+ // Or configure automatic checkpointing
349
+ const graph = await WarpGraph.open({
350
+ persistence,
351
+ graphName: 'demo',
352
+ writerId: 'writer-1',
353
+ checkpointPolicy: { every: 500 }, // auto-checkpoint every 500 patches
354
+ });
355
+ ```
356
+
357
+ ## Observability
358
+
359
+ ```javascript
360
+ // Operational health snapshot (does not trigger materialization)
361
+ const status = await graph.status();
362
+ // {
363
+ // cachedState: 'fresh', // 'fresh' | 'stale' | 'none'
364
+ // patchesSinceCheckpoint: 12,
365
+ // tombstoneRatio: 0.03,
366
+ // writers: 2,
367
+ // frontier: { alice: 'abc...', bob: 'def...' },
368
+ // }
369
+
370
+ // Tick receipts: see exactly what happened during materialization
371
+ const { state, receipts } = await graph.materialize({ receipts: true });
372
+ for (const receipt of receipts) {
373
+ for (const op of receipt.ops) {
374
+ if (op.result === 'superseded') {
375
+ console.log(`${op.op} on ${op.target}: ${op.reason}`);
376
+ }
377
+ }
378
+ }
379
+ ```
380
+
381
+ Core operations (`materialize()`, `syncWith()`, `createCheckpoint()`, `runGC()`) emit structured timing logs via `LoggerPort` when a logger is injected.
382
+
383
+ ## CLI
384
+
385
+ The CLI is available as `warp-graph` or as a Git subcommand `git warp`.
386
+
387
+ ```bash
388
+ # Install the git subcommand
389
+ npm run install:git-warp
390
+
391
+ # List graphs in a repo
392
+ git warp info
393
+
394
+ # Query nodes by pattern
395
+ git warp query --match 'user:*' --outgoing manages --json
396
+
397
+ # Find shortest path between nodes
398
+ git warp path --from user:alice --to user:bob --dir out
399
+
400
+ # Show patch history for a writer
401
+ git warp history --writer alice
402
+
403
+ # Check graph health, status, and GC metrics
404
+ git warp check
405
+
406
+ # Visualize query results (ascii output by default)
407
+ git warp query --match 'user:*' --outgoing manages --view
408
+ ```
409
+
410
+ All commands accept `--repo <path>` to target a specific Git repository, `--json` for machine-readable output, and `--view [mode]` for visual output (ascii by default, or browser, svg:FILE, html:FILE).
411
+
412
+ ## Architecture
413
+
414
+ The codebase follows hexagonal architecture with ports and adapters:
415
+
416
+ **Ports** define abstract interfaces for infrastructure:
417
+ - `GraphPersistencePort` -- Git operations (composite of CommitPort, BlobPort, TreePort, RefPort, ConfigPort)
418
+ - `CommitPort` / `BlobPort` / `TreePort` / `RefPort` / `ConfigPort` -- focused persistence interfaces
419
+ - `IndexStoragePort` -- bitmap index storage
420
+ - `CodecPort` -- encode/decode operations
421
+ - `CryptoPort` -- hash/HMAC operations
422
+ - `LoggerPort` -- structured logging
423
+ - `ClockPort` -- time measurement
424
+
425
+ **Adapters** implement the ports:
426
+ - `GitGraphAdapter` -- wraps `@git-stunts/plumbing` for Git operations
427
+ - `ClockAdapter` -- unified clock (factory: `ClockAdapter.node()`, `ClockAdapter.global()`)
428
+ - `NodeCryptoAdapter` -- cryptographic operations via `node:crypto`
429
+ - `WebCryptoAdapter` -- cryptographic operations via Web Crypto API (browsers, Deno, Bun, Node 20+)
430
+ - `NodeHttpAdapter` / `BunHttpAdapter` / `DenoHttpAdapter` -- HTTP server per runtime
431
+ - `ConsoleLogger` / `NoOpLogger` -- logging implementations
432
+ - `CborCodec` -- CBOR serialization for patches
433
+
434
+ **Domain** contains the core logic:
435
+ - `WarpGraph` -- public API facade
436
+ - `Writer` / `PatchSession` -- patch creation and commit
437
+ - `JoinReducer` -- CRDT-based state materialization
438
+ - `QueryBuilder` -- fluent query construction
439
+ - `LogicalTraversal` -- graph traversal over materialized state
440
+ - `SyncProtocol` -- multi-writer synchronization
441
+ - `CheckpointService` -- state snapshot creation and loading
442
+ - `ObserverView` -- read-only filtered graph projections
443
+ - `TemporalQuery` -- CTL* temporal operators over history
444
+ - `TranslationCost` -- MDL cost estimation between observer views
445
+ - `BitmapIndexBuilder` / `BitmapIndexReader` -- roaring bitmap indexes
446
+ - `VersionVector` / `ORSet` / `LWW` -- CRDT primitives
447
+
448
+ ## Dependencies
449
+
450
+ | Package | Purpose |
451
+ |---------|---------|
452
+ | `@git-stunts/plumbing` | Low-level Git operations |
453
+ | `@git-stunts/alfred` | Retry with exponential backoff |
454
+ | `@git-stunts/trailer-codec` | Git trailer encoding |
455
+ | `cbor-x` | CBOR binary serialization |
456
+ | `roaring` | Roaring bitmap indexes (native C++ bindings) |
457
+ | `zod` | Schema validation |
458
+
459
+ ## Testing
460
+
461
+ ```bash
462
+ npm test # unit tests (vitest)
463
+ npm run lint # eslint
464
+ npm run test:bench # benchmarks
465
+ npm run test:bats # CLI integration tests (Docker + BATS)
466
+ ```
467
+
468
+ ## AIΩN Foundations Series
469
+
470
+ This package is the reference implementation of WARP (Worldline Algebra for Recursive Provenance) graphs as described in the AIΩN Foundations Series. The papers define WARP graphs as a minimal recursive state object ([Paper I](https://doi.org/10.5281/zenodo.17908005)), equip them with deterministic tick-based operational semantics ([Paper II](https://doi.org/10.5281/zenodo.17934512)), develop computational holography, provenance payloads, and prefix forks ([Paper III](https://doi.org/10.5281/zenodo.17963669)), and introduce observer geometry with rulial distance and temporal logic ([Paper IV](https://doi.org/10.5281/zenodo.18038297)). This codebase implements the core data structures and multi-writer collaboration protocol described in those papers.
471
+
472
+ ## License
473
+
474
+ Apache-2.0
475
+
476
+ ---
477
+
478
+ <p align="center">
479
+ <sub>Built by <a href="https://github.com/flyingrobots">FLYING ROBOTS</a></sub>
480
+ </p>
package/SECURITY.md ADDED
@@ -0,0 +1,30 @@
1
+ # Security Model
2
+
3
+ @git-stunts/git-warp is designed with security-by-default principles, treating the underlying Git binary as an untrusted subsystem through the `@git-stunts/plumbing` layer.
4
+
5
+ ## 🛡️ Security Through Plumbing
6
+
7
+ This library inherits all security protections from `@git-stunts/plumbing`:
8
+
9
+ - **Command Sanitization**: All Git commands are validated through a strict whitelist
10
+ - **Argument Injection Prevention**: Refs are validated against strict patterns to prevent command injection
11
+ - **No Arbitrary Commands**: Only safe Git plumbing commands are permitted
12
+ - **Environment Isolation**: Git processes run in a clean environment with minimal variables
13
+
14
+ ## 🚫 Ref Validation
15
+
16
+ The `GitGraphAdapter` validates all ref arguments to prevent injection attacks:
17
+
18
+ - Refs must match the pattern: `^[a-zA-Z0-9._/-]+((~\d*|\^\d*|\.\.[a-zA-Z0-9._/-]+)*)$`
19
+ - Refs cannot start with `-` or `--` to prevent option injection
20
+ - Invalid refs throw an error immediately
21
+
22
+ ## 🌊 Resource Protection
23
+
24
+ - **Streaming-First**: Large graph traversals use async generators to prevent OOM
25
+ - **Bitmap Indexing**: Sharded Roaring Bitmap indexes enable O(1) lookups without loading entire graphs
26
+ - **Delimiter Safety**: Uses ASCII Record Separator (`\x1E`) to prevent message collision
27
+
28
+ ## 🐞 Reporting a Vulnerability
29
+
30
+ If you discover a security vulnerability, please send an e-mail to [james@flyingrobots.dev](mailto:james@flyingrobots.dev).
package/bin/git-warp ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import { existsSync } from 'node:fs';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const dir = dirname(fileURLToPath(import.meta.url));
9
+ const cliPath = resolve(dir, 'warp-graph.js');
10
+ const args = process.argv.slice(2);
11
+ let result;
12
+
13
+ if (existsSync(cliPath)) {
14
+ result = spawnSync(process.execPath, [cliPath, ...args], { stdio: 'inherit' });
15
+ } else {
16
+ result = spawnSync('warp-graph', args, { stdio: 'inherit' });
17
+ }
18
+
19
+ if (result.error) {
20
+ process.stderr.write(result.error.message + '\n');
21
+ process.exit(result.error.code || 1);
22
+ }
23
+
24
+ process.exit(result.status ?? 1);