@ibgib/core-gib 0.1.22 → 0.1.25

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 (86) hide show
  1. package/dist/common/other/graph-helper.d.mts +25 -0
  2. package/dist/common/other/graph-helper.d.mts.map +1 -1
  3. package/dist/common/other/graph-helper.mjs +75 -1
  4. package/dist/common/other/graph-helper.mjs.map +1 -1
  5. package/dist/sync/graft-info/graft-info-helpers.mjs +2 -2
  6. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -1
  7. package/dist/sync/sync-conflict.respec.mjs +8 -12
  8. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  9. package/dist/sync/sync-constants.d.mts +14 -4
  10. package/dist/sync/sync-constants.d.mts.map +1 -1
  11. package/dist/sync/sync-constants.mjs +15 -3
  12. package/dist/sync/sync-constants.mjs.map +1 -1
  13. package/dist/sync/sync-helpers.d.mts +22 -15
  14. package/dist/sync/sync-helpers.d.mts.map +1 -1
  15. package/dist/sync/sync-helpers.mjs +159 -90
  16. package/dist/sync/sync-helpers.mjs.map +1 -1
  17. package/dist/sync/sync-innerspace-constants.respec.mjs +8 -9
  18. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  19. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +8 -9
  20. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  21. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +8 -9
  22. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  23. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +8 -9
  24. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  25. package/dist/sync/sync-innerspace-partial-update.respec.mjs +8 -9
  26. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  27. package/dist/sync/sync-innerspace.respec.mjs +6 -7
  28. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  29. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts +2 -0
  30. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts.map +1 -1
  31. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs +4 -0
  32. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs.map +1 -1
  33. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +0 -15
  34. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -1
  35. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +30 -16
  36. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  37. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +47 -79
  38. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  39. package/dist/sync/sync-peer/sync-peer-types.d.mts +40 -1
  40. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  41. package/dist/sync/sync-peer/sync-peer-v1.d.mts +47 -14
  42. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  43. package/dist/sync/sync-peer/sync-peer-v1.mjs +188 -144
  44. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  45. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +39 -3
  46. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  47. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +137 -31
  48. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  49. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +5 -0
  50. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  51. package/dist/sync/sync-saga-coordinator.d.mts +81 -77
  52. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  53. package/dist/sync/sync-saga-coordinator.mjs +608 -597
  54. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  55. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +154 -26
  56. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  57. package/dist/sync/sync-types.d.mts +87 -92
  58. package/dist/sync/sync-types.d.mts.map +1 -1
  59. package/dist/sync/sync-types.mjs +6 -2
  60. package/dist/sync/sync-types.mjs.map +1 -1
  61. package/dist/timeline/timeline-api.d.mts.map +1 -1
  62. package/dist/timeline/timeline-api.mjs +15 -8
  63. package/dist/timeline/timeline-api.mjs.map +1 -1
  64. package/package.json +1 -1
  65. package/src/common/other/graph-helper.mts +79 -1
  66. package/src/sync/graft-info/graft-info-helpers.mts +3 -3
  67. package/src/sync/sync-conflict.respec.mts +8 -14
  68. package/src/sync/sync-constants.mts +15 -4
  69. package/src/sync/sync-helpers.mts +173 -101
  70. package/src/sync/sync-innerspace-constants.respec.mts +8 -9
  71. package/src/sync/sync-innerspace-deep-updates.respec.mts +8 -9
  72. package/src/sync/sync-innerspace-dest-ahead.respec.mts +8 -9
  73. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +8 -9
  74. package/src/sync/sync-innerspace-partial-update.respec.mts +9 -12
  75. package/src/sync/sync-innerspace.respec.mts +6 -7
  76. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mts +7 -0
  77. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +0 -15
  78. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +72 -96
  79. package/src/sync/sync-peer/sync-peer-types.mts +43 -2
  80. package/src/sync/sync-peer/sync-peer-v1.mts +215 -142
  81. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +145 -37
  82. package/src/sync/sync-saga-context/sync-saga-context-types.mts +5 -0
  83. package/src/sync/sync-saga-coordinator.mts +680 -714
  84. package/src/sync/sync-saga-message/sync-saga-message-types.mts +160 -24
  85. package/src/sync/sync-types.mts +96 -105
  86. 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, SYNC_CONFLICT_STRATEGY_VALID_VALUES, } from "./sync-types.mjs";
9
+ import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
10
+ import { getDeltaDependencyGraph, 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";
15
- import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
13
+ import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
14
+ import { createSyncSagaContext, validateContextAndSagaFrame } 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({
@@ -102,33 +95,22 @@ export class SyncSagaCoordinator {
102
95
  // Async execution wrapper
103
96
  (async () => {
104
97
  try {
105
- // 2. BOOTSTRAP IDENTITY (Session Keystone)
98
+ // BOOTSTRAP IDENTITY (Session Keystone)
106
99
  const sessionIdentity = useSessionIdentity
107
100
  ? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
108
101
  : undefined;
109
102
  // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
110
- // 3. CREATE INITIAL FRAME (Stage.init)
111
- const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
103
+ // CREATE INITIAL FRAME (Stage.init)
104
+ const { initFrame, initDomainGraph } = await this.createInitFrame({
112
105
  sagaId,
113
106
  sessionIdentity,
114
- localSpace,
115
107
  domainIbGibs,
116
- tempSpace,
117
- metaspace,
118
- conflictStrategy
108
+ conflictStrategy,
109
+ metaspace, localSpace, tempSpace,
119
110
  });
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,
111
+ // KICK OFF THE PING-PONG SAGA LOOP (FSM)
112
+ await this.executeSagaLoop({
113
+ initFrame, initDomainGraph,
132
114
  peer,
133
115
  sessionIdentity,
134
116
  updates$,
@@ -136,13 +118,6 @@ export class SyncSagaCoordinator {
136
118
  tempSpace,
137
119
  metaspace
138
120
  });
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
- }
146
121
  resolveDone();
147
122
  if (!updates$.complete) {
148
123
  throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`);
@@ -205,139 +180,98 @@ export class SyncSagaCoordinator {
205
180
  * **Execution Context**: **Sender (Local)**.
206
181
  *
207
182
  * 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`.
183
+ * It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
209
184
  *
210
185
  * **Data Transport Note**:
211
186
  * Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
212
- * When `handleSagaFrame` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
187
+ * When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
213
188
  * the NEXT request context.
214
189
  * When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
215
190
  */
216
- async executeSagaLoop({ initialFrame, srcGraph, peer, sessionIdentity, updates$, localSpace, tempSpace, metaspace }) {
191
+ async executeSagaLoop({ initFrame, initDomainGraph, peer, sessionIdentity, updates$, localSpace, tempSpace, metaspace }) {
217
192
  const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
218
- // The current frame we just generated (e.g., Init or Delta Request)
219
- let currentFrame = initialFrame;
220
- // The payload we need to attach to the message (data we are sending)
193
+ /** The current frame we just generated (e.g., Init or Delta Request) */
194
+ let currentFrame = initFrame;
195
+ /** The **domain** payload we need to transmit (data we are sending) */
221
196
  let nextDomainIbGibs = [];
222
- // Accumulator for all data we've successfully pulled from the remote
223
- const allReceivedIbGibs = [];
224
197
  while (currentFrame) {
225
- // A. Create Context (Request)
226
- // 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
- // We must do this BEFORE creating the Context so we can list them
229
- // all in payloadAddrsDomain and payloadAddrsControl.
230
- // const depsDomainIbGibs: IbGib_V1[] = [];
231
- // const depsControlIbGibs: IbGib_V1[] = [];
232
- const payloadIbGibsControl = [];
233
- const payloadIbGibsDomain = [];
234
- // A. Payload (Standard Deep Deps)
235
- for (const nextDomainIbGib of nextDomainIbGibs) {
236
- let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
237
- if (!nextDomainIbGibGraph) {
238
- nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
239
- }
240
- if (nextDomainIbGibGraph) {
241
- payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
242
- }
243
- else {
244
- throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
245
- }
246
- }
247
- 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
- }
259
- }
260
- // 2. Create Context (Envelope)
198
+ // first, prepare for context transmission...
199
+ /**
200
+ * this is used later in pollForDomainPayloads **iif** payloads are
201
+ * received **in the response**, i.e., on the return trip. So if we
202
+ * request addrs, the response should have these addrs and their
203
+ * dependencies. The ibgibs corresponding to these addrs will be
204
+ * streamed and placed into this map.
205
+ */
261
206
  const domainPayloadsMap = new Map();
207
+ // #region set up peer observable for any domainPayloadsMap
262
208
  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
209
+ const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
210
+ next: async (ibgib) => {
211
+ if (logalot) {
212
+ console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`);
213
+ }
214
+ const addr = getIbGibAddr({ ibGib: ibgib });
215
+ if (logalotControlDomain) {
216
+ console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`);
217
+ }
218
+ domainPayloadsMap.set(addr, ibgib);
219
+ },
220
+ error: async (e) => {
221
+ if (isIbGib(e)) {
222
+ console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
223
+ }
224
+ else {
225
+ console.dir(e);
226
+ console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
227
+ }
228
+ },
229
+ complete: async () => {
230
+ if (logalot) {
231
+ console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`);
232
+ }
233
+ await subscription.unsubscribe();
234
+ },
235
+ }));
236
+ // #endregion set up peer observable for any domainPayloadsMap
237
+ // ...create/compose the Request Context itself...
295
238
  const requestCtx = await createSyncSagaContext({
296
239
  sagaFrame: currentFrame,
297
240
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
298
- payloadIbGibsDomain,
241
+ /**
242
+ * init frame: empty
243
+ * ack frame: possible push offers
244
+ * delta frame: requested ibgibs or commit offer
245
+ * commit frame: empty
246
+ */
247
+ payloadIbGibsDomain: nextDomainIbGibs,
248
+ localSpace,
299
249
  });
300
- // Log what we're sending
250
+ // #region Log what we're sending
301
251
  if (logalotControlDomain) {
302
- const controlAddrs = payloadIbGibsControl.map(p => getIbGibAddr({ ibGib: p }));
303
- const domainAddrs = payloadIbGibsDomain.map(p => getIbGibAddr({ ibGib: p }));
304
- console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
252
+ const domainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
253
+ console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: 5b0081803698770f0bf64992220b312)`);
305
254
  console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
306
255
  console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
307
- console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
308
256
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
309
257
  }
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
258
+ // #endregion Log what we're sending
259
+ // update our saga listeners...
330
260
  // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
331
- updates$.next(requestCtx); // spins off (don't remove this comment!)
261
+ updates$.next(requestCtx); // spins off for saga UI updates
262
+ // ...And send the context.
263
+ peer.setOptionalOpts({ senderSpace: localSpace, senderTempSpace: tempSpace, });
332
264
  const responseCtx = await peer.witness(requestCtx);
333
- // C. Handle Response
265
+ // the send returned, but a peer can return a falsy responseCtx, if
266
+ // we just sent a commit to them and they're done. If so, there will
267
+ // necessarily be no payload to wait to receive
334
268
  if (!responseCtx) {
335
269
  if (currentFrame) {
336
270
  // Check for Commit (Peer silence expected)
337
271
  const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
338
272
  if (msg?.data?.stage === SyncStage.commit) {
339
273
  if (logalot) {
340
- console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`);
274
+ console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete. (I: 26f9ee073858ca78b8284753368b5226)`);
341
275
  }
342
276
  currentFrame = null;
343
277
  break;
@@ -350,79 +284,83 @@ export class SyncSagaCoordinator {
350
284
  throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
351
285
  }
352
286
  }
353
- // ---------------------------------------------------------------------
354
- // 2d. HANDLE RESPONSE
355
- // ---------------------------------------------------------------------
287
+ // at this point, we did indeed receive a response to analyze. BUT!
288
+ // we have only necessarily received the response context ibgib.
289
+ // if there were payloads expected to be transferred, they may not
290
+ // be done yet.
356
291
  if (!responseCtx.data) {
357
292
  throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
358
293
  }
359
- updates$.next(responseCtx); // spins off -- don't remove this comment!
294
+ updates$.next(responseCtx); // spins off for saga UI updating
295
+ // validate context
296
+ await validateContextAndSagaFrame;
360
297
  // Extract expected domain addresses from response context
361
298
  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
- // Poll for them if needed
299
+ // Poll for them if needed. see above jsdocs for domainPayloadsMap
364
300
  if (responsePayloadAddrsDomain.length > 0) {
365
- await this.pollForDomainPayloads({
301
+ responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
366
302
  expectedAddrs: responsePayloadAddrsDomain,
303
+ pollIntervalMs: 20, // relatively arbitrary right now
367
304
  domainPayloadsMap,
368
305
  tempSpace,
369
306
  });
370
307
  }
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)`);
308
+ // #region Log what we received back
309
+ if (!responseCtx.sagaFrame) {
310
+ throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`);
375
311
  }
376
- // Log what we received back
377
312
  if (logalotControlDomain) {
378
313
  const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] || [];
379
- console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
314
+ console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: 3dc76a9744d89a4fc3e2f076c2be4826)`);
380
315
  console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
381
- console.log(`${lc}${lcControlDomain} Response Frame: ${responseFrameAddr}`);
316
+ console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
382
317
  console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
383
318
  console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
384
319
  }
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
- // Handle Response Frame
395
- const handleResult = await this.handleSagaFrame({
396
- sagaIbGib: responseFrame,
397
- srcGraph: {},
398
- destSpace: localSpace,
399
- tempSpace,
320
+ // #endregion Log what we received back
321
+ // at this point, we have received the context AND **all** of the
322
+ // domain payloads (if applicable), so we are ready to do the next
323
+ // iteration in the saga loop
324
+ // this is the part that drives the FSM forward, i.e., when we
325
+ // evolve the sync saga ibgib itself to the next frame if we aren't
326
+ // finished/errored out.
327
+ const contextResult = await this.handleResponseSagaContext({
328
+ sagaContext: responseCtx,
329
+ mySpace: localSpace,
330
+ myTempSpace: tempSpace,
400
331
  metaspace,
401
- domainPayloadsMap,
402
- expectedDomainAddrs: responsePayloadAddrsDomain,
403
332
  });
404
- if (!handleResult) {
333
+ if (!contextResult) {
405
334
  if (logalot) {
406
- console.log(`${lc} Handler returned null (Saga End).`);
335
+ console.log(`${lc} Handler returned null (Saga End). (I: faae22abc818ba9b28ac6d2881cd7826)`);
407
336
  }
408
337
  break;
409
338
  }
410
- currentFrame = handleResult.frame;
411
- // Collect next DOMAIN payloads for the NEXT request
412
- // Control payloads are handled separately by ensureSagaFrameInBothSpaces
413
- nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
414
- // Log handler output for next iteration
339
+ // #region error conditions throw
340
+ if (contextResult.errorMsg) {
341
+ throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
342
+ }
343
+ else if (!contextResult.nextFrameInfo) {
344
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
345
+ }
346
+ else if (contextResult.nextFrameInfo?.responseWasNull) {
347
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
348
+ }
349
+ // #endregion error conditions throw
350
+ // we have another frame to process and send out, with possibly
351
+ // payload domain ibgibs as well
352
+ const { frame, payloadIbGibsDomain, } = contextResult.nextFrameInfo;
353
+ currentFrame = frame;
354
+ nextDomainIbGibs = [...(payloadIbGibsDomain || [])];
355
+ // #region Log handler output for next iteration
415
356
  if (logalotControlDomain) {
416
- const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
417
357
  const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
418
- console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
358
+ console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: 6b0d88c4c28857ccd812381515bd7826)`);
419
359
  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
360
  console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
422
361
  }
423
- // Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
362
+ // #endregion Log handler output for next iteration
424
363
  }
425
- return allReceivedIbGibs;
426
364
  }
427
365
  /**
428
366
  * Helper to get Knowledge Vector for specific domain ibGibs or TJPs.
@@ -481,7 +419,7 @@ export class SyncSagaCoordinator {
481
419
  }
482
420
  const res = await getLatestAddrs({ space, tjpAddrs: tjps });
483
421
  if (!res.data || !res.data.latestAddrsMap) {
484
- throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
422
+ throw new Error(`${lc} Failed to get latest addrs. (E: 7d395940c0e1419165c5196c39c6c826)`);
485
423
  }
486
424
  // if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
487
425
  return res.data.latestAddrsMap;
@@ -496,24 +434,27 @@ export class SyncSagaCoordinator {
496
434
  }
497
435
  }
498
436
  }
499
- async analyzeTimelines({ domainIbGibs, space, }) {
500
- const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
501
- const srcGraph = await getDependencyGraph({
437
+ async analyzeDomainIbGibs({ domainIbGibs, space, }) {
438
+ const lc = `${this.lc}[${this.analyzeDomainIbGibs.name}]`;
439
+ if (domainIbGibs.length === 0) {
440
+ throw new Error(`invalid domainIbGibs. expected at least one, but array is empty. (E: 820a719bcac5a16878a2af697113b826)`);
441
+ }
442
+ const fullGraph = await getDependencyGraph({
502
443
  ibGibs: domainIbGibs,
503
444
  live: true,
504
445
  space,
505
446
  });
506
- const srcGraphIsValid = srcGraph && Object.keys(srcGraph).length > 0;
447
+ const graphIsValid = fullGraph && Object.keys(fullGraph).length > 0;
507
448
  if (logalot) {
508
- console.log(`${lc} graph generated. nodes: ${srcGraphIsValid ? Object.keys(srcGraph).length : 0}`);
449
+ console.log(`${lc} graph generated. nodes: ${graphIsValid ? Object.keys(fullGraph).length : 0}`);
509
450
  }
510
- const srcIbGibs = srcGraphIsValid ? Object.values(srcGraph) : [];
451
+ const srcIbGibs = graphIsValid ? Object.values(fullGraph) : [];
511
452
  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 };
453
+ const stones = Object.values(src_MapWithoutTjps);
454
+ const withTimelines = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
455
+ const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: withTimelines });
456
+ const topologicallySortedTjpAddrs = this.sortTimelinesTopologically(timelinesMap);
457
+ return { stones, timelinesMap, topologicallySortedTjpAddrs, fullGraph };
517
458
  }
518
459
  /**
519
460
  * Creates the Initial Saga Frame (Init Stage).
@@ -524,57 +465,54 @@ export class SyncSagaCoordinator {
524
465
  * Generates the first frame containing the Knowledge Vector of the Local Space.
525
466
  * This is sent to the Receiver to begin Gap Analysis.
526
467
  */
527
- async createInitFrame({ sagaId, sessionIdentity, localSpace, domainIbGibs, tempSpace, metaspace, conflictStrategy, }) {
468
+ async createInitFrame({ sagaId, sessionIdentity, domainIbGibs, conflictStrategy, metaspace, localSpace, tempSpace, }) {
528
469
  const lc = `${this.lc}[${this.createInitFrame.name}]`;
529
470
  try {
530
471
  if (logalot) {
531
472
  console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`);
532
473
  }
533
474
  // 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
536
- // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
537
- const { srcTimelinesMap, srcGraph, srcStones, } = analysis;
475
+ const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
476
+ // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
477
+ const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
478
+ // we need to store the fullGraph in our tempSpace for later...
479
+ await putInSpace({ ibGibs: Object.values(fullGraph), space: tempSpace });
480
+ // populate our knowledge vector with tjp -> latest addr/tip in timeline
481
+ const knowledgeVector = {};
482
+ Object.keys(srcTimelinesMap).forEach(tjp => {
483
+ const timeline = srcTimelinesMap[tjp];
484
+ const tip = timeline.at(-1);
485
+ knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
486
+ });
538
487
  const initData = {
539
488
  sagaId,
540
489
  stage: SyncStage.init,
541
- knowledgeVector: {},
490
+ knowledgeVector,
542
491
  identity: sessionIdentity, // KeystoneIbGib is already public data
543
492
  mode: SyncMode.sync,
544
493
  stones: srcStones.map(s => getIbGibAddr({ ibGib: s })),
545
494
  };
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
495
  if (logalot) {
553
496
  console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
554
497
  console.log(`${lc} initData.stage: ${initData.stage}`);
555
498
  }
499
+ // create the stone and save/register it
556
500
  const initStone = await this.createSyncMsgStone({
557
501
  data: initData,
558
- space: tempSpace,
559
- metaspace
502
+ localSpace,
503
+ metaspace,
560
504
  });
561
505
  // if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
506
+ // create the initial sync ibgib frame, save/register it
562
507
  const sagaFrame = await this.evolveSyncSagaIbGib({
563
508
  msgStones: [initStone],
564
- identity: sessionIdentity,
565
- space: tempSpace,
566
- metaspace,
567
509
  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
510
+ sessionIdentity: sessionIdentity,
511
+ metaspace,
512
+ localSpace,
575
513
  });
576
514
  // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
577
- return { sagaFrame, srcGraph };
515
+ return { initFrame: sagaFrame, initDomainGraph: fullGraph };
578
516
  }
579
517
  catch (error) {
580
518
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -589,13 +527,16 @@ export class SyncSagaCoordinator {
589
527
  /**
590
528
  * Helper to poll for streaming domain payloads and put them in the
591
529
  * local {@link tempSpace}.
530
+ *
531
+ * @returns when all {@link expectedAddrs} are done being transmitted.
592
532
  */
593
- async pollForDomainPayloads({ expectedAddrs, domainPayloadsMap, tempSpace, }) {
533
+ async pollForDomainPayloads({ expectedAddrs, pollIntervalMs, domainPayloadsMap, tempSpace, }) {
594
534
  const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
595
535
  try {
596
536
  if (logalot) {
597
537
  console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`);
598
538
  }
539
+ let resultDomainPayloads = [];
599
540
  let pending = [...expectedAddrs];
600
541
  const start = Date.now();
601
542
  /**
@@ -619,12 +560,17 @@ export class SyncSagaCoordinator {
619
560
  }
620
561
  if (found.length > 0) {
621
562
  await putInSpace({ space: tempSpace, ibGibs: found });
563
+ found.forEach(x => resultDomainPayloads.push(x));
622
564
  }
623
565
  pending = stillPending;
624
566
  if (pending.length > 0) {
625
- await new Promise(resolve => setTimeout(resolve, 50));
567
+ await delay(pollIntervalMs);
626
568
  }
627
569
  }
570
+ if (expectedAddrs.length !== resultDomainPayloads.length) {
571
+ throw new Error(`(UNEXPECTED) expectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
572
+ }
573
+ return resultDomainPayloads;
628
574
  }
629
575
  catch (error) {
630
576
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -636,12 +582,33 @@ export class SyncSagaCoordinator {
636
582
  }
637
583
  }
638
584
  }
639
- async handleSagaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, identity, identitySecret, metaspace, }) {
640
- const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
585
+ /**
586
+ * This is the heart of the "ping pong" transaction, where we send a context
587
+ * and receive a context. IOW, this drives the FSM of the sync saga ibgib as
588
+ * a whole.
589
+ *
590
+ * This is called in two places:
591
+ *
592
+ * ## 1. Sender
593
+ *
594
+ * On the sender, this is called within the {@link executeSagaLoop} which
595
+ * initiates and drives the sync.
596
+ *
597
+ * ## 2. Receiver
598
+ *
599
+ * On the receiver, this is called directly by the receiving endpoint. That
600
+ * endpoint's job is basically to get these things collocated and prepared
601
+ * to make this call.
602
+ *
603
+ * This is a one-off on the receiver.
604
+ */
605
+ async handleResponseSagaContext({ sagaContext, mySpace, myTempSpace, identity, identitySecret, metaspace, }) {
606
+ const lc = `${this.lc}[${this.handleResponseSagaContext.name}]`;
641
607
  try {
642
608
  if (logalot) {
643
609
  console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`);
644
610
  }
611
+ const sagaIbGib = sagaContext.sagaFrame;
645
612
  if (!sagaIbGib.data) {
646
613
  throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`);
647
614
  }
@@ -649,26 +616,45 @@ export class SyncSagaCoordinator {
649
616
  console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
650
617
  }
651
618
  // Get Stage from Stone (or Frame for Init fallback)
652
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
619
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
653
620
  if (logalot) {
654
621
  console.log(`${lc} handling frame stage: ${stage}`);
655
622
  }
623
+ /**
624
+ * don't like this name, need to refactor
625
+ */
626
+ const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
627
+ let nextFrameInfo;
656
628
  switch (stage) {
657
629
  case SyncStage.init:
658
- return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
630
+ nextFrameInfo = await this.handleInitFrame({
631
+ sagaIbGib,
632
+ messageData: messageData,
633
+ metaspace,
634
+ mySpace: mySpace,
635
+ myTempSpace: myTempSpace,
636
+ identity,
637
+ identitySecret
638
+ });
639
+ break;
659
640
  case SyncStage.ack:
660
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
641
+ nextFrameInfo = await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
642
+ break;
661
643
  case SyncStage.delta:
662
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
644
+ nextFrameInfo = await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
645
+ break;
663
646
  case SyncStage.commit:
664
- return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
647
+ nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
648
+ break;
665
649
  default:
666
650
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
667
651
  }
652
+ return { errorMsg: undefined, nextFrameInfo, };
668
653
  }
669
654
  catch (error) {
670
- console.error(`${lc} ${extractErrorMsg(error)}`);
671
- throw error;
655
+ const errorMsg = `${lc} ${extractErrorMsg(error)}`;
656
+ console.error(errorMsg);
657
+ return { errorMsg };
672
658
  }
673
659
  finally {
674
660
  if (logalot) {
@@ -689,282 +675,332 @@ export class SyncSagaCoordinator {
689
675
  * 3. Identifies what Receiver needs (`deltaReqAddrs`).
690
676
  * 4. Returns an `Ack` frame containing these lists.
691
677
  */
692
- async handleInitFrame({ sagaIbGib, messageData, destSpace, tempSpace, metaspace, identity, identitySecret, }) {
678
+ async handleInitFrame({ sagaIbGib, messageData, mySpace, myTempSpace, metaspace, identity,
679
+ // identitySecret,
680
+ }) {
693
681
  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) {
682
+ try {
716
683
  if (logalot) {
717
- console.log(`${lc} processing stones: ${stones.length}`);
718
- }
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) {
684
+ console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`);
685
+ }
686
+ console.log(`${lc} [TEST DEBUG] Receiver mySpace: ${mySpace.ib}`);
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: c91be82970e4decc58f56bf8fc1ffc26)`);
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 deltaRequestAddrInfos = [];
701
+ const pushOfferInfos = [];
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}`);
707
+ }
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)`);
725
713
  }
726
- addrsNotFound.forEach(addr => {
727
- if (!deltaReqAddrs.includes(addr)) {
728
- deltaReqAddrs.push(addr);
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 (!deltaRequestAddrInfos.some(x => x.addr === addr)) {
721
+ deltaRequestAddrInfos.push({ addr }); // no tjpAddr
722
+ }
723
+ });
724
+ }
755
725
  }
756
- localKV = resGetLatestAddrs.data.latestAddrsMap;
757
- console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
726
+ /**
727
+ * "remote" is sender in this case
728
+ */
729
+ const remoteKV = initData.knowledgeVector;
758
730
  if (logalot) {
759
- console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
731
+ console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
760
732
  }
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);
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 localLatestAddrsMap = {};
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,
745
+ });
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
+ localLatestAddrsMap = resGetLatestAddrs.data.latestAddrsMap;
753
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localLatestAddrsMap)}`);
754
+ if (logalot) {
755
+ console.log(`${lc} localKV: ${pretty(localLatestAddrsMap)} (I: 980975642cbccd8018cf0cd808d30826)`);
756
+ }
788
757
  }
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,
758
+ // 2. Gap Analysis
759
+ for (const tjp of remoteTjps) {
760
+ const remoteAddr = remoteKV[tjp];
761
+ const localAddr = localLatestAddrsMap[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}`);
765
+ deltaRequestAddrInfos.push({
766
+ addr: remoteAddr,
767
+ tjpAddr: tjp,
768
+ // we don't have this timeline at all
769
+ // latestAddrAlreadyHave: undefined
770
+ });
771
+ continue;
772
+ }
773
+ // we do have this timeline...
774
+ if (localAddr === remoteAddr) {
775
+ // ...already synced
776
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
777
+ continue;
778
+ }
779
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
780
+ // we have this timeline but it's not synced...
781
+ // We're executing on receiver. Check if Remote (Sender) is in
782
+ // our past, and if so, we are ahead and need to push the delta
783
+ // to remote
784
+ const remoteIsInPast = await isPastFrame({
785
+ olderAddr: remoteAddr,
786
+ newerAddr: localAddr,
787
+ space: mySpace,
797
788
  });
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`);
801
- deltaReqAddrs.push(remoteAddr);
789
+ if (remoteIsInPast) {
790
+ // we're ahead, so push the delta of what the sender doesn't
791
+ // have (we have full knowledge)
792
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote (sender) is in past - offering push`);
793
+ const deltaGraph = await getDeltaDependencyGraph({
794
+ ibGibAddr: localAddr,
795
+ live: false, // always live: false right?
796
+ latestCommonFrameAddr: remoteAddr,
797
+ space: mySpace,
798
+ });
799
+ pushOfferInfos.push({ tjpAddr: tjp, addrs: Object.keys(deltaGraph), });
802
800
  }
803
801
  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
802
+ // Remote tip is not in our past. So, either Remote is
803
+ // ahead (we're in THEIR past, i.e., Fast-Backward) OR
804
+ // Diverged/conflict (we've both made edits).
805
+ // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
806
+ /**
807
+ * we could first check for existence of remoteAddr in
808
+ * mySpace, but this is quick and easy. If it throws, we
809
+ * don't have it so it can't be in the past.
810
+ */
811
+ let localIsInPast = false;
812
+ try {
813
+ // we could
814
+ localIsInPast = await isPastFrame({
815
+ olderAddr: localAddr,
816
+ newerAddr: remoteAddr,
817
+ space: mySpace,
818
818
  });
819
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)`);
820
+ catch (error) {
821
+ // couldn't get one of them, so localIsInPast remains false.
822
+ if (logalot) {
823
+ console.log(`${lc} expected error if we don't have remote. verbose logging error though: ${extractErrorMsg(error)} (I: 47ea08d0b418cf4aa8a502a7bcb80826)`);
834
824
  }
835
- const timelineAddrs = [localAddr, ...(localTip.rel8ns?.past || [])];
836
- conflicts.push({
825
+ }
826
+ if (localIsInPast) {
827
+ // Fast-Forward: We update to remote's tip.
828
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
829
+ deltaRequestAddrInfos.push({
830
+ addr: remoteAddr,
837
831
  tjpAddr: tjp,
838
- localAddr: localAddr,
839
- remoteAddr,
840
- timelineAddrs,
841
- reason: 'divergence',
842
- terminal: false
832
+ latestAddrAlreadyHave: localAddr,
843
833
  });
844
834
  }
845
835
  else {
846
- throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
836
+ // DIVERGENCE: Both have changes the other doesn't know about.
837
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
838
+ if (conflictStrategy === 'abort') {
839
+ // Abort Strategy: We will treat this as terminal.
840
+ // But for Unified Ack, we just mark it terminal in the list?
841
+ // Or do we actually throw/abort the saga?
842
+ // Current logic (below) aborts the saga if ANY conflict is terminal/abort.
843
+ conflicts.push({
844
+ tjpAddr: tjp,
845
+ localAddr: localAddr,
846
+ remoteAddr,
847
+ timelineAddrs: [], // Not needed for abort
848
+ reason: 'divergence',
849
+ terminal: true
850
+ });
851
+ }
852
+ else if (conflictStrategy === 'optimistic') {
853
+ // Optimistic: We want resolve this.
854
+ // We need to send our history to the Sender so they can Merge.
855
+ // Fetch Full History for Local Timeline
856
+ // Note: We might optimize this to only send "recent" history if we had a KV?
857
+ // But for now, get full past.
858
+ // Optimization: localKV might not have full history.
859
+ // We need to inspect the 'past' of the local tip.
860
+ // We need the ACTUAL object to get the past.
861
+ // We have localAddr.
862
+ const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
863
+ if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
864
+ throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: 83cb88a767e22bbda99c6788bec50526)`);
865
+ }
866
+ const localTip = resLocalTip.ibGibs[0];
867
+ if (!localTip) {
868
+ throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: c39448ad6b3a72af78339ad877a56826)`);
869
+ }
870
+ const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
871
+ conflicts.push({
872
+ tjpAddr: tjp,
873
+ localAddr,
874
+ remoteAddr,
875
+ timelineAddrs,
876
+ reason: 'divergence',
877
+ terminal: false
878
+ });
879
+ }
880
+ else {
881
+ throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy}. Only these currently implemented: ${SYNC_CONFLICT_STRATEGY_VALID_VALUES} (E: 8f12384180f8a718a983a749fe0adf26)`);
882
+ }
847
883
  }
848
884
  }
849
885
  }
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.
886
+ // Check if we should ABORT (if any conflict is terminal)
887
+ const hasTerminalConflicts = conflicts.some(c => c.terminal);
888
+ if (hasTerminalConflicts) {
889
+ // Abort Strategy: Kill the saga.
890
+ if (logalot) {
891
+ console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`);
892
+ }
893
+ throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
894
+ }
895
+ // 3. Create Ack Frame
896
+ if (!sagaIbGib.data) {
897
+ throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 24203af4600fb226ae6c1afbde442826)`);
898
+ }
899
+ const sagaId = sagaIbGib.data.uuid;
900
+ // Create Payload Stone
901
+ const ackData = {
902
+ sagaId,
903
+ stage: SyncStage.ack,
904
+ deltaRequestAddrInfos,
905
+ pushOfferInfos,
906
+ };
907
+ if (conflicts?.length > 0) {
908
+ ackData.conflicts = conflicts;
909
+ }
910
+ const ackStone = await this.createSyncMsgStone({
911
+ data: ackData,
912
+ localSpace: myTempSpace,
913
+ metaspace,
914
+ });
855
915
  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);
916
+ console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`);
877
917
  }
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];
918
+ // Evolve Saga
919
+ const ackFrame = await this.evolveSyncSagaIbGib({
920
+ prevSagaIbGib: sagaIbGib,
921
+ msgStones: [ackStone],
922
+ sessionIdentity: identity,
923
+ localSpace: mySpace,
924
+ metaspace,
925
+ });
926
+ // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
927
+ /**
928
+ * we want to push ibgibs to the remote/sender if we have push
929
+ * offers. an ack frame's payloads, if any, are those push offers
930
+ */
931
+ // let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
932
+ let payloadIbGibsDomain;
933
+ if (pushOfferInfos.length > 0) {
934
+ const searchSecondSpaceAddrs = [];
935
+ payloadIbGibsDomain = [];
936
+ const domainAddrs = pushOfferInfos.flatMap(x => x.addrs);
937
+ const resGet = await getFromSpace({ addrs: domainAddrs, space: mySpace, });
938
+ const resGetRawData = resGet.rawResultIbGib?.data;
939
+ if (!resGetRawData) {
940
+ throw new Error(`(UNEXPECTED) resGet.rawResultIbGib.data falsy? (E: 1a2cc8cb99a1ffa60837e45a8229b826)`);
941
+ }
942
+ const addrsNotFound = resGetRawData.addrsNotFound ?? [];
943
+ const addrsErrored = resGetRawData.addrsErrored ?? [];
944
+ if (resGet.ibGibs && resGet.ibGibs.length === domainAddrs.length) {
945
+ // found all of them
946
+ resGet.ibGibs.forEach(x => { payloadIbGibsDomain.push(x); });
947
+ }
948
+ else if (resGet.ibGibs && resGet.ibGibs.length > 0) {
949
+ // found some of them
950
+ resGet.ibGibs.forEach(x => { payloadIbGibsDomain.push(x); });
951
+ const foundPlusNotFoundCount = payloadIbGibsDomain.length +
952
+ addrsNotFound.length +
953
+ addrsErrored.length;
954
+ if (foundPlusNotFoundCount === domainAddrs.length) {
955
+ // we can still conceivably get the remaining addrs
956
+ addrsNotFound.forEach(x => searchSecondSpaceAddrs.push(x));
957
+ addrsErrored.forEach(x => searchSecondSpaceAddrs.push(x));
958
+ }
959
+ else if (addrsNotFound.length === 0 && addrsErrored.length === 0) {
960
+ throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored both empty? (E: ef1b2cf5bab8de2298ec507abe04e826)`);
961
+ }
962
+ else {
963
+ throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored don't contain the addrs not found? (E: ae9e015109f8c3c3a813da584cd98826)`);
896
964
  }
897
965
  }
898
- }
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];
966
+ else {
967
+ // found none of them(?)
968
+ 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)`);
969
+ domainAddrs.forEach(x => searchSecondSpaceAddrs.push(x));
913
970
  }
914
- }
915
- else {
916
- // We don't have `addr`.
917
- }
971
+ if (searchSecondSpaceAddrs.length > 0) {
972
+ if (logalot) {
973
+ console.log(`${lc} couldn't get all addrs in mySpace (${mySpace.ib}). searchSecondSpaceAddrs: ${searchSecondSpaceAddrs} (I: 233fd954dbd84e51bca02fa8eed5f826)`);
974
+ }
975
+ const resGet2 = await getFromSpace({ addrs: searchSecondSpaceAddrs, space: myTempSpace, });
976
+ if (resGet2.success && resGet2.ibGibs && resGet2.ibGibs.length === searchSecondSpaceAddrs.length) {
977
+ // got them all the second try
978
+ resGet2.ibGibs.forEach(x => payloadIbGibsDomain.push(x));
979
+ }
980
+ else {
981
+ resGet2.ibGibs?.forEach(x => payloadIbGibsDomain.push(x));
982
+ const addrsFailed = domainAddrs.filter(x => !payloadIbGibsDomain.map(pid => getIbGibAddr({ ibGib: pid })).includes(x));
983
+ throw new Error(`couldn't get some or all of addrs from either mySpace (${mySpace.ib}) or myTempSpace (${myTempSpace.ib}). addrsFailed: ${addrsFailed} (E: 1394d412c4ffa4dd085f269b43338826)`);
984
+ }
985
+ }
986
+ // we have now populated payloadIbGibsDomain
987
+ }
988
+ throw new Error(`not implemented (E: ed3f98abb0988c5ae8038bb8d741fb26)`);
989
+ // return {
990
+ // frame: ackFrame,
991
+ // // conflictInfos,
992
+ // payloadIbGibsDomain,
993
+ // };
918
994
  }
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)`);
995
+ catch (error) {
996
+ console.error(`${lc} ${extractErrorMsg(error)}`);
997
+ throw error;
950
998
  }
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);
999
+ finally {
1000
+ if (logalot) {
1001
+ console.log(`${lc} complete.`);
1002
+ }
966
1003
  }
967
- return { frame: ackFrame, payloadIbGibsControl };
968
1004
  }
969
1005
  /**
970
1006
  * Handles the `Ack` frame.
@@ -984,7 +1020,7 @@ export class SyncSagaCoordinator {
984
1020
  if (logalot) {
985
1021
  console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
986
1022
  }
987
- const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1023
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
988
1024
  const ackData = messageData;
989
1025
  if (!ackData) {
990
1026
  throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
@@ -1006,7 +1042,7 @@ export class SyncSagaCoordinator {
1006
1042
  console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
1007
1043
  // Terminal failure. Sender should probably Commit(Fail) or just Abort.
1008
1044
  // For now, throw to trigger abort.
1009
- throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
1045
+ throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
1010
1046
  }
1011
1047
  const optimisticConflicts = conflicts.filter(c => !c.terminal);
1012
1048
  const mergeDeltaReqs = []; // Additional requests for merging
@@ -1028,7 +1064,7 @@ export class SyncSagaCoordinator {
1028
1064
  // Or can we send a 'Delta Request' frame?
1029
1065
  // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
1030
1066
  // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
1031
- // Or we just proceed to Delta (sending what Receiver wants),
1067
+ // Or we just proceed to Delta (sending what Receiver wants),
1032
1068
  // AND we piggyback our own requests?
1033
1069
  // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
1034
1070
  // SIMPLIFICATION for V1:
@@ -1052,7 +1088,7 @@ export class SyncSagaCoordinator {
1052
1088
  const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1053
1089
  const senderTipIbGib = resSenderTip.ibGibs?.[0];
1054
1090
  if (!senderTipIbGib) {
1055
- throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
1091
+ throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`);
1056
1092
  }
1057
1093
  // Basic Diff: Find what Receiver has that we don't.
1058
1094
  // Actually, we need to traverse OUR past to find commonality.
@@ -1129,46 +1165,46 @@ export class SyncSagaCoordinator {
1129
1165
  console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1130
1166
  }
1131
1167
  // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1132
- const deltaReqAddrs = ackData.deltaReqAddrs || [];
1133
- const pushOfferAddrs = ackData.pushOfferAddrs || [];
1168
+ const deltaReqAddrs = ackData.deltaRequestAddrInfos || [];
1169
+ const pushOfferAddrs = ackData.pushOfferInfos || [];
1134
1170
  // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1135
1171
  const pullReqAddrs = [];
1136
1172
  for (const addr of pushOfferAddrs) {
1137
- const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1138
- if (!existing) {
1139
- pullReqAddrs.push(addr);
1140
- }
1173
+ // const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1174
+ // if (!existing) {
1175
+ // pullReqAddrs.push(addr);
1176
+ // }
1141
1177
  }
1142
1178
  // 2. Process Delta Requests (Push Payload)
1143
1179
  // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1180
+ // const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
1144
1181
  const skipAddrs = new Set();
1145
1182
  if (ackData.knowledgeVector) {
1146
1183
  Object.values(ackData.knowledgeVector).forEach(addrs => {
1147
- addrs.forEach(a => skipAddrs.add(a));
1184
+ // addrs.forEach(a => skipAddrs.add(a));
1148
1185
  });
1149
1186
  }
1150
1187
  const payloadIbGibs = [];
1151
1188
  // Gather all tips to sync first
1152
1189
  const tipsToSync = [];
1153
1190
  for (const addr of deltaReqAddrs) {
1154
- let ibGib = srcGraph[addr];
1155
- if (!ibGib) {
1156
- const res = await getFromSpace({ addr, space: destSpace });
1157
- if (res.ibGibs && res.ibGibs.length > 0) {
1158
- ibGib = res.ibGibs[0];
1159
- }
1160
- }
1161
- if (ibGib) {
1162
- tipsToSync.push(ibGib);
1163
- }
1164
- else {
1165
- throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1166
- }
1191
+ // let ibGib = srcGraph[addr];
1192
+ // if (!ibGib) {
1193
+ // const res = await getFromSpace({ addr, space: destSpace });
1194
+ // if (res.ibGibs && res.ibGibs.length > 0) {
1195
+ // ibGib = res.ibGibs[0];
1196
+ // }
1197
+ // }
1198
+ // if (ibGib) {
1199
+ // tipsToSync.push(ibGib);
1200
+ // } else {
1201
+ // throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1202
+ // }
1167
1203
  }
1168
1204
  // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1169
1205
  // Pass skipAddrs to `getDependencyGraph` or gather manually.
1170
1206
  // `getDependencyGraph` takes a single ibGib.
1171
- // We can optimize by doing it for each tip and unioning the result?
1207
+ // We can optimize by doing it for each tip and unioning the result?
1172
1208
  // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1173
1209
  // We will loop.
1174
1210
  const allDepsSet = new Set();
@@ -1221,18 +1257,16 @@ export class SyncSagaCoordinator {
1221
1257
  }
1222
1258
  const deltaStone = await this.createSyncMsgStone({
1223
1259
  data: deltaData,
1224
- space: tempSpace,
1260
+ localSpace: tempSpace,
1225
1261
  metaspace,
1226
1262
  });
1227
1263
  const deltaFrame = await this.evolveSyncSagaIbGib({
1228
1264
  prevSagaIbGib: sagaIbGib,
1229
1265
  msgStones: [deltaStone],
1230
- identity,
1231
- space: tempSpace,
1266
+ sessionIdentity: identity,
1267
+ localSpace: tempSpace,
1232
1268
  metaspace,
1233
1269
  });
1234
- // IMMEDIATELY persist to both spaces for audit trail
1235
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1236
1270
  if (logalot) {
1237
1271
  console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
1238
1272
  }
@@ -1241,7 +1275,9 @@ export class SyncSagaCoordinator {
1241
1275
  if (identity) {
1242
1276
  payloadIbGibsControl.push(identity);
1243
1277
  }
1244
- return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1278
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1279
+ // return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1280
+ throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
1245
1281
  }
1246
1282
  catch (error) {
1247
1283
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -1268,7 +1304,7 @@ export class SyncSagaCoordinator {
1268
1304
  if (logalot) {
1269
1305
  console.log(`${lc} starting...`);
1270
1306
  }
1271
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1307
+ const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1272
1308
  const deltaData = messageData;
1273
1309
  if (!deltaData) {
1274
1310
  throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
@@ -1277,7 +1313,7 @@ export class SyncSagaCoordinator {
1277
1313
  throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
1278
1314
  }
1279
1315
  if (logalot) {
1280
- console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`);
1316
+ console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`);
1281
1317
  }
1282
1318
  console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1283
1319
  const payloadAddrs = deltaData.payloadAddrs || [];
@@ -1286,8 +1322,8 @@ export class SyncSagaCoordinator {
1286
1322
  // 1. Process Received Payload (Ingest)
1287
1323
  const receivedPayloadIbGibs = [];
1288
1324
  if (payloadAddrs.length > 0) {
1289
- // We use `payloadAddrs` as the manifest.
1290
- // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1325
+ // We use `payloadAddrs` as the manifest.
1326
+ // The ACTUAL collection of ibGibs should be available via `getFromSpace`
1291
1327
  // assuming the "Transport" layer put them there implicitly?
1292
1328
  // OR, if we are local-only, we just get them.
1293
1329
  // The `handleDeltaFrame` contract assumes data is reachable in `space`.
@@ -1312,7 +1348,7 @@ export class SyncSagaCoordinator {
1312
1348
  // Get the requested ibGib
1313
1349
  let ibGib = srcGraph[addr];
1314
1350
  if (!ibGib) {
1315
- const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1351
+ const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1316
1352
  if (res.ibGibs && res.ibGibs.length > 0) {
1317
1353
  ibGib = res.ibGibs[0];
1318
1354
  }
@@ -1398,7 +1434,7 @@ export class SyncSagaCoordinator {
1398
1434
  console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1399
1435
  if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1400
1436
  // We have the tip!
1401
- // Do we have the full history?
1437
+ // Do we have the full history?
1402
1438
  // `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
1403
1439
  // If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
1404
1440
  // Perform Merge!
@@ -1442,9 +1478,9 @@ export class SyncSagaCoordinator {
1442
1478
  payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1443
1479
  requests: hasMyRequests ? myRequests : undefined,
1444
1480
  proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1445
- // Wait. If we send data, we are NOT committing yet.
1481
+ // Wait. If we send data, we are NOT committing yet.
1446
1482
  // We are sending data. The OTHER side must ingest it.
1447
- // So proposeCommit = true?
1483
+ // So proposeCommit = true?
1448
1484
  // "Here is the data. I'm done. If you are good, let's commit."
1449
1485
  // Yes.
1450
1486
  };
@@ -1454,24 +1490,24 @@ export class SyncSagaCoordinator {
1454
1490
  responseDeltaData.proposeCommit = true;
1455
1491
  const deltaStone = await this.createSyncMsgStone({
1456
1492
  data: responseDeltaData,
1457
- space: tempSpace,
1493
+ localSpace: tempSpace,
1458
1494
  metaspace
1459
1495
  });
1460
1496
  const deltaFrame = await this.evolveSyncSagaIbGib({
1461
1497
  prevSagaIbGib: sagaIbGib,
1462
1498
  msgStones: [deltaStone],
1463
- identity,
1464
- space: tempSpace,
1499
+ sessionIdentity: identity,
1500
+ localSpace: tempSpace,
1465
1501
  metaspace
1466
1502
  });
1467
- // IMMEDIATELY persist to both spaces for audit trail
1468
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1469
1503
  // Build control payloads: frame + its dependencies (msg stone, identity)
1470
1504
  const payloadIbGibsControl = [deltaFrame, deltaStone];
1471
1505
  if (identity) {
1472
1506
  payloadIbGibsControl.push(identity);
1473
1507
  }
1474
- return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1508
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1509
+ // return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1510
+ throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1475
1511
  }
1476
1512
  else {
1477
1513
  // We have nothing to send.
@@ -1484,24 +1520,24 @@ export class SyncSagaCoordinator {
1484
1520
  };
1485
1521
  const commitStone = await this.createSyncMsgStone({
1486
1522
  data: commitData,
1487
- space: tempSpace,
1523
+ localSpace: tempSpace,
1488
1524
  metaspace
1489
1525
  });
1490
1526
  const commitFrame = await this.evolveSyncSagaIbGib({
1491
1527
  prevSagaIbGib: sagaIbGib,
1492
1528
  msgStones: [commitStone],
1493
- identity,
1494
- space: tempSpace,
1529
+ sessionIdentity: identity,
1530
+ localSpace: tempSpace,
1495
1531
  metaspace
1496
1532
  });
1497
- // IMMEDIATELY persist to both spaces for audit trail
1498
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1499
1533
  // Build control payloads for commit
1500
1534
  const commitCtrlPayloads = [commitFrame, commitStone];
1501
1535
  if (identity) {
1502
1536
  commitCtrlPayloads.push(identity);
1503
1537
  }
1504
- return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1538
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1539
+ // return { frame: commitFrame, };
1540
+ throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
1505
1541
  }
1506
1542
  else {
1507
1543
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1515,18 +1551,16 @@ export class SyncSagaCoordinator {
1515
1551
  };
1516
1552
  const deltaStone = await this.createSyncMsgStone({
1517
1553
  data: responseDeltaData,
1518
- space: tempSpace,
1554
+ localSpace: tempSpace,
1519
1555
  metaspace
1520
1556
  });
1521
1557
  const deltaFrame = await this.evolveSyncSagaIbGib({
1522
1558
  prevSagaIbGib: sagaIbGib,
1523
1559
  msgStones: [deltaStone],
1524
- identity,
1525
- space: tempSpace,
1560
+ sessionIdentity: identity,
1561
+ localSpace: tempSpace,
1526
1562
  metaspace
1527
1563
  });
1528
- // IMMEDIATELY persist to both spaces for audit trail
1529
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1530
1564
  // Check if PEER proposed commit
1531
1565
  if (deltaData.proposeCommit) {
1532
1566
  if (logalot) {
@@ -1541,31 +1575,33 @@ export class SyncSagaCoordinator {
1541
1575
  };
1542
1576
  const commitStone = await this.createSyncMsgStone({
1543
1577
  data: commitData,
1544
- space: tempSpace,
1578
+ localSpace: tempSpace,
1545
1579
  metaspace
1546
1580
  });
1547
1581
  const commitFrame = await this.evolveSyncSagaIbGib({
1548
1582
  prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1549
1583
  msgStones: [commitStone],
1550
- identity,
1551
- space: tempSpace,
1584
+ sessionIdentity: identity,
1585
+ localSpace: tempSpace,
1552
1586
  metaspace
1553
1587
  });
1554
- // IMMEDIATELY persist to both spaces for audit trail
1555
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1556
1588
  // Build control payloads for commit
1557
1589
  const commitCtrlPayloads2 = [commitFrame, commitStone];
1558
1590
  if (identity) {
1559
1591
  commitCtrlPayloads2.push(identity);
1560
1592
  }
1561
- return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1593
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1594
+ // return { frame: commitFrame, };
1595
+ throw new Error(`not implemented (E: 27514878585889e531ef21f1abbef826)`);
1562
1596
  }
1563
1597
  // Build control payloads for delta propose
1564
1598
  const deltaCtrlPayloads = [deltaFrame, deltaStone];
1565
1599
  if (identity) {
1566
1600
  deltaCtrlPayloads.push(identity);
1567
1601
  }
1568
- return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1602
+ // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1603
+ // return { frame: deltaFrame, };
1604
+ throw new Error(`not implemented (E: ff35584696b6fcb3ad6dd7c5cade2f26)`);
1569
1605
  }
1570
1606
  }
1571
1607
  }
@@ -1585,10 +1621,11 @@ export class SyncSagaCoordinator {
1585
1621
  if (logalot) {
1586
1622
  console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`);
1587
1623
  }
1588
- return null;
1624
+ // return { responseWasNull: true };
1625
+ throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
1589
1626
  }
1590
1627
  // #endregion Handlers
1591
- async createSyncMsgStone({ data, space, metaspace, }) {
1628
+ async createSyncMsgStone({ data, localSpace, metaspace, }) {
1592
1629
  const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
1593
1630
  try {
1594
1631
  if (logalot) {
@@ -1604,7 +1641,7 @@ export class SyncSagaCoordinator {
1604
1641
  if (logalot) {
1605
1642
  console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`);
1606
1643
  }
1607
- await putInSpace({ space, ibGib: stone });
1644
+ await metaspace.put({ ibGib: stone, space: localSpace });
1608
1645
  await metaspace.registerNewIbGib({ ibGib: stone });
1609
1646
  return stone;
1610
1647
  }
@@ -1618,41 +1655,10 @@ export class SyncSagaCoordinator {
1618
1655
  }
1619
1656
  }
1620
1657
  }
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
1658
  /**
1653
1659
  * Evolves the saga timeline with a new frame.
1654
1660
  */
1655
- async evolveSyncSagaIbGib({ prevSagaIbGib, msgStones, identity, space, metaspace, conflictStrategy, }) {
1661
+ async evolveSyncSagaIbGib({ prevSagaIbGib, conflictStrategy, msgStones, sessionIdentity, localSpace, metaspace, }) {
1656
1662
  const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
1657
1663
  try {
1658
1664
  // Validation
@@ -1675,7 +1681,7 @@ export class SyncSagaCoordinator {
1675
1681
  throw new Error(`${lc} Mismatched stage in stones. Expected ${stage}, got ${d.stage} (E: d12c6571b0882f762921b60880c3f826)`);
1676
1682
  }
1677
1683
  }
1678
- const identityAddr = identity ? getIbGibAddr({ ibGib: identity }) : undefined;
1684
+ const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
1679
1685
  if (prevSagaIbGib) {
1680
1686
  /**
1681
1687
  * rel8ns always include the new msg stone(s)
@@ -1687,10 +1693,10 @@ export class SyncSagaCoordinator {
1687
1693
  }
1688
1694
  ];
1689
1695
  // if we're authenticating/signing, we'll have identity
1690
- if (identity) {
1691
- rel8nInfos.push({ rel8nName: 'identity', ibGibs: [identity], });
1696
+ if (sessionIdentity) {
1697
+ rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
1692
1698
  }
1693
- // remove the existing sync msg stones' addrs
1699
+ // remove the existing sync msg stones' addrs
1694
1700
  if (!prevSagaIbGib.rel8ns) {
1695
1701
  throw new Error(`(UNEXPECTED) prevSagaIbGib.rel8ns falsy? (E: 81375841aff85b1e48ea42ca218e6826)`);
1696
1702
  }
@@ -1709,7 +1715,7 @@ export class SyncSagaCoordinator {
1709
1715
  rel8nInfos,
1710
1716
  rel8nRemovalInfos,
1711
1717
  metaspace,
1712
- space,
1718
+ space: localSpace,
1713
1719
  noDna: true, // Explicitly no DNA for sync frames
1714
1720
  });
1715
1721
  const newFrame = resAppend;
@@ -1717,31 +1723,36 @@ export class SyncSagaCoordinator {
1717
1723
  }
1718
1724
  else {
1719
1725
  // Create New Timeline (Root Frame)
1726
+ // data
1720
1727
  const data = {
1721
1728
  uuid: sagaId,
1722
- // stage, // Removed from V1
1723
- payload: undefined, // Data in stone
1724
1729
  n: 0,
1725
1730
  isTjp: true,
1726
1731
  conflictStrategy,
1727
1732
  };
1733
+ // ib
1728
1734
  const ib = await getSyncIb({ data });
1735
+ // rel8ns
1729
1736
  const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
1730
- const rel8ns = {
1731
- [SYNC_MSG_REL8N_NAME]: stoneAddrs,
1732
- };
1737
+ const rel8ns = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
1733
1738
  if (identityAddr) {
1734
1739
  rel8ns.identity = [identityAddr];
1735
1740
  }
1736
1741
  const resNew = await createTimeline({
1737
- space,
1742
+ space: localSpace,
1738
1743
  metaspace,
1739
1744
  ib,
1740
1745
  data,
1741
1746
  rel8ns,
1742
1747
  parentIb: SYNC_ATOM, // "sync"
1748
+ /**
1749
+ * this will squash the result into a single frame
1750
+ */
1743
1751
  noDna: true,
1744
1752
  });
1753
+ if (!!resNew.intermediateIbGibs) {
1754
+ throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`);
1755
+ }
1745
1756
  return resNew.newIbGib;
1746
1757
  }
1747
1758
  }
@@ -1750,16 +1761,16 @@ export class SyncSagaCoordinator {
1750
1761
  throw error;
1751
1762
  }
1752
1763
  }
1753
- async getStageAndPayloadFromFrame({ ibGib, space }) {
1764
+ async getStageAndPayloadFromFrame({ sagaFrame, space }) {
1754
1765
  const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
1755
1766
  try {
1756
1767
  if (logalot) {
1757
1768
  console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`);
1758
1769
  }
1759
- if (!ibGib.rel8ns) {
1760
- throw new Error(`(UNEXPECTED) ibGib.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`);
1770
+ if (!sagaFrame.rel8ns) {
1771
+ throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`);
1761
1772
  }
1762
- const stoneAddrs = ibGib.rel8ns?.[SYNC_MSG_REL8N_NAME];
1773
+ const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1763
1774
  if (stoneAddrs && stoneAddrs.length > 0) {
1764
1775
  const stoneAddr = stoneAddrs[0];
1765
1776
  const res = await getFromSpace({ addr: stoneAddr, space });
@@ -1778,7 +1789,7 @@ export class SyncSagaCoordinator {
1778
1789
  }
1779
1790
  }
1780
1791
  else {
1781
- throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? ibGib addr: ${getIbGibAddr(ibGib)} (E: 43eae8579e289d016741b5526052f226)`);
1792
+ throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
1782
1793
  }
1783
1794
  }
1784
1795
  catch (error) {