@git-stunts/git-warp 12.2.0 → 12.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/bin/cli/commands/trust.js +37 -1
- package/bin/cli/infrastructure.js +14 -1
- package/bin/cli/schemas.js +4 -4
- package/bin/presenters/text.js +10 -3
- package/bin/warp-graph.js +4 -1
- package/index.d.ts +17 -1
- package/package.json +1 -1
- package/src/domain/WarpGraph.js +1 -1
- package/src/domain/crdt/Dot.js +5 -0
- package/src/domain/crdt/LWW.js +3 -1
- package/src/domain/crdt/ORSet.js +33 -23
- package/src/domain/crdt/VersionVector.js +12 -0
- package/src/domain/errors/PatchError.js +27 -0
- package/src/domain/errors/StorageError.js +8 -0
- package/src/domain/errors/WriterError.js +5 -0
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditReceiptService.js +2 -1
- package/src/domain/services/AuditVerifierService.js +33 -2
- package/src/domain/services/BitmapIndexBuilder.js +14 -9
- package/src/domain/services/BoundaryTransitionRecord.js +1 -0
- package/src/domain/services/CheckpointMessageCodec.js +5 -0
- package/src/domain/services/CheckpointService.js +29 -2
- package/src/domain/services/GCPolicy.js +25 -4
- package/src/domain/services/GraphTraversal.js +3 -1
- package/src/domain/services/IncrementalIndexUpdater.js +179 -36
- package/src/domain/services/JoinReducer.js +311 -75
- package/src/domain/services/KeyCodec.js +48 -0
- package/src/domain/services/MaterializedViewService.js +14 -3
- package/src/domain/services/MessageSchemaDetector.js +35 -5
- package/src/domain/services/OpNormalizer.js +79 -0
- package/src/domain/services/PatchBuilderV2.js +240 -160
- package/src/domain/services/QueryBuilder.js +4 -0
- package/src/domain/services/SyncAuthService.js +3 -0
- package/src/domain/services/SyncController.js +12 -31
- package/src/domain/services/SyncProtocol.js +76 -32
- package/src/domain/services/WarpMessageCodec.js +2 -0
- package/src/domain/trust/TrustCrypto.js +8 -5
- package/src/domain/trust/TrustRecordService.js +50 -36
- package/src/domain/types/TickReceipt.js +6 -4
- package/src/domain/types/WarpTypesV2.js +77 -5
- package/src/domain/utils/CachedValue.js +34 -5
- package/src/domain/utils/EventId.js +4 -1
- package/src/domain/utils/LRUCache.js +3 -1
- package/src/domain/utils/RefLayout.js +4 -0
- package/src/domain/utils/canonicalStringify.js +48 -18
- package/src/domain/utils/defaultClock.js +1 -0
- package/src/domain/utils/matchGlob.js +7 -0
- package/src/domain/warp/PatchSession.js +30 -24
- package/src/domain/warp/Writer.js +12 -1
- package/src/domain/warp/_wiredMethods.d.ts +1 -1
- package/src/domain/warp/checkpoint.methods.js +36 -7
- package/src/domain/warp/fork.methods.js +1 -1
- package/src/domain/warp/materialize.methods.js +44 -5
- package/src/domain/warp/materializeAdvanced.methods.js +50 -10
- package/src/domain/warp/patch.methods.js +21 -11
- package/src/infrastructure/adapters/GitGraphAdapter.js +55 -52
- package/src/infrastructure/codecs/CborCodec.js +2 -0
- package/src/domain/utils/fnv1a.js +0 -20
|
@@ -21,6 +21,9 @@ import IncrementalIndexUpdater from './IncrementalIndexUpdater.js';
|
|
|
21
21
|
import { orsetElements, orsetContains } from '../crdt/ORSet.js';
|
|
22
22
|
import { decodeEdgeKey } from './KeyCodec.js';
|
|
23
23
|
|
|
24
|
+
/** Prefix for property shard paths in the index tree. */
|
|
25
|
+
const PROPS_PREFIX = 'props_';
|
|
26
|
+
|
|
24
27
|
/**
|
|
25
28
|
* @typedef {import('./BitmapNeighborProvider.js').LogicalIndex} LogicalIndex
|
|
26
29
|
*/
|
|
@@ -66,7 +69,7 @@ function buildInMemoryPropertyReader(tree, codec) {
|
|
|
66
69
|
/** @type {Record<string, string>} */
|
|
67
70
|
const propShardOids = {};
|
|
68
71
|
for (const path of Object.keys(tree)) {
|
|
69
|
-
if (path.startsWith(
|
|
72
|
+
if (path.startsWith(PROPS_PREFIX)) {
|
|
70
73
|
propShardOids[path] = path;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -93,7 +96,7 @@ function partitionShardOids(shardOids) {
|
|
|
93
96
|
const propOids = {};
|
|
94
97
|
|
|
95
98
|
for (const [path, oid] of Object.entries(shardOids)) {
|
|
96
|
-
if (path.startsWith(
|
|
99
|
+
if (path.startsWith(PROPS_PREFIX)) {
|
|
97
100
|
propOids[path] = oid;
|
|
98
101
|
} else {
|
|
99
102
|
indexOids[path] = oid;
|
|
@@ -105,6 +108,10 @@ function partitionShardOids(shardOids) {
|
|
|
105
108
|
/**
|
|
106
109
|
* Mulberry32 PRNG — deterministic 32-bit generator from a seed.
|
|
107
110
|
*
|
|
111
|
+
* mulberry32 is a fast 32-bit PRNG by Tommy Ettinger. The magic constants
|
|
112
|
+
* (0x6D2B79F5, shifts 15/13/16) are part of the published algorithm.
|
|
113
|
+
* See: https://gist.github.com/tommyettinger/46a874533244883189143505d203312c
|
|
114
|
+
*
|
|
108
115
|
* @param {number} seed
|
|
109
116
|
* @returns {() => number} Returns values in [0, 1)
|
|
110
117
|
*/
|
|
@@ -134,6 +141,10 @@ function sampleNodes(allNodes, sampleRate, seed) {
|
|
|
134
141
|
}
|
|
135
142
|
const rng = mulberry32(seed);
|
|
136
143
|
const sampled = allNodes.filter(() => rng() < sampleRate);
|
|
144
|
+
// When the initial sample is empty (e.g., graph has fewer nodes than
|
|
145
|
+
// sample size), we fall back to using all available nodes. This changes
|
|
146
|
+
// the distribution but is acceptable since the sample is only used for
|
|
147
|
+
// layout heuristics.
|
|
137
148
|
if (sampled.length === 0) {
|
|
138
149
|
sampled.push(allNodes[Math.floor(rng() * allNodes.length)]);
|
|
139
150
|
}
|
|
@@ -343,7 +354,7 @@ export default class MaterializedViewService {
|
|
|
343
354
|
* @returns {VerifyResult}
|
|
344
355
|
*/
|
|
345
356
|
verifyIndex({ state, logicalIndex, options = {} }) {
|
|
346
|
-
const seed = options.seed ?? (
|
|
357
|
+
const seed = options.seed ?? (Math.random() * 0x7FFFFFFF >>> 0);
|
|
347
358
|
const sampleRate = options.sampleRate ?? 0.1;
|
|
348
359
|
const allNodes = [...orsetElements(state.nodeAlive)].sort();
|
|
349
360
|
const sampled = sampleNodes(allNodes, sampleRate, seed);
|
|
@@ -20,17 +20,33 @@ import { getCodec, TRAILER_KEYS } from './MessageCodecInternal.js';
|
|
|
20
20
|
// -----------------------------------------------------------------------------
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Patch schema version for classic node-only patches (V5 format).
|
|
24
24
|
* @type {number}
|
|
25
25
|
*/
|
|
26
26
|
export const SCHEMA_V2 = 2;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Patch schema version for patches that may contain edge property PropSet ops.
|
|
30
30
|
* @type {number}
|
|
31
31
|
*/
|
|
32
32
|
export const SCHEMA_V3 = 3;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Alias: patch schema v2 (classic node-only patches).
|
|
36
|
+
* Use this when you need to be explicit that you mean *patch* schema,
|
|
37
|
+
* not checkpoint schema.
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
export const PATCH_SCHEMA_V2 = SCHEMA_V2;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Alias: patch schema v3 (edge-property-aware patches).
|
|
44
|
+
* Use this when you need to be explicit that you mean *patch* schema,
|
|
45
|
+
* not checkpoint schema.
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
export const PATCH_SCHEMA_V3 = SCHEMA_V3;
|
|
49
|
+
|
|
34
50
|
// -----------------------------------------------------------------------------
|
|
35
51
|
// Schema Version Detection
|
|
36
52
|
// -----------------------------------------------------------------------------
|
|
@@ -50,6 +66,14 @@ export function detectSchemaVersion(ops) {
|
|
|
50
66
|
return SCHEMA_V2;
|
|
51
67
|
}
|
|
52
68
|
for (const op of ops) {
|
|
69
|
+
if (!op || typeof op !== 'object') {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Canonical EdgePropSet always implies schema 3
|
|
73
|
+
if (op.type === 'EdgePropSet') {
|
|
74
|
+
return SCHEMA_V3;
|
|
75
|
+
}
|
|
76
|
+
// Legacy raw PropSet with edge-property encoding
|
|
53
77
|
if (op.type === 'PropSet' && typeof op.node === 'string' && op.node.startsWith(EDGE_PROP_PREFIX)) {
|
|
54
78
|
return SCHEMA_V3;
|
|
55
79
|
}
|
|
@@ -90,10 +114,16 @@ export function assertOpsCompatible(ops, maxSchema) {
|
|
|
90
114
|
return;
|
|
91
115
|
}
|
|
92
116
|
for (const op of ops) {
|
|
117
|
+
if (!op || typeof op !== 'object') {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
93
120
|
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
op.
|
|
121
|
+
// Canonical EdgePropSet (ADR 1) — should never appear on wire pre-ADR 2,
|
|
122
|
+
// but reject defensively for v2 readers
|
|
123
|
+
op.type === 'EdgePropSet' ||
|
|
124
|
+
(op.type === 'PropSet' &&
|
|
125
|
+
typeof op.node === 'string' &&
|
|
126
|
+
op.node.startsWith(EDGE_PROP_PREFIX))
|
|
97
127
|
) {
|
|
98
128
|
throw new SchemaUnsupportedError(
|
|
99
129
|
'Upgrade to >=7.3.0 (WEIGHTED) to sync edge properties.',
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpNormalizer — raw ↔ canonical operation conversion.
|
|
3
|
+
*
|
|
4
|
+
* ADR 1 (Canonicalize Edge Property Operations Internally) requires that
|
|
5
|
+
* reducers, provenance, receipts, and queries operate on canonical ops:
|
|
6
|
+
*
|
|
7
|
+
* Raw (persisted): NodeAdd, NodeRemove, EdgeAdd, EdgeRemove, PropSet, BlobValue
|
|
8
|
+
* Canonical (internal): NodeAdd, NodeRemove, EdgeAdd, EdgeRemove, NodePropSet, EdgePropSet, BlobValue
|
|
9
|
+
*
|
|
10
|
+
* **Current normalization location:** Normalization is performed at the
|
|
11
|
+
* reducer entry points (`applyFast`, `applyWithReceipt`, `applyWithDiff`
|
|
12
|
+
* in JoinReducer.js), not at the CBOR decode boundary as originally
|
|
13
|
+
* planned in ADR 1. This is a pragmatic deviation — the reducer calls
|
|
14
|
+
* `normalizeRawOp()` on each op before dispatch. Lowering happens in
|
|
15
|
+
* `PatchBuilderV2.build()`/`commit()` via `lowerCanonicalOp()`.
|
|
16
|
+
*
|
|
17
|
+
* @module domain/services/OpNormalizer
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createNodePropSetV2, createEdgePropSetV2, createPropSetV2 } from '../types/WarpTypesV2.js';
|
|
21
|
+
import { isLegacyEdgePropNode, decodeLegacyEdgePropNode, encodeLegacyEdgePropNode } from './KeyCodec.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes a single raw (persisted) op into its canonical form.
|
|
25
|
+
*
|
|
26
|
+
* - Raw `PropSet` with \x01-prefixed node → canonical `EdgePropSet`
|
|
27
|
+
* - Raw `PropSet` without prefix → canonical `NodePropSet`
|
|
28
|
+
* - All other op types pass through unchanged.
|
|
29
|
+
*
|
|
30
|
+
* @param {import('../types/WarpTypesV2.js').RawOpV2 | {type: string}} rawOp
|
|
31
|
+
* @returns {import('../types/WarpTypesV2.js').CanonicalOpV2 | {type: string}}
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeRawOp(rawOp) {
|
|
34
|
+
if (!rawOp || typeof rawOp !== 'object' || typeof rawOp.type !== 'string') {
|
|
35
|
+
return rawOp;
|
|
36
|
+
}
|
|
37
|
+
if (rawOp.type !== 'PropSet') {
|
|
38
|
+
return rawOp;
|
|
39
|
+
}
|
|
40
|
+
const op = /** @type {import('../types/WarpTypesV2.js').OpV2PropSet} */ (rawOp);
|
|
41
|
+
if (isLegacyEdgePropNode(op.node)) {
|
|
42
|
+
const { from, to, label } = decodeLegacyEdgePropNode(op.node);
|
|
43
|
+
return createEdgePropSetV2(from, to, label, op.key, op.value);
|
|
44
|
+
}
|
|
45
|
+
return createNodePropSetV2(op.node, op.key, op.value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lowers a single canonical op back to raw (persisted) form.
|
|
50
|
+
*
|
|
51
|
+
* - Canonical `NodePropSet` → raw `PropSet`
|
|
52
|
+
* - Canonical `EdgePropSet` → raw `PropSet` with legacy \x01-prefixed node
|
|
53
|
+
* - All other op types pass through unchanged.
|
|
54
|
+
*
|
|
55
|
+
* In M13, this always produces legacy raw PropSet for property ops.
|
|
56
|
+
* A future graph capability cutover (ADR 2) may allow emitting raw
|
|
57
|
+
* `EdgePropSet` directly.
|
|
58
|
+
*
|
|
59
|
+
* @param {import('../types/WarpTypesV2.js').CanonicalOpV2 | {type: string}} canonicalOp
|
|
60
|
+
* @returns {import('../types/WarpTypesV2.js').RawOpV2 | {type: string}}
|
|
61
|
+
*/
|
|
62
|
+
export function lowerCanonicalOp(canonicalOp) {
|
|
63
|
+
switch (canonicalOp.type) {
|
|
64
|
+
case 'NodePropSet': {
|
|
65
|
+
const op = /** @type {import('../types/WarpTypesV2.js').OpV2NodePropSet} */ (canonicalOp);
|
|
66
|
+
return createPropSetV2(op.node, op.key, op.value);
|
|
67
|
+
}
|
|
68
|
+
case 'EdgePropSet': {
|
|
69
|
+
const op = /** @type {import('../types/WarpTypesV2.js').OpV2EdgePropSet} */ (canonicalOp);
|
|
70
|
+
return createPropSetV2(
|
|
71
|
+
encodeLegacyEdgePropNode(op.from, op.to, op.label),
|
|
72
|
+
op.key,
|
|
73
|
+
op.value,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
return canonicalOp;
|
|
78
|
+
}
|
|
79
|
+
}
|