@ibgib/core-gib 0.1.25 → 0.1.27

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 (46) hide show
  1. package/dist/common/other/ibgib-helper.d.mts +1 -1
  2. package/dist/common/other/ibgib-helper.d.mts.map +1 -1
  3. package/dist/common/other/ibgib-helper.mjs.map +1 -1
  4. package/dist/sync/sync-constants.d.mts +1 -0
  5. package/dist/sync/sync-constants.d.mts.map +1 -1
  6. package/dist/sync/sync-constants.mjs +1 -0
  7. package/dist/sync/sync-constants.mjs.map +1 -1
  8. package/dist/sync/sync-helpers.d.mts +21 -1
  9. package/dist/sync/sync-helpers.d.mts.map +1 -1
  10. package/dist/sync/sync-helpers.mjs +137 -4
  11. package/dist/sync/sync-helpers.mjs.map +1 -1
  12. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +0 -2
  13. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  14. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +169 -62
  15. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  16. package/dist/sync/sync-peer/sync-peer-v1.d.mts +13 -29
  17. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  18. package/dist/sync/sync-peer/sync-peer-v1.mjs +18 -57
  19. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  20. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +15 -1
  21. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  22. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +77 -7
  23. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  24. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +8 -0
  25. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  26. package/dist/sync/sync-saga-coordinator.d.mts +76 -17
  27. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  28. package/dist/sync/sync-saga-coordinator.mjs +545 -480
  29. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  30. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -1
  31. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  32. package/dist/sync/sync-types.d.mts +31 -4
  33. package/dist/sync/sync-types.d.mts.map +1 -1
  34. package/dist/sync/sync-types.mjs +32 -0
  35. package/dist/sync/sync-types.mjs.map +1 -1
  36. package/package.json +2 -2
  37. package/src/common/other/ibgib-helper.mts +1 -1
  38. package/src/sync/sync-constants.mts +1 -0
  39. package/src/sync/sync-helpers.mts +132 -4
  40. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +169 -47
  41. package/src/sync/sync-peer/sync-peer-v1.mts +33 -74
  42. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +69 -9
  43. package/src/sync/sync-saga-context/sync-saga-context-types.mts +13 -5
  44. package/src/sync/sync-saga-coordinator.mts +628 -530
  45. package/src/sync/sync-saga-message/sync-saga-message-types.mts +18 -18
  46. package/src/sync/sync-types.mts +40 -3
@@ -19,21 +19,25 @@ import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space
19
19
  import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
20
20
  import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
21
21
  import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
22
- import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
23
- import { appendToTimeline, createTimeline, getHistory, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
22
+ import {
23
+ SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN,
24
+ } from "./sync-constants.mjs";
25
+ import {
26
+ appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo
27
+ } from "../timeline/timeline-api.mjs";
24
28
  import {
25
29
  SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
26
30
  SyncRel8ns_V1, DomainIbGibAnalysisInfo, NextSagaFrameInfo,
27
- SYNC_CONFLICT_STRATEGY_VALID_VALUES,
28
- HandleSagaResponseContextResult,
31
+ SYNC_CONFLICT_STRATEGY_VALID_VALUES, HandleSagaResponseContextResult,
32
+ SyncExecutionContext,
33
+ SYNC_EXECUTION_CONTEXT_VALID_VALUES,
29
34
  } from "./sync-types.mjs";
30
- import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
35
+ import { getExecutionContext, getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
31
36
  import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
32
37
  import {
33
38
  SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
34
39
  SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
35
- SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
36
- SyncSagaPushOfferInfo,
40
+ SyncSagaMessageCommitData_V1, SyncSagaConflictInfo, SyncSagaPushOfferInfo,
37
41
  SyncSagaRequestAddrInfo,
38
42
  } from "./sync-saga-message/sync-saga-message-types.mjs";
39
43
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
@@ -49,14 +53,12 @@ import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
49
53
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
50
54
  import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
51
55
  import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
52
- import {
53
- IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
54
- } from "../witness/space/space-types.mjs";
56
+ import { IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns } from "../witness/space/space-types.mjs";
55
57
  import { FlatIbGibGraph } from "../common/other/graph-types.mjs";
56
58
 
57
59
 
58
60
  // const logalot = GLOBAL_LOG_A_LOT || true;
59
- const logalot = false;
61
+ const logalot = true;
60
62
  const logalotControlDomain = true;
61
63
  const lcControlDomain = '[ControlDomain]';
62
64
 
@@ -95,7 +97,7 @@ export class SyncSagaCoordinator {
95
97
  * @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
96
98
  * @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
97
99
  */
98
- async sync({
100
+ public async sync({
99
101
  peer,
100
102
  domainIbGibs,
101
103
  conflictStrategy = SyncConflictStrategy.abort,
@@ -191,6 +193,91 @@ export class SyncSagaCoordinator {
191
193
  };
192
194
  }
193
195
 
196
+ /**
197
+ * This is what the receiving side of the sync calls to drive the FSM to the
198
+ * next stage.
199
+ *
200
+ * So whereas the sender executes a saga loop and drives the entire process,
201
+ * this is a reactive one-off that drives just the single step that the
202
+ * receiver does in that saga.
203
+ *
204
+ * @returns next context result if another round, else if commit returns
205
+ * null
206
+ */
207
+ public async continueSync({
208
+ sagaContext,
209
+ mySpace,
210
+ myTempSpace,
211
+ identity,
212
+ identitySecret,
213
+ metaspace,
214
+ }: {
215
+ sagaContext: SyncSagaContextIbGib_V1,
216
+ /**
217
+ * Local space relative to the execution context's POV
218
+ */
219
+ mySpace: IbGibSpaceAny,
220
+ /**
221
+ * Local temp space relative to the execution context's POV
222
+ */
223
+ myTempSpace: IbGibSpaceAny,
224
+ identity?: KeystoneIbGib_V1,
225
+ identitySecret?: string,
226
+ metaspace: MetaspaceService,
227
+ }): Promise<SyncSagaContextIbGib_V1 | null> {
228
+ const lc = `${this.lc}[${this.continueSync.name}]`;
229
+ try {
230
+ if (logalot) { console.log(`${lc} starting... (I: f64e08bf77d1425378601f380384ec26)`); }
231
+
232
+ const contextResult = await this.handleResponseSagaContext({
233
+ sagaContext,
234
+ mySpace,
235
+ myTempSpace,
236
+ identity,
237
+ identitySecret,
238
+ metaspace,
239
+ });
240
+
241
+ if (!contextResult) {
242
+ if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`); }
243
+ return null;
244
+ }
245
+
246
+ // #region error conditions throw
247
+ if (contextResult.errorMsg) {
248
+ throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: 7b41a183cf3cb58a5859c803800cf826)`);
249
+ } else if (!contextResult.nextFrameInfo) {
250
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
251
+ } else if (contextResult.nextFrameInfo?.responseWasNull) {
252
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
253
+ }
254
+ // #endregion error conditions throw
255
+
256
+ // create the return context
257
+ const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
258
+
259
+ const responseCtx = await createSyncSagaContext({
260
+ sagaFrame: frame,
261
+ localSpace: mySpace,
262
+ payloadIbGibsDomain,
263
+ // todo: we need to thoroughly go through the identity per each step after getting basic merging
264
+ sessionKeystones: identity ? [identity] : undefined, // ??
265
+ });
266
+
267
+ const immediateValidationErrors = await validateContextAndSagaFrame({
268
+ context: responseCtx,
269
+ });
270
+ if (immediateValidationErrors.length > 0) { throw new Error(`(UNEXPECTED) just created sync saga context () and there were immediateValidationErrors? immediateValidationErrors: ${immediateValidationErrors} (E: c120e8e0aa98673d685267a8a36e5826)`); }
271
+
272
+ return responseCtx;
273
+ } catch (error) {
274
+ console.error(`${lc} ${extractErrorMsg(error)}`);
275
+ throw error;
276
+ } finally {
277
+ if (logalot) { console.log(`${lc} complete.`); }
278
+ }
279
+ }
280
+
194
281
  protected async getSessionIdentity({
195
282
  sagaId,
196
283
  metaspace,
@@ -253,6 +340,12 @@ export class SyncSagaCoordinator {
253
340
  metaspace
254
341
  }: {
255
342
  initFrame: SyncIbGib_V1,
343
+ /**
344
+ * This is the initial dependency graph of all domain ibgibs passed in
345
+ * to the original {@link sync} call.
346
+ *
347
+ * if we're executing on the sender, this will be populated
348
+ */
256
349
  initDomainGraph: FlatIbGibGraph,
257
350
  peer: SyncPeerWitness,
258
351
  sessionIdentity?: KeystoneIbGib_V1,
@@ -362,8 +455,11 @@ export class SyncSagaCoordinator {
362
455
  if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
363
456
  updates$.next(responseCtx); // spins off for saga UI updating
364
457
 
365
- // validate context
366
- await validateContextAndSagaFrame
458
+ // immediately validate context/saga frame (but not payloads because
459
+ // we may not have those yet)
460
+ const contextAndSagaFrameValidationErrors =
461
+ await validateContextAndSagaFrame({ context: responseCtx });
462
+ if (contextAndSagaFrameValidationErrors.length > 0) { throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`); }
367
463
 
368
464
  // Extract expected domain addresses from response context
369
465
  const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
@@ -399,6 +495,7 @@ export class SyncSagaCoordinator {
399
495
  // finished/errored out.
400
496
  const contextResult = await this.handleResponseSagaContext({
401
497
  sagaContext: responseCtx,
498
+ initDomainGraph,
402
499
  mySpace: localSpace,
403
500
  myTempSpace: tempSpace,
404
501
  metaspace,
@@ -625,7 +722,7 @@ export class SyncSagaCoordinator {
625
722
  localSpace,
626
723
  });
627
724
 
628
- // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
725
+ if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
629
726
 
630
727
  return { initFrame: sagaFrame, initDomainGraph: fullGraph };
631
728
  } catch (error) {
@@ -708,6 +805,7 @@ export class SyncSagaCoordinator {
708
805
 
709
806
  /**
710
807
  * This is the heart of the "ping pong" transaction, where we send a context
808
+ *
711
809
  * and receive a context. IOW, this drives the FSM of the sync saga ibgib as
712
810
  * a whole.
713
811
  *
@@ -728,6 +826,7 @@ export class SyncSagaCoordinator {
728
826
  */
729
827
  public async handleResponseSagaContext({
730
828
  sagaContext,
829
+ initDomainGraph,
731
830
  mySpace,
732
831
  myTempSpace,
733
832
  identity,
@@ -735,6 +834,13 @@ export class SyncSagaCoordinator {
735
834
  metaspace,
736
835
  }: {
737
836
  sagaContext: SyncSagaContextIbGib_V1,
837
+ /**
838
+ * This is the initial dependency graph of all domain ibgibs passed in
839
+ * to the original {@link sync} call.
840
+ *
841
+ * if we're executing on the sender, this will be populated
842
+ */
843
+ initDomainGraph?: FlatIbGibGraph,
738
844
  /**
739
845
  * Local space relative to the execution context's POV
740
846
  */
@@ -746,7 +852,7 @@ export class SyncSagaCoordinator {
746
852
  identity?: KeystoneIbGib_V1,
747
853
  identitySecret?: string,
748
854
  metaspace: MetaspaceService,
749
- }): Promise<HandleSagaResponseContextResult | null> {
855
+ }): Promise<HandleSagaResponseContextResult> {
750
856
  const lc = `${this.lc}[${this.handleResponseSagaContext.name}]`;
751
857
  try {
752
858
  if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
@@ -756,7 +862,7 @@ export class SyncSagaCoordinator {
756
862
  if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
757
863
 
758
864
  // Get Stage from Stone (or Frame for Init fallback)
759
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
865
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
760
866
 
761
867
  if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
762
868
 
@@ -771,30 +877,44 @@ export class SyncSagaCoordinator {
771
877
  nextFrameInfo = await this.handleInitFrame({
772
878
  sagaIbGib,
773
879
  messageData: messageData as SyncSagaMessageInitData_V1,
774
- metaspace,
775
- mySpace: mySpace,
776
- myTempSpace: myTempSpace,
777
- identity,
778
- identitySecret
880
+ metaspace, mySpace, myTempSpace,
881
+ identity, identitySecret
779
882
  });
780
883
  break;
781
884
 
782
885
  case SyncStage.ack:
783
- nextFrameInfo = await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
886
+ if (!initDomainGraph) { throw new Error(`(UNEXPECTED) initDomainGraph falsy on the sender? (E: a3d758ad954829aba88663188eafc826)`); }
887
+ nextFrameInfo = await this.handleAckFrame({
888
+ sagaContext,
889
+ sagaIbGib,
890
+ initDomainGraph,
891
+ metaspace, mySpace, myTempSpace,
892
+ identity,
893
+ });
784
894
  break;
785
895
 
786
896
  case SyncStage.delta:
787
- nextFrameInfo = await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
897
+ nextFrameInfo = await this.handleDeltaFrame({
898
+ sagaContext,
899
+ sagaIbGib,
900
+ srcGraph,
901
+ metaspace,
902
+ mySpace,
903
+ myTempSpace,
904
+ identity,
905
+ });
788
906
  break;
789
907
 
790
908
  case SyncStage.commit:
791
- nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
909
+ nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, mySpace: mySpace, myTempSpace: myTempSpace, identity, });
792
910
  break;
793
911
 
794
912
  default:
795
913
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
796
914
  }
797
915
 
916
+ if (logalot) { console.log(`${lc} nextFrameInfo: ${nextFrameInfo ? pretty(nextFrameInfo) : 'undefined'} (I: a8ad281ca8e89385686d18327a105726)`); }
917
+
798
918
  return { errorMsg: undefined, nextFrameInfo, }
799
919
 
800
920
  } catch (error) {
@@ -817,7 +937,7 @@ export class SyncSagaCoordinator {
817
937
  * The Receiver performs Gap Analysis here:
818
938
  * 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
819
939
  * 2. Identifies what Sender needs (`pushOfferAddrs`).
820
- * 3. Identifies what Receiver needs (`deltaReqAddrs`).
940
+ * 3. Identifies what Receiver needs (`deltaRequestAddrInfos`).
821
941
  * 4. Returns an `Ack` frame containing these lists.
822
942
  */
823
943
  protected async handleInitFrame({
@@ -1070,7 +1190,7 @@ export class SyncSagaCoordinator {
1070
1190
 
1071
1191
  const ackStone = await this.createSyncMsgStone({
1072
1192
  data: ackData,
1073
- localSpace: myTempSpace,
1193
+ localSpace: mySpace,
1074
1194
  metaspace,
1075
1195
  });
1076
1196
  if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
@@ -1090,8 +1210,6 @@ export class SyncSagaCoordinator {
1090
1210
  * we want to push ibgibs to the remote/sender if we have push
1091
1211
  * offers. an ack frame's payloads, if any, are those push offers
1092
1212
  */
1093
- // let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
1094
-
1095
1213
  let payloadIbGibsDomain: IbGib_V1[] | undefined;
1096
1214
  if (pushOfferInfos.length > 0) {
1097
1215
  const searchSecondSpaceAddrs: IbGibAddr[] = [];
@@ -1143,12 +1261,10 @@ export class SyncSagaCoordinator {
1143
1261
  // we have now populated payloadIbGibsDomain
1144
1262
  }
1145
1263
 
1146
- throw new Error(`not implemented (E: ed3f98abb0988c5ae8038bb8d741fb26)`);
1147
- // return {
1148
- // frame: ackFrame,
1149
- // // conflictInfos,
1150
- // payloadIbGibsDomain,
1151
- // };
1264
+ return {
1265
+ frame: ackFrame,
1266
+ payloadIbGibsDomain,
1267
+ };
1152
1268
 
1153
1269
  } catch (error) {
1154
1270
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -1165,23 +1281,38 @@ export class SyncSagaCoordinator {
1165
1281
  * **Execution Context**: **Sender (Local)**.
1166
1282
  *
1167
1283
  * The Sender reacts to the Receiver's requirements:
1168
- * 1. `deltaReqAddrs`: Receiver wants this data. Sender gathers it from `srcGraph` and puts it in `payloadIbGibs` (for next frame).
1169
- * 2. `pushOfferAddrs`: Receiver has newer data. Sender acknowledges and adds them to `requests` (asking Receiver to send them).
1284
+ * 1. `deltaRequestAddrInfos`: Receiver wants this data. Sender takes this
1285
+ * into account, plus gathers it from `initDomainGraph` and puts them in
1286
+ * `payloadIbGibs` (for next frame).
1287
+ * 2. `pushOfferAddrs`: Receiver has newer data. these should have been
1288
+ * included in the incoming context from the receiver..
1170
1289
  *
1171
1290
  * Returns a `Delta` frame.
1172
1291
  */
1173
1292
  protected async handleAckFrame({
1293
+ sagaContext,
1174
1294
  sagaIbGib,
1175
- srcGraph,
1176
- destSpace,
1177
- tempSpace,
1178
- metaspace,
1295
+ initDomainGraph,
1296
+ mySpace, myTempSpace, metaspace,
1179
1297
  identity,
1180
1298
  }: {
1299
+ /**
1300
+ * todo: figure out if we need to do something about incoming push offer payload domain ibgibs
1301
+ * I'm adding this because we should be checking incoming payloads
1302
+ * right? for push offers? This is when the receiver had new ibgibs.
1303
+ * Not sure if I need to put this here though...
1304
+ */
1305
+ sagaContext: SyncSagaContextIbGib_V1,
1181
1306
  sagaIbGib: SyncIbGib_V1,
1182
- srcGraph: { [addr: string]: IbGib_V1 },
1183
- destSpace: IbGibSpaceAny,
1184
- tempSpace: IbGibSpaceAny,
1307
+ /**
1308
+ * This is the initial dependency graph of all domain ibgibs passed in
1309
+ * to the original {@link sync} call.
1310
+ *
1311
+ * if we're executing on the sender, this will be populated
1312
+ */
1313
+ initDomainGraph: FlatIbGibGraph,
1314
+ mySpace: IbGibSpaceAny,
1315
+ myTempSpace: IbGibSpaceAny,
1185
1316
  metaspace: MetaspaceService,
1186
1317
  identity?: KeystoneIbGib_V1,
1187
1318
  }): Promise<NextSagaFrameInfo> {
@@ -1189,9 +1320,10 @@ export class SyncSagaCoordinator {
1189
1320
  try {
1190
1321
  if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
1191
1322
 
1192
- const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1323
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
1193
1324
  const ackData = messageData as SyncSagaMessageAckData_V1;
1194
1325
 
1326
+ // #region sanity/validation
1195
1327
  if (!ackData) {
1196
1328
  throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
1197
1329
  }
@@ -1199,6 +1331,8 @@ export class SyncSagaCoordinator {
1199
1331
  throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
1200
1332
  }
1201
1333
  if (logalot) { console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`); }
1334
+ if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 385e389610282aa9c5dbe4083adbde26)`); }
1335
+ // #region sanity/validation
1202
1336
 
1203
1337
  // 1. Check for Conflicts
1204
1338
  const conflicts = ackData.conflicts || [];
@@ -1215,11 +1349,17 @@ export class SyncSagaCoordinator {
1215
1349
  throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
1216
1350
  }
1217
1351
 
1218
- const optimisticConflicts = conflicts.filter(c => !c.terminal);
1219
- const mergeDeltaReqs: string[] = []; // Additional requests for merging
1352
+ // at this point, if we have conflicts, they are non-terminal
1353
+
1354
+ /**
1355
+ * at this point, we only request ibgibs for conflicted timelines.
1356
+ * If the receiver had known of any ibgibs this sender needed, it
1357
+ * would have been in the push offer.
1358
+ */
1359
+ const outgoingDeltaAddrRequestInfos: SyncSagaRequestAddrInfo[] = []; // Additional requests for merging
1220
1360
 
1221
- if (optimisticConflicts.length > 0) {
1222
- console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
1361
+ if (conflicts.length > 0) {
1362
+ console.log(`${lc} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
1223
1363
  // We need to resolve these.
1224
1364
  // Strategy:
1225
1365
  // 1. Analyze Divergence (Sender vs Receiver)
@@ -1228,8 +1368,8 @@ export class SyncSagaCoordinator {
1228
1368
  // 4. (Later in Delta Phase) Perform Merge.
1229
1369
 
1230
1370
  // BUT: The Delta Phase is usually generic "Send me these Addrs".
1231
- // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
1232
- // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
1371
+ // If we just add to `deltaRequestAddrInfos` (which are requests for Sender to send to Receiver?),
1372
+ // wait. `ackData.deltaRequestAddrInfos` are what RECEIVER wants from SENDER.
1233
1373
 
1234
1374
  // We (Sender) are processing the Ack.
1235
1375
  // We need to request data FROM Receiver.
@@ -1252,208 +1392,153 @@ export class SyncSagaCoordinator {
1252
1392
 
1253
1393
  // Let's analyze and pull immediately.
1254
1394
 
1255
- for (const conflict of optimisticConflicts) {
1256
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1257
-
1258
- // Sender History
1259
- // We need our own history for this timeline.
1260
- // We know the 'senderTip' (remoteAddr in Ack).
1261
- // Sender should verify it has this tip.
1262
-
1263
- // Compute Diffs
1264
- // We need to find `receiverOnly` addrs.
1265
- // Receiver sent us `timelineAddrs` (Full History).
1266
- const receiverHistorySet = new Set(timelineAddrs);
1267
-
1268
- // We need our execution context's history for this senderTip.
1269
- // We can fetch valid 'past' from space.
1270
- const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1271
- const senderTipIbGib = resSenderTip.ibGibs?.[0];
1272
- if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`); }
1273
-
1274
- // Basic Diff: Find what Receiver has that we don't.
1275
- // Actually, we need to traverse OUR past to find commonality.
1276
- const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1277
-
1278
- const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1279
-
1280
- if (receiverOnlyAddrs.length > 0) {
1281
- console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1282
- console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1283
- // PULL these frames from Peer into Local Space
1284
- // (Validation: We trust peer for now / verification happens on put)
1285
- for (const addr of receiverOnlyAddrs) {
1286
- console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1287
- }
1288
-
1289
- // Compute DELTA dependencies for each receiver-only frame
1290
- // Find LCA to determine what dependencies we already have
1291
- const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1292
- console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1293
-
1294
- const skipAddrsSet = new Set<string>();
1295
- if (lcaAddr) {
1296
- try {
1297
- const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1298
- const lcaIbGib = lcaRes.ibGibs?.[0];
1299
- if (lcaIbGib) {
1300
- const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1301
- if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1302
- console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1303
- }
1304
- } catch (e) {
1305
- console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1306
- }
1307
- }
1308
-
1309
-
1310
- // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1311
- for (const addr of receiverOnlyAddrs) {
1312
- // Add the frame itself first
1313
- if (!mergeDeltaReqs.includes(addr)) {
1314
- mergeDeltaReqs.push(addr);
1315
- }
1316
-
1317
- // Get the frame's delta dependencies (skip LCA's deps)
1318
- try {
1319
- const frameRes = await getFromSpace({ addr, space: destSpace });
1320
- const frameIbGib = frameRes.ibGibs?.[0];
1321
-
1322
- if (frameIbGib) {
1323
- // Get dependency graph, skipping all LCA dependencies
1324
- const frameDeltaDeps = await getDependencyGraph({
1325
- ibGib: frameIbGib,
1326
- space: destSpace,
1327
- skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1328
- });
1329
-
1330
- if (frameDeltaDeps) {
1331
- // Add all delta dependencies (Object.keys gives us the addresses)
1332
- Object.keys(frameDeltaDeps).forEach(depAddr => {
1333
- if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1334
- mergeDeltaReqs.push(depAddr);
1335
- }
1336
- });
1337
- }
1338
- }
1339
- } catch (depError) {
1340
- console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1341
- }
1342
- }
1343
-
1344
- console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1345
- } else {
1346
- console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1347
- }
1395
+ for (const conflict of conflicts) {
1396
+ // todo: integrate conflict strategies into this point...this whole block needs to be redone, but I want to check the fast-forward/backward simpler case tests first
1397
+
1398
+ throw new Error(`conflicts not (re)implemented yet (E: 3b7d0819f83842a6de3ae988819bc826)`);
1399
+
1400
+
1401
+ // const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1402
+
1403
+ // // Sender History
1404
+ // // We need our own history for this timeline.
1405
+ // // We know the 'senderTip' (remoteAddr in Ack).
1406
+ // // Sender should verify it has this tip.
1407
+
1408
+ // // Compute Diffs
1409
+ // // We need to find `receiverOnly` addrs.
1410
+ // // Receiver sent us `timelineAddrs` (Full History).
1411
+ // const receiverHistorySet = new Set(timelineAddrs);
1412
+
1413
+ // // We need our execution context's history for this senderTip.
1414
+ // // We can fetch valid 'past' from space.
1415
+ // const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1416
+ // const senderTipIbGib = resSenderTip.ibGibs?.[0];
1417
+ // if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`); }
1418
+
1419
+ // // Basic Diff: Find what Receiver has that we don't.
1420
+ // // Actually, we need to traverse OUR past to find commonality.
1421
+ // const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1422
+
1423
+ // const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1424
+
1425
+ // if (receiverOnlyAddrs.length > 0) {
1426
+ // console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1427
+ // console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1428
+ // // PULL these frames from Peer into Local Space
1429
+ // // (Validation: We trust peer for now / verification happens on put)
1430
+ // for (const addr of receiverOnlyAddrs) {
1431
+ // console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1432
+ // }
1433
+
1434
+ // // Compute DELTA dependencies for each receiver-only frame
1435
+ // // Find LCA to determine what dependencies we already have
1436
+ // const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1437
+ // console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1438
+
1439
+ // const skipAddrsSet = new Set<string>();
1440
+ // if (lcaAddr) {
1441
+ // try {
1442
+ // const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1443
+ // const lcaIbGib = lcaRes.ibGibs?.[0];
1444
+ // if (lcaIbGib) {
1445
+ // const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1446
+ // if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1447
+ // console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1448
+ // }
1449
+ // } catch (e) {
1450
+ // console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1451
+ // }
1452
+ // }
1453
+
1454
+
1455
+ // // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1456
+ // for (const addr of receiverOnlyAddrs) {
1457
+ // // Add the frame itself first
1458
+ // if (!outgoingDeltaAddrRequestInfos.includes(addr)) {
1459
+ // outgoingDeltaAddrRequestInfos.push(addr);
1460
+ // }
1461
+
1462
+ // // Get the frame's delta dependencies (skip LCA's deps)
1463
+ // try {
1464
+ // const frameRes = await getFromSpace({ addr, space: destSpace });
1465
+ // const frameIbGib = frameRes.ibGibs?.[0];
1466
+
1467
+ // if (frameIbGib) {
1468
+ // // Get dependency graph, skipping all LCA dependencies
1469
+ // const frameDeltaDeps = await getDependencyGraph({
1470
+ // ibGib: frameIbGib,
1471
+ // space: destSpace,
1472
+ // skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1473
+ // });
1474
+
1475
+ // if (frameDeltaDeps) {
1476
+ // // Add all delta dependencies (Object.keys gives us the addresses)
1477
+ // Object.keys(frameDeltaDeps).forEach(depAddr => {
1478
+ // if (!outgoingDeltaAddrRequestInfos.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1479
+ // outgoingDeltaAddrRequestInfos.push(depAddr);
1480
+ // }
1481
+ // });
1482
+ // }
1483
+ // }
1484
+ // } catch (depError) {
1485
+ // console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1486
+ // }
1487
+ // }
1488
+
1489
+ // console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${outgoingDeltaAddrRequestInfos.length}`);
1490
+ // } else {
1491
+ // console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1492
+ // }
1348
1493
  }
1349
1494
 
1350
- console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
1495
+ console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. outgoingDeltaAddrRequestInfos: ${outgoingDeltaAddrRequestInfos.length}`);
1351
1496
  } else {
1352
1497
  console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1353
1498
  }
1354
1499
 
1355
1500
  // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1356
1501
 
1357
- const deltaReqAddrs = ackData.deltaRequestAddrInfos || [];
1358
- const pushOfferAddrs = ackData.pushOfferInfos || [];
1359
-
1360
- // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1361
- const pullReqAddrs: string[] = [];
1362
- for (const addr of pushOfferAddrs) {
1363
- // const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1364
- // if (!existing) {
1365
- // pullReqAddrs.push(addr);
1366
- // }
1367
- }
1368
-
1369
- // 2. Process Delta Requests (Push Payload)
1370
- // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1371
- // const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
1372
- const skipAddrs = new Set<string>();
1373
- if (ackData.knowledgeVector) {
1374
- Object.values(ackData.knowledgeVector).forEach(addrs => {
1375
- // addrs.forEach(a => skipAddrs.add(a));
1376
- });
1377
- }
1378
-
1379
- const payloadIbGibs: IbGib_V1[] = [];
1380
- // Gather all tips to sync first
1381
- const tipsToSync: IbGib_V1[] = [];
1382
- for (const addr of deltaReqAddrs) {
1383
- // let ibGib = srcGraph[addr];
1384
- // if (!ibGib) {
1385
- // const res = await getFromSpace({ addr, space: destSpace });
1386
- // if (res.ibGibs && res.ibGibs.length > 0) {
1387
- // ibGib = res.ibGibs[0];
1388
- // }
1389
- // }
1390
- // if (ibGib) {
1391
- // tipsToSync.push(ibGib);
1392
- // } else {
1393
- // throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1394
- // }
1395
- }
1396
-
1397
- // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1398
- // Pass skipAddrs to `getDependencyGraph` or gather manually.
1399
- // `getDependencyGraph` takes a single ibGib.
1400
- // We can optimize by doing it for each tip and unioning the result?
1401
- // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1402
- // We will loop.
1403
-
1404
- const allDepsSet = new Set<string>();
1405
-
1406
- for (const tip of tipsToSync) {
1407
- // Always include the tip itself
1408
- const tipAddr = getIbGibAddr({ ibGib: tip });
1409
- // Only process if not skipped (though deltaReq implies they barely just asked for it)
1410
- // But detailed deps might be skipped.
1411
-
1412
- // Get Graph with Skips
1413
- // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
1414
- const deps = await getDependencyGraph({
1415
- ibGib: tip,
1416
- space: destSpace,
1417
- skipAddrs: Array.from(skipAddrs)
1418
- });
1419
-
1420
- // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
1421
- let tipIncluded = false;
1422
-
1423
- if (deps) {
1424
- Object.values(deps).forEach(d => {
1425
- const dAddr = getIbGibAddr({ ibGib: d });
1426
- if (!allDepsSet.has(dAddr)) {
1427
- allDepsSet.add(dAddr);
1428
- payloadIbGibs.push(d);
1429
- }
1430
- if (dAddr === tipAddr) { tipIncluded = true; }
1431
- });
1432
- }
1433
-
1434
- if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1435
- if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
1436
- if (!allDepsSet.has(tipAddr)) {
1437
- allDepsSet.add(tipAddr);
1438
- payloadIbGibs.push(tip);
1439
- }
1440
- }
1441
- }
1502
+ /**
1503
+ * these were requested addrs on the INCOMING frame (the ack data).
1504
+ *
1505
+ * This is in contrast to any OUTGOING requests we make in this
1506
+ * method.
1507
+ */
1508
+ const payloadIbGibsDomain = await this.getPayloadsForRequestedInfos({
1509
+ deltaRequestAddrInfos: ackData.deltaRequestAddrInfos || [],
1510
+ mySpace,
1511
+ });
1442
1512
 
1443
1513
  // 3. Create Delta Frame
1444
- const sagaId = ackData.sagaId;
1445
1514
  const deltaData: SyncSagaMessageDeltaData_V1 = {
1446
- sagaId: sagaIbGib.data!.uuid,
1515
+ sagaId: sagaIbGib.data.uuid,
1447
1516
  stage: SyncStage.delta,
1448
- payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1449
- requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1517
+ /**
1518
+ * we're sending these domain ibgibs as payload (they were
1519
+ * requested by receiver)
1520
+ */
1521
+ payloadAddrsDomain: payloadIbGibsDomain.length > 0 ?
1522
+ payloadIbGibsDomain.map(x => getIbGibAddr({ ibGib: x })) :
1523
+ undefined,
1524
+ /**
1525
+ * we're asking for these addrs
1526
+ */
1527
+ deltaRequestAddrInfos: outgoingDeltaAddrRequestInfos.length > 0 ?
1528
+ outgoingDeltaAddrRequestInfos :
1529
+ undefined,
1530
+ /**
1531
+ * if we have no changes and request none, propose commit to
1532
+ * finish the sync transaction.
1533
+ */
1534
+ proposeCommit: payloadIbGibsDomain.length === 0 && outgoingDeltaAddrRequestInfos.length === 0,
1450
1535
  };
1451
1536
 
1452
1537
  if (logalot) { console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`); }
1453
1538
 
1454
1539
  const deltaStone = await this.createSyncMsgStone({
1455
1540
  data: deltaData,
1456
- localSpace: tempSpace,
1541
+ localSpace: mySpace,
1457
1542
  metaspace,
1458
1543
  });
1459
1544
 
@@ -1461,7 +1546,7 @@ export class SyncSagaCoordinator {
1461
1546
  prevSagaIbGib: sagaIbGib,
1462
1547
  msgStones: [deltaStone],
1463
1548
  sessionIdentity: identity,
1464
- localSpace: tempSpace,
1549
+ localSpace: mySpace,
1465
1550
  metaspace,
1466
1551
  });
1467
1552
 
@@ -1471,9 +1556,71 @@ export class SyncSagaCoordinator {
1471
1556
  const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1472
1557
  if (identity) { payloadIbGibsControl.push(identity); }
1473
1558
 
1474
- // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1475
- // return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1476
- throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
1559
+ return { frame: deltaFrame, payloadIbGibsDomain, };
1560
+ } catch (error) {
1561
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1562
+ throw error;
1563
+ } finally {
1564
+ if (logalot) { console.log(`${lc} complete.`); }
1565
+ }
1566
+ }
1567
+
1568
+ private async getPayloadsForRequestedInfos({
1569
+ deltaRequestAddrInfos,
1570
+ mySpace,
1571
+ }: {
1572
+ deltaRequestAddrInfos: SyncSagaRequestAddrInfo[];
1573
+ mySpace: IbGibSpaceAny;
1574
+ }): Promise<IbGib_V1[]> {
1575
+ const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
1576
+ try {
1577
+ if (logalot) { console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`); }
1578
+ /**
1579
+ * graph of ibgibs we will send to the receiver. addr-based, so will
1580
+ * already be unique (no need to call `unique` on the domains
1581
+ * ibgibs)
1582
+ */
1583
+ const outgoingPayloadIbGibsDomainGraph: FlatIbGibGraph = {};
1584
+ for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
1585
+ let deltaDepGraph: FlatIbGibGraph;
1586
+ if (latestAddrAlreadyHave) {
1587
+ // already has some, so only get the delta
1588
+ // remember: if we didn't have the other's latest addr, then
1589
+ // this would be in the conflicts not requested addrs
1590
+ deltaDepGraph = await getDeltaDependencyGraph({
1591
+ ibGibAddr: addr,
1592
+ latestCommonFrameAddr: latestAddrAlreadyHave,
1593
+ space: mySpace,
1594
+ live: true,
1595
+ });
1596
+ } else {
1597
+ // doesn't have anything, so get the entire dependency graph
1598
+ // INEFFICIENT: we've already gotten all of the domain
1599
+ // dependencies in initDomainGraph, but getDependencyGraph
1600
+ // only works against a space so we are going to rerun this.
1601
+ // an optimization would be to adapt/create a new
1602
+ // getDependencyGraph to work against an existing flat ibgib
1603
+ // map.
1604
+ deltaDepGraph = await getDependencyGraph({
1605
+ ibGibAddr: addr,
1606
+ space: mySpace,
1607
+ live: true,
1608
+ });
1609
+ }
1610
+
1611
+ const depGraphSize = Object.keys(deltaDepGraph).length;
1612
+ if (depGraphSize === 0) {
1613
+ 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)`);
1614
+ } else {
1615
+ // we have dependencies!
1616
+ Object.values(deltaDepGraph).forEach(x => {
1617
+ outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
1618
+ });
1619
+ }
1620
+ }
1621
+
1622
+ const result = Object.values(outgoingPayloadIbGibsDomainGraph);
1623
+ return result;
1477
1624
  } catch (error) {
1478
1625
  console.error(`${lc} ${extractErrorMsg(error)}`);
1479
1626
  throw error;
@@ -1493,313 +1640,192 @@ export class SyncSagaCoordinator {
1493
1640
  * 3. **Completion**: If no more requests, transitions to `Commit`.
1494
1641
  */
1495
1642
  protected async handleDeltaFrame({
1643
+ sagaContext,
1496
1644
  sagaIbGib,
1497
1645
  srcGraph,
1498
- destSpace,
1499
- tempSpace,
1646
+ mySpace,
1647
+ myTempSpace,
1500
1648
  metaspace,
1501
1649
  identity,
1502
1650
  }: {
1651
+ sagaContext: SyncSagaContextIbGib_V1,
1503
1652
  sagaIbGib: SyncIbGib_V1,
1504
1653
  srcGraph: { [addr: string]: IbGib_V1 },
1505
- destSpace: IbGibSpaceAny,
1506
- tempSpace: IbGibSpaceAny,
1654
+ mySpace: IbGibSpaceAny,
1655
+ myTempSpace: IbGibSpaceAny,
1507
1656
  metaspace: MetaspaceService,
1508
1657
  identity?: KeystoneIbGib_V1,
1509
1658
  }): Promise<NextSagaFrameInfo> {
1510
1659
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1511
- if (logalot) { console.log(`${lc} starting...`); }
1512
-
1513
- const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1514
- const deltaData = messageData as SyncSagaMessageDeltaData_V1;
1515
-
1516
- if (!deltaData) {
1517
- throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
1518
- }
1519
- if (deltaData.stage !== SyncStage.delta) {
1520
- throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
1521
- }
1522
- if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
1523
-
1524
- console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1525
-
1526
- const payloadAddrs = deltaData.payloadAddrs || [];
1527
- const peerRequests = deltaData.requests || [];
1528
- const peerProposesCommit = deltaData.proposeCommit || false;
1529
-
1530
- // 1. Process Received Payload (Ingest)
1531
- const receivedPayloadIbGibs: IbGib_V1[] = [];
1532
- if (payloadAddrs.length > 0) {
1533
- // We use `payloadAddrs` as the manifest.
1534
- // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1535
- // assuming the "Transport" layer put them there implicitly?
1536
- // OR, if we are local-only, we just get them.
1537
- // The `handleDeltaFrame` contract assumes data is reachable in `space`.
1538
-
1539
- const res = await getFromSpace({
1540
- addrs: payloadAddrs,
1541
- space: tempSpace, // Incoming data is in tempSpace
1542
- });
1543
- if (res.ibGibs) {
1544
- receivedPayloadIbGibs.push(...res.ibGibs);
1545
- // Also put them? `getFromSpace` retrieves. If they are in space, they are persisted.
1546
- // If this is a Temp Space, they are safe.
1547
- } else {
1548
- console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
1549
- }
1550
- }
1660
+ try {
1661
+ if (logalot) { console.log(`${lc} starting... (I: a1d0a85eb4189466f86dfd61e3df2626)`); }
1551
1662
 
1552
- // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1553
- const outgoingPayload: IbGib_V1[] = [];
1554
- const outgoingAddrsSet = new Set<string>(); // Track what we've added
1663
+ const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
1664
+ const deltaData = messageData as SyncSagaMessageDeltaData_V1;
1555
1665
 
1556
- console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
1666
+ // #region validate/sanity
1667
+ if (!deltaData) { throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`); }
1668
+ if (deltaData.stage !== SyncStage.delta) { throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`); }
1669
+ if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
1670
+ // #endregion validate/sanity
1557
1671
 
1558
- for (const addr of peerRequests) {
1559
- // Get the requested ibGib
1560
- let ibGib = srcGraph[addr];
1561
- if (!ibGib) {
1562
- const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1563
- if (res.ibGibs && res.ibGibs.length > 0) {
1564
- ibGib = res.ibGibs[0];
1565
- }
1566
- }
1672
+ console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1567
1673
 
1568
- if (ibGib) {
1569
- // Add the requested ibGib itself
1570
- const ibGibAddr = getIbGibAddr({ ibGib });
1571
- if (!outgoingAddrsSet.has(ibGibAddr)) {
1572
- outgoingPayload.push(ibGib);
1573
- outgoingAddrsSet.add(ibGibAddr);
1574
- }
1674
+ const peerProposesCommit = deltaData.proposeCommit || false;
1575
1675
 
1576
- // Expand to include full dependency graph for this ibGib
1577
- // (Receiver needs all deps to properly process/merge)
1578
- try {
1579
- const deps = await getDependencyGraph({
1580
- ibGib,
1581
- space: destSpace,
1582
- });
1676
+ /**
1677
+ * these are already in the local temp space
1678
+ */
1679
+ const receivedPayloadIbGibs: IbGib_V1[] = sagaContext.payloadIbGibsDomain ?? [];
1583
1680
 
1584
- if (deps) {
1585
- Object.values(deps).forEach(depIbGib => {
1586
- const depAddr = getIbGibAddr({ ibGib: depIbGib });
1587
- if (!outgoingAddrsSet.has(depAddr)) {
1588
- outgoingPayload.push(depIbGib);
1589
- outgoingAddrsSet.add(depAddr);
1681
+ // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1682
+ console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${(deltaData.deltaRequestAddrInfos || []).length} peer requests`);
1683
+ const outgoingPayload = await this.getPayloadsForRequestedInfos({
1684
+ deltaRequestAddrInfos: deltaData.deltaRequestAddrInfos || [],
1685
+ mySpace,
1686
+ });
1687
+ console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1688
+
1689
+ // 3. Execute Merges (If applicable)
1690
+ // Check if we have pending conflicts that we CAN resolve now that we have data.
1691
+ // We look at the Saga History (Ack Frame) to find conflicts.
1692
+ // Optimization: Do this only if we received payloads.
1693
+ const mergeResultIbGibs: IbGib_V1[] = [];
1694
+
1695
+ console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1696
+
1697
+ if (receivedPayloadIbGibs.length > 0) {
1698
+ console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1699
+ // Find the Ack frame in history to get conflicts
1700
+ // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1701
+ // V1 timelines carry full history in `past`.
1702
+ const pastAddrs = sagaIbGib.rel8ns?.past || [];
1703
+ console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1704
+ let ackData: SyncSagaMessageAckData_V1 | undefined;
1705
+
1706
+ if (pastAddrs.length > 0) {
1707
+ // Batch fetch all past frames
1708
+ const resPast = await getFromSpace({ addrs: pastAddrs, space: myTempSpace });
1709
+ if (resPast.success && resPast.ibGibs) {
1710
+ // Iterate backwards (most recent first) to find the latest Ack
1711
+ for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1712
+ const pastFrame = resPast.ibGibs[i];
1713
+ const messageStone = await getSyncSagaMessageFromFrame({
1714
+ frameIbGib: pastFrame,
1715
+ space: myTempSpace
1716
+ });
1717
+ if (messageStone?.data?.stage === SyncStage.ack) {
1718
+ ackData = messageStone.data as SyncSagaMessageAckData_V1;
1719
+ console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1720
+ break;
1590
1721
  }
1591
- });
1592
- }
1593
- } catch (depError) {
1594
- console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
1595
- }
1596
- } else {
1597
- console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
1598
- }
1599
- }
1600
-
1601
- console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1602
-
1603
-
1604
- // 3. Execute Merges (If applicable)
1605
- // Check if we have pending conflicts that we CAN resolve now that we have data.
1606
- // We look at the Saga History (Ack Frame) to find conflicts.
1607
- // Optimization: Do this only if we received payloads.
1608
- const mergeResultIbGibs: IbGib_V1[] = [];
1609
-
1610
- console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1611
-
1612
- if (receivedPayloadIbGibs.length > 0) {
1613
- console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1614
- // Find the Ack frame in history to get conflicts
1615
- // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1616
- // V1 timelines carry full history in `past`.
1617
- const pastAddrs = sagaIbGib.rel8ns?.past || [];
1618
- console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1619
- let ackData: SyncSagaMessageAckData_V1 | undefined;
1620
-
1621
- if (pastAddrs.length > 0) {
1622
- // Batch fetch all past frames
1623
- const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
1624
- if (resPast.success && resPast.ibGibs) {
1625
- // Iterate backwards (most recent first) to find the latest Ack
1626
- for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1627
- const pastFrame = resPast.ibGibs[i];
1628
- const messageStone = await getSyncSagaMessageFromFrame({
1629
- frameIbGib: pastFrame,
1630
- space: tempSpace
1631
- });
1632
- if (messageStone?.data?.stage === SyncStage.ack) {
1633
- ackData = messageStone.data as SyncSagaMessageAckData_V1;
1634
- console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1635
- break;
1636
1722
  }
1637
1723
  }
1638
1724
  }
1639
- }
1640
1725
 
1641
- if (ackData && ackData.conflicts) {
1642
- const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
1643
- for (const conflict of optimisticConflicts) {
1644
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1645
- // We are Sender (usually) here if we are merging.
1646
- // Check if we have the history needed (timelineAddrs).
1647
- // Specifically, we needed the `receiverOnly` parts.
1648
-
1649
- // We blindly attempt merge if we have both tips accessible?
1650
- // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1651
-
1652
- // Check if we have receiverTip in space
1653
- console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1654
- const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
1655
- console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1656
- if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1657
- // We have the tip!
1658
- // Do we have the full history?
1659
- // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1660
- // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1661
-
1662
- // Perform Merge!
1663
- try {
1664
- const mergeResult = await mergeDivergentTimelines({
1665
- tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs![0], // Our tip from destSpace
1666
- tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
1667
- space: tempSpace, // Merge uses tempSpace
1668
- metaspace,
1669
- });
1670
- if (mergeResult) {
1671
- console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1672
- if (logalot) { console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`); }
1673
- mergeResultIbGibs.push(mergeResult);
1674
- outgoingPayload.push(mergeResult); // Send result to peer
1726
+ if (ackData && ackData.conflicts) {
1727
+ const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
1728
+ for (const conflict of optimisticConflicts) {
1729
+ const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1730
+ // We are Sender (usually) here if we are merging.
1731
+ // Check if we have the history needed (timelineAddrs).
1732
+ // Specifically, we needed the `receiverOnly` parts.
1733
+
1734
+ // We blindly attempt merge if we have both tips accessible?
1735
+ // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1736
+
1737
+ // Check if we have receiverTip in space
1738
+ console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1739
+ const resRecTip = await getFromSpace({ addr: receiverTip, space: myTempSpace }); // Check myTempSpace for incoming data
1740
+ console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in myTempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1741
+ if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1742
+ // We have the tip!
1743
+ // Do we have the full history?
1744
+ // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1745
+ // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1746
+
1747
+ // Perform Merge!
1748
+ try {
1749
+ const mergeResult = await mergeDivergentTimelines({
1750
+ tipA: (await getFromSpace({ addr: senderTip, space: mySpace })).ibGibs![0], // Our tip from destSpace
1751
+ tipB: resRecTip.ibGibs[0], // Their tip (from myTempSpace)
1752
+ space: myTempSpace, // Merge uses myTempSpace
1753
+ metaspace,
1754
+ });
1755
+ if (mergeResult) {
1756
+ console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1757
+ if (logalot) { console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`); }
1758
+ mergeResultIbGibs.push(mergeResult);
1759
+ outgoingPayload.push(mergeResult); // Send result to peer
1760
+ }
1761
+ } catch (e) {
1762
+ console.error(`${lc} Merge failed: ${e}`);
1763
+ // If merge fails, we might Abort or just continue?
1675
1764
  }
1676
- } catch (e) {
1677
- console.error(`${lc} Merge failed: ${e}`);
1678
- // If merge fails, we might Abort or just continue?
1679
1765
  }
1680
1766
  }
1681
1767
  }
1682
1768
  }
1683
- }
1684
-
1685
- // 4. Determine Next Action
1686
- // We have `outgoingPayload` (Requests + Merge Results).
1687
- // Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
1688
- // Do WE have outstanding requests?
1689
- // We might if `mergeResult` requires further sync? Usually no, result is complete.
1690
-
1691
- const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
1692
-
1693
- const hasOutgoing = outgoingPayload.length > 0;
1694
- const hasMyRequests = myRequests.length > 0;
1695
-
1696
- if (hasOutgoing || hasMyRequests) {
1697
- // We have business to attend to -> Send Delta
1698
- const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
1699
- sagaId: deltaData.sagaId,
1700
- stage: SyncStage.delta,
1701
- payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1702
- requests: hasMyRequests ? myRequests : undefined,
1703
- proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1704
- // Wait. If we send data, we are NOT committing yet.
1705
- // We are sending data. The OTHER side must ingest it.
1706
- // So proposeCommit = true?
1707
- // "Here is the data. I'm done. If you are good, let's commit."
1708
- // Yes.
1709
- };
1710
1769
 
1711
- // BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
1712
- // We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
1770
+ // 4. Determine Next Action
1771
+ // We have `outgoingPayload` (Requests + Merge Results).
1772
+ // Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
1773
+ // Do WE have outstanding requests?
1774
+ // We might if `mergeResult` requires further sync? Usually no, result is complete.
1713
1775
 
1714
- // So yes, proposeCommit = true.
1715
- responseDeltaData.proposeCommit = true;
1776
+ const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
1716
1777
 
1717
- const deltaStone = await this.createSyncMsgStone({
1718
- data: responseDeltaData,
1719
- localSpace: tempSpace,
1720
- metaspace
1721
- });
1778
+ const hasOutgoing = outgoingPayload.length > 0;
1779
+ const hasMyRequests = myRequests.length > 0;
1722
1780
 
1723
- const deltaFrame = await this.evolveSyncSagaIbGib({
1724
- prevSagaIbGib: sagaIbGib,
1725
- msgStones: [deltaStone],
1726
- sessionIdentity: identity,
1727
- localSpace: tempSpace,
1728
- metaspace
1729
- });
1730
-
1731
- // Build control payloads: frame + its dependencies (msg stone, identity)
1732
- const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1733
- if (identity) { payloadIbGibsControl.push(identity); }
1734
-
1735
- // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1736
- // return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1737
- throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1738
-
1739
- } else {
1740
- // We have nothing to send.
1741
-
1742
- if (peerProposesCommit) {
1743
- // Peer is done. We are done. -> Commit.
1744
- const commitData: SyncSagaMessageCommitData_V1 = {
1745
- sagaId: deltaData.sagaId,
1746
- stage: SyncStage.commit,
1747
- success: true,
1748
- };
1749
-
1750
- const commitStone = await this.createSyncMsgStone({
1751
- data: commitData,
1752
- localSpace: tempSpace,
1753
- metaspace
1754
- });
1755
-
1756
- const commitFrame = await this.evolveSyncSagaIbGib({
1757
- prevSagaIbGib: sagaIbGib,
1758
- msgStones: [commitStone],
1759
- sessionIdentity: identity,
1760
- localSpace: tempSpace,
1761
- metaspace
1762
- });
1763
-
1764
- // Build control payloads for commit
1765
- const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
1766
- if (identity) { commitCtrlPayloads.push(identity); }
1767
-
1768
- // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1769
- // return { frame: commitFrame, };
1770
- throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
1771
-
1772
- } else {
1773
- // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
1774
- // But we are empty.
1775
- // So WE propose commit.
1781
+ if (hasOutgoing || hasMyRequests) {
1782
+ // We have business to attend to -> Send Delta
1776
1783
  const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
1777
1784
  sagaId: deltaData.sagaId,
1778
1785
  stage: SyncStage.delta,
1779
- proposeCommit: true,
1780
- payloadAddrs: [], // Always include empty array if sending delta
1786
+ payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1787
+ requests: hasMyRequests ? myRequests : undefined,
1788
+ proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1789
+ // Wait. If we send data, we are NOT committing yet.
1790
+ // We are sending data. The OTHER side must ingest it.
1791
+ // So proposeCommit = true?
1792
+ // "Here is the data. I'm done. If you are good, let's commit."
1793
+ // Yes.
1781
1794
  };
1782
1795
 
1796
+ // BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
1797
+ // We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
1798
+
1799
+ // So yes, proposeCommit = true.
1800
+ responseDeltaData.proposeCommit = true;
1801
+
1783
1802
  const deltaStone = await this.createSyncMsgStone({
1784
1803
  data: responseDeltaData,
1785
- localSpace: tempSpace,
1786
- metaspace
1804
+ localSpace: mySpace,
1805
+ metaspace,
1787
1806
  });
1788
1807
 
1789
1808
  const deltaFrame = await this.evolveSyncSagaIbGib({
1790
1809
  prevSagaIbGib: sagaIbGib,
1791
1810
  msgStones: [deltaStone],
1792
1811
  sessionIdentity: identity,
1793
- localSpace: tempSpace,
1794
- metaspace
1812
+ localSpace: mySpace,
1813
+ metaspace,
1795
1814
  });
1796
1815
 
1797
- // Check if PEER proposed commit
1798
- if (deltaData.proposeCommit) {
1799
- if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
1800
- // Peer wants to commit and has no more requests.
1801
- // We should Commit.
1816
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1817
+ const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1818
+ if (identity) { payloadIbGibsControl.push(identity); }
1819
+
1820
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1821
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1822
+ // throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1802
1823
 
1824
+ } else {
1825
+ // We have nothing to send.
1826
+
1827
+ if (peerProposesCommit) {
1828
+ // Peer is done. We are done. -> Commit.
1803
1829
  const commitData: SyncSagaMessageCommitData_V1 = {
1804
1830
  sagaId: deltaData.sagaId,
1805
1831
  stage: SyncStage.commit,
@@ -1808,68 +1834,136 @@ export class SyncSagaCoordinator {
1808
1834
 
1809
1835
  const commitStone = await this.createSyncMsgStone({
1810
1836
  data: commitData,
1811
- localSpace: tempSpace,
1837
+ localSpace: mySpace,
1812
1838
  metaspace
1813
1839
  });
1814
1840
 
1815
1841
  const commitFrame = await this.evolveSyncSagaIbGib({
1816
- prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1842
+ prevSagaIbGib: sagaIbGib,
1817
1843
  msgStones: [commitStone],
1818
1844
  sessionIdentity: identity,
1819
- localSpace: tempSpace,
1845
+ localSpace: mySpace,
1820
1846
  metaspace
1821
1847
  });
1822
1848
 
1823
1849
  // Build control payloads for commit
1824
- const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
1825
- if (identity) { commitCtrlPayloads2.push(identity); }
1850
+ const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
1851
+ if (identity) { commitCtrlPayloads.push(identity); }
1826
1852
 
1827
- // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1828
- // return { frame: commitFrame, };
1829
- throw new Error(`not implemented (E: 27514878585889e531ef21f1abbef826)`);
1830
- }
1853
+ return { frame: commitFrame, };
1854
+
1855
+ } else {
1856
+ // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
1857
+ // But we are empty.
1858
+ // So WE propose commit.
1859
+ const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
1860
+ sagaId: deltaData.sagaId,
1861
+ stage: SyncStage.delta,
1862
+ proposeCommit: true,
1863
+ };
1864
+
1865
+ const deltaStone = await this.createSyncMsgStone({
1866
+ data: responseDeltaData,
1867
+ localSpace: mySpace,
1868
+ metaspace
1869
+ });
1870
+
1871
+ const deltaFrame = await this.evolveSyncSagaIbGib({
1872
+ prevSagaIbGib: sagaIbGib,
1873
+ msgStones: [deltaStone],
1874
+ sessionIdentity: identity,
1875
+ localSpace: mySpace,
1876
+ metaspace
1877
+ });
1831
1878
 
1832
- // Build control payloads for delta propose
1833
- const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
1834
- if (identity) { deltaCtrlPayloads.push(identity); }
1879
+ // Check if PEER proposed commit
1880
+ if (deltaData.proposeCommit) {
1881
+ if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
1882
+ // Peer wants to commit and has no more requests.
1883
+ // We should Commit.
1884
+
1885
+ const commitData: SyncSagaMessageCommitData_V1 = {
1886
+ sagaId: deltaData.sagaId,
1887
+ stage: SyncStage.commit,
1888
+ success: true,
1889
+ };
1890
+
1891
+ const commitStone = await this.createSyncMsgStone({
1892
+ data: commitData,
1893
+ localSpace: mySpace,
1894
+ metaspace
1895
+ });
1896
+
1897
+ const commitFrame = await this.evolveSyncSagaIbGib({
1898
+ prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1899
+ msgStones: [commitStone],
1900
+ sessionIdentity: identity,
1901
+ localSpace: mySpace,
1902
+ metaspace
1903
+ });
1835
1904
 
1836
- // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1837
- // return { frame: deltaFrame, };
1838
- throw new Error(`not implemented (E: ff35584696b6fcb3ad6dd7c5cade2f26)`);
1905
+ // Build control payloads for commit
1906
+ const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
1907
+ if (identity) { commitCtrlPayloads2.push(identity); }
1908
+
1909
+ return { frame: commitFrame, };
1910
+ }
1911
+
1912
+ // Build control payloads for delta propose
1913
+ const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
1914
+ if (identity) { deltaCtrlPayloads.push(identity); }
1915
+
1916
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1917
+ }
1839
1918
  }
1919
+ } catch (error) {
1920
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1921
+ throw error;
1922
+ } finally {
1923
+ if (logalot) { console.log(`${lc} complete.`); }
1840
1924
  }
1925
+
1841
1926
  }
1842
1927
 
1843
1928
 
1844
1929
  protected async handleCommitFrame({
1845
1930
  sagaIbGib,
1846
- destSpace,
1847
- tempSpace,
1931
+ mySpace,
1932
+ myTempSpace,
1848
1933
  metaspace,
1849
1934
  identity,
1850
1935
  }: {
1851
1936
  sagaIbGib: SyncIbGib_V1,
1852
- destSpace: IbGibSpaceAny,
1853
- tempSpace: IbGibSpaceAny,
1937
+ mySpace: IbGibSpaceAny,
1938
+ myTempSpace: IbGibSpaceAny,
1854
1939
  metaspace: MetaspaceService,
1855
1940
  identity?: KeystoneIbGib_V1,
1856
1941
  }): Promise<NextSagaFrameInfo> {
1857
1942
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1858
- if (logalot) { console.log(`${lc} Commit received.`); }
1943
+ try {
1944
+ if (logalot) { console.log(`${lc} starting... (I: e179573bdd881202f8ba3168da1c3826)`); }
1945
+
1946
+
1859
1947
 
1860
- // Sender Logic (Finalizing):
1861
- // If we are here, we received a Commit frame from the Peer.
1862
- // This implies the Peer has successfully committed.
1863
- // We should now:
1864
- // 1. Validate (implicitly done by receiving valid frame)
1865
- // 2. Perform our own cleanup (Temp -> Dest, if applicable)
1866
- // 3. Return null to signal saga completion.
1948
+ // Sender Logic (Finalizing):
1949
+ // If we are here, we received a Commit frame from the Peer.
1950
+ // This implies the Peer has successfully committed.
1951
+ // We should now:
1952
+ // 1. Validate (implicitly done by receiving valid frame)
1953
+ // 2. Perform our own cleanup (Temp -> Dest, if applicable)
1954
+ // 3. Return null to signal saga completion.
1867
1955
 
1868
- // Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
1956
+ // Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
1957
+ if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
1958
+
1959
+ return { responseWasNull: true };
1960
+ } catch (error) {
1961
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1962
+ throw error;
1963
+ } finally {
1964
+ if (logalot) { console.log(`${lc} complete.`); }
1965
+ }
1869
1966
 
1870
- if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
1871
- // return { responseWasNull: true };
1872
- throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
1873
1967
  }
1874
1968
 
1875
1969
  // #endregion Handlers
@@ -1923,6 +2017,10 @@ export class SyncSagaCoordinator {
1923
2017
  msgStones: IbGib_V1[],
1924
2018
  localSpace: IbGibSpaceAny,
1925
2019
  metaspace: MetaspaceService,
2020
+ /**
2021
+ * does NOT evolve the keystone. this should be done by the caller.
2022
+ * (NOT IMPLEMENTED YET ANYWAY)
2023
+ */
1926
2024
  sessionIdentity?: KeystoneIbGib_V1,
1927
2025
  }): Promise<SyncIbGib_V1> {
1928
2026
  const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;