@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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * TemporalQuery - CTL*-style temporal operators over WARP graph history.
3
+ *
4
+ * Implements `always` and `eventually` temporal operators from Paper IV
5
+ * (Echo and the WARP Core). These operators evaluate predicates across
6
+ * the graph's history by replaying patches incrementally and checking
7
+ * the predicate at each tick boundary.
8
+ *
9
+ * ## Temporal Operators
10
+ *
11
+ * - **always(nodeId, predicate, { since })**: True iff the predicate holds
12
+ * at every tick since `since` where the node exists.
13
+ * - **eventually(nodeId, predicate, { since })**: True iff the predicate holds
14
+ * at some tick since `since`.
15
+ *
16
+ * ## Implementation
17
+ *
18
+ * Both operators collect all patches, sort them by causal order (same as
19
+ * materialization), then apply patches one at a time. After each patch
20
+ * application, a node snapshot is extracted and passed to the predicate.
21
+ *
22
+ * The "tick" corresponds to a patch's Lamport timestamp. The `since` option
23
+ * filters out patches with Lamport timestamps below the threshold.
24
+ *
25
+ * @module domain/services/TemporalQuery
26
+ * @see Paper IV - Echo and the WARP Core (CTL* temporal logic on histories)
27
+ */
28
+
29
+ import { createEmptyStateV5, join as joinPatch } from './JoinReducer.js';
30
+ import { decodePropKey } from './KeyCodec.js';
31
+ import { orsetContains } from '../crdt/ORSet.js';
32
+
33
+ /**
34
+ * Unwraps a property value from its CRDT envelope.
35
+ *
36
+ * InlineValue objects `{ type: 'inline', value: ... }` are unwrapped
37
+ * to their inner value. All other values pass through unchanged.
38
+ *
39
+ * @param {*} value - Property value (potentially InlineValue-wrapped)
40
+ * @returns {*} The unwrapped value
41
+ * @private
42
+ */
43
+ function unwrapValue(value) {
44
+ if (value && typeof value === 'object' && value.type === 'inline') {
45
+ return value.value;
46
+ }
47
+ return value;
48
+ }
49
+
50
+ /**
51
+ * Extracts a node snapshot from the current WARP state.
52
+ *
53
+ * Returns an object with `{ id, exists, props }` where props is a
54
+ * plain object mapping property keys to their unwrapped values.
55
+ * InlineValue wrappers are stripped so predicates can compare against
56
+ * raw values directly (e.g., `n.props.status === 'active'`).
57
+ *
58
+ * If the node does not exist in the state, `exists` is false and
59
+ * `props` is an empty object.
60
+ *
61
+ * @param {import('./JoinReducer.js').WarpStateV5} state - Current state
62
+ * @param {string} nodeId - Node ID to extract
63
+ * @returns {{ id: string, exists: boolean, props: Object<string, *> }}
64
+ */
65
+ function extractNodeSnapshot(state, nodeId) {
66
+ const exists = orsetContains(state.nodeAlive, nodeId);
67
+ const props = {};
68
+
69
+ if (exists) {
70
+ const prefix = `${nodeId}\0`;
71
+ for (const [propKey, register] of state.prop) {
72
+ if (propKey.startsWith(prefix)) {
73
+ const decoded = decodePropKey(propKey);
74
+ props[decoded.propKey] = unwrapValue(register.value);
75
+ }
76
+ }
77
+ }
78
+
79
+ return { id: nodeId, exists, props };
80
+ }
81
+
82
+ /**
83
+ * TemporalQuery provides temporal logic operators over graph history.
84
+ *
85
+ * Constructed by WarpGraph and exposed via `graph.temporal`.
86
+ * Both methods are async because they need to load patches from Git.
87
+ */
88
+ export class TemporalQuery {
89
+ /**
90
+ * @param {Object} options
91
+ * @param {Function} options.loadAllPatches - Async function that returns
92
+ * all patches as Array<{ patch, sha }> in causal order.
93
+ */
94
+ constructor({ loadAllPatches }) {
95
+ /** @type {Function} */
96
+ this._loadAllPatches = loadAllPatches;
97
+ }
98
+
99
+ /**
100
+ * Tests whether a predicate holds at every tick since `since`.
101
+ *
102
+ * Replays patches from `since` to current. At each tick boundary,
103
+ * builds the node snapshot and tests the predicate. Returns true only
104
+ * if the predicate returned true at every tick where the node existed.
105
+ *
106
+ * Returns false if the node never existed in the range.
107
+ *
108
+ * @param {string} nodeId - The node ID to evaluate
109
+ * @param {Function} predicate - Predicate receiving node snapshot
110
+ * `{ id, exists, props }`. Should return boolean.
111
+ * @param {{ since?: number }} [options={}] - Options
112
+ * @param {number} [options.since=0] - Minimum Lamport tick (inclusive).
113
+ * Only patches with lamport >= since are considered.
114
+ * @returns {Promise<boolean>} True if predicate held at every tick
115
+ *
116
+ * @example
117
+ * const result = await graph.temporal.always(
118
+ * 'user:alice',
119
+ * n => n.props.status === 'active',
120
+ * { since: 0 }
121
+ * );
122
+ */
123
+ async always(nodeId, predicate, options = {}) {
124
+ const since = options.since ?? 0;
125
+ const allPatches = await this._loadAllPatches();
126
+
127
+ const state = createEmptyStateV5();
128
+ let nodeEverExisted = false;
129
+
130
+ for (const { patch, sha } of allPatches) {
131
+ // Apply the patch to state
132
+ joinPatch(state, patch, sha);
133
+
134
+ // Skip patches before the `since` threshold
135
+ if (patch.lamport < since) {
136
+ continue;
137
+ }
138
+
139
+ // Extract node snapshot at this tick
140
+ const snapshot = extractNodeSnapshot(state, nodeId);
141
+
142
+ if (snapshot.exists) {
143
+ nodeEverExisted = true;
144
+ if (!predicate(snapshot)) {
145
+ return false;
146
+ }
147
+ }
148
+ }
149
+
150
+ // If the node never existed in the range, return false
151
+ return nodeEverExisted;
152
+ }
153
+
154
+ /**
155
+ * Tests whether a predicate holds at some tick since `since`.
156
+ *
157
+ * Replays patches from `since` to current. At each tick boundary,
158
+ * builds the node snapshot and tests the predicate. Returns true as
159
+ * soon as the predicate returns true at any tick.
160
+ *
161
+ * @param {string} nodeId - The node ID to evaluate
162
+ * @param {Function} predicate - Predicate receiving node snapshot
163
+ * `{ id, exists, props }`. Should return boolean.
164
+ * @param {{ since?: number }} [options={}] - Options
165
+ * @param {number} [options.since=0] - Minimum Lamport tick (inclusive).
166
+ * Only patches with lamport >= since are considered.
167
+ * @returns {Promise<boolean>} True if predicate held at any tick
168
+ *
169
+ * @example
170
+ * const result = await graph.temporal.eventually(
171
+ * 'user:alice',
172
+ * n => n.props.status === 'merged',
173
+ * { since: 0 }
174
+ * );
175
+ */
176
+ async eventually(nodeId, predicate, options = {}) {
177
+ const since = options.since ?? 0;
178
+ const allPatches = await this._loadAllPatches();
179
+
180
+ const state = createEmptyStateV5();
181
+
182
+ for (const { patch, sha } of allPatches) {
183
+ // Apply the patch to state
184
+ joinPatch(state, patch, sha);
185
+
186
+ // Skip patches before the `since` threshold
187
+ if (patch.lamport < since) {
188
+ continue;
189
+ }
190
+
191
+ // Extract node snapshot at this tick
192
+ const snapshot = extractNodeSnapshot(state, nodeId);
193
+
194
+ if (snapshot.exists && predicate(snapshot)) {
195
+ return true;
196
+ }
197
+ }
198
+
199
+ return false;
200
+ }
201
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * TranslationCost - MDL-based cost estimation between observer views.
3
+ *
4
+ * Computes the directed cost of translating observer A's view into
5
+ * observer B's view, measuring information loss via Minimum Description
6
+ * Length (MDL) of the translation function.
7
+ *
8
+ * The cost is normalized to [0, 1]:
9
+ * 0 = identical views (no information lost)
10
+ * 1 = completely disjoint views (all information lost)
11
+ *
12
+ * @module domain/services/TranslationCost
13
+ * @see Paper IV, Section 4 -- Directed rulial cost
14
+ */
15
+
16
+ import { orsetElements, orsetContains } from '../crdt/ORSet.js';
17
+ import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
18
+
19
+ /**
20
+ * Tests whether a string matches a glob-style pattern.
21
+ *
22
+ * @param {string} pattern - Glob pattern (e.g. 'user:*', '*:admin', '*')
23
+ * @param {string} str - The string to test
24
+ * @returns {boolean} True if the string matches the pattern
25
+ */
26
+ function matchGlob(pattern, str) {
27
+ if (pattern === '*') {
28
+ return true;
29
+ }
30
+ if (!pattern.includes('*')) {
31
+ return pattern === str;
32
+ }
33
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
34
+ const regex = new RegExp(`^${escaped.replace(/\*/g, '.*')}$`);
35
+ return regex.test(str);
36
+ }
37
+
38
+ /**
39
+ * Computes the set of property keys visible under an observer config.
40
+ *
41
+ * @param {Map<string, *>} allNodeProps - Map of propKey -> placeholder
42
+ * @param {string[]|undefined} expose - Whitelist of property keys
43
+ * @param {string[]|undefined} redact - Blacklist of property keys
44
+ * @returns {Set<string>} Visible property keys
45
+ */
46
+ function visiblePropKeys(allNodeProps, expose, redact) {
47
+ const redactSet = redact && redact.length > 0 ? new Set(redact) : null;
48
+ const exposeSet = expose && expose.length > 0 ? new Set(expose) : null;
49
+
50
+ const keys = new Set();
51
+ for (const key of allNodeProps.keys()) {
52
+ if (redactSet && redactSet.has(key)) {
53
+ continue;
54
+ }
55
+ if (exposeSet && !exposeSet.has(key)) {
56
+ continue;
57
+ }
58
+ keys.add(key);
59
+ }
60
+ return keys;
61
+ }
62
+
63
+ /**
64
+ * Collects node property keys from state for a given node.
65
+ *
66
+ * @param {*} state - WarpStateV5 materialized state
67
+ * @param {string} nodeId - The node ID
68
+ * @returns {Map<string, boolean>} Map of propKey -> true
69
+ */
70
+ function collectNodePropKeys(state, nodeId) {
71
+ const props = new Map();
72
+ for (const [propKey] of state.prop) {
73
+ if (isEdgePropKey(propKey)) {
74
+ continue;
75
+ }
76
+ const decoded = decodePropKey(propKey);
77
+ if (decoded.nodeId === nodeId) {
78
+ props.set(decoded.propKey, true);
79
+ }
80
+ }
81
+ return props;
82
+ }
83
+
84
+ // Weights for MDL cost components
85
+ const NODE_WEIGHT = 0.5;
86
+ const EDGE_WEIGHT = 0.3;
87
+ const PROP_WEIGHT = 0.2;
88
+
89
+ /** @returns {{ cost: 0, breakdown: { nodeLoss: 0, edgeLoss: 0, propLoss: 0 } }} */
90
+ function zeroCost() {
91
+ return { cost: 0, breakdown: { nodeLoss: 0, edgeLoss: 0, propLoss: 0 } };
92
+ }
93
+
94
+ /**
95
+ * Counts how many items in `source` are absent from `targetSet`.
96
+ *
97
+ * @param {Array|Set} source - Source collection
98
+ * @param {Set} targetSet - Target set to test against
99
+ * @returns {number}
100
+ */
101
+ function countMissing(source, targetSet) {
102
+ let count = 0;
103
+ for (const item of source) {
104
+ if (!targetSet.has(item)) {
105
+ count++;
106
+ }
107
+ }
108
+ return count;
109
+ }
110
+
111
+ /**
112
+ * Computes edge loss between two observer node sets.
113
+ *
114
+ * @param {*} state - WarpStateV5
115
+ * @param {Set<string>} nodesASet - Nodes visible to A
116
+ * @param {Set<string>} nodesBSet - Nodes visible to B
117
+ * @returns {number} edgeLoss fraction
118
+ */
119
+ function computeEdgeLoss(state, nodesASet, nodesBSet) {
120
+ const allEdges = [...orsetElements(state.edgeAlive)];
121
+ const edgesA = [];
122
+ const edgesBSet = new Set();
123
+
124
+ for (const edgeKey of allEdges) {
125
+ const { from, to } = decodeEdgeKey(edgeKey);
126
+ if (!orsetContains(state.nodeAlive, from) || !orsetContains(state.nodeAlive, to)) {
127
+ continue;
128
+ }
129
+ if (nodesASet.has(from) && nodesASet.has(to)) {
130
+ edgesA.push(edgeKey);
131
+ }
132
+ if (nodesBSet.has(from) && nodesBSet.has(to)) {
133
+ edgesBSet.add(edgeKey);
134
+ }
135
+ }
136
+
137
+ return countMissing(edgesA, edgesBSet) / Math.max(edgesA.length, 1);
138
+ }
139
+
140
+ /**
141
+ * Counts lost properties for a single node between two observer configs.
142
+ *
143
+ * @param {Map<string, boolean>} nodeProps - Property keys for the node
144
+ * @param {{ configA: Object, configB: Object, nodeInB: boolean }} opts
145
+ * @returns {{ propsInA: number, lostProps: number }}
146
+ */
147
+ function countNodePropLoss(nodeProps, { configA, configB, nodeInB }) {
148
+ const propsA = visiblePropKeys(nodeProps, configA.expose, configA.redact);
149
+ if (!nodeInB) {
150
+ return { propsInA: propsA.size, lostProps: propsA.size };
151
+ }
152
+ const propsB = visiblePropKeys(nodeProps, configB.expose, configB.redact);
153
+ return { propsInA: propsA.size, lostProps: countMissing(propsA, propsB) };
154
+ }
155
+
156
+ /**
157
+ * Computes property loss across all A-visible nodes.
158
+ *
159
+ * @param {*} state - WarpStateV5
160
+ * @param {{ nodesA: string[], nodesBSet: Set<string>, configA: Object, configB: Object }} opts
161
+ * @returns {number} propLoss fraction
162
+ */
163
+ function computePropLoss(state, { nodesA, nodesBSet, configA, configB }) {
164
+ let totalPropsInA = 0;
165
+ let totalLostProps = 0;
166
+
167
+ for (const nodeId of nodesA) {
168
+ const nodeProps = collectNodePropKeys(state, nodeId);
169
+ if (nodeProps.size === 0) {
170
+ continue;
171
+ }
172
+ const { propsInA, lostProps } = countNodePropLoss(
173
+ nodeProps, { configA, configB, nodeInB: nodesBSet.has(nodeId) }
174
+ );
175
+ totalPropsInA += propsInA;
176
+ totalLostProps += lostProps;
177
+ }
178
+
179
+ return totalLostProps / Math.max(totalPropsInA, 1);
180
+ }
181
+
182
+ /**
183
+ * Computes the directed MDL translation cost from observer A to observer B.
184
+ *
185
+ * The cost measures how much information is lost when translating from
186
+ * A's view to B's view. It is asymmetric: cost(A->B) != cost(B->A) in general.
187
+ *
188
+ * @param {Object} configA - Observer configuration for A
189
+ * @param {string} configA.match - Glob pattern for visible nodes
190
+ * @param {string[]} [configA.expose] - Property keys to include
191
+ * @param {string[]} [configA.redact] - Property keys to exclude
192
+ * @param {Object} configB - Observer configuration for B
193
+ * @param {string} configB.match - Glob pattern for visible nodes
194
+ * @param {string[]} [configB.expose] - Property keys to include
195
+ * @param {string[]} [configB.redact] - Property keys to exclude
196
+ * @param {*} state - WarpStateV5 materialized state
197
+ * @returns {{ cost: number, breakdown: { nodeLoss: number, edgeLoss: number, propLoss: number } }}
198
+ */
199
+ export function computeTranslationCost(configA, configB, state) {
200
+ if (!configA || typeof configA.match !== 'string' ||
201
+ !configB || typeof configB.match !== 'string') {
202
+ throw new Error('configA.match and configB.match must be strings');
203
+ }
204
+ const allNodes = [...orsetElements(state.nodeAlive)];
205
+ const nodesA = allNodes.filter((id) => matchGlob(configA.match, id));
206
+
207
+ if (nodesA.length === 0) {
208
+ return zeroCost();
209
+ }
210
+
211
+ const nodesASet = new Set(nodesA);
212
+ const nodesBSet = new Set(allNodes.filter((id) => matchGlob(configB.match, id)));
213
+
214
+ const nodeLoss = countMissing(nodesA, nodesBSet) / Math.max(nodesA.length, 1);
215
+ const edgeLoss = computeEdgeLoss(state, nodesASet, nodesBSet);
216
+ const propLoss = computePropLoss(state, { nodesA, nodesBSet, configA, configB });
217
+
218
+ const cost = NODE_WEIGHT * nodeLoss + EDGE_WEIGHT * edgeLoss + PROP_WEIGHT * propLoss;
219
+
220
+ return { cost, breakdown: { nodeLoss, edgeLoss, propLoss } };
221
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @deprecated Use CommitDagTraversalService instead.
3
+ * This alias will be removed after one minor version.
4
+ */
5
+
6
+ import CommitDagTraversalService from './CommitDagTraversalService.js';
7
+
8
+ export default CommitDagTraversalService;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * WARP Message Codec — facade re-exporting all message encoding, decoding,
3
+ * and schema utilities.
4
+ *
5
+ * This module provides backward-compatible access to the three types of
6
+ * WARP (Write-Ahead Reference Protocol) commit messages:
7
+ * - Patch: Contains graph mutations from a single writer
8
+ * - Checkpoint: Contains a snapshot of materialized graph state
9
+ * - Anchor: Marks a merge point in the WARP DAG
10
+ *
11
+ * Implementation is split across focused sub-modules:
12
+ * - {@link module:domain/services/PatchMessageCodec}
13
+ * - {@link module:domain/services/CheckpointMessageCodec}
14
+ * - {@link module:domain/services/AnchorMessageCodec}
15
+ * - {@link module:domain/services/MessageSchemaDetector}
16
+ *
17
+ * @module domain/services/WarpMessageCodec
18
+ */
19
+
20
+ export { encodePatchMessage, decodePatchMessage } from './PatchMessageCodec.js';
21
+ export { encodeCheckpointMessage, decodeCheckpointMessage } from './CheckpointMessageCodec.js';
22
+ export { encodeAnchorMessage, decodeAnchorMessage } from './AnchorMessageCodec.js';
23
+ export {
24
+ detectSchemaVersion,
25
+ detectMessageKind,
26
+ assertOpsCompatible,
27
+ SCHEMA_V2,
28
+ SCHEMA_V3,
29
+ } from './MessageSchemaDetector.js';
@@ -0,0 +1,127 @@
1
+ /**
2
+ * WarpStateIndexBuilder - Builds bitmap index from materialized WARP state.
3
+ *
4
+ * This builder creates adjacency indexes from WarpStateV5.edgeAlive OR-Set,
5
+ * NOT from Git commit DAG topology. This is the correct WARP architecture
6
+ * as specified in TECH-SPEC-V7.md Task 6.
7
+ *
8
+ * The index supports O(1) neighbor lookups by node ID.
9
+ *
10
+ * @module domain/services/WarpStateIndexBuilder
11
+ */
12
+
13
+ import BitmapIndexBuilder from './BitmapIndexBuilder.js';
14
+ import { orsetContains, orsetElements } from '../crdt/ORSet.js';
15
+ import { decodeEdgeKey } from './KeyCodec.js';
16
+
17
+ /**
18
+ * Builds a bitmap index from materialized WARP state.
19
+ *
20
+ * This is the V7-compliant index builder that operates on logical graph edges
21
+ * from the edgeAlive OR-Set, not Git commit parents.
22
+ *
23
+ * @example
24
+ * import WarpStateIndexBuilder from './WarpStateIndexBuilder.js';
25
+ *
26
+ * const state = await graph.materialize();
27
+ * const builder = new WarpStateIndexBuilder();
28
+ * const indexData = builder.buildFromState(state);
29
+ */
30
+ export default class WarpStateIndexBuilder {
31
+ /**
32
+ * Creates a new WarpStateIndexBuilder.
33
+ * @param {Object} [options] - Configuration
34
+ * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort for shard checksums
35
+ */
36
+ constructor({ crypto } = {}) {
37
+ /** @type {BitmapIndexBuilder} */
38
+ this._builder = new BitmapIndexBuilder({ crypto });
39
+ }
40
+
41
+ /**
42
+ * Builds an index from materialized WARP state.
43
+ *
44
+ * Iterates over edgeAlive OR-Set and creates forward/reverse adjacency
45
+ * bitmaps for each node. Only includes edges where both endpoints are
46
+ * visible (exist in nodeAlive).
47
+ *
48
+ * @param {import('./JoinReducer.js').WarpStateV5} state - The materialized state
49
+ * @returns {{builder: BitmapIndexBuilder, stats: {nodes: number, edges: number}}} The populated builder and stats
50
+ * @throws {Error} If state is null or missing nodeAlive/edgeAlive fields
51
+ *
52
+ * @example
53
+ * const state = await graph.materialize();
54
+ * const { builder, stats } = new WarpStateIndexBuilder().buildFromState(state);
55
+ * console.log(`Indexed ${stats.nodes} nodes, ${stats.edges} edges`);
56
+ * const indexTree = await builder.serialize();
57
+ */
58
+ buildFromState(state) {
59
+ if (!state || !state.nodeAlive || !state.edgeAlive) {
60
+ throw new Error('Invalid state: must be a valid WarpStateV5 object');
61
+ }
62
+
63
+ let nodeCount = 0;
64
+ let edgeCount = 0;
65
+
66
+ // Register all visible nodes
67
+ for (const nodeId of orsetElements(state.nodeAlive)) {
68
+ this._builder.registerNode(nodeId);
69
+ nodeCount++;
70
+ }
71
+
72
+ // Add edges where both endpoints are visible
73
+ for (const edgeKey of orsetElements(state.edgeAlive)) {
74
+ const { from, to } = decodeEdgeKey(edgeKey);
75
+
76
+ // Only index edges where both endpoints exist in nodeAlive
77
+ if (orsetContains(state.nodeAlive, from) && orsetContains(state.nodeAlive, to)) {
78
+ this._builder.addEdge(from, to);
79
+ edgeCount++;
80
+ }
81
+ }
82
+
83
+ return {
84
+ builder: this._builder,
85
+ stats: { nodes: nodeCount, edges: edgeCount },
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Serializes the index to a tree structure of buffers.
91
+ *
92
+ * @returns {Promise<Record<string, Buffer>>} Map of path → serialized content
93
+ */
94
+ async serialize() {
95
+ return await this._builder.serialize();
96
+ }
97
+
98
+ /**
99
+ * Gets the underlying BitmapIndexBuilder.
100
+ *
101
+ * @returns {BitmapIndexBuilder}
102
+ */
103
+ get builder() {
104
+ return this._builder;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Convenience function to build and serialize a WARP state index.
110
+ *
111
+ * @param {import('./JoinReducer.js').WarpStateV5} state - The materialized state
112
+ * @param {Object} [options] - Configuration
113
+ * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort for shard checksums
114
+ * @returns {Promise<{tree: Record<string, Buffer>, stats: {nodes: number, edges: number}}>} Serialized index and stats
115
+ *
116
+ * @example
117
+ * import { buildWarpStateIndex } from './WarpStateIndexBuilder.js';
118
+ *
119
+ * const state = await graph.materialize();
120
+ * const { tree, stats } = await buildWarpStateIndex(state);
121
+ */
122
+ export async function buildWarpStateIndex(state, { crypto } = {}) {
123
+ const indexBuilder = new WarpStateIndexBuilder({ crypto });
124
+ const { stats } = indexBuilder.buildFromState(state);
125
+ const tree = await indexBuilder.serialize();
126
+ return { tree, stats };
127
+ }