@ibgib/core-gib 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/common/other/graph-helper.d.mts +8 -0
  2. package/dist/common/other/graph-helper.d.mts.map +1 -1
  3. package/dist/common/other/graph-helper.mjs +31 -1
  4. package/dist/common/other/graph-helper.mjs.map +1 -1
  5. package/dist/sync/sync-conflict.respec.mjs +31 -28
  6. package/dist/sync/sync-conflict.respec.mjs.map +1 -1
  7. package/dist/sync/sync-constants.d.mts +21 -11
  8. package/dist/sync/sync-constants.d.mts.map +1 -1
  9. package/dist/sync/sync-constants.mjs +18 -6
  10. package/dist/sync/sync-constants.mjs.map +1 -1
  11. package/dist/sync/sync-helpers.d.mts +24 -15
  12. package/dist/sync/sync-helpers.d.mts.map +1 -1
  13. package/dist/sync/sync-helpers.mjs +163 -92
  14. package/dist/sync/sync-helpers.mjs.map +1 -1
  15. package/dist/sync/sync-innerspace-constants.respec.mjs +12 -7
  16. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  17. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +12 -7
  18. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  19. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +15 -10
  20. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  21. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +13 -7
  22. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  23. package/dist/sync/sync-innerspace-partial-update.respec.mjs +15 -7
  24. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  25. package/dist/sync/sync-innerspace.respec.mjs +13 -7
  26. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  27. package/dist/sync/sync-peer/sync-peer-constants.d.mts +2 -0
  28. package/dist/sync/sync-peer/sync-peer-constants.d.mts.map +1 -0
  29. package/dist/sync/sync-peer/sync-peer-constants.mjs +2 -0
  30. package/dist/sync/sync-peer/sync-peer-constants.mjs.map +1 -0
  31. package/dist/sync/sync-peer/sync-peer-helpers.d.mts +2 -0
  32. package/dist/sync/sync-peer/sync-peer-helpers.d.mts.map +1 -0
  33. package/dist/sync/sync-peer/sync-peer-helpers.mjs +2 -0
  34. package/dist/sync/sync-peer/sync-peer-helpers.mjs.map +1 -0
  35. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts +8 -0
  36. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts.map +1 -0
  37. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs +8 -0
  38. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs.map +1 -0
  39. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.d.mts +18 -0
  40. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.d.mts.map +1 -0
  41. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mjs +54 -0
  42. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mjs.map +1 -0
  43. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +65 -0
  44. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -0
  45. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mjs +5 -0
  46. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mjs.map +1 -0
  47. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +44 -0
  48. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -0
  49. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +183 -0
  50. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -0
  51. package/dist/sync/sync-peer/sync-peer-types.d.mts +28 -0
  52. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  53. package/dist/sync/sync-peer/sync-peer-v1.d.mts +51 -9
  54. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  55. package/dist/sync/sync-peer/sync-peer-v1.mjs +244 -26
  56. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  57. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +26 -10
  58. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  59. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +119 -30
  60. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  61. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +31 -22
  62. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  63. package/dist/sync/sync-saga-context/sync-saga-context-types.mjs +1 -9
  64. package/dist/sync/sync-saga-context/sync-saga-context-types.mjs.map +1 -1
  65. package/dist/sync/sync-saga-coordinator.d.mts +81 -87
  66. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  67. package/dist/sync/sync-saga-coordinator.mjs +627 -571
  68. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  69. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +105 -22
  70. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  71. package/dist/sync/sync-types.d.mts +56 -76
  72. package/dist/sync/sync-types.d.mts.map +1 -1
  73. package/dist/sync/sync-types.mjs +5 -0
  74. package/dist/sync/sync-types.mjs.map +1 -1
  75. package/dist/timeline/timeline-api.d.mts.map +1 -1
  76. package/dist/timeline/timeline-api.mjs +15 -8
  77. package/dist/timeline/timeline-api.mjs.map +1 -1
  78. package/dist/witness/light-witness-base-v1.d.mts.map +1 -1
  79. package/dist/witness/light-witness-base-v1.mjs +2 -0
  80. package/dist/witness/light-witness-base-v1.mjs.map +1 -1
  81. package/dist/witness/space/inner-space/inner-space-v1.mjs +1 -1
  82. package/dist/witness/space/inner-space/inner-space-v1.mjs.map +1 -1
  83. package/package.json +1 -1
  84. package/src/common/other/graph-helper.mts +26 -1
  85. package/src/sync/README.md +31 -22
  86. package/src/sync/sync-conflict.respec.mts +31 -26
  87. package/src/sync/sync-constants.mts +19 -9
  88. package/src/sync/sync-helpers.mts +173 -97
  89. package/src/sync/sync-innerspace-constants.respec.mts +12 -7
  90. package/src/sync/sync-innerspace-deep-updates.respec.mts +12 -7
  91. package/src/sync/sync-innerspace-dest-ahead.respec.mts +14 -9
  92. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +13 -7
  93. package/src/sync/sync-innerspace-partial-update.respec.mts +15 -7
  94. package/src/sync/sync-innerspace.respec.mts +13 -7
  95. package/src/sync/sync-peer/sync-peer-constants.mts +0 -0
  96. package/src/sync/sync-peer/sync-peer-helpers.mts +0 -0
  97. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mts +8 -0
  98. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-helpers.mts +72 -0
  99. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +72 -0
  100. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +193 -0
  101. package/src/sync/sync-peer/sync-peer-types.mts +30 -1
  102. package/src/sync/sync-peer/sync-peer-v1.mts +229 -30
  103. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +140 -43
  104. package/src/sync/sync-saga-context/sync-saga-context-types.mts +34 -30
  105. package/src/sync/sync-saga-coordinator.mts +678 -660
  106. package/src/sync/sync-saga-message/sync-saga-message-types.mts +106 -22
  107. package/src/sync/sync-types.mts +59 -87
  108. package/src/timeline/timeline-api.mts +17 -10
  109. package/src/witness/light-witness-base-v1.mts +2 -1
  110. package/src/witness/space/inner-space/inner-space-v1.mts +1 -1
  111. package/test_output.log +0 -0
  112. package/tmp.md +62 -44
  113. package/dist/sync/sync-local-spaces.respec.d.mts +0 -2
  114. package/dist/sync/sync-local-spaces.respec.d.mts.map +0 -1
  115. package/dist/sync/sync-local-spaces.respec.mjs +0 -159
  116. package/dist/sync/sync-local-spaces.respec.mjs.map +0 -1
  117. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts +0 -42
  118. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts.map +0 -1
  119. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs +0 -194
  120. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs.map +0 -1
  121. package/dist/sync/sync-saga-coordinator.respec.d.mts +0 -2
  122. package/dist/sync/sync-saga-coordinator.respec.d.mts.map +0 -1
  123. package/dist/sync/sync-saga-coordinator.respec.mjs +0 -40
  124. package/dist/sync/sync-saga-coordinator.respec.mjs.map +0 -1
  125. package/src/sync/sync-local-spaces.respec.mts +0 -200
  126. package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +0 -240
  127. package/src/sync/sync-saga-coordinator.respec.mts +0 -52
@@ -4,13 +4,14 @@ 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,
9
+ delay,
8
10
  } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
11
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
10
- import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from "../common/other/ibgib-helper.mjs";
11
12
  import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
12
- import { IbGib_V1, IbGibRel8ns_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
13
- import { isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
13
+ import { IbGib_V1, } from "@ibgib/ts-gib/dist/V1/types.mjs";
14
+ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
14
15
 
15
16
  import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
16
17
  import { IbGibSpaceAny } from "../witness/space/space-base-v1.mjs";
@@ -18,35 +19,42 @@ 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 { appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
22
+ import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
23
+ import { appendToTimeline, createTimeline, getHistory, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
23
24
  import {
24
- SyncData_V1, SyncIbGib_V1, SyncInitData, SyncConflictStrategy,
25
- SyncMode,
26
- SyncOptions,
25
+ SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
26
+ SyncRel8ns_V1, DomainIbGibAnalysisInfo, HandleSagaFrameResult,
27
27
  } from "./sync-types.mjs";
28
- import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
29
- import { getSyncSagaDependencyGraph } from "./sync-helpers.mjs";
30
- import { getDependencyGraph } from "../common/other/graph-helper.mjs";
28
+ import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
29
+ import { getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
31
30
  import {
32
31
  SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
33
32
  SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
34
- SyncSagaMessageCommitData_V1
33
+ SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
35
34
  } from "./sync-saga-message/sync-saga-message-types.mjs";
36
35
  import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
37
36
  import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
38
37
  import { SyncSagaInfo } from "./sync-types.mjs";
38
+ import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
39
39
  import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
40
- import { SyncSagaContextIbGib_V1, SyncSagaContextCmd } from "./sync-saga-context/sync-saga-context-types.mjs";
40
+ import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
41
41
  import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
42
- import { newupSubject } from "../common/pubsub/subject/subject-helper.mjs";
42
+ import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
43
43
  import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
44
-
45
44
  import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
46
45
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
46
+ import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
47
+ import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
48
+ import {
49
+ IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
50
+ } from "../witness/space/space-types.mjs";
51
+ import { FlatIbGibGraph } from "../common/other/graph-types.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]';
50
58
 
51
59
  /**
52
60
  * Orchestrates the synchronization process between two spaces (Source and Destination).
@@ -85,18 +93,16 @@ export class SyncSagaCoordinator {
85
93
  */
86
94
  async sync({
87
95
  peer,
88
- localSpace: _localSpace,
89
- source: _source,
90
- metaspace,
91
96
  domainIbGibs,
92
- conflictStrategy = 'abort',
97
+ conflictStrategy = SyncConflictStrategy.abort,
93
98
  useSessionIdentity = true,
99
+ metaspace,
100
+ localSpace,
94
101
  }: SyncOptions): Promise<SyncSagaInfo> {
95
102
  const lc = `${this.lc}[${this.sync.name}]`;
96
103
  if (logalot) { console.log(`${lc} starting...`); }
97
104
 
98
- const localSpace = (_source || _localSpace)!;
99
- if (!localSpace) { throw new Error(`${lc} source (or localSpace) required (E: 8a9b0c1d)`); }
105
+ if (!localSpace) { throw new Error(`${lc} source (or localSpace) required (E: 25df3761f7686a1099a552f83c95d326)`); }
100
106
 
101
107
  // 1. SETUP SAGA METADATA
102
108
  const sagaId = await getUUID();
@@ -112,11 +118,6 @@ export class SyncSagaCoordinator {
112
118
  rejectDone = reject;
113
119
  });
114
120
 
115
- async function getTempSpaceName(): Promise<string> {
116
- const uuid = await getUUID();
117
- return `tmp_sync_space_${uuid.substring(0, 8)}`;
118
- }
119
-
120
121
  // WORKING CONTEXT (Transactional)
121
122
  const tempSpaceName = await getTempSpaceName();
122
123
  const tempSpace = await metaspace.createNewLocalSpace({
@@ -146,33 +147,25 @@ export class SyncSagaCoordinator {
146
147
  // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
147
148
 
148
149
  // 3. CREATE INITIAL FRAME (Stage.init)
149
- const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
150
+ const { sagaFrame: initFrame, initialDomainGraph } = await this.createInitFrame({
150
151
  sagaId,
151
152
  sessionIdentity,
152
- localSpace,
153
153
  domainIbGibs,
154
- tempSpace,
155
- metaspace,
156
- conflictStrategy
157
- });
158
-
159
- // 4. EXECUTE SAGA LOOP (FSM)
160
- const syncedIbGibs = await this.executeSagaLoop({
161
- initialFrame: initFrame,
162
- srcGraph,
163
- peer,
164
- sessionIdentity,
165
- updates$,
166
- localSpace,
167
- tempSpace,
168
- metaspace
154
+ conflictStrategy,
155
+ metaspace, localSpace, tempSpace,
169
156
  });
170
157
 
171
- // 5. MERGE RESULT (Optimistic Commit)
172
- if (syncedIbGibs && syncedIbGibs.length > 0) {
173
- if (logalot) { console.log(`${lc} Merging ${syncedIbGibs.length} synced ibGibs to localSpace...`); }
174
- await putInSpace({ space: localSpace, ibGibs: syncedIbGibs });
175
- }
158
+ // 4. KICK OFF THE PING-PONG SAGA LOOP (FSM)
159
+ // commented to compile
160
+ // await this.executeSagaLoop({
161
+ // initialFrame: initFrame,
162
+ // peer,
163
+ // sessionIdentity,
164
+ // updates$,
165
+ // localSpace,
166
+ // tempSpace,
167
+ // metaspace
168
+ // });
176
169
 
177
170
  resolveDone();
178
171
  if (!updates$.complete) { throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`); }
@@ -238,17 +231,17 @@ export class SyncSagaCoordinator {
238
231
  * **Execution Context**: **Sender (Local)**.
239
232
  *
240
233
  * This method manages the "Ping Pong" request-response cycle on the Sender.
241
- * It sends frames via the Peer Witness and processes the responses using `handleSagaFrame`.
234
+ * It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
242
235
  *
243
236
  * **Data Transport Note**:
244
237
  * Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
245
- * When `handleSagaFrame` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
238
+ * When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
246
239
  * the NEXT request context.
247
240
  * When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
248
241
  */
249
242
  protected async executeSagaLoop({
250
243
  initialFrame,
251
- srcGraph,
244
+ initialDomainGraph,
252
245
  peer,
253
246
  sessionIdentity,
254
247
  updates$,
@@ -257,161 +250,190 @@ export class SyncSagaCoordinator {
257
250
  metaspace
258
251
  }: {
259
252
  initialFrame: SyncIbGib_V1,
260
- srcGraph: { [addr: string]: IbGib_V1 },
253
+ initialDomainGraph: FlatIbGibGraph,
261
254
  peer: SyncPeerWitness,
262
255
  sessionIdentity?: KeystoneIbGib_V1,
263
- // updates$: Subject_V1<SyncSagaContextIbGib_V1>,
264
256
  updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
257
+ metaspace: MetaspaceService
265
258
  localSpace: IbGibSpaceAny,
266
259
  tempSpace: IbGibSpaceAny,
267
- metaspace: MetaspaceService
268
- }): Promise<IbGib_V1[]> {
260
+ }): Promise<void> {
269
261
  const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
270
262
 
271
263
  // The current frame we just generated (e.g., Init or Delta Request)
272
264
  let currentFrame: SyncIbGib_V1 | null = initialFrame;
273
265
  // The payload we need to attach to the message (data we are sending)
274
- let nextPayloadIbGibs: IbGib_V1[] = [];
275
- // Accumulator for all data we've successfully pulled from the remote
276
- const allReceivedIbGibs: IbGib_V1[] = [];
266
+ let nextDomainIbGibs: IbGib_V1[] = [];
267
+
268
+ // First, inject local/tempSpace into peer so it can pull as
269
+ // needed...code smell?
270
+ peer.senderSpace = localSpace;
271
+ peer.senderTempSpace = tempSpace;
277
272
 
278
273
  while (currentFrame) {
279
274
  // A. Create Context (Request)
280
275
  // 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[] = [];
276
+ // We must do this BEFORE creating the Context so we can list them
277
+ // all in payloadAddrsDomain and payloadAddrsControl.
278
+ const nextDomainIbGibsAndDeps: IbGib_V1[] = [];
283
279
 
284
280
  // 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 });
281
+ // TODO: THIS IS EXTREMELY INEFFICIENT. adjust this algorithm to
282
+ // only do the dependencies that the other end doesn't need (diff
283
+ // between tip and LCA). This can be done using the information in
284
+ // the ack's conflicts array.
285
+ for (const nextDomainIbGib of nextDomainIbGibs) {
286
+ let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
287
+ if (!nextDomainIbGibGraph) {
288
+ nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
289
289
  }
290
-
291
- if (graph) {
292
- allDeps.push(...Object.values(graph));
290
+ if (nextDomainIbGibGraph) {
291
+ nextDomainIbGibsAndDeps.push(...Object.values(nextDomainIbGibGraph));
293
292
  } else {
294
- allDeps.push(item);
293
+ throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
295
294
  }
296
295
  }
297
- if (logalot) {
298
- // console.log(`${lc} allDeps count: ${allDeps.length}`);
299
- }
300
-
301
- // B. Frames (Shallow Sync Deps)
302
- if (currentFrame) {
303
- const deps = await getSyncSagaDependencyGraph({ ibGib: currentFrame, space: tempSpace });
304
- if (deps) allDeps.push(...deps);
305
- }
296
+ if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${nextDomainIbGibsAndDeps.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`); }
306
297
 
307
298
  // 2. Create Context (Envelope)
308
- // Use the FULL dependency list as the payload manifest
309
- const payloadAddrs = allDeps.map(p => getIbGibAddr({ ibGib: p }));
310
-
299
+ // set up subscription for response's payload ibgibs (if any)
300
+ const domainPayloadsMap = new Map<string, IbGib_V1>();
301
+ const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
302
+ const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
303
+ next: async (ibgib: IbGib_V1) => {
304
+ if (logalot) { console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`); }
305
+ const addr = getIbGibAddr({ ibGib: ibgib });
306
+ if (logalotControlDomain) { console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`); }
307
+ domainPayloadsMap.set(addr, ibgib);
308
+ },
309
+ error: async (e: string | Error | ErrorIbGib_V1) => {
310
+ if (isIbGib(e)) {
311
+ console.error(`${sublc} error fired. error: ${JSON.stringify((e as IbGib_V1).data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
312
+ } else {
313
+ console.dir(e);
314
+ console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
315
+ }
316
+ },
317
+ complete: async () => {
318
+ if (logalot) { console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`); }
319
+ await subscription.unsubscribe();
320
+ },
321
+ }));
322
+
323
+ // Create the Request Context...
311
324
  const requestCtx = await createSyncSagaContext({
312
- cmd: SyncSagaContextCmd.process,
313
325
  sagaFrame: currentFrame,
314
326
  sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
315
- payloadAddrs: payloadAddrs.length > 0 ? payloadAddrs : undefined,
316
- });
317
327
 
318
- // Add Context Deps
319
- if (requestCtx) {
320
- const deps = await getSyncSagaDependencyGraph({ ibGib: requestCtx, space: tempSpace });
321
- if (deps) allDeps.push(...deps);
322
- }
323
-
324
- // 3. Identity (if exists)
325
- // Identity might be deep? Keystone? Usually self-contained or shallow references.
326
- if (sessionIdentity) {
327
- allDeps.push(sessionIdentity);
328
- }
328
+ /**
329
+ * init frame: empty
330
+ *
331
+ */
332
+ payloadIbGibsDomain: nextDomainIbGibsAndDeps,
333
+ localSpace,
334
+ });
329
335
 
330
- if (allDeps.length > 0) {
331
- await putInSpace({
332
- space: localSpace,
333
- ibGibs: allDeps
334
- });
336
+ // Log what we're sending
337
+ if (logalotControlDomain) {
338
+ const domainAddrs = nextDomainIbGibsAndDeps.map(p => getIbGibAddr({ ibGib: p }));
339
+ console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
340
+ console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
341
+ console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
342
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
335
343
  }
336
344
 
337
- // B. Transmit
345
+ // update our saga listeners...
338
346
  // if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
339
- updates$.next(requestCtx);
347
+ updates$.next(requestCtx); // spins off
348
+
349
+ // ...And send the context
340
350
  const responseCtx = await peer.witness(requestCtx);
341
351
 
342
352
  // C. Handle Response
343
353
  if (!responseCtx) {
344
- // Check if we just sent a Commit frame. If so, peer's silence is success/expected.
345
354
  if (currentFrame) {
355
+ // Check for Commit (Peer silence expected)
346
356
  const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
347
- if (logalot) { console.log(`${lc} Checking currentFrame stage: ${msg?.data?.stage} (Expected: ${SyncStage.commit})`); }
348
357
  if (msg?.data?.stage === SyncStage.commit) {
349
358
  if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`); }
350
359
  currentFrame = null;
351
360
  break;
361
+ } else {
362
+ 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
363
  }
364
+ } else {
365
+ throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
353
366
  }
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
367
  }
360
368
 
361
- if (logalot) {
362
- console.log(`${lc} received responseCtx: ${pretty(responseCtx)} (I: 8f35c8b7a9e886aa9743d38bee907826)`);
363
- }
364
-
365
- updates$.next(responseCtx);
366
-
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)`);
369
+ // ---------------------------------------------------------------------
370
+ // 2d. HANDLE RESPONSE
371
+ // ---------------------------------------------------------------------
372
+ // at this point, we have received the response context ibgib, but
373
+ // if there were payloads expected to be transferred, they may not
374
+ // be done yet.
375
+ if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
376
+ updates$.next(responseCtx); // spins off -- don't remove this comment!
377
+
378
+ // Extract expected domain addresses from response context
379
+ const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
380
+
381
+ // Poll for them if needed
382
+ if (responsePayloadAddrsDomain.length > 0) {
383
+ responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
384
+ expectedAddrs: responsePayloadAddrsDomain,
385
+ pollIntervalMs: 20, // relatively arbitrary right now
386
+ domainPayloadsMap,
387
+ tempSpace,
388
+ });
372
389
  }
373
390
 
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 });
391
+ // at this point, we have received the context AND **all** of the
392
+ // domain payloads (if applicable), so we are ready to do the next
393
+ // iteration in the saga loop
394
+
395
+ // Log what we received back
396
+ if (!responseCtx.sagaFrame) { throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`); }
397
+ if (logalotControlDomain) {
398
+ const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] as string[] || [];
399
+ console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
400
+ console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
401
+ console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
402
+ console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
403
+ console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
379
404
  }
380
- const remoteFrame = resRemoteFrame.ibGibs?.[0];
381
405
 
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
406
+ // Handle Response Frame
407
+ // interface HandleSagaFrameResult {
408
+ // frame: SyncIbGib_V1;
409
+ // payloadIbGibsDomain?: IbGib_V1[];
410
+ // conflictInfos?: SyncSagaConflictInfo;
411
+ // }
412
+ const handleResult = await this.handleSagaContext({
413
+ sagaContext: responseCtx,
414
+ mySpace: localSpace,
415
+ myTempSpace: tempSpace,
416
+ metaspace,
417
+ });
384
418
 
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 });
419
+ if (!handleResult) {
420
+ if (logalot) { console.log(`${lc} Handler returned null (Saga End).`); }
421
+ break;
390
422
  }
391
423
 
424
+ currentFrame = handleResult.frame;
392
425
 
393
- // React (Reducer)
394
- // This processes the FRAME we just got from the peer.
395
- // i.e., We Sent Init -> Got Ack. This calls handleAckFrame.
396
- // i.e., We Sent Delta -> Got Delta. This calls handleDeltaFrame.
397
- const result = await this.handleSagaFrame({
398
- sagaIbGib: remoteFrame as SyncIbGib_V1,
399
- srcGraph,
400
- destSpace: localSpace, // Query existing data from localSpace (Source)
401
- tempSpace: tempSpace, // Transaction space for saga frames
402
- identity: sessionIdentity,
403
- metaspace
404
- });
426
+ // Collect next DOMAIN payloads for the NEXT request
427
+ nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
405
428
 
406
- currentFrame = result?.frame || null;
407
- nextPayloadIbGibs = result?.payloadIbGibs || []; // Payload to send in NEXT Ping
408
- if (result?.receivedPayloadIbGibs) {
409
- // Keep track of what we received for final merge
410
- allReceivedIbGibs.push(...result.receivedPayloadIbGibs);
429
+ // Log handler output for next iteration
430
+ if (logalotControlDomain) {
431
+ const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
432
+ console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
433
+ console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
434
+ console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
411
435
  }
412
436
  }
413
-
414
- return allReceivedIbGibs;
415
437
  }
416
438
 
417
439
  /**
@@ -432,7 +454,7 @@ export class SyncSagaCoordinator {
432
454
  const lc = `${this.lc}[${this.getKnowledgeVector.name}]`;
433
455
  try {
434
456
  if (logalot) { console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`); }
435
- console.dir(space)
457
+ // console.dir(space);
436
458
 
437
459
  if (!(domainIbGibs && domainIbGibs.length > 0) &&
438
460
  !(tjpAddrs && tjpAddrs.length > 0)
@@ -451,19 +473,19 @@ export class SyncSagaCoordinator {
451
473
  tjps = tjpAddrs;
452
474
  } else if (domainIbGibs && domainIbGibs.length > 0) {
453
475
  // Extract TJPs from domain Ibgibs
454
- if (logalot) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
476
+ // if (false) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
455
477
 
456
478
  const { mapWithTjp_YesDna, mapWithTjp_NoDna } =
457
479
  splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
458
- if (logalot) { console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`); }
459
- if (logalot) { console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`); }
480
+ if (false) { console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`); }
481
+ if (false) { console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`); }
460
482
 
461
483
  const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
462
484
  const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
463
- if (logalot) { console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`); }
485
+ if (false) { console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`); }
464
486
 
465
487
  tjps = Object.keys(timelineMap);
466
- if (logalot) { console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`); }
488
+ if (false) { console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`); }
467
489
  } else {
468
490
  // No info provided. Return empty? Or throw?
469
491
  // User test context implied "everything", but implementation requires scope.
@@ -473,14 +495,14 @@ export class SyncSagaCoordinator {
473
495
 
474
496
  if (tjps.length === 0) { return {}; }
475
497
 
476
- if (logalot) { console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`); }
498
+ if (false) { console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`); }
477
499
 
478
500
  const res = await getLatestAddrs({ space, tjpAddrs: tjps });
479
501
  if (!res.data || !res.data.latestAddrsMap) {
480
502
  throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
481
503
  }
482
504
 
483
- if (logalot) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
505
+ // if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
484
506
 
485
507
  return res.data.latestAddrsMap;
486
508
  } catch (error) {
@@ -491,42 +513,41 @@ export class SyncSagaCoordinator {
491
513
  }
492
514
  }
493
515
 
494
- protected async analyzeTimelines({
516
+ protected async analyzeDomainIbGibs({
495
517
  domainIbGibs,
496
518
  space,
497
519
  }: {
498
520
  domainIbGibs: IbGib_V1[],
499
521
  space: IbGibSpaceAny,
500
- }): Promise<{
501
- srcStones: IbGib_V1[],
502
- srcTimelinesMap: { [tjp: string]: IbGib_V1[] },
503
- srcSortedTjps: string[],
504
- srcGraph: { [addr: string]: IbGib_V1 },
505
- }> {
506
- const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
507
- const srcGraph = await getDependencyGraph({
522
+ }): Promise<DomainIbGibAnalysisInfo> {
523
+ const lc = `${this.lc}[${this.analyzeDomainIbGibs.name}]`;
524
+ if (domainIbGibs.length === 0) {
525
+ throw new Error(`invalid domainIbGibs. expected at least one, but array is empty. (E: 820a719bcac5a16878a2af697113b826)`);
526
+ }
527
+
528
+ const fullGraph = await getDependencyGraph({
508
529
  ibGibs: domainIbGibs,
509
530
  live: true,
510
531
  space,
511
532
  });
512
533
 
513
- const srcGraphIsValid = srcGraph && Object.keys(srcGraph).length > 0;
514
- if (logalot) { console.log(`${lc} graph generated. nodes: ${srcGraphIsValid ? Object.keys(srcGraph).length : 0}`); }
534
+ const graphIsValid = fullGraph && Object.keys(fullGraph).length > 0;
535
+ if (logalot) { console.log(`${lc} graph generated. nodes: ${graphIsValid ? Object.keys(fullGraph).length : 0}`); }
515
536
 
516
- const srcIbGibs = srcGraphIsValid ? Object.values(srcGraph) : [];
537
+ const srcIbGibs = graphIsValid ? Object.values(fullGraph) : [];
517
538
  const {
518
539
  mapWithTjp_YesDna: srcMapWithTjp_YesDna,
519
540
  mapWithTjp_NoDna: srcMapWithTjp_NoDna,
520
541
  mapWithoutTjps: src_MapWithoutTjps
521
542
  } = splitPerTjpAndOrDna({ ibGibs: srcIbGibs });
522
543
 
523
- const srcStones = Object.values(src_MapWithoutTjps);
524
- const srcLiving = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
544
+ const stones = Object.values(src_MapWithoutTjps);
545
+ const withTimelines = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
525
546
 
526
- const srcTimelinesMap = getTimelinesGroupedByTjp({ ibGibs: srcLiving });
527
- const srcSortedTjps = this.sortTimelinesTopologically(srcTimelinesMap);
547
+ const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: withTimelines });
548
+ const topologicallySortedTjpAddrs = this.sortTimelinesTopologically(timelinesMap);
528
549
 
529
- return { srcStones, srcTimelinesMap, srcSortedTjps, srcGraph };
550
+ return { stones, timelinesMap, topologicallySortedTjpAddrs, fullGraph };
530
551
  }
531
552
 
532
553
  /**
@@ -541,78 +562,142 @@ export class SyncSagaCoordinator {
541
562
  protected async createInitFrame({
542
563
  sagaId,
543
564
  sessionIdentity,
544
- localSpace,
545
565
  domainIbGibs,
546
- tempSpace,
547
- metaspace,
548
566
  conflictStrategy,
567
+ metaspace,
568
+ localSpace,
569
+ tempSpace,
549
570
  }: {
550
571
  sagaId: string,
551
572
  sessionIdentity?: KeystoneIbGib_V1,
552
- localSpace: IbGibSpaceAny,
553
573
  domainIbGibs: IbGib_V1[],
554
- tempSpace: IbGibSpaceAny,
555
- metaspace: MetaspaceService,
556
574
  conflictStrategy: SyncConflictStrategy,
557
- }): Promise<{ sagaFrame: SyncIbGib_V1, srcGraph: { [addr: string]: IbGib_V1 } }> {
575
+ metaspace: MetaspaceService,
576
+ localSpace: IbGibSpaceAny,
577
+ tempSpace: IbGibSpaceAny,
578
+ }): Promise<{ sagaFrame: SyncIbGib_V1, initialDomainGraph: { [addr: string]: IbGib_V1 } }> {
558
579
  const lc = `${this.lc}[${this.createInitFrame.name}]`;
559
580
  try {
560
581
  if (logalot) { console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`); }
561
582
 
562
583
  // Analyze Timelines & Stones
563
- const analysis = await this.analyzeTimelines({ domainIbGibs, space: localSpace });
564
- // this is a lot, so uncomment this only if you want even more logging specific to this analysis
584
+ const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
565
585
  // if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
566
- const { srcTimelinesMap, srcGraph, srcStones, } = analysis;
586
+ const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
567
587
 
588
+ // we need to store the fullGraph in our tempSpace for later...
589
+ await putInSpace({ ibGibs: Object.values(fullGraph), space: tempSpace });
590
+
591
+ // populate our knowledge vector with tjp -> latest addr/tip in timeline
592
+ const knowledgeVector: { [tjp: string]: IbGibAddr } = {};
593
+ Object.keys(srcTimelinesMap).forEach(tjp => {
594
+ const timeline = srcTimelinesMap[tjp];
595
+ const tip = timeline.at(-1)!;
596
+ knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
597
+ });
568
598
  const initData: SyncSagaMessageInitData_V1 = {
569
599
  sagaId,
570
600
  stage: SyncStage.init,
571
- knowledgeVector: {},
601
+ knowledgeVector,
572
602
  identity: sessionIdentity, // KeystoneIbGib is already public data
573
603
  mode: SyncMode.sync,
574
604
  stones: srcStones.map(s => getIbGibAddr({ ibGib: s })),
575
605
  };
576
-
577
- // Populate Knowledge Vector
578
- Object.keys(srcTimelinesMap).forEach(tjp => {
579
- const timeline = srcTimelinesMap[tjp];
580
- const tip = timeline.at(-1)!;
581
- initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
582
- });
583
-
584
606
  if (logalot) {
585
607
  console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
586
608
  console.log(`${lc} initData.stage: ${initData.stage}`);
587
609
  }
588
610
 
611
+ // create the stone and save/register it
589
612
  const initStone = await this.createSyncMsgStone({
590
613
  data: initData,
591
- space: tempSpace,
592
- metaspace
614
+ localSpace,
615
+ metaspace,
593
616
  });
594
617
  // if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
595
618
 
619
+ // create the initial sync ibgib frame, save/register it
596
620
  const sagaFrame = await this.evolveSyncSagaIbGib({
597
621
  msgStones: [initStone],
598
- identity: sessionIdentity,
599
- space: tempSpace,
600
- metaspace,
601
622
  conflictStrategy,
602
- });
603
-
604
- // IMMEDIATELY persist to both spaces for audit trail
605
- await this.ensureSagaFrameInBothSpaces({
606
- frame: sagaFrame,
607
- destSpace: localSpace, // localSpace is the Sender's destSpace
608
- tempSpace,
609
- metaspace
623
+ sessionIdentity: sessionIdentity,
624
+ metaspace,
625
+ localSpace,
610
626
  });
611
627
 
612
628
  // if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
613
629
 
614
- return { sagaFrame, srcGraph };
630
+ return { sagaFrame, initialDomainGraph: fullGraph };
631
+ } catch (error) {
632
+ console.error(`${lc} ${extractErrorMsg(error)}`);
633
+ throw error;
634
+ } finally {
635
+ if (logalot) { console.log(`${lc} complete.`); }
636
+ }
637
+ }
615
638
 
639
+ /**
640
+ * Helper to poll for streaming domain payloads and put them in the
641
+ * local {@link tempSpace}.
642
+ *
643
+ * @returns when all {@link expectedAddrs} are done being transmitted.
644
+ */
645
+ protected async pollForDomainPayloads({
646
+ expectedAddrs,
647
+ pollIntervalMs,
648
+ domainPayloadsMap,
649
+ tempSpace,
650
+ }: {
651
+ expectedAddrs: string[],
652
+ pollIntervalMs: number,
653
+ domainPayloadsMap: Map<string, IbGib_V1>,
654
+ tempSpace: IbGibSpaceAny,
655
+ }): Promise<IbGib_V1[]> {
656
+ const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
657
+ try {
658
+ if (logalot) { console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`); }
659
+
660
+ let resultDomainPayloads: IbGib_V1[] = [];
661
+
662
+ let pending = [...expectedAddrs];
663
+ const start = Date.now();
664
+ /**
665
+ * This needs
666
+ */
667
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
668
+
669
+ while (pending.length > 0) {
670
+ if (Date.now() - start > timeoutMs) {
671
+ throw new Error(`Timeout waiting for payloads: ${pending.join(', ')} (E: 46e1683c9578095261aaf798bd5e1826)`);
672
+ }
673
+
674
+ const stillPending: string[] = [];
675
+ const found: IbGib_V1[] = [];
676
+
677
+ for (const addr of pending) {
678
+ if (domainPayloadsMap.has(addr)) {
679
+ found.push(domainPayloadsMap.get(addr)!);
680
+ domainPayloadsMap.delete(addr);
681
+ } else {
682
+ stillPending.push(addr);
683
+ }
684
+ }
685
+
686
+ if (found.length > 0) {
687
+ await putInSpace({ space: tempSpace, ibGibs: found });
688
+ found.forEach(x => resultDomainPayloads.push(x));
689
+ }
690
+
691
+ pending = stillPending;
692
+
693
+ if (pending.length > 0) { await delay(pollIntervalMs); }
694
+ }
695
+
696
+ if (expectedAddrs.length !== resultDomainPayloads.length) {
697
+ throw new Error(`(UNEXPECTED) expectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
698
+ }
699
+
700
+ return resultDomainPayloads;
616
701
  } catch (error) {
617
702
  console.error(`${lc} ${extractErrorMsg(error)}`);
618
703
  throw error;
@@ -622,59 +707,84 @@ export class SyncSagaCoordinator {
622
707
  }
623
708
 
624
709
  /**
625
- * Reacts to an incoming saga frame and dispatches to appropriate handler.
710
+ * This is the heart of the "ping pong" transaction, where we send a context
711
+ * and receive a context. IOW, this drives the FSM of the sync saga ibgib as
712
+ * a whole.
626
713
  *
627
- * @remarks
628
- * **Execution Context**: **Universal (Both Sender and Receiver)**.
714
+ * This is called in two places:
715
+ *
716
+ * ## 1. Sender
717
+ *
718
+ * On the sender, this is called within the {@link executeSagaLoop} which
719
+ * initiates and drives the sync.
720
+ *
721
+ * ## 2. Receiver
629
722
  *
630
- * This method acts as the "Reducer" for the Sync FSM. It determines the current stage
631
- * based on the incoming frame and delegates to the appropriate handler.
723
+ * On the receiver, this is called directly by the receiving endpoint. That
724
+ * endpoint's job is basically to get these things collocated and prepared
725
+ * to make this call.
632
726
  *
633
- * * If running on **Receiver**: Handles `Init` (via `handleInitFrame`).
634
- * * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
635
- * * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
727
+ * This is a one-off on the receiver.
636
728
  */
637
- async handleSagaFrame({
638
- sagaIbGib,
639
- srcGraph,
640
- destSpace,
641
- tempSpace,
729
+ public async handleSagaContext({
730
+ sagaContext,
731
+ mySpace,
732
+ myTempSpace,
642
733
  identity,
643
734
  identitySecret,
644
735
  metaspace,
645
736
  }: {
646
- sagaIbGib: SyncIbGib_V1,
647
- srcGraph: { [addr: string]: IbGib_V1 },
648
- destSpace: IbGibSpaceAny,
649
- tempSpace: IbGibSpaceAny,
737
+ sagaContext: SyncSagaContextIbGib_V1,
738
+ /**
739
+ * Local space relative to the execution context's POV
740
+ */
741
+ mySpace: IbGibSpaceAny,
742
+ /**
743
+ * Local temp space relative to the execution context's POV
744
+ */
745
+ myTempSpace: IbGibSpaceAny,
650
746
  identity?: KeystoneIbGib_V1,
651
747
  identitySecret?: string,
652
748
  metaspace: MetaspaceService,
653
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[], receivedPayloadIbGibs?: IbGib_V1[] } | null> {
654
- const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
749
+ }): Promise<HandleSagaFrameResult | null> {
750
+ const lc = `${this.lc}[${this.handleSagaContext.name}]`;
655
751
  try {
656
752
  if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
657
753
 
754
+ const sagaIbGib = sagaContext.sagaFrame;
658
755
  if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`); }
659
756
  if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
660
757
 
661
758
  // Get Stage from Stone (or Frame for Init fallback)
662
- const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
759
+ const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
663
760
 
664
761
  if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
665
762
 
763
+ /**
764
+ * don't like this name, need to refactor
765
+ */
766
+ const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
767
+
666
768
  switch (stage) {
667
769
  case SyncStage.init:
668
- return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
770
+ return await this.handleInitFrame({
771
+ sagaIbGib,
772
+ messageData: messageData as SyncSagaMessageInitData_V1,
773
+ metaspace,
774
+ mySpace: mySpace,
775
+ myTempSpace: myTempSpace,
776
+ identity,
777
+ identitySecret
778
+ });
669
779
 
670
780
  case SyncStage.ack:
671
- return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
781
+ return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
672
782
 
673
783
  case SyncStage.delta:
674
- return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
784
+ return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
675
785
 
676
786
  case SyncStage.commit:
677
- return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
787
+ return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
678
788
 
679
789
  default:
680
790
  throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
@@ -704,305 +814,297 @@ export class SyncSagaCoordinator {
704
814
  protected async handleInitFrame({
705
815
  sagaIbGib,
706
816
  messageData,
707
- destSpace,
708
- tempSpace,
817
+ mySpace,
818
+ myTempSpace,
709
819
  metaspace,
710
820
  identity,
711
- identitySecret,
821
+ // identitySecret,
712
822
  }: {
713
823
  sagaIbGib: SyncIbGib_V1,
714
- messageData: any,
715
- destSpace: IbGibSpaceAny,
716
- tempSpace: IbGibSpaceAny,
824
+ messageData: SyncSagaMessageInitData_V1,
825
+ /**
826
+ * Local space relative to the execution context's POV
827
+ */
828
+ mySpace: IbGibSpaceAny,
829
+ /**
830
+ * Local temp space relative to the execution context's POV.
831
+ *
832
+ * NOTE: Since this always executes on the receiver's end, this should
833
+ * be the receiver's temp space.
834
+ */
835
+ myTempSpace: IbGibSpaceAny,
717
836
  metaspace: MetaspaceService,
718
837
  identity?: KeystoneIbGib_V1,
719
838
  identitySecret?: string,
720
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
839
+ }): Promise<HandleSagaFrameResult | null> {
721
840
  const lc = `${this.lc}[${this.handleInitFrame.name}]`;
722
- console.log(`${lc} [TEST DEBUG] Received destSpace: ${destSpace.data?.name || destSpace.ib} (uuid: ${destSpace.data?.uuid || '[no uuid]'})`);
723
- if (logalot) { console.log(`${lc} starting...`); }
841
+ try {
842
+ if (logalot) { console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`); }
724
843
 
725
- // Extract Init Data
726
- const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
727
- if (initData.stage !== SyncStage.init) {
728
- throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
729
- }
730
- // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
731
- if (!initData || !initData.knowledgeVector) {
732
- throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
733
- }
844
+ console.log(`${lc} [TEST DEBUG] Received destSpace: ${mySpace.data?.name || mySpace.ib} (uuid: ${mySpace.data?.uuid || '[no uuid]'})`);
845
+ if (logalot) { console.log(`${lc} starting...`); }
734
846
 
735
- // Determine Strategy from Saga Data (since V1 stores it in root)
736
- const conflictStrategy = sagaIbGib.data!.conflictStrategy || 'abort';
737
-
738
- // 2. Gap Analysis
739
- const conflicts: { tjpAddr: string, localAddr: string, remoteAddr: string, timelineAddrs: string[], reason: string, terminal: boolean }[] = [];
740
-
741
- const deltaReqAddrs: string[] = [];
742
- const pushOfferAddrs: string[] = [];
743
-
744
- // Stones Analysis (Constants / Non-TJPs)
745
- const stones = initData.stones || [];
746
- if (stones.length > 0) {
747
- if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
748
- // Check if we have these stones
749
- const resStones = await getFromSpace({ space: destSpace, addrs: stones });
750
- const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
751
- if (addrsNotFound && addrsNotFound.length > 0) {
752
- if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
753
- addrsNotFound.forEach(addr => {
754
- if (!deltaReqAddrs.includes(addr)) {
755
- deltaReqAddrs.push(addr);
756
- }
757
- });
847
+ // Extract Init Data
848
+ const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
849
+ if (initData.stage !== SyncStage.init) {
850
+ throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
851
+ }
852
+ // if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
853
+ if (!initData || !initData.knowledgeVector) {
854
+ throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
758
855
  }
759
- }
760
-
761
- const remoteKV = initData.knowledgeVector;
762
- if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
763
- const remoteTjps = Object.keys(remoteKV);
764
- console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
765
- if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
766
-
767
- // 1. Get Local Latest Addrs for all TJPs
768
- let localKV: { [tjp: string]: string | null } = {};
769
- if (remoteTjps.length > 0) {
770
- // Batch get latest addrs for the TJPs
771
- const resGetLatestAddrs = await getLatestAddrs({
772
- space: destSpace,
773
- tjpAddrs: remoteTjps,
774
- });
775
- if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
776
- if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
777
- localKV = resGetLatestAddrs.data.latestAddrsMap;
778
- console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
779
- if (logalot) { console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`); }
780
- }
781
-
782
- // 2. Gap Analysis
783
-
784
-
785
- for (const tjp of remoteTjps) {
786
- const remoteAddr = remoteKV[tjp];
787
- const localAddr = localKV[tjp];
788
856
 
789
- if (!localAddr) {
790
- // We (Receiver) don't have this timeline. Request it.
791
- console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
792
- deltaReqAddrs.push(remoteAddr);
793
- continue;
857
+ // Determine Strategy from Saga Data (since V1 stores it in root)
858
+ const conflictStrategy = sagaIbGib.data!.conflictStrategy || SyncConflictStrategy.abort;
859
+
860
+ // 2. Gap Analysis
861
+ const conflicts: SyncSagaConflictInfo[] = [];
862
+ const deltaReqAddrs: string[] = [];
863
+ const pushOfferAddrs: string[] = [];
864
+
865
+ // First do the consant stones (Non-TJPs)
866
+ const stones = initData.stones || [];
867
+ if (stones.length > 0) {
868
+ if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
869
+ // Check if we have these stones
870
+ const resStones = await getFromSpace({ space: mySpace, addrs: stones });
871
+ const rawResultIbGib = resStones.rawResultIbGib as IbGibSpaceResultIbGib<IbGib_V1, IbGibSpaceResultData, IbGibSpaceResultRel8ns>;
872
+ if (!rawResultIbGib.data) { throw new Error(`(UNEXPECTED) rawResultIbGib.data falsy? (E: f3c40b492adc02c3f480b998fc7a5926)`); }
873
+ const addrsNotFound = rawResultIbGib.data.addrsNotFound;
874
+ if (addrsNotFound && addrsNotFound.length > 0) {
875
+ if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
876
+ addrsNotFound.forEach(addr => {
877
+ if (!deltaReqAddrs.includes(addr)) { deltaReqAddrs.push(addr); }
878
+ });
879
+ }
794
880
  }
795
881
 
796
- if (localAddr === remoteAddr) {
797
- // Synced
798
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
799
- continue;
882
+ /**
883
+ * "remote" local to receiver's context is the sender
884
+ */
885
+ const remoteKV = initData.knowledgeVector;
886
+ if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
887
+ const remoteTjps = Object.keys(remoteKV);
888
+ console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
889
+ if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
890
+
891
+ // 1. Get Local Latest Addrs for all TJPs
892
+ let localKV: { [tjp: string]: string | null } = {};
893
+ if (remoteTjps.length > 0) {
894
+ // Batch get latest addrs for the TJPs
895
+ const resGetLatestAddrs = await getLatestAddrs({
896
+ space: mySpace, // executing on receiver, so this is receiver's space
897
+ tjpAddrs: remoteTjps,
898
+ });
899
+ if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
900
+ if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
901
+ localKV = resGetLatestAddrs.data.latestAddrsMap;
902
+ console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
903
+ if (logalot) { console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`); }
800
904
  }
801
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
802
-
803
- // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
804
- // (Sender has older version, Receiver has newer) -> Receiver Offers Push
805
- const isRemoteInPast = await isPastFrame({
806
- olderAddr: remoteAddr,
807
- newerAddr: localAddr,
808
- space: destSpace,
809
- });
810
905
 
811
- if (isRemoteInPast) {
812
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
813
- pushOfferAddrs.push(localAddr);
814
- } else {
815
- // Remote is not in our past.
816
- // Either Remote is ahead (Fast-Backward) OR Diverged.
906
+ // 2. Gap Analysis
907
+ for (const tjp of remoteTjps) {
908
+ const remoteAddr = remoteKV[tjp];
909
+ const localAddr = localKV[tjp];
817
910
 
818
- // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
819
- const isLocalInPast = await isPastFrame({
820
- olderAddr: localAddr,
821
- newerAddr: remoteAddr,
822
- space: destSpace,
911
+ if (!localAddr) {
912
+ // We (Receiver) don't have this timeline at all. Request it.
913
+ console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
914
+ deltaReqAddrs.push(remoteAddr);
915
+ continue;
916
+ }
917
+
918
+ if (localAddr === remoteAddr) {
919
+ // Synced
920
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
921
+ continue;
922
+ }
923
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
924
+
925
+ // Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
926
+ // (Sender has older version, Receiver has newer) -> Receiver Offers Push
927
+ const isRemoteInPast = await isPastFrame({
928
+ olderAddr: remoteAddr,
929
+ newerAddr: localAddr,
930
+ space: mySpace,
823
931
  });
824
932
 
825
- if (isLocalInPast) {
826
- // Fast-Forward: We update to remote's tip.
827
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
828
- deltaReqAddrs.push(remoteAddr);
933
+ if (isRemoteInPast) {
934
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
935
+ pushOfferAddrs.push(localAddr);
829
936
  } else {
830
- // DIVERGENCE: Both have changes the other doesn't know about.
831
- console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
832
-
833
- if (conflictStrategy === 'abort') {
834
- // Abort Strategy: We will treat this as terminal.
835
- // But for Unified Ack, we just mark it terminal in the list?
836
- // Or do we actually throw/abort the saga?
837
- // Current logic (below) aborts the saga if ANY conflict is terminal/abort.
838
- conflicts.push({
839
- tjpAddr: tjp,
840
- localAddr: localAddr!,
841
- remoteAddr,
842
- timelineAddrs: [], // Not needed for abort
843
- reason: 'divergence',
844
- terminal: true
845
- });
846
- } else if (conflictStrategy === 'optimistic') {
847
- // Optimistic: We want to resolving this.
848
- // We need to send our history to the Sender so they can Merge.
849
-
850
- // Fetch Full History for Local Timeline
851
- // Note: We might optimize this to only send "recent" history if we had a KV?
852
- // But for now, get full past.
853
- // Optimization: localKV might not have full history.
854
- // We need to inspect the 'past' of the local tip.
855
-
856
- // We need the ACTUAL object to get the past.
857
- // We have localAddr.
858
- const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr! });
859
- const localTip = resLocalTip.ibGibs?.[0];
860
- if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`); }
861
-
862
- const timelineAddrs = [localAddr!, ...(localTip.rel8ns?.past || [])];
863
-
864
- conflicts.push({
865
- tjpAddr: tjp,
866
- localAddr: localAddr!,
867
- remoteAddr,
868
- timelineAddrs,
869
- reason: 'divergence',
870
- terminal: false
871
- });
937
+ // Remote is not in our past.
938
+ // Either Remote is ahead (Fast-Backward) OR Diverged.
939
+
940
+ // Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
941
+ const isLocalInPast = await isPastFrame({
942
+ olderAddr: localAddr,
943
+ newerAddr: remoteAddr,
944
+ space: mySpace,
945
+ });
872
946
 
947
+ if (isLocalInPast) {
948
+ // Fast-Forward: We update to remote's tip.
949
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
950
+ deltaReqAddrs.push(remoteAddr);
873
951
  } else {
874
- throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
952
+ // DIVERGENCE: Both have changes the other doesn't know about.
953
+ console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
954
+
955
+ if (conflictStrategy === 'abort') {
956
+ // Abort Strategy: We will treat this as terminal.
957
+ // But for Unified Ack, we just mark it terminal in the list?
958
+ // Or do we actually throw/abort the saga?
959
+ // Current logic (below) aborts the saga if ANY conflict is terminal/abort.
960
+ conflicts.push({
961
+ tjpAddr: tjp,
962
+ localAddr: localAddr,
963
+ remoteAddr,
964
+ timelineAddrs: [], // Not needed for abort
965
+ reason: 'divergence',
966
+ terminal: true
967
+ });
968
+ } else if (conflictStrategy === 'optimistic') {
969
+ // Optimistic: We want to resolving this.
970
+ // We need to send our history to the Sender so they can Merge.
971
+
972
+ // Fetch Full History for Local Timeline
973
+ // Note: We might optimize this to only send "recent" history if we had a KV?
974
+ // But for now, get full past.
975
+ // Optimization: localKV might not have full history.
976
+ // We need to inspect the 'past' of the local tip.
977
+
978
+ // We need the ACTUAL object to get the past.
979
+ // We have localAddr.
980
+ const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
981
+ if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
982
+ throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: ff06ff849fa8e8dba32ce09807411226)`);
983
+ }
984
+ const localTip = resLocalTip.ibGibs[0];
985
+ if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`); }
986
+
987
+ const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
988
+
989
+ conflicts.push({
990
+ tjpAddr: tjp,
991
+ localAddr,
992
+ remoteAddr,
993
+ timelineAddrs,
994
+ reason: 'divergence',
995
+ terminal: false
996
+ });
997
+
998
+ } else {
999
+ throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
1000
+ }
875
1001
  }
876
1002
  }
877
1003
  }
878
- }
879
-
880
- // Check if we should ABORT (if any conflict is terminal)
881
- const hasTerminalConflicts = conflicts.some(c => c.terminal);
882
1004
 
883
- if (hasTerminalConflicts) {
884
- // Abort Strategy: Kill the saga.
885
- if (logalot) { console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`); }
1005
+ // Check if we should ABORT (if any conflict is terminal)
1006
+ const hasTerminalConflicts = conflicts.some(c => c.terminal);
886
1007
 
887
- // We reuse the ConflictData structure for terminal aborts?
888
- // Or do we send an Ack with terminal conflicts?
889
- // Original design had explicit Conflict Frame for Abort.
890
- // Let's stick to that for purely terminal cases to be safe/explicit?
891
- // Or Unified: Just send Ack with terminal=true conflicts. Sender sees them and aborts.
1008
+ if (hasTerminalConflicts) {
1009
+ // Abort Strategy: Kill the saga.
1010
+ if (logalot) { console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`); }
892
1011
 
893
- // Decision: Unified Ack for everything is cleaner protocol.
894
- // But wait, the original code below creates a Conflict Stone.
895
- // Let's preserve the explicit 'Conflict' frame for total aborts if that's easier,
896
- // OR fully switch to Ack.
897
- // Protocol states: Init -> Ack. If Ack contains terminal errors, Sender can Commit(Fail).
898
-
899
- // Let's use Ack with conflicts.
900
- }
901
-
902
- // 2. Add Push Offers (Missing in Local)
903
- // Check if we have them. If not, ask for them.
904
- for (const addr of pushOfferAddrs) {
905
- const hasIt = await getFromSpace({ addr, space: destSpace });
906
- if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
907
- // If we don't have it, we put it in `deltaReqAddrs` of the Ack.
908
- deltaReqAddrs.push(addr);
1012
+ throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
909
1013
  }
910
- }
911
1014
 
912
- // 3. Build Knowledge Vector (Full History for known timelines)
913
- // [NEW] Smart Diff
914
- // We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
915
- // Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
916
-
917
- const knowledgeVector: { [groupKey: string]: string[] } = {};
918
- const relevantAddrs = new Set([...pushOfferAddrs, ...deltaReqAddrs]);
919
-
920
- // [Smart Diff] Populate knowledge from timelines identified by Sender
921
- for (const tjp of remoteTjps) {
922
- const localAddr = localKV[tjp];
923
- if (localAddr) {
924
- const res = await getFromSpace({ addr: localAddr, space: destSpace });
925
- if (res.success && res.ibGibs?.[0]) {
926
- const ibGib = res.ibGibs[0];
927
- const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
928
- if (!knowledgeVector[realTjp]) {
929
- const past = ibGib.rel8ns?.past || [];
930
- knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
1015
+ // 3. Build Knowledge Vector (Full History for known timelines)
1016
+ // [NEW] Smart Diff
1017
+ // We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
1018
+ // Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
1019
+
1020
+ /**
1021
+ * we will put this in {@link SyncSagaMessageAckData_V1.knowledgeVector}
1022
+ */
1023
+ const knowledgeVector: { [groupKey: string]: string[] } = {};
1024
+
1025
+ // [Smart Diff] Populate knowledge from timelines identified by Sender
1026
+ for (const tjp of remoteTjps) {
1027
+ const localAddr = localKV[tjp];
1028
+ if (localAddr) {
1029
+ const res = await getFromSpace({ addr: localAddr, space: mySpace });
1030
+ if (res.success && res.ibGibs?.[0]) {
1031
+ const ibGib = res.ibGibs[0];
1032
+ const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
1033
+ if (!knowledgeVector[realTjp]) {
1034
+ const past = ibGib.rel8ns?.past || [];
1035
+ knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
1036
+ }
931
1037
  }
932
1038
  }
933
1039
  }
934
- }
935
1040
 
936
- // [Smart Diff] Also check individual requested addresses (Fall back for constants/unknown TJPs)
937
- for (const addr of relevantAddrs) {
938
- // Only process if not already covered by TJP logic above
939
- // We can't really know if it's covered easily without resolving.
940
- // But if we don't have it (requesting), we won't find it here anyway.
941
- // If we DO have it (push offer), we might find it.
942
- const res = await getFromSpace({ addr, space: destSpace });
943
- if (res.success && res.ibGibs?.[0]) {
944
- const ibGib = res.ibGibs[0];
945
- const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
946
-
947
- if (!knowledgeVector[tjpAddr]) {
948
- const past = ibGib.rel8ns?.past || [];
949
- knowledgeVector[tjpAddr] = [getIbGibAddr({ ibGib }), ...past];
950
- }
951
- } else {
952
- // We don't have `addr`.
953
- }
954
- }
1041
+ // Also populate from `knowledgeVector` in Init if we want bidirectional?
1042
+ // No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
1043
+ // Let's assume standard flow:
1044
+ // 1. Sender says "I have X"
1045
+ // 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
1046
+ // Problem: Receiver doesn't know X is related to Y without X.
1047
+
1048
+ // SOLUTION:
1049
+ // The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
1050
+ // `SyncSagaMessageInitData_V1` extends `SyncInitData`.
1051
+ // `SyncInitData` has `knowledgeVector`.
1052
+ // If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
1053
+
1054
+ // Let's implement Sender populating `Init.knowledgeVector`.
1055
+ // But `SyncSagaCoordinator.startSaga` creates Init.
1056
+
1057
+ // 3. Create Ack Frame
1058
+ const sagaId = sagaIbGib.data!.uuid;
1059
+ // Create Payload Stone
1060
+ const ackData: SyncSagaMessageAckData_V1 = {
1061
+ sagaId,
1062
+ stage: SyncStage.ack,
1063
+ deltaReqAddrs,
1064
+ pushOfferAddrs,
1065
+ knowledgeVector,
1066
+ };
1067
+ if (conflicts?.length > 0) { ackData.conflicts = conflicts; }
955
1068
 
956
- // Also populate from `knowledgeVector` in Init if we want bidirectional?
957
- // No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
958
- // Let's assume standard flow:
959
- // 1. Sender says "I have X"
960
- // 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
961
- // Problem: Receiver doesn't know X is related to Y without X.
962
-
963
- // SOLUTION:
964
- // The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
965
- // `SyncSagaMessageInitData_V1` extends `SyncInitData`.
966
- // `SyncInitData` has `knowledgeVector`.
967
- // If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
968
-
969
- // Let's implement Sender populating `Init.knowledgeVector`.
970
- // But `SyncSagaCoordinator.startSaga` creates Init.
971
-
972
- // 3. Create Ack Frame
973
- const sagaId = sagaIbGib.data!.uuid;
974
- // Create Payload Stone
975
- const ackData: SyncSagaMessageAckData_V1 = {
976
- sagaId,
977
- stage: SyncStage.ack,
978
- deltaReqAddrs,
979
- pushOfferAddrs,
980
- knowledgeVector,
981
- conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
982
- };
1069
+ const ackStone = await this.createSyncMsgStone({
1070
+ data: ackData,
1071
+ localSpace: myTempSpace,
1072
+ metaspace,
1073
+ });
1074
+ if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
983
1075
 
984
- const ackStone = await this.createSyncMsgStone({
985
- data: ackData,
986
- space: tempSpace,
987
- metaspace,
988
- });
989
- if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
990
-
991
- // Evolve Saga
992
- const ackFrame = await this.evolveSyncSagaIbGib({
993
- prevSagaIbGib: sagaIbGib,
994
- msgStones: [ackStone],
995
- identity,
996
- space: tempSpace,
997
- metaspace,
998
- });
1076
+ // Evolve Saga
1077
+ const ackFrame = await this.evolveSyncSagaIbGib({
1078
+ prevSagaIbGib: sagaIbGib,
1079
+ msgStones: [ackStone],
1080
+ sessionIdentity: identity,
1081
+ localSpace: mySpace,
1082
+ metaspace,
1083
+ });
1084
+
1085
+ // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
1086
+
1087
+ /**
1088
+ * we want to push ibgibs to the remote/sender if we have push
1089
+ * offers. an ack frame's payloads, if any, are those push offers
1090
+ */
1091
+ // let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
999
1092
 
1000
- // IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
1001
- await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
1093
+ if (pushOfferAddrs.length > 0) {
1094
+ }
1002
1095
 
1003
- // if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
1096
+ return {
1097
+ frame: ackFrame,
1098
+ // conflictInfos,
1099
+ // payloadIbGibsDomain,
1100
+ };
1004
1101
 
1005
- return { frame: ackFrame };
1102
+ } catch (error) {
1103
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1104
+ throw error;
1105
+ } finally {
1106
+ if (logalot) { console.log(`${lc} complete.`); }
1107
+ }
1006
1108
  }
1007
1109
 
1008
1110
  /**
@@ -1031,12 +1133,12 @@ export class SyncSagaCoordinator {
1031
1133
  tempSpace: IbGibSpaceAny,
1032
1134
  metaspace: MetaspaceService,
1033
1135
  identity?: KeystoneIbGib_V1,
1034
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
1136
+ }): Promise<HandleSagaFrameResult | null> {
1035
1137
  const lc = `${this.lc}[${this.handleAckFrame.name}]`;
1036
1138
  try {
1037
1139
  if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
1038
1140
 
1039
- const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1141
+ const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1040
1142
  const ackData = messageData as SyncSagaMessageAckData_V1;
1041
1143
 
1042
1144
  if (!ackData) {
@@ -1130,62 +1232,7 @@ export class SyncSagaCoordinator {
1130
1232
  // PULL these frames from Peer into Local Space
1131
1233
  // (Validation: We trust peer for now / verification happens on put)
1132
1234
  for (const addr of receiverOnlyAddrs) {
1133
- // This 'pull' is a sync-peer method?
1134
- // The Coordinator 'peer' passed in 'sync()' might be needed here?
1135
- // Wait, `handleAckFrame` doesn't have reference to `peer`?
1136
- // It only has `space`, `metaspace`.
1137
- // The `peer` is held by the `executeSagaLoop`.
1138
-
1139
- // PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
1140
- // No, it's a method on Coordinator.
1141
- // But `executeSagaLoop` calls it.
1142
- // We might need to return "Requirements" to the loop?
1143
-
1144
- // Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
1145
- // It returns the NEXT frame (Delta).
1146
-
1147
- // If we need to fetch data, we are blocked.
1148
- // We can't easily "Pull" here without the Peer reference.
1149
-
1150
- // OPTION A: Pass `peer` to `handleAckFrame`.
1151
- // OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
1152
-
1153
- // Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
1154
- // No, Peer is ephemeral connection.
1155
-
1156
- // Let's add `peer` to `handleAckFrame` signature?
1157
- // It breaks the pattern of just handling frame + space.
1158
-
1159
- // ALTERNATIVE: Use the `Delta` frame to request data?
1160
- // `SyncSagaMessageDeltaData` has `requests?: string[]`.
1161
- // Sender sends Delta Frame.
1162
- // Does Receiver handle Delta Requests?
1163
- // `handleDeltaFrame` (Receiver) -> checks `requests`.
1164
- // YES.
1165
-
1166
- // So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
1167
- // Receiver sees them, fetches them, and includes them in the Response (Commit?).
1168
- // Wait, Init->Ack->Delta->Commit.
1169
- // If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
1170
- // Unless Commit is not the end?
1171
-
1172
- // Or we do a "Delta 2" loop?
1173
-
1174
- // "Iterative Resolution Loop" from plan.
1175
- // If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
1176
- // Sender gets Commit. Sees data. Merges.
1177
- // Then Sender needs to Send the MERGE result.
1178
- // Needs another Push/Delta.
1179
-
1180
- // REFINED FLOW:
1181
- // 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
1182
- // 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
1183
- // 3. Sender handles response -> Merges.
1184
- // 4. Sender sends Commit (containing Merge Frame).
1185
-
1186
- // Issue: Current state machine is Init->Ack->Delta->Commit.
1187
- // We need to keep Saga open.
1188
- // If Sender sends Delta with requests, does it transition to Commit?
1235
+ console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
1189
1236
  }
1190
1237
 
1191
1238
  // Compute DELTA dependencies for each receiver-only frame
@@ -1354,24 +1401,26 @@ export class SyncSagaCoordinator {
1354
1401
 
1355
1402
  const deltaStone = await this.createSyncMsgStone({
1356
1403
  data: deltaData,
1357
- space: tempSpace,
1404
+ localSpace: tempSpace,
1358
1405
  metaspace,
1359
1406
  });
1360
1407
 
1361
1408
  const deltaFrame = await this.evolveSyncSagaIbGib({
1362
1409
  prevSagaIbGib: sagaIbGib,
1363
1410
  msgStones: [deltaStone],
1364
- identity,
1365
- space: tempSpace,
1411
+ sessionIdentity: identity,
1412
+ localSpace: tempSpace,
1366
1413
  metaspace,
1367
1414
  });
1368
1415
 
1369
- // IMMEDIATELY persist to both spaces for audit trail
1370
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1371
-
1372
1416
  if (logalot) { console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`); }
1373
1417
 
1374
- return { frame: deltaFrame, payloadIbGibs };
1418
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1419
+ const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1420
+ if (identity) { payloadIbGibsControl.push(identity); }
1421
+
1422
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
1423
+ return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
1375
1424
  } catch (error) {
1376
1425
  console.error(`${lc} ${extractErrorMsg(error)}`);
1377
1426
  throw error;
@@ -1404,11 +1453,11 @@ export class SyncSagaCoordinator {
1404
1453
  tempSpace: IbGibSpaceAny,
1405
1454
  metaspace: MetaspaceService,
1406
1455
  identity?: KeystoneIbGib_V1,
1407
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[], receivedPayloadIbGibs?: IbGib_V1[] } | null> {
1456
+ }): Promise<HandleSagaFrameResult | null> {
1408
1457
  const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
1409
1458
  if (logalot) { console.log(`${lc} starting...`); }
1410
1459
 
1411
- const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
1460
+ const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
1412
1461
  const deltaData = messageData as SyncSagaMessageDeltaData_V1;
1413
1462
 
1414
1463
  if (!deltaData) {
@@ -1614,22 +1663,24 @@ export class SyncSagaCoordinator {
1614
1663
 
1615
1664
  const deltaStone = await this.createSyncMsgStone({
1616
1665
  data: responseDeltaData,
1617
- space: tempSpace,
1666
+ localSpace: tempSpace,
1618
1667
  metaspace
1619
1668
  });
1620
1669
 
1621
1670
  const deltaFrame = await this.evolveSyncSagaIbGib({
1622
1671
  prevSagaIbGib: sagaIbGib,
1623
1672
  msgStones: [deltaStone],
1624
- identity,
1625
- space: tempSpace,
1673
+ sessionIdentity: identity,
1674
+ localSpace: tempSpace,
1626
1675
  metaspace
1627
1676
  });
1628
1677
 
1629
- // IMMEDIATELY persist to both spaces for audit trail
1630
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1678
+ // Build control payloads: frame + its dependencies (msg stone, identity)
1679
+ const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1680
+ if (identity) { payloadIbGibsControl.push(identity); }
1631
1681
 
1632
- return { frame: deltaFrame, payloadIbGibs: outgoingPayload, receivedPayloadIbGibs };
1682
+ // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1683
+ return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1633
1684
 
1634
1685
  } else {
1635
1686
  // We have nothing to send.
@@ -1644,22 +1695,24 @@ export class SyncSagaCoordinator {
1644
1695
 
1645
1696
  const commitStone = await this.createSyncMsgStone({
1646
1697
  data: commitData,
1647
- space: tempSpace,
1698
+ localSpace: tempSpace,
1648
1699
  metaspace
1649
1700
  });
1650
1701
 
1651
1702
  const commitFrame = await this.evolveSyncSagaIbGib({
1652
1703
  prevSagaIbGib: sagaIbGib,
1653
1704
  msgStones: [commitStone],
1654
- identity,
1655
- space: tempSpace,
1705
+ sessionIdentity: identity,
1706
+ localSpace: tempSpace,
1656
1707
  metaspace
1657
1708
  });
1658
1709
 
1659
- // IMMEDIATELY persist to both spaces for audit trail
1660
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1710
+ // Build control payloads for commit
1711
+ const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
1712
+ if (identity) { commitCtrlPayloads.push(identity); }
1661
1713
 
1662
- return { frame: commitFrame, receivedPayloadIbGibs };
1714
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
1715
+ return { frame: commitFrame, };
1663
1716
 
1664
1717
  } else {
1665
1718
  // peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
@@ -1674,21 +1727,18 @@ export class SyncSagaCoordinator {
1674
1727
 
1675
1728
  const deltaStone = await this.createSyncMsgStone({
1676
1729
  data: responseDeltaData,
1677
- space: tempSpace,
1730
+ localSpace: tempSpace,
1678
1731
  metaspace
1679
1732
  });
1680
1733
 
1681
1734
  const deltaFrame = await this.evolveSyncSagaIbGib({
1682
1735
  prevSagaIbGib: sagaIbGib,
1683
1736
  msgStones: [deltaStone],
1684
- identity,
1685
- space: tempSpace,
1737
+ sessionIdentity: identity,
1738
+ localSpace: tempSpace,
1686
1739
  metaspace
1687
1740
  });
1688
1741
 
1689
- // IMMEDIATELY persist to both spaces for audit trail
1690
- await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
1691
-
1692
1742
  // Check if PEER proposed commit
1693
1743
  if (deltaData.proposeCommit) {
1694
1744
  if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
@@ -1703,25 +1753,32 @@ export class SyncSagaCoordinator {
1703
1753
 
1704
1754
  const commitStone = await this.createSyncMsgStone({
1705
1755
  data: commitData,
1706
- space: tempSpace,
1756
+ localSpace: tempSpace,
1707
1757
  metaspace
1708
1758
  });
1709
1759
 
1710
1760
  const commitFrame = await this.evolveSyncSagaIbGib({
1711
1761
  prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
1712
1762
  msgStones: [commitStone],
1713
- identity,
1714
- space: tempSpace,
1763
+ sessionIdentity: identity,
1764
+ localSpace: tempSpace,
1715
1765
  metaspace
1716
1766
  });
1717
1767
 
1718
- // IMMEDIATELY persist to both spaces for audit trail
1719
- await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
1768
+ // Build control payloads for commit
1769
+ const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
1770
+ if (identity) { commitCtrlPayloads2.push(identity); }
1720
1771
 
1721
- return { frame: commitFrame, receivedPayloadIbGibs };
1772
+ // return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
1773
+ return { frame: commitFrame, };
1722
1774
  }
1723
1775
 
1724
- return { frame: deltaFrame, receivedPayloadIbGibs };
1776
+ // Build control payloads for delta propose
1777
+ const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
1778
+ if (identity) { deltaCtrlPayloads.push(identity); }
1779
+
1780
+ // return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
1781
+ return { frame: deltaFrame, };
1725
1782
  }
1726
1783
  }
1727
1784
  }
@@ -1739,7 +1796,7 @@ export class SyncSagaCoordinator {
1739
1796
  tempSpace: IbGibSpaceAny,
1740
1797
  metaspace: MetaspaceService,
1741
1798
  identity?: KeystoneIbGib_V1,
1742
- }): Promise<{ frame: SyncIbGib_V1, payloadIbGibs?: IbGib_V1[] } | null> {
1799
+ }): Promise<HandleSagaFrameResult | null> {
1743
1800
  const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
1744
1801
  if (logalot) { console.log(`${lc} Commit received.`); }
1745
1802
 
@@ -1761,11 +1818,11 @@ export class SyncSagaCoordinator {
1761
1818
 
1762
1819
  protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
1763
1820
  data,
1764
- space,
1821
+ localSpace,
1765
1822
  metaspace,
1766
1823
  }: {
1767
1824
  data: TStoneData,
1768
- space: IbGibSpaceAny,
1825
+ localSpace: IbGibSpaceAny,
1769
1826
  metaspace: MetaspaceService,
1770
1827
  }): Promise<IbGib_V1<TStoneData>> {
1771
1828
  const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
@@ -1780,7 +1837,7 @@ export class SyncSagaCoordinator {
1780
1837
  uuid: true, // we want the stone to have its own uniqueness
1781
1838
  });
1782
1839
  if (logalot) { console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`); }
1783
- await putInSpace({ space, ibGib: stone });
1840
+ await metaspace.put({ ibGib: stone, space: localSpace });
1784
1841
  await metaspace.registerNewIbGib({ ibGib: stone });
1785
1842
  return stone as IbGib_V1<TStoneData>;
1786
1843
  } catch (error) {
@@ -1792,67 +1849,23 @@ export class SyncSagaCoordinator {
1792
1849
  }
1793
1850
 
1794
1851
 
1795
- /**
1796
- * Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
1797
- * Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
1798
- */
1799
- protected async ensureSagaFrameInBothSpaces({
1800
- frame,
1801
- destSpace,
1802
- tempSpace,
1803
- metaspace,
1804
- }: {
1805
- frame: SyncIbGib_V1,
1806
- destSpace: IbGibSpaceAny,
1807
- tempSpace: IbGibSpaceAny,
1808
- metaspace: MetaspaceService,
1809
- }): Promise<void> {
1810
- // Frame itself (already in tempSpace from creation, need in destSpace for audit)
1811
- await putInSpace({ space: destSpace, ibGib: frame });
1812
- await metaspace.registerNewIbGib({ ibGib: frame });
1813
-
1814
- // Msg stone(s) (already in tempSpace, need in destSpace for audit)
1815
- const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1816
- if (msgStoneAddrs && msgStoneAddrs.length > 0) {
1817
- const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
1818
- if (resMsgStones.ibGibs) {
1819
- for (const msgStone of resMsgStones.ibGibs) {
1820
- await putInSpace({ space: destSpace, ibGib: msgStone });
1821
- await metaspace.registerNewIbGib({ ibGib: msgStone });
1822
- }
1823
- }
1824
- }
1825
-
1826
- // Identity (if present, already in tempSpace, need in destSpace)
1827
- const identityAddrs = frame.rel8ns?.identity;
1828
- if (identityAddrs && identityAddrs.length > 0) {
1829
- const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
1830
- if (resIdentity.ibGibs) {
1831
- for (const identity of resIdentity.ibGibs) {
1832
- await putInSpace({ space: destSpace, ibGib: identity });
1833
- await metaspace.registerNewIbGib({ ibGib: identity });
1834
- }
1835
- }
1836
- }
1837
- }
1838
-
1839
1852
  /**
1840
1853
  * Evolves the saga timeline with a new frame.
1841
1854
  */
1842
1855
  protected async evolveSyncSagaIbGib({
1843
1856
  prevSagaIbGib,
1857
+ conflictStrategy,
1844
1858
  msgStones,
1845
- identity,
1846
- space,
1859
+ sessionIdentity,
1860
+ localSpace,
1847
1861
  metaspace,
1848
- conflictStrategy,
1849
1862
  }: {
1850
1863
  prevSagaIbGib?: SyncIbGib_V1,
1864
+ conflictStrategy?: SyncConflictStrategy,
1851
1865
  msgStones: IbGib_V1[],
1852
- identity?: KeystoneIbGib_V1,
1853
- space: IbGibSpaceAny,
1866
+ localSpace: IbGibSpaceAny,
1854
1867
  metaspace: MetaspaceService,
1855
- conflictStrategy?: SyncConflictStrategy,
1868
+ sessionIdentity?: KeystoneIbGib_V1,
1856
1869
  }): Promise<SyncIbGib_V1> {
1857
1870
  const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
1858
1871
  try {
@@ -1878,10 +1891,9 @@ export class SyncSagaCoordinator {
1878
1891
  }
1879
1892
  }
1880
1893
 
1881
- const identityAddr = identity ? getIbGibAddr({ ibGib: identity }) : undefined;
1894
+ const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
1882
1895
 
1883
1896
  if (prevSagaIbGib) {
1884
-
1885
1897
  /**
1886
1898
  * rel8ns always include the new msg stone(s)
1887
1899
  */
@@ -1893,8 +1905,8 @@ export class SyncSagaCoordinator {
1893
1905
  ];
1894
1906
 
1895
1907
  // if we're authenticating/signing, we'll have identity
1896
- if (identity) {
1897
- rel8nInfos.push({ rel8nName: 'identity', ibGibs: [identity], });
1908
+ if (sessionIdentity) {
1909
+ rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
1898
1910
  }
1899
1911
 
1900
1912
  // remove the existing sync msg stones' addrs
@@ -1915,7 +1927,7 @@ export class SyncSagaCoordinator {
1915
1927
  rel8nInfos,
1916
1928
  rel8nRemovalInfos,
1917
1929
  metaspace,
1918
- space,
1930
+ space: localSpace,
1919
1931
  noDna: true, // Explicitly no DNA for sync frames
1920
1932
  });
1921
1933
 
@@ -1923,33 +1935,33 @@ export class SyncSagaCoordinator {
1923
1935
  return newFrame;
1924
1936
  } else {
1925
1937
  // Create New Timeline (Root Frame)
1938
+ // data
1926
1939
  const data: SyncData_V1 = {
1927
1940
  uuid: sagaId,
1928
- // stage, // Removed from V1
1929
- payload: undefined, // Data in stone
1930
1941
  n: 0,
1931
1942
  isTjp: true,
1932
1943
  conflictStrategy,
1933
1944
  };
1945
+ // ib
1934
1946
  const ib = await getSyncIb({ data });
1935
-
1947
+ // rel8ns
1936
1948
  const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
1937
- const rel8ns: IbGibRel8ns_V1 = {
1938
- [SYNC_MSG_REL8N_NAME]: stoneAddrs,
1939
- };
1940
- if (identityAddr) {
1941
- rel8ns.identity = [identityAddr];
1942
- }
1949
+ const rel8ns: SyncRel8ns_V1 = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
1950
+ if (identityAddr) { rel8ns.identity = [identityAddr]; }
1943
1951
 
1944
1952
  const resNew = await createTimeline({
1945
- space,
1953
+ space: localSpace,
1946
1954
  metaspace,
1947
1955
  ib,
1948
1956
  data,
1949
1957
  rel8ns,
1950
1958
  parentIb: SYNC_ATOM, // "sync"
1959
+ /**
1960
+ * this will squash the result into a single frame
1961
+ */
1951
1962
  noDna: true,
1952
1963
  });
1964
+ if (!!resNew.intermediateIbGibs) { throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`); }
1953
1965
 
1954
1966
  return resNew.newIbGib as SyncIbGib_V1;
1955
1967
  }
@@ -1960,14 +1972,20 @@ export class SyncSagaCoordinator {
1960
1972
  }
1961
1973
  }
1962
1974
 
1963
- protected async getStageAndPayloadFromFrame({ ibGib, space }: { ibGib: IbGib_V1, space: IbGibSpaceAny }): Promise<{ stage: SyncStage, messageData: any }> {
1975
+ protected async getStageAndPayloadFromFrame({
1976
+ sagaFrame,
1977
+ space
1978
+ }: {
1979
+ sagaFrame: IbGib_V1,
1980
+ space: IbGibSpaceAny
1981
+ }): Promise<{ stage: SyncStage, messageData: unknown }> {
1964
1982
  const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
1965
1983
  try {
1966
1984
  if (logalot) { console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`); }
1967
1985
 
1968
- if (!ibGib.rel8ns) { throw new Error(`(UNEXPECTED) ibGib.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`); }
1986
+ if (!sagaFrame.rel8ns) { throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`); }
1969
1987
 
1970
- const stoneAddrs = ibGib.rel8ns?.[SYNC_MSG_REL8N_NAME];
1988
+ const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
1971
1989
  if (stoneAddrs && stoneAddrs.length > 0) {
1972
1990
  const stoneAddr = stoneAddrs[0];
1973
1991
  const res = await getFromSpace({ addr: stoneAddr, space });
@@ -1980,7 +1998,7 @@ export class SyncSagaCoordinator {
1980
1998
  throw new Error(`couldn't get stoneAddr (${stoneAddr}) from space (${space.ib}) (E: f3c826e756f8a5c358beb88aa4a65e26)`);
1981
1999
  }
1982
2000
  } else {
1983
- throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? ibGib addr: ${getIbGibAddr(ibGib)} (E: 43eae8579e289d016741b5526052f226)`);
2001
+ throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
1984
2002
  }
1985
2003
  } catch (error) {
1986
2004
  console.error(`${lc} ${extractErrorMsg(error)}`);