@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
@@ -4,10 +4,11 @@ import {
4
4
  getTimestamp, // so our timestamp strings are uniform
5
5
  getTimestampInTicks, // so our timestamp in ticks as a string are uniform
6
6
  pretty,
7
- clone
7
+ clone,
8
+ unique,
8
9
  } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
10
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
10
- import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from "../common/other/ibgib-helper.mjs";
11
+ import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
11
12
  import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
12
13
  import { IbGib_V1, IbGibRel8ns_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
13
14
  import { isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
@@ -18,7 +19,7 @@ import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space
18
19
  import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
19
20
  import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
20
21
  import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
21
- import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME } from "./sync-constants.mjs";
22
+ import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
22
23
  import { appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
23
24
  import {
24
25
  SyncData_V1, SyncIbGib_V1, SyncInitData, SyncConflictStrategy,
@@ -26,27 +27,44 @@ import {
26
27
  SyncOptions,
27
28
  } from "./sync-types.mjs";
28
29
  import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
29
- import { getSyncSagaDependencyGraph } from "./sync-helpers.mjs";
30
+ import { getSyncSagaDependencyGraph as getSyncSagaDependencyGraphForThisFrameOnly } from "./sync-helpers.mjs";
30
31
  import { getDependencyGraph } from "../common/other/graph-helper.mjs";
31
32
  import {
32
33
  SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
33
34
  SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
34
- SyncSagaMessageCommitData_V1, SyncSagaMessageConflictData_V1
35
+ SyncSagaMessageCommitData_V1
35
36
  } from "./sync-saga-message/sync-saga-message-types.mjs";
36
37
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
37
38
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
38
39
  import { SyncSagaInfo } from "./sync-types.mjs";
39
40
  import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
40
- import { SyncSagaContextIbGib_V1, SyncSagaContextCmd } from "./sync-saga-context/sync-saga-context-types.mjs";
41
+ import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
41
42
  import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
42
- import { newupSubject } from "../common/pubsub/subject/subject-helper.mjs";
43
+ import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
43
44
  import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
44
45
 
45
46
  import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
46
47
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
48
+ import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
49
+ import { SubscriptionWitness } from "../common/pubsub/subscription/subscription-types.mjs";
50
+ import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
51
+ import { SyncPeerInnerspace_V1 } from "./sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs";
47
52
 
48
53
 
49
- const logalot = GLOBAL_LOG_A_LOT || true;
54
+ // const logalot = GLOBAL_LOG_A_LOT || true;
55
+ const logalot = false;
56
+ const logalotControlDomain = true;
57
+ const lcControlDomain = '[ControlDomain]';
58
+
59
+ /**
60
+ * Result of handling a saga frame.
61
+ * Separates control and domain payloads.
62
+ */
63
+ export interface HandleSagaFrameResult {
64
+ frame: SyncIbGib_V1;
65
+ payloadIbGibsControl?: IbGib_V1[];
66
+ payloadIbGibsDomain?: IbGib_V1[];
67
+ }
50
68
 
51
69
  /**
52
70
  * Orchestrates the synchronization process between two spaces (Source and Destination).
@@ -143,7 +161,7 @@ export class SyncSagaCoordinator {
143
161
  const sessionIdentity = useSessionIdentity
144
162
  ? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
145
163
  : undefined;
146
- if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
164
+ // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
147
165
 
148
166
  // 3. CREATE INITIAL FRAME (Stage.init)
149
167
  const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
@@ -157,6 +175,13 @@ export class SyncSagaCoordinator {
157
175
  });
158
176
 
159
177
  // 4. EXECUTE SAGA LOOP (FSM)
178
+ // Inject tempSpace into peer so it can pull control payloads to the right place
179
+ // if ('opts' in peer && peer.opts) {
180
+ if (!peer.data) { throw new Error(`(UNEXPECTED) peer.data falsy? (E: 8546a884c82ffb1999e95d9867da2826)`); }
181
+ if (peer.data.classname === SyncPeerInnerspace_V1.name) {
182
+ // (peer as SyncPeerInnerspace_V1).senderTempSpace = tempSpace;
183
+ }
184
+
160
185
  const syncedIbGibs = await this.executeSagaLoop({
161
186
  initialFrame: initFrame,
162
187
  srcGraph,
@@ -260,7 +285,6 @@ export class SyncSagaCoordinator {
260
285
  srcGraph: { [addr: string]: IbGib_V1 },
261
286
  peer: SyncPeerWitness,
262
287
  sessionIdentity?: KeystoneIbGib_V1,
263
- // updates$: Subject_V1<SyncSagaContextIbGib_V1>,
264
288
  updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
265
289
  localSpace: IbGibSpaceAny,
266
290
  tempSpace: IbGibSpaceAny,
@@ -271,142 +295,213 @@ export class SyncSagaCoordinator {
271
295
  // The current frame we just generated (e.g., Init or Delta Request)
272
296
  let currentFrame: SyncIbGib_V1 | null = initialFrame;
273
297
  // The payload we need to attach to the message (data we are sending)
274
- let nextPayloadIbGibs: IbGib_V1[] = [];
298
+ let nextDomainIbGibs: IbGib_V1[] = [];
275
299
  // Accumulator for all data we've successfully pulled from the remote
276
300
  const allReceivedIbGibs: IbGib_V1[] = [];
277
301
 
278
302
  while (currentFrame) {
279
303
  // A. Create Context (Request)
280
304
  // 1. Calculate Full Dependency Graph (including Ancestors/DNA)
281
- // We must do this BEFORE creating the Context so we can list them all in payloadAddrs.
282
- const allDeps: IbGib_V1[] = [];
305
+ // TODO: adjust this algorithm to only do the dependencies that the other end doesn't need (diff between tip and LCA)
306
+ // We must do this BEFORE creating the Context so we can list them
307
+ // all in payloadAddrsDomain and payloadAddrsControl.
308
+ // const depsDomainIbGibs: IbGib_V1[] = [];
309
+ // const depsControlIbGibs: IbGib_V1[] = [];
310
+ const payloadIbGibsControl: IbGib_V1[] = [];
311
+ const payloadIbGibsDomain: IbGib_V1[] = [];
283
312
 
284
313
  // A. Payload (Standard Deep Deps)
285
- for (const item of nextPayloadIbGibs) {
286
- let graph = await getDependencyGraph({ ibGib: item, space: localSpace });
287
- if (!graph) {
288
- graph = await getDependencyGraph({ ibGib: item, space: tempSpace });
314
+ for (const nextDomainIbGib of nextDomainIbGibs) {
315
+ let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
316
+ if (!nextDomainIbGibGraph) {
317
+ nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
289
318
  }
290
319
 
291
- if (graph) {
292
- allDeps.push(...Object.values(graph));
320
+ if (nextDomainIbGibGraph) {
321
+ payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
293
322
  } else {
294
- allDeps.push(item);
323
+ throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
295
324
  }
296
325
  }
297
- if (logalot) {
298
- // console.log(`${lc} allDeps count: ${allDeps.length}`);
299
- }
326
+ if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${payloadIbGibsDomain.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`); }
300
327
 
301
328
  // B. Frames (Shallow Sync Deps)
302
329
  if (currentFrame) {
303
- const deps = await getSyncSagaDependencyGraph({ ibGib: currentFrame, space: tempSpace });
304
- if (deps) allDeps.push(...deps);
330
+ const depsCurrentFrame = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: currentFrame, space: tempSpace });
331
+ if (depsCurrentFrame.length > 0) {
332
+ depsCurrentFrame.forEach(x => payloadIbGibsControl.push(x));
333
+ } else {
334
+ throw new Error(`(UNEXPECTED) couldn't get deps for currentFrame? currentFrame: ${JSON.stringify(currentFrame)} (E: 06344d07adc80d80b809211171444d26)`);
335
+ }
305
336
  }
306
337
 
307
338
  // 2. Create Context (Envelope)
308
- // Use the FULL dependency list as the payload manifest
309
- const payloadAddrs = allDeps.map(p => getIbGibAddr({ ibGib: p }));
339
+ const domainPayloadsMap = new Map<string, IbGib_V1>();
340
+ const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
341
+ let subscription: SubscriptionWitness;
342
+ if (peer && peer.payloadIbGibsDomainReceived$) {
343
+ // Subscribe to stream
344
+ subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
345
+ next: async (ibgib: IbGib_V1) => {
346
+ if (logalot) { console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`); }
347
+ const addr = getIbGibAddr({ ibGib: ibgib });
348
+ if (logalotControlDomain) { console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`); }
349
+ domainPayloadsMap.set(addr, ibgib);
350
+ },
351
+ error: async (e: string | Error | ErrorIbGib_V1) => {
352
+ if (isIbGib(e)) {
353
+ console.error(`${sublc} error fired. error: ${JSON.stringify((e as IbGib_V1).data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
354
+ } else {
355
+ console.dir(e);
356
+ console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
357
+ }
358
+ },
359
+ complete: async () => {
360
+ if (logalot) { console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`); }
361
+ await subscription.unsubscribe();
362
+ },
363
+ }));
364
+ }
310
365
 
366
+ // 2b. Request Context
311
367
  const requestCtx = await createSyncSagaContext({
312
- cmd: SyncSagaContextCmd.process,
313
368
  sagaFrame: currentFrame,
314
369
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
315
- payloadAddrs: payloadAddrs.length > 0 ? payloadAddrs : undefined,
370
+ payloadIbGibsDomain,
316
371
  });
317
372
 
318
- // Add Context Deps
319
- if (requestCtx) {
320
- const deps = await getSyncSagaDependencyGraph({ ibGib: requestCtx, space: tempSpace });
321
- if (deps) allDeps.push(...deps);
373
+ // Log what we're sending
374
+ if (logalotControlDomain) {
375
+ const controlAddrs = payloadIbGibsControl.map(p => getIbGibAddr({ ibGib: p }));
376
+ const domainAddrs = payloadIbGibsDomain.map(p => getIbGibAddr({ ibGib: p }));
377
+ console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
378
+ console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
379
+ console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
380
+ console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
381
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
322
382
  }
323
383
 
384
+ // Add Context Deps
385
+ // do we need to add requestCtx to the payload ibgibs?
386
+ // if (requestCtx) {
387
+ // const deps = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: requestCtx, space: tempSpace });
388
+ // if (deps) { deps.forEach(x => payloadIbGibsControl.push(x)); }
389
+ // }
390
+
324
391
  // 3. Identity (if exists)
325
392
  // Identity might be deep? Keystone? Usually self-contained or shallow references.
326
393
  if (sessionIdentity) {
327
- allDeps.push(sessionIdentity);
394
+ payloadIbGibsControl.push(sessionIdentity);
328
395
  }
329
396
 
330
- if (allDeps.length > 0) {
331
- await putInSpace({
332
- space: localSpace,
333
- ibGibs: allDeps
334
- });
397
+ // we only put the **CONTROL** payload ibgibs in localSpace. we
398
+ // don't put any domain ibgibs into this durable space until the
399
+ // final commit phase.
400
+ // The requestCtx envelope itself also goes to localSpace so transfer can find it.
401
+ await putInSpace({ space: localSpace, ibGib: requestCtx });
402
+ if (payloadIbGibsControl.length > 0) {
403
+ await putInSpace({ space: localSpace, ibGibs: payloadIbGibsControl });
335
404
  }
336
405
 
337
406
  // B. Transmit
338
- if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
339
- updates$.next(requestCtx);
407
+ // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
408
+ updates$.next(requestCtx); // spins off (don't remove this comment!)
409
+
340
410
  const responseCtx = await peer.witness(requestCtx);
341
411
 
342
412
  // C. Handle Response
343
413
  if (!responseCtx) {
344
- // Check if we just sent a Commit frame. If so, peer's silence is success/expected.
345
414
  if (currentFrame) {
415
+ // Check for Commit (Peer silence expected)
346
416
  const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
347
- if (logalot) { console.log(`${lc} Checking currentFrame stage: ${msg?.data?.stage} (Expected: ${SyncStage.commit})`); }
348
417
  if (msg?.data?.stage === SyncStage.commit) {
349
418
  if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`); }
350
419
  currentFrame = null;
351
420
  break;
421
+ } else {
422
+ 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)`);
352
423
  }
424
+ } else {
425
+ throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
353
426
  }
354
-
355
- throw new Error(`responseCtx falsy. Peer returned no response context (E: c099d8073b48d85e881f917835158f26)`);
356
- // console.warn(`${lc} Peer returned no response context. Ending loop.`);
357
- // currentFrame = null;
358
- // break;
359
427
  }
360
428
 
361
- if (logalot) {
362
- console.log(`${lc} received responseCtx: ${pretty(responseCtx)} (I: 8f35c8b7a9e886aa9743d38bee907826)`);
363
- }
429
+ // ---------------------------------------------------------------------
430
+ // 2d. HANDLE RESPONSE
431
+ // ---------------------------------------------------------------------
432
+ if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
433
+ updates$.next(responseCtx); // spins off -- don't remove this comment!
434
+
435
+ // Extract expected domain addresses from response context
436
+ const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
364
437
 
365
- updates$.next(responseCtx);
438
+ // 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.
366
439
 
367
- // D. Extract Remote Frame & React
368
- const remoteFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
369
- if (!remoteFrameAddr) {
370
- // If the remote didn't send a saga frame, we can't continue the protocol.
371
- throw new Error(`${lc} Peer response has no sagaFrame. Ending loop. (E: 0224e84a83e1c7ee288aaed6bdc40826)`);
440
+ // Poll for them if needed
441
+ if (responsePayloadAddrsDomain.length > 0) {
442
+ await this.pollForDomainPayloads({
443
+ expectedAddrs: responsePayloadAddrsDomain,
444
+ domainPayloadsMap,
445
+ tempSpace,
446
+ });
372
447
  }
373
448
 
374
- // We look in localSpace first (where Peer delivers), then tempSpace.
375
- let resRemoteFrame = await getFromSpace({ addr: remoteFrameAddr, space: localSpace });
376
- if (!resRemoteFrame.success || !resRemoteFrame.ibGibs?.length) {
377
- // Fallback to tempSpace just in case
378
- resRemoteFrame = await getFromSpace({ addr: remoteFrameAddr, space: tempSpace });
449
+ // Extract Response Frame
450
+ const responseFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
451
+ if (!responseFrameAddr) { throw new Error(`${lc} Peer response missing sagaFrame (E: 83a0)`); }
452
+
453
+ // Log what we received back
454
+ if (logalotControlDomain) {
455
+ const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] as string[] || [];
456
+ console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
457
+ console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
458
+ console.log(`${lc}${lcControlDomain} Response Frame: ${responseFrameAddr}`);
459
+ console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
460
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
379
461
  }
380
- const remoteFrame = resRemoteFrame.ibGibs?.[0];
381
462
 
382
- if (!remoteFrame) { throw new Error(`Could not resolve remote frame: ${remoteFrameAddr}`); }
383
- // if (logalot) { console.log(`${lc} remoteFrame: ${pretty(remoteFrame)}`); } // leave this in for later use if needed
463
+ // Get response frame from localSpace (SyncPeer puts it there)
464
+ let resResponseFrame = await getFromSpace({ space: localSpace, addr: responseFrameAddr });
465
+ if (!resResponseFrame.success || !resResponseFrame.ibGibs?.length) {
466
+ // Fallback to tempSpace
467
+ resResponseFrame = await getFromSpace({ space: tempSpace, addr: responseFrameAddr });
468
+ }
469
+ const responseFrame = resResponseFrame.ibGibs?.[0] as any;
470
+ if (!responseFrame) throw new Error(`${lc} Response frame not found (E: 7c2a)`);
471
+
472
+ // Handle Response Frame
473
+ const handleResult = await this.handleSagaFrame({
474
+ sagaIbGib: responseFrame,
475
+ srcGraph: {},
476
+ destSpace: localSpace,
477
+ tempSpace,
478
+ metaspace,
479
+ domainPayloadsMap,
480
+ expectedDomainAddrs: responsePayloadAddrsDomain,
481
+ });
384
482
 
385
- // Ensure remote frame and its dependencies are in tempSpace
386
- // The Peer delivered them to localSpace, but handleSagaFrame works in tempSpace.
387
- const remoteDeps = await getSyncSagaDependencyGraph({ ibGib: remoteFrame, space: localSpace });
388
- if (remoteDeps && remoteDeps.length > 0) {
389
- await putInSpace({ space: tempSpace, ibGibs: remoteDeps });
483
+ if (!handleResult) {
484
+ if (logalot) { console.log(`${lc} Handler returned null (Saga End).`); }
485
+ break;
390
486
  }
391
487
 
392
- // React (Reducer)
393
- // This processes the FRAME we just got from the peer.
394
- // i.e., We Sent Init -> Got Ack. This calls handleAckFrame.
395
- // i.e., We Sent Delta -> Got Delta. This calls handleDeltaFrame.
396
- const result = await this.handleSagaFrame({
397
- sagaIbGib: remoteFrame as SyncIbGib_V1,
398
- srcGraph,
399
- space: localSpace, // Must be localSpace (Source) to find domain data
400
- identity: sessionIdentity,
401
- metaspace
402
- });
488
+ currentFrame = handleResult.frame;
403
489
 
404
- currentFrame = result?.frame || null;
405
- nextPayloadIbGibs = result?.payloadIbGibs || []; // Payload to send in NEXT Ping
406
- if (result?.receivedPayloadIbGibs) {
407
- // Keep track of what we received for final merge
408
- allReceivedIbGibs.push(...result.receivedPayloadIbGibs);
490
+ // Collect next DOMAIN payloads for the NEXT request
491
+ // Control payloads are handled separately by ensureSagaFrameInBothSpaces
492
+ nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
493
+
494
+ // Log handler output for next iteration
495
+ if (logalotControlDomain) {
496
+ const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
497
+ const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
498
+ console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
499
+ console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
500
+ console.log(`${lc}${lcControlDomain} CONTROL for next (${handlerControlAddrs.length}): ${handlerControlAddrs.join(', ') || '(none)'} (saved via ensureSagaFrameInBothSpaces)`);
501
+ console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
409
502
  }
503
+
504
+ // Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
410
505
  }
411
506
 
412
507
  return allReceivedIbGibs;
@@ -428,31 +523,65 @@ export class SyncSagaCoordinator {
428
523
  tjpAddrs?: string[],
429
524
  }): Promise<{ [tjp: string]: string | null }> {
430
525
  const lc = `${this.lc}[${this.getKnowledgeVector.name}]`;
431
- if (logalot) { console.log(`${lc} starting...`); }
526
+ try {
527
+ if (logalot) { console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`); }
528
+ // console.dir(space);
432
529
 
433
- let tjps: string[] = [];
434
- if (tjpAddrs) {
435
- tjps = tjpAddrs;
436
- } else if (domainIbGibs && domainIbGibs.length > 0) {
437
- // Extract TJPs from domain Ibgibs
438
- const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
439
- const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
440
- const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
441
- tjps = Object.keys(timelineMap);
442
- } else {
443
- // No info provided. Return empty? Or throw?
444
- // User test context implied "everything", but implementation requires scope.
445
- console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
446
- return {};
447
- }
530
+ if (!(domainIbGibs && domainIbGibs.length > 0) &&
531
+ !(tjpAddrs && tjpAddrs.length > 0)
532
+ ) {
533
+ throw new Error(`(UNEXPECTED) domainIbGibs and tjpAddrs falsy/empty? we need one or the other (E: f674285111c8648398cd79d8c08ec826)`);
534
+ }
535
+
536
+ if ((domainIbGibs && domainIbGibs.length > 0) &&
537
+ (tjpAddrs && tjpAddrs.length > 0)
538
+ ) {
539
+ throw new Error(`(UNEXPECTED) both domainIbGibs and tjpAddrs truthy? only pass in one or the other. (E: f674285111c8648398cd79d8c08ec826)`);
540
+ }
448
541
 
449
- if (tjps.length === 0) { return {}; }
542
+ let tjps: string[] = [];
543
+ if (tjpAddrs) {
544
+ tjps = tjpAddrs;
545
+ } else if (domainIbGibs && domainIbGibs.length > 0) {
546
+ // Extract TJPs from domain Ibgibs
547
+ // if (false) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
450
548
 
451
- const res = await getLatestAddrs({ space, tjpAddrs: tjps });
452
- if (!res.data || !res.data.latestAddrsMap) {
453
- throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
549
+ const { mapWithTjp_YesDna, mapWithTjp_NoDna } =
550
+ splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
551
+ if (false) { console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`); }
552
+ if (false) { console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`); }
553
+
554
+ const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
555
+ const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
556
+ if (false) { console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`); }
557
+
558
+ tjps = Object.keys(timelineMap);
559
+ if (false) { console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`); }
560
+ } else {
561
+ // No info provided. Return empty? Or throw?
562
+ // User test context implied "everything", but implementation requires scope.
563
+ console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
564
+ return {};
565
+ }
566
+
567
+ if (tjps.length === 0) { return {}; }
568
+
569
+ if (false) { console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`); }
570
+
571
+ const res = await getLatestAddrs({ space, tjpAddrs: tjps });
572
+ if (!res.data || !res.data.latestAddrsMap) {
573
+ throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
574
+ }
575
+
576
+ // if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
577
+
578
+ return res.data.latestAddrsMap;
579
+ } catch (error) {
580
+ console.error(`${lc} ${extractErrorMsg(error)}`);
581
+ throw error;
582
+ } finally {
583
+ if (logalot) { console.log(`${lc} complete.`); }
454
584
  }
455
- return res.data.latestAddrsMap;
456
585
  }
457
586
 
458
587
  protected async analyzeTimelines({
@@ -545,12 +674,17 @@ export class SyncSagaCoordinator {
545
674
  initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
546
675
  });
547
676
 
677
+ if (logalot) {
678
+ console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
679
+ console.log(`${lc} initData.stage: ${initData.stage}`);
680
+ }
681
+
548
682
  const initStone = await this.createSyncMsgStone({
549
683
  data: initData,
550
684
  space: tempSpace,
551
685
  metaspace
552
686
  });
553
- if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
687
+ // if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
554
688
 
555
689
  const sagaFrame = await this.evolveSyncSagaIbGib({
556
690
  msgStones: [initStone],
@@ -559,7 +693,16 @@ export class SyncSagaCoordinator {
559
693
  metaspace,
560
694
  conflictStrategy,
561
695
  });
562
- if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
696
+
697
+ // IMMEDIATELY persist to both spaces for audit trail
698
+ await this.ensureSagaFrameInBothSpaces({
699
+ frame: sagaFrame,
700
+ destSpace: localSpace, // localSpace is the Sender's destSpace
701
+ tempSpace,
702
+ metaspace
703
+ });
704
+
705
+ // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
563
706
 
564
707
  return { sagaFrame, srcGraph };
565
708
 
@@ -572,33 +715,83 @@ export class SyncSagaCoordinator {
572
715
  }
573
716
 
574
717
  /**
575
- * Reacts to an incoming saga frame and dispatches to appropriate handler.
576
- *
577
- * @remarks
578
- * **Execution Context**: **Universal (Both Sender and Receiver)**.
579
- *
580
- * This method acts as the "Reducer" for the Sync FSM. It determines the current stage
581
- * based on the incoming frame and delegates to the appropriate handler.
582
- *
583
- * * If running on **Receiver**: Handles `Init` (via `handleInitFrame`).
584
- * * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
585
- * * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
718
+ * Helper to poll for streaming domain payloads and put them in the
719
+ * local {@link tempSpace}.
586
720
  */
721
+ protected async pollForDomainPayloads({
722
+ expectedAddrs,
723
+ domainPayloadsMap,
724
+ tempSpace,
725
+ }: {
726
+ expectedAddrs: string[],
727
+ domainPayloadsMap: Map<string, IbGib_V1>,
728
+ tempSpace: IbGibSpaceAny,
729
+ }): Promise<void> {
730
+ const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
731
+ try {
732
+ if (logalot) { console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`); }
733
+
734
+ let pending = [...expectedAddrs];
735
+ const start = Date.now();
736
+ /**
737
+ * This needs
738
+ */
739
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
740
+
741
+ while (pending.length > 0) {
742
+ if (Date.now() - start > timeoutMs) {
743
+ throw new Error(`Timeout waiting for payloads: ${pending.join(', ')} (E: 46e1683c9578095261aaf798bd5e1826)`);
744
+ }
745
+
746
+ const stillPending: string[] = [];
747
+ const found: IbGib_V1[] = [];
748
+
749
+ for (const addr of pending) {
750
+ if (domainPayloadsMap.has(addr)) {
751
+ found.push(domainPayloadsMap.get(addr)!);
752
+ domainPayloadsMap.delete(addr);
753
+ } else {
754
+ stillPending.push(addr);
755
+ }
756
+ }
757
+
758
+ if (found.length > 0) {
759
+ await putInSpace({ space: tempSpace, ibGibs: found });
760
+ }
761
+
762
+ pending = stillPending;
763
+
764
+ if (pending.length > 0) {
765
+ await new Promise(resolve => setTimeout(resolve, 50));
766
+ }
767
+ }
768
+ } catch (error) {
769
+ console.error(`${lc} ${extractErrorMsg(error)}`);
770
+ throw error;
771
+ } finally {
772
+ if (logalot) { console.log(`${lc} complete.`); }
773
+ }
774
+ }
775
+
587
776
  async handleSagaFrame({
588
777
  sagaIbGib,
589
778
  srcGraph,
590
- space,
779
+ destSpace,
780
+ tempSpace,
591
781
  identity,
592
782
  identitySecret,
593
783
  metaspace,
594
784
  }: {
595
785
  sagaIbGib: SyncIbGib_V1,
596
786
  srcGraph: { [addr: string]: IbGib_V1 },
597
- space: IbGibSpaceAny,
787
+ destSpace: IbGibSpaceAny,
788
+ tempSpace: IbGibSpaceAny,
598
789
  identity?: KeystoneIbGib_V1,
599
790
  identitySecret?: string,
600
791
  metaspace: MetaspaceService,
601
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[], receivedPayloadIbGibs?: IbGib_V1[] } | null> {
792
+ domainPayloadsMap?: Map<string, IbGib_V1>,
793
+ expectedDomainAddrs?: string[],
794
+ }): Promise<HandleSagaFrameResult | null> {
602
795
  const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
603
796
  try {
604
797
  if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
@@ -607,25 +800,22 @@ export class SyncSagaCoordinator {
607
800
  if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
608
801
 
609
802
  // Get Stage from Stone (or Frame for Init fallback)
610
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
803
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
611
804
 
612
805
  if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
613
806
 
614
807
  switch (stage) {
615
808
  case SyncStage.init:
616
- return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, space, identity, identitySecret });
809
+ return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
617
810
 
618
811
  case SyncStage.ack:
619
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, space, identity });
812
+ return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
620
813
 
621
814
  case SyncStage.delta:
622
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, space, identity, });
815
+ return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
623
816
 
624
817
  case SyncStage.commit:
625
- return await this.handleCommitFrame({ sagaIbGib, metaspace, space, identity, });
626
-
627
- case SyncStage.conflict:
628
- return await this.handleConflictFrame({ sagaIbGib, metaspace, space, });
818
+ return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
629
819
 
630
820
  default:
631
821
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
@@ -655,19 +845,22 @@ export class SyncSagaCoordinator {
655
845
  protected async handleInitFrame({
656
846
  sagaIbGib,
657
847
  messageData,
658
- space,
848
+ destSpace,
849
+ tempSpace,
659
850
  metaspace,
660
851
  identity,
661
852
  identitySecret,
662
853
  }: {
663
854
  sagaIbGib: SyncIbGib_V1,
664
855
  messageData: any,
665
- space: IbGibSpaceAny,
856
+ destSpace: IbGibSpaceAny,
857
+ tempSpace: IbGibSpaceAny,
666
858
  metaspace: MetaspaceService,
667
859
  identity?: KeystoneIbGib_V1,
668
860
  identitySecret?: string,
669
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
861
+ }): Promise<HandleSagaFrameResult | null> {
670
862
  const lc = `${this.lc}[${this.handleInitFrame.name}]`;
863
+ console.log(`${lc} [TEST DEBUG] Received destSpace: ${destSpace.data?.name || destSpace.ib} (uuid: ${destSpace.data?.uuid || '[no uuid]'})`);
671
864
  if (logalot) { console.log(`${lc} starting...`); }
672
865
 
673
866
  // Extract Init Data
@@ -675,7 +868,7 @@ export class SyncSagaCoordinator {
675
868
  if (initData.stage !== SyncStage.init) {
676
869
  throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
677
870
  }
678
- if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
871
+ // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
679
872
  if (!initData || !initData.knowledgeVector) {
680
873
  throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
681
874
  }
@@ -694,7 +887,7 @@ export class SyncSagaCoordinator {
694
887
  if (stones.length > 0) {
695
888
  if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
696
889
  // Check if we have these stones
697
- const resStones = await getFromSpace({ space, addrs: stones });
890
+ const resStones = await getFromSpace({ space: destSpace, addrs: stones });
698
891
  const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
699
892
  if (addrsNotFound && addrsNotFound.length > 0) {
700
893
  if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
@@ -709,6 +902,7 @@ export class SyncSagaCoordinator {
709
902
  const remoteKV = initData.knowledgeVector;
710
903
  if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
711
904
  const remoteTjps = Object.keys(remoteKV);
905
+ console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
712
906
  if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
713
907
 
714
908
  // 1. Get Local Latest Addrs for all TJPs
@@ -716,12 +910,13 @@ export class SyncSagaCoordinator {
716
910
  if (remoteTjps.length > 0) {
717
911
  // Batch get latest addrs for the TJPs
718
912
  const resGetLatestAddrs = await getLatestAddrs({
719
- space,
913
+ space: destSpace,
720
914
  tjpAddrs: remoteTjps,
721
915
  });
722
916
  if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
723
917
  if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
724
918
  localKV = resGetLatestAddrs.data.latestAddrsMap;
919
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
725
920
  if (logalot) { console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`); }
726
921
  }
727
922
 
@@ -734,24 +929,28 @@ export class SyncSagaCoordinator {
734
929
 
735
930
  if (!localAddr) {
736
931
  // We (Receiver) don't have this timeline. Request it.
932
+ console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
737
933
  deltaReqAddrs.push(remoteAddr);
738
934
  continue;
739
935
  }
740
936
 
741
937
  if (localAddr === remoteAddr) {
742
938
  // Synced
939
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
743
940
  continue;
744
941
  }
942
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
745
943
 
746
944
  // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
747
945
  // (Sender has older version, Receiver has newer) -> Receiver Offers Push
748
946
  const isRemoteInPast = await isPastFrame({
749
947
  olderAddr: remoteAddr,
750
948
  newerAddr: localAddr,
751
- space,
949
+ space: destSpace,
752
950
  });
753
951
 
754
952
  if (isRemoteInPast) {
953
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
755
954
  pushOfferAddrs.push(localAddr);
756
955
  } else {
757
956
  // Remote is not in our past.
@@ -761,14 +960,16 @@ export class SyncSagaCoordinator {
761
960
  const isLocalInPast = await isPastFrame({
762
961
  olderAddr: localAddr,
763
962
  newerAddr: remoteAddr,
764
- space,
963
+ space: destSpace,
765
964
  });
766
965
 
767
966
  if (isLocalInPast) {
768
967
  // Fast-Forward: We update to remote's tip.
968
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
769
969
  deltaReqAddrs.push(remoteAddr);
770
970
  } else {
771
971
  // DIVERGENCE: Both have changes the other doesn't know about.
972
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
772
973
 
773
974
  if (conflictStrategy === 'abort') {
774
975
  // Abort Strategy: We will treat this as terminal.
@@ -795,7 +996,7 @@ export class SyncSagaCoordinator {
795
996
 
796
997
  // We need the ACTUAL object to get the past.
797
998
  // We have localAddr.
798
- const resLocalTip = await getFromSpace({ space, addr: localAddr! });
999
+ const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr! });
799
1000
  const localTip = resLocalTip.ibGibs?.[0];
800
1001
  if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`); }
801
1002
 
@@ -842,7 +1043,7 @@ export class SyncSagaCoordinator {
842
1043
  // 2. Add Push Offers (Missing in Local)
843
1044
  // Check if we have them. If not, ask for them.
844
1045
  for (const addr of pushOfferAddrs) {
845
- const hasIt = await getFromSpace({ addr, space });
1046
+ const hasIt = await getFromSpace({ addr, space: destSpace });
846
1047
  if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
847
1048
  // If we don't have it, we put it in `deltaReqAddrs` of the Ack.
848
1049
  deltaReqAddrs.push(addr);
@@ -861,7 +1062,7 @@ export class SyncSagaCoordinator {
861
1062
  for (const tjp of remoteTjps) {
862
1063
  const localAddr = localKV[tjp];
863
1064
  if (localAddr) {
864
- const res = await getFromSpace({ addr: localAddr, space });
1065
+ const res = await getFromSpace({ addr: localAddr, space: destSpace });
865
1066
  if (res.success && res.ibGibs?.[0]) {
866
1067
  const ibGib = res.ibGibs[0];
867
1068
  const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
@@ -879,7 +1080,7 @@ export class SyncSagaCoordinator {
879
1080
  // We can't really know if it's covered easily without resolving.
880
1081
  // But if we don't have it (requesting), we won't find it here anyway.
881
1082
  // If we DO have it (push offer), we might find it.
882
- const res = await getFromSpace({ addr, space });
1083
+ const res = await getFromSpace({ addr, space: destSpace });
883
1084
  if (res.success && res.ibGibs?.[0]) {
884
1085
  const ibGib = res.ibGibs[0];
885
1086
  const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
@@ -918,11 +1119,12 @@ export class SyncSagaCoordinator {
918
1119
  deltaReqAddrs,
919
1120
  pushOfferAddrs,
920
1121
  knowledgeVector,
1122
+ conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
921
1123
  };
922
1124
 
923
1125
  const ackStone = await this.createSyncMsgStone({
924
1126
  data: ackData,
925
- space,
1127
+ space: tempSpace,
926
1128
  metaspace,
927
1129
  });
928
1130
  if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
@@ -932,13 +1134,20 @@ export class SyncSagaCoordinator {
932
1134
  prevSagaIbGib: sagaIbGib,
933
1135
  msgStones: [ackStone],
934
1136
  identity,
935
- space,
1137
+ space: tempSpace,
936
1138
  metaspace,
937
1139
  });
938
1140
 
939
- if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
1141
+ // IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
1142
+ await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
1143
+
1144
+ // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
940
1145
 
941
- return { frame: ackFrame };
1146
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1147
+ const payloadIbGibsControl: IbGib_V1[] = [ackFrame, ackStone];
1148
+ if (identity) { payloadIbGibsControl.push(identity); }
1149
+
1150
+ return { frame: ackFrame, payloadIbGibsControl };
942
1151
  }
943
1152
 
944
1153
  /**
@@ -956,280 +1165,313 @@ export class SyncSagaCoordinator {
956
1165
  protected async handleAckFrame({
957
1166
  sagaIbGib,
958
1167
  srcGraph,
959
- space,
1168
+ destSpace,
1169
+ tempSpace,
960
1170
  metaspace,
961
1171
  identity,
962
1172
  }: {
963
1173
  sagaIbGib: SyncIbGib_V1,
964
1174
  srcGraph: { [addr: string]: IbGib_V1 },
965
- space: IbGibSpaceAny,
1175
+ destSpace: IbGibSpaceAny,
1176
+ tempSpace: IbGibSpaceAny,
966
1177
  metaspace: MetaspaceService,
967
1178
  identity?: KeystoneIbGib_V1,
968
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
1179
+ }): Promise<HandleSagaFrameResult | null> {
969
1180
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
970
- if (logalot) { console.log(`${lc} starting...`); }
1181
+ try {
1182
+ if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
971
1183
 
972
- const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
973
- const ackData = messageData as SyncSagaMessageAckData_V1;
1184
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1185
+ const ackData = messageData as SyncSagaMessageAckData_V1;
974
1186
 
975
- if (!ackData) {
976
- throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
977
- }
978
- if (ackData.stage !== SyncStage.ack) {
979
- throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
980
- }
981
- if (logalot) { console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`); }
982
-
983
- // 1. Check for Conflicts
984
- const conflicts = ackData.conflicts || [];
985
- const terminalConflicts = conflicts.filter(c => c.terminal);
986
- if (terminalConflicts.length > 0) {
987
- console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
988
- // Terminal failure. Sender should probably Commit(Fail) or just Abort.
989
- // For now, throw to trigger abort.
990
- throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
991
- }
1187
+ if (!ackData) {
1188
+ throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
1189
+ }
1190
+ if (ackData.stage !== SyncStage.ack) {
1191
+ throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
1192
+ }
1193
+ if (logalot) { console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`); }
992
1194
 
993
- const optimisticConflicts = conflicts.filter(c => !c.terminal);
994
- const mergeDeltaReqs: string[] = []; // Additional requests for merging
995
-
996
- if (optimisticConflicts.length > 0) {
997
- if (logalot) { console.log(`${lc} Processing optimistic conflicts: ${optimisticConflicts.length}`); }
998
- // We need to resolve these.
999
- // Strategy:
1000
- // 1. Analyze Divergence (Sender vs Receiver)
1001
- // 2. Identify missing data needed for merge (Receiver's unique frames)
1002
- // 3. Request that data (as Delta Reqs)
1003
- // 4. (Later in Delta Phase) Perform Merge.
1004
-
1005
- // BUT: The Delta Phase is usually generic "Send me these Addrs".
1006
- // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
1007
- // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
1008
-
1009
- // We (Sender) are processing the Ack.
1010
- // We need to request data FROM Receiver.
1011
- // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
1012
-
1013
- // If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
1014
- // Or can we send a 'Delta Request' frame?
1015
- // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
1016
-
1017
- // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
1018
- // Or we just proceed to Delta (sending what Receiver wants),
1019
- // AND we piggyback our own requests?
1020
- // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
1021
-
1022
- // SIMPLIFICATION for V1:
1023
- // If we need data to merge, we must get it.
1024
- // We are the Coordinator (Active). We can fetch from Peer immediately?
1025
- // `peer.pull(addr)`?
1026
- // Yes! The Coordinator has the `peer`.
1027
-
1028
- // Let's analyze and pull immediately.
1029
-
1030
- for (const conflict of optimisticConflicts) {
1031
- const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1032
-
1033
- // Sender History
1034
- // We need our own history for this timeline.
1035
- // We know the 'senderTip' (remoteAddr in Ack).
1036
- // Sender should verify it has this tip.
1037
-
1038
- // Compute Diffs
1039
- // We need to find `receiverOnly` addrs.
1040
- // Receiver sent us `timelineAddrs` (Full History).
1041
- const receiverHistorySet = new Set(timelineAddrs);
1042
-
1043
- // We need our execution context's history for this senderTip.
1044
- // We can fetch valid 'past' from space.
1045
- const resSenderTip = await getFromSpace({ space, addr: senderTip });
1046
- const senderTipIbGib = resSenderTip.ibGibs?.[0];
1047
- if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`); }
1048
-
1049
- // Basic Diff: Find what Receiver has that we don't.
1050
- // Actually, we need to traverse OUR past to find commonality.
1051
- const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1052
-
1053
- const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1054
-
1055
- if (receiverOnlyAddrs.length > 0) {
1056
- if (logalot) { console.log(`${lc} Pulling divergent history from Receiver: ${receiverOnlyAddrs.length} frames`); }
1057
- // PULL these frames from Peer into Local Space
1058
- // (Validation: We trust peer for now / verification happens on put)
1059
- for (const addr of receiverOnlyAddrs) {
1060
- // This 'pull' is a sync-peer method?
1061
- // The Coordinator 'peer' passed in 'sync()' might be needed here?
1062
- // Wait, `handleAckFrame` doesn't have reference to `peer`?
1063
- // It only has `space`, `metaspace`.
1064
- // The `peer` is held by the `executeSagaLoop`.
1065
-
1066
- // PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
1067
- // No, it's a method on Coordinator.
1068
- // But `executeSagaLoop` calls it.
1069
- // We might need to return "Requirements" to the loop?
1070
-
1071
- // Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
1072
- // It returns the NEXT frame (Delta).
1073
-
1074
- // If we need to fetch data, we are blocked.
1075
- // We can't easily "Pull" here without the Peer reference.
1076
-
1077
- // OPTION A: Pass `peer` to `handleAckFrame`.
1078
- // OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
1079
-
1080
- // Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
1081
- // No, Peer is ephemeral connection.
1082
-
1083
- // Let's add `peer` to `handleAckFrame` signature?
1084
- // It breaks the pattern of just handling frame + space.
1085
-
1086
- // ALTERNATIVE: Use the `Delta` frame to request data?
1087
- // `SyncSagaMessageDeltaData` has `requests?: string[]`.
1088
- // Sender sends Delta Frame.
1089
- // Does Receiver handle Delta Requests?
1090
- // `handleDeltaFrame` (Receiver) -> checks `requests`.
1091
- // YES.
1092
-
1093
- // So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
1094
- // Receiver sees them, fetches them, and includes them in the Response (Commit?).
1095
- // Wait, Init->Ack->Delta->Commit.
1096
- // If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
1097
- // Unless Commit is not the end?
1098
-
1099
- // Or we do a "Delta 2" loop?
1100
-
1101
- // "Iterative Resolution Loop" from plan.
1102
- // If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
1103
- // Sender gets Commit. Sees data. Merges.
1104
- // Then Sender needs to Send the MERGE result.
1105
- // Needs another Push/Delta.
1106
-
1107
- // REFINED FLOW:
1108
- // 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
1109
- // 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
1110
- // 3. Sender handles response -> Merges.
1111
- // 4. Sender sends Commit (containing Merge Frame).
1112
-
1113
- // Issue: Current state machine is Init->Ack->Delta->Commit.
1114
- // We need to keep Saga open.
1115
- // If Sender sends Delta with requests, does it transition to Commit?
1116
- }
1117
- mergeDeltaReqs.push(...receiverOnlyAddrs);
1118
- }
1195
+ // 1. Check for Conflicts
1196
+ const conflicts = ackData.conflicts || [];
1197
+ console.log(`${lc} [CONFLICT DEBUG] Received conflicts from Ack: ${conflicts.length}`);
1198
+ if (conflicts.length > 0) {
1199
+ console.log(`${lc} [CONFLICT DEBUG] Conflicts detail: ${JSON.stringify(conflicts, null, 2)}`);
1119
1200
  }
1120
- }
1121
1201
 
1122
- // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1202
+ const terminalConflicts = conflicts.filter(c => c.terminal);
1203
+ if (terminalConflicts.length > 0) {
1204
+ console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
1205
+ // Terminal failure. Sender should probably Commit(Fail) or just Abort.
1206
+ // For now, throw to trigger abort.
1207
+ throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
1208
+ }
1123
1209
 
1124
- const deltaReqAddrs = ackData.deltaReqAddrs || [];
1125
- const pushOfferAddrs = ackData.pushOfferAddrs || [];
1210
+ const optimisticConflicts = conflicts.filter(c => !c.terminal);
1211
+ const mergeDeltaReqs: string[] = []; // Additional requests for merging
1126
1212
 
1127
- // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1128
- const pullReqAddrs: string[] = [];
1129
- for (const addr of pushOfferAddrs) {
1130
- const existing = srcGraph[addr] || (await getFromSpace({ addr, space })).ibGibs?.[0];
1131
- if (!existing) {
1132
- pullReqAddrs.push(addr);
1133
- }
1134
- }
1213
+ if (optimisticConflicts.length > 0) {
1214
+ console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
1215
+ // We need to resolve these.
1216
+ // Strategy:
1217
+ // 1. Analyze Divergence (Sender vs Receiver)
1218
+ // 2. Identify missing data needed for merge (Receiver's unique frames)
1219
+ // 3. Request that data (as Delta Reqs)
1220
+ // 4. (Later in Delta Phase) Perform Merge.
1135
1221
 
1136
- // 2. Process Delta Requests (Push Payload)
1137
- // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1138
- const skipAddrs = new Set<string>();
1139
- if (ackData.knowledgeVector) {
1140
- Object.values(ackData.knowledgeVector).forEach(addrs => {
1141
- addrs.forEach(a => skipAddrs.add(a));
1142
- });
1143
- }
1222
+ // BUT: The Delta Phase is usually generic "Send me these Addrs".
1223
+ // If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
1224
+ // wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
1144
1225
 
1145
- const payloadIbGibs: IbGib_V1[] = [];
1146
- // Gather all tips to sync first
1147
- const tipsToSync: IbGib_V1[] = [];
1148
- for (const addr of deltaReqAddrs) {
1149
- let ibGib = srcGraph[addr];
1150
- if (!ibGib) {
1151
- const res = await getFromSpace({ addr, space });
1152
- if (res.ibGibs && res.ibGibs.length > 0) {
1153
- ibGib = res.ibGibs[0];
1226
+ // We (Sender) are processing the Ack.
1227
+ // We need to request data FROM Receiver.
1228
+ // But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
1229
+
1230
+ // If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
1231
+ // Or can we send a 'Delta Request' frame?
1232
+ // Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
1233
+
1234
+ // If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
1235
+ // Or we just proceed to Delta (sending what Receiver wants),
1236
+ // AND we piggyback our own requests?
1237
+ // OR: We treat the Conflict Resolution as a sub-saga or side-effect?
1238
+
1239
+ // SIMPLIFICATION for V1:
1240
+ // If we need data to merge, we must get it.
1241
+ // We are the Coordinator (Active). We can fetch from Peer immediately?
1242
+ // `peer.pull(addr)`?
1243
+ // Yes! The Coordinator has the `peer`.
1244
+
1245
+ // Let's analyze and pull immediately.
1246
+
1247
+ for (const conflict of optimisticConflicts) {
1248
+ const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
1249
+
1250
+ // Sender History
1251
+ // We need our own history for this timeline.
1252
+ // We know the 'senderTip' (remoteAddr in Ack).
1253
+ // Sender should verify it has this tip.
1254
+
1255
+ // Compute Diffs
1256
+ // We need to find `receiverOnly` addrs.
1257
+ // Receiver sent us `timelineAddrs` (Full History).
1258
+ const receiverHistorySet = new Set(timelineAddrs);
1259
+
1260
+ // We need our execution context's history for this senderTip.
1261
+ // We can fetch valid 'past' from space.
1262
+ const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
1263
+ const senderTipIbGib = resSenderTip.ibGibs?.[0];
1264
+ if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`); }
1265
+
1266
+ // Basic Diff: Find what Receiver has that we don't.
1267
+ // Actually, we need to traverse OUR past to find commonality.
1268
+ const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
1269
+
1270
+ const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
1271
+
1272
+ if (receiverOnlyAddrs.length > 0) {
1273
+ console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
1274
+ console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
1275
+ // PULL these frames from Peer into Local Space
1276
+ // (Validation: We trust peer for now / verification happens on put)
1277
+ for (const addr of receiverOnlyAddrs) {
1278
+ console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1279
+ }
1280
+
1281
+ // Compute DELTA dependencies for each receiver-only frame
1282
+ // Find LCA to determine what dependencies we already have
1283
+ const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
1284
+ console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
1285
+
1286
+ const skipAddrsSet = new Set<string>();
1287
+ if (lcaAddr) {
1288
+ try {
1289
+ const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
1290
+ const lcaIbGib = lcaRes.ibGibs?.[0];
1291
+ if (lcaIbGib) {
1292
+ const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
1293
+ if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
1294
+ console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
1295
+ }
1296
+ } catch (e) {
1297
+ console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
1298
+ }
1299
+ }
1300
+
1301
+
1302
+ // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
1303
+ for (const addr of receiverOnlyAddrs) {
1304
+ // Add the frame itself first
1305
+ if (!mergeDeltaReqs.includes(addr)) {
1306
+ mergeDeltaReqs.push(addr);
1307
+ }
1308
+
1309
+ // Get the frame's delta dependencies (skip LCA's deps)
1310
+ try {
1311
+ const frameRes = await getFromSpace({ addr, space: destSpace });
1312
+ const frameIbGib = frameRes.ibGibs?.[0];
1313
+
1314
+ if (frameIbGib) {
1315
+ // Get dependency graph, skipping all LCA dependencies
1316
+ const frameDeltaDeps = await getDependencyGraph({
1317
+ ibGib: frameIbGib,
1318
+ space: destSpace,
1319
+ skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
1320
+ });
1321
+
1322
+ if (frameDeltaDeps) {
1323
+ // Add all delta dependencies (Object.keys gives us the addresses)
1324
+ Object.keys(frameDeltaDeps).forEach(depAddr => {
1325
+ if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
1326
+ mergeDeltaReqs.push(depAddr);
1327
+ }
1328
+ });
1329
+ }
1330
+ }
1331
+ } catch (depError) {
1332
+ console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
1333
+ }
1334
+ }
1335
+
1336
+ console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
1337
+ } else {
1338
+ console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
1339
+ }
1154
1340
  }
1155
- }
1156
- if (ibGib) {
1157
- tipsToSync.push(ibGib);
1341
+
1342
+ console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
1158
1343
  } else {
1159
- throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1344
+ console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
1160
1345
  }
1161
- }
1162
1346
 
1163
- // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1164
- // Pass skipAddrs to `getDependencyGraph` or gather manually.
1165
- // `getDependencyGraph` takes a single ibGib.
1166
- // We can optimize by doing it for each tip and unioning the result?
1167
- // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1168
- // We will loop.
1169
-
1170
- const allDepsSet = new Set<string>();
1171
-
1172
- for (const tip of tipsToSync) {
1173
- // Always include the tip itself
1174
- const tipAddr = getIbGibAddr({ ibGib: tip });
1175
- // Only process if not skipped (though deltaReq implies they barely just asked for it)
1176
- // But detailed deps might be skipped.
1177
-
1178
- // Get Graph with Skips
1179
- // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
1180
- const deps = await getDependencyGraph({
1181
- ibGib: tip,
1182
- space,
1183
- skipAddrs: Array.from(skipAddrs)
1184
- });
1347
+ // 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
1185
1348
 
1186
- // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
1187
- let tipIncluded = false;
1349
+ const deltaReqAddrs = ackData.deltaReqAddrs || [];
1350
+ const pushOfferAddrs = ackData.pushOfferAddrs || [];
1188
1351
 
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) { tipIncluded = true; }
1352
+ // 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
1353
+ const pullReqAddrs: string[] = [];
1354
+ for (const addr of pushOfferAddrs) {
1355
+ const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
1356
+ if (!existing) {
1357
+ pullReqAddrs.push(addr);
1358
+ }
1359
+ }
1360
+
1361
+ // 2. Process Delta Requests (Push Payload)
1362
+ // [NEW] Smart Diff: Use knowledgeVector to skip dependencies
1363
+ const skipAddrs = new Set<string>();
1364
+ if (ackData.knowledgeVector) {
1365
+ Object.values(ackData.knowledgeVector).forEach(addrs => {
1366
+ addrs.forEach(a => skipAddrs.add(a));
1197
1367
  });
1198
1368
  }
1199
1369
 
1200
- if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1201
- if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
1202
- if (!allDepsSet.has(tipAddr)) {
1203
- allDepsSet.add(tipAddr);
1204
- payloadIbGibs.push(tip);
1370
+ const payloadIbGibs: IbGib_V1[] = [];
1371
+ // Gather all tips to sync first
1372
+ const tipsToSync: IbGib_V1[] = [];
1373
+ for (const addr of deltaReqAddrs) {
1374
+ let ibGib = srcGraph[addr];
1375
+ if (!ibGib) {
1376
+ const res = await getFromSpace({ addr, space: destSpace });
1377
+ if (res.ibGibs && res.ibGibs.length > 0) {
1378
+ ibGib = res.ibGibs[0];
1379
+ }
1380
+ }
1381
+ if (ibGib) {
1382
+ tipsToSync.push(ibGib);
1383
+ } else {
1384
+ throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
1205
1385
  }
1206
1386
  }
1207
- }
1208
1387
 
1209
- // 3. Create Delta Frame
1210
- const sagaId = ackData.sagaId;
1211
- const deltaData: SyncSagaMessageDeltaData_V1 = {
1212
- sagaId: sagaIbGib.data!.uuid,
1213
- stage: SyncStage.delta,
1214
- payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1215
- requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1216
- };
1388
+ // Calculate Dependency Graph for ALL tips, effectively utilizing common history
1389
+ // Pass skipAddrs to `getDependencyGraph` or gather manually.
1390
+ // `getDependencyGraph` takes a single ibGib.
1391
+ // We can optimize by doing it for each tip and unioning the result?
1392
+ // Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
1393
+ // We will loop.
1394
+
1395
+ const allDepsSet = new Set<string>();
1396
+
1397
+ for (const tip of tipsToSync) {
1398
+ // Always include the tip itself
1399
+ const tipAddr = getIbGibAddr({ ibGib: tip });
1400
+ // Only process if not skipped (though deltaReq implies they barely just asked for it)
1401
+ // But detailed deps might be skipped.
1402
+
1403
+ // Get Graph with Skips
1404
+ // Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
1405
+ const deps = await getDependencyGraph({
1406
+ ibGib: tip,
1407
+ space: destSpace,
1408
+ skipAddrs: Array.from(skipAddrs)
1409
+ });
1217
1410
 
1218
- const deltaStone = await this.createSyncMsgStone({
1219
- data: deltaData,
1220
- space,
1221
- metaspace,
1222
- });
1411
+ // [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
1412
+ let tipIncluded = false;
1223
1413
 
1224
- const deltaFrame = await this.evolveSyncSagaIbGib({
1225
- prevSagaIbGib: sagaIbGib,
1226
- msgStones: [deltaStone],
1227
- identity,
1228
- space,
1229
- metaspace,
1230
- });
1414
+ if (deps) {
1415
+ Object.values(deps).forEach(d => {
1416
+ const dAddr = getIbGibAddr({ ibGib: d });
1417
+ if (!allDepsSet.has(dAddr)) {
1418
+ allDepsSet.add(dAddr);
1419
+ payloadIbGibs.push(d);
1420
+ }
1421
+ if (dAddr === tipAddr) { tipIncluded = true; }
1422
+ });
1423
+ }
1424
+
1425
+ if (!tipIncluded && !skipAddrs.has(tipAddr)) {
1426
+ if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
1427
+ if (!allDepsSet.has(tipAddr)) {
1428
+ allDepsSet.add(tipAddr);
1429
+ payloadIbGibs.push(tip);
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ // 3. Create Delta Frame
1435
+ const sagaId = ackData.sagaId;
1436
+ const deltaData: SyncSagaMessageDeltaData_V1 = {
1437
+ sagaId: sagaIbGib.data!.uuid,
1438
+ stage: SyncStage.delta,
1439
+ payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
1440
+ requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
1441
+ };
1442
+
1443
+ if (logalot) { console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`); }
1444
+
1445
+ const deltaStone = await this.createSyncMsgStone({
1446
+ data: deltaData,
1447
+ space: tempSpace,
1448
+ metaspace,
1449
+ });
1231
1450
 
1232
- return { frame: deltaFrame, payloadIbGibs };
1451
+ const deltaFrame = await this.evolveSyncSagaIbGib({
1452
+ prevSagaIbGib: sagaIbGib,
1453
+ msgStones: [deltaStone],
1454
+ identity,
1455
+ space: tempSpace,
1456
+ metaspace,
1457
+ });
1458
+
1459
+ // IMMEDIATELY persist to both spaces for audit trail
1460
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1461
+
1462
+ if (logalot) { console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`); }
1463
+
1464
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1465
+ const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1466
+ if (identity) { payloadIbGibsControl.push(identity); }
1467
+
1468
+ return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1469
+ } catch (error) {
1470
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1471
+ throw error;
1472
+ } finally {
1473
+ if (logalot) { console.log(`${lc} complete.`); }
1474
+ }
1233
1475
  }
1234
1476
 
1235
1477
  /**
@@ -1245,20 +1487,22 @@ export class SyncSagaCoordinator {
1245
1487
  protected async handleDeltaFrame({
1246
1488
  sagaIbGib,
1247
1489
  srcGraph,
1248
- space,
1490
+ destSpace,
1491
+ tempSpace,
1249
1492
  metaspace,
1250
1493
  identity,
1251
1494
  }: {
1252
1495
  sagaIbGib: SyncIbGib_V1,
1253
1496
  srcGraph: { [addr: string]: IbGib_V1 },
1254
- space: IbGibSpaceAny,
1497
+ destSpace: IbGibSpaceAny,
1498
+ tempSpace: IbGibSpaceAny,
1255
1499
  metaspace: MetaspaceService,
1256
1500
  identity?: KeystoneIbGib_V1,
1257
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[], receivedPayloadIbGibs?: IbGib_V1[] } | null> {
1501
+ }): Promise<HandleSagaFrameResult | null> {
1258
1502
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1259
1503
  if (logalot) { console.log(`${lc} starting...`); }
1260
1504
 
1261
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
1505
+ const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1262
1506
  const deltaData = messageData as SyncSagaMessageDeltaData_V1;
1263
1507
 
1264
1508
  if (!deltaData) {
@@ -1269,6 +1513,8 @@ export class SyncSagaCoordinator {
1269
1513
  }
1270
1514
  if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`); }
1271
1515
 
1516
+ console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
1517
+
1272
1518
  const payloadAddrs = deltaData.payloadAddrs || [];
1273
1519
  const peerRequests = deltaData.requests || [];
1274
1520
  const peerProposesCommit = deltaData.proposeCommit || false;
@@ -1284,7 +1530,7 @@ export class SyncSagaCoordinator {
1284
1530
 
1285
1531
  const res = await getFromSpace({
1286
1532
  addrs: payloadAddrs,
1287
- space,
1533
+ space: tempSpace, // Incoming data is in tempSpace
1288
1534
  });
1289
1535
  if (res.ibGibs) {
1290
1536
  receivedPayloadIbGibs.push(...res.ibGibs);
@@ -1295,49 +1541,89 @@ export class SyncSagaCoordinator {
1295
1541
  }
1296
1542
  }
1297
1543
 
1298
- // 2. Fulfill Peer Requests (Outgoing Payload)
1544
+ // 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
1299
1545
  const outgoingPayload: IbGib_V1[] = [];
1546
+ const outgoingAddrsSet = new Set<string>(); // Track what we've added
1547
+
1548
+ console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
1549
+
1300
1550
  for (const addr of peerRequests) {
1551
+ // Get the requested ibGib
1301
1552
  let ibGib = srcGraph[addr];
1302
1553
  if (!ibGib) {
1303
- const res = await getFromSpace({ addr, space });
1554
+ const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
1304
1555
  if (res.ibGibs && res.ibGibs.length > 0) {
1305
1556
  ibGib = res.ibGibs[0];
1306
1557
  }
1307
1558
  }
1559
+
1308
1560
  if (ibGib) {
1309
- outgoingPayload.push(ibGib);
1561
+ // Add the requested ibGib itself
1562
+ const ibGibAddr = getIbGibAddr({ ibGib });
1563
+ if (!outgoingAddrsSet.has(ibGibAddr)) {
1564
+ outgoingPayload.push(ibGib);
1565
+ outgoingAddrsSet.add(ibGibAddr);
1566
+ }
1567
+
1568
+ // Expand to include full dependency graph for this ibGib
1569
+ // (Receiver needs all deps to properly process/merge)
1570
+ try {
1571
+ const deps = await getDependencyGraph({
1572
+ ibGib,
1573
+ space: destSpace,
1574
+ });
1575
+
1576
+ if (deps) {
1577
+ Object.values(deps).forEach(depIbGib => {
1578
+ const depAddr = getIbGibAddr({ ibGib: depIbGib });
1579
+ if (!outgoingAddrsSet.has(depAddr)) {
1580
+ outgoingPayload.push(depIbGib);
1581
+ outgoingAddrsSet.add(depAddr);
1582
+ }
1583
+ });
1584
+ }
1585
+ } catch (depError) {
1586
+ console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
1587
+ }
1310
1588
  } else {
1311
1589
  console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
1312
1590
  }
1313
1591
  }
1314
1592
 
1593
+ console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
1594
+
1595
+
1315
1596
  // 3. Execute Merges (If applicable)
1316
1597
  // Check if we have pending conflicts that we CAN resolve now that we have data.
1317
1598
  // We look at the Saga History (Ack Frame) to find conflicts.
1318
1599
  // Optimization: Do this only if we received payloads.
1319
1600
  const mergeResultIbGibs: IbGib_V1[] = [];
1320
1601
 
1602
+ console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
1603
+
1321
1604
  if (receivedPayloadIbGibs.length > 0) {
1605
+ console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
1322
1606
  // Find the Ack frame in history to get conflicts
1323
1607
  // Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
1324
1608
  // V1 timelines carry full history in `past`.
1325
1609
  const pastAddrs = sagaIbGib.rel8ns?.past || [];
1610
+ console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1326
1611
  let ackData: SyncSagaMessageAckData_V1 | undefined;
1327
1612
 
1328
1613
  if (pastAddrs.length > 0) {
1329
1614
  // Batch fetch all past frames
1330
- const resPast = await getFromSpace({ addrs: pastAddrs, space });
1615
+ const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
1331
1616
  if (resPast.success && resPast.ibGibs) {
1332
1617
  // Iterate backwards (most recent first) to find the latest Ack
1333
1618
  for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1334
1619
  const pastFrame = resPast.ibGibs[i];
1335
1620
  const messageStone = await getSyncSagaMessageFromFrame({
1336
1621
  frameIbGib: pastFrame,
1337
- space
1622
+ space: tempSpace
1338
1623
  });
1339
1624
  if (messageStone?.data?.stage === SyncStage.ack) {
1340
1625
  ackData = messageStone.data as SyncSagaMessageAckData_V1;
1626
+ console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1341
1627
  break;
1342
1628
  }
1343
1629
  }
@@ -1356,7 +1642,9 @@ export class SyncSagaCoordinator {
1356
1642
  // We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
1357
1643
 
1358
1644
  // Check if we have receiverTip in space
1359
- const resRecTip = await getFromSpace({ addr: receiverTip, space });
1645
+ console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
1646
+ const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
1647
+ console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
1360
1648
  if (resRecTip.success && resRecTip.ibGibs?.[0]) {
1361
1649
  // We have the tip!
1362
1650
  // Do we have the full history?
@@ -1366,12 +1654,13 @@ export class SyncSagaCoordinator {
1366
1654
  // Perform Merge!
1367
1655
  try {
1368
1656
  const mergeResult = await mergeDivergentTimelines({
1369
- tipA: (await getFromSpace({ addr: senderTip, space })).ibGibs![0], // Our tip
1370
- tipB: resRecTip.ibGibs[0], // Their tip
1371
- space,
1657
+ tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs![0], // Our tip from destSpace
1658
+ tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
1659
+ space: tempSpace, // Merge uses tempSpace
1372
1660
  metaspace,
1373
1661
  });
1374
1662
  if (mergeResult) {
1663
+ console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
1375
1664
  if (logalot) { console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`); }
1376
1665
  mergeResultIbGibs.push(mergeResult);
1377
1666
  outgoingPayload.push(mergeResult); // Send result to peer
@@ -1419,7 +1708,7 @@ export class SyncSagaCoordinator {
1419
1708
 
1420
1709
  const deltaStone = await this.createSyncMsgStone({
1421
1710
  data: responseDeltaData,
1422
- space,
1711
+ space: tempSpace,
1423
1712
  metaspace
1424
1713
  });
1425
1714
 
@@ -1427,11 +1716,18 @@ export class SyncSagaCoordinator {
1427
1716
  prevSagaIbGib: sagaIbGib,
1428
1717
  msgStones: [deltaStone],
1429
1718
  identity,
1430
- space,
1719
+ space: tempSpace,
1431
1720
  metaspace
1432
1721
  });
1433
1722
 
1434
- return { frame: deltaFrame, payloadIbGibs: outgoingPayload, receivedPayloadIbGibs };
1723
+ // IMMEDIATELY persist to both spaces for audit trail
1724
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1725
+
1726
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1727
+ const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1728
+ if (identity) { payloadIbGibsControl.push(identity); }
1729
+
1730
+ return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1435
1731
 
1436
1732
  } else {
1437
1733
  // We have nothing to send.
@@ -1446,7 +1742,7 @@ export class SyncSagaCoordinator {
1446
1742
 
1447
1743
  const commitStone = await this.createSyncMsgStone({
1448
1744
  data: commitData,
1449
- space,
1745
+ space: tempSpace,
1450
1746
  metaspace
1451
1747
  });
1452
1748
 
@@ -1454,11 +1750,18 @@ export class SyncSagaCoordinator {
1454
1750
  prevSagaIbGib: sagaIbGib,
1455
1751
  msgStones: [commitStone],
1456
1752
  identity,
1457
- space,
1753
+ space: tempSpace,
1458
1754
  metaspace
1459
1755
  });
1460
1756
 
1461
- return { frame: commitFrame, receivedPayloadIbGibs };
1757
+ // IMMEDIATELY persist to both spaces for audit trail
1758
+ await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1759
+
1760
+ // Build control payloads for commit
1761
+ const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
1762
+ if (identity) { commitCtrlPayloads.push(identity); }
1763
+
1764
+ return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1462
1765
 
1463
1766
  } else {
1464
1767
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1473,7 +1776,7 @@ export class SyncSagaCoordinator {
1473
1776
 
1474
1777
  const deltaStone = await this.createSyncMsgStone({
1475
1778
  data: responseDeltaData,
1476
- space,
1779
+ space: tempSpace,
1477
1780
  metaspace
1478
1781
  });
1479
1782
 
@@ -1481,10 +1784,13 @@ export class SyncSagaCoordinator {
1481
1784
  prevSagaIbGib: sagaIbGib,
1482
1785
  msgStones: [deltaStone],
1483
1786
  identity,
1484
- space,
1787
+ space: tempSpace,
1485
1788
  metaspace
1486
1789
  });
1487
1790
 
1791
+ // IMMEDIATELY persist to both spaces for audit trail
1792
+ await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1793
+
1488
1794
  // Check if PEER proposed commit
1489
1795
  if (deltaData.proposeCommit) {
1490
1796
  if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
@@ -1499,7 +1805,7 @@ export class SyncSagaCoordinator {
1499
1805
 
1500
1806
  const commitStone = await this.createSyncMsgStone({
1501
1807
  data: commitData,
1502
- space,
1808
+ space: tempSpace,
1503
1809
  metaspace
1504
1810
  });
1505
1811
 
@@ -1507,14 +1813,25 @@ export class SyncSagaCoordinator {
1507
1813
  prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1508
1814
  msgStones: [commitStone],
1509
1815
  identity,
1510
- space,
1816
+ space: tempSpace,
1511
1817
  metaspace
1512
1818
  });
1513
1819
 
1514
- return { frame: commitFrame, receivedPayloadIbGibs };
1820
+ // IMMEDIATELY persist to both spaces for audit trail
1821
+ await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1822
+
1823
+ // Build control payloads for commit
1824
+ const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
1825
+ if (identity) { commitCtrlPayloads2.push(identity); }
1826
+
1827
+ return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1515
1828
  }
1516
1829
 
1517
- return { frame: deltaFrame, receivedPayloadIbGibs };
1830
+ // Build control payloads for delta propose
1831
+ const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
1832
+ if (identity) { deltaCtrlPayloads.push(identity); }
1833
+
1834
+ return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1518
1835
  }
1519
1836
  }
1520
1837
  }
@@ -1522,14 +1839,17 @@ export class SyncSagaCoordinator {
1522
1839
 
1523
1840
  protected async handleCommitFrame({
1524
1841
  sagaIbGib,
1525
- space,
1842
+ destSpace,
1843
+ tempSpace,
1526
1844
  metaspace,
1845
+ identity,
1527
1846
  }: {
1528
1847
  sagaIbGib: SyncIbGib_V1,
1529
- space: IbGibSpaceAny,
1848
+ destSpace: IbGibSpaceAny,
1849
+ tempSpace: IbGibSpaceAny,
1530
1850
  metaspace: MetaspaceService,
1531
1851
  identity?: KeystoneIbGib_V1,
1532
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
1852
+ }): Promise<HandleSagaFrameResult | null> {
1533
1853
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1534
1854
  if (logalot) { console.log(`${lc} Commit received.`); }
1535
1855
 
@@ -1547,29 +1867,6 @@ export class SyncSagaCoordinator {
1547
1867
  return null;
1548
1868
  }
1549
1869
 
1550
- protected async handleConflictFrame({
1551
- sagaIbGib,
1552
- metaspace,
1553
- space,
1554
- }: {
1555
- sagaIbGib: SyncIbGib_V1,
1556
- metaspace: MetaspaceService,
1557
- space: IbGibSpaceAny,
1558
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
1559
- const lc = `${this.lc}[${this.handleConflictFrame.name}]`;
1560
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
1561
- const conflictData = messageData as SyncSagaMessageConflictData_V1;
1562
-
1563
- if (logalot) { console.log(`${lc} Conflict received. Strategy: ${conflictData?.conflictStrategy}. Terminal: ${conflictData?.isTerminal}`); }
1564
-
1565
- if (conflictData?.isTerminal) {
1566
- throw new Error(`${lc} Saga aborted due to conflicts: ${JSON.stringify(conflictData.conflicts)} (E: b08d1f2a3c4e5d6f7a8b9c0d1e2f3a4b)`);
1567
- }
1568
-
1569
- // Non-terminal logic (stub for future)
1570
- return null;
1571
- }
1572
-
1573
1870
  // #endregion Handlers
1574
1871
 
1575
1872
  protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
@@ -1605,6 +1902,50 @@ export class SyncSagaCoordinator {
1605
1902
  }
1606
1903
 
1607
1904
 
1905
+ /**
1906
+ * Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
1907
+ * Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
1908
+ */
1909
+ protected async ensureSagaFrameInBothSpaces({
1910
+ frame,
1911
+ destSpace,
1912
+ tempSpace,
1913
+ metaspace,
1914
+ }: {
1915
+ frame: SyncIbGib_V1,
1916
+ destSpace: IbGibSpaceAny,
1917
+ tempSpace: IbGibSpaceAny,
1918
+ metaspace: MetaspaceService,
1919
+ }): Promise<void> {
1920
+ // Frame itself (already in tempSpace from creation, need in destSpace for audit)
1921
+ await putInSpace({ space: destSpace, ibGib: frame });
1922
+ await metaspace.registerNewIbGib({ ibGib: frame });
1923
+
1924
+ // Msg stone(s) (already in tempSpace, need in destSpace for audit)
1925
+ const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1926
+ if (msgStoneAddrs && msgStoneAddrs.length > 0) {
1927
+ const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
1928
+ if (resMsgStones.ibGibs) {
1929
+ for (const msgStone of resMsgStones.ibGibs) {
1930
+ await putInSpace({ space: destSpace, ibGib: msgStone });
1931
+ await metaspace.registerNewIbGib({ ibGib: msgStone });
1932
+ }
1933
+ }
1934
+ }
1935
+
1936
+ // Identity (if present, already in tempSpace, need in destSpace)
1937
+ const identityAddrs = frame.rel8ns?.identity;
1938
+ if (identityAddrs && identityAddrs.length > 0) {
1939
+ const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
1940
+ if (resIdentity.ibGibs) {
1941
+ for (const identity of resIdentity.ibGibs) {
1942
+ await putInSpace({ space: destSpace, ibGib: identity });
1943
+ await metaspace.registerNewIbGib({ ibGib: identity });
1944
+ }
1945
+ }
1946
+ }
1947
+ }
1948
+
1608
1949
  /**
1609
1950
  * Evolves the saga timeline with a new frame.
1610
1951
  */