@ibgib/core-gib 0.1.41 → 0.1.43
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/sync/sync-conflict-adv-multitimelines.respec.mjs +2 -2
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.d.mts +4 -1
- package/dist/sync/sync-conflict-text-merge.respec.d.mts.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.mjs +314 -181
- package/dist/sync/sync-conflict-text-merge.respec.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 +252 -0
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.mjs +3 -3
- package/dist/sync/sync-saga-coordinator.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 +118 -0
- package/src/keystone/docs/architecture.md +30 -1
- 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-adv-multitimelines.respec.mts +3 -3
- package/src/sync/sync-conflict-text-merge.respec.mts +326 -164
- package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +316 -0
- package/src/sync/sync-saga-coordinator.mts +4 -4
- package/src/test/mock-space.mts +72 -72
- package/src/sync/docs/verification.md +0 -43
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sync-innerspace-dest-ahead.respec
|
|
3
|
+
*
|
|
4
|
+
* Verifies Sync Scenario where the receiver is ahead, with identity enabled.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
respecfully, lastOfAll, ifWe, iReckon,
|
|
9
|
+
ifWeMight
|
|
10
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
11
|
+
const maam = `[${import.meta.url}]`, sir = maam;
|
|
12
|
+
import { clone, delay, extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
13
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
14
|
+
import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
15
|
+
|
|
16
|
+
import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
|
|
17
|
+
import { putInSpace, getFromSpace, registerNewIbGib } from '../witness/space/space-helper.mjs';
|
|
18
|
+
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
|
|
19
|
+
import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
|
|
20
|
+
import { createTimelineRootTestHelper, getTestKeystoneServiceHelper } from '../test-helpers.mjs';
|
|
21
|
+
import { mut8Timeline } from '../timeline/timeline-api.mjs';
|
|
22
|
+
import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
|
|
23
|
+
import { toDto } from '../common/other/ibgib-helper.mjs';
|
|
24
|
+
import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
|
|
25
|
+
import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
|
|
26
|
+
import { GetIbGibResult } from '../common/other/other-types.mjs';
|
|
27
|
+
import { IbGibSpaceAny } from '../witness/space/space-base-v1.mjs';
|
|
28
|
+
import { getDependencyGraph } from '../common/other/graph-helper.mjs';
|
|
29
|
+
|
|
30
|
+
const logalot = false;
|
|
31
|
+
const lc = `[sync-innerspace-dest-ahead.respec]`;
|
|
32
|
+
|
|
33
|
+
await respecfully(sir, `Sync InnerSpaces (Dest Ahead)`, async () => {
|
|
34
|
+
|
|
35
|
+
let metaspace: Metaspace_Innerspace;
|
|
36
|
+
let sourceSpace: InnerSpace_V1;
|
|
37
|
+
let destSpace: InnerSpace_V1;
|
|
38
|
+
|
|
39
|
+
interface TestData {
|
|
40
|
+
type: string;
|
|
41
|
+
label?: string;
|
|
42
|
+
uuid?: string;
|
|
43
|
+
n?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await respecfully(sir, `Dest Ahead (Remote Newer)`, async () => {
|
|
47
|
+
// 1. Setup Spaces
|
|
48
|
+
metaspace = new Metaspace_Innerspace(undefined);
|
|
49
|
+
await metaspace.initialize({
|
|
50
|
+
getFnAlert: () => async ({ title, msg }) => { },
|
|
51
|
+
getFnPrompt: () => async ({ title, msg }) => { return ''; },
|
|
52
|
+
getFnPromptPassword: () => async (title, msg) => { return null; },
|
|
53
|
+
});
|
|
54
|
+
while (!metaspace.initialized) { await delay(10); }
|
|
55
|
+
|
|
56
|
+
const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
|
|
57
|
+
await defaultLocalUserSpace!.initialized;
|
|
58
|
+
|
|
59
|
+
sourceSpace = new InnerSpace_V1({
|
|
60
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
61
|
+
name: 'source',
|
|
62
|
+
uuid: 'source_uuid',
|
|
63
|
+
description: 'source test space',
|
|
64
|
+
});
|
|
65
|
+
await sourceSpace.initialized;
|
|
66
|
+
|
|
67
|
+
destSpace = new InnerSpace_V1({
|
|
68
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
69
|
+
name: 'dest',
|
|
70
|
+
uuid: 'dest_uuid',
|
|
71
|
+
description: 'dest test space',
|
|
72
|
+
});
|
|
73
|
+
await destSpace.initialized;
|
|
74
|
+
|
|
75
|
+
// 2. Seed Data
|
|
76
|
+
// Root -> V1 (Shared) -> V2 (Dest has New)
|
|
77
|
+
// Source only has V1.
|
|
78
|
+
|
|
79
|
+
const v0 = await createTimelineRootTestHelper<TestData>({
|
|
80
|
+
ib: 'timeline_root_ff',
|
|
81
|
+
data: { type: 'root', label: 'Root' },
|
|
82
|
+
space: sourceSpace,
|
|
83
|
+
});
|
|
84
|
+
const addrV0 = getIbGibAddr({ ibGib: v0 });
|
|
85
|
+
console.log(pretty(v0));
|
|
86
|
+
|
|
87
|
+
// V1 (Both have it, but we create in source and copy to dest)
|
|
88
|
+
const v1 = await mut8Timeline<TestData>({
|
|
89
|
+
timeline: v0,
|
|
90
|
+
mut8Opts: { dataToAddOrPatch: { type: 'comment', label: 'V1' } },
|
|
91
|
+
metaspace,
|
|
92
|
+
space: sourceSpace,
|
|
93
|
+
});
|
|
94
|
+
const addrV1 = getIbGibAddr({ ibGib: v1 });
|
|
95
|
+
console.log(pretty(v1));
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
// Transfer Root & V1 to Dest
|
|
99
|
+
const initialDepGraph = await getDependencyGraph({ ibGibs: [v0, v1], space: sourceSpace });
|
|
100
|
+
await putInSpace({ space: destSpace, ibGibs: Object.values(initialDepGraph) }); // Naive seeding
|
|
101
|
+
await registerNewIbGib({ space: destSpace, ibGib: v0 });
|
|
102
|
+
await registerNewIbGib({ space: destSpace, ibGib: v1 });
|
|
103
|
+
|
|
104
|
+
// V2 (Created in Dest ONLY)
|
|
105
|
+
const v2 = await mut8Timeline<TestData>({
|
|
106
|
+
timeline: v1, // v1 is in memory, linked to source, but we want to Mutate IN DEST SPACE
|
|
107
|
+
mut8Opts: { dataToAddOrPatch: { type: 'comment', label: 'V2' } },
|
|
108
|
+
metaspace,
|
|
109
|
+
space: destSpace, // Mutate in Dest
|
|
110
|
+
});
|
|
111
|
+
const addrV2 = getIbGibAddr({ ibGib: v2 });
|
|
112
|
+
console.log(pretty(v2));
|
|
113
|
+
|
|
114
|
+
const fnAddrExistsInSpace = async (addr: IbGibAddr, space: IbGibSpaceAny) => {
|
|
115
|
+
const resGet = await getFromSpace({ addr, space });
|
|
116
|
+
return resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await ifWeMight(sir, 'verify setup', async () => {
|
|
120
|
+
// Ensure V2 is ONLY in Dest (it is, per `space: destSpace`)
|
|
121
|
+
// Ensure Source does NOT have V2
|
|
122
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV0, sourceSpace)).asTo('source has V0').isGonnaBeTrue();
|
|
123
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV1, sourceSpace)).asTo('source has V1').isGonnaBeTrue();
|
|
124
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV2, sourceSpace)).asTo('source has V2').isGonnaBeFalse();
|
|
125
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV0, destSpace)).asTo('dest has V0').isGonnaBeTrue();
|
|
126
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV1, destSpace)).asTo('dest has V1').isGonnaBeTrue();
|
|
127
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV2, destSpace)).asTo('dest has V2').isGonnaBeTrue();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 3. Setup Sync
|
|
131
|
+
const mockKeystone = await getTestKeystoneServiceHelper();
|
|
132
|
+
const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
|
|
133
|
+
const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
|
|
134
|
+
|
|
135
|
+
const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
136
|
+
await peer.initialized;
|
|
137
|
+
await peer.initializeSender({
|
|
138
|
+
senderSpace: sourceSpace, // "Client"
|
|
139
|
+
receiverSpace: destSpace, // "Server"
|
|
140
|
+
receiverCoordinator,
|
|
141
|
+
receiverMetaspace: metaspace,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 4. Run Sync (Source Pushes V1)
|
|
145
|
+
console.log(`${lc} Running Sync...`);
|
|
146
|
+
const { done, sagaId, updates$ } = await senderCoordinator.sync({
|
|
147
|
+
peer: peer,
|
|
148
|
+
localSpace: sourceSpace,
|
|
149
|
+
metaspace: metaspace,
|
|
150
|
+
domainIbGibs: [v1], // Source tries to push V1
|
|
151
|
+
useSessionIdentity: true,
|
|
152
|
+
});
|
|
153
|
+
await done;
|
|
154
|
+
|
|
155
|
+
// 5. Verify Sync (v2 should be in both source and dest now)
|
|
156
|
+
console.log(`${lc} Verifying Sync...`);
|
|
157
|
+
|
|
158
|
+
await ifWeMight(sir, `verify v2 now also in source`, async () => {
|
|
159
|
+
// Verify Tip (V2)
|
|
160
|
+
|
|
161
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV0, sourceSpace)).asTo('source has V0').isGonnaBeTrue();
|
|
162
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV1, sourceSpace)).asTo('source has V1').isGonnaBeTrue();
|
|
163
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV2, sourceSpace)).asTo('source has V2').isGonnaBeTrue();
|
|
164
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV0, destSpace)).asTo('dest has V0').isGonnaBeTrue();
|
|
165
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV1, destSpace)).asTo('dest has V1').isGonnaBeTrue();
|
|
166
|
+
iReckon(sir, await fnAddrExistsInSpace(addrV2, destSpace)).asTo('dest has V2').isGonnaBeTrue();
|
|
167
|
+
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await ifWeMight(sir, `dependency graphs the same`, async () => {
|
|
171
|
+
|
|
172
|
+
const sourceDepGraph = await getDependencyGraph({
|
|
173
|
+
ibGibAddr: addrV2,
|
|
174
|
+
space: sourceSpace,
|
|
175
|
+
});
|
|
176
|
+
const destDepGraph = await getDependencyGraph({
|
|
177
|
+
ibGibAddr: addrV2,
|
|
178
|
+
space: destSpace,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const sourceDepGraphAddrs = Object.keys(sourceDepGraph);
|
|
182
|
+
const destDepGraphAddrs = Object.keys(destDepGraph);
|
|
183
|
+
|
|
184
|
+
iReckon(sir, sourceDepGraphAddrs.length === destDepGraphAddrs.length).asTo('dep graphs same size').isGonnaBeTrue();
|
|
185
|
+
|
|
186
|
+
sourceDepGraphAddrs.forEach(sourceDepAddr => {
|
|
187
|
+
iReckon(sir, destDepGraphAddrs.includes(sourceDepAddr)).asTo(`${sourceDepAddr} is both graphs`).isGonnaBeTrue();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ========================================================================
|
|
192
|
+
// IDENTITY-RELATED ASSERTIONS (TDD - Expose Implementation Gaps)
|
|
193
|
+
// ========================================================================
|
|
194
|
+
|
|
195
|
+
// Need to capture session identity and saga context from sync result
|
|
196
|
+
// For now, we'll retrieve from spaces after sync completes
|
|
197
|
+
let sessionKeystoneAddr: IbGibAddr | undefined;
|
|
198
|
+
|
|
199
|
+
await ifWeMight(sir, 'IDENTITY: session keystone exists in sender space', async () => {
|
|
200
|
+
// Session keystone should be in sender's durable space
|
|
201
|
+
// Since we can't enumerate space easily, we verify indirectly:
|
|
202
|
+
// The fact that sync completed means keystone was findable
|
|
203
|
+
|
|
204
|
+
// TODO: Capture sessionKeystoneAddr from sync() return value
|
|
205
|
+
// For now, placeholder passes
|
|
206
|
+
iReckon(sir, true)
|
|
207
|
+
.asTo('sync completed (keystone must exist)')
|
|
208
|
+
.isGonnaBeTrue();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await ifWeMight(sir, 'IDENTITY: session keystone exists in receiver space', async () => {
|
|
212
|
+
// Session keystone should be transferred to receiver's durable space
|
|
213
|
+
iReckon(sir, sessionKeystoneAddr)
|
|
214
|
+
.asTo('session keystone address was captured')
|
|
215
|
+
.isGonnaBeTruthy();
|
|
216
|
+
|
|
217
|
+
if (sessionKeystoneAddr) {
|
|
218
|
+
const destResult = await getFromSpace({ addr: sessionKeystoneAddr, space: destSpace });
|
|
219
|
+
iReckon(sir, destResult.success)
|
|
220
|
+
.asTo('receiver has session keystone')
|
|
221
|
+
.isGonnaBeTrue();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await ifWeMight(sir, 'IDENTITY: saga frames are signed', async () => {
|
|
226
|
+
// TODO: Get saga frames and check each has a proof
|
|
227
|
+
// This will FAIL when we actually check - that's the point (TDD RED)
|
|
228
|
+
|
|
229
|
+
iReckon(sir, false)
|
|
230
|
+
.asTo('saga frames have proofs (NOT IMPLEMENTED - should fail)')
|
|
231
|
+
.isGonnaBeTrue();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await ifWeMight(sir, 'IDENTITY: frame signatures are valid', async () => {
|
|
235
|
+
// TODO: For each saga frame, validate proof against session keystone
|
|
236
|
+
// const isValid = await validateProofWithKeystone({
|
|
237
|
+
// proof: frame.proof,
|
|
238
|
+
// keystone: sessionKeystone,
|
|
239
|
+
// targetFrame: frame
|
|
240
|
+
// });
|
|
241
|
+
// iReckon(sir, isValid).asTo('proof is valid').isGonnaBeTrue();
|
|
242
|
+
|
|
243
|
+
// Placeholder for now
|
|
244
|
+
iReckon(sir, true)
|
|
245
|
+
.asTo('proof validation not yet implemented')
|
|
246
|
+
.isGonnaBeTrue();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await ifWeMight(sir, 'IDENTITY: session keystone challenges are depleted', async () => {
|
|
250
|
+
// TODO: Session keystone should evolve after signing frames
|
|
251
|
+
// This will FAIL because keystone evolution not implemented yet
|
|
252
|
+
|
|
253
|
+
iReckon(sir, false)
|
|
254
|
+
.asTo('challenges depleted (NOT IMPLEMENTED - should fail)')
|
|
255
|
+
.isGonnaBeTrue();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await ifWeMight(sir, 'IDENTITY: frame timestamps are present and fresh', async () => {
|
|
259
|
+
// TODO: Check each frame has timestamp in proof claim
|
|
260
|
+
// const claim = JSON.parse(frame.proof.claim.scope);
|
|
261
|
+
// iReckon(sir, claim.timestamp).asTo('has timestamp').isGonnaBeTruthy();
|
|
262
|
+
// const age = Date.now() - claim.timestamp;
|
|
263
|
+
// iReckon(sir, age < 60000).asTo('timestamp is fresh (<60s)').isGonnaBeTrue();
|
|
264
|
+
|
|
265
|
+
// Placeholder
|
|
266
|
+
iReckon(sir, true)
|
|
267
|
+
.asTo('timestamp validation not yet implemented')
|
|
268
|
+
.isGonnaBeTrue();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await ifWeMight(sir, 'IDENTITY: keystone has no hard links to domain ibgibs', async () => {
|
|
272
|
+
if (sessionKeystoneAddr) {
|
|
273
|
+
const keystoneResult = await getFromSpace({
|
|
274
|
+
addr: sessionKeystoneAddr,
|
|
275
|
+
space: sourceSpace
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (keystoneResult.success && keystoneResult.ibGibs && keystoneResult.ibGibs.length > 0) {
|
|
279
|
+
const keystone = keystoneResult.ibGibs[0];
|
|
280
|
+
const rel8ns = keystone.rel8ns || {};
|
|
281
|
+
const allRel8nAddrs = Object.values(rel8ns)
|
|
282
|
+
.flat()
|
|
283
|
+
.filter(addr => typeof addr === 'string');
|
|
284
|
+
|
|
285
|
+
const domainAddrs = [addrV0, addrV1, addrV2];
|
|
286
|
+
|
|
287
|
+
for (const domainAddr of domainAddrs) {
|
|
288
|
+
iReckon(sir, allRel8nAddrs.includes(domainAddr))
|
|
289
|
+
.asTo(`keystone does not hard link to ${domainAddr}`)
|
|
290
|
+
.isGonnaBeFalse();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await ifWeMight(sir, 'IDENTITY: saga frames have no hard links to domain ibgibs', async () => {
|
|
297
|
+
// Saga frames should NOT have hard links to domain ibgibs
|
|
298
|
+
// This currently PASSES but will expose issues if hard links exist
|
|
299
|
+
|
|
300
|
+
iReckon(sir, true)
|
|
301
|
+
.asTo('hard link validation (placeholder)')
|
|
302
|
+
.isGonnaBeTrue();
|
|
303
|
+
|
|
304
|
+
// TODO: Get saga frames and check their rel8ns
|
|
305
|
+
// for (const frame of sagaFrames) {
|
|
306
|
+
// const rel8nAddrs = Object.values(frame.rel8ns || {}).flat();
|
|
307
|
+
// for (const domainAddr of [addrV0, addrV1, addrV2]) {
|
|
308
|
+
// iReckon(sir, rel8nAddrs.includes(domainAddr)).asTo get('no hard link').isGonnaBeFalse();
|
|
309
|
+
// }
|
|
310
|
+
// }
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
});
|
|
@@ -197,7 +197,7 @@ export class SyncSagaCoordinator {
|
|
|
197
197
|
|
|
198
198
|
// BOOTSTRAP IDENTITY (Session Keystone)
|
|
199
199
|
const sessionIdentity = useSessionIdentity
|
|
200
|
-
? await this.getSessionIdentity({ sagaId, metaspace,
|
|
200
|
+
? await this.getSessionIdentity({ sagaId, metaspace, localSpace })
|
|
201
201
|
: undefined;
|
|
202
202
|
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
203
203
|
|
|
@@ -328,11 +328,11 @@ export class SyncSagaCoordinator {
|
|
|
328
328
|
private async getSessionIdentity({
|
|
329
329
|
sagaId,
|
|
330
330
|
metaspace,
|
|
331
|
-
|
|
331
|
+
localSpace,
|
|
332
332
|
}: {
|
|
333
333
|
sagaId: string,
|
|
334
334
|
metaspace: MetaspaceService,
|
|
335
|
-
|
|
335
|
+
localSpace: IbGibSpaceAny,
|
|
336
336
|
}): Promise<KeystoneIbGib_V1> {
|
|
337
337
|
const lc = `${this.lc}[${this.getSessionIdentity.name}]`;
|
|
338
338
|
try {
|
|
@@ -349,7 +349,7 @@ export class SyncSagaCoordinator {
|
|
|
349
349
|
masterSecret: sagaId,
|
|
350
350
|
configs: [config],
|
|
351
351
|
metaspace,
|
|
352
|
-
space:
|
|
352
|
+
space: localSpace // ✅ FIXED: Use durable space, not temp space
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
return sessionIdentity;
|
package/src/test/mock-space.mts
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
2
|
-
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
1
|
+
// import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
2
|
+
// import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export class MockIbGibSpace {
|
|
11
|
-
|
|
4
|
+
// /**
|
|
5
|
+
// * A simple in-memory map acting as a Space.
|
|
6
|
+
// * Pure Storage. No Indexing logic.
|
|
7
|
+
// *
|
|
8
|
+
// * Copied/Adapted from Keystone Respecs.
|
|
9
|
+
// */
|
|
10
|
+
// export class MockIbGibSpace {
|
|
11
|
+
// store = new Map<string, IbGib_V1>();
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// constructor(public name: string = "mock_space") { }
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// async put({ ibGib }: { ibGib: IbGib_V1 }): Promise<void> {
|
|
16
|
+
// const addr = getIbGibAddr({ ibGib });
|
|
17
|
+
// this.store.set(addr, JSON.parse(JSON.stringify(ibGib))); // Deep copy
|
|
18
|
+
// }
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// async get({ addr }: { addr: string }): Promise<IbGib_V1 | null> {
|
|
21
|
+
// const data = this.store.get(addr);
|
|
22
|
+
// return data ? JSON.parse(JSON.stringify(data)) : null;
|
|
23
|
+
// }
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
25
|
+
// async witness(arg: any): Promise<any> {
|
|
26
|
+
// const cmd = arg.data?.cmd;
|
|
27
|
+
// if (cmd === 'get') {
|
|
28
|
+
// const addrs = arg.data.ibGibAddrs || [];
|
|
29
|
+
// const ibGibs: IbGib_V1[] = [];
|
|
30
|
+
// for (const addr of addrs) {
|
|
31
|
+
// const ig = await this.get({ addr });
|
|
32
|
+
// if (ig) ibGibs.push(ig);
|
|
33
|
+
// }
|
|
34
|
+
// return { ibGibs };
|
|
35
|
+
// }
|
|
36
|
+
// if (cmd === 'put') {
|
|
37
|
+
// const ibGibs = arg.ibGibs || [];
|
|
38
|
+
// for (const ibGib of ibGibs) {
|
|
39
|
+
// await this.put({ ibGib });
|
|
40
|
+
// }
|
|
41
|
+
// return { ibGibs: [] }; // Return empty result or whatever witness expects
|
|
42
|
+
// }
|
|
43
|
+
// return undefined;
|
|
44
|
+
// }
|
|
45
|
+
// }
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export class MockMetaspaceService {
|
|
47
|
+
// /**
|
|
48
|
+
// * A partial mock of Metaspace.
|
|
49
|
+
// */
|
|
50
|
+
// export class MockMetaspaceService {
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
// /**
|
|
53
|
+
// * Map of TJP Gib (Timeline ID) -> Latest IbGib Addr (Head)
|
|
54
|
+
// */
|
|
55
|
+
// timelineHeads = new Map<string, string>();
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// constructor(public space: MockIbGibSpace) { }
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// async getLocalUserSpace({ lock }: { lock: boolean }): Promise<MockIbGibSpace> {
|
|
60
|
+
// return this.space;
|
|
61
|
+
// }
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
// async put(args: any): Promise<void> {
|
|
64
|
+
// const target = args.space || this.space;
|
|
65
|
+
// return target.put(args);
|
|
66
|
+
// }
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// async registerNewIbGib(args: { ibGib: IbGib_V1, space?: any }): Promise<void> {
|
|
69
|
+
// const { ibGib } = args;
|
|
70
|
+
// const targetSpace = args.space || this.space;
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// // 1. Ensure it is stored
|
|
73
|
+
// await targetSpace.put({ ibGib });
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
75
|
+
// // 2. Extract TJP
|
|
76
|
+
// const gib = ibGib.gib || '';
|
|
77
|
+
// let tjpGib = gib;
|
|
78
|
+
// if (gib.includes('.')) {
|
|
79
|
+
// const parts = gib.split('.');
|
|
80
|
+
// tjpGib = parts.slice(1).join('.');
|
|
81
|
+
// }
|
|
82
|
+
// const addr = getIbGibAddr({ ibGib });
|
|
83
|
+
// this.timelineHeads.set(tjpGib, addr);
|
|
84
|
+
// }
|
|
85
|
+
// }
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Sync Protocol Verification
|
|
2
|
-
|
|
3
|
-
This document outlines the testing strategy and verification results for the Symmetric Sync Protocol.
|
|
4
|
-
|
|
5
|
-
## Testing Strategy
|
|
6
|
-
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.
|
|
7
|
-
|
|
8
|
-
### Key Test Files
|
|
9
|
-
* `sync-innerspace.respec.mts`: Basic Push/Pull scenarios.
|
|
10
|
-
* `sync-innerspace-multiple-timelines.respec.mts`: Handling multiple independent timelines in one Saga.
|
|
11
|
-
* `sync-innerspace-deep-updates.respec.mts`: Syncing timelines with extensive history (ensuring `past` links are traversed).
|
|
12
|
-
* `sync-innerspace-partial-update.respec.mts`: **Smart Diff** verification (Sender uses Receiver's context to send only deltas).
|
|
13
|
-
* `sync-innerspace-dest-ahead.respec.mts`: Verifying "Fast-Backward" or "Push Offer" logic (Sender offers update, Receiver accepts).
|
|
14
|
-
* `sync-innerspace-constants.respec.mts`: Verifying synchronization of **Stones** (Constants/Non-TJPs).
|
|
15
|
-
|
|
16
|
-
## Verification Matrix
|
|
17
|
-
|
|
18
|
-
### 1. Basic Scenarios
|
|
19
|
-
| Feature | Description | Status | Test File |
|
|
20
|
-
| :--- | :--- | :--- | :--- |
|
|
21
|
-
| **Push** | Source syncs a new timeline to an empty Dest. | ✅ Verified | `sync-innerspace.respec.mts` |
|
|
22
|
-
| **Pull** | Dest requests data from Source (Reverse Sync). | ⏳ Pending | `sync-innerspace.respec.mts` |
|
|
23
|
-
| **Dest Ahead** | Dest has newer data; Source offers Push. | ✅ Verified | `sync-innerspace-dest-ahead.respec.mts` |
|
|
24
|
-
|
|
25
|
-
### 2. Complex Graph Scenarios
|
|
26
|
-
| Feature | Description | Status | Test File |
|
|
27
|
-
| :--- | :--- | :--- | :--- |
|
|
28
|
-
| **Multi-Timeline** | Syncing multiple separate timelines. | ✅ Verified | `sync-innerspace-multiple-timelines.respec.mts` |
|
|
29
|
-
| **Deep Updates** | Syncing deep dependency chains (Ancestors). | ✅ Verified | `sync-innerspace-deep-updates.respec.mts` |
|
|
30
|
-
| **Smart Diff** | Sender skips data Receiver already has. | ✅ Verified | `sync-innerspace-partial-update.respec.mts` |
|
|
31
|
-
| **Constants** | Syncing immutable stones (no TJP). | ✅ Verified | `sync-innerspace-constants.respec.mts` |
|
|
32
|
-
|
|
33
|
-
### 3. Failure & Edge Cases
|
|
34
|
-
| Feature | Description | Status | Test File |
|
|
35
|
-
| :--- | :--- | :--- | :--- |
|
|
36
|
-
| **Validation** | Verifying Integrity Checks (ib/gib). | ⏳ Pending | - |
|
|
37
|
-
| **Conflicts** | Divergent branches (Merge Strategy). | ⏳ Pending | - |
|
|
38
|
-
|
|
39
|
-
## Recent Verifications
|
|
40
|
-
### Sync Constants (2026-01-08)
|
|
41
|
-
* **Goal**: Ensure constants (ibGibs without `tjp` or `past`) are synced.
|
|
42
|
-
* **Result**: Passed.
|
|
43
|
-
* **Fixes**: Updated `handleInitFrame` to request missing stones; updated `handleAckFrame` to include "tip" stones in payload even without dependencies.
|