@ibgib/core-gib 0.1.26 → 0.1.28

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 (34) hide show
  1. package/dist/sync/sync-helpers.d.mts +23 -2
  2. package/dist/sync/sync-helpers.d.mts.map +1 -1
  3. package/dist/sync/sync-helpers.mjs +121 -4
  4. package/dist/sync/sync-helpers.mjs.map +1 -1
  5. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  6. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +30 -9
  7. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  8. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  9. package/dist/sync/sync-peer/sync-peer-v1.mjs +1 -0
  10. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  11. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  12. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +17 -3
  13. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  14. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +8 -0
  15. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  16. package/dist/sync/sync-saga-coordinator.d.mts +30 -153
  17. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  18. package/dist/sync/sync-saga-coordinator.mjs +585 -406
  19. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  20. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +13 -18
  21. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  22. package/dist/sync/sync-types.d.mts +36 -6
  23. package/dist/sync/sync-types.d.mts.map +1 -1
  24. package/dist/sync/sync-types.mjs +32 -0
  25. package/dist/sync/sync-types.mjs.map +1 -1
  26. package/package.json +2 -2
  27. package/src/sync/sync-helpers.mts +113 -6
  28. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +30 -8
  29. package/src/sync/sync-peer/sync-peer-v1.mts +2 -0
  30. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +14 -2
  31. package/src/sync/sync-saga-context/sync-saga-context-types.mts +13 -5
  32. package/src/sync/sync-saga-coordinator.mts +656 -463
  33. package/src/sync/sync-saga-message/sync-saga-message-types.mts +27 -32
  34. package/src/sync/sync-types.mts +45 -8
@@ -3,10 +3,10 @@ pretty, delay, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
3
3
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
4
4
  import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
5
5
  import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
6
- import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
6
+ import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN, } from "./sync-constants.mjs";
7
7
  import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
8
8
  import { SyncConflictStrategy, SyncMode, SYNC_CONFLICT_STRATEGY_VALID_VALUES, } from "./sync-types.mjs";
9
- import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
9
+ import { getFullSyncSagaHistory, getSyncIb, getTempSpaceName, isPastFrame, putInSpace_dnasThenNonDnas, validateFullSyncSagaHistory } from "./sync-helpers.mjs";
10
10
  import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
11
11
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
12
12
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
@@ -17,7 +17,7 @@ import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
17
17
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
18
18
  import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
19
19
  // const logalot = GLOBAL_LOG_A_LOT || true;
20
- const logalot = false;
20
+ const logalot = true;
21
21
  const logalotControlDomain = true;
22
22
  const lcControlDomain = '[ControlDomain]';
23
23
  /**
@@ -153,8 +153,8 @@ export class SyncSagaCoordinator {
153
153
  * @returns next context result if another round, else if commit returns
154
154
  * null
155
155
  */
156
- async receiverContinueSync({ sagaContext, mySpace, myTempSpace, identity, identitySecret, metaspace, }) {
157
- const lc = `${this.lc}[${this.receiverContinueSync.name}]`;
156
+ async continueSync({ sagaContext, mySpace, myTempSpace, identity, identitySecret, metaspace, }) {
157
+ const lc = `${this.lc}[${this.continueSync.name}]`;
158
158
  try {
159
159
  if (logalot) {
160
160
  console.log(`${lc} starting... (I: f64e08bf77d1425378601f380384ec26)`);
@@ -171,7 +171,12 @@ export class SyncSagaCoordinator {
171
171
  if (logalot) {
172
172
  console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`);
173
173
  }
174
- return null;
174
+ // does this ever hit now?
175
+ return null; /* <<<< returns early */
176
+ }
177
+ else if (contextResult.nextFrameInfo?.sagaComplete) {
178
+ // this is the current proper workflow I believe as of 01/22/2026
179
+ return null; /* <<<< returns early */
175
180
  }
176
181
  // #region error conditions throw
177
182
  if (contextResult.errorMsg) {
@@ -180,9 +185,6 @@ export class SyncSagaCoordinator {
180
185
  else if (!contextResult.nextFrameInfo) {
181
186
  throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
182
187
  }
183
- else if (contextResult.nextFrameInfo?.responseWasNull) {
184
- throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
185
- }
186
188
  // #endregion error conditions throw
187
189
  // create the return context
188
190
  const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
@@ -417,8 +419,8 @@ export class SyncSagaCoordinator {
417
419
  else if (!contextResult.nextFrameInfo) {
418
420
  throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
419
421
  }
420
- else if (contextResult.nextFrameInfo?.responseWasNull) {
421
- throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
422
+ else if (contextResult.nextFrameInfo?.sagaComplete) {
423
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.sagaComplete? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
422
424
  }
423
425
  // #endregion error conditions throw
424
426
  // we have another frame to process and send out, with possibly
@@ -585,7 +587,9 @@ export class SyncSagaCoordinator {
585
587
  metaspace,
586
588
  localSpace,
587
589
  });
588
- // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
590
+ if (logalot) {
591
+ console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`);
592
+ }
589
593
  return { initFrame: sagaFrame, initDomainGraph: fullGraph };
590
594
  }
591
595
  catch (error) {
@@ -691,7 +695,7 @@ export class SyncSagaCoordinator {
691
695
  console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
692
696
  }
693
697
  // Get Stage from Stone (or Frame for Init fallback)
694
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
698
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
695
699
  if (logalot) {
696
700
  console.log(`${lc} handling frame stage: ${stage}`);
697
701
  }
@@ -705,11 +709,8 @@ export class SyncSagaCoordinator {
705
709
  nextFrameInfo = await this.handleInitFrame({
706
710
  sagaIbGib,
707
711
  messageData: messageData,
708
- metaspace,
709
- mySpace: mySpace,
710
- myTempSpace: myTempSpace,
711
- identity,
712
- identitySecret
712
+ metaspace, mySpace, myTempSpace,
713
+ identity, identitySecret
713
714
  });
714
715
  break;
715
716
  case SyncStage.ack:
@@ -717,24 +718,33 @@ export class SyncSagaCoordinator {
717
718
  throw new Error(`(UNEXPECTED) initDomainGraph falsy on the sender? (E: a3d758ad954829aba88663188eafc826)`);
718
719
  }
719
720
  nextFrameInfo = await this.handleAckFrame({
721
+ sagaContext,
720
722
  sagaIbGib,
721
- srcGraph,
722
723
  initDomainGraph,
723
- metaspace,
724
- destSpace: mySpace,
725
- tempSpace: myTempSpace,
724
+ metaspace, mySpace, myTempSpace,
726
725
  identity,
727
726
  });
728
727
  break;
729
728
  case SyncStage.delta:
730
- nextFrameInfo = await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
729
+ nextFrameInfo = await this.handleDeltaFrame({
730
+ sagaContext,
731
+ sagaIbGib,
732
+ srcGraph,
733
+ metaspace,
734
+ mySpace,
735
+ myTempSpace,
736
+ identity,
737
+ });
731
738
  break;
732
739
  case SyncStage.commit:
733
- nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
740
+ nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, mySpace: mySpace, myTempSpace: myTempSpace, identity, });
734
741
  break;
735
742
  default:
736
743
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
737
744
  }
745
+ if (logalot) {
746
+ console.log(`${lc} nextFrameInfo: ${nextFrameInfo ? pretty(nextFrameInfo) : 'undefined'} (I: a8ad281ca8e89385686d18327a105726)`);
747
+ }
738
748
  return { errorMsg: undefined, nextFrameInfo, };
739
749
  }
740
750
  catch (error) {
@@ -758,7 +768,7 @@ export class SyncSagaCoordinator {
758
768
  * The Receiver performs Gap Analysis here:
759
769
  * 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
760
770
  * 2. Identifies what Sender needs (`pushOfferAddrs`).
761
- * 3. Identifies what Receiver needs (`deltaReqAddrs`).
771
+ * 3. Identifies what Receiver needs (`deltaRequestAddrInfos`).
762
772
  * 4. Returns an `Ack` frame containing these lists.
763
773
  */
764
774
  async handleInitFrame({ sagaIbGib, messageData, mySpace, myTempSpace, metaspace, identity,
@@ -995,7 +1005,7 @@ export class SyncSagaCoordinator {
995
1005
  }
996
1006
  const ackStone = await this.createSyncMsgStone({
997
1007
  data: ackData,
998
- localSpace: myTempSpace,
1008
+ localSpace: mySpace,
999
1009
  metaspace,
1000
1010
  });
1001
1011
  if (logalot) {
@@ -1092,19 +1102,23 @@ export class SyncSagaCoordinator {
1092
1102
  * **Execution Context**: **Sender (Local)**.
1093
1103
  *
1094
1104
  * The Sender reacts to the Receiver's requirements:
1095
- * 1. `deltaReqAddrs`: Receiver wants this data. Sender gathers it from `srcGraph` and puts it in `payloadIbGibs` (for next frame).
1096
- * 2. `pushOfferAddrs`: Receiver has newer data. Sender acknowledges and adds them to `requests` (asking Receiver to send them).
1105
+ * 1. `deltaRequestAddrInfos`: Receiver wants this data. Sender takes this
1106
+ * into account, plus gathers it from `initDomainGraph` and puts them in
1107
+ * `payloadIbGibs` (for next frame).
1108
+ * 2. `pushOfferAddrs`: Receiver has newer data. these should have been
1109
+ * included in the incoming context from the receiver..
1097
1110
  *
1098
1111
  * Returns a `Delta` frame.
1099
1112
  */
1100
- async handleAckFrame({ sagaIbGib, srcGraph, initDomainGraph, destSpace, tempSpace, metaspace, identity, }) {
1113
+ async handleAckFrame({ sagaContext, sagaIbGib, initDomainGraph, mySpace, myTempSpace, metaspace, identity, }) {
1101
1114
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
1102
1115
  try {
1103
1116
  if (logalot) {
1104
1117
  console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
1105
1118
  }
1106
- const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1119
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
1107
1120
  const ackData = messageData;
1121
+ // #region sanity/validation
1108
1122
  if (!ackData) {
1109
1123
  throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
1110
1124
  }
@@ -1114,6 +1128,10 @@ export class SyncSagaCoordinator {
1114
1128
  if (logalot) {
1115
1129
  console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
1116
1130
  }
1131
+ if (!sagaIbGib.data) {
1132
+ throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 385e389610282aa9c5dbe4083adbde26)`);
1133
+ }
1134
+ // #region sanity/validation
1117
1135
  // 1. Check for Conflicts
1118
1136
  const conflicts = ackData.conflicts || [];
1119
1137
  console.log(`${lc} [CONFLICT DEBUG] Received conflicts from Ack: ${conflicts.length}`);
@@ -1128,7 +1146,12 @@ export class SyncSagaCoordinator {
1128
1146
  throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
1129
1147
  }
1130
1148
  // at this point, if we have conflicts, they are non-terminal
1131
- const mergeDeltaReqs = []; // Additional requests for merging
1149
+ /**
1150
+ * at this point, we only request ibgibs for conflicted timelines.
1151
+ * If the receiver had known of any ibgibs this sender needed, it
1152
+ * would have been in the push offer.
1153
+ */
1154
+ const outgoingDeltaAddrRequestInfos = []; // Additional requests for merging
1132
1155
  if (conflicts.length > 0) {
1133
1156
  console.log(`${lc} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
1134
1157
  // We need to resolve these.
@@ -1138,8 +1161,8 @@ export class SyncSagaCoordinator {
1138
1161
  // 3. Request that data (as Delta Reqs)
1139
1162
  // 4. (Later in Delta Phase) Perform Merge.
1140
1163
  // BUT: The Delta Phase is usually generic "Send me these Addrs".
1141
- // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
1142
- // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
1164
+ // If we just add to `deltaRequestAddrInfos` (which are requests for Sender to send to Receiver?),
1165
+ // wait. `ackData.deltaRequestAddrInfos` are what RECEIVER wants from SENDER.
1143
1166
  // We (Sender) are processing the Ack.
1144
1167
  // We need to request data FROM Receiver.
1145
1168
  // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
@@ -1206,8 +1229,8 @@ export class SyncSagaCoordinator {
1206
1229
  // // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1207
1230
  // for (const addr of receiverOnlyAddrs) {
1208
1231
  // // Add the frame itself first
1209
- // if (!mergeDeltaReqs.includes(addr)) {
1210
- // mergeDeltaReqs.push(addr);
1232
+ // if (!outgoingDeltaAddrRequestInfos.includes(addr)) {
1233
+ // outgoingDeltaAddrRequestInfos.push(addr);
1211
1234
  // }
1212
1235
  // // Get the frame's delta dependencies (skip LCA's deps)
1213
1236
  // try {
@@ -1223,8 +1246,8 @@ export class SyncSagaCoordinator {
1223
1246
  // if (frameDeltaDeps) {
1224
1247
  // // Add all delta dependencies (Object.keys gives us the addresses)
1225
1248
  // Object.keys(frameDeltaDeps).forEach(depAddr => {
1226
- // if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1227
- // mergeDeltaReqs.push(depAddr);
1249
+ // if (!outgoingDeltaAddrRequestInfos.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1250
+ // outgoingDeltaAddrRequestInfos.push(depAddr);
1228
1251
  // }
1229
1252
  // });
1230
1253
  // }
@@ -1233,117 +1256,63 @@ export class SyncSagaCoordinator {
1233
1256
  // console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1234
1257
  // }
1235
1258
  // }
1236
- // console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1259
+ // console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${outgoingDeltaAddrRequestInfos.length}`);
1237
1260
  // } else {
1238
1261
  // console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1239
1262
  // }
1240
1263
  }
1241
- console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
1264
+ console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. outgoingDeltaAddrRequestInfos: ${outgoingDeltaAddrRequestInfos.length}`);
1242
1265
  }
1243
1266
  else {
1244
1267
  console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1245
1268
  }
1246
1269
  // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1247
- const deltaReqAddrs = ackData.deltaRequestAddrInfos || [];
1248
- const pushOfferAddrs = ackData.pushOfferInfos || [];
1249
- // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1250
- const pullReqAddrs = [];
1251
- for (const addr of pushOfferAddrs) {
1252
- // const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1253
- // if (!existing) {
1254
- // pullReqAddrs.push(addr);
1255
- // }
1256
- }
1257
- // 2. Process Delta Requests (Push Payload)
1258
- // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1259
- // const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
1260
- const skipAddrs = new Set();
1261
- if (ackData.knowledgeVector) {
1262
- Object.values(ackData.knowledgeVector).forEach(addrs => {
1263
- // addrs.forEach(a => skipAddrs.add(a));
1264
- });
1265
- }
1266
- const payloadIbGibs = [];
1267
- // Gather all tips to sync first
1268
- const tipsToSync = [];
1269
- for (const addr of deltaReqAddrs) {
1270
- // let ibGib = srcGraph[addr];
1271
- // if (!ibGib) {
1272
- // const res = await getFromSpace({ addr, space: destSpace });
1273
- // if (res.ibGibs && res.ibGibs.length > 0) {
1274
- // ibGib = res.ibGibs[0];
1275
- // }
1276
- // }
1277
- // if (ibGib) {
1278
- // tipsToSync.push(ibGib);
1279
- // } else {
1280
- // throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1281
- // }
1282
- }
1283
- // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1284
- // Pass skipAddrs to `getDependencyGraph` or gather manually.
1285
- // `getDependencyGraph` takes a single ibGib.
1286
- // We can optimize by doing it for each tip and unioning the result?
1287
- // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1288
- // We will loop.
1289
- const allDepsSet = new Set();
1290
- for (const tip of tipsToSync) {
1291
- // Always include the tip itself
1292
- const tipAddr = getIbGibAddr({ ibGib: tip });
1293
- // Only process if not skipped (though deltaReq implies they barely just asked for it)
1294
- // But detailed deps might be skipped.
1295
- // Get Graph with Skips
1296
- // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
1297
- const deps = await getDependencyGraph({
1298
- ibGib: tip,
1299
- space: destSpace,
1300
- skipAddrs: Array.from(skipAddrs)
1301
- });
1302
- // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
1303
- let tipIncluded = false;
1304
- if (deps) {
1305
- Object.values(deps).forEach(d => {
1306
- const dAddr = getIbGibAddr({ ibGib: d });
1307
- if (!allDepsSet.has(dAddr)) {
1308
- allDepsSet.add(dAddr);
1309
- payloadIbGibs.push(d);
1310
- }
1311
- if (dAddr === tipAddr) {
1312
- tipIncluded = true;
1313
- }
1314
- });
1315
- }
1316
- if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1317
- if (logalot) {
1318
- console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
1319
- }
1320
- if (!allDepsSet.has(tipAddr)) {
1321
- allDepsSet.add(tipAddr);
1322
- payloadIbGibs.push(tip);
1323
- }
1324
- }
1325
- }
1270
+ /**
1271
+ * these were requested addrs on the INCOMING frame (the ack data).
1272
+ *
1273
+ * This is in contrast to any OUTGOING requests we make in this
1274
+ * method.
1275
+ */
1276
+ const payloadIbGibsDomain = await this.getPayloadsForRequestedInfos({
1277
+ deltaRequestAddrInfos: ackData.deltaRequestAddrInfos || [],
1278
+ mySpace,
1279
+ });
1326
1280
  // 3. Create Delta Frame
1327
- const sagaId = ackData.sagaId;
1328
1281
  const deltaData = {
1329
1282
  sagaId: sagaIbGib.data.uuid,
1330
1283
  stage: SyncStage.delta,
1331
- payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1332
- requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1284
+ /**
1285
+ * we're sending these domain ibgibs as payload (they were
1286
+ * requested by receiver)
1287
+ */
1288
+ payloadAddrsDomain: payloadIbGibsDomain.length > 0 ?
1289
+ payloadIbGibsDomain.map(x => getIbGibAddr({ ibGib: x })) :
1290
+ undefined,
1291
+ /**
1292
+ * we're asking for these addrs
1293
+ */
1294
+ deltaRequestAddrInfos: outgoingDeltaAddrRequestInfos.length > 0 ?
1295
+ outgoingDeltaAddrRequestInfos :
1296
+ undefined,
1297
+ /**
1298
+ * if we have no changes and request none, propose commit to
1299
+ * finish the sync transaction.
1300
+ */
1301
+ proposeCommit: payloadIbGibsDomain.length === 0 && outgoingDeltaAddrRequestInfos.length === 0,
1333
1302
  };
1334
1303
  if (logalot) {
1335
1304
  console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`);
1336
1305
  }
1337
1306
  const deltaStone = await this.createSyncMsgStone({
1338
1307
  data: deltaData,
1339
- localSpace: tempSpace,
1308
+ localSpace: mySpace,
1340
1309
  metaspace,
1341
1310
  });
1342
1311
  const deltaFrame = await this.evolveSyncSagaIbGib({
1343
1312
  prevSagaIbGib: sagaIbGib,
1344
1313
  msgStones: [deltaStone],
1345
1314
  sessionIdentity: identity,
1346
- localSpace: tempSpace,
1315
+ localSpace: mySpace,
1347
1316
  metaspace,
1348
1317
  });
1349
1318
  if (logalot) {
@@ -1354,9 +1323,7 @@ export class SyncSagaCoordinator {
1354
1323
  if (identity) {
1355
1324
  payloadIbGibsControl.push(identity);
1356
1325
  }
1357
- // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1358
- // return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1359
- throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
1326
+ return { frame: deltaFrame, payloadIbGibsDomain, };
1360
1327
  }
1361
1328
  catch (error) {
1362
1329
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -1378,330 +1345,479 @@ export class SyncSagaCoordinator {
1378
1345
  * 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
1379
1346
  * 3. **Completion**: If no more requests, transitions to `Commit`.
1380
1347
  */
1381
- async handleDeltaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
1348
+ async handleDeltaFrame({ sagaContext, sagaIbGib, srcGraph, mySpace, myTempSpace, metaspace, identity, }) {
1382
1349
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1383
- if (logalot) {
1384
- console.log(`${lc} starting...`);
1385
- }
1386
- const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1387
- const deltaData = messageData;
1388
- if (!deltaData) {
1389
- throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
1390
- }
1391
- if (deltaData.stage !== SyncStage.delta) {
1392
- throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
1393
- }
1394
- if (logalot) {
1395
- console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`);
1396
- }
1397
- console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1398
- const payloadAddrs = deltaData.payloadAddrs || [];
1399
- const peerRequests = deltaData.requests || [];
1400
- const peerProposesCommit = deltaData.proposeCommit || false;
1401
- // 1. Process Received Payload (Ingest)
1402
- const receivedPayloadIbGibs = [];
1403
- if (payloadAddrs.length > 0) {
1404
- // We use `payloadAddrs` as the manifest.
1405
- // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1406
- // assuming the "Transport" layer put them there implicitly?
1407
- // OR, if we are local-only, we just get them.
1408
- // The `handleDeltaFrame` contract assumes data is reachable in `space`.
1409
- const res = await getFromSpace({
1410
- addrs: payloadAddrs,
1411
- space: tempSpace, // Incoming data is in tempSpace
1412
- });
1413
- if (res.ibGibs) {
1414
- receivedPayloadIbGibs.push(...res.ibGibs);
1415
- // Also put them? `getFromSpace` retrieves. If they are in space, they are persisted.
1416
- // If this is a Temp Space, they are safe.
1350
+ try {
1351
+ if (logalot) {
1352
+ console.log(`${lc} starting... (I: a1d0a85eb4189466f86dfd61e3df2626)`);
1417
1353
  }
1418
- else {
1419
- console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
1354
+ const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
1355
+ const deltaData = messageData;
1356
+ // #region validate/sanity
1357
+ if (!deltaData) {
1358
+ throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
1420
1359
  }
1421
- }
1422
- // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1423
- const outgoingPayload = [];
1424
- const outgoingAddrsSet = new Set(); // Track what we've added
1425
- console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
1426
- for (const addr of peerRequests) {
1427
- // Get the requested ibGib
1428
- let ibGib = srcGraph[addr];
1429
- if (!ibGib) {
1430
- const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1431
- if (res.ibGibs && res.ibGibs.length > 0) {
1432
- ibGib = res.ibGibs[0];
1433
- }
1360
+ if (deltaData.stage !== SyncStage.delta) {
1361
+ throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
1362
+ }
1363
+ if (logalot) {
1364
+ console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`);
1434
1365
  }
1435
- if (ibGib) {
1436
- // Add the requested ibGib itself
1437
- const ibGibAddr = getIbGibAddr({ ibGib });
1438
- if (!outgoingAddrsSet.has(ibGibAddr)) {
1439
- outgoingPayload.push(ibGib);
1440
- outgoingAddrsSet.add(ibGibAddr);
1366
+ // #endregion validate/sanity
1367
+ console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1368
+ const peerProposesCommit = deltaData.proposeCommit || false;
1369
+ /**
1370
+ * these are already in the local temp space
1371
+ */
1372
+ const receivedPayloadIbGibs = sagaContext.payloadIbGibsDomain ?? [];
1373
+ // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1374
+ console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${(deltaData.deltaRequestAddrInfos || []).length} peer requests`);
1375
+ const outgoingPayload = await this.getPayloadsForRequestedInfos({
1376
+ deltaRequestAddrInfos: deltaData.deltaRequestAddrInfos || [],
1377
+ mySpace,
1378
+ });
1379
+ console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1380
+ // 3. Execute Merges (If applicable)
1381
+ // Check if we have pending conflicts that we CAN resolve now that we have data.
1382
+ // We look at the Saga History (Ack Frame) to find conflicts.
1383
+ // Optimization: Do this only if we received payloads.
1384
+ const mergeResultIbGibs = [];
1385
+ console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1386
+ if (receivedPayloadIbGibs.length > 0) {
1387
+ console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1388
+ // Find the Ack frame in history to get conflicts
1389
+ // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1390
+ // V1 timelines carry full history in `past`.
1391
+ const pastAddrs = sagaIbGib.rel8ns?.past || [];
1392
+ console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1393
+ const sagaHistory = await getFullSyncSagaHistory({
1394
+ sagaIbGib,
1395
+ space: mySpace,
1396
+ });
1397
+ // #region validate/sanity sagaHistory
1398
+ if (sagaHistory.length === 0) {
1399
+ throw new Error(`(UNEXPECTED) sagaHistory.length is 0? We're in handleDeltaFrame. So we should have at least init and ack. (E: 815735c9cf756b275719bf434f180826)`);
1441
1400
  }
1442
- // Expand to include full dependency graph for this ibGib
1443
- // (Receiver needs all deps to properly process/merge)
1444
- try {
1445
- const deps = await getDependencyGraph({
1446
- ibGib,
1447
- space: destSpace,
1448
- });
1449
- if (deps) {
1450
- Object.values(deps).forEach(depIbGib => {
1451
- const depAddr = getIbGibAddr({ ibGib: depIbGib });
1452
- if (!outgoingAddrsSet.has(depAddr)) {
1453
- outgoingPayload.push(depIbGib);
1454
- outgoingAddrsSet.add(depAddr);
1455
- }
1456
- });
1457
- }
1401
+ if (sagaHistory.length === 1) {
1402
+ throw new Error(`(UNEXPECTED) sagaHistory.length is 1? We're in handleDeltaFrame. So we should have at least init and ack. (E: 0e4ef8e3ed088e83b2cdcd18ecea9826)`);
1458
1403
  }
1459
- catch (depError) {
1460
- console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
1404
+ // #endregion validate/sanity sagaHistory
1405
+ const ackGraphs = sagaHistory.filter(x => x.msgStones.at(0).data.stage === SyncStage.ack);
1406
+ // #region validate/sanity ackGraphs
1407
+ if (ackGraphs.length > 1) {
1408
+ throw new Error(`(UNEXPECTED) more than one ack stage found in sagaHistory? we're expecting exactly one. (E: 7150983bb3a841caf8acd60826ba4f26)`);
1461
1409
  }
1462
- }
1463
- else {
1464
- console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
1465
- }
1466
- }
1467
- console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1468
- // 3. Execute Merges (If applicable)
1469
- // Check if we have pending conflicts that we CAN resolve now that we have data.
1470
- // We look at the Saga History (Ack Frame) to find conflicts.
1471
- // Optimization: Do this only if we received payloads.
1472
- const mergeResultIbGibs = [];
1473
- console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1474
- if (receivedPayloadIbGibs.length > 0) {
1475
- console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1476
- // Find the Ack frame in history to get conflicts
1477
- // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1478
- // V1 timelines carry full history in `past`.
1479
- const pastAddrs = sagaIbGib.rel8ns?.past || [];
1480
- console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1481
- let ackData;
1482
- if (pastAddrs.length > 0) {
1483
- // Batch fetch all past frames
1484
- const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
1485
- if (resPast.success && resPast.ibGibs) {
1486
- // Iterate backwards (most recent first) to find the latest Ack
1487
- for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1488
- const pastFrame = resPast.ibGibs[i];
1489
- const messageStone = await getSyncSagaMessageFromFrame({
1490
- frameIbGib: pastFrame,
1491
- space: tempSpace
1492
- });
1493
- if (messageStone?.data?.stage === SyncStage.ack) {
1494
- ackData = messageStone.data;
1495
- console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1496
- break;
1497
- }
1498
- }
1410
+ else if (ackGraphs.length === 0) {
1411
+ throw new Error(`(UNEXPECTED) couldn't find ack stage in sagaHistory? (E: 7d2da859196b86de28e7c8183af1e826)`);
1499
1412
  }
1500
- }
1501
- if (ackData && ackData.conflicts) {
1502
- const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
1503
- for (const conflict of optimisticConflicts) {
1504
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1505
- // We are Sender (usually) here if we are merging.
1506
- // Check if we have the history needed (timelineAddrs).
1507
- // Specifically, we needed the `receiverOnly` parts.
1508
- // We blindly attempt merge if we have both tips accessible?
1509
- // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1510
- // Check if we have receiverTip in space
1511
- console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1512
- const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
1513
- console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1514
- if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1515
- // We have the tip!
1516
- // Do we have the full history?
1517
- // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1518
- // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1519
- // Perform Merge!
1520
- try {
1521
- const mergeResult = await mergeDivergentTimelines({
1522
- tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs[0], // Our tip from destSpace
1523
- tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
1524
- space: tempSpace, // Merge uses tempSpace
1525
- metaspace,
1526
- });
1527
- if (mergeResult) {
1528
- console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1529
- if (logalot) {
1530
- console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1413
+ if (ackGraphs[0].msgStones.length !== 1) {
1414
+ throw new Error(`(UNEXPECTED) ackGraph has more than one msg stone? only one expected right now. (E: f07d388abff8d26f98cbf6e8d3932826)`);
1415
+ }
1416
+ // #endregion validate/sanity ackGraphs
1417
+ const ackData = ackGraphs[0].msgStones[0].data;
1418
+ if (!ackData) {
1419
+ throw new Error(`(UNEXPECTED) ackData falsy? (E: f9af88427f66a8db1ba868b810e8b826)`);
1420
+ }
1421
+ if (ackData && ackData.conflicts) {
1422
+ const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
1423
+ for (const conflict of optimisticConflicts) {
1424
+ const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1425
+ // We are Sender (usually) here if we are merging.
1426
+ // Check if we have the history needed (timelineAddrs).
1427
+ // Specifically, we needed the `receiverOnly` parts.
1428
+ // We blindly attempt merge if we have both tips accessible?
1429
+ // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1430
+ // Check if we have receiverTip in space
1431
+ console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1432
+ const resRecTip = await getFromSpace({ addr: receiverTip, space: myTempSpace }); // Check myTempSpace for incoming data
1433
+ console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in myTempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1434
+ if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1435
+ // We have the tip!
1436
+ // Do we have the full history?
1437
+ // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1438
+ // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1439
+ // Perform Merge!
1440
+ try {
1441
+ const mergeResult = await mergeDivergentTimelines({
1442
+ tipA: (await getFromSpace({ addr: senderTip, space: mySpace })).ibGibs[0], // Our tip from destSpace
1443
+ tipB: resRecTip.ibGibs[0], // Their tip (from myTempSpace)
1444
+ space: myTempSpace, // Merge uses myTempSpace
1445
+ metaspace,
1446
+ });
1447
+ if (mergeResult) {
1448
+ console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1449
+ if (logalot) {
1450
+ console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1451
+ }
1452
+ mergeResultIbGibs.push(mergeResult);
1453
+ outgoingPayload.push(mergeResult); // Send result to peer
1531
1454
  }
1532
- mergeResultIbGibs.push(mergeResult);
1533
- outgoingPayload.push(mergeResult); // Send result to peer
1534
1455
  }
1535
- }
1536
- catch (e) {
1537
- console.error(`${lc} Merge failed: ${e}`);
1538
- // If merge fails, we might Abort or just continue?
1456
+ catch (e) {
1457
+ console.error(`${lc} Merge failed: ${e}`);
1458
+ // If merge fails, we might Abort or just continue?
1459
+ }
1539
1460
  }
1540
1461
  }
1541
1462
  }
1542
1463
  }
1543
- }
1544
- // 4. Determine Next Action
1545
- // We have `outgoingPayload` (Requests + Merge Results).
1546
- // Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
1547
- // Do WE have outstanding requests?
1548
- // We might if `mergeResult` requires further sync? Usually no, result is complete.
1549
- const myRequests = []; // If we had more needs (e.g. partial payload), we'd add here.
1550
- const hasOutgoing = outgoingPayload.length > 0;
1551
- const hasMyRequests = myRequests.length > 0;
1552
- if (hasOutgoing || hasMyRequests) {
1553
- // We have business to attend to -> Send Delta
1554
- const responseDeltaData = {
1555
- sagaId: deltaData.sagaId,
1556
- stage: SyncStage.delta,
1557
- payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1558
- requests: hasMyRequests ? myRequests : undefined,
1559
- proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1560
- // Wait. If we send data, we are NOT committing yet.
1561
- // We are sending data. The OTHER side must ingest it.
1562
- // So proposeCommit = true?
1563
- // "Here is the data. I'm done. If you are good, let's commit."
1564
- // Yes.
1565
- };
1566
- // BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
1567
- // We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
1568
- // So yes, proposeCommit = true.
1569
- responseDeltaData.proposeCommit = true;
1570
- const deltaStone = await this.createSyncMsgStone({
1571
- data: responseDeltaData,
1572
- localSpace: tempSpace,
1573
- metaspace
1574
- });
1575
- const deltaFrame = await this.evolveSyncSagaIbGib({
1576
- prevSagaIbGib: sagaIbGib,
1577
- msgStones: [deltaStone],
1578
- sessionIdentity: identity,
1579
- localSpace: tempSpace,
1580
- metaspace
1581
- });
1582
- // Build control payloads: frame + its dependencies (msg stone, identity)
1583
- const payloadIbGibsControl = [deltaFrame, deltaStone];
1584
- if (identity) {
1585
- payloadIbGibsControl.push(identity);
1586
- }
1587
- // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1588
- // return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1589
- throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1590
- }
1591
- else {
1592
- // We have nothing to send.
1593
- if (peerProposesCommit) {
1594
- // Peer is done. We are done. -> Commit.
1595
- const commitData = {
1596
- sagaId: deltaData.sagaId,
1597
- stage: SyncStage.commit,
1598
- success: true,
1599
- };
1600
- const commitStone = await this.createSyncMsgStone({
1601
- data: commitData,
1602
- localSpace: tempSpace,
1603
- metaspace
1604
- });
1605
- const commitFrame = await this.evolveSyncSagaIbGib({
1606
- prevSagaIbGib: sagaIbGib,
1607
- msgStones: [commitStone],
1608
- sessionIdentity: identity,
1609
- localSpace: tempSpace,
1610
- metaspace
1611
- });
1612
- // Build control payloads for commit
1613
- const commitCtrlPayloads = [commitFrame, commitStone];
1614
- if (identity) {
1615
- commitCtrlPayloads.push(identity);
1616
- }
1617
- // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1618
- // return { frame: commitFrame, };
1619
- throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
1620
- }
1621
- else {
1622
- // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
1623
- // But we are empty.
1624
- // So WE propose commit.
1464
+ // 4. Determine Next Action
1465
+ // We have `outgoingPayload` (Requests + Merge Results).
1466
+ // Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
1467
+ // Do WE have outstanding requests?
1468
+ // We might if `mergeResult` requires further sync? Usually no, result is complete.
1469
+ const myRequests = []; // If we had more needs (e.g. partial payload), we'd add here.
1470
+ const hasOutgoing = outgoingPayload.length > 0;
1471
+ const hasMyRequests = myRequests.length > 0;
1472
+ if (hasOutgoing || hasMyRequests) {
1473
+ // We have business to attend to -> Send Delta
1625
1474
  const responseDeltaData = {
1626
1475
  sagaId: deltaData.sagaId,
1627
1476
  stage: SyncStage.delta,
1628
- proposeCommit: true,
1629
- payloadAddrs: [], // Always include empty array if sending delta
1477
+ payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1478
+ requests: hasMyRequests ? myRequests : undefined,
1630
1479
  };
1631
1480
  const deltaStone = await this.createSyncMsgStone({
1632
1481
  data: responseDeltaData,
1633
- localSpace: tempSpace,
1634
- metaspace
1482
+ localSpace: mySpace,
1483
+ metaspace,
1635
1484
  });
1636
1485
  const deltaFrame = await this.evolveSyncSagaIbGib({
1637
1486
  prevSagaIbGib: sagaIbGib,
1638
1487
  msgStones: [deltaStone],
1639
1488
  sessionIdentity: identity,
1640
- localSpace: tempSpace,
1641
- metaspace
1489
+ localSpace: mySpace,
1490
+ metaspace,
1642
1491
  });
1643
- // Check if PEER proposed commit
1644
- if (deltaData.proposeCommit) {
1645
- if (logalot) {
1646
- console.log(`${lc} Peer proposed commit. Accepting & Committing.`);
1492
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1493
+ }
1494
+ else {
1495
+ // We have nothing to send.
1496
+ if (peerProposesCommit) {
1497
+ // Peer is done. We are done. -> Commit.
1498
+ // one last validate entire history?
1499
+ const history = await getFullSyncSagaHistory({
1500
+ sagaIbGib,
1501
+ space: mySpace,
1502
+ });
1503
+ const validationErrors = await validateFullSyncSagaHistory({
1504
+ history,
1505
+ });
1506
+ if (validationErrors.length > 0) {
1507
+ const errorCommitFrame = await this.createCommitFrame({
1508
+ sagaIbGib,
1509
+ errors: validationErrors,
1510
+ metaspace, mySpace,
1511
+ identity,
1512
+ });
1513
+ return { frame: errorCommitFrame, }; /* <<<< returns early */
1647
1514
  }
1648
- // Peer wants to commit and has no more requests.
1649
- // We should Commit.
1650
- const commitData = {
1515
+ await this.executeLocalCommit({
1516
+ sagaHistory: history,
1517
+ deltaFrame: sagaIbGib,
1518
+ localTempSpace: myTempSpace,
1519
+ localSpace: mySpace,
1520
+ metaspace,
1521
+ });
1522
+ const commitFrame = await this.createCommitFrame({
1523
+ sagaIbGib,
1524
+ errors: undefined,
1525
+ metaspace,
1526
+ mySpace,
1527
+ identity,
1528
+ });
1529
+ return { frame: commitFrame, };
1530
+ }
1531
+ else {
1532
+ // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
1533
+ // But we are empty.
1534
+ // So WE propose commit.
1535
+ const responseDeltaData = {
1651
1536
  sagaId: deltaData.sagaId,
1652
- stage: SyncStage.commit,
1653
- success: true,
1537
+ stage: SyncStage.delta,
1538
+ proposeCommit: true,
1654
1539
  };
1655
- const commitStone = await this.createSyncMsgStone({
1656
- data: commitData,
1657
- localSpace: tempSpace,
1540
+ const deltaStone = await this.createSyncMsgStone({
1541
+ data: responseDeltaData,
1542
+ localSpace: mySpace,
1658
1543
  metaspace
1659
1544
  });
1660
- const commitFrame = await this.evolveSyncSagaIbGib({
1661
- prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1662
- msgStones: [commitStone],
1545
+ const deltaFrame = await this.evolveSyncSagaIbGib({
1546
+ prevSagaIbGib: sagaIbGib,
1547
+ msgStones: [deltaStone],
1663
1548
  sessionIdentity: identity,
1664
- localSpace: tempSpace,
1549
+ localSpace: mySpace,
1665
1550
  metaspace
1666
1551
  });
1667
- // Build control payloads for commit
1668
- const commitCtrlPayloads2 = [commitFrame, commitStone];
1552
+ // Check if PEER proposed commit
1553
+ if (deltaData.proposeCommit) {
1554
+ if (logalot) {
1555
+ console.log(`${lc} Peer proposed commit. Accepting & Committing.`);
1556
+ }
1557
+ // Peer wants to commit and has no more requests.
1558
+ // We should Commit.
1559
+ const commitData = {
1560
+ sagaId: deltaData.sagaId,
1561
+ stage: SyncStage.commit,
1562
+ success: true,
1563
+ };
1564
+ const commitStone = await this.createSyncMsgStone({
1565
+ data: commitData,
1566
+ localSpace: mySpace,
1567
+ metaspace
1568
+ });
1569
+ const commitFrame = await this.evolveSyncSagaIbGib({
1570
+ prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1571
+ msgStones: [commitStone],
1572
+ sessionIdentity: identity,
1573
+ localSpace: mySpace,
1574
+ metaspace
1575
+ });
1576
+ // Build control payloads for commit
1577
+ const commitCtrlPayloads2 = [commitFrame, commitStone];
1578
+ if (identity) {
1579
+ commitCtrlPayloads2.push(identity);
1580
+ }
1581
+ return { frame: commitFrame, };
1582
+ }
1583
+ // Build control payloads for delta propose
1584
+ const deltaCtrlPayloads = [deltaFrame, deltaStone];
1669
1585
  if (identity) {
1670
- commitCtrlPayloads2.push(identity);
1586
+ deltaCtrlPayloads.push(identity);
1671
1587
  }
1672
- // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1673
- // return { frame: commitFrame, };
1674
- throw new Error(`not implemented (E: 27514878585889e531ef21f1abbef826)`);
1588
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1589
+ }
1590
+ }
1591
+ }
1592
+ catch (error) {
1593
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1594
+ throw error;
1595
+ }
1596
+ finally {
1597
+ if (logalot) {
1598
+ console.log(`${lc} complete.`);
1599
+ }
1600
+ }
1601
+ }
1602
+ /**
1603
+ * should throw if fails
1604
+ */
1605
+ async executeLocalCommit({ deltaFrame, sagaHistory, metaspace, localSpace, localTempSpace, identity, }) {
1606
+ const lc = `${this.lc}[${this.executeLocalCommit.name}]`;
1607
+ try {
1608
+ if (logalot) {
1609
+ console.log(`${lc} starting... (I: 6734980446b86a63c1af6e2e206de826)`);
1610
+ }
1611
+ // #region validate/sanity
1612
+ if (!deltaFrame.data) {
1613
+ throw new Error(`(UNEXPECTED) deltaFrame.data falsy? (E: a8be68d48668d93d992d793834823826)`);
1614
+ }
1615
+ // #endregion validate/sanity
1616
+ // * move all payload addrs from temp space to local space
1617
+ // * register each and every iteration of each timeline (including
1618
+ // stones, which are like their own sealed timeline of length 1)
1619
+ const allPayloadAddrsDomainTransferred = [];
1620
+ const fnAddPayloadAddr = (addr) => {
1621
+ if (!allPayloadAddrsDomainTransferred.includes(addr)) {
1622
+ allPayloadAddrsDomainTransferred.push(addr);
1675
1623
  }
1676
- // Build control payloads for delta propose
1677
- const deltaCtrlPayloads = [deltaFrame, deltaStone];
1678
- if (identity) {
1679
- deltaCtrlPayloads.push(identity);
1624
+ };
1625
+ sagaHistory.forEach(x => {
1626
+ if (!x.sagaIbGib.data) {
1627
+ throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 34d7f8cdee14717ce828878d98f89826)`);
1680
1628
  }
1681
- // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1682
- // return { frame: deltaFrame, };
1683
- throw new Error(`not implemented (E: ff35584696b6fcb3ad6dd7c5cade2f26)`);
1629
+ x.msgStones.forEach(msgStone => {
1630
+ if (!msgStone.data) {
1631
+ throw new Error(`(UNEXPECTED) msgStone.data falsy? (E: 21406101f91847514cc759c8a7382f26)`);
1632
+ }
1633
+ if (msgStone.data.stage === SyncStage.ack) {
1634
+ const ackData = msgStone.data;
1635
+ if (ackData.pushOfferInfos && ackData.pushOfferInfos.length > 0) {
1636
+ ackData.pushOfferInfos.forEach(info => {
1637
+ info.addrs.forEach(addr => fnAddPayloadAddr(addr));
1638
+ });
1639
+ }
1640
+ }
1641
+ else if (msgStone.data.stage === SyncStage.delta) {
1642
+ const deltaData = msgStone.data;
1643
+ if (deltaData.payloadAddrsDomain && deltaData.payloadAddrsDomain.length > 0) {
1644
+ deltaData.payloadAddrsDomain.forEach(addr => fnAddPayloadAddr(addr));
1645
+ }
1646
+ }
1647
+ });
1648
+ });
1649
+ // at this point, we have a list of ALL payload addrs retrieved.
1650
+ let allPayloadIbGibsDomainTransferred = [];
1651
+ const resGetAllTransferred = await getFromSpace({ addrs: allPayloadAddrsDomainTransferred, space: localTempSpace });
1652
+ if (resGetAllTransferred.success && resGetAllTransferred.ibGibs && resGetAllTransferred.ibGibs.length === allPayloadAddrsDomainTransferred.length) {
1653
+ allPayloadIbGibsDomainTransferred = resGetAllTransferred.ibGibs.concat();
1654
+ }
1655
+ else {
1656
+ // errored out, gather info
1657
+ if (!resGetAllTransferred.rawResultIbGib) {
1658
+ throw new Error(`(UNEXPECTED) !resGetAllTransferred.rawResultIbGib falsy? (E: dc6cf8729668f4fbe8c024f887d97a26)`);
1659
+ }
1660
+ const { addrsNotFound, addrsErrored, errors } = resGetAllTransferred.rawResultIbGib.data;
1661
+ throw new Error(`(UNEXPECTED) we couldn't get all addrs transferred throughout the saga from the tempspace (${localTempSpace.ib})? addrsNotFound: ${addrsNotFound ?? []}. addrsErrored: ${addrsErrored ?? []}. errors: ${errors ?? []}(E: 222e341e634862b4d88ae7282584d826)`);
1662
+ }
1663
+ // now we have all ibgibs transferred, first put them all the local space
1664
+ const { payload_Dnas, payload_NonDnas } = await putInSpace_dnasThenNonDnas({
1665
+ ibGibs: allPayloadIbGibsDomainTransferred,
1666
+ space: localSpace
1667
+ });
1668
+ const { mapWithTjp_NoDna, mapWithTjp_YesDna, mapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs: payload_NonDnas, filterPrimitives: true });
1669
+ // first register all non-tjp stones
1670
+ const nontjps = Object.values(mapWithoutTjps);
1671
+ for (const nontjp of nontjps) {
1672
+ await metaspace.registerNewIbGib({
1673
+ ibGib: nontjp,
1674
+ space: localSpace,
1675
+ });
1676
+ }
1677
+ // next register each timeline in order...
1678
+ // ...first the ones without dna...
1679
+ const timelinesByTjpAddr_NoDna = getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_NoDna) });
1680
+ const tjpAddrs_NoDna = Object.keys(timelinesByTjpAddr_NoDna);
1681
+ for (const tjpAddr_NoDna of tjpAddrs_NoDna) {
1682
+ const timelineIbGibs = timelinesByTjpAddr_NoDna[tjpAddr_NoDna];
1683
+ for (const ibGib of timelineIbGibs) {
1684
+ await metaspace.registerNewIbGib({
1685
+ ibGib,
1686
+ space: localSpace,
1687
+ });
1688
+ }
1689
+ }
1690
+ // ...then the ones WITH dna.
1691
+ const timelinesByTjpAddr_YesDna = getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_YesDna) });
1692
+ const tjpAddrs_YesDna = Object.keys(timelinesByTjpAddr_YesDna);
1693
+ for (const tjpAddr_YesDna of tjpAddrs_YesDna) {
1694
+ const timelineIbGibs = timelinesByTjpAddr_YesDna[tjpAddr_YesDna];
1695
+ for (const ibGib of timelineIbGibs) {
1696
+ await metaspace.registerNewIbGib({
1697
+ ibGib,
1698
+ space: localSpace,
1699
+ });
1700
+ }
1701
+ }
1702
+ }
1703
+ catch (error) {
1704
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1705
+ throw error;
1706
+ }
1707
+ finally {
1708
+ if (logalot) {
1709
+ console.log(`${lc} complete.`);
1710
+ }
1711
+ }
1712
+ }
1713
+ async createCommitFrame({ sagaIbGib, errors, metaspace, mySpace, identity, }) {
1714
+ const lc = `[${this.createCommitFrame.name}]`;
1715
+ try {
1716
+ if (logalot) {
1717
+ console.log(`${lc} starting... (I: 48fc57c023f80122135a284855757526)`);
1718
+ }
1719
+ const commitData = errors && errors.length > 0 ?
1720
+ {
1721
+ // errored
1722
+ sagaId: sagaIbGib.data.uuid,
1723
+ stage: SyncStage.commit,
1724
+ success: false,
1725
+ errors,
1726
+ } :
1727
+ {
1728
+ // not errored
1729
+ sagaId: sagaIbGib.data.uuid,
1730
+ stage: SyncStage.commit,
1731
+ success: true,
1732
+ };
1733
+ const commitStone = await this.createSyncMsgStone({
1734
+ data: commitData,
1735
+ localSpace: mySpace,
1736
+ metaspace
1737
+ });
1738
+ const commitFrame = await this.evolveSyncSagaIbGib({
1739
+ prevSagaIbGib: sagaIbGib,
1740
+ msgStones: [commitStone],
1741
+ sessionIdentity: identity,
1742
+ localSpace: mySpace,
1743
+ metaspace,
1744
+ });
1745
+ return commitFrame;
1746
+ }
1747
+ catch (error) {
1748
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1749
+ throw error;
1750
+ }
1751
+ finally {
1752
+ if (logalot) {
1753
+ console.log(`${lc} complete.`);
1684
1754
  }
1685
1755
  }
1686
1756
  }
1687
- async handleCommitFrame({ sagaIbGib, destSpace, tempSpace, metaspace, identity, }) {
1757
+ async handleCommitFrame({ sagaIbGib, mySpace, myTempSpace, metaspace, identity, }) {
1688
1758
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1689
- if (logalot) {
1690
- console.log(`${lc} Commit received.`);
1759
+ try {
1760
+ if (logalot) {
1761
+ console.log(`${lc} starting... (I: e179573bdd881202f8ba3168da1c3826)`);
1762
+ }
1763
+ let resNextSagaFrameInfo;
1764
+ // Sender Logic (Finalizing):
1765
+ // If we are here, we received a Commit frame from the Peer.
1766
+ // This implies the Peer has successfully committed.
1767
+ // We should now:
1768
+ // 1. Validate (implicitly done by receiving valid frame)
1769
+ // 2. Perform our own cleanup (Temp -> Dest, if applicable)
1770
+ // 3. Return saga completion.
1771
+ // one last validate entire history?
1772
+ const history = await getFullSyncSagaHistory({
1773
+ sagaIbGib,
1774
+ space: mySpace,
1775
+ });
1776
+ const validationErrors = await validateFullSyncSagaHistory({
1777
+ history,
1778
+ });
1779
+ if (validationErrors.length > 0) {
1780
+ const errorCommitFrame = await this.createCommitFrame({
1781
+ sagaIbGib,
1782
+ metaspace,
1783
+ mySpace,
1784
+ identity,
1785
+ errors: validationErrors,
1786
+ });
1787
+ return { frame: errorCommitFrame, }; /* <<<< returns early */
1788
+ }
1789
+ await this.executeLocalCommit({
1790
+ deltaFrame: sagaIbGib,
1791
+ sagaHistory: history,
1792
+ localSpace: mySpace,
1793
+ localTempSpace: myTempSpace,
1794
+ metaspace,
1795
+ });
1796
+ // todo: implement explicit cleanup logic here and in peer
1797
+ console.error(`${lc} NAG ERROR (NOT THROWN): implement cleanup logic, including add a cleanup method to the peer (E: 3a9a24befb98a981a88fbdbf52920e26)`);
1798
+ if (logalot) {
1799
+ console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`);
1800
+ }
1801
+ // the holy grail!
1802
+ return { sagaComplete: true };
1691
1803
  }
1692
- // Sender Logic (Finalizing):
1693
- // If we are here, we received a Commit frame from the Peer.
1694
- // This implies the Peer has successfully committed.
1695
- // We should now:
1696
- // 1. Validate (implicitly done by receiving valid frame)
1697
- // 2. Perform our own cleanup (Temp -> Dest, if applicable)
1698
- // 3. Return null to signal saga completion.
1699
- // Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
1700
- if (logalot) {
1701
- console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`);
1804
+ catch (error) {
1805
+ const emsg = `${lc} ${extractErrorMsg(error)}`;
1806
+ console.error(emsg);
1807
+ const errorCommitFrame = await this.createCommitFrame({
1808
+ sagaIbGib,
1809
+ errors: [emsg],
1810
+ metaspace,
1811
+ mySpace,
1812
+ identity,
1813
+ });
1814
+ return { frame: errorCommitFrame };
1815
+ }
1816
+ finally {
1817
+ if (logalot) {
1818
+ console.log(`${lc} complete.`);
1819
+ }
1702
1820
  }
1703
- // return { responseWasNull: true };
1704
- throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
1705
1821
  }
1706
1822
  // #endregion Handlers
1707
1823
  async createSyncMsgStone({ data, localSpace, metaspace, }) {
@@ -1734,6 +1850,69 @@ export class SyncSagaCoordinator {
1734
1850
  }
1735
1851
  }
1736
1852
  }
1853
+ async getPayloadsForRequestedInfos({ deltaRequestAddrInfos, mySpace, }) {
1854
+ const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
1855
+ try {
1856
+ if (logalot) {
1857
+ console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`);
1858
+ }
1859
+ /**
1860
+ * graph of ibgibs we will send to the receiver. addr-based, so will
1861
+ * already be unique (no need to call `unique` on the domains
1862
+ * ibgibs)
1863
+ */
1864
+ const outgoingPayloadIbGibsDomainGraph = {};
1865
+ for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
1866
+ let deltaDepGraph;
1867
+ if (latestAddrAlreadyHave) {
1868
+ // already has some, so only get the delta
1869
+ // remember: if we didn't have the other's latest addr, then
1870
+ // this would be in the conflicts not requested addrs
1871
+ deltaDepGraph = await getDeltaDependencyGraph({
1872
+ ibGibAddr: addr,
1873
+ latestCommonFrameAddr: latestAddrAlreadyHave,
1874
+ space: mySpace,
1875
+ live: true,
1876
+ });
1877
+ }
1878
+ else {
1879
+ // doesn't have anything, so get the entire dependency graph
1880
+ // INEFFICIENT: we've already gotten all of the domain
1881
+ // dependencies in initDomainGraph, but getDependencyGraph
1882
+ // only works against a space so we are going to rerun this.
1883
+ // an optimization would be to adapt/create a new
1884
+ // getDependencyGraph to work against an existing flat ibgib
1885
+ // map.
1886
+ deltaDepGraph = await getDependencyGraph({
1887
+ ibGibAddr: addr,
1888
+ space: mySpace,
1889
+ live: true,
1890
+ });
1891
+ }
1892
+ const depGraphSize = Object.keys(deltaDepGraph).length;
1893
+ if (depGraphSize === 0) {
1894
+ throw new Error(`(UNEXPECTED) couldn't get requested addrs in mySpace (${mySpace.ib})? How did the receiver know to ask for these addrs if they weren't in our original graph? (E: 281eaebcdf77dd73e8245b2872100826)`);
1895
+ }
1896
+ else {
1897
+ // we have dependencies!
1898
+ Object.values(deltaDepGraph).forEach(x => {
1899
+ outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
1900
+ });
1901
+ }
1902
+ }
1903
+ const result = Object.values(outgoingPayloadIbGibsDomainGraph);
1904
+ return result;
1905
+ }
1906
+ catch (error) {
1907
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1908
+ throw error;
1909
+ }
1910
+ finally {
1911
+ if (logalot) {
1912
+ console.log(`${lc} complete.`);
1913
+ }
1914
+ }
1915
+ }
1737
1916
  /**
1738
1917
  * Evolves the saga timeline with a new frame.
1739
1918
  */