@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.
Files changed (73) hide show
  1. package/dist/sync/graft-info/graft-info-constants.d.mts +5 -0
  2. package/dist/sync/graft-info/graft-info-constants.d.mts.map +1 -0
  3. package/dist/sync/graft-info/graft-info-constants.mjs +5 -0
  4. package/dist/sync/graft-info/graft-info-constants.mjs.map +1 -0
  5. package/dist/sync/graft-info/graft-info-helpers.d.mts +49 -0
  6. package/dist/sync/graft-info/graft-info-helpers.d.mts.map +1 -0
  7. package/dist/sync/graft-info/graft-info-helpers.mjs +236 -0
  8. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -0
  9. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts +2 -0
  10. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts.map +1 -0
  11. package/dist/sync/graft-info/graft-info-helpers.respec.mjs +70 -0
  12. package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -0
  13. package/dist/sync/graft-info/graft-info-types.d.mts +31 -0
  14. package/dist/sync/{merge-info/merge-info-types.d.mts.map → graft-info/graft-info-types.d.mts.map} +1 -1
  15. package/dist/sync/graft-info/graft-info-types.mjs +2 -0
  16. package/dist/sync/graft-info/graft-info-types.mjs.map +1 -0
  17. package/dist/sync/strategies/conflict-optimistic.d.mts +1 -1
  18. package/dist/sync/strategies/conflict-optimistic.d.mts.map +1 -1
  19. package/dist/sync/strategies/conflict-optimistic.mjs +10 -60
  20. package/dist/sync/strategies/conflict-optimistic.mjs.map +1 -1
  21. package/dist/sync/sync-conflict.respec.mjs +152 -33
  22. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  23. package/dist/sync/sync-constants.d.mts +1 -3
  24. package/dist/sync/sync-constants.d.mts.map +1 -1
  25. package/dist/sync/sync-constants.mjs +0 -2
  26. package/dist/sync/sync-constants.mjs.map +1 -1
  27. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts +5 -2
  28. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts.map +1 -1
  29. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs +70 -7
  30. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs.map +1 -1
  31. package/dist/sync/sync-saga-coordinator.d.mts +25 -18
  32. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  33. package/dist/sync/sync-saga-coordinator.mjs +508 -316
  34. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  35. package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts.map +1 -1
  36. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs +1 -0
  37. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs.map +1 -1
  38. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -12
  39. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  40. package/ibgib-foundations.md +20 -2
  41. package/package.json +1 -1
  42. package/src/sync/graft-info/graft-info-constants.mts +4 -0
  43. package/src/sync/graft-info/graft-info-helpers.mts +308 -0
  44. package/src/sync/graft-info/graft-info-helpers.respec.mts +83 -0
  45. package/src/sync/graft-info/graft-info-types.mts +33 -0
  46. package/src/sync/strategies/conflict-optimistic.mts +11 -70
  47. package/src/sync/sync-conflict.respec.mts +171 -35
  48. package/src/sync/sync-constants.mts +1 -4
  49. package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +85 -12
  50. package/src/sync/sync-saga-coordinator.mts +569 -338
  51. package/src/sync/sync-saga-message/sync-saga-message-helpers.mts +2 -0
  52. package/src/sync/sync-saga-message/sync-saga-message-types.mts +0 -11
  53. package/test_output.log +0 -0
  54. package/tmp.md +43 -2
  55. package/dist/sync/merge-info/merge-info-constants.d.mts +0 -2
  56. package/dist/sync/merge-info/merge-info-constants.d.mts.map +0 -1
  57. package/dist/sync/merge-info/merge-info-constants.mjs +0 -2
  58. package/dist/sync/merge-info/merge-info-constants.mjs.map +0 -1
  59. package/dist/sync/merge-info/merge-info-helpers.d.mts +0 -51
  60. package/dist/sync/merge-info/merge-info-helpers.d.mts.map +0 -1
  61. package/dist/sync/merge-info/merge-info-helpers.mjs +0 -92
  62. package/dist/sync/merge-info/merge-info-helpers.mjs.map +0 -1
  63. package/dist/sync/merge-info/merge-info-helpers.respec.d.mts +0 -2
  64. package/dist/sync/merge-info/merge-info-helpers.respec.d.mts.map +0 -1
  65. package/dist/sync/merge-info/merge-info-helpers.respec.mjs +0 -32
  66. package/dist/sync/merge-info/merge-info-helpers.respec.mjs.map +0 -1
  67. package/dist/sync/merge-info/merge-info-types.d.mts +0 -26
  68. package/dist/sync/merge-info/merge-info-types.mjs +0 -2
  69. package/dist/sync/merge-info/merge-info-types.mjs.map +0 -1
  70. package/src/sync/merge-info/merge-info-constants.mts +0 -1
  71. package/src/sync/merge-info/merge-info-helpers.mts +0 -134
  72. package/src/sync/merge-info/merge-info-helpers.respec.mts +0 -41
  73. 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
- space: localSpace, // Must be localSpace (Source) to find domain data
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
- if (logalot) {
349
- console.log(`${lc} starting...`);
350
- }
351
- let tjps = [];
352
- if (tjpAddrs) {
353
- tjps = tjpAddrs;
354
- }
355
- else if (domainIbGibs && domainIbGibs.length > 0) {
356
- // Extract TJPs from domain Ibgibs
357
- const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
358
- const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
359
- const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
360
- tjps = Object.keys(timelineMap);
361
- }
362
- else {
363
- // No info provided. Return empty? Or throw?
364
- // User test context implied "everything", but implementation requires scope.
365
- console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
366
- return {};
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
- if (tjps.length === 0) {
369
- return {};
405
+ catch (error) {
406
+ console.error(`${lc} ${extractErrorMsg(error)}`);
407
+ throw error;
370
408
  }
371
- const res = await getLatestAddrs({ space, tjpAddrs: tjps });
372
- if (!res.data || !res.data.latestAddrsMap) {
373
- throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
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
- if (logalot) {
446
- console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`);
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, space, identity, identitySecret, metaspace, }) {
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, space, identity, identitySecret });
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, space, identity });
539
+ return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
495
540
  case SyncStage.delta:
496
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, space, identity, });
541
+ return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
497
542
  case SyncStage.commit:
498
- return await this.handleCommitFrame({ sagaIbGib, metaspace, space, identity, });
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, space, metaspace, identity, identitySecret, }) {
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
- if (logalot) {
788
- console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`);
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, space, metaspace, identity, }) {
855
+ async handleAckFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
805
856
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
806
- if (logalot) {
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} Processing optimistic conflicts: ${optimisticConflicts.length}`);
834
- }
835
- // We need to resolve these.
836
- // Strategy:
837
- // 1. Analyze Divergence (Sender vs Receiver)
838
- // 2. Identify missing data needed for merge (Receiver's unique frames)
839
- // 3. Request that data (as Delta Reqs)
840
- // 4. (Later in Delta Phase) Perform Merge.
841
- // BUT: The Delta Phase is usually generic "Send me these Addrs".
842
- // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
843
- // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
844
- // We (Sender) are processing the Ack.
845
- // We need to request data FROM Receiver.
846
- // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
847
- // If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
848
- // Or can we send a 'Delta Request' frame?
849
- // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
850
- // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
851
- // Or we just proceed to Delta (sending what Receiver wants),
852
- // AND we piggyback our own requests?
853
- // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
854
- // SIMPLIFICATION for V1:
855
- // If we need data to merge, we must get it.
856
- // We are the Coordinator (Active). We can fetch from Peer immediately?
857
- // `peer.pull(addr)`?
858
- // Yes! The Coordinator has the `peer`.
859
- // Let's analyze and pull immediately.
860
- for (const conflict of optimisticConflicts) {
861
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
862
- // Sender History
863
- // We need our own history for this timeline.
864
- // We know the 'senderTip' (remoteAddr in Ack).
865
- // Sender should verify it has this tip.
866
- // Compute Diffs
867
- // We need to find `receiverOnly` addrs.
868
- // Receiver sent us `timelineAddrs` (Full History).
869
- const receiverHistorySet = new Set(timelineAddrs);
870
- // We need our execution context's history for this senderTip.
871
- // We can fetch valid 'past' from space.
872
- const resSenderTip = await getFromSpace({ space, addr: senderTip });
873
- const senderTipIbGib = resSenderTip.ibGibs?.[0];
874
- if (!senderTipIbGib) {
875
- throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
876
- }
877
- // Basic Diff: Find what Receiver has that we don't.
878
- // Actually, we need to traverse OUR past to find commonality.
879
- const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
880
- const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
881
- if (receiverOnlyAddrs.length > 0) {
882
- if (logalot) {
883
- console.log(`${lc} Pulling divergent history from Receiver: ${receiverOnlyAddrs.length} frames`);
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
- // PULL these frames from Peer into Local Space
886
- // (Validation: We trust peer for now / verification happens on put)
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
- // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
938
- const deltaReqAddrs = ackData.deltaReqAddrs || [];
939
- const pushOfferAddrs = ackData.pushOfferAddrs || [];
940
- // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
941
- const pullReqAddrs = [];
942
- for (const addr of pushOfferAddrs) {
943
- const existing = srcGraph[addr] || (await getFromSpace({ addr, space })).ibGibs?.[0];
944
- if (!existing) {
945
- pullReqAddrs.push(addr);
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
- if (ibGib) {
968
- tipsToSync.push(ibGib);
969
- }
970
- else {
971
- throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
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
- // Calculate Dependency Graph for ALL tips, effectively utilizing common history
975
- // Pass skipAddrs to `getDependencyGraph` or gather manually.
976
- // `getDependencyGraph` takes a single ibGib.
977
- // We can optimize by doing it for each tip and unioning the result?
978
- // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
979
- // We will loop.
980
- const allDepsSet = new Set();
981
- for (const tip of tipsToSync) {
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
- if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1008
- if (logalot) {
1009
- console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
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 (!allDepsSet.has(tipAddr)) {
1012
- allDepsSet.add(tipAddr);
1013
- payloadIbGibs.push(tip);
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, space, metaspace, identity, }) {
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
- outgoingPayload.push(ibGib);
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
- const resRecTip = await getFromSpace({ addr: receiverTip, space });
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, space, metaspace, }) {
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
  */