@ibgib/core-gib 0.1.23 → 0.1.26

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 (80) hide show
  1. package/dist/common/other/graph-helper.d.mts +17 -0
  2. package/dist/common/other/graph-helper.d.mts.map +1 -1
  3. package/dist/common/other/graph-helper.mjs +44 -0
  4. package/dist/common/other/graph-helper.mjs.map +1 -1
  5. package/dist/common/other/ibgib-helper.d.mts +1 -1
  6. package/dist/common/other/ibgib-helper.d.mts.map +1 -1
  7. package/dist/common/other/ibgib-helper.mjs.map +1 -1
  8. package/dist/sync/graft-info/graft-info-helpers.mjs +2 -2
  9. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -1
  10. package/dist/sync/sync-conflict.respec.mjs +10 -15
  11. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  12. package/dist/sync/sync-constants.d.mts +1 -0
  13. package/dist/sync/sync-constants.d.mts.map +1 -1
  14. package/dist/sync/sync-constants.mjs +1 -0
  15. package/dist/sync/sync-constants.mjs.map +1 -1
  16. package/dist/sync/sync-helpers.d.mts +5 -0
  17. package/dist/sync/sync-helpers.d.mts.map +1 -1
  18. package/dist/sync/sync-helpers.mjs +37 -1
  19. package/dist/sync/sync-helpers.mjs.map +1 -1
  20. package/dist/sync/sync-innerspace-constants.respec.mjs +10 -12
  21. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  22. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +10 -12
  23. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  24. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +10 -12
  25. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  26. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +9 -12
  27. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  28. package/dist/sync/sync-innerspace-partial-update.respec.mjs +9 -14
  29. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  30. package/dist/sync/sync-innerspace.respec.mjs +9 -12
  31. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  32. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts +2 -0
  33. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts.map +1 -1
  34. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs +4 -0
  35. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs.map +1 -1
  36. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +24 -13
  37. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  38. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +176 -76
  39. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  40. package/dist/sync/sync-peer/sync-peer-types.d.mts +29 -6
  41. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  42. package/dist/sync/sync-peer/sync-peer-v1.d.mts +38 -55
  43. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  44. package/dist/sync/sync-peer/sync-peer-v1.mjs +111 -244
  45. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  46. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +32 -1
  47. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  48. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +96 -7
  49. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  50. package/dist/sync/sync-saga-coordinator.d.mts +59 -13
  51. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  52. package/dist/sync/sync-saga-coordinator.mjs +446 -304
  53. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  54. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +51 -6
  55. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  56. package/dist/sync/sync-types.d.mts +35 -10
  57. package/dist/sync/sync-types.d.mts.map +1 -1
  58. package/dist/sync/sync-types.mjs +1 -2
  59. package/dist/sync/sync-types.mjs.map +1 -1
  60. package/package.json +1 -1
  61. package/src/common/other/graph-helper.mts +53 -0
  62. package/src/common/other/ibgib-helper.mts +1 -1
  63. package/src/sync/graft-info/graft-info-helpers.mts +3 -3
  64. package/src/sync/sync-conflict.respec.mts +10 -17
  65. package/src/sync/sync-constants.mts +1 -0
  66. package/src/sync/sync-helpers.mts +47 -7
  67. package/src/sync/sync-innerspace-constants.respec.mts +10 -12
  68. package/src/sync/sync-innerspace-deep-updates.respec.mts +10 -12
  69. package/src/sync/sync-innerspace-dest-ahead.respec.mts +10 -12
  70. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +9 -12
  71. package/src/sync/sync-innerspace-partial-update.respec.mts +9 -14
  72. package/src/sync/sync-innerspace.respec.mts +9 -12
  73. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mts +7 -0
  74. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +200 -75
  75. package/src/sync/sync-peer/sync-peer-types.mts +35 -11
  76. package/src/sync/sync-peer/sync-peer-v1.mts +154 -257
  77. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +90 -13
  78. package/src/sync/sync-saga-coordinator.mts +536 -356
  79. package/src/sync/sync-saga-message/sync-saga-message-types.mts +56 -4
  80. package/src/sync/sync-types.mts +46 -13
@@ -23,14 +23,18 @@ import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMA
23
23
  import { appendToTimeline, createTimeline, getHistory, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
24
24
  import {
25
25
  SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
26
- SyncRel8ns_V1, DomainIbGibAnalysisInfo, HandleSagaFrameResult,
26
+ SyncRel8ns_V1, DomainIbGibAnalysisInfo, NextSagaFrameInfo,
27
+ SYNC_CONFLICT_STRATEGY_VALID_VALUES,
28
+ HandleSagaResponseContextResult,
27
29
  } from "./sync-types.mjs";
28
30
  import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
29
- import { getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
31
+ import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
30
32
  import {
31
33
  SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
32
34
  SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
33
35
  SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
36
+ SyncSagaPushOfferInfo,
37
+ SyncSagaRequestAddrInfo,
34
38
  } from "./sync-saga-message/sync-saga-message-types.mjs";
35
39
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
36
40
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
@@ -38,7 +42,7 @@ import { SyncSagaInfo } from "./sync-types.mjs";
38
42
  import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
39
43
  import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
40
44
  import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
41
- import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
45
+ import { createSyncSagaContext, validateContextAndSagaFrame } from "./sync-saga-context/sync-saga-context-helpers.mjs";
42
46
  import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
43
47
  import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
44
48
  import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
@@ -58,14 +62,14 @@ const lcControlDomain = '[ControlDomain]';
58
62
 
59
63
  /**
60
64
  * Orchestrates the synchronization process between two spaces (Source and Destination).
61
- *
65
+ *
62
66
  * ## Execution Contexts
63
- *
67
+ *
64
68
  * The methods in this class are designed to be run on specific nodes in the sync topology:
65
- *
69
+ *
66
70
  * * **Sender/Local Node**: The node *initiating* the sync (calling `sync()`).
67
71
  * * **Receiver/Remote Node**: The node *accepting* the sync request (the Peer).
68
- *
72
+ *
69
73
  * Note that in a peer-to-peer architecture, "Sender" and "Receiver" are roles relative
70
74
  * to a specific Saga session, not fixed node identities.
71
75
  */
@@ -80,18 +84,18 @@ export class SyncSagaCoordinator {
80
84
 
81
85
  /**
82
86
  * Executes a synchronization saga using the Symmetric Sync Protocol.
83
- *
87
+ *
84
88
  * @remarks
85
89
  * **Execution Context**: **Sender (Local)**.
86
90
  * This method is the entry point for starting a sync session.
87
- *
91
+ *
88
92
  * @param opts.peer - The remote peer witness to communicate with.
89
93
  * @param opts.localSpace - The local space that will be read from and written to.
90
94
  * @param opts.metaspace - Service for creating temp spaces and managing ibgibs.
91
95
  * @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
92
96
  * @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
93
97
  */
94
- async sync({
98
+ public async sync({
95
99
  peer,
96
100
  domainIbGibs,
97
101
  conflictStrategy = SyncConflictStrategy.abort,
@@ -140,14 +144,14 @@ export class SyncSagaCoordinator {
140
144
  (async () => {
141
145
  try {
142
146
 
143
- // 2. BOOTSTRAP IDENTITY (Session Keystone)
147
+ // BOOTSTRAP IDENTITY (Session Keystone)
144
148
  const sessionIdentity = useSessionIdentity
145
149
  ? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
146
150
  : undefined;
147
151
  // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
148
152
 
149
- // 3. CREATE INITIAL FRAME (Stage.init)
150
- const { sagaFrame: initFrame, initialDomainGraph } = await this.createInitFrame({
153
+ // CREATE INITIAL FRAME (Stage.init)
154
+ const { initFrame, initDomainGraph } = await this.createInitFrame({
151
155
  sagaId,
152
156
  sessionIdentity,
153
157
  domainIbGibs,
@@ -155,17 +159,16 @@ export class SyncSagaCoordinator {
155
159
  metaspace, localSpace, tempSpace,
156
160
  });
157
161
 
158
- // 4. KICK OFF THE PING-PONG SAGA LOOP (FSM)
159
- // commented to compile
160
- // await this.executeSagaLoop({
161
- // initialFrame: initFrame,
162
- // peer,
163
- // sessionIdentity,
164
- // updates$,
165
- // localSpace,
166
- // tempSpace,
167
- // metaspace
168
- // });
162
+ // KICK OFF THE PING-PONG SAGA LOOP (FSM)
163
+ await this.executeSagaLoop({
164
+ initFrame, initDomainGraph,
165
+ peer,
166
+ sessionIdentity,
167
+ updates$,
168
+ localSpace,
169
+ tempSpace,
170
+ metaspace
171
+ });
169
172
 
170
173
  resolveDone();
171
174
  if (!updates$.complete) { throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`); }
@@ -188,6 +191,91 @@ export class SyncSagaCoordinator {
188
191
  };
189
192
  }
190
193
 
194
+ /**
195
+ * This is what the receiving side of the sync calls to drive the FSM to the
196
+ * next stage.
197
+ *
198
+ * So whereas the sender executes a saga loop and drives the entire process,
199
+ * this is a reactive one-off that drives just the single step that the
200
+ * receiver does in that saga.
201
+ *
202
+ * @returns next context result if another round, else if commit returns
203
+ * null
204
+ */
205
+ public async receiverContinueSync({
206
+ sagaContext,
207
+ mySpace,
208
+ myTempSpace,
209
+ identity,
210
+ identitySecret,
211
+ metaspace,
212
+ }: {
213
+ sagaContext: SyncSagaContextIbGib_V1,
214
+ /**
215
+ * Local space relative to the execution context's POV
216
+ */
217
+ mySpace: IbGibSpaceAny,
218
+ /**
219
+ * Local temp space relative to the execution context's POV
220
+ */
221
+ myTempSpace: IbGibSpaceAny,
222
+ identity?: KeystoneIbGib_V1,
223
+ identitySecret?: string,
224
+ metaspace: MetaspaceService,
225
+ }): Promise<SyncSagaContextIbGib_V1 | null> {
226
+ const lc = `${this.lc}[${this.receiverContinueSync.name}]`;
227
+ try {
228
+ if (logalot) { console.log(`${lc} starting... (I: f64e08bf77d1425378601f380384ec26)`); }
229
+
230
+ const contextResult = await this.handleResponseSagaContext({
231
+ sagaContext,
232
+ mySpace,
233
+ myTempSpace,
234
+ identity,
235
+ identitySecret,
236
+ metaspace,
237
+ });
238
+
239
+ if (!contextResult) {
240
+ if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`); }
241
+ return null;
242
+ }
243
+
244
+ // #region error conditions throw
245
+ if (contextResult.errorMsg) {
246
+ throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: 7b41a183cf3cb58a5859c803800cf826)`);
247
+ } else if (!contextResult.nextFrameInfo) {
248
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
249
+ } else if (contextResult.nextFrameInfo?.responseWasNull) {
250
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
251
+ }
252
+ // #endregion error conditions throw
253
+
254
+ // create the return context
255
+ const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
256
+
257
+ const responseCtx = await createSyncSagaContext({
258
+ sagaFrame: frame,
259
+ localSpace: mySpace,
260
+ payloadIbGibsDomain,
261
+ // todo: we need to thoroughly go through the identity per each step after getting basic merging
262
+ sessionKeystones: identity ? [identity] : undefined, // ??
263
+ });
264
+
265
+ const immediateValidationErrors = await validateContextAndSagaFrame({
266
+ context: responseCtx,
267
+ });
268
+ if (immediateValidationErrors.length > 0) { throw new Error(`(UNEXPECTED) just created sync saga context () and there were immediateValidationErrors? immediateValidationErrors: ${immediateValidationErrors} (E: c120e8e0aa98673d685267a8a36e5826)`); }
269
+
270
+ return responseCtx;
271
+ } catch (error) {
272
+ console.error(`${lc} ${extractErrorMsg(error)}`);
273
+ throw error;
274
+ } finally {
275
+ if (logalot) { console.log(`${lc} complete.`); }
276
+ }
277
+ }
278
+
191
279
  protected async getSessionIdentity({
192
280
  sagaId,
193
281
  metaspace,
@@ -226,13 +314,13 @@ export class SyncSagaCoordinator {
226
314
 
227
315
  /**
228
316
  * Drives the FSM loop of the Saga.
229
- *
317
+ *
230
318
  * @remarks
231
319
  * **Execution Context**: **Sender (Local)**.
232
- *
320
+ *
233
321
  * This method manages the "Ping Pong" request-response cycle on the Sender.
234
322
  * It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
235
- *
323
+ *
236
324
  * **Data Transport Note**:
237
325
  * Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
238
326
  * When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
@@ -240,8 +328,8 @@ export class SyncSagaCoordinator {
240
328
  * When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
241
329
  */
242
330
  protected async executeSagaLoop({
243
- initialFrame,
244
- initialDomainGraph,
331
+ initFrame,
332
+ initDomainGraph,
245
333
  peer,
246
334
  sessionIdentity,
247
335
  updates$,
@@ -249,8 +337,14 @@ export class SyncSagaCoordinator {
249
337
  tempSpace,
250
338
  metaspace
251
339
  }: {
252
- initialFrame: SyncIbGib_V1,
253
- initialDomainGraph: FlatIbGibGraph,
340
+ initFrame: SyncIbGib_V1,
341
+ /**
342
+ * This is the initial dependency graph of all domain ibgibs passed in
343
+ * to the original {@link sync} call.
344
+ *
345
+ * if we're executing on the sender, this will be populated
346
+ */
347
+ initDomainGraph: FlatIbGibGraph,
254
348
  peer: SyncPeerWitness,
255
349
  sessionIdentity?: KeystoneIbGib_V1,
256
350
  updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
@@ -260,44 +354,23 @@ export class SyncSagaCoordinator {
260
354
  }): Promise<void> {
261
355
  const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
262
356
 
263
- // The current frame we just generated (e.g., Init or Delta Request)
264
- let currentFrame: SyncIbGib_V1 | null = initialFrame;
265
- // The payload we need to attach to the message (data we are sending)
357
+ /** The current frame we just generated (e.g., Init or Delta Request) */
358
+ let currentFrame: SyncIbGib_V1 | null = initFrame;
359
+ /** The **domain** payload we need to transmit (data we are sending) */
266
360
  let nextDomainIbGibs: IbGib_V1[] = [];
267
361
 
268
- // First, inject local/tempSpace into peer so it can pull as
269
- // needed...code smell?
270
- peer.senderSpace = localSpace;
271
- peer.senderTempSpace = tempSpace;
272
-
273
362
  while (currentFrame) {
274
- // A. Create Context (Request)
275
- // 1. Calculate Full Dependency Graph (including Ancestors/DNA)
276
- // We must do this BEFORE creating the Context so we can list them
277
- // all in payloadAddrsDomain and payloadAddrsControl.
278
- const nextDomainIbGibsAndDeps: IbGib_V1[] = [];
279
-
280
- // A. Payload (Standard Deep Deps)
281
- // TODO: THIS IS EXTREMELY INEFFICIENT. adjust this algorithm to
282
- // only do the dependencies that the other end doesn't need (diff
283
- // between tip and LCA). This can be done using the information in
284
- // the ack's conflicts array.
285
- for (const nextDomainIbGib of nextDomainIbGibs) {
286
- let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
287
- if (!nextDomainIbGibGraph) {
288
- nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
289
- }
290
- if (nextDomainIbGibGraph) {
291
- nextDomainIbGibsAndDeps.push(...Object.values(nextDomainIbGibGraph));
292
- } else {
293
- throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
294
- }
295
- }
296
- if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${nextDomainIbGibsAndDeps.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`); }
363
+ // first, prepare for context transmission...
297
364
 
298
- // 2. Create Context (Envelope)
299
- // set up subscription for response's payload ibgibs (if any)
365
+ /**
366
+ * this is used later in pollForDomainPayloads **iif** payloads are
367
+ * received **in the response**, i.e., on the return trip. So if we
368
+ * request addrs, the response should have these addrs and their
369
+ * dependencies. The ibgibs corresponding to these addrs will be
370
+ * streamed and placed into this map.
371
+ */
300
372
  const domainPayloadsMap = new Map<string, IbGib_V1>();
373
+ // #region set up peer observable for any domainPayloadsMap
301
374
  const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
302
375
  const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
303
376
  next: async (ibgib: IbGib_V1) => {
@@ -319,43 +392,49 @@ export class SyncSagaCoordinator {
319
392
  await subscription.unsubscribe();
320
393
  },
321
394
  }));
395
+ // #endregion set up peer observable for any domainPayloadsMap
322
396
 
323
- // Create the Request Context...
397
+ // ...create/compose the Request Context itself...
324
398
  const requestCtx = await createSyncSagaContext({
325
399
  sagaFrame: currentFrame,
326
400
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
327
-
328
401
  /**
329
402
  * init frame: empty
330
- *
403
+ * ack frame: possible push offers
404
+ * delta frame: requested ibgibs or commit offer
405
+ * commit frame: empty
331
406
  */
332
- payloadIbGibsDomain: nextDomainIbGibsAndDeps,
407
+ payloadIbGibsDomain: nextDomainIbGibs,
333
408
  localSpace,
334
409
  });
335
410
 
336
- // Log what we're sending
411
+ // #region Log what we're sending
337
412
  if (logalotControlDomain) {
338
- const domainAddrs = nextDomainIbGibsAndDeps.map(p => getIbGibAddr({ ibGib: p }));
339
- console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
413
+ const domainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
414
+ console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: 5b0081803698770f0bf64992220b312)`);
340
415
  console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
341
416
  console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
342
417
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
343
418
  }
419
+ // #endregion Log what we're sending
344
420
 
345
421
  // update our saga listeners...
346
422
  // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
347
- updates$.next(requestCtx); // spins off
423
+ updates$.next(requestCtx); // spins off for saga UI updates
348
424
 
349
- // ...And send the context
425
+ // ...And send the context.
426
+ peer.setOptionalOpts({ senderSpace: localSpace, senderTempSpace: tempSpace, });
350
427
  const responseCtx = await peer.witness(requestCtx);
351
428
 
352
- // C. Handle Response
429
+ // the send returned, but a peer can return a falsy responseCtx, if
430
+ // we just sent a commit to them and they're done. If so, there will
431
+ // necessarily be no payload to wait to receive
353
432
  if (!responseCtx) {
354
433
  if (currentFrame) {
355
434
  // Check for Commit (Peer silence expected)
356
435
  const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
357
436
  if (msg?.data?.stage === SyncStage.commit) {
358
- if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`); }
437
+ if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete. (I: 26f9ee073858ca78b8284753368b5226)`); }
359
438
  currentFrame = null;
360
439
  break;
361
440
  } else {
@@ -366,19 +445,24 @@ export class SyncSagaCoordinator {
366
445
  }
367
446
  }
368
447
 
369
- // ---------------------------------------------------------------------
370
- // 2d. HANDLE RESPONSE
371
- // ---------------------------------------------------------------------
372
- // at this point, we have received the response context ibgib, but
448
+ // at this point, we did indeed receive a response to analyze. BUT!
449
+ // we have only necessarily received the response context ibgib.
373
450
  // if there were payloads expected to be transferred, they may not
374
451
  // be done yet.
452
+
375
453
  if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
376
- updates$.next(responseCtx); // spins off -- don't remove this comment!
454
+ updates$.next(responseCtx); // spins off for saga UI updating
455
+
456
+ // immediately validate context/saga frame (but not payloads because
457
+ // we may not have those yet)
458
+ const contextAndSagaFrameValidationErrors =
459
+ await validateContextAndSagaFrame({ context: responseCtx });
460
+ if (contextAndSagaFrameValidationErrors.length > 0) { throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`); }
377
461
 
378
462
  // Extract expected domain addresses from response context
379
463
  const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
380
464
 
381
- // Poll for them if needed
465
+ // Poll for them if needed. see above jsdocs for domainPayloadsMap
382
466
  if (responsePayloadAddrsDomain.length > 0) {
383
467
  responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
384
468
  expectedAddrs: responsePayloadAddrsDomain,
@@ -388,51 +472,62 @@ export class SyncSagaCoordinator {
388
472
  });
389
473
  }
390
474
 
391
- // at this point, we have received the context AND **all** of the
392
- // domain payloads (if applicable), so we are ready to do the next
393
- // iteration in the saga loop
394
-
395
- // Log what we received back
475
+ // #region Log what we received back
396
476
  if (!responseCtx.sagaFrame) { throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`); }
397
477
  if (logalotControlDomain) {
398
478
  const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] as string[] || [];
399
- console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
479
+ console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: 3dc76a9744d89a4fc3e2f076c2be4826)`);
400
480
  console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
401
481
  console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
402
482
  console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
403
483
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
404
484
  }
485
+ // #endregion Log what we received back
486
+
487
+ // at this point, we have received the context AND **all** of the
488
+ // domain payloads (if applicable), so we are ready to do the next
489
+ // iteration in the saga loop
405
490
 
406
- // Handle Response Frame
407
- // interface HandleSagaFrameResult {
408
- // frame: SyncIbGib_V1;
409
- // payloadIbGibsDomain?: IbGib_V1[];
410
- // conflictInfos?: SyncSagaConflictInfo;
411
- // }
412
- const handleResult = await this.handleSagaContext({
491
+ // this is the part that drives the FSM forward, i.e., when we
492
+ // evolve the sync saga ibgib itself to the next frame if we aren't
493
+ // finished/errored out.
494
+ const contextResult = await this.handleResponseSagaContext({
413
495
  sagaContext: responseCtx,
496
+ initDomainGraph,
414
497
  mySpace: localSpace,
415
498
  myTempSpace: tempSpace,
416
499
  metaspace,
417
500
  });
418
501
 
419
- if (!handleResult) {
420
- if (logalot) { console.log(`${lc} Handler returned null (Saga End).`); }
502
+ if (!contextResult) {
503
+ if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: faae22abc818ba9b28ac6d2881cd7826)`); }
421
504
  break;
422
505
  }
423
506
 
424
- currentFrame = handleResult.frame;
507
+ // #region error conditions throw
508
+ if (contextResult.errorMsg) {
509
+ throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
510
+ } else if (!contextResult.nextFrameInfo) {
511
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
512
+ } else if (contextResult.nextFrameInfo?.responseWasNull) {
513
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
514
+ }
515
+ // #endregion error conditions throw
425
516
 
426
- // Collect next DOMAIN payloads for the NEXT request
427
- nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
517
+ // we have another frame to process and send out, with possibly
518
+ // payload domain ibgibs as well
519
+ const { frame, payloadIbGibsDomain, } = contextResult.nextFrameInfo;
520
+ currentFrame = frame;
521
+ nextDomainIbGibs = [...(payloadIbGibsDomain || [])];
428
522
 
429
- // Log handler output for next iteration
523
+ // #region Log handler output for next iteration
430
524
  if (logalotControlDomain) {
431
525
  const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
432
- console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
526
+ console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: 6b0d88c4c28857ccd812381515bd7826)`);
433
527
  console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
434
528
  console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
435
529
  }
530
+ // #endregion Log handler output for next iteration
436
531
  }
437
532
  }
438
533
 
@@ -499,7 +594,7 @@ export class SyncSagaCoordinator {
499
594
 
500
595
  const res = await getLatestAddrs({ space, tjpAddrs: tjps });
501
596
  if (!res.data || !res.data.latestAddrsMap) {
502
- throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
597
+ throw new Error(`${lc} Failed to get latest addrs. (E: 7d395940c0e1419165c5196c39c6c826)`);
503
598
  }
504
599
 
505
600
  // if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
@@ -552,10 +647,10 @@ export class SyncSagaCoordinator {
552
647
 
553
648
  /**
554
649
  * Creates the Initial Saga Frame (Init Stage).
555
- *
650
+ *
556
651
  * @remarks
557
652
  * **Execution Context**: **Sender (Local)**.
558
- *
653
+ *
559
654
  * Generates the first frame containing the Knowledge Vector of the Local Space.
560
655
  * This is sent to the Receiver to begin Gap Analysis.
561
656
  */
@@ -575,14 +670,14 @@ export class SyncSagaCoordinator {
575
670
  metaspace: MetaspaceService,
576
671
  localSpace: IbGibSpaceAny,
577
672
  tempSpace: IbGibSpaceAny,
578
- }): Promise<{ sagaFrame: SyncIbGib_V1, initialDomainGraph: { [addr: string]: IbGib_V1 } }> {
673
+ }): Promise<{ initFrame: SyncIbGib_V1, initDomainGraph: { [addr: string]: IbGib_V1 } }> {
579
674
  const lc = `${this.lc}[${this.createInitFrame.name}]`;
580
675
  try {
581
676
  if (logalot) { console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`); }
582
677
 
583
678
  // Analyze Timelines & Stones
584
679
  const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
585
- // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
680
+ // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
586
681
  const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
587
682
 
588
683
  // we need to store the fullGraph in our tempSpace for later...
@@ -627,7 +722,7 @@ export class SyncSagaCoordinator {
627
722
 
628
723
  // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
629
724
 
630
- return { sagaFrame, initialDomainGraph: fullGraph };
725
+ return { initFrame: sagaFrame, initDomainGraph: fullGraph };
631
726
  } catch (error) {
632
727
  console.error(`${lc} ${extractErrorMsg(error)}`);
633
728
  throw error;
@@ -639,7 +734,7 @@ export class SyncSagaCoordinator {
639
734
  /**
640
735
  * Helper to poll for streaming domain payloads and put them in the
641
736
  * local {@link tempSpace}.
642
- *
737
+ *
643
738
  * @returns when all {@link expectedAddrs} are done being transmitted.
644
739
  */
645
740
  protected async pollForDomainPayloads({
@@ -662,7 +757,7 @@ export class SyncSagaCoordinator {
662
757
  let pending = [...expectedAddrs];
663
758
  const start = Date.now();
664
759
  /**
665
- * This needs
760
+ * This needs
666
761
  */
667
762
  const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
668
763
 
@@ -708,26 +803,28 @@ export class SyncSagaCoordinator {
708
803
 
709
804
  /**
710
805
  * This is the heart of the "ping pong" transaction, where we send a context
806
+ *
711
807
  * and receive a context. IOW, this drives the FSM of the sync saga ibgib as
712
808
  * a whole.
713
- *
809
+ *
714
810
  * This is called in two places:
715
- *
811
+ *
716
812
  * ## 1. Sender
717
- *
813
+ *
718
814
  * On the sender, this is called within the {@link executeSagaLoop} which
719
- * initiates and drives the sync.
720
- *
815
+ * initiates and drives the sync.
816
+ *
721
817
  * ## 2. Receiver
722
- *
818
+ *
723
819
  * On the receiver, this is called directly by the receiving endpoint. That
724
- * endpoint's job is basically to get these things collocated and prepared
820
+ * endpoint's job is basically to get these things collocated and prepared
725
821
  * to make this call.
726
- *
822
+ *
727
823
  * This is a one-off on the receiver.
728
824
  */
729
- public async handleSagaContext({
825
+ public async handleResponseSagaContext({
730
826
  sagaContext,
827
+ initDomainGraph,
731
828
  mySpace,
732
829
  myTempSpace,
733
830
  identity,
@@ -735,6 +832,13 @@ export class SyncSagaCoordinator {
735
832
  metaspace,
736
833
  }: {
737
834
  sagaContext: SyncSagaContextIbGib_V1,
835
+ /**
836
+ * This is the initial dependency graph of all domain ibgibs passed in
837
+ * to the original {@link sync} call.
838
+ *
839
+ * if we're executing on the sender, this will be populated
840
+ */
841
+ initDomainGraph?: FlatIbGibGraph,
738
842
  /**
739
843
  * Local space relative to the execution context's POV
740
844
  */
@@ -746,8 +850,8 @@ export class SyncSagaCoordinator {
746
850
  identity?: KeystoneIbGib_V1,
747
851
  identitySecret?: string,
748
852
  metaspace: MetaspaceService,
749
- }): Promise<HandleSagaFrameResult | null> {
750
- const lc = `${this.lc}[${this.handleSagaContext.name}]`;
853
+ }): Promise<HandleSagaResponseContextResult> {
854
+ const lc = `${this.lc}[${this.handleResponseSagaContext.name}]`;
751
855
  try {
752
856
  if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
753
857
 
@@ -765,9 +869,10 @@ export class SyncSagaCoordinator {
765
869
  */
766
870
  const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
767
871
 
872
+ let nextFrameInfo: NextSagaFrameInfo;
768
873
  switch (stage) {
769
874
  case SyncStage.init:
770
- return await this.handleInitFrame({
875
+ nextFrameInfo = await this.handleInitFrame({
771
876
  sagaIbGib,
772
877
  messageData: messageData as SyncSagaMessageInitData_V1,
773
878
  metaspace,
@@ -776,22 +881,39 @@ export class SyncSagaCoordinator {
776
881
  identity,
777
882
  identitySecret
778
883
  });
884
+ break;
779
885
 
780
886
  case SyncStage.ack:
781
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
887
+ if (!initDomainGraph) { throw new Error(`(UNEXPECTED) initDomainGraph falsy on the sender? (E: a3d758ad954829aba88663188eafc826)`); }
888
+ nextFrameInfo = await this.handleAckFrame({
889
+ sagaIbGib,
890
+ srcGraph,
891
+ initDomainGraph,
892
+ metaspace,
893
+ destSpace: mySpace,
894
+ tempSpace: myTempSpace,
895
+ identity,
896
+ });
897
+ break;
782
898
 
783
899
  case SyncStage.delta:
784
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
900
+ nextFrameInfo = await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
901
+ break;
785
902
 
786
903
  case SyncStage.commit:
787
- return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
904
+ nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
905
+ break;
788
906
 
789
907
  default:
790
908
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
791
909
  }
910
+
911
+ return { errorMsg: undefined, nextFrameInfo, }
912
+
792
913
  } catch (error) {
793
- console.error(`${lc} ${extractErrorMsg(error)}`);
794
- throw error;
914
+ const errorMsg = `${lc} ${extractErrorMsg(error)}`;
915
+ console.error(errorMsg);
916
+ return { errorMsg }
795
917
  } finally {
796
918
  if (logalot) { console.log(`${lc} complete.`); }
797
919
  }
@@ -801,10 +923,10 @@ export class SyncSagaCoordinator {
801
923
 
802
924
  /**
803
925
  * Handles the `Init` frame.
804
- *
926
+ *
805
927
  * @remarks
806
928
  * **Execution Context**: **Receiver (Remote)**.
807
- *
929
+ *
808
930
  * The Receiver performs Gap Analysis here:
809
931
  * 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
810
932
  * 2. Identifies what Sender needs (`pushOfferAddrs`).
@@ -828,7 +950,7 @@ export class SyncSagaCoordinator {
828
950
  mySpace: IbGibSpaceAny,
829
951
  /**
830
952
  * Local temp space relative to the execution context's POV.
831
- *
953
+ *
832
954
  * NOTE: Since this always executes on the receiver's end, this should
833
955
  * be the receiver's temp space.
834
956
  */
@@ -836,18 +958,17 @@ export class SyncSagaCoordinator {
836
958
  metaspace: MetaspaceService,
837
959
  identity?: KeystoneIbGib_V1,
838
960
  identitySecret?: string,
839
- }): Promise<HandleSagaFrameResult | null> {
961
+ }): Promise<NextSagaFrameInfo> {
840
962
  const lc = `${this.lc}[${this.handleInitFrame.name}]`;
841
963
  try {
842
964
  if (logalot) { console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`); }
843
965
 
844
- console.log(`${lc} [TEST DEBUG] Received destSpace: ${mySpace.data?.name || mySpace.ib} (uuid: ${mySpace.data?.uuid || '[no uuid]'})`);
845
- if (logalot) { console.log(`${lc} starting...`); }
966
+ console.log(`${lc} [TEST DEBUG] Receiver mySpace: ${mySpace.ib}`);
846
967
 
847
968
  // Extract Init Data
848
969
  const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
849
970
  if (initData.stage !== SyncStage.init) {
850
- throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
971
+ throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: c91be82970e4decc58f56bf8fc1ffc26)`);
851
972
  }
852
973
  // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
853
974
  if (!initData || !initData.knowledgeVector) {
@@ -859,8 +980,8 @@ export class SyncSagaCoordinator {
859
980
 
860
981
  // 2. Gap Analysis
861
982
  const conflicts: SyncSagaConflictInfo[] = [];
862
- const deltaReqAddrs: string[] = [];
863
- const pushOfferAddrs: string[] = [];
983
+ const deltaRequestAddrInfos: SyncSagaRequestAddrInfo[] = [];
984
+ const pushOfferInfos: SyncSagaPushOfferInfo[] = [];
864
985
 
865
986
  // First do the consant stones (Non-TJPs)
866
987
  const stones = initData.stones || [];
@@ -874,13 +995,15 @@ export class SyncSagaCoordinator {
874
995
  if (addrsNotFound && addrsNotFound.length > 0) {
875
996
  if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
876
997
  addrsNotFound.forEach(addr => {
877
- if (!deltaReqAddrs.includes(addr)) { deltaReqAddrs.push(addr); }
998
+ if (!deltaRequestAddrInfos.some(x => x.addr === addr)) {
999
+ deltaRequestAddrInfos.push({ addr }); // no tjpAddr
1000
+ }
878
1001
  });
879
1002
  }
880
1003
  }
881
1004
 
882
1005
  /**
883
- * "remote" local to receiver's context is the sender
1006
+ * "remote" is sender in this case
884
1007
  */
885
1008
  const remoteKV = initData.knowledgeVector;
886
1009
  if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
@@ -889,7 +1012,7 @@ export class SyncSagaCoordinator {
889
1012
  if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
890
1013
 
891
1014
  // 1. Get Local Latest Addrs for all TJPs
892
- let localKV: { [tjp: string]: string | null } = {};
1015
+ let localLatestAddrsMap: { [tjp: string]: string | null } = {};
893
1016
  if (remoteTjps.length > 0) {
894
1017
  // Batch get latest addrs for the TJPs
895
1018
  const resGetLatestAddrs = await getLatestAddrs({
@@ -898,56 +1021,91 @@ export class SyncSagaCoordinator {
898
1021
  });
899
1022
  if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
900
1023
  if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
901
- localKV = resGetLatestAddrs.data.latestAddrsMap;
902
- console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
903
- if (logalot) { console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`); }
1024
+ localLatestAddrsMap = resGetLatestAddrs.data.latestAddrsMap;
1025
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localLatestAddrsMap)}`);
1026
+ if (logalot) { console.log(`${lc} localKV: ${pretty(localLatestAddrsMap)} (I: 980975642cbccd8018cf0cd808d30826)`); }
904
1027
  }
905
1028
 
906
1029
  // 2. Gap Analysis
907
1030
  for (const tjp of remoteTjps) {
908
1031
  const remoteAddr = remoteKV[tjp];
909
- const localAddr = localKV[tjp];
1032
+ const localAddr = localLatestAddrsMap[tjp];
910
1033
 
911
1034
  if (!localAddr) {
912
1035
  // We (Receiver) don't have this timeline at all. Request it.
913
1036
  console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
914
- deltaReqAddrs.push(remoteAddr);
1037
+ deltaRequestAddrInfos.push({
1038
+ addr: remoteAddr,
1039
+ tjpAddr: tjp,
1040
+ // we don't have this timeline at all
1041
+ // latestAddrAlreadyHave: undefined
1042
+ });
915
1043
  continue;
916
1044
  }
917
1045
 
1046
+ // we do have this timeline...
1047
+
918
1048
  if (localAddr === remoteAddr) {
919
- // Synced
1049
+ // ...already synced
920
1050
  console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
921
1051
  continue;
922
1052
  }
923
1053
  console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
924
1054
 
925
- // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
926
- // (Sender has older version, Receiver has newer) -> Receiver Offers Push
927
- const isRemoteInPast = await isPastFrame({
1055
+ // we have this timeline but it's not synced...
1056
+
1057
+ // We're executing on receiver. Check if Remote (Sender) is in
1058
+ // our past, and if so, we are ahead and need to push the delta
1059
+ // to remote
1060
+ const remoteIsInPast = await isPastFrame({
928
1061
  olderAddr: remoteAddr,
929
1062
  newerAddr: localAddr,
930
1063
  space: mySpace,
931
1064
  });
932
1065
 
933
- if (isRemoteInPast) {
934
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
935
- pushOfferAddrs.push(localAddr);
1066
+ if (remoteIsInPast) {
1067
+ // we're ahead, so push the delta of what the sender doesn't
1068
+ // have (we have full knowledge)
1069
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote (sender) is in past - offering push`);
1070
+ const deltaGraph = await getDeltaDependencyGraph({
1071
+ ibGibAddr: localAddr,
1072
+ live: false, // always live: false right?
1073
+ latestCommonFrameAddr: remoteAddr,
1074
+ space: mySpace,
1075
+ });
1076
+ pushOfferInfos.push({ tjpAddr: tjp, addrs: Object.keys(deltaGraph), });
936
1077
  } else {
937
- // Remote is not in our past.
938
- // Either Remote is ahead (Fast-Backward) OR Diverged.
1078
+ // Remote tip is not in our past. So, either Remote is
1079
+ // ahead (we're in THEIR past, i.e., Fast-Backward) OR
1080
+ // Diverged/conflict (we've both made edits).
939
1081
 
940
1082
  // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
941
- const isLocalInPast = await isPastFrame({
942
- olderAddr: localAddr,
943
- newerAddr: remoteAddr,
944
- space: mySpace,
945
- });
1083
+ /**
1084
+ * we could first check for existence of remoteAddr in
1085
+ * mySpace, but this is quick and easy. If it throws, we
1086
+ * don't have it so it can't be in the past.
1087
+ */
1088
+ let localIsInPast = false;
1089
+ try {
1090
+ // we could
1091
+ localIsInPast = await isPastFrame({
1092
+ olderAddr: localAddr,
1093
+ newerAddr: remoteAddr,
1094
+ space: mySpace,
1095
+ });
1096
+ } catch (error) {
1097
+ // couldn't get one of them, so localIsInPast remains false.
1098
+ if (logalot) { console.log(`${lc} expected error if we don't have remote. verbose logging error though: ${extractErrorMsg(error)} (I: 47ea08d0b418cf4aa8a502a7bcb80826)`); }
1099
+ }
946
1100
 
947
- if (isLocalInPast) {
1101
+ if (localIsInPast) {
948
1102
  // Fast-Forward: We update to remote's tip.
949
1103
  console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
950
- deltaReqAddrs.push(remoteAddr);
1104
+ deltaRequestAddrInfos.push({
1105
+ addr: remoteAddr,
1106
+ tjpAddr: tjp,
1107
+ latestAddrAlreadyHave: localAddr,
1108
+ });
951
1109
  } else {
952
1110
  // DIVERGENCE: Both have changes the other doesn't know about.
953
1111
  console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
@@ -966,7 +1124,7 @@ export class SyncSagaCoordinator {
966
1124
  terminal: true
967
1125
  });
968
1126
  } else if (conflictStrategy === 'optimistic') {
969
- // Optimistic: We want to resolving this.
1127
+ // Optimistic: We want resolve this.
970
1128
  // We need to send our history to the Sender so they can Merge.
971
1129
 
972
1130
  // Fetch Full History for Local Timeline
@@ -979,10 +1137,10 @@ export class SyncSagaCoordinator {
979
1137
  // We have localAddr.
980
1138
  const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
981
1139
  if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
982
- throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: ff06ff849fa8e8dba32ce09807411226)`);
1140
+ throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: 83cb88a767e22bbda99c6788bec50526)`);
983
1141
  }
984
1142
  const localTip = resLocalTip.ibGibs[0];
985
- if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`); }
1143
+ if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: c39448ad6b3a72af78339ad877a56826)`); }
986
1144
 
987
1145
  const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
988
1146
 
@@ -994,9 +1152,8 @@ export class SyncSagaCoordinator {
994
1152
  reason: 'divergence',
995
1153
  terminal: false
996
1154
  });
997
-
998
1155
  } else {
999
- throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
1156
+ throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy}. Only these currently implemented: ${SYNC_CONFLICT_STRATEGY_VALID_VALUES} (E: 8f12384180f8a718a983a749fe0adf26)`);
1000
1157
  }
1001
1158
  }
1002
1159
  }
@@ -1012,57 +1169,15 @@ export class SyncSagaCoordinator {
1012
1169
  throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
1013
1170
  }
1014
1171
 
1015
- // 3. Build Knowledge Vector (Full History for known timelines)
1016
- // [NEW] Smart Diff
1017
- // We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
1018
- // Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
1019
-
1020
- /**
1021
- * we will put this in {@link SyncSagaMessageAckData_V1.knowledgeVector}
1022
- */
1023
- const knowledgeVector: { [groupKey: string]: string[] } = {};
1024
-
1025
- // [Smart Diff] Populate knowledge from timelines identified by Sender
1026
- for (const tjp of remoteTjps) {
1027
- const localAddr = localKV[tjp];
1028
- if (localAddr) {
1029
- const res = await getFromSpace({ addr: localAddr, space: mySpace });
1030
- if (res.success && res.ibGibs?.[0]) {
1031
- const ibGib = res.ibGibs[0];
1032
- const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
1033
- if (!knowledgeVector[realTjp]) {
1034
- const past = ibGib.rel8ns?.past || [];
1035
- knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
1036
- }
1037
- }
1038
- }
1039
- }
1040
-
1041
- // Also populate from `knowledgeVector` in Init if we want bidirectional?
1042
- // No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
1043
- // Let's assume standard flow:
1044
- // 1. Sender says "I have X"
1045
- // 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
1046
- // Problem: Receiver doesn't know X is related to Y without X.
1047
-
1048
- // SOLUTION:
1049
- // The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
1050
- // `SyncSagaMessageInitData_V1` extends `SyncInitData`.
1051
- // `SyncInitData` has `knowledgeVector`.
1052
- // If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
1053
-
1054
- // Let's implement Sender populating `Init.knowledgeVector`.
1055
- // But `SyncSagaCoordinator.startSaga` creates Init.
1056
-
1057
1172
  // 3. Create Ack Frame
1058
- const sagaId = sagaIbGib.data!.uuid;
1173
+ if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 24203af4600fb226ae6c1afbde442826)`); }
1174
+ const sagaId = sagaIbGib.data.uuid;
1059
1175
  // Create Payload Stone
1060
1176
  const ackData: SyncSagaMessageAckData_V1 = {
1061
1177
  sagaId,
1062
1178
  stage: SyncStage.ack,
1063
- deltaReqAddrs,
1064
- pushOfferAddrs,
1065
- knowledgeVector,
1179
+ deltaRequestAddrInfos,
1180
+ pushOfferInfos,
1066
1181
  };
1067
1182
  if (conflicts?.length > 0) { ackData.conflicts = conflicts; }
1068
1183
 
@@ -1088,15 +1203,60 @@ export class SyncSagaCoordinator {
1088
1203
  * we want to push ibgibs to the remote/sender if we have push
1089
1204
  * offers. an ack frame's payloads, if any, are those push offers
1090
1205
  */
1091
- // let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
1092
-
1093
- if (pushOfferAddrs.length > 0) {
1206
+ let payloadIbGibsDomain: IbGib_V1[] | undefined;
1207
+ if (pushOfferInfos.length > 0) {
1208
+ const searchSecondSpaceAddrs: IbGibAddr[] = [];
1209
+ payloadIbGibsDomain = [];
1210
+ const domainAddrs: IbGibAddr[] = pushOfferInfos.flatMap(x => x.addrs);
1211
+ const resGet = await getFromSpace({ addrs: domainAddrs, space: mySpace, });
1212
+ const resGetRawData = resGet.rawResultIbGib?.data as IbGibSpaceResultData | undefined;
1213
+ if (!resGetRawData) { throw new Error(`(UNEXPECTED) resGet.rawResultIbGib.data falsy? (E: 1a2cc8cb99a1ffa60837e45a8229b826)`); }
1214
+ const addrsNotFound = resGetRawData.addrsNotFound ?? [];
1215
+ const addrsErrored = resGetRawData.addrsErrored ?? [];
1216
+ if (resGet.ibGibs && resGet.ibGibs.length === domainAddrs.length) {
1217
+ // found all of them
1218
+ resGet.ibGibs.forEach(x => { payloadIbGibsDomain!.push(x) })
1219
+ } else if (resGet.ibGibs && resGet.ibGibs.length > 0) {
1220
+ // found some of them
1221
+ resGet.ibGibs.forEach(x => { payloadIbGibsDomain!.push(x) })
1222
+ const foundPlusNotFoundCount =
1223
+ payloadIbGibsDomain.length +
1224
+ addrsNotFound.length +
1225
+ addrsErrored.length;
1226
+ if (foundPlusNotFoundCount === domainAddrs.length) {
1227
+ // we can still conceivably get the remaining addrs
1228
+ addrsNotFound.forEach(x => searchSecondSpaceAddrs.push(x));
1229
+ addrsErrored.forEach(x => searchSecondSpaceAddrs.push(x));
1230
+ } else if (addrsNotFound.length === 0 && addrsErrored.length === 0) {
1231
+ throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored both empty? (E: ef1b2cf5bab8de2298ec507abe04e826)`);
1232
+ } else {
1233
+ throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored don't contain the addrs not found? (E: ae9e015109f8c3c3a813da584cd98826)`);
1234
+ }
1235
+ } else {
1236
+ // found none of them(?)
1237
+ console.warn(`${lc} we were expecting all domainAddrs in mySpace (${mySpace.ib}) but we couldn't get all of them. addrsNotFound: ${addrsNotFound ?? []}. addrsErrored: ${addrsErrored ?? []}. Trying temp space (${myTempSpace.ib}). (W: f61908e264f8dddd2e3fb9084463d826)`);
1238
+ domainAddrs.forEach(x => searchSecondSpaceAddrs.push(x));
1239
+ }
1240
+ if (searchSecondSpaceAddrs.length > 0) {
1241
+ if (logalot) { console.log(`${lc} couldn't get all addrs in mySpace (${mySpace.ib}). searchSecondSpaceAddrs: ${searchSecondSpaceAddrs} (I: 233fd954dbd84e51bca02fa8eed5f826)`); }
1242
+ const resGet2 = await getFromSpace({ addrs: searchSecondSpaceAddrs, space: myTempSpace, });
1243
+ if (resGet2.success && resGet2.ibGibs && resGet2.ibGibs.length === searchSecondSpaceAddrs.length) {
1244
+ // got them all the second try
1245
+ resGet2.ibGibs.forEach(x => payloadIbGibsDomain!.push(x));
1246
+ } else {
1247
+ resGet2.ibGibs?.forEach(x => payloadIbGibsDomain!.push(x));
1248
+ const addrsFailed = domainAddrs.filter(x =>
1249
+ !payloadIbGibsDomain!.map(pid => getIbGibAddr({ ibGib: pid })).includes(x)
1250
+ );
1251
+ throw new Error(`couldn't get some or all of addrs from either mySpace (${mySpace.ib}) or myTempSpace (${myTempSpace.ib}). addrsFailed: ${addrsFailed} (E: 1394d412c4ffa4dd085f269b43338826)`);
1252
+ }
1253
+ }
1254
+ // we have now populated payloadIbGibsDomain
1094
1255
  }
1095
1256
 
1096
1257
  return {
1097
1258
  frame: ackFrame,
1098
- // conflictInfos,
1099
- // payloadIbGibsDomain,
1259
+ payloadIbGibsDomain,
1100
1260
  };
1101
1261
 
1102
1262
  } catch (error) {
@@ -1109,19 +1269,20 @@ export class SyncSagaCoordinator {
1109
1269
 
1110
1270
  /**
1111
1271
  * Handles the `Ack` frame.
1112
- *
1272
+ *
1113
1273
  * @remarks
1114
1274
  * **Execution Context**: **Sender (Local)**.
1115
- *
1275
+ *
1116
1276
  * The Sender reacts to the Receiver's requirements:
1117
1277
  * 1. `deltaReqAddrs`: Receiver wants this data. Sender gathers it from `srcGraph` and puts it in `payloadIbGibs` (for next frame).
1118
1278
  * 2. `pushOfferAddrs`: Receiver has newer data. Sender acknowledges and adds them to `requests` (asking Receiver to send them).
1119
- *
1279
+ *
1120
1280
  * Returns a `Delta` frame.
1121
1281
  */
1122
1282
  protected async handleAckFrame({
1123
1283
  sagaIbGib,
1124
1284
  srcGraph,
1285
+ initDomainGraph,
1125
1286
  destSpace,
1126
1287
  tempSpace,
1127
1288
  metaspace,
@@ -1129,11 +1290,18 @@ export class SyncSagaCoordinator {
1129
1290
  }: {
1130
1291
  sagaIbGib: SyncIbGib_V1,
1131
1292
  srcGraph: { [addr: string]: IbGib_V1 },
1293
+ /**
1294
+ * This is the initial dependency graph of all domain ibgibs passed in
1295
+ * to the original {@link sync} call.
1296
+ *
1297
+ * if we're executing on the sender, this will be populated
1298
+ */
1299
+ initDomainGraph: FlatIbGibGraph,
1132
1300
  destSpace: IbGibSpaceAny,
1133
1301
  tempSpace: IbGibSpaceAny,
1134
1302
  metaspace: MetaspaceService,
1135
1303
  identity?: KeystoneIbGib_V1,
1136
- }): Promise<HandleSagaFrameResult | null> {
1304
+ }): Promise<NextSagaFrameInfo> {
1137
1305
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
1138
1306
  try {
1139
1307
  if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
@@ -1161,14 +1329,15 @@ export class SyncSagaCoordinator {
1161
1329
  console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
1162
1330
  // Terminal failure. Sender should probably Commit(Fail) or just Abort.
1163
1331
  // For now, throw to trigger abort.
1164
- throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
1332
+ throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
1165
1333
  }
1166
1334
 
1167
- const optimisticConflicts = conflicts.filter(c => !c.terminal);
1335
+ // at this point, if we have conflicts, they are non-terminal
1336
+
1168
1337
  const mergeDeltaReqs: string[] = []; // Additional requests for merging
1169
1338
 
1170
- if (optimisticConflicts.length > 0) {
1171
- console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
1339
+ if (conflicts.length > 0) {
1340
+ console.log(`${lc} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
1172
1341
  // We need to resolve these.
1173
1342
  // Strategy:
1174
1343
  // 1. Analyze Divergence (Sender vs Receiver)
@@ -1189,7 +1358,7 @@ export class SyncSagaCoordinator {
1189
1358
  // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
1190
1359
 
1191
1360
  // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
1192
- // Or we just proceed to Delta (sending what Receiver wants),
1361
+ // Or we just proceed to Delta (sending what Receiver wants),
1193
1362
  // AND we piggyback our own requests?
1194
1363
  // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
1195
1364
 
@@ -1201,126 +1370,131 @@ export class SyncSagaCoordinator {
1201
1370
 
1202
1371
  // Let's analyze and pull immediately.
1203
1372
 
1204
- for (const conflict of optimisticConflicts) {
1205
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1206
-
1207
- // Sender History
1208
- // We need our own history for this timeline.
1209
- // We know the 'senderTip' (remoteAddr in Ack).
1210
- // Sender should verify it has this tip.
1211
-
1212
- // Compute Diffs
1213
- // We need to find `receiverOnly` addrs.
1214
- // Receiver sent us `timelineAddrs` (Full History).
1215
- const receiverHistorySet = new Set(timelineAddrs);
1216
-
1217
- // We need our execution context's history for this senderTip.
1218
- // We can fetch valid 'past' from space.
1219
- const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1220
- const senderTipIbGib = resSenderTip.ibGibs?.[0];
1221
- if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`); }
1222
-
1223
- // Basic Diff: Find what Receiver has that we don't.
1224
- // Actually, we need to traverse OUR past to find commonality.
1225
- const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1226
-
1227
- const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1228
-
1229
- if (receiverOnlyAddrs.length > 0) {
1230
- console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1231
- console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1232
- // PULL these frames from Peer into Local Space
1233
- // (Validation: We trust peer for now / verification happens on put)
1234
- for (const addr of receiverOnlyAddrs) {
1235
- console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1236
- }
1237
-
1238
- // Compute DELTA dependencies for each receiver-only frame
1239
- // Find LCA to determine what dependencies we already have
1240
- const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1241
- console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1242
-
1243
- const skipAddrsSet = new Set<string>();
1244
- if (lcaAddr) {
1245
- try {
1246
- const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1247
- const lcaIbGib = lcaRes.ibGibs?.[0];
1248
- if (lcaIbGib) {
1249
- const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1250
- if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1251
- console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1252
- }
1253
- } catch (e) {
1254
- console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1255
- }
1256
- }
1257
-
1258
-
1259
- // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1260
- for (const addr of receiverOnlyAddrs) {
1261
- // Add the frame itself first
1262
- if (!mergeDeltaReqs.includes(addr)) {
1263
- mergeDeltaReqs.push(addr);
1264
- }
1265
-
1266
- // Get the frame's delta dependencies (skip LCA's deps)
1267
- try {
1268
- const frameRes = await getFromSpace({ addr, space: destSpace });
1269
- const frameIbGib = frameRes.ibGibs?.[0];
1270
-
1271
- if (frameIbGib) {
1272
- // Get dependency graph, skipping all LCA dependencies
1273
- const frameDeltaDeps = await getDependencyGraph({
1274
- ibGib: frameIbGib,
1275
- space: destSpace,
1276
- skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1277
- });
1278
-
1279
- if (frameDeltaDeps) {
1280
- // Add all delta dependencies (Object.keys gives us the addresses)
1281
- Object.keys(frameDeltaDeps).forEach(depAddr => {
1282
- if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1283
- mergeDeltaReqs.push(depAddr);
1284
- }
1285
- });
1286
- }
1287
- }
1288
- } catch (depError) {
1289
- console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1290
- }
1291
- }
1292
-
1293
- console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1294
- } else {
1295
- console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1296
- }
1373
+ for (const conflict of conflicts) {
1374
+ // 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
1375
+
1376
+ throw new Error(`conflicts not (re)implemented yet (E: 3b7d0819f83842a6de3ae988819bc826)`);
1377
+
1378
+ // const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1379
+
1380
+ // // Sender History
1381
+ // // We need our own history for this timeline.
1382
+ // // We know the 'senderTip' (remoteAddr in Ack).
1383
+ // // Sender should verify it has this tip.
1384
+
1385
+ // // Compute Diffs
1386
+ // // We need to find `receiverOnly` addrs.
1387
+ // // Receiver sent us `timelineAddrs` (Full History).
1388
+ // const receiverHistorySet = new Set(timelineAddrs);
1389
+
1390
+ // // We need our execution context's history for this senderTip.
1391
+ // // We can fetch valid 'past' from space.
1392
+ // const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1393
+ // const senderTipIbGib = resSenderTip.ibGibs?.[0];
1394
+ // if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`); }
1395
+
1396
+ // // Basic Diff: Find what Receiver has that we don't.
1397
+ // // Actually, we need to traverse OUR past to find commonality.
1398
+ // const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1399
+
1400
+ // const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1401
+
1402
+ // if (receiverOnlyAddrs.length > 0) {
1403
+ // console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1404
+ // console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1405
+ // // PULL these frames from Peer into Local Space
1406
+ // // (Validation: We trust peer for now / verification happens on put)
1407
+ // for (const addr of receiverOnlyAddrs) {
1408
+ // console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1409
+ // }
1410
+
1411
+ // // Compute DELTA dependencies for each receiver-only frame
1412
+ // // Find LCA to determine what dependencies we already have
1413
+ // const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1414
+ // console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1415
+
1416
+ // const skipAddrsSet = new Set<string>();
1417
+ // if (lcaAddr) {
1418
+ // try {
1419
+ // const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1420
+ // const lcaIbGib = lcaRes.ibGibs?.[0];
1421
+ // if (lcaIbGib) {
1422
+ // const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1423
+ // if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1424
+ // console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1425
+ // }
1426
+ // } catch (e) {
1427
+ // console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1428
+ // }
1429
+ // }
1430
+
1431
+
1432
+ // // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1433
+ // for (const addr of receiverOnlyAddrs) {
1434
+ // // Add the frame itself first
1435
+ // if (!mergeDeltaReqs.includes(addr)) {
1436
+ // mergeDeltaReqs.push(addr);
1437
+ // }
1438
+
1439
+ // // Get the frame's delta dependencies (skip LCA's deps)
1440
+ // try {
1441
+ // const frameRes = await getFromSpace({ addr, space: destSpace });
1442
+ // const frameIbGib = frameRes.ibGibs?.[0];
1443
+
1444
+ // if (frameIbGib) {
1445
+ // // Get dependency graph, skipping all LCA dependencies
1446
+ // const frameDeltaDeps = await getDependencyGraph({
1447
+ // ibGib: frameIbGib,
1448
+ // space: destSpace,
1449
+ // skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1450
+ // });
1451
+
1452
+ // if (frameDeltaDeps) {
1453
+ // // Add all delta dependencies (Object.keys gives us the addresses)
1454
+ // Object.keys(frameDeltaDeps).forEach(depAddr => {
1455
+ // if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1456
+ // mergeDeltaReqs.push(depAddr);
1457
+ // }
1458
+ // });
1459
+ // }
1460
+ // }
1461
+ // } catch (depError) {
1462
+ // console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1463
+ // }
1464
+ // }
1465
+
1466
+ // console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1467
+ // } else {
1468
+ // console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1469
+ // }
1297
1470
  }
1298
1471
 
1299
- console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
1472
+ console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
1300
1473
  } else {
1301
1474
  console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1302
1475
  }
1303
1476
 
1304
1477
  // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1305
1478
 
1306
- const deltaReqAddrs = ackData.deltaReqAddrs || [];
1307
- const pushOfferAddrs = ackData.pushOfferAddrs || [];
1479
+ const deltaReqAddrs = ackData.deltaRequestAddrInfos || [];
1480
+ const pushOfferAddrs = ackData.pushOfferInfos || [];
1308
1481
 
1309
1482
  // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1310
1483
  const pullReqAddrs: string[] = [];
1311
1484
  for (const addr of pushOfferAddrs) {
1312
- const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1313
- if (!existing) {
1314
- pullReqAddrs.push(addr);
1315
- }
1485
+ // const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1486
+ // if (!existing) {
1487
+ // pullReqAddrs.push(addr);
1488
+ // }
1316
1489
  }
1317
1490
 
1318
1491
  // 2. Process Delta Requests (Push Payload)
1319
1492
  // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1493
+ // const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
1320
1494
  const skipAddrs = new Set<string>();
1321
1495
  if (ackData.knowledgeVector) {
1322
1496
  Object.values(ackData.knowledgeVector).forEach(addrs => {
1323
- addrs.forEach(a => skipAddrs.add(a));
1497
+ // addrs.forEach(a => skipAddrs.add(a));
1324
1498
  });
1325
1499
  }
1326
1500
 
@@ -1328,24 +1502,24 @@ export class SyncSagaCoordinator {
1328
1502
  // Gather all tips to sync first
1329
1503
  const tipsToSync: IbGib_V1[] = [];
1330
1504
  for (const addr of deltaReqAddrs) {
1331
- let ibGib = srcGraph[addr];
1332
- if (!ibGib) {
1333
- const res = await getFromSpace({ addr, space: destSpace });
1334
- if (res.ibGibs && res.ibGibs.length > 0) {
1335
- ibGib = res.ibGibs[0];
1336
- }
1337
- }
1338
- if (ibGib) {
1339
- tipsToSync.push(ibGib);
1340
- } else {
1341
- throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1342
- }
1505
+ // let ibGib = srcGraph[addr];
1506
+ // if (!ibGib) {
1507
+ // const res = await getFromSpace({ addr, space: destSpace });
1508
+ // if (res.ibGibs && res.ibGibs.length > 0) {
1509
+ // ibGib = res.ibGibs[0];
1510
+ // }
1511
+ // }
1512
+ // if (ibGib) {
1513
+ // tipsToSync.push(ibGib);
1514
+ // } else {
1515
+ // throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1516
+ // }
1343
1517
  }
1344
1518
 
1345
1519
  // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1346
1520
  // Pass skipAddrs to `getDependencyGraph` or gather manually.
1347
1521
  // `getDependencyGraph` takes a single ibGib.
1348
- // We can optimize by doing it for each tip and unioning the result?
1522
+ // We can optimize by doing it for each tip and unioning the result?
1349
1523
  // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1350
1524
  // We will loop.
1351
1525
 
@@ -1420,7 +1594,8 @@ export class SyncSagaCoordinator {
1420
1594
  if (identity) { payloadIbGibsControl.push(identity); }
1421
1595
 
1422
1596
  // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1423
- return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1597
+ // return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1598
+ throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
1424
1599
  } catch (error) {
1425
1600
  console.error(`${lc} ${extractErrorMsg(error)}`);
1426
1601
  throw error;
@@ -1431,10 +1606,10 @@ export class SyncSagaCoordinator {
1431
1606
 
1432
1607
  /**
1433
1608
  * Handles the `Delta` frame.
1434
- *
1609
+ *
1435
1610
  * @remarks
1436
1611
  * **Execution Context**: **Both**.
1437
- *
1612
+ *
1438
1613
  * 1. **Ingestion**: Receives data sent by Peer (`payloadAddrs`). Resolves them from `Rel8ns.payload` (implicitly via space/context) and returns them in `receivedPayload` to be saved.
1439
1614
  * 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
1440
1615
  * 3. **Completion**: If no more requests, transitions to `Commit`.
@@ -1453,7 +1628,7 @@ export class SyncSagaCoordinator {
1453
1628
  tempSpace: IbGibSpaceAny,
1454
1629
  metaspace: MetaspaceService,
1455
1630
  identity?: KeystoneIbGib_V1,
1456
- }): Promise<HandleSagaFrameResult | null> {
1631
+ }): Promise<NextSagaFrameInfo> {
1457
1632
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1458
1633
  if (logalot) { console.log(`${lc} starting...`); }
1459
1634
 
@@ -1466,7 +1641,7 @@ export class SyncSagaCoordinator {
1466
1641
  if (deltaData.stage !== SyncStage.delta) {
1467
1642
  throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
1468
1643
  }
1469
- if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`); }
1644
+ if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
1470
1645
 
1471
1646
  console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1472
1647
 
@@ -1477,8 +1652,8 @@ export class SyncSagaCoordinator {
1477
1652
  // 1. Process Received Payload (Ingest)
1478
1653
  const receivedPayloadIbGibs: IbGib_V1[] = [];
1479
1654
  if (payloadAddrs.length > 0) {
1480
- // We use `payloadAddrs` as the manifest.
1481
- // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1655
+ // We use `payloadAddrs` as the manifest.
1656
+ // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1482
1657
  // assuming the "Transport" layer put them there implicitly?
1483
1658
  // OR, if we are local-only, we just get them.
1484
1659
  // The `handleDeltaFrame` contract assumes data is reachable in `space`.
@@ -1506,7 +1681,7 @@ export class SyncSagaCoordinator {
1506
1681
  // Get the requested ibGib
1507
1682
  let ibGib = srcGraph[addr];
1508
1683
  if (!ibGib) {
1509
- const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1684
+ const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1510
1685
  if (res.ibGibs && res.ibGibs.length > 0) {
1511
1686
  ibGib = res.ibGibs[0];
1512
1687
  }
@@ -1602,7 +1777,7 @@ export class SyncSagaCoordinator {
1602
1777
  console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1603
1778
  if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1604
1779
  // We have the tip!
1605
- // Do we have the full history?
1780
+ // Do we have the full history?
1606
1781
  // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1607
1782
  // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1608
1783
 
@@ -1648,9 +1823,9 @@ export class SyncSagaCoordinator {
1648
1823
  payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1649
1824
  requests: hasMyRequests ? myRequests : undefined,
1650
1825
  proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1651
- // Wait. If we send data, we are NOT committing yet.
1826
+ // Wait. If we send data, we are NOT committing yet.
1652
1827
  // We are sending data. The OTHER side must ingest it.
1653
- // So proposeCommit = true?
1828
+ // So proposeCommit = true?
1654
1829
  // "Here is the data. I'm done. If you are good, let's commit."
1655
1830
  // Yes.
1656
1831
  };
@@ -1680,7 +1855,8 @@ export class SyncSagaCoordinator {
1680
1855
  if (identity) { payloadIbGibsControl.push(identity); }
1681
1856
 
1682
1857
  // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1683
- return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1858
+ // return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1859
+ throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1684
1860
 
1685
1861
  } else {
1686
1862
  // We have nothing to send.
@@ -1712,7 +1888,8 @@ export class SyncSagaCoordinator {
1712
1888
  if (identity) { commitCtrlPayloads.push(identity); }
1713
1889
 
1714
1890
  // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1715
- return { frame: commitFrame, };
1891
+ // return { frame: commitFrame, };
1892
+ throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
1716
1893
 
1717
1894
  } else {
1718
1895
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1770,7 +1947,8 @@ export class SyncSagaCoordinator {
1770
1947
  if (identity) { commitCtrlPayloads2.push(identity); }
1771
1948
 
1772
1949
  // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1773
- return { frame: commitFrame, };
1950
+ // return { frame: commitFrame, };
1951
+ throw new Error(`not implemented (E: 27514878585889e531ef21f1abbef826)`);
1774
1952
  }
1775
1953
 
1776
1954
  // Build control payloads for delta propose
@@ -1778,7 +1956,8 @@ export class SyncSagaCoordinator {
1778
1956
  if (identity) { deltaCtrlPayloads.push(identity); }
1779
1957
 
1780
1958
  // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1781
- return { frame: deltaFrame, };
1959
+ // return { frame: deltaFrame, };
1960
+ throw new Error(`not implemented (E: ff35584696b6fcb3ad6dd7c5cade2f26)`);
1782
1961
  }
1783
1962
  }
1784
1963
  }
@@ -1796,7 +1975,7 @@ export class SyncSagaCoordinator {
1796
1975
  tempSpace: IbGibSpaceAny,
1797
1976
  metaspace: MetaspaceService,
1798
1977
  identity?: KeystoneIbGib_V1,
1799
- }): Promise<HandleSagaFrameResult | null> {
1978
+ }): Promise<NextSagaFrameInfo> {
1800
1979
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1801
1980
  if (logalot) { console.log(`${lc} Commit received.`); }
1802
1981
 
@@ -1811,7 +1990,8 @@ export class SyncSagaCoordinator {
1811
1990
  // Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
1812
1991
 
1813
1992
  if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
1814
- return null;
1993
+ // return { responseWasNull: true };
1994
+ throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
1815
1995
  }
1816
1996
 
1817
1997
  // #endregion Handlers
@@ -1909,7 +2089,7 @@ export class SyncSagaCoordinator {
1909
2089
  rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
1910
2090
  }
1911
2091
 
1912
- // remove the existing sync msg stones' addrs
2092
+ // remove the existing sync msg stones' addrs
1913
2093
  if (!prevSagaIbGib.rel8ns) { throw new Error(`(UNEXPECTED) prevSagaIbGib.rel8ns falsy? (E: 81375841aff85b1e48ea42ca218e6826)`); }
1914
2094
  if (!prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME] || prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME].length === 0) {
1915
2095
  throw new Error(`(UNEXPECTED) prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME] falsy/empty? (E: 15156baad26fcccda80aa3a31718c726)`);