@ibgib/core-gib 0.1.42 → 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-text-merge.respec.mjs +26 -26
- 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-text-merge.respec.mts +25 -25
- 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
|
@@ -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
|
// });
|
|
@@ -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;
|