@ibgib/core-gib 0.1.20 → 0.1.21
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/graft-info/graft-info-constants.d.mts +5 -0
- package/dist/sync/graft-info/graft-info-constants.d.mts.map +1 -0
- package/dist/sync/graft-info/graft-info-constants.mjs +5 -0
- package/dist/sync/graft-info/graft-info-constants.mjs.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.d.mts +49 -0
- package/dist/sync/graft-info/graft-info-helpers.d.mts.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.mjs +236 -0
- package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.d.mts +2 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.d.mts.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs +70 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -0
- package/dist/sync/graft-info/graft-info-types.d.mts +31 -0
- package/dist/sync/{merge-info/merge-info-types.d.mts.map → graft-info/graft-info-types.d.mts.map} +1 -1
- package/dist/sync/graft-info/graft-info-types.mjs +2 -0
- package/dist/sync/graft-info/graft-info-types.mjs.map +1 -0
- package/dist/sync/strategies/conflict-optimistic.d.mts +1 -1
- package/dist/sync/strategies/conflict-optimistic.d.mts.map +1 -1
- package/dist/sync/strategies/conflict-optimistic.mjs +10 -60
- package/dist/sync/strategies/conflict-optimistic.mjs.map +1 -1
- package/dist/sync/sync-conflict.respec.mjs +152 -33
- package/dist/sync/sync-conflict.respec.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +1 -3
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +0 -2
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts +5 -2
- package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs +70 -7
- package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +25 -18
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +508 -316
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -12
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/ibgib-foundations.md +20 -2
- package/package.json +1 -1
- package/src/sync/graft-info/graft-info-constants.mts +4 -0
- package/src/sync/graft-info/graft-info-helpers.mts +308 -0
- package/src/sync/graft-info/graft-info-helpers.respec.mts +83 -0
- package/src/sync/graft-info/graft-info-types.mts +33 -0
- package/src/sync/strategies/conflict-optimistic.mts +11 -70
- package/src/sync/sync-conflict.respec.mts +171 -35
- package/src/sync/sync-constants.mts +1 -4
- package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +85 -12
- package/src/sync/sync-saga-coordinator.mts +569 -338
- package/src/sync/sync-saga-message/sync-saga-message-helpers.mts +2 -0
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +0 -11
- package/test_output.log +0 -0
- package/tmp.md +43 -2
- package/dist/sync/merge-info/merge-info-constants.d.mts +0 -2
- package/dist/sync/merge-info/merge-info-constants.d.mts.map +0 -1
- package/dist/sync/merge-info/merge-info-constants.mjs +0 -2
- package/dist/sync/merge-info/merge-info-constants.mjs.map +0 -1
- package/dist/sync/merge-info/merge-info-helpers.d.mts +0 -51
- package/dist/sync/merge-info/merge-info-helpers.d.mts.map +0 -1
- package/dist/sync/merge-info/merge-info-helpers.mjs +0 -92
- package/dist/sync/merge-info/merge-info-helpers.mjs.map +0 -1
- package/dist/sync/merge-info/merge-info-helpers.respec.d.mts +0 -2
- package/dist/sync/merge-info/merge-info-helpers.respec.d.mts.map +0 -1
- package/dist/sync/merge-info/merge-info-helpers.respec.mjs +0 -32
- package/dist/sync/merge-info/merge-info-helpers.respec.mjs.map +0 -1
- package/dist/sync/merge-info/merge-info-types.d.mts +0 -26
- package/dist/sync/merge-info/merge-info-types.mjs +0 -2
- package/dist/sync/merge-info/merge-info-types.mjs.map +0 -1
- package/src/sync/merge-info/merge-info-constants.mts +0 -1
- package/src/sync/merge-info/merge-info-helpers.mts +0 -134
- package/src/sync/merge-info/merge-info-helpers.respec.mts +0 -41
- package/src/sync/merge-info/merge-info-types.mts +0 -28
|
@@ -103,9 +103,7 @@ export class SyncSagaCoordinator {
|
|
|
103
103
|
const sessionIdentity = useSessionIdentity
|
|
104
104
|
? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
|
|
105
105
|
: undefined;
|
|
106
|
-
if (logalot) {
|
|
107
|
-
console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`);
|
|
108
|
-
}
|
|
106
|
+
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
109
107
|
// 3. CREATE INITIAL FRAME (Stage.init)
|
|
110
108
|
const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
|
|
111
109
|
sagaId,
|
|
@@ -266,9 +264,7 @@ export class SyncSagaCoordinator {
|
|
|
266
264
|
});
|
|
267
265
|
}
|
|
268
266
|
// B. Transmit
|
|
269
|
-
if (logalot) {
|
|
270
|
-
console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`);
|
|
271
|
-
}
|
|
267
|
+
// if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
|
|
272
268
|
updates$.next(requestCtx);
|
|
273
269
|
const responseCtx = await peer.witness(requestCtx);
|
|
274
270
|
// C. Handle Response
|
|
@@ -326,7 +322,8 @@ export class SyncSagaCoordinator {
|
|
|
326
322
|
const result = await this.handleSagaFrame({
|
|
327
323
|
sagaIbGib: remoteFrame,
|
|
328
324
|
srcGraph,
|
|
329
|
-
|
|
325
|
+
destSpace: localSpace, // Query existing data from localSpace (Source)
|
|
326
|
+
tempSpace: tempSpace, // Transaction space for saga frames
|
|
330
327
|
identity: sessionIdentity,
|
|
331
328
|
metaspace
|
|
332
329
|
});
|
|
@@ -345,34 +342,75 @@ export class SyncSagaCoordinator {
|
|
|
345
342
|
*/
|
|
346
343
|
async getKnowledgeVector({ space, metaspace, domainIbGibs, tjpAddrs, }) {
|
|
347
344
|
const lc = `${this.lc}[${this.getKnowledgeVector.name}]`;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
345
|
+
try {
|
|
346
|
+
if (logalot) {
|
|
347
|
+
console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`);
|
|
348
|
+
}
|
|
349
|
+
console.dir(space);
|
|
350
|
+
if (!(domainIbGibs && domainIbGibs.length > 0) &&
|
|
351
|
+
!(tjpAddrs && tjpAddrs.length > 0)) {
|
|
352
|
+
throw new Error(`(UNEXPECTED) domainIbGibs and tjpAddrs falsy/empty? we need one or the other (E: f674285111c8648398cd79d8c08ec826)`);
|
|
353
|
+
}
|
|
354
|
+
if ((domainIbGibs && domainIbGibs.length > 0) &&
|
|
355
|
+
(tjpAddrs && tjpAddrs.length > 0)) {
|
|
356
|
+
throw new Error(`(UNEXPECTED) both domainIbGibs and tjpAddrs truthy? only pass in one or the other. (E: f674285111c8648398cd79d8c08ec826)`);
|
|
357
|
+
}
|
|
358
|
+
let tjps = [];
|
|
359
|
+
if (tjpAddrs) {
|
|
360
|
+
tjps = tjpAddrs;
|
|
361
|
+
}
|
|
362
|
+
else if (domainIbGibs && domainIbGibs.length > 0) {
|
|
363
|
+
// Extract TJPs from domain Ibgibs
|
|
364
|
+
if (logalot) {
|
|
365
|
+
console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`);
|
|
366
|
+
}
|
|
367
|
+
const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
|
|
368
|
+
if (logalot) {
|
|
369
|
+
console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`);
|
|
370
|
+
}
|
|
371
|
+
if (logalot) {
|
|
372
|
+
console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`);
|
|
373
|
+
}
|
|
374
|
+
const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
|
|
375
|
+
const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
|
|
376
|
+
if (logalot) {
|
|
377
|
+
console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`);
|
|
378
|
+
}
|
|
379
|
+
tjps = Object.keys(timelineMap);
|
|
380
|
+
if (logalot) {
|
|
381
|
+
console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// No info provided. Return empty? Or throw?
|
|
386
|
+
// User test context implied "everything", but implementation requires scope.
|
|
387
|
+
console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
|
|
388
|
+
return {};
|
|
389
|
+
}
|
|
390
|
+
if (tjps.length === 0) {
|
|
391
|
+
return {};
|
|
392
|
+
}
|
|
393
|
+
if (logalot) {
|
|
394
|
+
console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`);
|
|
395
|
+
}
|
|
396
|
+
const res = await getLatestAddrs({ space, tjpAddrs: tjps });
|
|
397
|
+
if (!res.data || !res.data.latestAddrsMap) {
|
|
398
|
+
throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
|
|
399
|
+
}
|
|
400
|
+
if (logalot) {
|
|
401
|
+
console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`);
|
|
402
|
+
}
|
|
403
|
+
return res.data.latestAddrsMap;
|
|
367
404
|
}
|
|
368
|
-
|
|
369
|
-
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
407
|
+
throw error;
|
|
370
408
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
409
|
+
finally {
|
|
410
|
+
if (logalot) {
|
|
411
|
+
console.log(`${lc} complete.`);
|
|
412
|
+
}
|
|
374
413
|
}
|
|
375
|
-
return res.data.latestAddrsMap;
|
|
376
414
|
}
|
|
377
415
|
async analyzeTimelines({ domainIbGibs, space, }) {
|
|
378
416
|
const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
|
|
@@ -427,14 +465,16 @@ export class SyncSagaCoordinator {
|
|
|
427
465
|
const tip = timeline.at(-1);
|
|
428
466
|
initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
429
467
|
});
|
|
468
|
+
if (logalot) {
|
|
469
|
+
console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
|
|
470
|
+
console.log(`${lc} initData.stage: ${initData.stage}`);
|
|
471
|
+
}
|
|
430
472
|
const initStone = await this.createSyncMsgStone({
|
|
431
473
|
data: initData,
|
|
432
474
|
space: tempSpace,
|
|
433
475
|
metaspace
|
|
434
476
|
});
|
|
435
|
-
if (logalot) {
|
|
436
|
-
console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`);
|
|
437
|
-
}
|
|
477
|
+
// if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
|
|
438
478
|
const sagaFrame = await this.evolveSyncSagaIbGib({
|
|
439
479
|
msgStones: [initStone],
|
|
440
480
|
identity: sessionIdentity,
|
|
@@ -442,9 +482,14 @@ export class SyncSagaCoordinator {
|
|
|
442
482
|
metaspace,
|
|
443
483
|
conflictStrategy,
|
|
444
484
|
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
485
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
486
|
+
await this.ensureSagaFrameInBothSpaces({
|
|
487
|
+
frame: sagaFrame,
|
|
488
|
+
destSpace: localSpace, // localSpace is the Sender's destSpace
|
|
489
|
+
tempSpace,
|
|
490
|
+
metaspace
|
|
491
|
+
});
|
|
492
|
+
// if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
448
493
|
return { sagaFrame, srcGraph };
|
|
449
494
|
}
|
|
450
495
|
catch (error) {
|
|
@@ -470,7 +515,7 @@ export class SyncSagaCoordinator {
|
|
|
470
515
|
* * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
|
|
471
516
|
* * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
|
|
472
517
|
*/
|
|
473
|
-
async handleSagaFrame({ sagaIbGib, srcGraph,
|
|
518
|
+
async handleSagaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, identity, identitySecret, metaspace, }) {
|
|
474
519
|
const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
|
|
475
520
|
try {
|
|
476
521
|
if (logalot) {
|
|
@@ -483,21 +528,19 @@ export class SyncSagaCoordinator {
|
|
|
483
528
|
console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
|
|
484
529
|
}
|
|
485
530
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
486
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
531
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
487
532
|
if (logalot) {
|
|
488
533
|
console.log(`${lc} handling frame stage: ${stage}`);
|
|
489
534
|
}
|
|
490
535
|
switch (stage) {
|
|
491
536
|
case SyncStage.init:
|
|
492
|
-
return await this.handleInitFrame({ sagaIbGib, messageData, metaspace,
|
|
537
|
+
return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
|
|
493
538
|
case SyncStage.ack:
|
|
494
|
-
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace,
|
|
539
|
+
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
|
|
495
540
|
case SyncStage.delta:
|
|
496
|
-
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace,
|
|
541
|
+
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
|
|
497
542
|
case SyncStage.commit:
|
|
498
|
-
return await this.handleCommitFrame({ sagaIbGib, metaspace,
|
|
499
|
-
case SyncStage.conflict:
|
|
500
|
-
return await this.handleConflictFrame({ sagaIbGib, metaspace, space, });
|
|
543
|
+
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
|
|
501
544
|
default:
|
|
502
545
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
503
546
|
}
|
|
@@ -525,8 +568,9 @@ export class SyncSagaCoordinator {
|
|
|
525
568
|
* 3. Identifies what Receiver needs (`deltaReqAddrs`).
|
|
526
569
|
* 4. Returns an `Ack` frame containing these lists.
|
|
527
570
|
*/
|
|
528
|
-
async handleInitFrame({ sagaIbGib, messageData,
|
|
571
|
+
async handleInitFrame({ sagaIbGib, messageData, destSpace, tempSpace, metaspace, identity, identitySecret, }) {
|
|
529
572
|
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
573
|
+
console.log(`${lc} [TEST DEBUG] Received destSpace: ${destSpace.data?.name || destSpace.ib} (uuid: ${destSpace.data?.uuid || '[no uuid]'})`);
|
|
530
574
|
if (logalot) {
|
|
531
575
|
console.log(`${lc} starting...`);
|
|
532
576
|
}
|
|
@@ -535,9 +579,7 @@ export class SyncSagaCoordinator {
|
|
|
535
579
|
if (initData.stage !== SyncStage.init) {
|
|
536
580
|
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
|
|
537
581
|
}
|
|
538
|
-
if (logalot) {
|
|
539
|
-
console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`);
|
|
540
|
-
}
|
|
582
|
+
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
541
583
|
if (!initData || !initData.knowledgeVector) {
|
|
542
584
|
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
543
585
|
}
|
|
@@ -554,7 +596,7 @@ export class SyncSagaCoordinator {
|
|
|
554
596
|
console.log(`${lc} processing stones: ${stones.length}`);
|
|
555
597
|
}
|
|
556
598
|
// Check if we have these stones
|
|
557
|
-
const resStones = await getFromSpace({ space, addrs: stones });
|
|
599
|
+
const resStones = await getFromSpace({ space: destSpace, addrs: stones });
|
|
558
600
|
const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
|
|
559
601
|
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
560
602
|
if (logalot) {
|
|
@@ -572,6 +614,7 @@ export class SyncSagaCoordinator {
|
|
|
572
614
|
console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
|
|
573
615
|
}
|
|
574
616
|
const remoteTjps = Object.keys(remoteKV);
|
|
617
|
+
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
575
618
|
if (logalot) {
|
|
576
619
|
console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
|
|
577
620
|
}
|
|
@@ -580,7 +623,7 @@ export class SyncSagaCoordinator {
|
|
|
580
623
|
if (remoteTjps.length > 0) {
|
|
581
624
|
// Batch get latest addrs for the TJPs
|
|
582
625
|
const resGetLatestAddrs = await getLatestAddrs({
|
|
583
|
-
space,
|
|
626
|
+
space: destSpace,
|
|
584
627
|
tjpAddrs: remoteTjps,
|
|
585
628
|
});
|
|
586
629
|
if (!resGetLatestAddrs.data) {
|
|
@@ -590,6 +633,7 @@ export class SyncSagaCoordinator {
|
|
|
590
633
|
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
|
|
591
634
|
}
|
|
592
635
|
localKV = resGetLatestAddrs.data.latestAddrsMap;
|
|
636
|
+
console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
|
|
593
637
|
if (logalot) {
|
|
594
638
|
console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
|
|
595
639
|
}
|
|
@@ -600,21 +644,25 @@ export class SyncSagaCoordinator {
|
|
|
600
644
|
const localAddr = localKV[tjp];
|
|
601
645
|
if (!localAddr) {
|
|
602
646
|
// We (Receiver) don't have this timeline. Request it.
|
|
647
|
+
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
603
648
|
deltaReqAddrs.push(remoteAddr);
|
|
604
649
|
continue;
|
|
605
650
|
}
|
|
606
651
|
if (localAddr === remoteAddr) {
|
|
607
652
|
// Synced
|
|
653
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
|
|
608
654
|
continue;
|
|
609
655
|
}
|
|
656
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
610
657
|
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
611
658
|
// (Sender has older version, Receiver has newer) -> Receiver Offers Push
|
|
612
659
|
const isRemoteInPast = await isPastFrame({
|
|
613
660
|
olderAddr: remoteAddr,
|
|
614
661
|
newerAddr: localAddr,
|
|
615
|
-
space,
|
|
662
|
+
space: destSpace,
|
|
616
663
|
});
|
|
617
664
|
if (isRemoteInPast) {
|
|
665
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
618
666
|
pushOfferAddrs.push(localAddr);
|
|
619
667
|
}
|
|
620
668
|
else {
|
|
@@ -624,14 +672,16 @@ export class SyncSagaCoordinator {
|
|
|
624
672
|
const isLocalInPast = await isPastFrame({
|
|
625
673
|
olderAddr: localAddr,
|
|
626
674
|
newerAddr: remoteAddr,
|
|
627
|
-
space,
|
|
675
|
+
space: destSpace,
|
|
628
676
|
});
|
|
629
677
|
if (isLocalInPast) {
|
|
630
678
|
// Fast-Forward: We update to remote's tip.
|
|
679
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
|
|
631
680
|
deltaReqAddrs.push(remoteAddr);
|
|
632
681
|
}
|
|
633
682
|
else {
|
|
634
683
|
// DIVERGENCE: Both have changes the other doesn't know about.
|
|
684
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
|
|
635
685
|
if (conflictStrategy === 'abort') {
|
|
636
686
|
// Abort Strategy: We will treat this as terminal.
|
|
637
687
|
// But for Unified Ack, we just mark it terminal in the list?
|
|
@@ -656,7 +706,7 @@ export class SyncSagaCoordinator {
|
|
|
656
706
|
// We need to inspect the 'past' of the local tip.
|
|
657
707
|
// We need the ACTUAL object to get the past.
|
|
658
708
|
// We have localAddr.
|
|
659
|
-
const resLocalTip = await getFromSpace({ space, addr: localAddr });
|
|
709
|
+
const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr });
|
|
660
710
|
const localTip = resLocalTip.ibGibs?.[0];
|
|
661
711
|
if (!localTip) {
|
|
662
712
|
throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
|
|
@@ -699,7 +749,7 @@ export class SyncSagaCoordinator {
|
|
|
699
749
|
// 2. Add Push Offers (Missing in Local)
|
|
700
750
|
// Check if we have them. If not, ask for them.
|
|
701
751
|
for (const addr of pushOfferAddrs) {
|
|
702
|
-
const hasIt = await getFromSpace({ addr, space });
|
|
752
|
+
const hasIt = await getFromSpace({ addr, space: destSpace });
|
|
703
753
|
if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
|
|
704
754
|
// If we don't have it, we put it in `deltaReqAddrs` of the Ack.
|
|
705
755
|
deltaReqAddrs.push(addr);
|
|
@@ -715,7 +765,7 @@ export class SyncSagaCoordinator {
|
|
|
715
765
|
for (const tjp of remoteTjps) {
|
|
716
766
|
const localAddr = localKV[tjp];
|
|
717
767
|
if (localAddr) {
|
|
718
|
-
const res = await getFromSpace({ addr: localAddr, space });
|
|
768
|
+
const res = await getFromSpace({ addr: localAddr, space: destSpace });
|
|
719
769
|
if (res.success && res.ibGibs?.[0]) {
|
|
720
770
|
const ibGib = res.ibGibs[0];
|
|
721
771
|
const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
|
|
@@ -732,7 +782,7 @@ export class SyncSagaCoordinator {
|
|
|
732
782
|
// We can't really know if it's covered easily without resolving.
|
|
733
783
|
// But if we don't have it (requesting), we won't find it here anyway.
|
|
734
784
|
// If we DO have it (push offer), we might find it.
|
|
735
|
-
const res = await getFromSpace({ addr, space });
|
|
785
|
+
const res = await getFromSpace({ addr, space: destSpace });
|
|
736
786
|
if (res.success && res.ibGibs?.[0]) {
|
|
737
787
|
const ibGib = res.ibGibs[0];
|
|
738
788
|
const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
|
|
@@ -767,10 +817,11 @@ export class SyncSagaCoordinator {
|
|
|
767
817
|
deltaReqAddrs,
|
|
768
818
|
pushOfferAddrs,
|
|
769
819
|
knowledgeVector,
|
|
820
|
+
conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
|
|
770
821
|
};
|
|
771
822
|
const ackStone = await this.createSyncMsgStone({
|
|
772
823
|
data: ackData,
|
|
773
|
-
space,
|
|
824
|
+
space: tempSpace,
|
|
774
825
|
metaspace,
|
|
775
826
|
});
|
|
776
827
|
if (logalot) {
|
|
@@ -781,12 +832,12 @@ export class SyncSagaCoordinator {
|
|
|
781
832
|
prevSagaIbGib: sagaIbGib,
|
|
782
833
|
msgStones: [ackStone],
|
|
783
834
|
identity,
|
|
784
|
-
space,
|
|
835
|
+
space: tempSpace,
|
|
785
836
|
metaspace,
|
|
786
837
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
838
|
+
// IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
|
|
839
|
+
await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
|
|
840
|
+
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
790
841
|
return { frame: ackFrame };
|
|
791
842
|
}
|
|
792
843
|
/**
|
|
@@ -801,240 +852,318 @@ export class SyncSagaCoordinator {
|
|
|
801
852
|
*
|
|
802
853
|
* Returns a `Delta` frame.
|
|
803
854
|
*/
|
|
804
|
-
async handleAckFrame({ sagaIbGib, srcGraph,
|
|
855
|
+
async handleAckFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
|
|
805
856
|
const lc = `${this.lc}[${this.handleAckFrame.name}]`;
|
|
806
|
-
|
|
807
|
-
console.log(`${lc} starting...`);
|
|
808
|
-
}
|
|
809
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
810
|
-
const ackData = messageData;
|
|
811
|
-
if (!ackData) {
|
|
812
|
-
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
813
|
-
}
|
|
814
|
-
if (ackData.stage !== SyncStage.ack) {
|
|
815
|
-
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
816
|
-
}
|
|
817
|
-
if (logalot) {
|
|
818
|
-
console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
|
|
819
|
-
}
|
|
820
|
-
// 1. Check for Conflicts
|
|
821
|
-
const conflicts = ackData.conflicts || [];
|
|
822
|
-
const terminalConflicts = conflicts.filter(c => c.terminal);
|
|
823
|
-
if (terminalConflicts.length > 0) {
|
|
824
|
-
console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
|
|
825
|
-
// Terminal failure. Sender should probably Commit(Fail) or just Abort.
|
|
826
|
-
// For now, throw to trigger abort.
|
|
827
|
-
throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
|
|
828
|
-
}
|
|
829
|
-
const optimisticConflicts = conflicts.filter(c => !c.terminal);
|
|
830
|
-
const mergeDeltaReqs = []; // Additional requests for merging
|
|
831
|
-
if (optimisticConflicts.length > 0) {
|
|
857
|
+
try {
|
|
832
858
|
if (logalot) {
|
|
833
|
-
console.log(`${lc}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
//
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
// We need
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
859
|
+
console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
|
|
860
|
+
}
|
|
861
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
862
|
+
const ackData = messageData;
|
|
863
|
+
if (!ackData) {
|
|
864
|
+
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
865
|
+
}
|
|
866
|
+
if (ackData.stage !== SyncStage.ack) {
|
|
867
|
+
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
868
|
+
}
|
|
869
|
+
if (logalot) {
|
|
870
|
+
console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
|
|
871
|
+
}
|
|
872
|
+
// 1. Check for Conflicts
|
|
873
|
+
const conflicts = ackData.conflicts || [];
|
|
874
|
+
console.log(`${lc} [CONFLICT DEBUG] Received conflicts from Ack: ${conflicts.length}`);
|
|
875
|
+
if (conflicts.length > 0) {
|
|
876
|
+
console.log(`${lc} [CONFLICT DEBUG] Conflicts detail: ${JSON.stringify(conflicts, null, 2)}`);
|
|
877
|
+
}
|
|
878
|
+
const terminalConflicts = conflicts.filter(c => c.terminal);
|
|
879
|
+
if (terminalConflicts.length > 0) {
|
|
880
|
+
console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
|
|
881
|
+
// Terminal failure. Sender should probably Commit(Fail) or just Abort.
|
|
882
|
+
// For now, throw to trigger abort.
|
|
883
|
+
throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
|
|
884
|
+
}
|
|
885
|
+
const optimisticConflicts = conflicts.filter(c => !c.terminal);
|
|
886
|
+
const mergeDeltaReqs = []; // Additional requests for merging
|
|
887
|
+
if (optimisticConflicts.length > 0) {
|
|
888
|
+
console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
|
|
889
|
+
// We need to resolve these.
|
|
890
|
+
// Strategy:
|
|
891
|
+
// 1. Analyze Divergence (Sender vs Receiver)
|
|
892
|
+
// 2. Identify missing data needed for merge (Receiver's unique frames)
|
|
893
|
+
// 3. Request that data (as Delta Reqs)
|
|
894
|
+
// 4. (Later in Delta Phase) Perform Merge.
|
|
895
|
+
// BUT: The Delta Phase is usually generic "Send me these Addrs".
|
|
896
|
+
// If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
|
|
897
|
+
// wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
|
|
898
|
+
// We (Sender) are processing the Ack.
|
|
899
|
+
// We need to request data FROM Receiver.
|
|
900
|
+
// But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
|
|
901
|
+
// If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
|
|
902
|
+
// Or can we send a 'Delta Request' frame?
|
|
903
|
+
// Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
|
|
904
|
+
// If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
|
|
905
|
+
// Or we just proceed to Delta (sending what Receiver wants),
|
|
906
|
+
// AND we piggyback our own requests?
|
|
907
|
+
// OR: We treat the Conflict Resolution as a sub-saga or side-effect?
|
|
908
|
+
// SIMPLIFICATION for V1:
|
|
909
|
+
// If we need data to merge, we must get it.
|
|
910
|
+
// We are the Coordinator (Active). We can fetch from Peer immediately?
|
|
911
|
+
// `peer.pull(addr)`?
|
|
912
|
+
// Yes! The Coordinator has the `peer`.
|
|
913
|
+
// Let's analyze and pull immediately.
|
|
914
|
+
for (const conflict of optimisticConflicts) {
|
|
915
|
+
const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
916
|
+
// Sender History
|
|
917
|
+
// We need our own history for this timeline.
|
|
918
|
+
// We know the 'senderTip' (remoteAddr in Ack).
|
|
919
|
+
// Sender should verify it has this tip.
|
|
920
|
+
// Compute Diffs
|
|
921
|
+
// We need to find `receiverOnly` addrs.
|
|
922
|
+
// Receiver sent us `timelineAddrs` (Full History).
|
|
923
|
+
const receiverHistorySet = new Set(timelineAddrs);
|
|
924
|
+
// We need our execution context's history for this senderTip.
|
|
925
|
+
// We can fetch valid 'past' from space.
|
|
926
|
+
const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
|
|
927
|
+
const senderTipIbGib = resSenderTip.ibGibs?.[0];
|
|
928
|
+
if (!senderTipIbGib) {
|
|
929
|
+
throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
|
|
930
|
+
}
|
|
931
|
+
// Basic Diff: Find what Receiver has that we don't.
|
|
932
|
+
// Actually, we need to traverse OUR past to find commonality.
|
|
933
|
+
const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
|
|
934
|
+
const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
|
|
935
|
+
if (receiverOnlyAddrs.length > 0) {
|
|
936
|
+
console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
|
|
937
|
+
console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
|
|
938
|
+
// PULL these frames from Peer into Local Space
|
|
939
|
+
// (Validation: We trust peer for now / verification happens on put)
|
|
940
|
+
for (const addr of receiverOnlyAddrs) {
|
|
941
|
+
// This 'pull' is a sync-peer method?
|
|
942
|
+
// The Coordinator 'peer' passed in 'sync()' might be needed here?
|
|
943
|
+
// Wait, `handleAckFrame` doesn't have reference to `peer`?
|
|
944
|
+
// It only has `space`, `metaspace`.
|
|
945
|
+
// The `peer` is held by the `executeSagaLoop`.
|
|
946
|
+
// PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
|
|
947
|
+
// No, it's a method on Coordinator.
|
|
948
|
+
// But `executeSagaLoop` calls it.
|
|
949
|
+
// We might need to return "Requirements" to the loop?
|
|
950
|
+
// Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
|
|
951
|
+
// It returns the NEXT frame (Delta).
|
|
952
|
+
// If we need to fetch data, we are blocked.
|
|
953
|
+
// We can't easily "Pull" here without the Peer reference.
|
|
954
|
+
// OPTION A: Pass `peer` to `handleAckFrame`.
|
|
955
|
+
// OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
|
|
956
|
+
// Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
|
|
957
|
+
// No, Peer is ephemeral connection.
|
|
958
|
+
// Let's add `peer` to `handleAckFrame` signature?
|
|
959
|
+
// It breaks the pattern of just handling frame + space.
|
|
960
|
+
// ALTERNATIVE: Use the `Delta` frame to request data?
|
|
961
|
+
// `SyncSagaMessageDeltaData` has `requests?: string[]`.
|
|
962
|
+
// Sender sends Delta Frame.
|
|
963
|
+
// Does Receiver handle Delta Requests?
|
|
964
|
+
// `handleDeltaFrame` (Receiver) -> checks `requests`.
|
|
965
|
+
// YES.
|
|
966
|
+
// So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
|
|
967
|
+
// Receiver sees them, fetches them, and includes them in the Response (Commit?).
|
|
968
|
+
// Wait, Init->Ack->Delta->Commit.
|
|
969
|
+
// If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
|
|
970
|
+
// Unless Commit is not the end?
|
|
971
|
+
// Or we do a "Delta 2" loop?
|
|
972
|
+
// "Iterative Resolution Loop" from plan.
|
|
973
|
+
// If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
|
|
974
|
+
// Sender gets Commit. Sees data. Merges.
|
|
975
|
+
// Then Sender needs to Send the MERGE result.
|
|
976
|
+
// Needs another Push/Delta.
|
|
977
|
+
// REFINED FLOW:
|
|
978
|
+
// 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
|
|
979
|
+
// 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
|
|
980
|
+
// 3. Sender handles response -> Merges.
|
|
981
|
+
// 4. Sender sends Commit (containing Merge Frame).
|
|
982
|
+
// Issue: Current state machine is Init->Ack->Delta->Commit.
|
|
983
|
+
// We need to keep Saga open.
|
|
984
|
+
// If Sender sends Delta with requests, does it transition to Commit?
|
|
985
|
+
}
|
|
986
|
+
// Compute DELTA dependencies for each receiver-only frame
|
|
987
|
+
// Find LCA to determine what dependencies we already have
|
|
988
|
+
const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
|
|
989
|
+
console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
|
|
990
|
+
const skipAddrsSet = new Set();
|
|
991
|
+
if (lcaAddr) {
|
|
992
|
+
try {
|
|
993
|
+
const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
|
|
994
|
+
const lcaIbGib = lcaRes.ibGibs?.[0];
|
|
995
|
+
if (lcaIbGib) {
|
|
996
|
+
const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
|
|
997
|
+
if (lcaDeps)
|
|
998
|
+
Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
|
|
999
|
+
console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
catch (e) {
|
|
1003
|
+
console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
// For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
|
|
1007
|
+
for (const addr of receiverOnlyAddrs) {
|
|
1008
|
+
// Add the frame itself first
|
|
1009
|
+
if (!mergeDeltaReqs.includes(addr)) {
|
|
1010
|
+
mergeDeltaReqs.push(addr);
|
|
1011
|
+
}
|
|
1012
|
+
// Get the frame's delta dependencies (skip LCA's deps)
|
|
1013
|
+
try {
|
|
1014
|
+
const frameRes = await getFromSpace({ addr, space: destSpace });
|
|
1015
|
+
const frameIbGib = frameRes.ibGibs?.[0];
|
|
1016
|
+
if (frameIbGib) {
|
|
1017
|
+
// Get dependency graph, skipping all LCA dependencies
|
|
1018
|
+
const frameDeltaDeps = await getDependencyGraph({
|
|
1019
|
+
ibGib: frameIbGib,
|
|
1020
|
+
space: destSpace,
|
|
1021
|
+
skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
|
|
1022
|
+
});
|
|
1023
|
+
if (frameDeltaDeps) {
|
|
1024
|
+
// Add all delta dependencies (Object.keys gives us the addresses)
|
|
1025
|
+
Object.keys(frameDeltaDeps).forEach(depAddr => {
|
|
1026
|
+
if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
|
|
1027
|
+
mergeDeltaReqs.push(depAddr);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
catch (depError) {
|
|
1034
|
+
console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
|
|
884
1038
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
for (const addr of receiverOnlyAddrs) {
|
|
888
|
-
// This 'pull' is a sync-peer method?
|
|
889
|
-
// The Coordinator 'peer' passed in 'sync()' might be needed here?
|
|
890
|
-
// Wait, `handleAckFrame` doesn't have reference to `peer`?
|
|
891
|
-
// It only has `space`, `metaspace`.
|
|
892
|
-
// The `peer` is held by the `executeSagaLoop`.
|
|
893
|
-
// PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
|
|
894
|
-
// No, it's a method on Coordinator.
|
|
895
|
-
// But `executeSagaLoop` calls it.
|
|
896
|
-
// We might need to return "Requirements" to the loop?
|
|
897
|
-
// Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
|
|
898
|
-
// It returns the NEXT frame (Delta).
|
|
899
|
-
// If we need to fetch data, we are blocked.
|
|
900
|
-
// We can't easily "Pull" here without the Peer reference.
|
|
901
|
-
// OPTION A: Pass `peer` to `handleAckFrame`.
|
|
902
|
-
// OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
|
|
903
|
-
// Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
|
|
904
|
-
// No, Peer is ephemeral connection.
|
|
905
|
-
// Let's add `peer` to `handleAckFrame` signature?
|
|
906
|
-
// It breaks the pattern of just handling frame + space.
|
|
907
|
-
// ALTERNATIVE: Use the `Delta` frame to request data?
|
|
908
|
-
// `SyncSagaMessageDeltaData` has `requests?: string[]`.
|
|
909
|
-
// Sender sends Delta Frame.
|
|
910
|
-
// Does Receiver handle Delta Requests?
|
|
911
|
-
// `handleDeltaFrame` (Receiver) -> checks `requests`.
|
|
912
|
-
// YES.
|
|
913
|
-
// So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
|
|
914
|
-
// Receiver sees them, fetches them, and includes them in the Response (Commit?).
|
|
915
|
-
// Wait, Init->Ack->Delta->Commit.
|
|
916
|
-
// If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
|
|
917
|
-
// Unless Commit is not the end?
|
|
918
|
-
// Or we do a "Delta 2" loop?
|
|
919
|
-
// "Iterative Resolution Loop" from plan.
|
|
920
|
-
// If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
|
|
921
|
-
// Sender gets Commit. Sees data. Merges.
|
|
922
|
-
// Then Sender needs to Send the MERGE result.
|
|
923
|
-
// Needs another Push/Delta.
|
|
924
|
-
// REFINED FLOW:
|
|
925
|
-
// 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
|
|
926
|
-
// 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
|
|
927
|
-
// 3. Sender handles response -> Merges.
|
|
928
|
-
// 4. Sender sends Commit (containing Merge Frame).
|
|
929
|
-
// Issue: Current state machine is Init->Ack->Delta->Commit.
|
|
930
|
-
// We need to keep Saga open.
|
|
931
|
-
// If Sender sends Delta with requests, does it transition to Commit?
|
|
1039
|
+
else {
|
|
1040
|
+
console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
|
|
932
1041
|
}
|
|
933
|
-
mergeDeltaReqs.push(...receiverOnlyAddrs);
|
|
934
1042
|
}
|
|
1043
|
+
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
|
|
935
1044
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
// 2. Process Delta Requests (Push Payload)
|
|
949
|
-
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
950
|
-
const skipAddrs = new Set();
|
|
951
|
-
if (ackData.knowledgeVector) {
|
|
952
|
-
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
953
|
-
addrs.forEach(a => skipAddrs.add(a));
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
const payloadIbGibs = [];
|
|
957
|
-
// Gather all tips to sync first
|
|
958
|
-
const tipsToSync = [];
|
|
959
|
-
for (const addr of deltaReqAddrs) {
|
|
960
|
-
let ibGib = srcGraph[addr];
|
|
961
|
-
if (!ibGib) {
|
|
962
|
-
const res = await getFromSpace({ addr, space });
|
|
963
|
-
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
964
|
-
ibGib = res.ibGibs[0];
|
|
1045
|
+
else {
|
|
1046
|
+
console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
|
|
1047
|
+
}
|
|
1048
|
+
// 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
|
|
1049
|
+
const deltaReqAddrs = ackData.deltaReqAddrs || [];
|
|
1050
|
+
const pushOfferAddrs = ackData.pushOfferAddrs || [];
|
|
1051
|
+
// 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
|
|
1052
|
+
const pullReqAddrs = [];
|
|
1053
|
+
for (const addr of pushOfferAddrs) {
|
|
1054
|
+
const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
|
|
1055
|
+
if (!existing) {
|
|
1056
|
+
pullReqAddrs.push(addr);
|
|
965
1057
|
}
|
|
966
1058
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1059
|
+
// 2. Process Delta Requests (Push Payload)
|
|
1060
|
+
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
1061
|
+
const skipAddrs = new Set();
|
|
1062
|
+
if (ackData.knowledgeVector) {
|
|
1063
|
+
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
1064
|
+
addrs.forEach(a => skipAddrs.add(a));
|
|
1065
|
+
});
|
|
972
1066
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
// Always include the tip itself
|
|
983
|
-
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
984
|
-
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
985
|
-
// But detailed deps might be skipped.
|
|
986
|
-
// Get Graph with Skips
|
|
987
|
-
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
988
|
-
const deps = await getDependencyGraph({
|
|
989
|
-
ibGib: tip,
|
|
990
|
-
space,
|
|
991
|
-
skipAddrs: Array.from(skipAddrs)
|
|
992
|
-
});
|
|
993
|
-
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
994
|
-
let tipIncluded = false;
|
|
995
|
-
if (deps) {
|
|
996
|
-
Object.values(deps).forEach(d => {
|
|
997
|
-
const dAddr = getIbGibAddr({ ibGib: d });
|
|
998
|
-
if (!allDepsSet.has(dAddr)) {
|
|
999
|
-
allDepsSet.add(dAddr);
|
|
1000
|
-
payloadIbGibs.push(d);
|
|
1001
|
-
}
|
|
1002
|
-
if (dAddr === tipAddr) {
|
|
1003
|
-
tipIncluded = true;
|
|
1067
|
+
const payloadIbGibs = [];
|
|
1068
|
+
// Gather all tips to sync first
|
|
1069
|
+
const tipsToSync = [];
|
|
1070
|
+
for (const addr of deltaReqAddrs) {
|
|
1071
|
+
let ibGib = srcGraph[addr];
|
|
1072
|
+
if (!ibGib) {
|
|
1073
|
+
const res = await getFromSpace({ addr, space: destSpace });
|
|
1074
|
+
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1075
|
+
ibGib = res.ibGibs[0];
|
|
1004
1076
|
}
|
|
1005
|
-
}
|
|
1077
|
+
}
|
|
1078
|
+
if (ibGib) {
|
|
1079
|
+
tipsToSync.push(ibGib);
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
1083
|
+
}
|
|
1006
1084
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1085
|
+
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
1086
|
+
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
1087
|
+
// `getDependencyGraph` takes a single ibGib.
|
|
1088
|
+
// We can optimize by doing it for each tip and unioning the result?
|
|
1089
|
+
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
1090
|
+
// We will loop.
|
|
1091
|
+
const allDepsSet = new Set();
|
|
1092
|
+
for (const tip of tipsToSync) {
|
|
1093
|
+
// Always include the tip itself
|
|
1094
|
+
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
1095
|
+
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
1096
|
+
// But detailed deps might be skipped.
|
|
1097
|
+
// Get Graph with Skips
|
|
1098
|
+
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
1099
|
+
const deps = await getDependencyGraph({
|
|
1100
|
+
ibGib: tip,
|
|
1101
|
+
space: destSpace,
|
|
1102
|
+
skipAddrs: Array.from(skipAddrs)
|
|
1103
|
+
});
|
|
1104
|
+
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
1105
|
+
let tipIncluded = false;
|
|
1106
|
+
if (deps) {
|
|
1107
|
+
Object.values(deps).forEach(d => {
|
|
1108
|
+
const dAddr = getIbGibAddr({ ibGib: d });
|
|
1109
|
+
if (!allDepsSet.has(dAddr)) {
|
|
1110
|
+
allDepsSet.add(dAddr);
|
|
1111
|
+
payloadIbGibs.push(d);
|
|
1112
|
+
}
|
|
1113
|
+
if (dAddr === tipAddr) {
|
|
1114
|
+
tipIncluded = true;
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1010
1117
|
}
|
|
1011
|
-
if (!
|
|
1012
|
-
|
|
1013
|
-
|
|
1118
|
+
if (!tipIncluded && !skipAddrs.has(tipAddr)) {
|
|
1119
|
+
if (logalot) {
|
|
1120
|
+
console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
|
|
1121
|
+
}
|
|
1122
|
+
if (!allDepsSet.has(tipAddr)) {
|
|
1123
|
+
allDepsSet.add(tipAddr);
|
|
1124
|
+
payloadIbGibs.push(tip);
|
|
1125
|
+
}
|
|
1014
1126
|
}
|
|
1015
1127
|
}
|
|
1128
|
+
// 3. Create Delta Frame
|
|
1129
|
+
const sagaId = ackData.sagaId;
|
|
1130
|
+
const deltaData = {
|
|
1131
|
+
sagaId: sagaIbGib.data.uuid,
|
|
1132
|
+
stage: SyncStage.delta,
|
|
1133
|
+
payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
|
|
1134
|
+
requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
|
|
1135
|
+
};
|
|
1136
|
+
if (logalot) {
|
|
1137
|
+
console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`);
|
|
1138
|
+
}
|
|
1139
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
1140
|
+
data: deltaData,
|
|
1141
|
+
space: tempSpace,
|
|
1142
|
+
metaspace,
|
|
1143
|
+
});
|
|
1144
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1145
|
+
prevSagaIbGib: sagaIbGib,
|
|
1146
|
+
msgStones: [deltaStone],
|
|
1147
|
+
identity,
|
|
1148
|
+
space: tempSpace,
|
|
1149
|
+
metaspace,
|
|
1150
|
+
});
|
|
1151
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1152
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1153
|
+
if (logalot) {
|
|
1154
|
+
console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
|
|
1155
|
+
}
|
|
1156
|
+
return { frame: deltaFrame, payloadIbGibs };
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1160
|
+
throw error;
|
|
1161
|
+
}
|
|
1162
|
+
finally {
|
|
1163
|
+
if (logalot) {
|
|
1164
|
+
console.log(`${lc} complete.`);
|
|
1165
|
+
}
|
|
1016
1166
|
}
|
|
1017
|
-
// 3. Create Delta Frame
|
|
1018
|
-
const sagaId = ackData.sagaId;
|
|
1019
|
-
const deltaData = {
|
|
1020
|
-
sagaId: sagaIbGib.data.uuid,
|
|
1021
|
-
stage: SyncStage.delta,
|
|
1022
|
-
payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
|
|
1023
|
-
requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
|
|
1024
|
-
};
|
|
1025
|
-
const deltaStone = await this.createSyncMsgStone({
|
|
1026
|
-
data: deltaData,
|
|
1027
|
-
space,
|
|
1028
|
-
metaspace,
|
|
1029
|
-
});
|
|
1030
|
-
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1031
|
-
prevSagaIbGib: sagaIbGib,
|
|
1032
|
-
msgStones: [deltaStone],
|
|
1033
|
-
identity,
|
|
1034
|
-
space,
|
|
1035
|
-
metaspace,
|
|
1036
|
-
});
|
|
1037
|
-
return { frame: deltaFrame, payloadIbGibs };
|
|
1038
1167
|
}
|
|
1039
1168
|
/**
|
|
1040
1169
|
* Handles the `Delta` frame.
|
|
@@ -1046,12 +1175,12 @@ export class SyncSagaCoordinator {
|
|
|
1046
1175
|
* 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
|
|
1047
1176
|
* 3. **Completion**: If no more requests, transitions to `Commit`.
|
|
1048
1177
|
*/
|
|
1049
|
-
async handleDeltaFrame({ sagaIbGib, srcGraph,
|
|
1178
|
+
async handleDeltaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
|
|
1050
1179
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1051
1180
|
if (logalot) {
|
|
1052
1181
|
console.log(`${lc} starting...`);
|
|
1053
1182
|
}
|
|
1054
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
1183
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
1055
1184
|
const deltaData = messageData;
|
|
1056
1185
|
if (!deltaData) {
|
|
1057
1186
|
throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
@@ -1062,6 +1191,7 @@ export class SyncSagaCoordinator {
|
|
|
1062
1191
|
if (logalot) {
|
|
1063
1192
|
console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`);
|
|
1064
1193
|
}
|
|
1194
|
+
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1065
1195
|
const payloadAddrs = deltaData.payloadAddrs || [];
|
|
1066
1196
|
const peerRequests = deltaData.requests || [];
|
|
1067
1197
|
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
@@ -1075,7 +1205,7 @@ export class SyncSagaCoordinator {
|
|
|
1075
1205
|
// The `handleDeltaFrame` contract assumes data is reachable in `space`.
|
|
1076
1206
|
const res = await getFromSpace({
|
|
1077
1207
|
addrs: payloadAddrs,
|
|
1078
|
-
space,
|
|
1208
|
+
space: tempSpace, // Incoming data is in tempSpace
|
|
1079
1209
|
});
|
|
1080
1210
|
if (res.ibGibs) {
|
|
1081
1211
|
receivedPayloadIbGibs.push(...res.ibGibs);
|
|
@@ -1086,47 +1216,80 @@ export class SyncSagaCoordinator {
|
|
|
1086
1216
|
console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
|
|
1087
1217
|
}
|
|
1088
1218
|
}
|
|
1089
|
-
// 2. Fulfill Peer Requests (Outgoing Payload)
|
|
1219
|
+
// 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
|
|
1090
1220
|
const outgoingPayload = [];
|
|
1221
|
+
const outgoingAddrsSet = new Set(); // Track what we've added
|
|
1222
|
+
console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
|
|
1091
1223
|
for (const addr of peerRequests) {
|
|
1224
|
+
// Get the requested ibGib
|
|
1092
1225
|
let ibGib = srcGraph[addr];
|
|
1093
1226
|
if (!ibGib) {
|
|
1094
|
-
const res = await getFromSpace({ addr, space });
|
|
1227
|
+
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1095
1228
|
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1096
1229
|
ibGib = res.ibGibs[0];
|
|
1097
1230
|
}
|
|
1098
1231
|
}
|
|
1099
1232
|
if (ibGib) {
|
|
1100
|
-
|
|
1233
|
+
// Add the requested ibGib itself
|
|
1234
|
+
const ibGibAddr = getIbGibAddr({ ibGib });
|
|
1235
|
+
if (!outgoingAddrsSet.has(ibGibAddr)) {
|
|
1236
|
+
outgoingPayload.push(ibGib);
|
|
1237
|
+
outgoingAddrsSet.add(ibGibAddr);
|
|
1238
|
+
}
|
|
1239
|
+
// Expand to include full dependency graph for this ibGib
|
|
1240
|
+
// (Receiver needs all deps to properly process/merge)
|
|
1241
|
+
try {
|
|
1242
|
+
const deps = await getDependencyGraph({
|
|
1243
|
+
ibGib,
|
|
1244
|
+
space: destSpace,
|
|
1245
|
+
});
|
|
1246
|
+
if (deps) {
|
|
1247
|
+
Object.values(deps).forEach(depIbGib => {
|
|
1248
|
+
const depAddr = getIbGibAddr({ ibGib: depIbGib });
|
|
1249
|
+
if (!outgoingAddrsSet.has(depAddr)) {
|
|
1250
|
+
outgoingPayload.push(depIbGib);
|
|
1251
|
+
outgoingAddrsSet.add(depAddr);
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
catch (depError) {
|
|
1257
|
+
console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1258
|
+
}
|
|
1101
1259
|
}
|
|
1102
1260
|
else {
|
|
1103
1261
|
console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
|
|
1104
1262
|
}
|
|
1105
1263
|
}
|
|
1264
|
+
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1106
1265
|
// 3. Execute Merges (If applicable)
|
|
1107
1266
|
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1108
1267
|
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1109
1268
|
// Optimization: Do this only if we received payloads.
|
|
1110
1269
|
const mergeResultIbGibs = [];
|
|
1270
|
+
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1111
1271
|
if (receivedPayloadIbGibs.length > 0) {
|
|
1272
|
+
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1112
1273
|
// Find the Ack frame in history to get conflicts
|
|
1113
1274
|
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1114
1275
|
// V1 timelines carry full history in `past`.
|
|
1115
1276
|
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1277
|
+
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1116
1278
|
let ackData;
|
|
1117
1279
|
if (pastAddrs.length > 0) {
|
|
1118
1280
|
// Batch fetch all past frames
|
|
1119
|
-
const resPast = await getFromSpace({ addrs: pastAddrs, space });
|
|
1281
|
+
const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
|
|
1120
1282
|
if (resPast.success && resPast.ibGibs) {
|
|
1121
1283
|
// Iterate backwards (most recent first) to find the latest Ack
|
|
1122
1284
|
for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
|
|
1123
1285
|
const pastFrame = resPast.ibGibs[i];
|
|
1124
1286
|
const messageStone = await getSyncSagaMessageFromFrame({
|
|
1125
1287
|
frameIbGib: pastFrame,
|
|
1126
|
-
space
|
|
1288
|
+
space: tempSpace
|
|
1127
1289
|
});
|
|
1128
1290
|
if (messageStone?.data?.stage === SyncStage.ack) {
|
|
1129
1291
|
ackData = messageStone.data;
|
|
1292
|
+
console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
|
|
1130
1293
|
break;
|
|
1131
1294
|
}
|
|
1132
1295
|
}
|
|
@@ -1142,7 +1305,9 @@ export class SyncSagaCoordinator {
|
|
|
1142
1305
|
// We blindly attempt merge if we have both tips accessible?
|
|
1143
1306
|
// We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
|
|
1144
1307
|
// Check if we have receiverTip in space
|
|
1145
|
-
|
|
1308
|
+
console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
|
|
1309
|
+
const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
|
|
1310
|
+
console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
|
|
1146
1311
|
if (resRecTip.success && resRecTip.ibGibs?.[0]) {
|
|
1147
1312
|
// We have the tip!
|
|
1148
1313
|
// Do we have the full history?
|
|
@@ -1151,12 +1316,13 @@ export class SyncSagaCoordinator {
|
|
|
1151
1316
|
// Perform Merge!
|
|
1152
1317
|
try {
|
|
1153
1318
|
const mergeResult = await mergeDivergentTimelines({
|
|
1154
|
-
tipA: (await getFromSpace({ addr: senderTip, space })).ibGibs[0], // Our tip
|
|
1155
|
-
tipB: resRecTip.ibGibs[0], // Their tip
|
|
1156
|
-
space,
|
|
1319
|
+
tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs[0], // Our tip from destSpace
|
|
1320
|
+
tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
|
|
1321
|
+
space: tempSpace, // Merge uses tempSpace
|
|
1157
1322
|
metaspace,
|
|
1158
1323
|
});
|
|
1159
1324
|
if (mergeResult) {
|
|
1325
|
+
console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1160
1326
|
if (logalot) {
|
|
1161
1327
|
console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1162
1328
|
}
|
|
@@ -1200,16 +1366,18 @@ export class SyncSagaCoordinator {
|
|
|
1200
1366
|
responseDeltaData.proposeCommit = true;
|
|
1201
1367
|
const deltaStone = await this.createSyncMsgStone({
|
|
1202
1368
|
data: responseDeltaData,
|
|
1203
|
-
space,
|
|
1369
|
+
space: tempSpace,
|
|
1204
1370
|
metaspace
|
|
1205
1371
|
});
|
|
1206
1372
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1207
1373
|
prevSagaIbGib: sagaIbGib,
|
|
1208
1374
|
msgStones: [deltaStone],
|
|
1209
1375
|
identity,
|
|
1210
|
-
space,
|
|
1376
|
+
space: tempSpace,
|
|
1211
1377
|
metaspace
|
|
1212
1378
|
});
|
|
1379
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1380
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1213
1381
|
return { frame: deltaFrame, payloadIbGibs: outgoingPayload, receivedPayloadIbGibs };
|
|
1214
1382
|
}
|
|
1215
1383
|
else {
|
|
@@ -1223,16 +1391,18 @@ export class SyncSagaCoordinator {
|
|
|
1223
1391
|
};
|
|
1224
1392
|
const commitStone = await this.createSyncMsgStone({
|
|
1225
1393
|
data: commitData,
|
|
1226
|
-
space,
|
|
1394
|
+
space: tempSpace,
|
|
1227
1395
|
metaspace
|
|
1228
1396
|
});
|
|
1229
1397
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1230
1398
|
prevSagaIbGib: sagaIbGib,
|
|
1231
1399
|
msgStones: [commitStone],
|
|
1232
1400
|
identity,
|
|
1233
|
-
space,
|
|
1401
|
+
space: tempSpace,
|
|
1234
1402
|
metaspace
|
|
1235
1403
|
});
|
|
1404
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1405
|
+
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1236
1406
|
return { frame: commitFrame, receivedPayloadIbGibs };
|
|
1237
1407
|
}
|
|
1238
1408
|
else {
|
|
@@ -1247,16 +1417,18 @@ export class SyncSagaCoordinator {
|
|
|
1247
1417
|
};
|
|
1248
1418
|
const deltaStone = await this.createSyncMsgStone({
|
|
1249
1419
|
data: responseDeltaData,
|
|
1250
|
-
space,
|
|
1420
|
+
space: tempSpace,
|
|
1251
1421
|
metaspace
|
|
1252
1422
|
});
|
|
1253
1423
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1254
1424
|
prevSagaIbGib: sagaIbGib,
|
|
1255
1425
|
msgStones: [deltaStone],
|
|
1256
1426
|
identity,
|
|
1257
|
-
space,
|
|
1427
|
+
space: tempSpace,
|
|
1258
1428
|
metaspace
|
|
1259
1429
|
});
|
|
1430
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1431
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1260
1432
|
// Check if PEER proposed commit
|
|
1261
1433
|
if (deltaData.proposeCommit) {
|
|
1262
1434
|
if (logalot) {
|
|
@@ -1271,23 +1443,25 @@ export class SyncSagaCoordinator {
|
|
|
1271
1443
|
};
|
|
1272
1444
|
const commitStone = await this.createSyncMsgStone({
|
|
1273
1445
|
data: commitData,
|
|
1274
|
-
space,
|
|
1446
|
+
space: tempSpace,
|
|
1275
1447
|
metaspace
|
|
1276
1448
|
});
|
|
1277
1449
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1278
1450
|
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1279
1451
|
msgStones: [commitStone],
|
|
1280
1452
|
identity,
|
|
1281
|
-
space,
|
|
1453
|
+
space: tempSpace,
|
|
1282
1454
|
metaspace
|
|
1283
1455
|
});
|
|
1456
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1457
|
+
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1284
1458
|
return { frame: commitFrame, receivedPayloadIbGibs };
|
|
1285
1459
|
}
|
|
1286
1460
|
return { frame: deltaFrame, receivedPayloadIbGibs };
|
|
1287
1461
|
}
|
|
1288
1462
|
}
|
|
1289
1463
|
}
|
|
1290
|
-
async handleCommitFrame({ sagaIbGib,
|
|
1464
|
+
async handleCommitFrame({ sagaIbGib, destSpace, tempSpace, metaspace, identity, }) {
|
|
1291
1465
|
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
1292
1466
|
if (logalot) {
|
|
1293
1467
|
console.log(`${lc} Commit received.`);
|
|
@@ -1305,19 +1479,6 @@ export class SyncSagaCoordinator {
|
|
|
1305
1479
|
}
|
|
1306
1480
|
return null;
|
|
1307
1481
|
}
|
|
1308
|
-
async handleConflictFrame({ sagaIbGib, metaspace, space, }) {
|
|
1309
|
-
const lc = `${this.lc}[${this.handleConflictFrame.name}]`;
|
|
1310
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
1311
|
-
const conflictData = messageData;
|
|
1312
|
-
if (logalot) {
|
|
1313
|
-
console.log(`${lc} Conflict received. Strategy: ${conflictData?.conflictStrategy}. Terminal: ${conflictData?.isTerminal}`);
|
|
1314
|
-
}
|
|
1315
|
-
if (conflictData?.isTerminal) {
|
|
1316
|
-
throw new Error(`${lc} Saga aborted due to conflicts: ${JSON.stringify(conflictData.conflicts)} (E: b08d1f2a3c4e5d6f7a8b9c0d1e2f3a4b)`);
|
|
1317
|
-
}
|
|
1318
|
-
// Non-terminal logic (stub for future)
|
|
1319
|
-
return null;
|
|
1320
|
-
}
|
|
1321
1482
|
// #endregion Handlers
|
|
1322
1483
|
async createSyncMsgStone({ data, space, metaspace, }) {
|
|
1323
1484
|
const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
|
|
@@ -1349,6 +1510,37 @@ export class SyncSagaCoordinator {
|
|
|
1349
1510
|
}
|
|
1350
1511
|
}
|
|
1351
1512
|
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
|
|
1515
|
+
* Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
|
|
1516
|
+
*/
|
|
1517
|
+
async ensureSagaFrameInBothSpaces({ frame, destSpace, tempSpace, metaspace, }) {
|
|
1518
|
+
// Frame itself (already in tempSpace from creation, need in destSpace for audit)
|
|
1519
|
+
await putInSpace({ space: destSpace, ibGib: frame });
|
|
1520
|
+
await metaspace.registerNewIbGib({ ibGib: frame });
|
|
1521
|
+
// Msg stone(s) (already in tempSpace, need in destSpace for audit)
|
|
1522
|
+
const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
1523
|
+
if (msgStoneAddrs && msgStoneAddrs.length > 0) {
|
|
1524
|
+
const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
|
|
1525
|
+
if (resMsgStones.ibGibs) {
|
|
1526
|
+
for (const msgStone of resMsgStones.ibGibs) {
|
|
1527
|
+
await putInSpace({ space: destSpace, ibGib: msgStone });
|
|
1528
|
+
await metaspace.registerNewIbGib({ ibGib: msgStone });
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
// Identity (if present, already in tempSpace, need in destSpace)
|
|
1533
|
+
const identityAddrs = frame.rel8ns?.identity;
|
|
1534
|
+
if (identityAddrs && identityAddrs.length > 0) {
|
|
1535
|
+
const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
|
|
1536
|
+
if (resIdentity.ibGibs) {
|
|
1537
|
+
for (const identity of resIdentity.ibGibs) {
|
|
1538
|
+
await putInSpace({ space: destSpace, ibGib: identity });
|
|
1539
|
+
await metaspace.registerNewIbGib({ ibGib: identity });
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1352
1544
|
/**
|
|
1353
1545
|
* Evolves the saga timeline with a new frame.
|
|
1354
1546
|
*/
|