@ibgib/core-gib 0.1.42 → 0.1.44
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/dist/keystone/kdf/kdf-constants.d.mts +25 -0
- package/dist/keystone/kdf/kdf-constants.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-constants.mjs +28 -0
- package/dist/keystone/kdf/kdf-constants.mjs.map +1 -0
- package/dist/keystone/kdf/kdf-helpers.d.mts +45 -0
- package/dist/keystone/kdf/kdf-helpers.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-helpers.mjs +94 -0
- package/dist/keystone/kdf/kdf-helpers.mjs.map +1 -0
- package/dist/keystone/kdf/kdf-types.d.mts +49 -0
- package/dist/keystone/kdf/kdf-types.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-types.mjs +2 -0
- package/dist/keystone/kdf/kdf-types.mjs.map +1 -0
- package/dist/keystone/keystone-config-builder.d.mts +65 -12
- package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
- package/dist/keystone/keystone-config-builder.mjs +138 -46
- package/dist/keystone/keystone-config-builder.mjs.map +1 -1
- package/dist/keystone/keystone-config-builder.respec.mjs +21 -13
- package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -1
- package/dist/keystone/keystone-constants.d.mts +15 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +16 -0
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +4 -4
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +8 -5
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +1 -1
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +6 -5
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +72 -45
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +28 -18
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- package/dist/keystone/keystone-types.mjs +26 -15
- package/dist/keystone/keystone-types.mjs.map +1 -1
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts.map +1 -1
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs +7 -10
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.mjs +26 -26
- package/dist/sync/sync-conflict-text-merge.respec.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +9 -0
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +10 -0
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts +7 -0
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts.map +1 -0
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +282 -0
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.d.mts +22 -11
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +120 -27
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -7
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +11 -0
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/test/mock-space.d.mts +1 -38
- package/dist/test/mock-space.d.mts.map +1 -1
- package/dist/test/mock-space.mjs +73 -78
- package/dist/test/mock-space.mjs.map +1 -1
- package/package.json +1 -1
- package/src/keystone/README.md +119 -0
- package/src/keystone/docs/architecture.md +32 -1
- package/src/keystone/kdf/kdf-constants.mts +34 -0
- package/src/keystone/kdf/kdf-helpers.mts +105 -0
- package/src/keystone/kdf/kdf-types.mts +58 -0
- package/src/keystone/keystone-config-builder.mts +170 -47
- package/src/keystone/keystone-config-builder.respec.mts +21 -14
- package/src/keystone/keystone-constants.mts +21 -2
- package/src/keystone/keystone-helpers.mts +19 -14
- package/src/keystone/keystone-service-v1.mts +23 -22
- package/src/keystone/keystone-service-v1.respec.mts +71 -44
- package/src/keystone/keystone-types.mts +37 -23
- package/src/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mts +9 -13
- package/src/sync/README.md +122 -5
- package/src/sync/docs/architecture.md +2 -2
- package/src/sync/{SYNC_TESTING.md → docs/testing.md} +113 -28
- package/src/sync/sync-conflict-text-merge.respec.mts +25 -25
- package/src/sync/sync-constants.mts +12 -0
- package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +349 -0
- package/src/sync/sync-saga-coordinator.mts +158 -33
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +1 -7
- package/src/sync/sync-types.mts +12 -0
- package/src/test/mock-space.mts +72 -72
- package/tmp.md +0 -274
- package/src/sync/docs/verification.md +0 -43
package/src/sync/README.md
CHANGED
|
@@ -39,15 +39,132 @@ Init -> Ack -> Delta(s) -> Conflict/Commit via "ping pong" exchange.
|
|
|
39
39
|
* For local-only syncs, the `SyncPeer_V1` implementation is responsible for moving verified `ibGibs` from the temporary saga space to the durable/persistent domain space.
|
|
40
40
|
* From each endpoint's POV, the cleanup should remove the temp spaces used, in addition to any other required transaction cleanup (like closing any connections).
|
|
41
41
|
|
|
42
|
-
###
|
|
42
|
+
### Session Identity (`useSessionIdentity`)
|
|
43
|
+
|
|
43
44
|
The sync protocol can optionally generate an ephemeral Keystone Identity for the duration of the saga.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
**Purpose**: To allow secure, signed exchanges even if the Node doesn't have a long-lived identity, or to isolate the sync session security from master identity.
|
|
47
|
+
|
|
48
|
+
**Current Status**:
|
|
49
|
+
* ✅ Session keystone creation implemented ([getSessionIdentity()](file:./sync-saga-coordinator.mts#L328-L362))
|
|
50
|
+
* ✅ Keystone passed via Init message ([initData.identity](file:./sync-saga-message/sync-saga-message-types.mts#L75))
|
|
51
|
+
* ⚠️ **Signature generation/verification: NOT yet implemented**
|
|
52
|
+
* ⚠️ **Per-frame proof creation: NOT yet implemented**
|
|
53
|
+
|
|
54
|
+
**Usage**:
|
|
55
|
+
```typescript
|
|
56
|
+
const { done } = await coordinator.sync({
|
|
57
|
+
peer,
|
|
58
|
+
domainIbGibs: [timeline],
|
|
59
|
+
localSpace,
|
|
60
|
+
metaspace,
|
|
61
|
+
useSessionIdentity: true, // Default, but currently non-functional
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> [!CAUTION]
|
|
66
|
+
> **🚧 Session identity is NOT working yet.** Tests explicitly set `useSessionIdentity: false` to avoid failures. See [sync-innerspace-dest-ahead-withid.respec.mts](file:./sync-innerspace-dest-ahead-withid.respec.mts) for the failing test case.
|
|
67
|
+
|
|
68
|
+
**Authentication/Authorization Hooks**:
|
|
69
|
+
|
|
70
|
+
Validation occurs in [SyncPeer_V1.witness()](file:./sync-peer/sync-peer-v1.mts#L129-L219):
|
|
71
|
+
1. `validateContextAndSagaFrame()` - Intrinsic validation
|
|
72
|
+
2. `authenticateContext()` - Verify identity (stub)
|
|
73
|
+
3. `authorizeContext()` - Check permissions (stub)
|
|
74
|
+
|
|
75
|
+
### Conflict Resolution via Grafting
|
|
76
|
+
|
|
77
|
+
> [!IMPORTANT]
|
|
78
|
+
> **🔀 When timelines diverge**, the sync protocol uses **grafting** to merge branches.
|
|
79
|
+
|
|
80
|
+
**Mechanism**:
|
|
81
|
+
1. **Detect divergence** in Ack stage (tips differ, neither recognizes other's tip)
|
|
82
|
+
2. **Exchange full timeline history** via `SyncSagaConflictInfo.receiverKnowledgeTimelineAddrs`
|
|
83
|
+
3. **Find Latest Common Ancestor (LCA)**
|
|
84
|
+
4. **Replay DNA transforms** from both branches
|
|
85
|
+
5. **Create graft timeline** with merged result
|
|
86
|
+
6. **Track via** `GraftInfoIbGib_V1` ([graft-info-types.mts](file:./graft-info/graft-info-types.mts))
|
|
87
|
+
|
|
88
|
+
**Conflict Info Tracking**:
|
|
89
|
+
* `graftInfoAddr`: Points to graft metadata
|
|
90
|
+
* `createdDnaAddrs`: DNA ibgibs created during merge
|
|
91
|
+
* `replayedDnaAddrs`: Original DNAs replayed
|
|
92
|
+
* `createdDomainAddrs`: Final timeline frames (in order)
|
|
93
|
+
|
|
94
|
+
See [graft-info-helpers.mts](file:./graft-info/graft-info-helpers.mts) for implementation.
|
|
95
|
+
|
|
96
|
+
### Transaction Model (Temp Spaces)
|
|
97
|
+
|
|
98
|
+
> [!NOTE]
|
|
99
|
+
> **💾 Sync uses a two-phase commit pattern** with temporary spaces for atomic operations.
|
|
100
|
+
|
|
101
|
+
**Phases**:
|
|
102
|
+
1. **Accumulation**: Domain ibgibs received during sync go to `tempSpace`
|
|
103
|
+
2. **Commit**: On successful saga completion, verified ibgibs move to durable `localSpace`
|
|
104
|
+
|
|
105
|
+
**Rollback**: If saga fails, temp space cleanup discards unverified data.
|
|
106
|
+
|
|
107
|
+
**Lifecycle**:
|
|
108
|
+
* **Created**: `tempSpace = await metaspace.createNewLocalSpace()` at saga start
|
|
109
|
+
* **Destroyed**: Cleanup function runs in `sync()` finally block
|
|
110
|
+
|
|
111
|
+
**Why**: Ensures atomic sync - either all data validates and commits, or none does.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## 🔒 Security Considerations
|
|
115
|
+
|
|
116
|
+
> [!WARNING]
|
|
117
|
+
> Session identity is **currently non-functional**. The mitigations below describe the intended security model.
|
|
118
|
+
|
|
119
|
+
### Attack Vectors & Mitigations
|
|
120
|
+
|
|
121
|
+
| Attack Vector | Description | Mitigation |
|
|
122
|
+
|---------------|-------------|------------|
|
|
123
|
+
| **MITM Replay** | Attacker replays captured saga frames | • Frame signatures include timestamp<br>• Freshness checks reject old frames<br>• Challenge depletion prevents reuse<br>• sagaId deduplication |
|
|
124
|
+
| **Session Fixation** | Attacker pre-generates session keystone | • Session secret derived via KDF (strategy in keystone metadata)<br>• `sessionSecret = KDF(masterSecret, sagaId, strategy)` |
|
|
125
|
+
| **Identity Spoofing** | Impersonation via fake session keystone | • Session keystone includes `derivedFrom` master address<br>• Receiver validates master keystone is known/trusted |
|
|
126
|
+
| **Challenge Grinding** | Brute-force challenge solutions for target binding | • Hash pre-imaging computationally infeasible<br>• Stochastic selection adds randomness |
|
|
127
|
+
| **DoS (Pool Exhaustion)** | Excessive syncs deplete challenge pools | • Top-up replenishment refills challenges<br>• Rate limiting per identity<br>• Pool separation for critical ops |
|
|
128
|
+
| **Saga Hijacking** | Mid-flight frame injection | • All frames signed with session keystone<br>• `past` rel8n creates tamper-evident chain |
|
|
129
|
+
|
|
130
|
+
### Cryptographic Primitives
|
|
131
|
+
|
|
132
|
+
* **Hash Function**: SHA-256 (content addressing, challenge generation)
|
|
133
|
+
* **Proof System**: Hash-reveal protocol (`hash-reveal-v1`)
|
|
134
|
+
* **Binding**: Target address → hex bucket → challenge selection
|
|
135
|
+
* **Entropy**: Stochastic challenge selection (anti-grinding)
|
|
136
|
+
|
|
137
|
+
### Trust Model
|
|
138
|
+
|
|
139
|
+
**Session Keystones**:
|
|
140
|
+
- Ephemeral identity per sync saga
|
|
141
|
+
- Derived from master keystone + `sagaId`
|
|
142
|
+
- Validates via proof-of-work (solving challenges)
|
|
143
|
+
- Post-hoc audit trail: master can prove session authorship
|
|
144
|
+
|
|
145
|
+
**Propagation**:
|
|
146
|
+
- Sync does NOT rely on global PKI or certificate authorities
|
|
147
|
+
- Trust propagates through sync sessions (Alice syncs with Bob → Bob witnesses Alice's keystone)
|
|
148
|
+
- Revocation propagates same way (Alice revokes → syncs revocation to Bob → Bob sees revoked timeline)
|
|
149
|
+
|
|
150
|
+
**Threat Boundaries**:
|
|
151
|
+
- ✅ Protects against: MITM replay, impersonation, frame tampering
|
|
152
|
+
- ⚠️ Does NOT protect against: Compromised endpoint (live memory access during active session)
|
|
153
|
+
- ⚠️ Master secrets: Raw secrets should NOT stored; keystones master secrets to keystones should be derived secrets via key stretching passwords. raw secret files of course can drive these passwords and must be protected
|
|
154
|
+
- ⚠️ Transport security: Use TLS for network layer encryption (ibgib protocol provides authentication/integrity, not confidentiality)
|
|
155
|
+
|
|
156
|
+
### Best Practices
|
|
157
|
+
|
|
158
|
+
1. **Master Secret Protection**: Store master secrets in secure enclaves/keychains
|
|
159
|
+
2. **Session Lifetime**: Limit saga duration, revoke sessions post-completion
|
|
160
|
+
3. **Rate Limiting**: Implement per-identity sync rate limits
|
|
161
|
+
4. **Freshness**: Reject frames older than 60 seconds
|
|
162
|
+
5. **Audit Logs**: Persist saga timelines for post-hoc validation (saga timelines are ibgibs, so integrity is built-in via content addressing)
|
|
163
|
+
6. **TLS**: Always use TLS for network transport (defense-in-depth)
|
|
47
164
|
|
|
48
165
|
## 📚 Documentation
|
|
49
166
|
* **[Architecture](./docs/architecture.md)**: Detailed design of the Saga Protocol, State Machine, and Smart Diff logic.
|
|
50
|
-
* **[Verification](./docs/
|
|
167
|
+
* **[Testing & Verification](./docs/testing.md)**: Test strategy, verification matrix, and comprehensive testing guidelines.
|
|
51
168
|
|
|
52
169
|
## Features
|
|
53
170
|
* **Symmetric Design**: No distinct "Client" or "Server" logic.
|
|
@@ -42,7 +42,7 @@ The `SyncSagaCoordinator` drives a Finite State Machine (FSM) via the `handleSag
|
|
|
42
42
|
|
|
43
43
|
4. **Convergence (`handleDeltaFrame`)**:
|
|
44
44
|
* Bob receives `Delta`.
|
|
45
|
-
* **Verifies**: Checks hashes (`ib` vs `gib`)
|
|
45
|
+
* **Verifies**: Checks content-address hashes (`ib` vs `gib`). ⚠️ Keystone signature verification pending.
|
|
46
46
|
* **Merges**: Puts data into his local space.
|
|
47
47
|
* Bob sends `Commit` frame.
|
|
48
48
|
|
|
@@ -65,5 +65,5 @@ The protocol supports two types of data:
|
|
|
65
65
|
* **Stones (Constants)**: Immutable data without a history (e.g., Code lists, Configs). Syncing involves simple existence checks.
|
|
66
66
|
|
|
67
67
|
## 4. Security & Identity
|
|
68
|
-
* **Session Keystone**: Each Saga
|
|
68
|
+
* **Session Keystone**: Each Saga can optionally generate an ephemeral identity to sign frames, linked to a master identity. ⚠️ **Currently non-functional** - see [README Session Identity section](file:../README.md#session-identity-usesessionidentity) for status.
|
|
69
69
|
* **Trust Chain**: Each frame points to the previous one (`past` rel8n), creating a hash-linked chain of custody.
|
|
@@ -1,38 +1,116 @@
|
|
|
1
|
-
# Sync Testing
|
|
1
|
+
# Sync Protocol Testing & Verification
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document outlines the testing strategy, verification results, and comprehensive guidelines for testing the Symmetric Sync Protocol.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
exponentially. I mean you won't believe just how vastly, hugely, mind-bogglingly
|
|
7
|
-
fast...
|
|
5
|
+
💣💥 **USE THESE GUIDELINES TO MITIGATE COMPLEXITY EXPLOSION** 💣💥
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Sync needs thorough testing and complexity increases exponentially. Ridiculously exponentially. I mean you won't believe just how vastly, hugely, mind-bogglingly fast...
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Testing Strategy
|
|
12
|
+
|
|
13
|
+
We use `respec-gib` (a BDD-style framework) with **In-Memory Simulation (`InnerSpace`)** to verify logic. This allows us to test complex graph scenarios without network or disk I/O overhead.
|
|
14
|
+
|
|
15
|
+
### Key Test Files
|
|
16
|
+
|
|
17
|
+
To see the most up-to-date test files for sync, search inside `libs/core-gib/src/sync` for `*.respec.mts` files.
|
|
18
|
+
|
|
19
|
+
**Basic Scenarios**:
|
|
20
|
+
* `sync-innerspace.respec.mts`: Basic Push/Pull scenarios
|
|
21
|
+
* `sync-innerspace-dest-ahead.respec.mts`: Receiver has newer data scenarios
|
|
22
|
+
* `sync-innerspace-multiple-timelines.respec.mts`: Multiple independent timelines in one saga
|
|
23
|
+
* `sync-innerspace-partial-update.respec.mts`: Smart diff verification (delta-only transfers)
|
|
24
|
+
* `sync-innerspace-deep-updates.respec.mts`: Timelines with extensive history (past link traversal)
|
|
25
|
+
* `sync-innerspace-constants.respec.mts`: Synchronization of stones (constants/non-TJPs)
|
|
26
|
+
|
|
27
|
+
**Conflict Resolution**:
|
|
28
|
+
* `sync-conflict-basic-divergence.respec.mts`: Single timeline divergence and merge
|
|
29
|
+
* `sync-conflict-basic-multitimelines.respec.mts`: Multiple timelines with conflicts
|
|
30
|
+
* `sync-conflict-adv-multitimelines.respec.mts`: Advanced multi-timeline, multi-round sync
|
|
31
|
+
* `sync-conflict-text-merge.respec.mts`: LCS-based text merging for `data.text` fields
|
|
32
|
+
|
|
33
|
+
**Identity & Security**:
|
|
34
|
+
* `sync-innerspace-dest-ahead-withid.respec.mts`: Session identity test (🚧 **currently failing**)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Verification Matrix
|
|
39
|
+
|
|
40
|
+
### 1. Basic Scenarios
|
|
41
|
+
| Feature | Description | Status | Test File |
|
|
42
|
+
| :--- | :--- | :--- | :--- |
|
|
43
|
+
| **Push** | Source syncs a new timeline to an empty Dest. | ✅ Verified | `sync-innerspace.respec.mts` |
|
|
44
|
+
| **Pull** | Dest requests data from Source (Reverse Sync). | ⏳ Pending | `sync-innerspace.respec.mts` |
|
|
45
|
+
| **Dest Ahead** | Dest has newer data; Source receives update. | ✅ Verified | `sync-innerspace-dest-ahead.respec.mts` |
|
|
46
|
+
|
|
47
|
+
### 2. Complex Graph Scenarios
|
|
48
|
+
| Feature | Description | Status | Test File |
|
|
49
|
+
| :--- | :--- | :--- | :--- |
|
|
50
|
+
| **Multi-Timeline** | Syncing multiple separate timelines. | ✅ Verified | `sync-innerspace-multiple-timelines.respec.mts` |
|
|
51
|
+
| **Deep Updates** | Syncing deep dependency chains (Ancestors). | ✅ Verified | `sync-innerspace-deep-updates.respec.mts` |
|
|
52
|
+
| **Smart Diff** | Sender skips data Receiver already has. | ✅ Verified | `sync-innerspace-partial-update.respec.mts` |
|
|
53
|
+
| **Constants** | Syncing immutable stones (no TJP). | ✅ Verified | `sync-innerspace-constants.respec.mts` |
|
|
54
|
+
|
|
55
|
+
### 3. Conflict Resolution & Edge Cases
|
|
56
|
+
| Feature | Description | Status | Test File |
|
|
57
|
+
| :--- | :--- | :--- | :--- |
|
|
58
|
+
| **Hash Validation** | Verifying integrity checks (ib/gib). | ✅ Verified | Built-in to all tests |
|
|
59
|
+
| **Basic Conflicts** | Divergent branches (single timeline). | ✅ Verified | `sync-conflict-basic-divergence.respec.mts` |
|
|
60
|
+
| **Multi-Timeline Conflicts** | Multiple timelines diverging. | ✅ Verified | `sync-conflict-basic-multitimelines.respec.mts` |
|
|
61
|
+
| **Advanced Conflicts** | Complex multi-round scenarios. | ✅ Verified | `sync-conflict-adv-multitimelines.respec.mts` |
|
|
62
|
+
| **Text Merge Conflicts** | LCS-based text merging. | ✅ Verified | `sync-conflict-text-merge.respec.mts` |
|
|
63
|
+
|
|
64
|
+
### 4. Security & Identity
|
|
65
|
+
| Feature | Description | Status | Test File |
|
|
66
|
+
| :--- | :--- | :--- | :--- |
|
|
67
|
+
| **Session Keystones** | Ephemeral identity for sync sessions. | 🚧 **Non-functional** | `sync-innerspace-dest-ahead-withid.respec.mts` |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Recent Verifications
|
|
72
|
+
|
|
73
|
+
### Sync Constants (2026-01-08)
|
|
74
|
+
* **Goal**: Ensure constants (ibGibs without `tjp` or `past`) are synced.
|
|
75
|
+
* **Result**: ✅ Passed.
|
|
76
|
+
* **Fixes**: Updated `handleInitFrame` to request missing stones; updated `handleAckFrame` to include "tip" stones in payload even without dependencies.
|
|
77
|
+
|
|
78
|
+
### Text Merge Conflicts (2026-01-14)
|
|
79
|
+
* **Goal**: Verify LCS-based text merging for divergent `data.text` fields.
|
|
80
|
+
* **Result**: ✅ Passed.
|
|
81
|
+
* **Implementation**: Uses Longest Common Subsequence algorithm for three-way merge.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Testing Guidelines
|
|
86
|
+
|
|
87
|
+
### Core Concepts
|
|
88
|
+
|
|
89
|
+
#### Rounds
|
|
12
90
|
|
|
13
91
|
Tests are organized into **rounds**. Each round represents a complete cycle of:
|
|
14
92
|
1. **State Setup**: Establish expected initial conditions
|
|
15
93
|
2. **Actions**: Perform mutations, relations, and sync operations
|
|
16
94
|
3. **Verification**: Assert final state expectations
|
|
17
95
|
|
|
18
|
-
The **first round**
|
|
19
|
-
subsequent rounds perform one or more op(s) and sync(s).
|
|
96
|
+
The **first round** establishes common history between source and dest, and subsequent rounds perform one or more op(s) and sync(s).
|
|
20
97
|
|
|
21
|
-
|
|
98
|
+
#### Broad Test Scenarios
|
|
22
99
|
|
|
23
100
|
Here are the broad category of ops that can occur to timelines WRT syncing:
|
|
24
101
|
* **Fast-Forward (Source Ahead)**: Source has newer versions that dest lacks
|
|
25
102
|
* **Fast-Forward (Dest Ahead)**: Dest has newer versions that source lacks
|
|
26
103
|
* **Conflict**: Both source and dest have divergent edits from a common ancestor
|
|
27
104
|
* **No Change**: Timeline exists on both sides with no edits
|
|
28
|
-
* **Stones**: Stones are treated slightly differently in the sync engine right
|
|
29
|
-
|
|
105
|
+
* **Stones**: Stones are treated slightly differently in the sync engine right now, so these must also be tested in various permutations.
|
|
106
|
+
|
|
107
|
+
---
|
|
30
108
|
|
|
31
109
|
## Variable Naming Convention
|
|
32
110
|
|
|
33
111
|
Variable names are a COMBINATION of BOTH snake_case AND camelCase:
|
|
34
112
|
|
|
35
|
-
Format
|
|
113
|
+
**Format**: `round_timeline_version_location_commentsHereInCamelCase_possiblyOtherComments`
|
|
36
114
|
|
|
37
115
|
### Components
|
|
38
116
|
|
|
@@ -54,6 +132,8 @@ r1_delta_v1_source_createdInR1_rel8edFromAlpha // Round 1, delta v1 with multip
|
|
|
54
132
|
r2_alpha_v4_dest_postConflict // Round 2, alpha v4 on dest, after conflict resolution
|
|
55
133
|
```
|
|
56
134
|
|
|
135
|
+
---
|
|
136
|
+
|
|
57
137
|
## Test Structure
|
|
58
138
|
|
|
59
139
|
### File Organization
|
|
@@ -144,8 +224,7 @@ Pattern examples:
|
|
|
144
224
|
|
|
145
225
|
* **`respecfully`**: Outer verification block per round with text "r{N} verify pre/post"
|
|
146
226
|
* **`ifWeMight`**: Per-timeline verification within a round
|
|
147
|
-
* NOTE: Graphs should be used for verification of sync, not individual addrs/ibgibs
|
|
148
|
-
in most (all?) cases
|
|
227
|
+
* NOTE: Graphs should be used for verification of sync, not individual addrs/ibgibs in most (all?) cases
|
|
149
228
|
* **`iReckon`**: Individual assertions
|
|
150
229
|
|
|
151
230
|
Example:
|
|
@@ -163,15 +242,15 @@ await respecfully(sir, `r1 verify results`, async () => {
|
|
|
163
242
|
});
|
|
164
243
|
```
|
|
165
244
|
|
|
245
|
+
---
|
|
246
|
+
|
|
166
247
|
## Multiple Syncs Per Round
|
|
167
248
|
|
|
168
249
|
A single round can have **multiple sync calls**. This is useful when:
|
|
169
250
|
* Creating multiple timeline pairs (some on source, some on dest)
|
|
170
251
|
* Testing specific sync scenarios in isolation
|
|
171
252
|
|
|
172
|
-
Key principle: Verify **final state** at end of round, not intermediate states
|
|
173
|
-
between syncs. We don't want to get bogged down trying to test every detail
|
|
174
|
-
after every sync. The broad coverage should handle this.
|
|
253
|
+
Key principle: Verify **final state** at end of round, not intermediate states between syncs. We don't want to get bogged down trying to test every detail after every sync. The broad coverage should handle this.
|
|
175
254
|
|
|
176
255
|
```typescript
|
|
177
256
|
// Round 1: Establish common history
|
|
@@ -187,6 +266,8 @@ await respecfully(sir, `r1 verify all timelines synced`, async () => {
|
|
|
187
266
|
});
|
|
188
267
|
```
|
|
189
268
|
|
|
269
|
+
---
|
|
270
|
+
|
|
190
271
|
## Domain-Level Focus
|
|
191
272
|
|
|
192
273
|
Advanced tests focus on **domain-level correctness**, not protocol mechanics:
|
|
@@ -195,6 +276,8 @@ Advanced tests focus on **domain-level correctness**, not protocol mechanics:
|
|
|
195
276
|
❌ **Don't use**: `delay()` calls to control base/orphan selection
|
|
196
277
|
✅ **Do focus on**: Data field correctness, relation preservation, dependency completeness
|
|
197
278
|
|
|
279
|
+
---
|
|
280
|
+
|
|
198
281
|
## Complexity Management
|
|
199
282
|
|
|
200
283
|
### Principles
|
|
@@ -207,14 +290,9 @@ Advanced tests focus on **domain-level correctness**, not protocol mechanics:
|
|
|
207
290
|
|
|
208
291
|
### Test Transformer
|
|
209
292
|
|
|
210
|
-
Tests are making relatively arbitrary fork/mut8/rel8 calls. To help reduce
|
|
211
|
-
implementation detail noise, the `TestTransformer` helper class was created.
|
|
212
|
-
|
|
213
|
-
These methods return a `TestStepInfo` which itself has references to the `ibGib`
|
|
214
|
-
and `addr`, as well as other metadata. So the variable
|
|
215
|
-
`r2_alpha_v2_source_arbitraryMut8` does NOT reference an ibgib, rather, it
|
|
216
|
-
references one of these test step info objects.
|
|
293
|
+
Tests are making relatively arbitrary fork/mut8/rel8 calls. To help reduce implementation detail noise, the `TestTransformer` helper class was created.
|
|
217
294
|
|
|
295
|
+
These methods return a `TestStepInfo` which itself has references to the `ibGib` and `addr`, as well as other metadata. So the variable `r2_alpha_v2_source_arbitraryMut8` does NOT reference an ibgib, rather, it references one of these test step info objects.
|
|
218
296
|
|
|
219
297
|
```typescript
|
|
220
298
|
const r2_beta_v0_source = await testTransformer.create({
|
|
@@ -233,6 +311,8 @@ const r2_alpha_v2_source_arbitraryMut8 = await testTransformer.mut8({
|
|
|
233
311
|
|
|
234
312
|
See `test-helpers.mts` and `test-types.mts` for implementations.
|
|
235
313
|
|
|
314
|
+
---
|
|
315
|
+
|
|
236
316
|
## Scenarios to Test
|
|
237
317
|
|
|
238
318
|
### Basic Scenarios
|
|
@@ -248,9 +328,14 @@ See `test-helpers.mts` and `test-types.mts` for implementations.
|
|
|
248
328
|
* **Reverse dependencies**: Timeline created on dest, source adds rel8n to it
|
|
249
329
|
* **Removal conflicts**: Source removes rel8n while dest mut8s the rel8'd ibgib
|
|
250
330
|
* **Chain edits**: Mutation → rel8 → mutation again on same timeline in one round
|
|
251
|
-
* **Text edits**: Edit ibGib.data.text
|
|
252
|
-
|
|
253
|
-
|
|
331
|
+
* **Text edits**: Edit `ibGib.data.text`. If divergent, uses LCS-based merge. See `sync-conflict-text-merge.respec.mts`.
|
|
332
|
+
|
|
333
|
+
### Security Scenarios
|
|
334
|
+
* **Session identity creation**: Verify ephemeral keystone generation (🚧 pending implementation)
|
|
335
|
+
* **Signature verification**: (🚧 pending implementation)
|
|
336
|
+
* **Authorization checks**: Verify identity-based permissions (🚧 pending implementation)
|
|
337
|
+
|
|
338
|
+
---
|
|
254
339
|
|
|
255
340
|
## Summary
|
|
256
341
|
|
|
@@ -148,7 +148,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
148
148
|
});
|
|
149
149
|
const r1_alpha_source_tipAddr = r1_alpha_sourceKV[alpha_tjpAddr];
|
|
150
150
|
if (!r1_alpha_source_tipAddr) {
|
|
151
|
-
|
|
151
|
+
ifWe(sir, 'r1_alpha_source_tipAddr is falsy?', async () => {
|
|
152
152
|
iReckon(sir, true).asTo('fail').isGonnaBeFalse();
|
|
153
153
|
});
|
|
154
154
|
return; /* <<<< returns early */
|
|
@@ -161,17 +161,17 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
161
161
|
});
|
|
162
162
|
const r1_alpha_dest_tipAddr = r1_alpha_destKV[alpha_tjpAddr];
|
|
163
163
|
if (!r1_alpha_dest_tipAddr) {
|
|
164
|
-
|
|
164
|
+
ifWe(sir, 'r1_alpha_dest_tipAddr is falsy?', async () => {
|
|
165
165
|
iReckon(sir, true).asTo('fail').isGonnaBeFalse();
|
|
166
166
|
});
|
|
167
167
|
return; /* <<<< returns early */
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
await
|
|
170
|
+
await ifWe(sir, 'r1 tip addrs match', async () => {
|
|
171
171
|
iReckon(sir, r1_alpha_source_tipAddr).asTo('R1 source/dest have same tip').isGonnaBe(r1_alpha_dest_tipAddr);
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
await
|
|
174
|
+
await ifWe(sir, 'r1 text synced correctly', async () => {
|
|
175
175
|
if (!r1_alpha_dest_tipAddr) {
|
|
176
176
|
throw new Error(`r1_dest_tipAddr is null/undefined (E: 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d)`);
|
|
177
177
|
}
|
|
@@ -181,7 +181,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
181
181
|
iReckon(sir, destTipIbGib.data!.text).asTo('Dest has initial text').isGonnaBe(INITIAL_TEXT);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
await
|
|
184
|
+
await ifWe(sir, 'r1 dep graphs synced', async () => {
|
|
185
185
|
const [r1_alpha_source_tip] = await getIbGibsFromCache_fallbackToSpaces({
|
|
186
186
|
addrs: [r1_alpha_source_tipAddr],
|
|
187
187
|
space: sourceSpace,
|
|
@@ -260,7 +260,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
260
260
|
// #endregion r2 dest edits
|
|
261
261
|
|
|
262
262
|
await respecfully(sir, `r2 verify pre`, async () => {
|
|
263
|
-
await
|
|
263
|
+
await ifWe(sir, 'texts as expected', async () => {
|
|
264
264
|
// before the sync, each side only has their edit. after the sync,
|
|
265
265
|
// both sides should have both prepended and appended text
|
|
266
266
|
iReckon(sir, r2_alpha_v1_source_appendedText.ibGib.data?.text.includes(INITIAL_TEXT)).asTo('alpha source has initial text').isGonnaBeTrue();
|
|
@@ -296,14 +296,14 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
296
296
|
const kv_dest = await senderCoordinator.getKnowledgeMap({ space: destSpace, metaspace, domainIbGibs: [r1_alpha_v0_source.ibGib] });
|
|
297
297
|
const r2_alpha_source_tipAddr = kv_source[alpha_tjpAddr];
|
|
298
298
|
if (!r2_alpha_source_tipAddr) {
|
|
299
|
-
await
|
|
299
|
+
await ifWe(sir, 'r2_alpha_source_tipAddr falsy?', async () => {
|
|
300
300
|
iReckon(sir, true).asTo('fails').isGonnaBe(false);
|
|
301
301
|
});
|
|
302
302
|
return; /* <<<< returns early */
|
|
303
303
|
}
|
|
304
304
|
const r2_alpha_dest_tipAddr = kv_dest[alpha_tjpAddr];
|
|
305
305
|
if (!r2_alpha_dest_tipAddr) {
|
|
306
|
-
await
|
|
306
|
+
await ifWe(sir, 'r2_alpha_dest_tipAddr falsy?', async () => {
|
|
307
307
|
iReckon(sir, true).asTo('fails').isGonnaBe(false);
|
|
308
308
|
});
|
|
309
309
|
return; /* <<<< returns early */
|
|
@@ -317,11 +317,11 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
317
317
|
space: sourceSpace,
|
|
318
318
|
});
|
|
319
319
|
|
|
320
|
-
await
|
|
320
|
+
await ifWe(sir, 'r2 tip addrs match', async () => {
|
|
321
321
|
iReckon(sir, r2_alpha_source_tipAddr).asTo('alpha').isGonnaBe(r2_alpha_dest_tipAddr);
|
|
322
322
|
});
|
|
323
323
|
|
|
324
|
-
await
|
|
324
|
+
await ifWe(sir, 'r2 text merged correctly', async () => {
|
|
325
325
|
// before the sync, each side only has their edit. after the sync,
|
|
326
326
|
// both sides should have both prepended and appended text
|
|
327
327
|
iReckon(sir, r2_alpha_v1_source_appendedText.ibGib.data?.text.includes(INITIAL_TEXT)).asTo('alpha source has initial text').isGonnaBeTrue();
|
|
@@ -340,7 +340,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
340
340
|
iReckon(sir, text).asTo('R2 has both prepend and append').isGonnaBe(DEST_PREPEND + INITIAL_TEXT + SOURCE_APPEND);
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
-
await
|
|
343
|
+
await ifWe(sir, 'r2 dep graphs synced', async () => {
|
|
344
344
|
// alpha's full dep graph should exist on dest
|
|
345
345
|
const depGraph_alpha_source = await getDependencyGraph({
|
|
346
346
|
ibGib: r2_alpha_source_tip,
|
|
@@ -414,7 +414,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
414
414
|
// // #endregion r3 dest edits
|
|
415
415
|
|
|
416
416
|
// await respecfully(sir, `r3 verify pre`, async () => {
|
|
417
|
-
// await
|
|
417
|
+
// await ifWe(sir, 'dest has alpha after R2 graft', async () => {
|
|
418
418
|
// const kv = await receiverCoordinator.getKnowledgeMap({ space: destSpace, metaspace, domainIbGibs: [r3_v0_graft_fromDest] });
|
|
419
419
|
// iReckon(sir, !!kv[alpha_tjpAddr]).asTo('dest has tip').isGonnaBeTrue();
|
|
420
420
|
// });
|
|
@@ -445,11 +445,11 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
445
445
|
// const r3_tip_s = kv_s[alpha_tjpAddr];
|
|
446
446
|
// const r3_tip_d = kv_d[alpha_tjpAddr];
|
|
447
447
|
|
|
448
|
-
// await
|
|
448
|
+
// await ifWe(sir, 'r3 tip addrs match', async () => {
|
|
449
449
|
// iReckon(sir, r3_tip_s).asTo('R3 tips match').isGonnaBe(r3_tip_d);
|
|
450
450
|
// });
|
|
451
451
|
|
|
452
|
-
// await
|
|
452
|
+
// await ifWe(sir, 'r3 both paragraph edits merged', async () => {
|
|
453
453
|
// const res = await getFromSpace({ space: sourceSpace, addr: r3_tip_s! });
|
|
454
454
|
// const tip = res.ibGibs![0] as IbGib_V1<TestData>;
|
|
455
455
|
// const mergedText = tip.data!.text!;
|
|
@@ -468,7 +468,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
468
468
|
// .isGonnaBeTrue();
|
|
469
469
|
// });
|
|
470
470
|
|
|
471
|
-
// await
|
|
471
|
+
// await ifWe(sir, 'r3 dep graphs synced', async () => {
|
|
472
472
|
// const [d] = await getIbGibsFromCache_fallbackToSpaces({ addrs: [r3_tip_s!], space: destSpace });
|
|
473
473
|
// iReckon(sir, d).asTo('exists dest').isGonnaBeTruthy();
|
|
474
474
|
// });
|
|
@@ -502,7 +502,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
502
502
|
// // #endregion r4 dest edits
|
|
503
503
|
|
|
504
504
|
// await respecfully(sir, `r4 verify pre`, async () => {
|
|
505
|
-
// await
|
|
505
|
+
// await ifWe(sir, 'dest has alpha after R3 graft', async () => {
|
|
506
506
|
// // TODO: Verify pre-sync state
|
|
507
507
|
// });
|
|
508
508
|
// });
|
|
@@ -512,16 +512,16 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
512
512
|
// // TODO: Add sync operation
|
|
513
513
|
|
|
514
514
|
// await respecfully(sir, `r4 verify post`, async () => {
|
|
515
|
-
// await
|
|
515
|
+
// await ifWe(sir, 'r4 tip addrs match', async () => {
|
|
516
516
|
// // TODO: Verify tips match
|
|
517
517
|
// });
|
|
518
518
|
|
|
519
|
-
// await
|
|
519
|
+
// await ifWe(sir, 'r4 LCS merged both word changes', async () => {
|
|
520
520
|
// // TODO: Verify text has both source and dest word changes
|
|
521
521
|
// // TODO: Verify LCS algorithm preserved both edits correctly
|
|
522
522
|
// });
|
|
523
523
|
|
|
524
|
-
// await
|
|
524
|
+
// await ifWe(sir, 'r4 dep graphs synced', async () => {
|
|
525
525
|
// // TODO: Verify dep graphs
|
|
526
526
|
// });
|
|
527
527
|
// });
|
|
@@ -556,7 +556,7 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
556
556
|
// // #endregion r5 dest edits
|
|
557
557
|
|
|
558
558
|
// await respecfully(sir, `r5 verify pre`, async () => {
|
|
559
|
-
// await
|
|
559
|
+
// await ifWe(sir, 'dest has alpha after R4 graft', async () => {
|
|
560
560
|
// // TODO: Verify pre-sync state
|
|
561
561
|
// });
|
|
562
562
|
// });
|
|
@@ -566,24 +566,24 @@ await respecfully(sir, `Text merge (LCS) conflict resolution`, async () => {
|
|
|
566
566
|
// // TODO: Add sync operation
|
|
567
567
|
|
|
568
568
|
// await respecfully(sir, `r5 verify post`, async () => {
|
|
569
|
-
// await
|
|
569
|
+
// await ifWe(sir, 'r5 tip addrs match', async () => {
|
|
570
570
|
// // TODO: Verify tips match
|
|
571
571
|
// });
|
|
572
572
|
|
|
573
|
-
// await
|
|
573
|
+
// await ifWe(sir, 'r5 text field merged', async () => {
|
|
574
574
|
// // TODO: Verify text field has both source and dest changes
|
|
575
575
|
// });
|
|
576
576
|
|
|
577
|
-
// await
|
|
577
|
+
// await ifWe(sir, 'r5 description field merged', async () => {
|
|
578
578
|
// // TODO: Verify description field has both source and dest changes
|
|
579
579
|
// });
|
|
580
580
|
|
|
581
|
-
// await
|
|
581
|
+
// await ifWe(sir, 'r5 both fields independently LCS-merged', async () => {
|
|
582
582
|
// // TODO: Verify each field was merged independently
|
|
583
583
|
// // TODO: Ensure one field's merge didn't affect the other
|
|
584
584
|
// });
|
|
585
585
|
|
|
586
|
-
// await
|
|
586
|
+
// await ifWe(sir, 'r5 dep graphs synced', async () => {
|
|
587
587
|
// // TODO: Verify dep graphs
|
|
588
588
|
// });
|
|
589
589
|
// });
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
|
|
2
|
+
|
|
1
3
|
export const SYNC_ATOM = "sync";
|
|
2
4
|
|
|
3
5
|
export const SYNC_MSG_REL8N_NAME = "syncmsg";
|
|
@@ -75,3 +77,13 @@ export function isValidSyncConflictStrategy(strategy: string): strategy is SyncC
|
|
|
75
77
|
return SYNC_CONFLICT_STRATEGY_VALID_VALUES.includes(strategy as SyncConflictStrategy);
|
|
76
78
|
}
|
|
77
79
|
// #endregion SyncConflictStrategy
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* When synchronizing, the plan for identity integration is to create a session
|
|
83
|
+
* keystone. This keystone will have a primary pool, driven by the sender's
|
|
84
|
+
* secret, and a secondary delegated pool for use by the receiver. Initially,
|
|
85
|
+
* this will have a known, weak "secret" and it is the job of the receiver to
|
|
86
|
+
* use this to then change the keystone to use a secret chosen by the
|
|
87
|
+
* receiver's end.
|
|
88
|
+
*/
|
|
89
|
+
export const DEFAULT_SESSION_IDENTITY_INITIAL_DELEGATE_SECRET = ROOT_ADDR;
|