@ibgib/core-gib 0.1.22 → 0.1.23

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 (78) hide show
  1. package/dist/common/other/graph-helper.d.mts +8 -0
  2. package/dist/common/other/graph-helper.d.mts.map +1 -1
  3. package/dist/common/other/graph-helper.mjs +31 -1
  4. package/dist/common/other/graph-helper.mjs.map +1 -1
  5. package/dist/sync/sync-conflict.respec.mjs +12 -11
  6. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  7. package/dist/sync/sync-constants.d.mts +14 -4
  8. package/dist/sync/sync-constants.d.mts.map +1 -1
  9. package/dist/sync/sync-constants.mjs +15 -3
  10. package/dist/sync/sync-constants.mjs.map +1 -1
  11. package/dist/sync/sync-helpers.d.mts +22 -15
  12. package/dist/sync/sync-helpers.d.mts.map +1 -1
  13. package/dist/sync/sync-helpers.mjs +159 -90
  14. package/dist/sync/sync-helpers.mjs.map +1 -1
  15. package/dist/sync/sync-innerspace-constants.respec.mjs +11 -10
  16. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  17. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +11 -10
  18. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  19. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +11 -10
  20. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  21. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +12 -10
  22. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  23. package/dist/sync/sync-innerspace-partial-update.respec.mjs +14 -10
  24. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  25. package/dist/sync/sync-innerspace.respec.mjs +12 -10
  26. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  27. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +0 -15
  28. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -1
  29. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +15 -14
  30. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  31. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +9 -55
  32. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  33. package/dist/sync/sync-peer/sync-peer-types.d.mts +16 -0
  34. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  35. package/dist/sync/sync-peer/sync-peer-v1.d.mts +39 -5
  36. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  37. package/dist/sync/sync-peer/sync-peer-v1.mjs +141 -4
  38. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  39. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +22 -3
  40. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  41. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +104 -31
  42. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  43. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +5 -0
  44. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  45. package/dist/sync/sync-saga-coordinator.d.mts +74 -70
  46. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  47. package/dist/sync/sync-saga-coordinator.mjs +475 -527
  48. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  49. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +105 -22
  50. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  51. package/dist/sync/sync-types.d.mts +56 -88
  52. package/dist/sync/sync-types.d.mts.map +1 -1
  53. package/dist/sync/sync-types.mjs +5 -0
  54. package/dist/sync/sync-types.mjs.map +1 -1
  55. package/dist/timeline/timeline-api.d.mts.map +1 -1
  56. package/dist/timeline/timeline-api.mjs +15 -8
  57. package/dist/timeline/timeline-api.mjs.map +1 -1
  58. package/package.json +1 -1
  59. package/src/common/other/graph-helper.mts +26 -1
  60. package/src/sync/sync-conflict.respec.mts +12 -11
  61. package/src/sync/sync-constants.mts +15 -4
  62. package/src/sync/sync-helpers.mts +167 -95
  63. package/src/sync/sync-innerspace-constants.respec.mts +11 -10
  64. package/src/sync/sync-innerspace-deep-updates.respec.mts +11 -10
  65. package/src/sync/sync-innerspace-dest-ahead.respec.mts +11 -10
  66. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +12 -10
  67. package/src/sync/sync-innerspace-partial-update.respec.mts +14 -12
  68. package/src/sync/sync-innerspace.respec.mts +12 -10
  69. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +0 -15
  70. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +16 -65
  71. package/src/sync/sync-peer/sync-peer-types.mts +17 -0
  72. package/src/sync/sync-peer/sync-peer-v1.mts +141 -8
  73. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +116 -37
  74. package/src/sync/sync-saga-context/sync-saga-context-types.mts +5 -0
  75. package/src/sync/sync-saga-coordinator.mts +525 -617
  76. package/src/sync/sync-saga-message/sync-saga-message-types.mts +106 -22
  77. package/src/sync/sync-types.mts +59 -101
  78. package/src/timeline/timeline-api.mts +17 -10
@@ -1,23 +1,21 @@
1
1
  import { extractErrorMsg, getUUID, // so our timestamp in ticks as a string are uniform
2
- pretty, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
2
+ pretty, delay, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
3
3
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
4
- import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
5
4
  import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
6
5
  import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
7
6
  import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
8
7
  import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
9
- import { SyncMode, } from "./sync-types.mjs";
10
- import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
11
- import { getSyncSagaDependencyGraph as getSyncSagaDependencyGraphForThisFrameOnly } from "./sync-helpers.mjs";
12
- import { getDependencyGraph } from "../common/other/graph-helper.mjs";
8
+ import { SyncConflictStrategy, SyncMode, } from "./sync-types.mjs";
9
+ import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
10
+ import { getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
13
11
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
14
12
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
13
+ import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
15
14
  import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
16
15
  import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
17
16
  import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
18
17
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
19
18
  import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
20
- import { SyncPeerInnerspace_V1 } from "./sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs";
21
19
  // const logalot = GLOBAL_LOG_A_LOT || true;
22
20
  const logalot = false;
23
21
  const logalotControlDomain = true;
@@ -54,14 +52,13 @@ export class SyncSagaCoordinator {
54
52
  * @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
55
53
  * @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
56
54
  */
57
- async sync({ peer, localSpace: _localSpace, source: _source, metaspace, domainIbGibs, conflictStrategy = 'abort', useSessionIdentity = true, }) {
55
+ async sync({ peer, domainIbGibs, conflictStrategy = SyncConflictStrategy.abort, useSessionIdentity = true, metaspace, localSpace, }) {
58
56
  const lc = `${this.lc}[${this.sync.name}]`;
59
57
  if (logalot) {
60
58
  console.log(`${lc} starting...`);
61
59
  }
62
- const localSpace = (_source || _localSpace);
63
60
  if (!localSpace) {
64
- throw new Error(`${lc} source (or localSpace) required (E: 8a9b0c1d)`);
61
+ throw new Error(`${lc} source (or localSpace) required (E: 25df3761f7686a1099a552f83c95d326)`);
65
62
  }
66
63
  // 1. SETUP SAGA METADATA
67
64
  const sagaId = await getUUID();
@@ -77,10 +74,6 @@ export class SyncSagaCoordinator {
77
74
  resolveDone = resolve;
78
75
  rejectDone = reject;
79
76
  });
80
- async function getTempSpaceName() {
81
- const uuid = await getUUID();
82
- return `tmp_sync_space_${uuid.substring(0, 8)}`;
83
- }
84
77
  // WORKING CONTEXT (Transactional)
85
78
  const tempSpaceName = await getTempSpaceName();
86
79
  const tempSpace = await metaspace.createNewLocalSpace({
@@ -108,41 +101,24 @@ export class SyncSagaCoordinator {
108
101
  : undefined;
109
102
  // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
110
103
  // 3. CREATE INITIAL FRAME (Stage.init)
111
- const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
104
+ const { sagaFrame: initFrame, initialDomainGraph } = await this.createInitFrame({
112
105
  sagaId,
113
106
  sessionIdentity,
114
- localSpace,
115
107
  domainIbGibs,
116
- tempSpace,
117
- metaspace,
118
- conflictStrategy
119
- });
120
- // 4. EXECUTE SAGA LOOP (FSM)
121
- // Inject tempSpace into peer so it can pull control payloads to the right place
122
- // if ('opts' in peer && peer.opts) {
123
- if (!peer.data) {
124
- throw new Error(`(UNEXPECTED) peer.data falsy? (E: 8546a884c82ffb1999e95d9867da2826)`);
125
- }
126
- if (peer.data.classname === SyncPeerInnerspace_V1.name) {
127
- // (peer as SyncPeerInnerspace_V1).senderTempSpace = tempSpace;
128
- }
129
- const syncedIbGibs = await this.executeSagaLoop({
130
- initialFrame: initFrame,
131
- srcGraph,
132
- peer,
133
- sessionIdentity,
134
- updates$,
135
- localSpace,
136
- tempSpace,
137
- metaspace
108
+ conflictStrategy,
109
+ metaspace, localSpace, tempSpace,
138
110
  });
139
- // 5. MERGE RESULT (Optimistic Commit)
140
- if (syncedIbGibs && syncedIbGibs.length > 0) {
141
- if (logalot) {
142
- console.log(`${lc} Merging ${syncedIbGibs.length} synced ibGibs to localSpace...`);
143
- }
144
- await putInSpace({ space: localSpace, ibGibs: syncedIbGibs });
145
- }
111
+ // 4. KICK OFF THE PING-PONG SAGA LOOP (FSM)
112
+ // commented to compile
113
+ // await this.executeSagaLoop({
114
+ // initialFrame: initFrame,
115
+ // peer,
116
+ // sessionIdentity,
117
+ // updates$,
118
+ // localSpace,
119
+ // tempSpace,
120
+ // metaspace
121
+ // });
146
122
  resolveDone();
147
123
  if (!updates$.complete) {
148
124
  throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`);
@@ -205,130 +181,104 @@ export class SyncSagaCoordinator {
205
181
  * **Execution Context**: **Sender (Local)**.
206
182
  *
207
183
  * This method manages the "Ping Pong" request-response cycle on the Sender.
208
- * It sends frames via the Peer Witness and processes the responses using `handleSagaFrame`.
184
+ * It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
209
185
  *
210
186
  * **Data Transport Note**:
211
187
  * Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
212
- * When `handleSagaFrame` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
188
+ * When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
213
189
  * the NEXT request context.
214
190
  * When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
215
191
  */
216
- async executeSagaLoop({ initialFrame, srcGraph, peer, sessionIdentity, updates$, localSpace, tempSpace, metaspace }) {
192
+ async executeSagaLoop({ initialFrame, initialDomainGraph, peer, sessionIdentity, updates$, localSpace, tempSpace, metaspace }) {
217
193
  const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
218
194
  // The current frame we just generated (e.g., Init or Delta Request)
219
195
  let currentFrame = initialFrame;
220
196
  // The payload we need to attach to the message (data we are sending)
221
197
  let nextDomainIbGibs = [];
222
- // Accumulator for all data we've successfully pulled from the remote
223
- const allReceivedIbGibs = [];
198
+ // First, inject local/tempSpace into peer so it can pull as
199
+ // needed...code smell?
200
+ peer.senderSpace = localSpace;
201
+ peer.senderTempSpace = tempSpace;
224
202
  while (currentFrame) {
225
203
  // A. Create Context (Request)
226
204
  // 1. Calculate Full Dependency Graph (including Ancestors/DNA)
227
- // TODO: adjust this algorithm to only do the dependencies that the other end doesn't need (diff between tip and LCA)
228
205
  // We must do this BEFORE creating the Context so we can list them
229
206
  // all in payloadAddrsDomain and payloadAddrsControl.
230
- // const depsDomainIbGibs: IbGib_V1[] = [];
231
- // const depsControlIbGibs: IbGib_V1[] = [];
232
- const payloadIbGibsControl = [];
233
- const payloadIbGibsDomain = [];
207
+ const nextDomainIbGibsAndDeps = [];
234
208
  // A. Payload (Standard Deep Deps)
209
+ // TODO: THIS IS EXTREMELY INEFFICIENT. adjust this algorithm to
210
+ // only do the dependencies that the other end doesn't need (diff
211
+ // between tip and LCA). This can be done using the information in
212
+ // the ack's conflicts array.
235
213
  for (const nextDomainIbGib of nextDomainIbGibs) {
236
214
  let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
237
215
  if (!nextDomainIbGibGraph) {
238
216
  nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
239
217
  }
240
218
  if (nextDomainIbGibGraph) {
241
- payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
219
+ nextDomainIbGibsAndDeps.push(...Object.values(nextDomainIbGibGraph));
242
220
  }
243
221
  else {
244
222
  throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
245
223
  }
246
224
  }
247
225
  if (logalot) {
248
- console.log(`${lc} payloadIbGibsDomain count: ${payloadIbGibsDomain.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`);
249
- }
250
- // B. Frames (Shallow Sync Deps)
251
- if (currentFrame) {
252
- const depsCurrentFrame = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: currentFrame, space: tempSpace });
253
- if (depsCurrentFrame.length > 0) {
254
- depsCurrentFrame.forEach(x => payloadIbGibsControl.push(x));
255
- }
256
- else {
257
- throw new Error(`(UNEXPECTED) couldn't get deps for currentFrame? currentFrame: ${JSON.stringify(currentFrame)} (E: 06344d07adc80d80b809211171444d26)`);
258
- }
226
+ console.log(`${lc} payloadIbGibsDomain count: ${nextDomainIbGibsAndDeps.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`);
259
227
  }
260
228
  // 2. Create Context (Envelope)
229
+ // set up subscription for response's payload ibgibs (if any)
261
230
  const domainPayloadsMap = new Map();
262
231
  const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
263
- let subscription;
264
- if (peer && peer.payloadIbGibsDomainReceived$) {
265
- // Subscribe to stream
266
- subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
267
- next: async (ibgib) => {
268
- if (logalot) {
269
- console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`);
270
- }
271
- const addr = getIbGibAddr({ ibGib: ibgib });
272
- if (logalotControlDomain) {
273
- console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`);
274
- }
275
- domainPayloadsMap.set(addr, ibgib);
276
- },
277
- error: async (e) => {
278
- if (isIbGib(e)) {
279
- console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
280
- }
281
- else {
282
- console.dir(e);
283
- console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
284
- }
285
- },
286
- complete: async () => {
287
- if (logalot) {
288
- console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`);
289
- }
290
- await subscription.unsubscribe();
291
- },
292
- }));
293
- }
294
- // 2b. Request Context
232
+ const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
233
+ next: async (ibgib) => {
234
+ if (logalot) {
235
+ console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`);
236
+ }
237
+ const addr = getIbGibAddr({ ibGib: ibgib });
238
+ if (logalotControlDomain) {
239
+ console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`);
240
+ }
241
+ domainPayloadsMap.set(addr, ibgib);
242
+ },
243
+ error: async (e) => {
244
+ if (isIbGib(e)) {
245
+ console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
246
+ }
247
+ else {
248
+ console.dir(e);
249
+ console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
250
+ }
251
+ },
252
+ complete: async () => {
253
+ if (logalot) {
254
+ console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`);
255
+ }
256
+ await subscription.unsubscribe();
257
+ },
258
+ }));
259
+ // Create the Request Context...
295
260
  const requestCtx = await createSyncSagaContext({
296
261
  sagaFrame: currentFrame,
297
262
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
298
- payloadIbGibsDomain,
263
+ /**
264
+ * init frame: empty
265
+ *
266
+ */
267
+ payloadIbGibsDomain: nextDomainIbGibsAndDeps,
268
+ localSpace,
299
269
  });
300
270
  // Log what we're sending
301
271
  if (logalotControlDomain) {
302
- const controlAddrs = payloadIbGibsControl.map(p => getIbGibAddr({ ibGib: p }));
303
- const domainAddrs = payloadIbGibsDomain.map(p => getIbGibAddr({ ibGib: p }));
272
+ const domainAddrs = nextDomainIbGibsAndDeps.map(p => getIbGibAddr({ ibGib: p }));
304
273
  console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
305
274
  console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
306
275
  console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
307
- console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
308
276
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
309
277
  }
310
- // Add Context Deps
311
- // do we need to add requestCtx to the payload ibgibs?
312
- // if (requestCtx) {
313
- // const deps = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: requestCtx, space: tempSpace });
314
- // if (deps) { deps.forEach(x => payloadIbGibsControl.push(x)); }
315
- // }
316
- // 3. Identity (if exists)
317
- // Identity might be deep? Keystone? Usually self-contained or shallow references.
318
- if (sessionIdentity) {
319
- payloadIbGibsControl.push(sessionIdentity);
320
- }
321
- // we only put the **CONTROL** payload ibgibs in localSpace. we
322
- // don't put any domain ibgibs into this durable space until the
323
- // final commit phase.
324
- // The requestCtx envelope itself also goes to localSpace so transfer can find it.
325
- await putInSpace({ space: localSpace, ibGib: requestCtx });
326
- if (payloadIbGibsControl.length > 0) {
327
- await putInSpace({ space: localSpace, ibGibs: payloadIbGibsControl });
328
- }
329
- // B. Transmit
278
+ // update our saga listeners...
330
279
  // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
331
- updates$.next(requestCtx); // spins off (don't remove this comment!)
280
+ updates$.next(requestCtx); // spins off
281
+ // ...And send the context
332
282
  const responseCtx = await peer.witness(requestCtx);
333
283
  // C. Handle Response
334
284
  if (!responseCtx) {
@@ -353,53 +303,50 @@ export class SyncSagaCoordinator {
353
303
  // ---------------------------------------------------------------------
354
304
  // 2d. HANDLE RESPONSE
355
305
  // ---------------------------------------------------------------------
306
+ // at this point, we have received the response context ibgib, but
307
+ // if there were payloads expected to be transferred, they may not
308
+ // be done yet.
356
309
  if (!responseCtx.data) {
357
310
  throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
358
311
  }
359
312
  updates$.next(responseCtx); // spins off -- don't remove this comment!
360
313
  // Extract expected domain addresses from response context
361
314
  const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] || [];
362
- // TODO: check if we are validating the responseCtx in sync peer. I'm thinking that we must, because we can't start the domain ibgibs until we know the response's control ibgibs are valid.
363
315
  // Poll for them if needed
364
316
  if (responsePayloadAddrsDomain.length > 0) {
365
- await this.pollForDomainPayloads({
317
+ responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
366
318
  expectedAddrs: responsePayloadAddrsDomain,
319
+ pollIntervalMs: 20, // relatively arbitrary right now
367
320
  domainPayloadsMap,
368
321
  tempSpace,
369
322
  });
370
323
  }
371
- // Extract Response Frame
372
- const responseFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
373
- if (!responseFrameAddr) {
374
- throw new Error(`${lc} Peer response missing sagaFrame (E: 83a0)`);
375
- }
324
+ // at this point, we have received the context AND **all** of the
325
+ // domain payloads (if applicable), so we are ready to do the next
326
+ // iteration in the saga loop
376
327
  // Log what we received back
328
+ if (!responseCtx.sagaFrame) {
329
+ throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`);
330
+ }
377
331
  if (logalotControlDomain) {
378
332
  const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] || [];
379
333
  console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
380
334
  console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
381
- console.log(`${lc}${lcControlDomain} Response Frame: ${responseFrameAddr}`);
335
+ console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
382
336
  console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
383
337
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
384
338
  }
385
- // Get response frame from localSpace (SyncPeer puts it there)
386
- let resResponseFrame = await getFromSpace({ space: localSpace, addr: responseFrameAddr });
387
- if (!resResponseFrame.success || !resResponseFrame.ibGibs?.length) {
388
- // Fallback to tempSpace
389
- resResponseFrame = await getFromSpace({ space: tempSpace, addr: responseFrameAddr });
390
- }
391
- const responseFrame = resResponseFrame.ibGibs?.[0];
392
- if (!responseFrame)
393
- throw new Error(`${lc} Response frame not found (E: 7c2a)`);
394
339
  // Handle Response Frame
395
- const handleResult = await this.handleSagaFrame({
396
- sagaIbGib: responseFrame,
397
- srcGraph: {},
398
- destSpace: localSpace,
399
- tempSpace,
340
+ // interface HandleSagaFrameResult {
341
+ // frame: SyncIbGib_V1;
342
+ // payloadIbGibsDomain?: IbGib_V1[];
343
+ // conflictInfos?: SyncSagaConflictInfo;
344
+ // }
345
+ const handleResult = await this.handleSagaContext({
346
+ sagaContext: responseCtx,
347
+ mySpace: localSpace,
348
+ myTempSpace: tempSpace,
400
349
  metaspace,
401
- domainPayloadsMap,
402
- expectedDomainAddrs: responsePayloadAddrsDomain,
403
350
  });
404
351
  if (!handleResult) {
405
352
  if (logalot) {
@@ -409,20 +356,15 @@ export class SyncSagaCoordinator {
409
356
  }
410
357
  currentFrame = handleResult.frame;
411
358
  // Collect next DOMAIN payloads for the NEXT request
412
- // Control payloads are handled separately by ensureSagaFrameInBothSpaces
413
359
  nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
414
360
  // Log handler output for next iteration
415
361
  if (logalotControlDomain) {
416
- const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
417
362
  const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
418
363
  console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
419
364
  console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
420
- console.log(`${lc}${lcControlDomain} CONTROL for next (${handlerControlAddrs.length}): ${handlerControlAddrs.join(', ') || '(none)'} (saved via ensureSagaFrameInBothSpaces)`);
421
365
  console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
422
366
  }
423
- // Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
424
367
  }
425
- return allReceivedIbGibs;
426
368
  }
427
369
  /**
428
370
  * Helper to get Knowledge Vector for specific domain ibGibs or TJPs.
@@ -496,24 +438,27 @@ export class SyncSagaCoordinator {
496
438
  }
497
439
  }
498
440
  }
499
- async analyzeTimelines({ domainIbGibs, space, }) {
500
- const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
501
- const srcGraph = await getDependencyGraph({
441
+ async analyzeDomainIbGibs({ domainIbGibs, space, }) {
442
+ const lc = `${this.lc}[${this.analyzeDomainIbGibs.name}]`;
443
+ if (domainIbGibs.length === 0) {
444
+ throw new Error(`invalid domainIbGibs. expected at least one, but array is empty. (E: 820a719bcac5a16878a2af697113b826)`);
445
+ }
446
+ const fullGraph = await getDependencyGraph({
502
447
  ibGibs: domainIbGibs,
503
448
  live: true,
504
449
  space,
505
450
  });
506
- const srcGraphIsValid = srcGraph && Object.keys(srcGraph).length > 0;
451
+ const graphIsValid = fullGraph && Object.keys(fullGraph).length > 0;
507
452
  if (logalot) {
508
- console.log(`${lc} graph generated. nodes: ${srcGraphIsValid ? Object.keys(srcGraph).length : 0}`);
453
+ console.log(`${lc} graph generated. nodes: ${graphIsValid ? Object.keys(fullGraph).length : 0}`);
509
454
  }
510
- const srcIbGibs = srcGraphIsValid ? Object.values(srcGraph) : [];
455
+ const srcIbGibs = graphIsValid ? Object.values(fullGraph) : [];
511
456
  const { mapWithTjp_YesDna: srcMapWithTjp_YesDna, mapWithTjp_NoDna: srcMapWithTjp_NoDna, mapWithoutTjps: src_MapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs: srcIbGibs });
512
- const srcStones = Object.values(src_MapWithoutTjps);
513
- const srcLiving = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
514
- const srcTimelinesMap = getTimelinesGroupedByTjp({ ibGibs: srcLiving });
515
- const srcSortedTjps = this.sortTimelinesTopologically(srcTimelinesMap);
516
- return { srcStones, srcTimelinesMap, srcSortedTjps, srcGraph };
457
+ const stones = Object.values(src_MapWithoutTjps);
458
+ const withTimelines = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
459
+ const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: withTimelines });
460
+ const topologicallySortedTjpAddrs = this.sortTimelinesTopologically(timelinesMap);
461
+ return { stones, timelinesMap, topologicallySortedTjpAddrs, fullGraph };
517
462
  }
518
463
  /**
519
464
  * Creates the Initial Saga Frame (Init Stage).
@@ -524,57 +469,54 @@ export class SyncSagaCoordinator {
524
469
  * Generates the first frame containing the Knowledge Vector of the Local Space.
525
470
  * This is sent to the Receiver to begin Gap Analysis.
526
471
  */
527
- async createInitFrame({ sagaId, sessionIdentity, localSpace, domainIbGibs, tempSpace, metaspace, conflictStrategy, }) {
472
+ async createInitFrame({ sagaId, sessionIdentity, domainIbGibs, conflictStrategy, metaspace, localSpace, tempSpace, }) {
528
473
  const lc = `${this.lc}[${this.createInitFrame.name}]`;
529
474
  try {
530
475
  if (logalot) {
531
476
  console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`);
532
477
  }
533
478
  // Analyze Timelines & Stones
534
- const analysis = await this.analyzeTimelines({ domainIbGibs, space: localSpace });
535
- // this is a lot, so uncomment this only if you want even more logging specific to this analysis
479
+ const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
536
480
  // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
537
- const { srcTimelinesMap, srcGraph, srcStones, } = analysis;
481
+ const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
482
+ // we need to store the fullGraph in our tempSpace for later...
483
+ await putInSpace({ ibGibs: Object.values(fullGraph), space: tempSpace });
484
+ // populate our knowledge vector with tjp -> latest addr/tip in timeline
485
+ const knowledgeVector = {};
486
+ Object.keys(srcTimelinesMap).forEach(tjp => {
487
+ const timeline = srcTimelinesMap[tjp];
488
+ const tip = timeline.at(-1);
489
+ knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
490
+ });
538
491
  const initData = {
539
492
  sagaId,
540
493
  stage: SyncStage.init,
541
- knowledgeVector: {},
494
+ knowledgeVector,
542
495
  identity: sessionIdentity, // KeystoneIbGib is already public data
543
496
  mode: SyncMode.sync,
544
497
  stones: srcStones.map(s => getIbGibAddr({ ibGib: s })),
545
498
  };
546
- // Populate Knowledge Vector
547
- Object.keys(srcTimelinesMap).forEach(tjp => {
548
- const timeline = srcTimelinesMap[tjp];
549
- const tip = timeline.at(-1);
550
- initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
551
- });
552
499
  if (logalot) {
553
500
  console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
554
501
  console.log(`${lc} initData.stage: ${initData.stage}`);
555
502
  }
503
+ // create the stone and save/register it
556
504
  const initStone = await this.createSyncMsgStone({
557
505
  data: initData,
558
- space: tempSpace,
559
- metaspace
506
+ localSpace,
507
+ metaspace,
560
508
  });
561
509
  // if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
510
+ // create the initial sync ibgib frame, save/register it
562
511
  const sagaFrame = await this.evolveSyncSagaIbGib({
563
512
  msgStones: [initStone],
564
- identity: sessionIdentity,
565
- space: tempSpace,
566
- metaspace,
567
513
  conflictStrategy,
568
- });
569
- // IMMEDIATELY persist to both spaces for audit trail
570
- await this.ensureSagaFrameInBothSpaces({
571
- frame: sagaFrame,
572
- destSpace: localSpace, // localSpace is the Sender's destSpace
573
- tempSpace,
574
- metaspace
514
+ sessionIdentity: sessionIdentity,
515
+ metaspace,
516
+ localSpace,
575
517
  });
576
518
  // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
577
- return { sagaFrame, srcGraph };
519
+ return { sagaFrame, initialDomainGraph: fullGraph };
578
520
  }
579
521
  catch (error) {
580
522
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -589,13 +531,16 @@ export class SyncSagaCoordinator {
589
531
  /**
590
532
  * Helper to poll for streaming domain payloads and put them in the
591
533
  * local {@link tempSpace}.
534
+ *
535
+ * @returns when all {@link expectedAddrs} are done being transmitted.
592
536
  */
593
- async pollForDomainPayloads({ expectedAddrs, domainPayloadsMap, tempSpace, }) {
537
+ async pollForDomainPayloads({ expectedAddrs, pollIntervalMs, domainPayloadsMap, tempSpace, }) {
594
538
  const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
595
539
  try {
596
540
  if (logalot) {
597
541
  console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`);
598
542
  }
543
+ let resultDomainPayloads = [];
599
544
  let pending = [...expectedAddrs];
600
545
  const start = Date.now();
601
546
  /**
@@ -619,12 +564,17 @@ export class SyncSagaCoordinator {
619
564
  }
620
565
  if (found.length > 0) {
621
566
  await putInSpace({ space: tempSpace, ibGibs: found });
567
+ found.forEach(x => resultDomainPayloads.push(x));
622
568
  }
623
569
  pending = stillPending;
624
570
  if (pending.length > 0) {
625
- await new Promise(resolve => setTimeout(resolve, 50));
571
+ await delay(pollIntervalMs);
626
572
  }
627
573
  }
574
+ if (expectedAddrs.length !== resultDomainPayloads.length) {
575
+ throw new Error(`(UNEXPECTED) expectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
576
+ }
577
+ return resultDomainPayloads;
628
578
  }
629
579
  catch (error) {
630
580
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -636,12 +586,33 @@ export class SyncSagaCoordinator {
636
586
  }
637
587
  }
638
588
  }
639
- async handleSagaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, identity, identitySecret, metaspace, }) {
640
- const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
589
+ /**
590
+ * This is the heart of the "ping pong" transaction, where we send a context
591
+ * and receive a context. IOW, this drives the FSM of the sync saga ibgib as
592
+ * a whole.
593
+ *
594
+ * This is called in two places:
595
+ *
596
+ * ## 1. Sender
597
+ *
598
+ * On the sender, this is called within the {@link executeSagaLoop} which
599
+ * initiates and drives the sync.
600
+ *
601
+ * ## 2. Receiver
602
+ *
603
+ * On the receiver, this is called directly by the receiving endpoint. That
604
+ * endpoint's job is basically to get these things collocated and prepared
605
+ * to make this call.
606
+ *
607
+ * This is a one-off on the receiver.
608
+ */
609
+ async handleSagaContext({ sagaContext, mySpace, myTempSpace, identity, identitySecret, metaspace, }) {
610
+ const lc = `${this.lc}[${this.handleSagaContext.name}]`;
641
611
  try {
642
612
  if (logalot) {
643
613
  console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`);
644
614
  }
615
+ const sagaIbGib = sagaContext.sagaFrame;
645
616
  if (!sagaIbGib.data) {
646
617
  throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`);
647
618
  }
@@ -649,19 +620,31 @@ export class SyncSagaCoordinator {
649
620
  console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
650
621
  }
651
622
  // Get Stage from Stone (or Frame for Init fallback)
652
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
623
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
653
624
  if (logalot) {
654
625
  console.log(`${lc} handling frame stage: ${stage}`);
655
626
  }
627
+ /**
628
+ * don't like this name, need to refactor
629
+ */
630
+ const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
656
631
  switch (stage) {
657
632
  case SyncStage.init:
658
- return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
633
+ return await this.handleInitFrame({
634
+ sagaIbGib,
635
+ messageData: messageData,
636
+ metaspace,
637
+ mySpace: mySpace,
638
+ myTempSpace: myTempSpace,
639
+ identity,
640
+ identitySecret
641
+ });
659
642
  case SyncStage.ack:
660
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
643
+ return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
661
644
  case SyncStage.delta:
662
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
645
+ return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
663
646
  case SyncStage.commit:
664
- return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
647
+ return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
665
648
  default:
666
649
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
667
650
  }
@@ -689,282 +672,278 @@ export class SyncSagaCoordinator {
689
672
  * 3. Identifies what Receiver needs (`deltaReqAddrs`).
690
673
  * 4. Returns an `Ack` frame containing these lists.
691
674
  */
692
- async handleInitFrame({ sagaIbGib, messageData, destSpace, tempSpace, metaspace, identity, identitySecret, }) {
675
+ async handleInitFrame({ sagaIbGib, messageData, mySpace, myTempSpace, metaspace, identity,
676
+ // identitySecret,
677
+ }) {
693
678
  const lc = `${this.lc}[${this.handleInitFrame.name}]`;
694
- console.log(`${lc} [TEST DEBUG] Received destSpace: ${destSpace.data?.name || destSpace.ib} (uuid: ${destSpace.data?.uuid || '[no uuid]'})`);
695
- if (logalot) {
696
- console.log(`${lc} starting...`);
697
- }
698
- // Extract Init Data
699
- const initData = messageData; // Using renamed variable for clarity
700
- if (initData.stage !== SyncStage.init) {
701
- throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
702
- }
703
- // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
704
- if (!initData || !initData.knowledgeVector) {
705
- throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
706
- }
707
- // Determine Strategy from Saga Data (since V1 stores it in root)
708
- const conflictStrategy = sagaIbGib.data.conflictStrategy || 'abort';
709
- // 2. Gap Analysis
710
- const conflicts = [];
711
- const deltaReqAddrs = [];
712
- const pushOfferAddrs = [];
713
- // Stones Analysis (Constants / Non-TJPs)
714
- const stones = initData.stones || [];
715
- if (stones.length > 0) {
679
+ try {
716
680
  if (logalot) {
717
- console.log(`${lc} processing stones: ${stones.length}`);
681
+ console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`);
718
682
  }
719
- // Check if we have these stones
720
- const resStones = await getFromSpace({ space: destSpace, addrs: stones });
721
- const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
722
- if (addrsNotFound && addrsNotFound.length > 0) {
683
+ console.log(`${lc} [TEST DEBUG] Received destSpace: ${mySpace.data?.name || mySpace.ib} (uuid: ${mySpace.data?.uuid || '[no uuid]'})`);
684
+ if (logalot) {
685
+ console.log(`${lc} starting...`);
686
+ }
687
+ // Extract Init Data
688
+ const initData = messageData; // Using renamed variable for clarity
689
+ if (initData.stage !== SyncStage.init) {
690
+ throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
691
+ }
692
+ // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
693
+ if (!initData || !initData.knowledgeVector) {
694
+ throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
695
+ }
696
+ // Determine Strategy from Saga Data (since V1 stores it in root)
697
+ const conflictStrategy = sagaIbGib.data.conflictStrategy || SyncConflictStrategy.abort;
698
+ // 2. Gap Analysis
699
+ const conflicts = [];
700
+ const deltaReqAddrs = [];
701
+ const pushOfferAddrs = [];
702
+ // First do the consant stones (Non-TJPs)
703
+ const stones = initData.stones || [];
704
+ if (stones.length > 0) {
723
705
  if (logalot) {
724
- console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`);
706
+ console.log(`${lc} processing stones: ${stones.length}`);
725
707
  }
726
- addrsNotFound.forEach(addr => {
727
- if (!deltaReqAddrs.includes(addr)) {
728
- deltaReqAddrs.push(addr);
708
+ // Check if we have these stones
709
+ const resStones = await getFromSpace({ space: mySpace, addrs: stones });
710
+ const rawResultIbGib = resStones.rawResultIbGib;
711
+ if (!rawResultIbGib.data) {
712
+ throw new Error(`(UNEXPECTED) rawResultIbGib.data falsy? (E: f3c40b492adc02c3f480b998fc7a5926)`);
713
+ }
714
+ const addrsNotFound = rawResultIbGib.data.addrsNotFound;
715
+ if (addrsNotFound && addrsNotFound.length > 0) {
716
+ if (logalot) {
717
+ console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`);
729
718
  }
730
- });
731
- }
732
- }
733
- const remoteKV = initData.knowledgeVector;
734
- if (logalot) {
735
- console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
736
- }
737
- const remoteTjps = Object.keys(remoteKV);
738
- console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
739
- if (logalot) {
740
- console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
741
- }
742
- // 1. Get Local Latest Addrs for all TJPs
743
- let localKV = {};
744
- if (remoteTjps.length > 0) {
745
- // Batch get latest addrs for the TJPs
746
- const resGetLatestAddrs = await getLatestAddrs({
747
- space: destSpace,
748
- tjpAddrs: remoteTjps,
749
- });
750
- if (!resGetLatestAddrs.data) {
751
- throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`);
752
- }
753
- if (!resGetLatestAddrs.data.latestAddrsMap) {
754
- throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
719
+ addrsNotFound.forEach(addr => {
720
+ if (!deltaReqAddrs.includes(addr)) {
721
+ deltaReqAddrs.push(addr);
722
+ }
723
+ });
724
+ }
755
725
  }
756
- localKV = resGetLatestAddrs.data.latestAddrsMap;
757
- console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
726
+ /**
727
+ * "remote" local to receiver's context is the sender
728
+ */
729
+ const remoteKV = initData.knowledgeVector;
758
730
  if (logalot) {
759
- console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
760
- }
761
- }
762
- // 2. Gap Analysis
763
- for (const tjp of remoteTjps) {
764
- const remoteAddr = remoteKV[tjp];
765
- const localAddr = localKV[tjp];
766
- if (!localAddr) {
767
- // We (Receiver) don't have this timeline. Request it.
768
- console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
769
- deltaReqAddrs.push(remoteAddr);
770
- continue;
771
- }
772
- if (localAddr === remoteAddr) {
773
- // Synced
774
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
775
- continue;
776
- }
777
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
778
- // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
779
- // (Sender has older version, Receiver has newer) -> Receiver Offers Push
780
- const isRemoteInPast = await isPastFrame({
781
- olderAddr: remoteAddr,
782
- newerAddr: localAddr,
783
- space: destSpace,
784
- });
785
- if (isRemoteInPast) {
786
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
787
- pushOfferAddrs.push(localAddr);
731
+ console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
788
732
  }
789
- else {
790
- // Remote is not in our past.
791
- // Either Remote is ahead (Fast-Backward) OR Diverged.
792
- // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
793
- const isLocalInPast = await isPastFrame({
794
- olderAddr: localAddr,
795
- newerAddr: remoteAddr,
796
- space: destSpace,
733
+ const remoteTjps = Object.keys(remoteKV);
734
+ console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
735
+ if (logalot) {
736
+ console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
737
+ }
738
+ // 1. Get Local Latest Addrs for all TJPs
739
+ let localKV = {};
740
+ if (remoteTjps.length > 0) {
741
+ // Batch get latest addrs for the TJPs
742
+ const resGetLatestAddrs = await getLatestAddrs({
743
+ space: mySpace, // executing on receiver, so this is receiver's space
744
+ tjpAddrs: remoteTjps,
797
745
  });
798
- if (isLocalInPast) {
799
- // Fast-Forward: We update to remote's tip.
800
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
746
+ if (!resGetLatestAddrs.data) {
747
+ throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`);
748
+ }
749
+ if (!resGetLatestAddrs.data.latestAddrsMap) {
750
+ throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
751
+ }
752
+ localKV = resGetLatestAddrs.data.latestAddrsMap;
753
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
754
+ if (logalot) {
755
+ console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
756
+ }
757
+ }
758
+ // 2. Gap Analysis
759
+ for (const tjp of remoteTjps) {
760
+ const remoteAddr = remoteKV[tjp];
761
+ const localAddr = localKV[tjp];
762
+ if (!localAddr) {
763
+ // We (Receiver) don't have this timeline at all. Request it.
764
+ console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
801
765
  deltaReqAddrs.push(remoteAddr);
766
+ continue;
767
+ }
768
+ if (localAddr === remoteAddr) {
769
+ // Synced
770
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
771
+ continue;
772
+ }
773
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
774
+ // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
775
+ // (Sender has older version, Receiver has newer) -> Receiver Offers Push
776
+ const isRemoteInPast = await isPastFrame({
777
+ olderAddr: remoteAddr,
778
+ newerAddr: localAddr,
779
+ space: mySpace,
780
+ });
781
+ if (isRemoteInPast) {
782
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
783
+ pushOfferAddrs.push(localAddr);
802
784
  }
803
785
  else {
804
- // DIVERGENCE: Both have changes the other doesn't know about.
805
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
806
- if (conflictStrategy === 'abort') {
807
- // Abort Strategy: We will treat this as terminal.
808
- // But for Unified Ack, we just mark it terminal in the list?
809
- // Or do we actually throw/abort the saga?
810
- // Current logic (below) aborts the saga if ANY conflict is terminal/abort.
811
- conflicts.push({
812
- tjpAddr: tjp,
813
- localAddr: localAddr,
814
- remoteAddr,
815
- timelineAddrs: [], // Not needed for abort
816
- reason: 'divergence',
817
- terminal: true
818
- });
819
- }
820
- else if (conflictStrategy === 'optimistic') {
821
- // Optimistic: We want to resolving this.
822
- // We need to send our history to the Sender so they can Merge.
823
- // Fetch Full History for Local Timeline
824
- // Note: We might optimize this to only send "recent" history if we had a KV?
825
- // But for now, get full past.
826
- // Optimization: localKV might not have full history.
827
- // We need to inspect the 'past' of the local tip.
828
- // We need the ACTUAL object to get the past.
829
- // We have localAddr.
830
- const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr });
831
- const localTip = resLocalTip.ibGibs?.[0];
832
- if (!localTip) {
833
- throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
834
- }
835
- const timelineAddrs = [localAddr, ...(localTip.rel8ns?.past || [])];
836
- conflicts.push({
837
- tjpAddr: tjp,
838
- localAddr: localAddr,
839
- remoteAddr,
840
- timelineAddrs,
841
- reason: 'divergence',
842
- terminal: false
843
- });
786
+ // Remote is not in our past.
787
+ // Either Remote is ahead (Fast-Backward) OR Diverged.
788
+ // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
789
+ const isLocalInPast = await isPastFrame({
790
+ olderAddr: localAddr,
791
+ newerAddr: remoteAddr,
792
+ space: mySpace,
793
+ });
794
+ if (isLocalInPast) {
795
+ // Fast-Forward: We update to remote's tip.
796
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
797
+ deltaReqAddrs.push(remoteAddr);
844
798
  }
845
799
  else {
846
- throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
800
+ // DIVERGENCE: Both have changes the other doesn't know about.
801
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
802
+ if (conflictStrategy === 'abort') {
803
+ // Abort Strategy: We will treat this as terminal.
804
+ // But for Unified Ack, we just mark it terminal in the list?
805
+ // Or do we actually throw/abort the saga?
806
+ // Current logic (below) aborts the saga if ANY conflict is terminal/abort.
807
+ conflicts.push({
808
+ tjpAddr: tjp,
809
+ localAddr: localAddr,
810
+ remoteAddr,
811
+ timelineAddrs: [], // Not needed for abort
812
+ reason: 'divergence',
813
+ terminal: true
814
+ });
815
+ }
816
+ else if (conflictStrategy === 'optimistic') {
817
+ // Optimistic: We want to resolving this.
818
+ // We need to send our history to the Sender so they can Merge.
819
+ // Fetch Full History for Local Timeline
820
+ // Note: We might optimize this to only send "recent" history if we had a KV?
821
+ // But for now, get full past.
822
+ // Optimization: localKV might not have full history.
823
+ // We need to inspect the 'past' of the local tip.
824
+ // We need the ACTUAL object to get the past.
825
+ // We have localAddr.
826
+ const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
827
+ if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
828
+ throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: ff06ff849fa8e8dba32ce09807411226)`);
829
+ }
830
+ const localTip = resLocalTip.ibGibs[0];
831
+ if (!localTip) {
832
+ throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
833
+ }
834
+ const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
835
+ conflicts.push({
836
+ tjpAddr: tjp,
837
+ localAddr,
838
+ remoteAddr,
839
+ timelineAddrs,
840
+ reason: 'divergence',
841
+ terminal: false
842
+ });
843
+ }
844
+ else {
845
+ throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
846
+ }
847
847
  }
848
848
  }
849
849
  }
850
- }
851
- // Check if we should ABORT (if any conflict is terminal)
852
- const hasTerminalConflicts = conflicts.some(c => c.terminal);
853
- if (hasTerminalConflicts) {
854
- // Abort Strategy: Kill the saga.
855
- if (logalot) {
856
- console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`);
857
- }
858
- // We reuse the ConflictData structure for terminal aborts?
859
- // Or do we send an Ack with terminal conflicts?
860
- // Original design had explicit Conflict Frame for Abort.
861
- // Let's stick to that for purely terminal cases to be safe/explicit?
862
- // Or Unified: Just send Ack with terminal=true conflicts. Sender sees them and aborts.
863
- // Decision: Unified Ack for everything is cleaner protocol.
864
- // But wait, the original code below creates a Conflict Stone.
865
- // Let's preserve the explicit 'Conflict' frame for total aborts if that's easier,
866
- // OR fully switch to Ack.
867
- // Protocol states: Init -> Ack. If Ack contains terminal errors, Sender can Commit(Fail).
868
- // Let's use Ack with conflicts.
869
- }
870
- // 2. Add Push Offers (Missing in Local)
871
- // Check if we have them. If not, ask for them.
872
- for (const addr of pushOfferAddrs) {
873
- const hasIt = await getFromSpace({ addr, space: destSpace });
874
- if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
875
- // If we don't have it, we put it in `deltaReqAddrs` of the Ack.
876
- deltaReqAddrs.push(addr);
850
+ // Check if we should ABORT (if any conflict is terminal)
851
+ const hasTerminalConflicts = conflicts.some(c => c.terminal);
852
+ if (hasTerminalConflicts) {
853
+ // Abort Strategy: Kill the saga.
854
+ if (logalot) {
855
+ console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`);
856
+ }
857
+ throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
877
858
  }
878
- }
879
- // 3. Build Knowledge Vector (Full History for known timelines)
880
- // [NEW] Smart Diff
881
- // We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
882
- // Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
883
- const knowledgeVector = {};
884
- const relevantAddrs = new Set([...pushOfferAddrs, ...deltaReqAddrs]);
885
- // [Smart Diff] Populate knowledge from timelines identified by Sender
886
- for (const tjp of remoteTjps) {
887
- const localAddr = localKV[tjp];
888
- if (localAddr) {
889
- const res = await getFromSpace({ addr: localAddr, space: destSpace });
890
- if (res.success && res.ibGibs?.[0]) {
891
- const ibGib = res.ibGibs[0];
892
- const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
893
- if (!knowledgeVector[realTjp]) {
894
- const past = ibGib.rel8ns?.past || [];
895
- knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
859
+ // 3. Build Knowledge Vector (Full History for known timelines)
860
+ // [NEW] Smart Diff
861
+ // We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
862
+ // Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
863
+ /**
864
+ * we will put this in {@link SyncSagaMessageAckData_V1.knowledgeVector}
865
+ */
866
+ const knowledgeVector = {};
867
+ // [Smart Diff] Populate knowledge from timelines identified by Sender
868
+ for (const tjp of remoteTjps) {
869
+ const localAddr = localKV[tjp];
870
+ if (localAddr) {
871
+ const res = await getFromSpace({ addr: localAddr, space: mySpace });
872
+ if (res.success && res.ibGibs?.[0]) {
873
+ const ibGib = res.ibGibs[0];
874
+ const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
875
+ if (!knowledgeVector[realTjp]) {
876
+ const past = ibGib.rel8ns?.past || [];
877
+ knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
878
+ }
896
879
  }
897
880
  }
898
881
  }
899
- }
900
- // [Smart Diff] Also check individual requested addresses (Fall back for constants/unknown TJPs)
901
- for (const addr of relevantAddrs) {
902
- // Only process if not already covered by TJP logic above
903
- // We can't really know if it's covered easily without resolving.
904
- // But if we don't have it (requesting), we won't find it here anyway.
905
- // If we DO have it (push offer), we might find it.
906
- const res = await getFromSpace({ addr, space: destSpace });
907
- if (res.success && res.ibGibs?.[0]) {
908
- const ibGib = res.ibGibs[0];
909
- const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
910
- if (!knowledgeVector[tjpAddr]) {
911
- const past = ibGib.rel8ns?.past || [];
912
- knowledgeVector[tjpAddr] = [getIbGibAddr({ ibGib }), ...past];
913
- }
882
+ // Also populate from `knowledgeVector` in Init if we want bidirectional?
883
+ // No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
884
+ // Let's assume standard flow:
885
+ // 1. Sender says "I have X"
886
+ // 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
887
+ // Problem: Receiver doesn't know X is related to Y without X.
888
+ // SOLUTION:
889
+ // The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
890
+ // `SyncSagaMessageInitData_V1` extends `SyncInitData`.
891
+ // `SyncInitData` has `knowledgeVector`.
892
+ // If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
893
+ // Let's implement Sender populating `Init.knowledgeVector`.
894
+ // But `SyncSagaCoordinator.startSaga` creates Init.
895
+ // 3. Create Ack Frame
896
+ const sagaId = sagaIbGib.data.uuid;
897
+ // Create Payload Stone
898
+ const ackData = {
899
+ sagaId,
900
+ stage: SyncStage.ack,
901
+ deltaReqAddrs,
902
+ pushOfferAddrs,
903
+ knowledgeVector,
904
+ };
905
+ if (conflicts?.length > 0) {
906
+ ackData.conflicts = conflicts;
914
907
  }
915
- else {
916
- // We don't have `addr`.
908
+ const ackStone = await this.createSyncMsgStone({
909
+ data: ackData,
910
+ localSpace: myTempSpace,
911
+ metaspace,
912
+ });
913
+ if (logalot) {
914
+ console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`);
917
915
  }
916
+ // Evolve Saga
917
+ const ackFrame = await this.evolveSyncSagaIbGib({
918
+ prevSagaIbGib: sagaIbGib,
919
+ msgStones: [ackStone],
920
+ sessionIdentity: identity,
921
+ localSpace: mySpace,
922
+ metaspace,
923
+ });
924
+ // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
925
+ /**
926
+ * we want to push ibgibs to the remote/sender if we have push
927
+ * offers. an ack frame's payloads, if any, are those push offers
928
+ */
929
+ // let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
930
+ if (pushOfferAddrs.length > 0) {
931
+ }
932
+ return {
933
+ frame: ackFrame,
934
+ // conflictInfos,
935
+ // payloadIbGibsDomain,
936
+ };
918
937
  }
919
- // Also populate from `knowledgeVector` in Init if we want bidirectional?
920
- // No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
921
- // Let's assume standard flow:
922
- // 1. Sender says "I have X"
923
- // 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
924
- // Problem: Receiver doesn't know X is related to Y without X.
925
- // SOLUTION:
926
- // The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
927
- // `SyncSagaMessageInitData_V1` extends `SyncInitData`.
928
- // `SyncInitData` has `knowledgeVector`.
929
- // If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
930
- // Let's implement Sender populating `Init.knowledgeVector`.
931
- // But `SyncSagaCoordinator.startSaga` creates Init.
932
- // 3. Create Ack Frame
933
- const sagaId = sagaIbGib.data.uuid;
934
- // Create Payload Stone
935
- const ackData = {
936
- sagaId,
937
- stage: SyncStage.ack,
938
- deltaReqAddrs,
939
- pushOfferAddrs,
940
- knowledgeVector,
941
- conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
942
- };
943
- const ackStone = await this.createSyncMsgStone({
944
- data: ackData,
945
- space: tempSpace,
946
- metaspace,
947
- });
948
- if (logalot) {
949
- console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`);
938
+ catch (error) {
939
+ console.error(`${lc} ${extractErrorMsg(error)}`);
940
+ throw error;
950
941
  }
951
- // Evolve Saga
952
- const ackFrame = await this.evolveSyncSagaIbGib({
953
- prevSagaIbGib: sagaIbGib,
954
- msgStones: [ackStone],
955
- identity,
956
- space: tempSpace,
957
- metaspace,
958
- });
959
- // IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
960
- await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
961
- // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
962
- // Build control payloads: frame + its dependencies (msg stone, identity)
963
- const payloadIbGibsControl = [ackFrame, ackStone];
964
- if (identity) {
965
- payloadIbGibsControl.push(identity);
942
+ finally {
943
+ if (logalot) {
944
+ console.log(`${lc} complete.`);
945
+ }
966
946
  }
967
- return { frame: ackFrame, payloadIbGibsControl };
968
947
  }
969
948
  /**
970
949
  * Handles the `Ack` frame.
@@ -984,7 +963,7 @@ export class SyncSagaCoordinator {
984
963
  if (logalot) {
985
964
  console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
986
965
  }
987
- const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
966
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
988
967
  const ackData = messageData;
989
968
  if (!ackData) {
990
969
  throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
@@ -1221,18 +1200,16 @@ export class SyncSagaCoordinator {
1221
1200
  }
1222
1201
  const deltaStone = await this.createSyncMsgStone({
1223
1202
  data: deltaData,
1224
- space: tempSpace,
1203
+ localSpace: tempSpace,
1225
1204
  metaspace,
1226
1205
  });
1227
1206
  const deltaFrame = await this.evolveSyncSagaIbGib({
1228
1207
  prevSagaIbGib: sagaIbGib,
1229
1208
  msgStones: [deltaStone],
1230
- identity,
1231
- space: tempSpace,
1209
+ sessionIdentity: identity,
1210
+ localSpace: tempSpace,
1232
1211
  metaspace,
1233
1212
  });
1234
- // IMMEDIATELY persist to both spaces for audit trail
1235
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1236
1213
  if (logalot) {
1237
1214
  console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
1238
1215
  }
@@ -1241,7 +1218,8 @@ export class SyncSagaCoordinator {
1241
1218
  if (identity) {
1242
1219
  payloadIbGibsControl.push(identity);
1243
1220
  }
1244
- return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1221
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1222
+ return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1245
1223
  }
1246
1224
  catch (error) {
1247
1225
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -1268,7 +1246,7 @@ export class SyncSagaCoordinator {
1268
1246
  if (logalot) {
1269
1247
  console.log(`${lc} starting...`);
1270
1248
  }
1271
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1249
+ const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1272
1250
  const deltaData = messageData;
1273
1251
  if (!deltaData) {
1274
1252
  throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
@@ -1454,24 +1432,23 @@ export class SyncSagaCoordinator {
1454
1432
  responseDeltaData.proposeCommit = true;
1455
1433
  const deltaStone = await this.createSyncMsgStone({
1456
1434
  data: responseDeltaData,
1457
- space: tempSpace,
1435
+ localSpace: tempSpace,
1458
1436
  metaspace
1459
1437
  });
1460
1438
  const deltaFrame = await this.evolveSyncSagaIbGib({
1461
1439
  prevSagaIbGib: sagaIbGib,
1462
1440
  msgStones: [deltaStone],
1463
- identity,
1464
- space: tempSpace,
1441
+ sessionIdentity: identity,
1442
+ localSpace: tempSpace,
1465
1443
  metaspace
1466
1444
  });
1467
- // IMMEDIATELY persist to both spaces for audit trail
1468
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1469
1445
  // Build control payloads: frame + its dependencies (msg stone, identity)
1470
1446
  const payloadIbGibsControl = [deltaFrame, deltaStone];
1471
1447
  if (identity) {
1472
1448
  payloadIbGibsControl.push(identity);
1473
1449
  }
1474
- return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1450
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1451
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1475
1452
  }
1476
1453
  else {
1477
1454
  // We have nothing to send.
@@ -1484,24 +1461,23 @@ export class SyncSagaCoordinator {
1484
1461
  };
1485
1462
  const commitStone = await this.createSyncMsgStone({
1486
1463
  data: commitData,
1487
- space: tempSpace,
1464
+ localSpace: tempSpace,
1488
1465
  metaspace
1489
1466
  });
1490
1467
  const commitFrame = await this.evolveSyncSagaIbGib({
1491
1468
  prevSagaIbGib: sagaIbGib,
1492
1469
  msgStones: [commitStone],
1493
- identity,
1494
- space: tempSpace,
1470
+ sessionIdentity: identity,
1471
+ localSpace: tempSpace,
1495
1472
  metaspace
1496
1473
  });
1497
- // IMMEDIATELY persist to both spaces for audit trail
1498
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1499
1474
  // Build control payloads for commit
1500
1475
  const commitCtrlPayloads = [commitFrame, commitStone];
1501
1476
  if (identity) {
1502
1477
  commitCtrlPayloads.push(identity);
1503
1478
  }
1504
- return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1479
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1480
+ return { frame: commitFrame, };
1505
1481
  }
1506
1482
  else {
1507
1483
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1515,18 +1491,16 @@ export class SyncSagaCoordinator {
1515
1491
  };
1516
1492
  const deltaStone = await this.createSyncMsgStone({
1517
1493
  data: responseDeltaData,
1518
- space: tempSpace,
1494
+ localSpace: tempSpace,
1519
1495
  metaspace
1520
1496
  });
1521
1497
  const deltaFrame = await this.evolveSyncSagaIbGib({
1522
1498
  prevSagaIbGib: sagaIbGib,
1523
1499
  msgStones: [deltaStone],
1524
- identity,
1525
- space: tempSpace,
1500
+ sessionIdentity: identity,
1501
+ localSpace: tempSpace,
1526
1502
  metaspace
1527
1503
  });
1528
- // IMMEDIATELY persist to both spaces for audit trail
1529
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1530
1504
  // Check if PEER proposed commit
1531
1505
  if (deltaData.proposeCommit) {
1532
1506
  if (logalot) {
@@ -1541,31 +1515,31 @@ export class SyncSagaCoordinator {
1541
1515
  };
1542
1516
  const commitStone = await this.createSyncMsgStone({
1543
1517
  data: commitData,
1544
- space: tempSpace,
1518
+ localSpace: tempSpace,
1545
1519
  metaspace
1546
1520
  });
1547
1521
  const commitFrame = await this.evolveSyncSagaIbGib({
1548
1522
  prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1549
1523
  msgStones: [commitStone],
1550
- identity,
1551
- space: tempSpace,
1524
+ sessionIdentity: identity,
1525
+ localSpace: tempSpace,
1552
1526
  metaspace
1553
1527
  });
1554
- // IMMEDIATELY persist to both spaces for audit trail
1555
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1556
1528
  // Build control payloads for commit
1557
1529
  const commitCtrlPayloads2 = [commitFrame, commitStone];
1558
1530
  if (identity) {
1559
1531
  commitCtrlPayloads2.push(identity);
1560
1532
  }
1561
- return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1533
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1534
+ return { frame: commitFrame, };
1562
1535
  }
1563
1536
  // Build control payloads for delta propose
1564
1537
  const deltaCtrlPayloads = [deltaFrame, deltaStone];
1565
1538
  if (identity) {
1566
1539
  deltaCtrlPayloads.push(identity);
1567
1540
  }
1568
- return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1541
+ // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1542
+ return { frame: deltaFrame, };
1569
1543
  }
1570
1544
  }
1571
1545
  }
@@ -1588,7 +1562,7 @@ export class SyncSagaCoordinator {
1588
1562
  return null;
1589
1563
  }
1590
1564
  // #endregion Handlers
1591
- async createSyncMsgStone({ data, space, metaspace, }) {
1565
+ async createSyncMsgStone({ data, localSpace, metaspace, }) {
1592
1566
  const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
1593
1567
  try {
1594
1568
  if (logalot) {
@@ -1604,7 +1578,7 @@ export class SyncSagaCoordinator {
1604
1578
  if (logalot) {
1605
1579
  console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`);
1606
1580
  }
1607
- await putInSpace({ space, ibGib: stone });
1581
+ await metaspace.put({ ibGib: stone, space: localSpace });
1608
1582
  await metaspace.registerNewIbGib({ ibGib: stone });
1609
1583
  return stone;
1610
1584
  }
@@ -1618,41 +1592,10 @@ export class SyncSagaCoordinator {
1618
1592
  }
1619
1593
  }
1620
1594
  }
1621
- /**
1622
- * Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
1623
- * Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
1624
- */
1625
- async ensureSagaFrameInBothSpaces({ frame, destSpace, tempSpace, metaspace, }) {
1626
- // Frame itself (already in tempSpace from creation, need in destSpace for audit)
1627
- await putInSpace({ space: destSpace, ibGib: frame });
1628
- await metaspace.registerNewIbGib({ ibGib: frame });
1629
- // Msg stone(s) (already in tempSpace, need in destSpace for audit)
1630
- const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1631
- if (msgStoneAddrs && msgStoneAddrs.length > 0) {
1632
- const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
1633
- if (resMsgStones.ibGibs) {
1634
- for (const msgStone of resMsgStones.ibGibs) {
1635
- await putInSpace({ space: destSpace, ibGib: msgStone });
1636
- await metaspace.registerNewIbGib({ ibGib: msgStone });
1637
- }
1638
- }
1639
- }
1640
- // Identity (if present, already in tempSpace, need in destSpace)
1641
- const identityAddrs = frame.rel8ns?.identity;
1642
- if (identityAddrs && identityAddrs.length > 0) {
1643
- const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
1644
- if (resIdentity.ibGibs) {
1645
- for (const identity of resIdentity.ibGibs) {
1646
- await putInSpace({ space: destSpace, ibGib: identity });
1647
- await metaspace.registerNewIbGib({ ibGib: identity });
1648
- }
1649
- }
1650
- }
1651
- }
1652
1595
  /**
1653
1596
  * Evolves the saga timeline with a new frame.
1654
1597
  */
1655
- async evolveSyncSagaIbGib({ prevSagaIbGib, msgStones, identity, space, metaspace, conflictStrategy, }) {
1598
+ async evolveSyncSagaIbGib({ prevSagaIbGib, conflictStrategy, msgStones, sessionIdentity, localSpace, metaspace, }) {
1656
1599
  const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
1657
1600
  try {
1658
1601
  // Validation
@@ -1675,7 +1618,7 @@ export class SyncSagaCoordinator {
1675
1618
  throw new Error(`${lc} Mismatched stage in stones. Expected ${stage}, got ${d.stage} (E: d12c6571b0882f762921b60880c3f826)`);
1676
1619
  }
1677
1620
  }
1678
- const identityAddr = identity ? getIbGibAddr({ ibGib: identity }) : undefined;
1621
+ const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
1679
1622
  if (prevSagaIbGib) {
1680
1623
  /**
1681
1624
  * rel8ns always include the new msg stone(s)
@@ -1687,8 +1630,8 @@ export class SyncSagaCoordinator {
1687
1630
  }
1688
1631
  ];
1689
1632
  // if we're authenticating/signing, we'll have identity
1690
- if (identity) {
1691
- rel8nInfos.push({ rel8nName: 'identity', ibGibs: [identity], });
1633
+ if (sessionIdentity) {
1634
+ rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
1692
1635
  }
1693
1636
  // remove the existing sync msg stones' addrs
1694
1637
  if (!prevSagaIbGib.rel8ns) {
@@ -1709,7 +1652,7 @@ export class SyncSagaCoordinator {
1709
1652
  rel8nInfos,
1710
1653
  rel8nRemovalInfos,
1711
1654
  metaspace,
1712
- space,
1655
+ space: localSpace,
1713
1656
  noDna: true, // Explicitly no DNA for sync frames
1714
1657
  });
1715
1658
  const newFrame = resAppend;
@@ -1717,31 +1660,36 @@ export class SyncSagaCoordinator {
1717
1660
  }
1718
1661
  else {
1719
1662
  // Create New Timeline (Root Frame)
1663
+ // data
1720
1664
  const data = {
1721
1665
  uuid: sagaId,
1722
- // stage, // Removed from V1
1723
- payload: undefined, // Data in stone
1724
1666
  n: 0,
1725
1667
  isTjp: true,
1726
1668
  conflictStrategy,
1727
1669
  };
1670
+ // ib
1728
1671
  const ib = await getSyncIb({ data });
1672
+ // rel8ns
1729
1673
  const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
1730
- const rel8ns = {
1731
- [SYNC_MSG_REL8N_NAME]: stoneAddrs,
1732
- };
1674
+ const rel8ns = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
1733
1675
  if (identityAddr) {
1734
1676
  rel8ns.identity = [identityAddr];
1735
1677
  }
1736
1678
  const resNew = await createTimeline({
1737
- space,
1679
+ space: localSpace,
1738
1680
  metaspace,
1739
1681
  ib,
1740
1682
  data,
1741
1683
  rel8ns,
1742
1684
  parentIb: SYNC_ATOM, // "sync"
1685
+ /**
1686
+ * this will squash the result into a single frame
1687
+ */
1743
1688
  noDna: true,
1744
1689
  });
1690
+ if (!!resNew.intermediateIbGibs) {
1691
+ throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`);
1692
+ }
1745
1693
  return resNew.newIbGib;
1746
1694
  }
1747
1695
  }
@@ -1750,16 +1698,16 @@ export class SyncSagaCoordinator {
1750
1698
  throw error;
1751
1699
  }
1752
1700
  }
1753
- async getStageAndPayloadFromFrame({ ibGib, space }) {
1701
+ async getStageAndPayloadFromFrame({ sagaFrame, space }) {
1754
1702
  const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
1755
1703
  try {
1756
1704
  if (logalot) {
1757
1705
  console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`);
1758
1706
  }
1759
- if (!ibGib.rel8ns) {
1760
- throw new Error(`(UNEXPECTED) ibGib.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`);
1707
+ if (!sagaFrame.rel8ns) {
1708
+ throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`);
1761
1709
  }
1762
- const stoneAddrs = ibGib.rel8ns?.[SYNC_MSG_REL8N_NAME];
1710
+ const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1763
1711
  if (stoneAddrs && stoneAddrs.length > 0) {
1764
1712
  const stoneAddr = stoneAddrs[0];
1765
1713
  const res = await getFromSpace({ addr: stoneAddr, space });
@@ -1778,7 +1726,7 @@ export class SyncSagaCoordinator {
1778
1726
  }
1779
1727
  }
1780
1728
  else {
1781
- throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? ibGib addr: ${getIbGibAddr(ibGib)} (E: 43eae8579e289d016741b5526052f226)`);
1729
+ throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
1782
1730
  }
1783
1731
  }
1784
1732
  catch (error) {