@ibgib/core-gib 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/common/other/graph-helper.d.mts.map +1 -1
  2. package/dist/common/other/graph-helper.mjs +18 -3
  3. package/dist/common/other/graph-helper.mjs.map +1 -1
  4. package/dist/sync/graft-info/graft-info-helpers.mjs +1 -1
  5. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -1
  6. package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs +321 -235
  7. package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
  8. package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs +6 -6
  9. package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs.map +1 -1
  10. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +1 -1
  11. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  12. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  13. package/dist/sync/sync-saga-coordinator.mjs +49 -12
  14. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  15. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +4 -0
  16. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  17. package/dist/test-helpers.d.mts +104 -47
  18. package/dist/test-helpers.d.mts.map +1 -1
  19. package/dist/test-helpers.mjs +335 -131
  20. package/dist/test-helpers.mjs.map +1 -1
  21. package/dist/test-types.d.mts +25 -7
  22. package/dist/test-types.d.mts.map +1 -1
  23. package/dist/witness/space/reconciliation-space/reconciliation-space-helper.mjs +2 -2
  24. package/dist/witness/space/reconciliation-space/reconciliation-space-helper.mjs.map +1 -1
  25. package/package.json +1 -1
  26. package/src/common/other/graph-helper.mts +14 -2
  27. package/src/sync/SYNC_TESTING.md +135 -70
  28. package/src/sync/graft-info/graft-info-helpers.mts +1 -1
  29. package/src/sync/sync-conflict-adv-multitimelines.respec.mts +332 -245
  30. package/src/sync/sync-conflict-basic-multitimelines.respec.mts +5 -5
  31. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +1 -1
  32. package/src/sync/sync-saga-coordinator.mts +58 -14
  33. package/src/sync/sync-saga-message/sync-saga-message-types.mts +4 -0
  34. package/src/test-helpers.mts +429 -152
  35. package/src/test-types.mts +30 -7
  36. package/src/witness/space/reconciliation-space/reconciliation-space-helper.mts +2 -2
  37. package/tmp.md +205 -1160
@@ -14,29 +14,26 @@ import { clone, delay, extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/he
14
14
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
15
15
  import { IbGibData_V1, IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
16
16
 
17
+ import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
18
+ import { TestTransformer, getTestKeystoneServiceHelper } from '../test-helpers.mjs';
19
+ import { TestMut8Info, } from '../test-types.mjs';
17
20
  import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
18
21
  import { getFromSpace } from '../witness/space/space-helper.mjs';
19
22
  import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
20
23
  import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
21
- import { createTimelineRootTestHelper, getTestKeystoneServiceHelper } from '../test-helpers.mjs';
22
- import { appendToTimeline, createTimeline, mut8Timeline } from '../timeline/timeline-api.mjs';
23
24
  import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
24
25
  import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
25
- import { getIbGibsFromCache_fallbackToSpaces, getTjpAddr, toDto } from '../common/other/ibgib-helper.mjs';
26
+ import { getIbGibsFromCache_fallbackToSpaces, getTjpAddr, } from '../common/other/ibgib-helper.mjs';
26
27
  import { SyncSagaInfo } from './sync-types.mjs';
27
- import { fnObs } from '../common/pubsub/observer/observer-helper.mjs';
28
28
  import { getDependencyGraph, graphsAreEquivalent } from '../common/other/graph-helper.mjs';
29
- import { ErrorIbGib_V1 } from '../common/error/error-types.mjs';
30
29
  import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
31
30
  import { SyncConflictStrategy } from './sync-constants.mjs';
32
- import { GRAFT_BASE_REL8N_NAME, GRAFT_INFO_REL8N_NAME, GRAFT_ORPHAN_REL8N_NAME } from './graft-info/graft-info-constants.mjs';
31
+ import { getHistory } from '../timeline/timeline-api.mjs';
33
32
 
34
- const logalot = false;
33
+ const logalot = GLOBAL_LOG_A_LOT;
35
34
  const lc = sir;
36
35
 
37
-
38
36
  interface TestData extends IbGibData_V1 {
39
- type?: string;
40
37
  /**
41
38
  * field that is made in a common (pre-sync) ibgib
42
39
  */
@@ -47,9 +44,10 @@ interface TestData extends IbGibData_V1 {
47
44
 
48
45
  const TEST_REL8N_NAME = 'testrel8n';
49
46
 
50
- await respecfully(sir, `Two different fields and rel8d`, async () => {
47
+ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
48
+
49
+ // #region Init/Setup
51
50
 
52
- // 1. Setup Spaces
53
51
  const metaspace = new Metaspace_Innerspace(undefined);
54
52
  await metaspace.initialize({
55
53
  getFnAlert: () => async ({ title, msg }) => { console.log(`[Alert] ${title}: ${msg}`); },
@@ -77,316 +75,341 @@ await respecfully(sir, `Two different fields and rel8d`, async () => {
77
75
  });
78
76
  await destSpace.initialized;
79
77
 
80
- // 2. Seed common History (v0 -> v1)
81
- console.log(`${lc} Creating common History...`);
82
- const alpha_v0 = await createTimelineRootTestHelper<TestData>({
83
- /**
84
- * greek letters are distinct timelines
85
- */
86
- ib: 'alpha',
87
- data: { type: 'alpha', },
88
- space: sourceSpace,
89
- });
90
- // Create v1 (common)
91
- /**
92
- * **created** on source, but sync will take it to dest
93
- */
94
- const alpha_v1_common_onSource = await mut8Timeline<TestData>({
95
- timeline: alpha_v0,
96
- mut8Opts: {
97
- mut8Ib: alpha_v0.ib + '_common',
98
- dataToAddOrPatch: { commonField: 'common yo' }
99
- },
100
- metaspace,
101
- space: sourceSpace,
102
- });
103
- const tjpAddr =
104
- getTjpAddr({ ibGib: alpha_v0, defaultIfNone: 'incomingAddr' }) ??
105
- getIbGibAddr({ ibGib: alpha_v0 });
106
- // Sync alpha to dest immediately so they start synced
107
- // must get the entire dependency graph for alpha
108
- const depGraph_alpha_v1Andcommon = await getDependencyGraph({
109
- ibGibAddrs: [tjpAddr],
110
- space: sourceSpace,
111
- live: true,
112
- }) ?? {};
113
- console.log(`${lc} depGraph_testRootAndV1Common: ${pretty(depGraph_alpha_v1Andcommon)}`)
114
- if (Object.keys(depGraph_alpha_v1Andcommon).length === 0) {
115
- throw new Error(`(UNEXPECTED) depGraph_testRootAndV1Common empty? (E: 39b4d855ffa65476084b4123786da826)`);
116
- }
117
- // put the entire graph into the destspace, but...
118
- await metaspace.put({ ibGibs: Object.values(depGraph_alpha_v1Andcommon), space: destSpace });
119
- const resGetDest = await getFromSpace({ space: destSpace, addr: getIbGibAddr({ ibGib: alpha_v1_common_onSource }) });
120
- if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
121
- throw new Error(`Failed to retrieve v1_Common_onDest from destSpace. (E: 40c67811eba14f729880a46dcbb65126)`);
78
+ const testTransformer = new TestTransformer(metaspace, sourceSpace, destSpace);
79
+
80
+ if (logalot) { console.log(`${lc} Setting up Coordinators...`); }
81
+ const mockKeystone = await getTestKeystoneServiceHelper();
82
+ const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
83
+ const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
84
+
85
+ async function newTestPeer(): Promise<SyncPeerInnerspace_V1> {
86
+ const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
87
+ await peer.initialized;
88
+ await peer.initializeSender({
89
+ senderSpace: sourceSpace,
90
+ receiverSpace: destSpace,
91
+ receiverCoordinator: receiverCoordinator,
92
+ receiverMetaspace: metaspace,
93
+ });
94
+ return peer;
122
95
  }
96
+
97
+ // #endregion Init/Setup
98
+
99
+ // #region Round 1: Seed common history
100
+
123
101
  /**
124
- * same as v1_common
102
+ * not sure if we're going to do anything with the round info itself
125
103
  */
126
- const alpha_v1_common_onDest = resGetDest.ibGibs[0];
127
- // ...only register the main ibgibs
128
- await metaspace.registerNewIbGib({ ibGib: alpha_v0, space: destSpace });
129
- await metaspace.registerNewIbGib({ ibGib: alpha_v1_common_onDest, space: destSpace }); // it's the "on source" one, but only because
104
+ const _r1 = testTransformer.newRound({
105
+ name: 'r1_seedCommonHistory',
106
+ description: 'Create alpha v0 and v1 on source, sync to dest so both v0 and v1 are established as synced.',
107
+ });
130
108
 
109
+ // #region r1 setup
131
110
 
132
- // 3. Create Divergence(s) (v2source vs v2dest), i.e. "simultaneous" parallel edits
133
- // NOTE: source vs dest here is to indicate the location the edit is made.
134
- // syncing will obviously blur this line.
135
- console.log(`${lc} Creating Divergence...`);
111
+ if (logalot) { console.log(`${lc} Creating common History...`); }
112
+ const r1_alpha_v0_source = await testTransformer.create({
113
+ atom: 'alpha',
114
+ in: 'source',
115
+ name: 'r1_alpha_v0_source', // should be auto-calculated?
116
+ });
136
117
 
137
- // #region edit on source
118
+ // Create v1 (common) - will be synced to dest
119
+ const r1_alpha_v1_source_common = await testTransformer.mut8({
120
+ stepInfo: r1_alpha_v0_source,
121
+ strField: 'commonField',
122
+ name: 'r1_alpha_v1_source_common'
123
+ });
124
+ const alpha_tjpAddr =
125
+ getTjpAddr({ ibGib: r1_alpha_v0_source.ibGib, defaultIfNone: 'incomingAddr' })!;
138
126
 
139
- // Source: v1 (common) -> v2source (Edit Field A and rel8 beta)
127
+ // #endregion r1 setup
140
128
 
141
- const beta_v0_source = await createTimelineRootTestHelper<TestData>({
142
- ib: 'beta',
143
- data: {
144
- fieldA: 'beta field A created on source',
145
- },
146
- space: sourceSpace,
129
+ await respecfully(sir, `r1 verify pre`, async () => {
130
+ await ifWeMight(sir, 'dest should NOT have alpha', async () => {
131
+ const resGet = await getFromSpace({
132
+ space: destSpace,
133
+ addr: alpha_tjpAddr,
134
+ });
135
+ // When timeline doesn't exist, getFromSpace returns success:false
136
+ iReckon(sir, resGet.success).asTo('getFromSpace should return false for missing timeline').isGonnaBeFalse();
137
+ iReckon(sir, resGet.ibGibs?.length ?? 0).asTo('Dest should NOT have alpha timeline yet').isGonnaBe(0);
138
+ });
147
139
  });
148
- const betaAddr_v0 = getIbGibAddr({ ibGib: beta_v0_source });
149
-
150
- const alpha_v2_source_sansBeta = await mut8Timeline<TestData>({
151
- timeline: alpha_v1_common_onSource,
152
- mut8Opts: {
153
- mut8Ib: alpha_v0.ib + '_v2source',
154
- dataToAddOrPatch: { fieldA: 'source_edit' }
155
- },
140
+
141
+ const r1_syncSaga = await senderCoordinator.sync({
142
+ peer: await newTestPeer(),
143
+ localSpace: sourceSpace,
156
144
  metaspace,
157
- space: sourceSpace,
145
+ domainIbGibs: [r1_alpha_v1_source_common.ibGib],
146
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
147
+ useSessionIdentity: false,
158
148
  });
159
- const alpha_v3_source_rel8beta = await appendToTimeline({
160
- timeline: alpha_v2_source_sansBeta,
161
- metaspace,
162
- space: sourceSpace,
163
- rel8nInfos: [
164
- { rel8nName: TEST_REL8N_NAME, ibGibs: [beta_v0_source] }
165
- ],
149
+
150
+ await r1_syncSaga.done;
151
+ if (logalot) { console.log(`${lc} r1_syncSaga Complete.`); }
152
+
153
+ await respecfully(sir, `r1 verify post`, async () => {
154
+ await ifWeMight(sir, 'dest should have alpha', async () => {
155
+ // alpha's full dep graph should exist on dest
156
+ const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
157
+ addrs: [r1_alpha_v0_source.addr],
158
+ space: destSpace,
159
+ });
160
+ iReckon(sir, alpha_dest).asTo('alpha exists in dest').isGonnaBeTruthy();
161
+ if (alpha_dest) {
162
+ const depGraph_alpha_source = await getDependencyGraph({ ibGib: r1_alpha_v0_source.ibGib, space: sourceSpace });
163
+ const depGraph_alpha_dest = await getDependencyGraph({ ibGib: alpha_dest, space: destSpace });
164
+ const alphaDepsAreEqual = graphsAreEquivalent({
165
+ graphA: depGraph_alpha_source, graphB: depGraph_alpha_dest
166
+ });
167
+ iReckon(sir, alphaDepsAreEqual).asTo('alpha got synced and graphs are equal').isGonnaBeTrue();
168
+ }
169
+ });
166
170
  });
167
- /**
168
- * since we're doing this first, this will be the base in our graft
169
- */
170
- const baseAddr = getIbGibAddr({ ibGib: alpha_v3_source_rel8beta });
171
171
 
172
- // #endregion edit on source
172
+ // Get alpha_v1_common from destSpace for Round 2
173
+ const resGetDest = await getFromSpace({ space: destSpace, addr: alpha_tjpAddr });
174
+ if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
175
+ throw new Error(`Failed to retrieve alpha from destSpace after sync. (E: 40c67811eba14f729880a46dcbb65126)`);
176
+ }
177
+ const alpha_v1_common_onDest = resGetDest.ibGibs[0];
173
178
 
174
- /**
175
- * timestamp/timestampMs drives orphan/base timeline selection in
176
- * graftTimelines
177
- */
178
- await delay(16);
179
+ // #endregion Round 1: Seed common history
179
180
 
181
+ // #region Round 2: Create Divergence (v2source vs v2dest) - "simultaneous" parallel edits
180
182
 
181
- // #region edit on dest
182
- // Dest: v1 (common) -> alpha_v2_dest (Edit Field B)
183
- const alpha_v2_dest = await mut8Timeline<TestData>({
184
- timeline: alpha_v1_common_onDest,
185
- mut8Opts: {
186
- mut8Ib: alpha_v0.ib + '_v2dest',
187
- dataToAddOrPatch: { fieldB: 'dest_edit' }
188
- },
189
- metaspace,
190
- space: destSpace,
183
+ const _r2 = testTransformer.newRound({
184
+ name: 'r2_divergent_edits',
185
+ description: 'Create conflicts on alpha, add beta relation on source, separate edit on dest'
191
186
  });
192
- /**
193
- * since we're doing this second (i.e., later temporally), this will be
194
- * the orphan in our graft
195
- */
196
- const orphanAddr = getIbGibAddr({ ibGib: alpha_v2_dest });
197
187
 
198
- // Verify Divergence
199
- console.log(`${lc} Divergence created.`);
188
+ // #region r2 source edits
200
189
 
190
+ if (logalot) { console.log(`${lc} Creating Divergence...`); }
201
191
 
202
- // 4. Run Sync (Optimistic)
203
- console.log(`${lc} Setting up Coordinators...`);
204
- const mockKeystone = await getTestKeystoneServiceHelper();
205
- const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
206
- const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
192
+ // Source: Create beta, mutate alpha fieldA, add beta relation
193
+ const r2_beta_v0_source = await testTransformer.create({
194
+ atom: 'beta',
195
+ in: 'source',
196
+ data: { fieldA: 'beta field A created on source' },
197
+ name: 'r2_beta_v0_source'
198
+ });
199
+ const r2_alpha_v2_source_arbitraryMut8 = await testTransformer.mut8({
200
+ ibGib: r1_alpha_v1_source_common.ibGib,
201
+ in: 'source',
202
+ strField: 'fieldA',
203
+ name: 'r2_alpha_v2_source_arbitraryMut8',
204
+ comments_snake_plus_CamelCase: 'editedOnSource',
205
+ });
206
+ const r2_alpha_v3_source_rel8dBeta = await testTransformer.rel8({
207
+ stepInfo: r2_alpha_v2_source_arbitraryMut8,
208
+ in: 'source',
209
+ targetIbGibs: [r2_beta_v0_source.ibGib],
210
+ rel8nName: TEST_REL8N_NAME,
211
+ name: 'r2_alpha_v3_source_rel8dBeta'
212
+ });
207
213
 
208
- const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
209
- await peer.initialized;
210
- await peer.initializeSender({
211
- senderSpace: sourceSpace, // "Client"
212
- receiverSpace: destSpace, // "Server"
213
- receiverCoordinator,
214
- receiverMetaspace: metaspace,
214
+ // #endregion r2 source edits
215
+
216
+ // #region r2 dest edits
217
+
218
+ // Dest: Mutate alpha fieldB (different field than source)
219
+ const r2_alpha_v2_dest_mut8FieldB = await testTransformer.mut8({
220
+ ibGib: alpha_v1_common_onDest,
221
+ in: 'dest',
222
+ strField: 'fieldB',
223
+ name: 'r2_alpha_v2_dest_mut8FieldB',
224
+ comments_snake_plus_CamelCase: 'editedOnDest',
215
225
  });
216
226
 
227
+ if (logalot) { console.log(`${lc} Divergence created.`); }
228
+
229
+ // #endregion r2 dest edits
230
+
217
231
  // Verify Receiver has correct KV (Pre-Sync Check)
218
232
  // This ensures the conflict precondition exists.
219
- await ifWeMight(sir, 'verify receiver KV pre-sync', async () => {
220
- try {
221
- const destKV = await receiverCoordinator.getKnowledgeMap({
222
- space: destSpace,
223
- metaspace,
224
- domainIbGibs: [alpha_v1_common_onDest]
225
- });
226
- const v1TjpAddr = getTjpAddr({ ibGib: alpha_v1_common_onDest, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: alpha_v1_common_onDest });
233
+ await respecfully(sir, `r2 verify pre`, async () => {
234
+ await ifWeMight(sir, 'dest has alpha v1 common', async () => {
235
+ try {
236
+ const destKV = await receiverCoordinator.getKnowledgeMap({
237
+ space: destSpace,
238
+ metaspace,
239
+ domainIbGibs: [alpha_v1_common_onDest]
240
+ });
241
+ const alpha_v1_common_tjpAddr_onDest =
242
+ getTjpAddr({ ibGib: alpha_v1_common_onDest, defaultIfNone: 'incomingAddr' })!;
227
243
 
228
- // Actually v1 has tjp rel8n.
229
- const destTip = destKV[v1TjpAddr];
230
- iReckon(sir, !!destTip).asTo(`Dest KV has timeline tip for ${v1TjpAddr}`).isGonnaBeTruthy();
231
- if (!destTip) {
232
- throw new Error(`Test Setup Fail: Dest Space does not have index for timeline ${v1TjpAddr}. Seeding failed. (E: c194a80b4e4877b77826c37a1753b826)`);
244
+ const destTip = destKV[alpha_v1_common_tjpAddr_onDest];
245
+ iReckon(sir, !!destTip).asTo(`Dest KV has timeline tip for ${alpha_v1_common_tjpAddr_onDest}`).isGonnaBeTruthy();
246
+ if (!destTip) {
247
+ throw new Error(`Test Setup Fail: Dest Space does not have index for timeline ${alpha_v1_common_tjpAddr_onDest}. Seeding failed. (E: c194a80b4e4877b77826c37a1753b826)`);
248
+ }
249
+ } catch (error) {
250
+ console.error(`${lc} ${extractErrorMsg(error)}`);
251
+ iReckon(sir, true).asTo('getKnowledgeMap errored out').isGonnaBeFalse();
233
252
  }
234
- } catch (error) {
235
- console.error(`${lc} ${extractErrorMsg(error)}`);
236
- iReckon(sir, true).asTo('getKnowledgeMap errored out').isGonnaBeFalse();
237
- }
253
+ });
238
254
  });
239
255
 
240
- console.log(`${lc} Running Sync (ConflictStrategy: optimistic)...`);
256
+ if (logalot) { console.log(`${lc} Running Sync (ConflictStrategy: optimistic)...`); }
241
257
 
242
- let resSync: SyncSagaInfo | undefined;
258
+ let r2_syncSaga: SyncSagaInfo | undefined;
243
259
  try {
244
- console.dir(sourceSpace)
245
- console.dir(destSpace)
246
- resSync = await senderCoordinator.sync({
247
- peer,
260
+ if (logalot) { console.dir(sourceSpace) }
261
+ if (logalot) { console.dir(destSpace) }
262
+ r2_syncSaga = await senderCoordinator.sync({
263
+ peer: await newTestPeer(),
248
264
  localSpace: sourceSpace,
249
265
  metaspace,
250
- domainIbGibs: [alpha_v3_source_rel8beta],
266
+ domainIbGibs: [r2_alpha_v3_source_rel8dBeta.ibGib],
251
267
  conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
252
268
  useSessionIdentity: false,
253
269
  });
254
270
 
255
- const sublc = `${lc}[updates$]`;
256
- /**
257
- * I have added this so you can see how to subscribe to an ibgib
258
- * observable using {@link fnObs}.
259
- */
260
- const subscription = await resSync.updates$.subscribe(fnObs({
261
- next: async (ctxIbGib) => {
262
- // console.log(`${sublc} next fired. ${JSON.stringify(ctxIbGib)}`);
263
- console.log(`${sublc} next fired. (I: e68d8894bac8800f9f3430e8a38d6626)`);
264
- },
265
- error: async (e: ErrorIbGib_V1) => {
266
- if (e.data) {
267
- console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: eddf17f76a486b9c5a2f4ee86ed38b26)`);
268
- } else {
269
- console.dir(e);
270
- console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: af9c3b6f1c88befeff77ca46111b3826)`);
271
- }
272
- },
273
- complete: async () => {
274
- console.log(`${sublc} complete fired`);
275
- },
276
- }));
277
-
278
- console.log(`${lc} awaiting resSync.done`)
279
- await resSync.done;
271
+ if (logalot) { console.log(`${lc} awaiting r2_syncSaga.done`) }
272
+ await r2_syncSaga.done;
280
273
 
281
- console.log(`${lc} Sync Complete.`);
274
+ if (logalot) { console.log(`${lc} r2_syncSaga Complete.`); }
282
275
  } catch (e) {
283
276
  console.error(`${lc} Sync Failed with Error:`, e);
284
277
  iReckon(sir, false).asTo(`Sync failed with error: ${e}`).isGonnaBeTruthy();
285
278
  return; // Exit test early
286
279
  }
287
280
 
288
- // 5. Verification
289
281
  // Expectation: Both Spaces should now have a NEW tip (V3) that merges v2source and v2dest.
282
+ await respecfully(sir, `r2 verify post`, async () => {
290
283
 
291
- const resSourceTip = await getFromSpace({ space: sourceSpace, addr: getIbGibAddr({ ibGib: alpha_v3_source_rel8beta }) });
292
-
293
- await respecfully(sir, `verify merge`, async () => {
294
284
  // Retrieve updated KV from Source (or check what happened to v2source)
295
285
  // Ideally, we just check the source space for the timeline tip
296
286
  // The timeline tip for alpha/v0 should now be NEW.
297
287
 
298
288
  try {
299
289
  // Get the KV for the Source Space
300
- const sourceKV_afterSync = await senderCoordinator.getKnowledgeMap({
290
+ const alpha_sourceKV_afterSync = await senderCoordinator.getKnowledgeMap({
301
291
  space: sourceSpace,
302
292
  metaspace,
303
- domainIbGibs: [alpha_v0] // We want to know the tip of this timeline
293
+ domainIbGibs: [r1_alpha_v0_source.ibGib] // We want to know the tip of this timeline
304
294
  });
305
- const tjpAddr =
306
- getTjpAddr({ ibGib: alpha_v0, defaultIfNone: 'incomingAddr' }) ??
307
- getIbGibAddr({ ibGib: alpha_v0 });
308
-
309
- if (logalot) { console.log(`${lc} getKnowledgeMap returned. sourceKV_afterSync: ${pretty(sourceKV_afterSync)} (I: e8780cda37c8b2a46eeb85786874e926)`); }
295
+ if (logalot) { console.log(`${lc} getKnowledgeMap returned. sourceKV_afterSync: ${pretty(alpha_sourceKV_afterSync)} (I: e8780cda37c8b2a46eeb85786874e926)`); }
296
+ const alpha_source_tipAddr = alpha_sourceKV_afterSync[alpha_tjpAddr];
297
+ if (logalot) { console.log(`${lc} alpha_source_tipAddr: ${alpha_source_tipAddr} (I: 34590f29c9b706ba04a54b0f633a6426)`); }
298
+ if (!alpha_source_tipAddr) {
299
+ throw new Error(`Source Space missing timeline tip for ${alpha_tjpAddr} (E: ec95980b9c980c5c5870812e15e43826)`);
300
+ }
310
301
 
311
- const sourceTipAddr = sourceKV_afterSync[tjpAddr];
312
- if (!sourceTipAddr) {
313
- throw new Error(`Source Space missing timeline tip for ${tjpAddr} (E: ec95980b9c980c5c5870812e15e43826)`);
302
+ const alpha_destKV_afterSync = await senderCoordinator.getKnowledgeMap({
303
+ space: destSpace,
304
+ metaspace,
305
+ domainIbGibs: [r1_alpha_v0_source.ibGib] // We want to know the tip of this timeline
306
+ });
307
+ if (logalot) { console.log(`${lc} getKnowledgeMap returned. destKV_afterSync: ${pretty(alpha_destKV_afterSync)} (I: e8780cda37c8b2a46eeb85786874e926)`); }
308
+ const alpha_dest_tipAddr = alpha_destKV_afterSync[alpha_tjpAddr];
309
+ if (logalot) { console.log(`${lc} alpha_dest_tipAddr: ${alpha_dest_tipAddr} (I: afac71eba4e6d3f3c47952680ad8b826)`); }
310
+ if (!alpha_dest_tipAddr) {
311
+ throw new Error(`dest Space missing timeline tip for ${alpha_tjpAddr} (E: 30b018c6349917aa28c9f538fa567826)`);
314
312
  }
313
+ await ifWeMight(sir, 'tip addrs', async () => {
314
+ iReckon(sir, alpha_source_tipAddr).asTo('source/dest have same tip addrs').isGonnaBe(alpha_dest_tipAddr);
315
+ })
315
316
 
316
317
  // Fetch the new tip
317
- const resTip = await getFromSpace({ space: sourceSpace, addr: sourceTipAddr });
318
- const newTip = resTip.ibGibs![0] as IbGib_V1<TestData>;
318
+ const resGet_alpha_source_tipAddr = await getFromSpace({ space: sourceSpace, addr: alpha_source_tipAddr });
319
+ const gotten_alpha_source_tipIbGib = resGet_alpha_source_tipAddr.ibGibs![0] as IbGib_V1<TestData>;
319
320
 
320
- await ifWeMight(sir, 'basics of alpha merge', async () => {
321
- iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be alpha_v3_source_rel8beta`).not.isGonnaBe(getIbGibAddr({ ibGib: alpha_v3_source_rel8beta }));
322
- iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be v2dest`).not.isGonnaBe(getIbGibAddr({ ibGib: alpha_v2_dest }));
321
+ if (logalot || true) {
322
+ console.log(`${lc} gotten_alpha_source_tipIbGib:\n${pretty(gotten_alpha_source_tipIbGib)} (I: e9c608c1fcb85f08d17d4af319d62726)`);
323
+ }
323
324
 
324
- // Check Data: Should have BOTH edits
325
- iReckon(sir, newTip.data!.fieldA).asTo('New Tip Data Field A').isGonnaBe('source_edit');
326
- iReckon(sir, newTip.data!.fieldB).asTo('New Tip Data Field B').isGonnaBe('dest_edit');
325
+ // #region DEBUG SANITY CHECK
326
+ const resGetTjp = await getFromSpace({ space: sourceSpace, addr: alpha_tjpAddr });
327
+ const gottenTjp = resGetTjp.ibGibs![0] as IbGib_V1<TestData>;
328
+ debugger;
329
+ const history = await getHistory({
330
+ timeline: gotten_alpha_source_tipIbGib,
331
+ space: sourceSpace,
332
+ metaspace,
333
+ includeDna: true,
327
334
  });
328
-
329
-
330
- // Check Graft Structure
331
- // Look for 'graftinfo' rel8n
332
-
333
- await ifWeMight(sir, 'graft info is valid', async () => {
334
- if (!newTip.rel8ns) { throw new Error(`(UNEXPECTED) newTip.rel8ns falsy? (E: eb4078712ea2f02c7ee84b983ed77826)`); }
335
- const graftInfoRel = newTip.rel8ns[GRAFT_INFO_REL8N_NAME];
336
- iReckon(sir, graftInfoRel).asTo('New Tip should have graftinfo rel8n').isGonnaBeTruthy();
337
- if (!graftInfoRel) { return; /* <<<< returns early, above reckoning fails */ }
338
- iReckon(sir, graftInfoRel!.length).asTo('Only 1 graftinfo').isGonnaBe(1);
339
-
340
- // Fetch Graft Info Stone
341
- const resGraft = await getFromSpace({ space: sourceSpace, addr: graftInfoRel![0] });
342
- const graftInfo = resGraft.ibGibs![0];
343
-
344
- // Check Graft Relations (graftbase, graftorphan)
345
- if (!graftInfo.rel8ns) { throw new Error(`(UNEXPECTED) graftInfo.rel8ns falsy? (E: 8e39e84e9e0813e09e8f6788ce488926)`); }
346
- const baseRel = graftInfo.rel8ns[GRAFT_BASE_REL8N_NAME];
347
- const orphanRel = graftInfo.rel8ns[GRAFT_ORPHAN_REL8N_NAME];
348
-
349
- iReckon(sir, baseRel).asTo('Graft Base exists').isGonnaBeTruthy();
350
- iReckon(sir, baseRel!.length).asTo('Graft Base length is 2').isGonnaBe(2);
351
- iReckon(sir, orphanRel).asTo('Graft Orphan exists').isGonnaBeTruthy();
352
- iReckon(sir, orphanRel!.length).asTo('Graft Orphan length is 1').isGonnaBe(1);
353
- iReckon(sir, baseRel!.at(-1)).asTo('base rel8ns final addr will be baseAddr').isGonnaBe(baseAddr);
354
- iReckon(sir, orphanRel![0]).asTo('orphan rel8ns will be later addr').isGonnaBe(orphanAddr);
335
+ debugger;
336
+ // #endregion DEBUG SANITY CHECK
337
+
338
+
339
+ await ifWeMight(sir, 'r2 basics of alpha merge', async () => {
340
+ iReckon(sir, alpha_source_tipAddr)
341
+ .asTo(`Source Tip (${alpha_source_tipAddr}) should NOT be r2_alpha_v3_source_rel8dBeta`)
342
+ .not.isGonnaBe(r2_alpha_v3_source_rel8dBeta.addr);
343
+ iReckon(sir, alpha_source_tipAddr)
344
+ .asTo(`Source Tip (${alpha_source_tipAddr}) should NOT be r2_alpha_v2_dest_mut8FieldB`)
345
+ .not.isGonnaBe(r2_alpha_v2_dest_mut8FieldB.addr);
346
+
347
+ // Check Data: Should have BOTH edits (using testTransformer-generated values)
348
+ const r2_source_mut8Info = r2_alpha_v2_source_arbitraryMut8.infos[0] as TestMut8Info;
349
+ const r2_dest_mut8Info = r2_alpha_v2_dest_mut8FieldB.infos[0] as TestMut8Info;
350
+
351
+ const a = gotten_alpha_source_tipIbGib.data![r2_source_mut8Info.key!];
352
+ const a2 = r2_source_mut8Info.value!;
353
+ console.log(`${a} should be ${a2}`)
354
+ iReckon(sir, gotten_alpha_source_tipIbGib.data![r2_source_mut8Info.key!]).asTo(`New Tip has source field ${r2_source_mut8Info.key!}`).isGonnaBe(r2_source_mut8Info.value!);
355
+
356
+ const b = gotten_alpha_source_tipIbGib.data![r2_dest_mut8Info.key!];
357
+ const b2 = r2_dest_mut8Info.value!;
358
+ console.log(`${b} should be ${b2}`)
359
+ iReckon(sir, gotten_alpha_source_tipIbGib.data![r2_dest_mut8Info.key!]).asTo(`New Tip has dest field ${r2_dest_mut8Info.key!}`).isGonnaBe(r2_dest_mut8Info.value!);
355
360
  });
356
361
 
357
- await ifWeMight(sir, 'alpha and deps synced', async () => {
358
- // alpha's full dep graph should exist on dest
359
- const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
360
- addrs: [betaAddr_v0],
362
+ await ifWeMight(sir, 'r2 alpha and deps synced', async () => {
363
+ // alpha's full dep graph should exist on dest, even though its
364
+ // timeline was grafted
365
+ const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
366
+ addrs: [r1_alpha_v0_source.addr],
361
367
  space: destSpace,
362
368
  });
363
- iReckon(sir, beta_dest).asTo('beta exists in dest').isGonnaBeTruthy();
364
- if (beta_dest) {
365
- const depGraph_beta_source = await getDependencyGraph({ ibGib: beta_v0_source, space: sourceSpace });
366
- if (Object.keys(depGraph_alpha_v1Andcommon).length === 0) {
367
- throw new Error(`(UNEXPECTED) depGraph_testRootAndV1Common empty? (E: 39b4d855ffa65476084b4123786da826)`);
369
+ iReckon(sir, alpha_dest).asTo('alpha exists in dest').isGonnaBeTruthy();
370
+ if (alpha_dest) {
371
+ try {
372
+ const depGraph_alpha_source = await getDependencyGraph({
373
+ ibGib: r1_alpha_v0_source.ibGib,
374
+ live: true,
375
+ space: sourceSpace
376
+ });
377
+ const depGraph_alpha_dest = await getDependencyGraph({
378
+ ibGib: alpha_dest,
379
+ live: true,
380
+ space: destSpace
381
+ });
382
+ const alphaDepsAreEqual = graphsAreEquivalent({
383
+ graphA: depGraph_alpha_source,
384
+ graphB: depGraph_alpha_dest,
385
+ slowButThorough: true,
386
+ });
387
+ iReckon(sir, alphaDepsAreEqual).asTo('alpha got synced and graphs are equal').isGonnaBeTrue();
388
+ } catch (error) {
389
+ console.error(`${lc} ${extractErrorMsg(error)}`);
390
+ iReckon(sir, true).asTo('ERROR: alpha exists in dest').isGonnaBeFalse(); // fails
368
391
  }
369
- const depGraph_beta_dest = await getDependencyGraph({ ibGib: beta_dest, space: destSpace });
370
- const betaDepsAreEqual = graphsAreEquivalent({
371
- graphA: depGraph_beta_source, graphB: depGraph_beta_dest
372
- });
373
- iReckon(sir, betaDepsAreEqual).asTo('beta got synced and graphs are equal').isGonnaBeTrue();
374
392
  }
375
393
  });
376
394
 
377
- await ifWeMight(sir, 'beta and deps synced', async () => {
395
+ await ifWeMight(sir, 'r2 beta and deps synced', async () => {
378
396
  // beta's full dep graph should exist on dest
379
397
  const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
380
- addrs: [betaAddr_v0],
398
+ addrs: [r2_beta_v0_source.addr],
381
399
  space: destSpace,
382
400
  });
383
401
  iReckon(sir, beta_dest).asTo('beta exists in dest').isGonnaBeTruthy();
384
402
  if (beta_dest) {
385
- const depGraph_beta_source = await getDependencyGraph({ ibGib: beta_v0_source, space: sourceSpace });
386
- if (Object.keys(depGraph_alpha_v1Andcommon).length === 0) {
387
- throw new Error(`(UNEXPECTED) depGraph_testRootAndV1Common empty? (E: 39b4d855ffa65476084b4123786da826)`);
388
- }
389
- const depGraph_beta_dest = await getDependencyGraph({ ibGib: beta_dest, space: destSpace });
403
+ const depGraph_beta_source = await getDependencyGraph({
404
+ ibGib: r2_beta_v0_source.ibGib,
405
+ live: true,
406
+ space: sourceSpace
407
+ });
408
+ const depGraph_beta_dest = await getDependencyGraph({
409
+ ibGib: beta_dest,
410
+ live: true,
411
+ space: destSpace
412
+ });
390
413
  const betaDepsAreEqual = graphsAreEquivalent({
391
414
  graphA: depGraph_beta_source,
392
415
  graphB: depGraph_beta_dest,
@@ -403,4 +426,68 @@ await respecfully(sir, `Two different fields and rel8d`, async () => {
403
426
 
404
427
  });
405
428
 
429
+ // #endregion Round 2: Create Divergence (v2source vs v2dest) - "simultaneous" parallel edits
430
+
431
+ // #region Round 3: Multiple Timeline Conflicts
432
+
433
+ const _r3 = testTransformer.newRound({
434
+ name: 'r3_multiple_timeline_conflicts',
435
+ description: 'Both alpha AND beta have divergent edits on source vs dest - tests multiple simultaneous grafts'
436
+ });
437
+
438
+ // #region r3 source edits
439
+
440
+ if (logalot) { console.log(`${lc} Creating r3 divergent edits on source...`); }
441
+
442
+ // Source: Mutate alpha fieldC, mutate beta betaFieldA
443
+ // TODO: Add source edits here
444
+
445
+ // #endregion r3 source edits
446
+
447
+ // #region r3 dest edits
448
+
449
+ if (logalot) { console.log(`${lc} Creating r3 divergent edits on dest...`); }
450
+
451
+ // Dest: Mutate alpha fieldD, mutate beta betaFieldB
452
+ // TODO: Add dest edits here
453
+
454
+ // #endregion r3 dest edits
455
+
456
+ await respecfully(sir, `r3 verify pre`, async () => {
457
+ // TODO: Verify pre-sync state
458
+ await ifWeMight(sir, 'source has divergent alpha and beta', async () => {
459
+ // Verify source has its versions
460
+ });
461
+
462
+ await ifWeMight(sir, 'dest has divergent alpha and beta', async () => {
463
+ // Verify dest has its versions
464
+ });
465
+ });
466
+
467
+ if (logalot) { console.log(`${lc} Running r3 Sync...`); }
468
+
469
+ // TODO: Add sync operation here
470
+ // const r3_syncSaga = await senderCoordinator.sync({...});
471
+
472
+ await respecfully(sir, `r3 verify post`, async () => {
473
+ // TODO: Verify post-sync state
474
+
475
+ await ifWeMight(sir, 'r3 alpha merge has both edits', async () => {
476
+ // Verify alpha has fieldC (source) AND fieldD (dest)
477
+ });
478
+
479
+ await ifWeMight(sir, 'r3 beta merge has both edits', async () => {
480
+ // Verify beta has betaFieldA (source) AND betaFieldB (dest)
481
+ });
482
+
483
+ await ifWeMight(sir, 'r3 alpha and deps synced', async () => {
484
+ // Verify alpha's full dep graph on both spaces
485
+ });
486
+
487
+ await ifWeMight(sir, 'r3 beta and deps synced', async () => {
488
+ // Verify beta's full dep graph on both spaces
489
+ });
490
+ });
491
+
492
+ // #endregion Round 3: Multiple Timeline Conflicts
406
493
  });