@ibgib/core-gib 0.1.20 → 0.1.22

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 (165) hide show
  1. package/dist/sync/graft-info/graft-info-constants.d.mts +5 -0
  2. package/dist/sync/graft-info/graft-info-constants.d.mts.map +1 -0
  3. package/dist/sync/graft-info/graft-info-constants.mjs +5 -0
  4. package/dist/sync/graft-info/graft-info-constants.mjs.map +1 -0
  5. package/dist/sync/graft-info/graft-info-helpers.d.mts +49 -0
  6. package/dist/sync/graft-info/graft-info-helpers.d.mts.map +1 -0
  7. package/dist/sync/graft-info/graft-info-helpers.mjs +236 -0
  8. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -0
  9. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts +2 -0
  10. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts.map +1 -0
  11. package/dist/sync/graft-info/graft-info-helpers.respec.mjs +70 -0
  12. package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -0
  13. package/dist/sync/graft-info/graft-info-types.d.mts +31 -0
  14. package/dist/sync/{merge-info/merge-info-types.d.mts.map → graft-info/graft-info-types.d.mts.map} +1 -1
  15. package/dist/sync/graft-info/graft-info-types.mjs +2 -0
  16. package/dist/sync/graft-info/graft-info-types.mjs.map +1 -0
  17. package/dist/sync/strategies/conflict-optimistic.d.mts +1 -1
  18. package/dist/sync/strategies/conflict-optimistic.d.mts.map +1 -1
  19. package/dist/sync/strategies/conflict-optimistic.mjs +10 -60
  20. package/dist/sync/strategies/conflict-optimistic.mjs.map +1 -1
  21. package/dist/sync/sync-conflict.respec.mjs +155 -34
  22. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  23. package/dist/sync/sync-constants.d.mts +9 -11
  24. package/dist/sync/sync-constants.d.mts.map +1 -1
  25. package/dist/sync/sync-constants.mjs +3 -5
  26. package/dist/sync/sync-constants.mjs.map +1 -1
  27. package/dist/sync/sync-helpers.d.mts +2 -0
  28. package/dist/sync/sync-helpers.d.mts.map +1 -1
  29. package/dist/sync/sync-helpers.mjs +11 -9
  30. package/dist/sync/sync-helpers.mjs.map +1 -1
  31. package/dist/sync/sync-innerspace-constants.respec.mjs +5 -1
  32. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  33. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +5 -1
  34. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  35. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +8 -4
  36. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  37. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +5 -1
  38. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  39. package/dist/sync/sync-innerspace-partial-update.respec.mjs +5 -1
  40. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  41. package/dist/sync/sync-innerspace.respec.mjs +5 -1
  42. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  43. package/dist/sync/sync-peer/sync-peer-constants.d.mts +2 -0
  44. package/dist/sync/sync-peer/sync-peer-constants.d.mts.map +1 -0
  45. package/dist/sync/sync-peer/sync-peer-constants.mjs +2 -0
  46. package/dist/sync/sync-peer/sync-peer-constants.mjs.map +1 -0
  47. package/dist/sync/sync-peer/sync-peer-helpers.d.mts +2 -0
  48. package/dist/sync/sync-peer/sync-peer-helpers.d.mts.map +1 -0
  49. package/dist/sync/sync-peer/sync-peer-helpers.mjs +2 -0
  50. package/dist/sync/sync-peer/sync-peer-helpers.mjs.map +1 -0
  51. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts +8 -0
  52. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts.map +1 -0
  53. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs +8 -0
  54. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs.map +1 -0
  55. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.d.mts +18 -0
  56. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.d.mts.map +1 -0
  57. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mjs +54 -0
  58. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mjs.map +1 -0
  59. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +80 -0
  60. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -0
  61. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mjs +5 -0
  62. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mjs.map +1 -0
  63. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +43 -0
  64. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -0
  65. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +229 -0
  66. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -0
  67. package/dist/sync/sync-peer/sync-peer-types.d.mts +12 -0
  68. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  69. package/dist/sync/sync-peer/sync-peer-v1.d.mts +15 -7
  70. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  71. package/dist/sync/sync-peer/sync-peer-v1.mjs +105 -24
  72. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  73. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +5 -8
  74. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  75. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +34 -18
  76. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  77. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +26 -22
  78. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  79. package/dist/sync/sync-saga-context/sync-saga-context-types.mjs +1 -9
  80. package/dist/sync/sync-saga-context/sync-saga-context-types.mjs.map +1 -1
  81. package/dist/sync/sync-saga-coordinator.d.mts +48 -51
  82. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  83. package/dist/sync/sync-saga-coordinator.mjs +720 -420
  84. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  85. package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts.map +1 -1
  86. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs +1 -0
  87. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs.map +1 -1
  88. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -12
  89. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  90. package/dist/sync/sync-types.d.mts +15 -3
  91. package/dist/sync/sync-types.d.mts.map +1 -1
  92. package/dist/witness/light-witness-base-v1.d.mts.map +1 -1
  93. package/dist/witness/light-witness-base-v1.mjs +2 -0
  94. package/dist/witness/light-witness-base-v1.mjs.map +1 -1
  95. package/dist/witness/space/inner-space/inner-space-v1.mjs +1 -1
  96. package/dist/witness/space/inner-space/inner-space-v1.mjs.map +1 -1
  97. package/ibgib-foundations.md +20 -2
  98. package/package.json +1 -1
  99. package/src/sync/README.md +31 -22
  100. package/src/sync/graft-info/graft-info-constants.mts +4 -0
  101. package/src/sync/graft-info/graft-info-helpers.mts +308 -0
  102. package/src/sync/graft-info/graft-info-helpers.respec.mts +83 -0
  103. package/src/sync/graft-info/graft-info-types.mts +33 -0
  104. package/src/sync/strategies/conflict-optimistic.mts +11 -70
  105. package/src/sync/sync-conflict.respec.mts +177 -37
  106. package/src/sync/sync-constants.mts +5 -9
  107. package/src/sync/sync-helpers.mts +11 -7
  108. package/src/sync/sync-innerspace-constants.respec.mts +5 -1
  109. package/src/sync/sync-innerspace-deep-updates.respec.mts +5 -1
  110. package/src/sync/sync-innerspace-dest-ahead.respec.mts +7 -3
  111. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +5 -1
  112. package/src/sync/sync-innerspace-partial-update.respec.mts +8 -2
  113. package/src/sync/sync-innerspace.respec.mts +5 -1
  114. package/src/sync/sync-peer/sync-peer-constants.mts +0 -0
  115. package/src/sync/sync-peer/sync-peer-helpers.mts +0 -0
  116. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mts +8 -0
  117. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mts +72 -0
  118. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +87 -0
  119. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +242 -0
  120. package/src/sync/sync-peer/sync-peer-types.mts +13 -1
  121. package/src/sync/sync-peer/sync-peer-v1.mts +93 -27
  122. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +47 -29
  123. package/src/sync/sync-saga-context/sync-saga-context-types.mts +29 -30
  124. package/src/sync/sync-saga-coordinator.mts +782 -441
  125. package/src/sync/sync-saga-message/sync-saga-message-helpers.mts +2 -0
  126. package/src/sync/sync-saga-message/sync-saga-message-types.mts +0 -11
  127. package/src/sync/sync-types.mts +17 -3
  128. package/src/witness/light-witness-base-v1.mts +2 -1
  129. package/src/witness/space/inner-space/inner-space-v1.mts +1 -1
  130. package/test_output.log +489 -0
  131. package/tmp.md +61 -2
  132. package/dist/sync/merge-info/merge-info-constants.d.mts +0 -2
  133. package/dist/sync/merge-info/merge-info-constants.d.mts.map +0 -1
  134. package/dist/sync/merge-info/merge-info-constants.mjs +0 -2
  135. package/dist/sync/merge-info/merge-info-constants.mjs.map +0 -1
  136. package/dist/sync/merge-info/merge-info-helpers.d.mts +0 -51
  137. package/dist/sync/merge-info/merge-info-helpers.d.mts.map +0 -1
  138. package/dist/sync/merge-info/merge-info-helpers.mjs +0 -92
  139. package/dist/sync/merge-info/merge-info-helpers.mjs.map +0 -1
  140. package/dist/sync/merge-info/merge-info-helpers.respec.d.mts +0 -2
  141. package/dist/sync/merge-info/merge-info-helpers.respec.d.mts.map +0 -1
  142. package/dist/sync/merge-info/merge-info-helpers.respec.mjs +0 -32
  143. package/dist/sync/merge-info/merge-info-helpers.respec.mjs.map +0 -1
  144. package/dist/sync/merge-info/merge-info-types.d.mts +0 -26
  145. package/dist/sync/merge-info/merge-info-types.mjs +0 -2
  146. package/dist/sync/merge-info/merge-info-types.mjs.map +0 -1
  147. package/dist/sync/sync-local-spaces.respec.d.mts +0 -2
  148. package/dist/sync/sync-local-spaces.respec.d.mts.map +0 -1
  149. package/dist/sync/sync-local-spaces.respec.mjs +0 -159
  150. package/dist/sync/sync-local-spaces.respec.mjs.map +0 -1
  151. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts +0 -39
  152. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts.map +0 -1
  153. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs +0 -131
  154. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs.map +0 -1
  155. package/dist/sync/sync-saga-coordinator.respec.d.mts +0 -2
  156. package/dist/sync/sync-saga-coordinator.respec.d.mts.map +0 -1
  157. package/dist/sync/sync-saga-coordinator.respec.mjs +0 -40
  158. package/dist/sync/sync-saga-coordinator.respec.mjs.map +0 -1
  159. package/src/sync/merge-info/merge-info-constants.mts +0 -1
  160. package/src/sync/merge-info/merge-info-helpers.mts +0 -134
  161. package/src/sync/merge-info/merge-info-helpers.respec.mts +0 -41
  162. package/src/sync/merge-info/merge-info-types.mts +0 -28
  163. package/src/sync/sync-local-spaces.respec.mts +0 -200
  164. package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +0 -167
  165. package/src/sync/sync-saga-coordinator.respec.mts +0 -52
@@ -1,24 +1,27 @@
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, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
3
3
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
4
- import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from "../common/other/ibgib-helper.mjs";
4
+ import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
5
5
  import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
6
- import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
7
6
  import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
8
- import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME } from "./sync-constants.mjs";
7
+ import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
9
8
  import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
10
9
  import { SyncMode, } from "./sync-types.mjs";
11
10
  import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
12
- import { getSyncSagaDependencyGraph } from "./sync-helpers.mjs";
11
+ import { getSyncSagaDependencyGraph as getSyncSagaDependencyGraphForThisFrameOnly } from "./sync-helpers.mjs";
13
12
  import { getDependencyGraph } from "../common/other/graph-helper.mjs";
14
13
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
15
14
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
16
- import { SyncSagaContextCmd } from "./sync-saga-context/sync-saga-context-types.mjs";
17
15
  import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
18
- import { newupSubject } from "../common/pubsub/subject/subject-helper.mjs";
16
+ import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
19
17
  import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
20
18
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
21
- const logalot = GLOBAL_LOG_A_LOT || true;
19
+ 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
+ // const logalot = GLOBAL_LOG_A_LOT || true;
22
+ const logalot = false;
23
+ const logalotControlDomain = true;
24
+ const lcControlDomain = '[ControlDomain]';
22
25
  /**
23
26
  * Orchestrates the synchronization process between two spaces (Source and Destination).
24
27
  *
@@ -103,9 +106,7 @@ export class SyncSagaCoordinator {
103
106
  const sessionIdentity = useSessionIdentity
104
107
  ? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
105
108
  : undefined;
106
- if (logalot) {
107
- console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`);
108
- }
109
+ // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
109
110
  // 3. CREATE INITIAL FRAME (Stage.init)
110
111
  const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
111
112
  sagaId,
@@ -117,6 +118,14 @@ export class SyncSagaCoordinator {
117
118
  conflictStrategy
118
119
  });
119
120
  // 4. EXECUTE SAGA LOOP (FSM)
121
+ // Inject tempSpace into peer so it can pull control payloads to the right place
122
+ // if ('opts' in peer && peer.opts) {
123
+ if (!peer.data) {
124
+ throw new Error(`(UNEXPECTED) peer.data falsy? (E: 8546a884c82ffb1999e95d9867da2826)`);
125
+ }
126
+ if (peer.data.classname === SyncPeerInnerspace_V1.name) {
127
+ // (peer as SyncPeerInnerspace_V1).senderTempSpace = tempSpace;
128
+ }
120
129
  const syncedIbGibs = await this.executeSagaLoop({
121
130
  initialFrame: initFrame,
122
131
  srcGraph,
@@ -209,76 +218,123 @@ export class SyncSagaCoordinator {
209
218
  // The current frame we just generated (e.g., Init or Delta Request)
210
219
  let currentFrame = initialFrame;
211
220
  // The payload we need to attach to the message (data we are sending)
212
- let nextPayloadIbGibs = [];
221
+ let nextDomainIbGibs = [];
213
222
  // Accumulator for all data we've successfully pulled from the remote
214
223
  const allReceivedIbGibs = [];
215
224
  while (currentFrame) {
216
225
  // A. Create Context (Request)
217
226
  // 1. Calculate Full Dependency Graph (including Ancestors/DNA)
218
- // We must do this BEFORE creating the Context so we can list them all in payloadAddrs.
219
- const allDeps = [];
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 = [];
220
234
  // A. Payload (Standard Deep Deps)
221
- for (const item of nextPayloadIbGibs) {
222
- let graph = await getDependencyGraph({ ibGib: item, space: localSpace });
223
- if (!graph) {
224
- graph = await getDependencyGraph({ ibGib: item, space: tempSpace });
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 });
225
239
  }
226
- if (graph) {
227
- allDeps.push(...Object.values(graph));
240
+ if (nextDomainIbGibGraph) {
241
+ payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
228
242
  }
229
243
  else {
230
- allDeps.push(item);
244
+ throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
231
245
  }
232
246
  }
233
247
  if (logalot) {
234
- // console.log(`${lc} allDeps count: ${allDeps.length}`);
248
+ console.log(`${lc} payloadIbGibsDomain count: ${payloadIbGibsDomain.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`);
235
249
  }
236
250
  // B. Frames (Shallow Sync Deps)
237
251
  if (currentFrame) {
238
- const deps = await getSyncSagaDependencyGraph({ ibGib: currentFrame, space: tempSpace });
239
- if (deps)
240
- allDeps.push(...deps);
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
+ }
241
259
  }
242
260
  // 2. Create Context (Envelope)
243
- // Use the FULL dependency list as the payload manifest
244
- const payloadAddrs = allDeps.map(p => getIbGibAddr({ ibGib: p }));
261
+ const domainPayloadsMap = new Map();
262
+ 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
245
295
  const requestCtx = await createSyncSagaContext({
246
- cmd: SyncSagaContextCmd.process,
247
296
  sagaFrame: currentFrame,
248
297
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
249
- payloadAddrs: payloadAddrs.length > 0 ? payloadAddrs : undefined,
298
+ payloadIbGibsDomain,
250
299
  });
251
- // Add Context Deps
252
- if (requestCtx) {
253
- const deps = await getSyncSagaDependencyGraph({ ibGib: requestCtx, space: tempSpace });
254
- if (deps)
255
- allDeps.push(...deps);
300
+ // Log what we're sending
301
+ 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)`);
305
+ console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
306
+ console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
307
+ console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
308
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
256
309
  }
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
+ // }
257
316
  // 3. Identity (if exists)
258
317
  // Identity might be deep? Keystone? Usually self-contained or shallow references.
259
318
  if (sessionIdentity) {
260
- allDeps.push(sessionIdentity);
319
+ payloadIbGibsControl.push(sessionIdentity);
261
320
  }
262
- if (allDeps.length > 0) {
263
- await putInSpace({
264
- space: localSpace,
265
- ibGibs: allDeps
266
- });
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 });
267
328
  }
268
329
  // B. Transmit
269
- if (logalot) {
270
- console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`);
271
- }
272
- updates$.next(requestCtx);
330
+ // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
331
+ updates$.next(requestCtx); // spins off (don't remove this comment!)
273
332
  const responseCtx = await peer.witness(requestCtx);
274
333
  // C. Handle Response
275
334
  if (!responseCtx) {
276
- // Check if we just sent a Commit frame. If so, peer's silence is success/expected.
277
335
  if (currentFrame) {
336
+ // Check for Commit (Peer silence expected)
278
337
  const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
279
- if (logalot) {
280
- console.log(`${lc} Checking currentFrame stage: ${msg?.data?.stage} (Expected: ${SyncStage.commit})`);
281
- }
282
338
  if (msg?.data?.stage === SyncStage.commit) {
283
339
  if (logalot) {
284
340
  console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`);
@@ -286,56 +342,85 @@ export class SyncSagaCoordinator {
286
342
  currentFrame = null;
287
343
  break;
288
344
  }
345
+ else {
346
+ throw new Error(`(UNEXPECTED) responseCtx falsy and currentFrame truthy, but we're not in the commit stage? This may be expected ultimately, but atow I am not seeing this as being expected. (E: cc34498962bd370deeff351fac939f26)`);
347
+ }
348
+ }
349
+ else {
350
+ throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
289
351
  }
290
- throw new Error(`responseCtx falsy. Peer returned no response context (E: c099d8073b48d85e881f917835158f26)`);
291
- // console.warn(`${lc} Peer returned no response context. Ending loop.`);
292
- // currentFrame = null;
293
- // break;
294
352
  }
295
- if (logalot) {
296
- console.log(`${lc} received responseCtx: ${pretty(responseCtx)} (I: 8f35c8b7a9e886aa9743d38bee907826)`);
297
- }
298
- updates$.next(responseCtx);
299
- // D. Extract Remote Frame & React
300
- const remoteFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
301
- if (!remoteFrameAddr) {
302
- // If the remote didn't send a saga frame, we can't continue the protocol.
303
- throw new Error(`${lc} Peer response has no sagaFrame. Ending loop. (E: 0224e84a83e1c7ee288aaed6bdc40826)`);
304
- }
305
- // We look in localSpace first (where Peer delivers), then tempSpace.
306
- let resRemoteFrame = await getFromSpace({ addr: remoteFrameAddr, space: localSpace });
307
- if (!resRemoteFrame.success || !resRemoteFrame.ibGibs?.length) {
308
- // Fallback to tempSpace just in case
309
- resRemoteFrame = await getFromSpace({ addr: remoteFrameAddr, space: tempSpace });
310
- }
311
- const remoteFrame = resRemoteFrame.ibGibs?.[0];
312
- if (!remoteFrame) {
313
- throw new Error(`Could not resolve remote frame: ${remoteFrameAddr}`);
314
- }
315
- // if (logalot) { console.log(`${lc} remoteFrame: ${pretty(remoteFrame)}`); } // leave this in for later use if needed
316
- // Ensure remote frame and its dependencies are in tempSpace
317
- // The Peer delivered them to localSpace, but handleSagaFrame works in tempSpace.
318
- const remoteDeps = await getSyncSagaDependencyGraph({ ibGib: remoteFrame, space: localSpace });
319
- if (remoteDeps && remoteDeps.length > 0) {
320
- await putInSpace({ space: tempSpace, ibGibs: remoteDeps });
321
- }
322
- // React (Reducer)
323
- // This processes the FRAME we just got from the peer.
324
- // i.e., We Sent Init -> Got Ack. This calls handleAckFrame.
325
- // i.e., We Sent Delta -> Got Delta. This calls handleDeltaFrame.
326
- const result = await this.handleSagaFrame({
327
- sagaIbGib: remoteFrame,
328
- srcGraph,
329
- space: localSpace, // Must be localSpace (Source) to find domain data
330
- identity: sessionIdentity,
331
- metaspace
332
- });
333
- currentFrame = result?.frame || null;
334
- nextPayloadIbGibs = result?.payloadIbGibs || []; // Payload to send in NEXT Ping
335
- if (result?.receivedPayloadIbGibs) {
336
- // Keep track of what we received for final merge
337
- allReceivedIbGibs.push(...result.receivedPayloadIbGibs);
353
+ // ---------------------------------------------------------------------
354
+ // 2d. HANDLE RESPONSE
355
+ // ---------------------------------------------------------------------
356
+ if (!responseCtx.data) {
357
+ throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
358
+ }
359
+ updates$.next(responseCtx); // spins off -- don't remove this comment!
360
+ // Extract expected domain addresses from response context
361
+ 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
364
+ if (responsePayloadAddrsDomain.length > 0) {
365
+ await this.pollForDomainPayloads({
366
+ expectedAddrs: responsePayloadAddrsDomain,
367
+ domainPayloadsMap,
368
+ tempSpace,
369
+ });
338
370
  }
371
+ // Extract Response Frame
372
+ const responseFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
373
+ if (!responseFrameAddr) {
374
+ throw new Error(`${lc} Peer response missing sagaFrame (E: 83a0)`);
375
+ }
376
+ // Log what we received back
377
+ if (logalotControlDomain) {
378
+ const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] || [];
379
+ console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
380
+ console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
381
+ console.log(`${lc}${lcControlDomain} Response Frame: ${responseFrameAddr}`);
382
+ console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
383
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
384
+ }
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,
400
+ metaspace,
401
+ domainPayloadsMap,
402
+ expectedDomainAddrs: responsePayloadAddrsDomain,
403
+ });
404
+ if (!handleResult) {
405
+ if (logalot) {
406
+ console.log(`${lc} Handler returned null (Saga End).`);
407
+ }
408
+ break;
409
+ }
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
415
+ if (logalotControlDomain) {
416
+ const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
417
+ const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
418
+ console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
419
+ 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
+ console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
422
+ }
423
+ // Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
339
424
  }
340
425
  return allReceivedIbGibs;
341
426
  }
@@ -345,34 +430,71 @@ export class SyncSagaCoordinator {
345
430
  */
346
431
  async getKnowledgeVector({ space, metaspace, domainIbGibs, tjpAddrs, }) {
347
432
  const lc = `${this.lc}[${this.getKnowledgeVector.name}]`;
348
- if (logalot) {
349
- console.log(`${lc} starting...`);
350
- }
351
- let tjps = [];
352
- if (tjpAddrs) {
353
- tjps = tjpAddrs;
354
- }
355
- else if (domainIbGibs && domainIbGibs.length > 0) {
356
- // Extract TJPs from domain Ibgibs
357
- const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
358
- const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
359
- const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
360
- tjps = Object.keys(timelineMap);
361
- }
362
- else {
363
- // No info provided. Return empty? Or throw?
364
- // User test context implied "everything", but implementation requires scope.
365
- console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
366
- return {};
433
+ try {
434
+ if (logalot) {
435
+ console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`);
436
+ }
437
+ // console.dir(space);
438
+ if (!(domainIbGibs && domainIbGibs.length > 0) &&
439
+ !(tjpAddrs && tjpAddrs.length > 0)) {
440
+ throw new Error(`(UNEXPECTED) domainIbGibs and tjpAddrs falsy/empty? we need one or the other (E: f674285111c8648398cd79d8c08ec826)`);
441
+ }
442
+ if ((domainIbGibs && domainIbGibs.length > 0) &&
443
+ (tjpAddrs && tjpAddrs.length > 0)) {
444
+ throw new Error(`(UNEXPECTED) both domainIbGibs and tjpAddrs truthy? only pass in one or the other. (E: f674285111c8648398cd79d8c08ec826)`);
445
+ }
446
+ let tjps = [];
447
+ if (tjpAddrs) {
448
+ tjps = tjpAddrs;
449
+ }
450
+ else if (domainIbGibs && domainIbGibs.length > 0) {
451
+ // Extract TJPs from domain Ibgibs
452
+ // if (false) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
453
+ const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
454
+ if (false) {
455
+ console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`);
456
+ }
457
+ if (false) {
458
+ console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`);
459
+ }
460
+ const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
461
+ const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
462
+ if (false) {
463
+ console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`);
464
+ }
465
+ tjps = Object.keys(timelineMap);
466
+ if (false) {
467
+ console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`);
468
+ }
469
+ }
470
+ else {
471
+ // No info provided. Return empty? Or throw?
472
+ // User test context implied "everything", but implementation requires scope.
473
+ console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
474
+ return {};
475
+ }
476
+ if (tjps.length === 0) {
477
+ return {};
478
+ }
479
+ if (false) {
480
+ console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`);
481
+ }
482
+ const res = await getLatestAddrs({ space, tjpAddrs: tjps });
483
+ if (!res.data || !res.data.latestAddrsMap) {
484
+ throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
485
+ }
486
+ // if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
487
+ return res.data.latestAddrsMap;
367
488
  }
368
- if (tjps.length === 0) {
369
- return {};
489
+ catch (error) {
490
+ console.error(`${lc} ${extractErrorMsg(error)}`);
491
+ throw error;
370
492
  }
371
- const res = await getLatestAddrs({ space, tjpAddrs: tjps });
372
- if (!res.data || !res.data.latestAddrsMap) {
373
- throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
493
+ finally {
494
+ if (logalot) {
495
+ console.log(`${lc} complete.`);
496
+ }
374
497
  }
375
- return res.data.latestAddrsMap;
376
498
  }
377
499
  async analyzeTimelines({ domainIbGibs, space, }) {
378
500
  const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
@@ -427,14 +549,16 @@ export class SyncSagaCoordinator {
427
549
  const tip = timeline.at(-1);
428
550
  initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
429
551
  });
552
+ if (logalot) {
553
+ console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
554
+ console.log(`${lc} initData.stage: ${initData.stage}`);
555
+ }
430
556
  const initStone = await this.createSyncMsgStone({
431
557
  data: initData,
432
558
  space: tempSpace,
433
559
  metaspace
434
560
  });
435
- if (logalot) {
436
- console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`);
437
- }
561
+ // if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
438
562
  const sagaFrame = await this.evolveSyncSagaIbGib({
439
563
  msgStones: [initStone],
440
564
  identity: sessionIdentity,
@@ -442,9 +566,14 @@ export class SyncSagaCoordinator {
442
566
  metaspace,
443
567
  conflictStrategy,
444
568
  });
445
- if (logalot) {
446
- console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`);
447
- }
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
575
+ });
576
+ // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
448
577
  return { sagaFrame, srcGraph };
449
578
  }
450
579
  catch (error) {
@@ -458,19 +587,56 @@ export class SyncSagaCoordinator {
458
587
  }
459
588
  }
460
589
  /**
461
- * Reacts to an incoming saga frame and dispatches to appropriate handler.
462
- *
463
- * @remarks
464
- * **Execution Context**: **Universal (Both Sender and Receiver)**.
465
- *
466
- * This method acts as the "Reducer" for the Sync FSM. It determines the current stage
467
- * based on the incoming frame and delegates to the appropriate handler.
468
- *
469
- * * If running on **Receiver**: Handles `Init` (via `handleInitFrame`).
470
- * * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
471
- * * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
590
+ * Helper to poll for streaming domain payloads and put them in the
591
+ * local {@link tempSpace}.
472
592
  */
473
- async handleSagaFrame({ sagaIbGib, srcGraph, space, identity, identitySecret, metaspace, }) {
593
+ async pollForDomainPayloads({ expectedAddrs, domainPayloadsMap, tempSpace, }) {
594
+ const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
595
+ try {
596
+ if (logalot) {
597
+ console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`);
598
+ }
599
+ let pending = [...expectedAddrs];
600
+ const start = Date.now();
601
+ /**
602
+ * This needs
603
+ */
604
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
605
+ while (pending.length > 0) {
606
+ if (Date.now() - start > timeoutMs) {
607
+ throw new Error(`Timeout waiting for payloads: ${pending.join(', ')} (E: 46e1683c9578095261aaf798bd5e1826)`);
608
+ }
609
+ const stillPending = [];
610
+ const found = [];
611
+ for (const addr of pending) {
612
+ if (domainPayloadsMap.has(addr)) {
613
+ found.push(domainPayloadsMap.get(addr));
614
+ domainPayloadsMap.delete(addr);
615
+ }
616
+ else {
617
+ stillPending.push(addr);
618
+ }
619
+ }
620
+ if (found.length > 0) {
621
+ await putInSpace({ space: tempSpace, ibGibs: found });
622
+ }
623
+ pending = stillPending;
624
+ if (pending.length > 0) {
625
+ await new Promise(resolve => setTimeout(resolve, 50));
626
+ }
627
+ }
628
+ }
629
+ catch (error) {
630
+ console.error(`${lc} ${extractErrorMsg(error)}`);
631
+ throw error;
632
+ }
633
+ finally {
634
+ if (logalot) {
635
+ console.log(`${lc} complete.`);
636
+ }
637
+ }
638
+ }
639
+ async handleSagaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, identity, identitySecret, metaspace, }) {
474
640
  const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
475
641
  try {
476
642
  if (logalot) {
@@ -483,21 +649,19 @@ export class SyncSagaCoordinator {
483
649
  console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
484
650
  }
485
651
  // Get Stage from Stone (or Frame for Init fallback)
486
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
652
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
487
653
  if (logalot) {
488
654
  console.log(`${lc} handling frame stage: ${stage}`);
489
655
  }
490
656
  switch (stage) {
491
657
  case SyncStage.init:
492
- return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, space, identity, identitySecret });
658
+ return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
493
659
  case SyncStage.ack:
494
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, space, identity });
660
+ return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
495
661
  case SyncStage.delta:
496
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, space, identity, });
662
+ return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
497
663
  case SyncStage.commit:
498
- return await this.handleCommitFrame({ sagaIbGib, metaspace, space, identity, });
499
- case SyncStage.conflict:
500
- return await this.handleConflictFrame({ sagaIbGib, metaspace, space, });
664
+ return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
501
665
  default:
502
666
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
503
667
  }
@@ -525,8 +689,9 @@ export class SyncSagaCoordinator {
525
689
  * 3. Identifies what Receiver needs (`deltaReqAddrs`).
526
690
  * 4. Returns an `Ack` frame containing these lists.
527
691
  */
528
- async handleInitFrame({ sagaIbGib, messageData, space, metaspace, identity, identitySecret, }) {
692
+ async handleInitFrame({ sagaIbGib, messageData, destSpace, tempSpace, metaspace, identity, identitySecret, }) {
529
693
  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]'})`);
530
695
  if (logalot) {
531
696
  console.log(`${lc} starting...`);
532
697
  }
@@ -535,9 +700,7 @@ export class SyncSagaCoordinator {
535
700
  if (initData.stage !== SyncStage.init) {
536
701
  throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
537
702
  }
538
- if (logalot) {
539
- console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`);
540
- }
703
+ // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
541
704
  if (!initData || !initData.knowledgeVector) {
542
705
  throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
543
706
  }
@@ -554,7 +717,7 @@ export class SyncSagaCoordinator {
554
717
  console.log(`${lc} processing stones: ${stones.length}`);
555
718
  }
556
719
  // Check if we have these stones
557
- const resStones = await getFromSpace({ space, addrs: stones });
720
+ const resStones = await getFromSpace({ space: destSpace, addrs: stones });
558
721
  const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
559
722
  if (addrsNotFound && addrsNotFound.length > 0) {
560
723
  if (logalot) {
@@ -572,6 +735,7 @@ export class SyncSagaCoordinator {
572
735
  console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
573
736
  }
574
737
  const remoteTjps = Object.keys(remoteKV);
738
+ console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
575
739
  if (logalot) {
576
740
  console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
577
741
  }
@@ -580,7 +744,7 @@ export class SyncSagaCoordinator {
580
744
  if (remoteTjps.length > 0) {
581
745
  // Batch get latest addrs for the TJPs
582
746
  const resGetLatestAddrs = await getLatestAddrs({
583
- space,
747
+ space: destSpace,
584
748
  tjpAddrs: remoteTjps,
585
749
  });
586
750
  if (!resGetLatestAddrs.data) {
@@ -590,6 +754,7 @@ export class SyncSagaCoordinator {
590
754
  throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
591
755
  }
592
756
  localKV = resGetLatestAddrs.data.latestAddrsMap;
757
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
593
758
  if (logalot) {
594
759
  console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
595
760
  }
@@ -600,21 +765,25 @@ export class SyncSagaCoordinator {
600
765
  const localAddr = localKV[tjp];
601
766
  if (!localAddr) {
602
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}`);
603
769
  deltaReqAddrs.push(remoteAddr);
604
770
  continue;
605
771
  }
606
772
  if (localAddr === remoteAddr) {
607
773
  // Synced
774
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
608
775
  continue;
609
776
  }
777
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
610
778
  // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
611
779
  // (Sender has older version, Receiver has newer) -> Receiver Offers Push
612
780
  const isRemoteInPast = await isPastFrame({
613
781
  olderAddr: remoteAddr,
614
782
  newerAddr: localAddr,
615
- space,
783
+ space: destSpace,
616
784
  });
617
785
  if (isRemoteInPast) {
786
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
618
787
  pushOfferAddrs.push(localAddr);
619
788
  }
620
789
  else {
@@ -624,14 +793,16 @@ export class SyncSagaCoordinator {
624
793
  const isLocalInPast = await isPastFrame({
625
794
  olderAddr: localAddr,
626
795
  newerAddr: remoteAddr,
627
- space,
796
+ space: destSpace,
628
797
  });
629
798
  if (isLocalInPast) {
630
799
  // Fast-Forward: We update to remote's tip.
800
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
631
801
  deltaReqAddrs.push(remoteAddr);
632
802
  }
633
803
  else {
634
804
  // DIVERGENCE: Both have changes the other doesn't know about.
805
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
635
806
  if (conflictStrategy === 'abort') {
636
807
  // Abort Strategy: We will treat this as terminal.
637
808
  // But for Unified Ack, we just mark it terminal in the list?
@@ -656,7 +827,7 @@ export class SyncSagaCoordinator {
656
827
  // We need to inspect the 'past' of the local tip.
657
828
  // We need the ACTUAL object to get the past.
658
829
  // We have localAddr.
659
- const resLocalTip = await getFromSpace({ space, addr: localAddr });
830
+ const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr });
660
831
  const localTip = resLocalTip.ibGibs?.[0];
661
832
  if (!localTip) {
662
833
  throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
@@ -699,7 +870,7 @@ export class SyncSagaCoordinator {
699
870
  // 2. Add Push Offers (Missing in Local)
700
871
  // Check if we have them. If not, ask for them.
701
872
  for (const addr of pushOfferAddrs) {
702
- const hasIt = await getFromSpace({ addr, space });
873
+ const hasIt = await getFromSpace({ addr, space: destSpace });
703
874
  if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
704
875
  // If we don't have it, we put it in `deltaReqAddrs` of the Ack.
705
876
  deltaReqAddrs.push(addr);
@@ -715,7 +886,7 @@ export class SyncSagaCoordinator {
715
886
  for (const tjp of remoteTjps) {
716
887
  const localAddr = localKV[tjp];
717
888
  if (localAddr) {
718
- const res = await getFromSpace({ addr: localAddr, space });
889
+ const res = await getFromSpace({ addr: localAddr, space: destSpace });
719
890
  if (res.success && res.ibGibs?.[0]) {
720
891
  const ibGib = res.ibGibs[0];
721
892
  const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
@@ -732,7 +903,7 @@ export class SyncSagaCoordinator {
732
903
  // We can't really know if it's covered easily without resolving.
733
904
  // But if we don't have it (requesting), we won't find it here anyway.
734
905
  // If we DO have it (push offer), we might find it.
735
- const res = await getFromSpace({ addr, space });
906
+ const res = await getFromSpace({ addr, space: destSpace });
736
907
  if (res.success && res.ibGibs?.[0]) {
737
908
  const ibGib = res.ibGibs[0];
738
909
  const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
@@ -767,10 +938,11 @@ export class SyncSagaCoordinator {
767
938
  deltaReqAddrs,
768
939
  pushOfferAddrs,
769
940
  knowledgeVector,
941
+ conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
770
942
  };
771
943
  const ackStone = await this.createSyncMsgStone({
772
944
  data: ackData,
773
- space,
945
+ space: tempSpace,
774
946
  metaspace,
775
947
  });
776
948
  if (logalot) {
@@ -781,13 +953,18 @@ export class SyncSagaCoordinator {
781
953
  prevSagaIbGib: sagaIbGib,
782
954
  msgStones: [ackStone],
783
955
  identity,
784
- space,
956
+ space: tempSpace,
785
957
  metaspace,
786
958
  });
787
- if (logalot) {
788
- console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`);
789
- }
790
- return { frame: ackFrame };
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);
966
+ }
967
+ return { frame: ackFrame, payloadIbGibsControl };
791
968
  }
792
969
  /**
793
970
  * Handles the `Ack` frame.
@@ -801,240 +978,280 @@ export class SyncSagaCoordinator {
801
978
  *
802
979
  * Returns a `Delta` frame.
803
980
  */
804
- async handleAckFrame({ sagaIbGib, srcGraph, space, metaspace, identity, }) {
981
+ async handleAckFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
805
982
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
806
- if (logalot) {
807
- console.log(`${lc} starting...`);
808
- }
809
- const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
810
- const ackData = messageData;
811
- if (!ackData) {
812
- throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
813
- }
814
- if (ackData.stage !== SyncStage.ack) {
815
- throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
816
- }
817
- if (logalot) {
818
- console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
819
- }
820
- // 1. Check for Conflicts
821
- const conflicts = ackData.conflicts || [];
822
- const terminalConflicts = conflicts.filter(c => c.terminal);
823
- if (terminalConflicts.length > 0) {
824
- console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
825
- // Terminal failure. Sender should probably Commit(Fail) or just Abort.
826
- // For now, throw to trigger abort.
827
- throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
828
- }
829
- const optimisticConflicts = conflicts.filter(c => !c.terminal);
830
- const mergeDeltaReqs = []; // Additional requests for merging
831
- if (optimisticConflicts.length > 0) {
983
+ try {
832
984
  if (logalot) {
833
- console.log(`${lc} Processing optimistic conflicts: ${optimisticConflicts.length}`);
834
- }
835
- // We need to resolve these.
836
- // Strategy:
837
- // 1. Analyze Divergence (Sender vs Receiver)
838
- // 2. Identify missing data needed for merge (Receiver's unique frames)
839
- // 3. Request that data (as Delta Reqs)
840
- // 4. (Later in Delta Phase) Perform Merge.
841
- // BUT: The Delta Phase is usually generic "Send me these Addrs".
842
- // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
843
- // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
844
- // We (Sender) are processing the Ack.
845
- // We need to request data FROM Receiver.
846
- // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
847
- // If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
848
- // Or can we send a 'Delta Request' frame?
849
- // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
850
- // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
851
- // Or we just proceed to Delta (sending what Receiver wants),
852
- // AND we piggyback our own requests?
853
- // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
854
- // SIMPLIFICATION for V1:
855
- // If we need data to merge, we must get it.
856
- // We are the Coordinator (Active). We can fetch from Peer immediately?
857
- // `peer.pull(addr)`?
858
- // Yes! The Coordinator has the `peer`.
859
- // Let's analyze and pull immediately.
860
- for (const conflict of optimisticConflicts) {
861
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
862
- // Sender History
863
- // We need our own history for this timeline.
864
- // We know the 'senderTip' (remoteAddr in Ack).
865
- // Sender should verify it has this tip.
866
- // Compute Diffs
867
- // We need to find `receiverOnly` addrs.
868
- // Receiver sent us `timelineAddrs` (Full History).
869
- const receiverHistorySet = new Set(timelineAddrs);
870
- // We need our execution context's history for this senderTip.
871
- // We can fetch valid 'past' from space.
872
- const resSenderTip = await getFromSpace({ space, addr: senderTip });
873
- const senderTipIbGib = resSenderTip.ibGibs?.[0];
874
- if (!senderTipIbGib) {
875
- throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
876
- }
877
- // Basic Diff: Find what Receiver has that we don't.
878
- // Actually, we need to traverse OUR past to find commonality.
879
- const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
880
- const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
881
- if (receiverOnlyAddrs.length > 0) {
882
- if (logalot) {
883
- console.log(`${lc} Pulling divergent history from Receiver: ${receiverOnlyAddrs.length} frames`);
985
+ console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
986
+ }
987
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
988
+ const ackData = messageData;
989
+ if (!ackData) {
990
+ throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
991
+ }
992
+ if (ackData.stage !== SyncStage.ack) {
993
+ throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
994
+ }
995
+ if (logalot) {
996
+ console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
997
+ }
998
+ // 1. Check for Conflicts
999
+ const conflicts = ackData.conflicts || [];
1000
+ console.log(`${lc} [CONFLICT DEBUG] Received conflicts from Ack: ${conflicts.length}`);
1001
+ if (conflicts.length > 0) {
1002
+ console.log(`${lc} [CONFLICT DEBUG] Conflicts detail: ${JSON.stringify(conflicts, null, 2)}`);
1003
+ }
1004
+ const terminalConflicts = conflicts.filter(c => c.terminal);
1005
+ if (terminalConflicts.length > 0) {
1006
+ console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
1007
+ // Terminal failure. Sender should probably Commit(Fail) or just Abort.
1008
+ // For now, throw to trigger abort.
1009
+ throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
1010
+ }
1011
+ const optimisticConflicts = conflicts.filter(c => !c.terminal);
1012
+ const mergeDeltaReqs = []; // Additional requests for merging
1013
+ if (optimisticConflicts.length > 0) {
1014
+ console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
1015
+ // We need to resolve these.
1016
+ // Strategy:
1017
+ // 1. Analyze Divergence (Sender vs Receiver)
1018
+ // 2. Identify missing data needed for merge (Receiver's unique frames)
1019
+ // 3. Request that data (as Delta Reqs)
1020
+ // 4. (Later in Delta Phase) Perform Merge.
1021
+ // BUT: The Delta Phase is usually generic "Send me these Addrs".
1022
+ // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
1023
+ // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
1024
+ // We (Sender) are processing the Ack.
1025
+ // We need to request data FROM Receiver.
1026
+ // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
1027
+ // If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
1028
+ // Or can we send a 'Delta Request' frame?
1029
+ // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
1030
+ // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
1031
+ // Or we just proceed to Delta (sending what Receiver wants),
1032
+ // AND we piggyback our own requests?
1033
+ // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
1034
+ // SIMPLIFICATION for V1:
1035
+ // If we need data to merge, we must get it.
1036
+ // We are the Coordinator (Active). We can fetch from Peer immediately?
1037
+ // `peer.pull(addr)`?
1038
+ // Yes! The Coordinator has the `peer`.
1039
+ // Let's analyze and pull immediately.
1040
+ for (const conflict of optimisticConflicts) {
1041
+ const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1042
+ // Sender History
1043
+ // We need our own history for this timeline.
1044
+ // We know the 'senderTip' (remoteAddr in Ack).
1045
+ // Sender should verify it has this tip.
1046
+ // Compute Diffs
1047
+ // We need to find `receiverOnly` addrs.
1048
+ // Receiver sent us `timelineAddrs` (Full History).
1049
+ const receiverHistorySet = new Set(timelineAddrs);
1050
+ // We need our execution context's history for this senderTip.
1051
+ // We can fetch valid 'past' from space.
1052
+ const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1053
+ const senderTipIbGib = resSenderTip.ibGibs?.[0];
1054
+ if (!senderTipIbGib) {
1055
+ throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
884
1056
  }
885
- // PULL these frames from Peer into Local Space
886
- // (Validation: We trust peer for now / verification happens on put)
887
- for (const addr of receiverOnlyAddrs) {
888
- // This 'pull' is a sync-peer method?
889
- // The Coordinator 'peer' passed in 'sync()' might be needed here?
890
- // Wait, `handleAckFrame` doesn't have reference to `peer`?
891
- // It only has `space`, `metaspace`.
892
- // The `peer` is held by the `executeSagaLoop`.
893
- // PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
894
- // No, it's a method on Coordinator.
895
- // But `executeSagaLoop` calls it.
896
- // We might need to return "Requirements" to the loop?
897
- // Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
898
- // It returns the NEXT frame (Delta).
899
- // If we need to fetch data, we are blocked.
900
- // We can't easily "Pull" here without the Peer reference.
901
- // OPTION A: Pass `peer` to `handleAckFrame`.
902
- // OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
903
- // Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
904
- // No, Peer is ephemeral connection.
905
- // Let's add `peer` to `handleAckFrame` signature?
906
- // It breaks the pattern of just handling frame + space.
907
- // ALTERNATIVE: Use the `Delta` frame to request data?
908
- // `SyncSagaMessageDeltaData` has `requests?: string[]`.
909
- // Sender sends Delta Frame.
910
- // Does Receiver handle Delta Requests?
911
- // `handleDeltaFrame` (Receiver) -> checks `requests`.
912
- // YES.
913
- // So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
914
- // Receiver sees them, fetches them, and includes them in the Response (Commit?).
915
- // Wait, Init->Ack->Delta->Commit.
916
- // If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
917
- // Unless Commit is not the end?
918
- // Or we do a "Delta 2" loop?
919
- // "Iterative Resolution Loop" from plan.
920
- // If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
921
- // Sender gets Commit. Sees data. Merges.
922
- // Then Sender needs to Send the MERGE result.
923
- // Needs another Push/Delta.
924
- // REFINED FLOW:
925
- // 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
926
- // 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
927
- // 3. Sender handles response -> Merges.
928
- // 4. Sender sends Commit (containing Merge Frame).
929
- // Issue: Current state machine is Init->Ack->Delta->Commit.
930
- // We need to keep Saga open.
931
- // If Sender sends Delta with requests, does it transition to Commit?
1057
+ // Basic Diff: Find what Receiver has that we don't.
1058
+ // Actually, we need to traverse OUR past to find commonality.
1059
+ const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1060
+ const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1061
+ if (receiverOnlyAddrs.length > 0) {
1062
+ console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1063
+ console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1064
+ // PULL these frames from Peer into Local Space
1065
+ // (Validation: We trust peer for now / verification happens on put)
1066
+ for (const addr of receiverOnlyAddrs) {
1067
+ console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1068
+ }
1069
+ // Compute DELTA dependencies for each receiver-only frame
1070
+ // Find LCA to determine what dependencies we already have
1071
+ const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1072
+ console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1073
+ const skipAddrsSet = new Set();
1074
+ if (lcaAddr) {
1075
+ try {
1076
+ const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1077
+ const lcaIbGib = lcaRes.ibGibs?.[0];
1078
+ if (lcaIbGib) {
1079
+ const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1080
+ if (lcaDeps)
1081
+ Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1082
+ console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1083
+ }
1084
+ }
1085
+ catch (e) {
1086
+ console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1087
+ }
1088
+ }
1089
+ // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1090
+ for (const addr of receiverOnlyAddrs) {
1091
+ // Add the frame itself first
1092
+ if (!mergeDeltaReqs.includes(addr)) {
1093
+ mergeDeltaReqs.push(addr);
1094
+ }
1095
+ // Get the frame's delta dependencies (skip LCA's deps)
1096
+ try {
1097
+ const frameRes = await getFromSpace({ addr, space: destSpace });
1098
+ const frameIbGib = frameRes.ibGibs?.[0];
1099
+ if (frameIbGib) {
1100
+ // Get dependency graph, skipping all LCA dependencies
1101
+ const frameDeltaDeps = await getDependencyGraph({
1102
+ ibGib: frameIbGib,
1103
+ space: destSpace,
1104
+ skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1105
+ });
1106
+ if (frameDeltaDeps) {
1107
+ // Add all delta dependencies (Object.keys gives us the addresses)
1108
+ Object.keys(frameDeltaDeps).forEach(depAddr => {
1109
+ if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1110
+ mergeDeltaReqs.push(depAddr);
1111
+ }
1112
+ });
1113
+ }
1114
+ }
1115
+ }
1116
+ catch (depError) {
1117
+ console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1118
+ }
1119
+ }
1120
+ console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1121
+ }
1122
+ else {
1123
+ console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
932
1124
  }
933
- mergeDeltaReqs.push(...receiverOnlyAddrs);
934
1125
  }
1126
+ console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
935
1127
  }
936
- }
937
- // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
938
- const deltaReqAddrs = ackData.deltaReqAddrs || [];
939
- const pushOfferAddrs = ackData.pushOfferAddrs || [];
940
- // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
941
- const pullReqAddrs = [];
942
- for (const addr of pushOfferAddrs) {
943
- const existing = srcGraph[addr] || (await getFromSpace({ addr, space })).ibGibs?.[0];
944
- if (!existing) {
945
- pullReqAddrs.push(addr);
946
- }
947
- }
948
- // 2. Process Delta Requests (Push Payload)
949
- // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
950
- const skipAddrs = new Set();
951
- if (ackData.knowledgeVector) {
952
- Object.values(ackData.knowledgeVector).forEach(addrs => {
953
- addrs.forEach(a => skipAddrs.add(a));
954
- });
955
- }
956
- const payloadIbGibs = [];
957
- // Gather all tips to sync first
958
- const tipsToSync = [];
959
- for (const addr of deltaReqAddrs) {
960
- let ibGib = srcGraph[addr];
961
- if (!ibGib) {
962
- const res = await getFromSpace({ addr, space });
963
- if (res.ibGibs && res.ibGibs.length > 0) {
964
- ibGib = res.ibGibs[0];
1128
+ else {
1129
+ console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1130
+ }
1131
+ // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1132
+ const deltaReqAddrs = ackData.deltaReqAddrs || [];
1133
+ const pushOfferAddrs = ackData.pushOfferAddrs || [];
1134
+ // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1135
+ const pullReqAddrs = [];
1136
+ for (const addr of pushOfferAddrs) {
1137
+ const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1138
+ if (!existing) {
1139
+ pullReqAddrs.push(addr);
965
1140
  }
966
1141
  }
967
- if (ibGib) {
968
- tipsToSync.push(ibGib);
969
- }
970
- else {
971
- throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1142
+ // 2. Process Delta Requests (Push Payload)
1143
+ // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1144
+ const skipAddrs = new Set();
1145
+ if (ackData.knowledgeVector) {
1146
+ Object.values(ackData.knowledgeVector).forEach(addrs => {
1147
+ addrs.forEach(a => skipAddrs.add(a));
1148
+ });
972
1149
  }
973
- }
974
- // Calculate Dependency Graph for ALL tips, effectively utilizing common history
975
- // Pass skipAddrs to `getDependencyGraph` or gather manually.
976
- // `getDependencyGraph` takes a single ibGib.
977
- // We can optimize by doing it for each tip and unioning the result?
978
- // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
979
- // We will loop.
980
- const allDepsSet = new Set();
981
- for (const tip of tipsToSync) {
982
- // Always include the tip itself
983
- const tipAddr = getIbGibAddr({ ibGib: tip });
984
- // Only process if not skipped (though deltaReq implies they barely just asked for it)
985
- // But detailed deps might be skipped.
986
- // Get Graph with Skips
987
- // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
988
- const deps = await getDependencyGraph({
989
- ibGib: tip,
990
- space,
991
- skipAddrs: Array.from(skipAddrs)
992
- });
993
- // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
994
- let tipIncluded = false;
995
- if (deps) {
996
- Object.values(deps).forEach(d => {
997
- const dAddr = getIbGibAddr({ ibGib: d });
998
- if (!allDepsSet.has(dAddr)) {
999
- allDepsSet.add(dAddr);
1000
- payloadIbGibs.push(d);
1001
- }
1002
- if (dAddr === tipAddr) {
1003
- tipIncluded = true;
1150
+ const payloadIbGibs = [];
1151
+ // Gather all tips to sync first
1152
+ const tipsToSync = [];
1153
+ 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];
1004
1159
  }
1005
- });
1160
+ }
1161
+ if (ibGib) {
1162
+ tipsToSync.push(ibGib);
1163
+ }
1164
+ else {
1165
+ throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1166
+ }
1006
1167
  }
1007
- if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1008
- if (logalot) {
1009
- console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
1168
+ // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1169
+ // Pass skipAddrs to `getDependencyGraph` or gather manually.
1170
+ // `getDependencyGraph` takes a single ibGib.
1171
+ // We can optimize by doing it for each tip and unioning the result?
1172
+ // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1173
+ // We will loop.
1174
+ const allDepsSet = new Set();
1175
+ for (const tip of tipsToSync) {
1176
+ // Always include the tip itself
1177
+ const tipAddr = getIbGibAddr({ ibGib: tip });
1178
+ // Only process if not skipped (though deltaReq implies they barely just asked for it)
1179
+ // But detailed deps might be skipped.
1180
+ // Get Graph with Skips
1181
+ // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
1182
+ const deps = await getDependencyGraph({
1183
+ ibGib: tip,
1184
+ space: destSpace,
1185
+ skipAddrs: Array.from(skipAddrs)
1186
+ });
1187
+ // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
1188
+ let tipIncluded = false;
1189
+ if (deps) {
1190
+ Object.values(deps).forEach(d => {
1191
+ const dAddr = getIbGibAddr({ ibGib: d });
1192
+ if (!allDepsSet.has(dAddr)) {
1193
+ allDepsSet.add(dAddr);
1194
+ payloadIbGibs.push(d);
1195
+ }
1196
+ if (dAddr === tipAddr) {
1197
+ tipIncluded = true;
1198
+ }
1199
+ });
1010
1200
  }
1011
- if (!allDepsSet.has(tipAddr)) {
1012
- allDepsSet.add(tipAddr);
1013
- payloadIbGibs.push(tip);
1201
+ if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1202
+ if (logalot) {
1203
+ console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
1204
+ }
1205
+ if (!allDepsSet.has(tipAddr)) {
1206
+ allDepsSet.add(tipAddr);
1207
+ payloadIbGibs.push(tip);
1208
+ }
1014
1209
  }
1015
1210
  }
1211
+ // 3. Create Delta Frame
1212
+ const sagaId = ackData.sagaId;
1213
+ const deltaData = {
1214
+ sagaId: sagaIbGib.data.uuid,
1215
+ stage: SyncStage.delta,
1216
+ payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1217
+ requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1218
+ };
1219
+ if (logalot) {
1220
+ console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`);
1221
+ }
1222
+ const deltaStone = await this.createSyncMsgStone({
1223
+ data: deltaData,
1224
+ space: tempSpace,
1225
+ metaspace,
1226
+ });
1227
+ const deltaFrame = await this.evolveSyncSagaIbGib({
1228
+ prevSagaIbGib: sagaIbGib,
1229
+ msgStones: [deltaStone],
1230
+ identity,
1231
+ space: tempSpace,
1232
+ metaspace,
1233
+ });
1234
+ // IMMEDIATELY persist to both spaces for audit trail
1235
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1236
+ if (logalot) {
1237
+ console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
1238
+ }
1239
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1240
+ const payloadIbGibsControl = [deltaFrame, deltaStone];
1241
+ if (identity) {
1242
+ payloadIbGibsControl.push(identity);
1243
+ }
1244
+ return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1245
+ }
1246
+ catch (error) {
1247
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1248
+ throw error;
1249
+ }
1250
+ finally {
1251
+ if (logalot) {
1252
+ console.log(`${lc} complete.`);
1253
+ }
1016
1254
  }
1017
- // 3. Create Delta Frame
1018
- const sagaId = ackData.sagaId;
1019
- const deltaData = {
1020
- sagaId: sagaIbGib.data.uuid,
1021
- stage: SyncStage.delta,
1022
- payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1023
- requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1024
- };
1025
- const deltaStone = await this.createSyncMsgStone({
1026
- data: deltaData,
1027
- space,
1028
- metaspace,
1029
- });
1030
- const deltaFrame = await this.evolveSyncSagaIbGib({
1031
- prevSagaIbGib: sagaIbGib,
1032
- msgStones: [deltaStone],
1033
- identity,
1034
- space,
1035
- metaspace,
1036
- });
1037
- return { frame: deltaFrame, payloadIbGibs };
1038
1255
  }
1039
1256
  /**
1040
1257
  * Handles the `Delta` frame.
@@ -1046,12 +1263,12 @@ export class SyncSagaCoordinator {
1046
1263
  * 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
1047
1264
  * 3. **Completion**: If no more requests, transitions to `Commit`.
1048
1265
  */
1049
- async handleDeltaFrame({ sagaIbGib, srcGraph, space, metaspace, identity, }) {
1266
+ async handleDeltaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
1050
1267
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1051
1268
  if (logalot) {
1052
1269
  console.log(`${lc} starting...`);
1053
1270
  }
1054
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
1271
+ const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1055
1272
  const deltaData = messageData;
1056
1273
  if (!deltaData) {
1057
1274
  throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
@@ -1062,6 +1279,7 @@ export class SyncSagaCoordinator {
1062
1279
  if (logalot) {
1063
1280
  console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`);
1064
1281
  }
1282
+ console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1065
1283
  const payloadAddrs = deltaData.payloadAddrs || [];
1066
1284
  const peerRequests = deltaData.requests || [];
1067
1285
  const peerProposesCommit = deltaData.proposeCommit || false;
@@ -1075,7 +1293,7 @@ export class SyncSagaCoordinator {
1075
1293
  // The `handleDeltaFrame` contract assumes data is reachable in `space`.
1076
1294
  const res = await getFromSpace({
1077
1295
  addrs: payloadAddrs,
1078
- space,
1296
+ space: tempSpace, // Incoming data is in tempSpace
1079
1297
  });
1080
1298
  if (res.ibGibs) {
1081
1299
  receivedPayloadIbGibs.push(...res.ibGibs);
@@ -1086,47 +1304,80 @@ export class SyncSagaCoordinator {
1086
1304
  console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
1087
1305
  }
1088
1306
  }
1089
- // 2. Fulfill Peer Requests (Outgoing Payload)
1307
+ // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1090
1308
  const outgoingPayload = [];
1309
+ const outgoingAddrsSet = new Set(); // Track what we've added
1310
+ console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
1091
1311
  for (const addr of peerRequests) {
1312
+ // Get the requested ibGib
1092
1313
  let ibGib = srcGraph[addr];
1093
1314
  if (!ibGib) {
1094
- const res = await getFromSpace({ addr, space });
1315
+ const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1095
1316
  if (res.ibGibs && res.ibGibs.length > 0) {
1096
1317
  ibGib = res.ibGibs[0];
1097
1318
  }
1098
1319
  }
1099
1320
  if (ibGib) {
1100
- outgoingPayload.push(ibGib);
1321
+ // Add the requested ibGib itself
1322
+ const ibGibAddr = getIbGibAddr({ ibGib });
1323
+ if (!outgoingAddrsSet.has(ibGibAddr)) {
1324
+ outgoingPayload.push(ibGib);
1325
+ outgoingAddrsSet.add(ibGibAddr);
1326
+ }
1327
+ // Expand to include full dependency graph for this ibGib
1328
+ // (Receiver needs all deps to properly process/merge)
1329
+ try {
1330
+ const deps = await getDependencyGraph({
1331
+ ibGib,
1332
+ space: destSpace,
1333
+ });
1334
+ if (deps) {
1335
+ Object.values(deps).forEach(depIbGib => {
1336
+ const depAddr = getIbGibAddr({ ibGib: depIbGib });
1337
+ if (!outgoingAddrsSet.has(depAddr)) {
1338
+ outgoingPayload.push(depIbGib);
1339
+ outgoingAddrsSet.add(depAddr);
1340
+ }
1341
+ });
1342
+ }
1343
+ }
1344
+ catch (depError) {
1345
+ console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
1346
+ }
1101
1347
  }
1102
1348
  else {
1103
1349
  console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
1104
1350
  }
1105
1351
  }
1352
+ console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1106
1353
  // 3. Execute Merges (If applicable)
1107
1354
  // Check if we have pending conflicts that we CAN resolve now that we have data.
1108
1355
  // We look at the Saga History (Ack Frame) to find conflicts.
1109
1356
  // Optimization: Do this only if we received payloads.
1110
1357
  const mergeResultIbGibs = [];
1358
+ console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1111
1359
  if (receivedPayloadIbGibs.length > 0) {
1360
+ console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1112
1361
  // Find the Ack frame in history to get conflicts
1113
1362
  // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1114
1363
  // V1 timelines carry full history in `past`.
1115
1364
  const pastAddrs = sagaIbGib.rel8ns?.past || [];
1365
+ console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1116
1366
  let ackData;
1117
1367
  if (pastAddrs.length > 0) {
1118
1368
  // Batch fetch all past frames
1119
- const resPast = await getFromSpace({ addrs: pastAddrs, space });
1369
+ const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
1120
1370
  if (resPast.success && resPast.ibGibs) {
1121
1371
  // Iterate backwards (most recent first) to find the latest Ack
1122
1372
  for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1123
1373
  const pastFrame = resPast.ibGibs[i];
1124
1374
  const messageStone = await getSyncSagaMessageFromFrame({
1125
1375
  frameIbGib: pastFrame,
1126
- space
1376
+ space: tempSpace
1127
1377
  });
1128
1378
  if (messageStone?.data?.stage === SyncStage.ack) {
1129
1379
  ackData = messageStone.data;
1380
+ console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1130
1381
  break;
1131
1382
  }
1132
1383
  }
@@ -1142,7 +1393,9 @@ export class SyncSagaCoordinator {
1142
1393
  // We blindly attempt merge if we have both tips accessible?
1143
1394
  // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1144
1395
  // Check if we have receiverTip in space
1145
- const resRecTip = await getFromSpace({ addr: receiverTip, space });
1396
+ console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1397
+ const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
1398
+ console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1146
1399
  if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1147
1400
  // We have the tip!
1148
1401
  // Do we have the full history?
@@ -1151,12 +1404,13 @@ export class SyncSagaCoordinator {
1151
1404
  // Perform Merge!
1152
1405
  try {
1153
1406
  const mergeResult = await mergeDivergentTimelines({
1154
- tipA: (await getFromSpace({ addr: senderTip, space })).ibGibs[0], // Our tip
1155
- tipB: resRecTip.ibGibs[0], // Their tip
1156
- space,
1407
+ tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs[0], // Our tip from destSpace
1408
+ tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
1409
+ space: tempSpace, // Merge uses tempSpace
1157
1410
  metaspace,
1158
1411
  });
1159
1412
  if (mergeResult) {
1413
+ console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1160
1414
  if (logalot) {
1161
1415
  console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1162
1416
  }
@@ -1200,17 +1454,24 @@ export class SyncSagaCoordinator {
1200
1454
  responseDeltaData.proposeCommit = true;
1201
1455
  const deltaStone = await this.createSyncMsgStone({
1202
1456
  data: responseDeltaData,
1203
- space,
1457
+ space: tempSpace,
1204
1458
  metaspace
1205
1459
  });
1206
1460
  const deltaFrame = await this.evolveSyncSagaIbGib({
1207
1461
  prevSagaIbGib: sagaIbGib,
1208
1462
  msgStones: [deltaStone],
1209
1463
  identity,
1210
- space,
1464
+ space: tempSpace,
1211
1465
  metaspace
1212
1466
  });
1213
- return { frame: deltaFrame, payloadIbGibs: outgoingPayload, receivedPayloadIbGibs };
1467
+ // IMMEDIATELY persist to both spaces for audit trail
1468
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1469
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1470
+ const payloadIbGibsControl = [deltaFrame, deltaStone];
1471
+ if (identity) {
1472
+ payloadIbGibsControl.push(identity);
1473
+ }
1474
+ return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1214
1475
  }
1215
1476
  else {
1216
1477
  // We have nothing to send.
@@ -1223,17 +1484,24 @@ export class SyncSagaCoordinator {
1223
1484
  };
1224
1485
  const commitStone = await this.createSyncMsgStone({
1225
1486
  data: commitData,
1226
- space,
1487
+ space: tempSpace,
1227
1488
  metaspace
1228
1489
  });
1229
1490
  const commitFrame = await this.evolveSyncSagaIbGib({
1230
1491
  prevSagaIbGib: sagaIbGib,
1231
1492
  msgStones: [commitStone],
1232
1493
  identity,
1233
- space,
1494
+ space: tempSpace,
1234
1495
  metaspace
1235
1496
  });
1236
- return { frame: commitFrame, receivedPayloadIbGibs };
1497
+ // IMMEDIATELY persist to both spaces for audit trail
1498
+ await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1499
+ // Build control payloads for commit
1500
+ const commitCtrlPayloads = [commitFrame, commitStone];
1501
+ if (identity) {
1502
+ commitCtrlPayloads.push(identity);
1503
+ }
1504
+ return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1237
1505
  }
1238
1506
  else {
1239
1507
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1247,16 +1515,18 @@ export class SyncSagaCoordinator {
1247
1515
  };
1248
1516
  const deltaStone = await this.createSyncMsgStone({
1249
1517
  data: responseDeltaData,
1250
- space,
1518
+ space: tempSpace,
1251
1519
  metaspace
1252
1520
  });
1253
1521
  const deltaFrame = await this.evolveSyncSagaIbGib({
1254
1522
  prevSagaIbGib: sagaIbGib,
1255
1523
  msgStones: [deltaStone],
1256
1524
  identity,
1257
- space,
1525
+ space: tempSpace,
1258
1526
  metaspace
1259
1527
  });
1528
+ // IMMEDIATELY persist to both spaces for audit trail
1529
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1260
1530
  // Check if PEER proposed commit
1261
1531
  if (deltaData.proposeCommit) {
1262
1532
  if (logalot) {
@@ -1271,23 +1541,35 @@ export class SyncSagaCoordinator {
1271
1541
  };
1272
1542
  const commitStone = await this.createSyncMsgStone({
1273
1543
  data: commitData,
1274
- space,
1544
+ space: tempSpace,
1275
1545
  metaspace
1276
1546
  });
1277
1547
  const commitFrame = await this.evolveSyncSagaIbGib({
1278
1548
  prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1279
1549
  msgStones: [commitStone],
1280
1550
  identity,
1281
- space,
1551
+ space: tempSpace,
1282
1552
  metaspace
1283
1553
  });
1284
- return { frame: commitFrame, receivedPayloadIbGibs };
1554
+ // IMMEDIATELY persist to both spaces for audit trail
1555
+ await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1556
+ // Build control payloads for commit
1557
+ const commitCtrlPayloads2 = [commitFrame, commitStone];
1558
+ if (identity) {
1559
+ commitCtrlPayloads2.push(identity);
1560
+ }
1561
+ return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1562
+ }
1563
+ // Build control payloads for delta propose
1564
+ const deltaCtrlPayloads = [deltaFrame, deltaStone];
1565
+ if (identity) {
1566
+ deltaCtrlPayloads.push(identity);
1285
1567
  }
1286
- return { frame: deltaFrame, receivedPayloadIbGibs };
1568
+ return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1287
1569
  }
1288
1570
  }
1289
1571
  }
1290
- async handleCommitFrame({ sagaIbGib, space, metaspace, }) {
1572
+ async handleCommitFrame({ sagaIbGib, destSpace, tempSpace, metaspace, identity, }) {
1291
1573
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1292
1574
  if (logalot) {
1293
1575
  console.log(`${lc} Commit received.`);
@@ -1305,19 +1587,6 @@ export class SyncSagaCoordinator {
1305
1587
  }
1306
1588
  return null;
1307
1589
  }
1308
- async handleConflictFrame({ sagaIbGib, metaspace, space, }) {
1309
- const lc = `${this.lc}[${this.handleConflictFrame.name}]`;
1310
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
1311
- const conflictData = messageData;
1312
- if (logalot) {
1313
- console.log(`${lc} Conflict received. Strategy: ${conflictData?.conflictStrategy}. Terminal: ${conflictData?.isTerminal}`);
1314
- }
1315
- if (conflictData?.isTerminal) {
1316
- throw new Error(`${lc} Saga aborted due to conflicts: ${JSON.stringify(conflictData.conflicts)} (E: b08d1f2a3c4e5d6f7a8b9c0d1e2f3a4b)`);
1317
- }
1318
- // Non-terminal logic (stub for future)
1319
- return null;
1320
- }
1321
1590
  // #endregion Handlers
1322
1591
  async createSyncMsgStone({ data, space, metaspace, }) {
1323
1592
  const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
@@ -1349,6 +1618,37 @@ export class SyncSagaCoordinator {
1349
1618
  }
1350
1619
  }
1351
1620
  }
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
+ }
1352
1652
  /**
1353
1653
  * Evolves the saga timeline with a new frame.
1354
1654
  */