@ibgib/core-gib 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/other/graph-helper.d.mts +8 -0
- package/dist/common/other/graph-helper.d.mts.map +1 -1
- package/dist/common/other/graph-helper.mjs +31 -1
- package/dist/common/other/graph-helper.mjs.map +1 -1
- package/dist/sync/sync-conflict.respec.mjs +12 -11
- package/dist/sync/sync-conflict.respec.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +14 -4
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +15 -3
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +22 -15
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +159 -90
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace-constants.respec.mjs +11 -10
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +11 -10
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +11 -10
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +12 -10
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +14 -10
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +12 -10
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +0 -15
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +15 -14
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +9 -55
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts +16 -0
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +39 -5
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +141 -4
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +22 -3
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +104 -31
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +5 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +74 -70
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +475 -527
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +105 -22
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +56 -88
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +5 -0
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/timeline/timeline-api.d.mts.map +1 -1
- package/dist/timeline/timeline-api.mjs +15 -8
- package/dist/timeline/timeline-api.mjs.map +1 -1
- package/package.json +1 -1
- package/src/common/other/graph-helper.mts +26 -1
- package/src/sync/sync-conflict.respec.mts +12 -11
- package/src/sync/sync-constants.mts +15 -4
- package/src/sync/sync-helpers.mts +167 -95
- package/src/sync/sync-innerspace-constants.respec.mts +11 -10
- package/src/sync/sync-innerspace-deep-updates.respec.mts +11 -10
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +11 -10
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +12 -10
- package/src/sync/sync-innerspace-partial-update.respec.mts +14 -12
- package/src/sync/sync-innerspace.respec.mts +12 -10
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +0 -15
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +16 -65
- package/src/sync/sync-peer/sync-peer-types.mts +17 -0
- package/src/sync/sync-peer/sync-peer-v1.mts +141 -8
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +116 -37
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +5 -0
- package/src/sync/sync-saga-coordinator.mts +525 -617
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +106 -22
- package/src/sync/sync-types.mts +59 -101
- package/src/timeline/timeline-api.mts +17 -10
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
pretty,
|
|
7
7
|
clone,
|
|
8
8
|
unique,
|
|
9
|
+
delay,
|
|
9
10
|
} from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
10
11
|
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
11
|
-
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
|
|
12
12
|
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
|
|
13
|
-
import { IbGib_V1,
|
|
14
|
-
import {
|
|
13
|
+
import { IbGib_V1, } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
14
|
+
import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
|
|
15
15
|
|
|
16
16
|
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
|
|
17
17
|
import { IbGibSpaceAny } from "../witness/space/space-base-v1.mjs";
|
|
@@ -20,35 +20,35 @@ import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
|
|
|
20
20
|
import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
|
|
21
21
|
import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
|
|
22
22
|
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
|
|
23
|
-
import { appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
|
|
23
|
+
import { appendToTimeline, createTimeline, getHistory, Rel8nInfo, Rel8nRemovalInfo } from "../timeline/timeline-api.mjs";
|
|
24
24
|
import {
|
|
25
|
-
SyncData_V1, SyncIbGib_V1,
|
|
26
|
-
|
|
27
|
-
SyncOptions,
|
|
25
|
+
SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
|
|
26
|
+
SyncRel8ns_V1, DomainIbGibAnalysisInfo, HandleSagaFrameResult,
|
|
28
27
|
} from "./sync-types.mjs";
|
|
29
|
-
import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
|
|
30
|
-
import {
|
|
31
|
-
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";
|
|
32
30
|
import {
|
|
33
31
|
SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
|
|
34
32
|
SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
|
|
35
|
-
SyncSagaMessageCommitData_V1
|
|
33
|
+
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
|
|
36
34
|
} from "./sync-saga-message/sync-saga-message-types.mjs";
|
|
37
35
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
38
36
|
import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
|
|
39
37
|
import { SyncSagaInfo } from "./sync-types.mjs";
|
|
38
|
+
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
|
|
40
39
|
import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
|
|
41
40
|
import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
|
|
42
41
|
import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
43
42
|
import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
|
|
44
43
|
import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
|
|
45
|
-
|
|
46
44
|
import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
47
45
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
48
46
|
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
49
|
-
import { SubscriptionWitness } from "../common/pubsub/subscription/subscription-types.mjs";
|
|
50
47
|
import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
|
|
51
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
|
|
50
|
+
} from "../witness/space/space-types.mjs";
|
|
51
|
+
import { FlatIbGibGraph } from "../common/other/graph-types.mjs";
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
@@ -56,16 +56,6 @@ const logalot = false;
|
|
|
56
56
|
const logalotControlDomain = true;
|
|
57
57
|
const lcControlDomain = '[ControlDomain]';
|
|
58
58
|
|
|
59
|
-
/**
|
|
60
|
-
* Result of handling a saga frame.
|
|
61
|
-
* Separates control and domain payloads.
|
|
62
|
-
*/
|
|
63
|
-
export interface HandleSagaFrameResult {
|
|
64
|
-
frame: SyncIbGib_V1;
|
|
65
|
-
payloadIbGibsControl?: IbGib_V1[];
|
|
66
|
-
payloadIbGibsDomain?: IbGib_V1[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
59
|
/**
|
|
70
60
|
* Orchestrates the synchronization process between two spaces (Source and Destination).
|
|
71
61
|
*
|
|
@@ -103,18 +93,16 @@ export class SyncSagaCoordinator {
|
|
|
103
93
|
*/
|
|
104
94
|
async sync({
|
|
105
95
|
peer,
|
|
106
|
-
localSpace: _localSpace,
|
|
107
|
-
source: _source,
|
|
108
|
-
metaspace,
|
|
109
96
|
domainIbGibs,
|
|
110
|
-
conflictStrategy =
|
|
97
|
+
conflictStrategy = SyncConflictStrategy.abort,
|
|
111
98
|
useSessionIdentity = true,
|
|
99
|
+
metaspace,
|
|
100
|
+
localSpace,
|
|
112
101
|
}: SyncOptions): Promise<SyncSagaInfo> {
|
|
113
102
|
const lc = `${this.lc}[${this.sync.name}]`;
|
|
114
103
|
if (logalot) { console.log(`${lc} starting...`); }
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
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)`); }
|
|
118
106
|
|
|
119
107
|
// 1. SETUP SAGA METADATA
|
|
120
108
|
const sagaId = await getUUID();
|
|
@@ -130,11 +118,6 @@ export class SyncSagaCoordinator {
|
|
|
130
118
|
rejectDone = reject;
|
|
131
119
|
});
|
|
132
120
|
|
|
133
|
-
async function getTempSpaceName(): Promise<string> {
|
|
134
|
-
const uuid = await getUUID();
|
|
135
|
-
return `tmp_sync_space_${uuid.substring(0, 8)}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
121
|
// WORKING CONTEXT (Transactional)
|
|
139
122
|
const tempSpaceName = await getTempSpaceName();
|
|
140
123
|
const tempSpace = await metaspace.createNewLocalSpace({
|
|
@@ -164,40 +147,25 @@ export class SyncSagaCoordinator {
|
|
|
164
147
|
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
165
148
|
|
|
166
149
|
// 3. CREATE INITIAL FRAME (Stage.init)
|
|
167
|
-
const { sagaFrame: initFrame,
|
|
150
|
+
const { sagaFrame: initFrame, initialDomainGraph } = await this.createInitFrame({
|
|
168
151
|
sagaId,
|
|
169
152
|
sessionIdentity,
|
|
170
|
-
localSpace,
|
|
171
153
|
domainIbGibs,
|
|
172
|
-
|
|
173
|
-
metaspace,
|
|
174
|
-
conflictStrategy
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// 4. EXECUTE SAGA LOOP (FSM)
|
|
178
|
-
// Inject tempSpace into peer so it can pull control payloads to the right place
|
|
179
|
-
// if ('opts' in peer && peer.opts) {
|
|
180
|
-
if (!peer.data) { throw new Error(`(UNEXPECTED) peer.data falsy? (E: 8546a884c82ffb1999e95d9867da2826)`); }
|
|
181
|
-
if (peer.data.classname === SyncPeerInnerspace_V1.name) {
|
|
182
|
-
// (peer as SyncPeerInnerspace_V1).senderTempSpace = tempSpace;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const syncedIbGibs = await this.executeSagaLoop({
|
|
186
|
-
initialFrame: initFrame,
|
|
187
|
-
srcGraph,
|
|
188
|
-
peer,
|
|
189
|
-
sessionIdentity,
|
|
190
|
-
updates$,
|
|
191
|
-
localSpace,
|
|
192
|
-
tempSpace,
|
|
193
|
-
metaspace
|
|
154
|
+
conflictStrategy,
|
|
155
|
+
metaspace, localSpace, tempSpace,
|
|
194
156
|
});
|
|
195
157
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
// });
|
|
201
169
|
|
|
202
170
|
resolveDone();
|
|
203
171
|
if (!updates$.complete) { throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`); }
|
|
@@ -263,17 +231,17 @@ export class SyncSagaCoordinator {
|
|
|
263
231
|
* **Execution Context**: **Sender (Local)**.
|
|
264
232
|
*
|
|
265
233
|
* This method manages the "Ping Pong" request-response cycle on the Sender.
|
|
266
|
-
* It sends frames via the Peer Witness and processes the responses using `
|
|
234
|
+
* It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
|
|
267
235
|
*
|
|
268
236
|
* **Data Transport Note**:
|
|
269
237
|
* Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
|
|
270
|
-
* When `
|
|
238
|
+
* When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
|
|
271
239
|
* the NEXT request context.
|
|
272
240
|
* When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
|
|
273
241
|
*/
|
|
274
242
|
protected async executeSagaLoop({
|
|
275
243
|
initialFrame,
|
|
276
|
-
|
|
244
|
+
initialDomainGraph,
|
|
277
245
|
peer,
|
|
278
246
|
sessionIdentity,
|
|
279
247
|
updates$,
|
|
@@ -282,131 +250,103 @@ export class SyncSagaCoordinator {
|
|
|
282
250
|
metaspace
|
|
283
251
|
}: {
|
|
284
252
|
initialFrame: SyncIbGib_V1,
|
|
285
|
-
|
|
253
|
+
initialDomainGraph: FlatIbGibGraph,
|
|
286
254
|
peer: SyncPeerWitness,
|
|
287
255
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
288
256
|
updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
|
|
257
|
+
metaspace: MetaspaceService
|
|
289
258
|
localSpace: IbGibSpaceAny,
|
|
290
259
|
tempSpace: IbGibSpaceAny,
|
|
291
|
-
|
|
292
|
-
}): Promise<IbGib_V1[]> {
|
|
260
|
+
}): Promise<void> {
|
|
293
261
|
const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
|
|
294
262
|
|
|
295
263
|
// The current frame we just generated (e.g., Init or Delta Request)
|
|
296
264
|
let currentFrame: SyncIbGib_V1 | null = initialFrame;
|
|
297
265
|
// The payload we need to attach to the message (data we are sending)
|
|
298
266
|
let nextDomainIbGibs: IbGib_V1[] = [];
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
272
|
|
|
302
273
|
while (currentFrame) {
|
|
303
274
|
// A. Create Context (Request)
|
|
304
275
|
// 1. Calculate Full Dependency Graph (including Ancestors/DNA)
|
|
305
|
-
// TODO: adjust this algorithm to only do the dependencies that the other end doesn't need (diff between tip and LCA)
|
|
306
276
|
// We must do this BEFORE creating the Context so we can list them
|
|
307
277
|
// all in payloadAddrsDomain and payloadAddrsControl.
|
|
308
|
-
|
|
309
|
-
// const depsControlIbGibs: IbGib_V1[] = [];
|
|
310
|
-
const payloadIbGibsControl: IbGib_V1[] = [];
|
|
311
|
-
const payloadIbGibsDomain: IbGib_V1[] = [];
|
|
278
|
+
const nextDomainIbGibsAndDeps: IbGib_V1[] = [];
|
|
312
279
|
|
|
313
280
|
// A. Payload (Standard Deep Deps)
|
|
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.
|
|
314
285
|
for (const nextDomainIbGib of nextDomainIbGibs) {
|
|
315
286
|
let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
|
|
316
287
|
if (!nextDomainIbGibGraph) {
|
|
317
288
|
nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
|
|
318
289
|
}
|
|
319
|
-
|
|
320
290
|
if (nextDomainIbGibGraph) {
|
|
321
|
-
|
|
291
|
+
nextDomainIbGibsAndDeps.push(...Object.values(nextDomainIbGibGraph));
|
|
322
292
|
} else {
|
|
323
293
|
throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
|
|
324
294
|
}
|
|
325
295
|
}
|
|
326
|
-
if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${
|
|
327
|
-
|
|
328
|
-
// B. Frames (Shallow Sync Deps)
|
|
329
|
-
if (currentFrame) {
|
|
330
|
-
const depsCurrentFrame = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: currentFrame, space: tempSpace });
|
|
331
|
-
if (depsCurrentFrame.length > 0) {
|
|
332
|
-
depsCurrentFrame.forEach(x => payloadIbGibsControl.push(x));
|
|
333
|
-
} else {
|
|
334
|
-
throw new Error(`(UNEXPECTED) couldn't get deps for currentFrame? currentFrame: ${JSON.stringify(currentFrame)} (E: 06344d07adc80d80b809211171444d26)`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
296
|
+
if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${nextDomainIbGibsAndDeps.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`); }
|
|
337
297
|
|
|
338
298
|
// 2. Create Context (Envelope)
|
|
299
|
+
// set up subscription for response's payload ibgibs (if any)
|
|
339
300
|
const domainPayloadsMap = new Map<string, IbGib_V1>();
|
|
340
301
|
const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// 2b. Request Context
|
|
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...
|
|
367
324
|
const requestCtx = await createSyncSagaContext({
|
|
368
325
|
sagaFrame: currentFrame,
|
|
369
326
|
sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
|
|
370
|
-
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* init frame: empty
|
|
330
|
+
*
|
|
331
|
+
*/
|
|
332
|
+
payloadIbGibsDomain: nextDomainIbGibsAndDeps,
|
|
333
|
+
localSpace,
|
|
371
334
|
});
|
|
372
335
|
|
|
373
336
|
// Log what we're sending
|
|
374
337
|
if (logalotControlDomain) {
|
|
375
|
-
const
|
|
376
|
-
const domainAddrs = payloadIbGibsDomain.map(p => getIbGibAddr({ ibGib: p }));
|
|
338
|
+
const domainAddrs = nextDomainIbGibsAndDeps.map(p => getIbGibAddr({ ibGib: p }));
|
|
377
339
|
console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
|
|
378
340
|
console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
|
|
379
341
|
console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
380
|
-
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
|
|
381
342
|
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
|
|
382
343
|
}
|
|
383
344
|
|
|
384
|
-
//
|
|
385
|
-
// do we need to add requestCtx to the payload ibgibs?
|
|
386
|
-
// if (requestCtx) {
|
|
387
|
-
// const deps = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: requestCtx, space: tempSpace });
|
|
388
|
-
// if (deps) { deps.forEach(x => payloadIbGibsControl.push(x)); }
|
|
389
|
-
// }
|
|
390
|
-
|
|
391
|
-
// 3. Identity (if exists)
|
|
392
|
-
// Identity might be deep? Keystone? Usually self-contained or shallow references.
|
|
393
|
-
if (sessionIdentity) {
|
|
394
|
-
payloadIbGibsControl.push(sessionIdentity);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// we only put the **CONTROL** payload ibgibs in localSpace. we
|
|
398
|
-
// don't put any domain ibgibs into this durable space until the
|
|
399
|
-
// final commit phase.
|
|
400
|
-
// The requestCtx envelope itself also goes to localSpace so transfer can find it.
|
|
401
|
-
await putInSpace({ space: localSpace, ibGib: requestCtx });
|
|
402
|
-
if (payloadIbGibsControl.length > 0) {
|
|
403
|
-
await putInSpace({ space: localSpace, ibGibs: payloadIbGibsControl });
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// B. Transmit
|
|
345
|
+
// update our saga listeners...
|
|
407
346
|
// if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
|
|
408
|
-
updates$.next(requestCtx); // spins off
|
|
347
|
+
updates$.next(requestCtx); // spins off
|
|
409
348
|
|
|
349
|
+
// ...And send the context
|
|
410
350
|
const responseCtx = await peer.witness(requestCtx);
|
|
411
351
|
|
|
412
352
|
// C. Handle Response
|
|
@@ -429,55 +369,51 @@ export class SyncSagaCoordinator {
|
|
|
429
369
|
// ---------------------------------------------------------------------
|
|
430
370
|
// 2d. HANDLE RESPONSE
|
|
431
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.
|
|
432
375
|
if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
|
|
433
376
|
updates$.next(responseCtx); // spins off -- don't remove this comment!
|
|
434
377
|
|
|
435
378
|
// Extract expected domain addresses from response context
|
|
436
379
|
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
|
|
437
380
|
|
|
438
|
-
// TODO: check if we are validating the responseCtx in sync peer. I'm thinking that we must, because we can't start the domain ibgibs until we know the response's control ibgibs are valid.
|
|
439
|
-
|
|
440
381
|
// Poll for them if needed
|
|
441
382
|
if (responsePayloadAddrsDomain.length > 0) {
|
|
442
|
-
await this.pollForDomainPayloads({
|
|
383
|
+
responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
|
|
443
384
|
expectedAddrs: responsePayloadAddrsDomain,
|
|
385
|
+
pollIntervalMs: 20, // relatively arbitrary right now
|
|
444
386
|
domainPayloadsMap,
|
|
445
387
|
tempSpace,
|
|
446
388
|
});
|
|
447
389
|
}
|
|
448
390
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
452
394
|
|
|
453
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)`); }
|
|
454
397
|
if (logalotControlDomain) {
|
|
455
398
|
const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] as string[] || [];
|
|
456
399
|
console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
|
|
457
400
|
console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
|
|
458
|
-
console.log(`${lc}${lcControlDomain} Response Frame: ${
|
|
401
|
+
console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
|
|
459
402
|
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
|
|
460
403
|
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
|
|
461
404
|
}
|
|
462
405
|
|
|
463
|
-
// Get response frame from localSpace (SyncPeer puts it there)
|
|
464
|
-
let resResponseFrame = await getFromSpace({ space: localSpace, addr: responseFrameAddr });
|
|
465
|
-
if (!resResponseFrame.success || !resResponseFrame.ibGibs?.length) {
|
|
466
|
-
// Fallback to tempSpace
|
|
467
|
-
resResponseFrame = await getFromSpace({ space: tempSpace, addr: responseFrameAddr });
|
|
468
|
-
}
|
|
469
|
-
const responseFrame = resResponseFrame.ibGibs?.[0] as any;
|
|
470
|
-
if (!responseFrame) throw new Error(`${lc} Response frame not found (E: 7c2a)`);
|
|
471
|
-
|
|
472
406
|
// Handle Response Frame
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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,
|
|
478
416
|
metaspace,
|
|
479
|
-
domainPayloadsMap,
|
|
480
|
-
expectedDomainAddrs: responsePayloadAddrsDomain,
|
|
481
417
|
});
|
|
482
418
|
|
|
483
419
|
if (!handleResult) {
|
|
@@ -488,23 +424,16 @@ export class SyncSagaCoordinator {
|
|
|
488
424
|
currentFrame = handleResult.frame;
|
|
489
425
|
|
|
490
426
|
// Collect next DOMAIN payloads for the NEXT request
|
|
491
|
-
// Control payloads are handled separately by ensureSagaFrameInBothSpaces
|
|
492
427
|
nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
|
|
493
428
|
|
|
494
429
|
// Log handler output for next iteration
|
|
495
430
|
if (logalotControlDomain) {
|
|
496
|
-
const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
|
|
497
431
|
const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
|
|
498
432
|
console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
|
|
499
433
|
console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
500
|
-
console.log(`${lc}${lcControlDomain} CONTROL for next (${handlerControlAddrs.length}): ${handlerControlAddrs.join(', ') || '(none)'} (saved via ensureSagaFrameInBothSpaces)`);
|
|
501
434
|
console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
|
|
502
435
|
}
|
|
503
|
-
|
|
504
|
-
// Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
|
|
505
436
|
}
|
|
506
|
-
|
|
507
|
-
return allReceivedIbGibs;
|
|
508
437
|
}
|
|
509
438
|
|
|
510
439
|
/**
|
|
@@ -584,42 +513,41 @@ export class SyncSagaCoordinator {
|
|
|
584
513
|
}
|
|
585
514
|
}
|
|
586
515
|
|
|
587
|
-
protected async
|
|
516
|
+
protected async analyzeDomainIbGibs({
|
|
588
517
|
domainIbGibs,
|
|
589
518
|
space,
|
|
590
519
|
}: {
|
|
591
520
|
domainIbGibs: IbGib_V1[],
|
|
592
521
|
space: IbGibSpaceAny,
|
|
593
|
-
}): Promise<{
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const
|
|
600
|
-
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({
|
|
601
529
|
ibGibs: domainIbGibs,
|
|
602
530
|
live: true,
|
|
603
531
|
space,
|
|
604
532
|
});
|
|
605
533
|
|
|
606
|
-
const
|
|
607
|
-
if (logalot) { console.log(`${lc} graph generated. nodes: ${
|
|
534
|
+
const graphIsValid = fullGraph && Object.keys(fullGraph).length > 0;
|
|
535
|
+
if (logalot) { console.log(`${lc} graph generated. nodes: ${graphIsValid ? Object.keys(fullGraph).length : 0}`); }
|
|
608
536
|
|
|
609
|
-
const srcIbGibs =
|
|
537
|
+
const srcIbGibs = graphIsValid ? Object.values(fullGraph) : [];
|
|
610
538
|
const {
|
|
611
539
|
mapWithTjp_YesDna: srcMapWithTjp_YesDna,
|
|
612
540
|
mapWithTjp_NoDna: srcMapWithTjp_NoDna,
|
|
613
541
|
mapWithoutTjps: src_MapWithoutTjps
|
|
614
542
|
} = splitPerTjpAndOrDna({ ibGibs: srcIbGibs });
|
|
615
543
|
|
|
616
|
-
const
|
|
617
|
-
const
|
|
544
|
+
const stones = Object.values(src_MapWithoutTjps);
|
|
545
|
+
const withTimelines = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
|
|
618
546
|
|
|
619
|
-
const
|
|
620
|
-
const
|
|
547
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: withTimelines });
|
|
548
|
+
const topologicallySortedTjpAddrs = this.sortTimelinesTopologically(timelinesMap);
|
|
621
549
|
|
|
622
|
-
return {
|
|
550
|
+
return { stones, timelinesMap, topologicallySortedTjpAddrs, fullGraph };
|
|
623
551
|
}
|
|
624
552
|
|
|
625
553
|
/**
|
|
@@ -634,78 +562,72 @@ export class SyncSagaCoordinator {
|
|
|
634
562
|
protected async createInitFrame({
|
|
635
563
|
sagaId,
|
|
636
564
|
sessionIdentity,
|
|
637
|
-
localSpace,
|
|
638
565
|
domainIbGibs,
|
|
639
|
-
tempSpace,
|
|
640
|
-
metaspace,
|
|
641
566
|
conflictStrategy,
|
|
567
|
+
metaspace,
|
|
568
|
+
localSpace,
|
|
569
|
+
tempSpace,
|
|
642
570
|
}: {
|
|
643
571
|
sagaId: string,
|
|
644
572
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
645
|
-
localSpace: IbGibSpaceAny,
|
|
646
573
|
domainIbGibs: IbGib_V1[],
|
|
647
|
-
tempSpace: IbGibSpaceAny,
|
|
648
|
-
metaspace: MetaspaceService,
|
|
649
574
|
conflictStrategy: SyncConflictStrategy,
|
|
650
|
-
|
|
575
|
+
metaspace: MetaspaceService,
|
|
576
|
+
localSpace: IbGibSpaceAny,
|
|
577
|
+
tempSpace: IbGibSpaceAny,
|
|
578
|
+
}): Promise<{ sagaFrame: SyncIbGib_V1, initialDomainGraph: { [addr: string]: IbGib_V1 } }> {
|
|
651
579
|
const lc = `${this.lc}[${this.createInitFrame.name}]`;
|
|
652
580
|
try {
|
|
653
581
|
if (logalot) { console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`); }
|
|
654
582
|
|
|
655
583
|
// Analyze Timelines & Stones
|
|
656
|
-
const analysis = await this.
|
|
657
|
-
// 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 });
|
|
658
585
|
// if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
|
|
659
|
-
const { srcTimelinesMap,
|
|
586
|
+
const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
|
|
587
|
+
|
|
588
|
+
// we need to store the fullGraph in our tempSpace for later...
|
|
589
|
+
await putInSpace({ ibGibs: Object.values(fullGraph), space: tempSpace });
|
|
660
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
|
+
});
|
|
661
598
|
const initData: SyncSagaMessageInitData_V1 = {
|
|
662
599
|
sagaId,
|
|
663
600
|
stage: SyncStage.init,
|
|
664
|
-
knowledgeVector
|
|
601
|
+
knowledgeVector,
|
|
665
602
|
identity: sessionIdentity, // KeystoneIbGib is already public data
|
|
666
603
|
mode: SyncMode.sync,
|
|
667
604
|
stones: srcStones.map(s => getIbGibAddr({ ibGib: s })),
|
|
668
605
|
};
|
|
669
|
-
|
|
670
|
-
// Populate Knowledge Vector
|
|
671
|
-
Object.keys(srcTimelinesMap).forEach(tjp => {
|
|
672
|
-
const timeline = srcTimelinesMap[tjp];
|
|
673
|
-
const tip = timeline.at(-1)!;
|
|
674
|
-
initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
675
|
-
});
|
|
676
|
-
|
|
677
606
|
if (logalot) {
|
|
678
607
|
console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
|
|
679
608
|
console.log(`${lc} initData.stage: ${initData.stage}`);
|
|
680
609
|
}
|
|
681
610
|
|
|
611
|
+
// create the stone and save/register it
|
|
682
612
|
const initStone = await this.createSyncMsgStone({
|
|
683
613
|
data: initData,
|
|
684
|
-
|
|
685
|
-
metaspace
|
|
614
|
+
localSpace,
|
|
615
|
+
metaspace,
|
|
686
616
|
});
|
|
687
617
|
// if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
|
|
688
618
|
|
|
619
|
+
// create the initial sync ibgib frame, save/register it
|
|
689
620
|
const sagaFrame = await this.evolveSyncSagaIbGib({
|
|
690
621
|
msgStones: [initStone],
|
|
691
|
-
identity: sessionIdentity,
|
|
692
|
-
space: tempSpace,
|
|
693
|
-
metaspace,
|
|
694
622
|
conflictStrategy,
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
await this.ensureSagaFrameInBothSpaces({
|
|
699
|
-
frame: sagaFrame,
|
|
700
|
-
destSpace: localSpace, // localSpace is the Sender's destSpace
|
|
701
|
-
tempSpace,
|
|
702
|
-
metaspace
|
|
623
|
+
sessionIdentity: sessionIdentity,
|
|
624
|
+
metaspace,
|
|
625
|
+
localSpace,
|
|
703
626
|
});
|
|
704
627
|
|
|
705
628
|
// if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
706
629
|
|
|
707
|
-
return { sagaFrame,
|
|
708
|
-
|
|
630
|
+
return { sagaFrame, initialDomainGraph: fullGraph };
|
|
709
631
|
} catch (error) {
|
|
710
632
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
711
633
|
throw error;
|
|
@@ -717,20 +639,26 @@ export class SyncSagaCoordinator {
|
|
|
717
639
|
/**
|
|
718
640
|
* Helper to poll for streaming domain payloads and put them in the
|
|
719
641
|
* local {@link tempSpace}.
|
|
642
|
+
*
|
|
643
|
+
* @returns when all {@link expectedAddrs} are done being transmitted.
|
|
720
644
|
*/
|
|
721
645
|
protected async pollForDomainPayloads({
|
|
722
646
|
expectedAddrs,
|
|
647
|
+
pollIntervalMs,
|
|
723
648
|
domainPayloadsMap,
|
|
724
649
|
tempSpace,
|
|
725
650
|
}: {
|
|
726
651
|
expectedAddrs: string[],
|
|
652
|
+
pollIntervalMs: number,
|
|
727
653
|
domainPayloadsMap: Map<string, IbGib_V1>,
|
|
728
654
|
tempSpace: IbGibSpaceAny,
|
|
729
|
-
}): Promise<
|
|
655
|
+
}): Promise<IbGib_V1[]> {
|
|
730
656
|
const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
|
|
731
657
|
try {
|
|
732
658
|
if (logalot) { console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`); }
|
|
733
659
|
|
|
660
|
+
let resultDomainPayloads: IbGib_V1[] = [];
|
|
661
|
+
|
|
734
662
|
let pending = [...expectedAddrs];
|
|
735
663
|
const start = Date.now();
|
|
736
664
|
/**
|
|
@@ -757,14 +685,19 @@ export class SyncSagaCoordinator {
|
|
|
757
685
|
|
|
758
686
|
if (found.length > 0) {
|
|
759
687
|
await putInSpace({ space: tempSpace, ibGibs: found });
|
|
688
|
+
found.forEach(x => resultDomainPayloads.push(x));
|
|
760
689
|
}
|
|
761
690
|
|
|
762
691
|
pending = stillPending;
|
|
763
692
|
|
|
764
|
-
if (pending.length > 0) {
|
|
765
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
766
|
-
}
|
|
693
|
+
if (pending.length > 0) { await delay(pollIntervalMs); }
|
|
767
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;
|
|
768
701
|
} catch (error) {
|
|
769
702
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
770
703
|
throw error;
|
|
@@ -773,49 +706,85 @@ export class SyncSagaCoordinator {
|
|
|
773
706
|
}
|
|
774
707
|
}
|
|
775
708
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
709
|
+
/**
|
|
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.
|
|
713
|
+
*
|
|
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
|
|
722
|
+
*
|
|
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.
|
|
726
|
+
*
|
|
727
|
+
* This is a one-off on the receiver.
|
|
728
|
+
*/
|
|
729
|
+
public async handleSagaContext({
|
|
730
|
+
sagaContext,
|
|
731
|
+
mySpace,
|
|
732
|
+
myTempSpace,
|
|
781
733
|
identity,
|
|
782
734
|
identitySecret,
|
|
783
735
|
metaspace,
|
|
784
736
|
}: {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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,
|
|
789
746
|
identity?: KeystoneIbGib_V1,
|
|
790
747
|
identitySecret?: string,
|
|
791
748
|
metaspace: MetaspaceService,
|
|
792
|
-
domainPayloadsMap?: Map<string, IbGib_V1>,
|
|
793
|
-
expectedDomainAddrs?: string[],
|
|
794
749
|
}): Promise<HandleSagaFrameResult | null> {
|
|
795
|
-
const lc = `${this.lc}[${this.
|
|
750
|
+
const lc = `${this.lc}[${this.handleSagaContext.name}]`;
|
|
796
751
|
try {
|
|
797
752
|
if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
|
|
798
753
|
|
|
754
|
+
const sagaIbGib = sagaContext.sagaFrame;
|
|
799
755
|
if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`); }
|
|
800
756
|
if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
|
|
801
757
|
|
|
802
758
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
803
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({
|
|
759
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
|
|
804
760
|
|
|
805
761
|
if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
|
|
806
762
|
|
|
763
|
+
/**
|
|
764
|
+
* don't like this name, need to refactor
|
|
765
|
+
*/
|
|
766
|
+
const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
|
|
767
|
+
|
|
807
768
|
switch (stage) {
|
|
808
769
|
case SyncStage.init:
|
|
809
|
-
return await this.handleInitFrame({
|
|
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
|
+
});
|
|
810
779
|
|
|
811
780
|
case SyncStage.ack:
|
|
812
|
-
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
|
|
781
|
+
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
|
|
813
782
|
|
|
814
783
|
case SyncStage.delta:
|
|
815
|
-
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
|
|
784
|
+
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
816
785
|
|
|
817
786
|
case SyncStage.commit:
|
|
818
|
-
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
|
|
787
|
+
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
819
788
|
|
|
820
789
|
default:
|
|
821
790
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
@@ -845,309 +814,297 @@ export class SyncSagaCoordinator {
|
|
|
845
814
|
protected async handleInitFrame({
|
|
846
815
|
sagaIbGib,
|
|
847
816
|
messageData,
|
|
848
|
-
|
|
849
|
-
|
|
817
|
+
mySpace,
|
|
818
|
+
myTempSpace,
|
|
850
819
|
metaspace,
|
|
851
820
|
identity,
|
|
852
|
-
identitySecret,
|
|
821
|
+
// identitySecret,
|
|
853
822
|
}: {
|
|
854
823
|
sagaIbGib: SyncIbGib_V1,
|
|
855
|
-
messageData:
|
|
856
|
-
|
|
857
|
-
|
|
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,
|
|
858
836
|
metaspace: MetaspaceService,
|
|
859
837
|
identity?: KeystoneIbGib_V1,
|
|
860
838
|
identitySecret?: string,
|
|
861
839
|
}): Promise<HandleSagaFrameResult | null> {
|
|
862
840
|
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
863
|
-
|
|
864
|
-
|
|
841
|
+
try {
|
|
842
|
+
if (logalot) { console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`); }
|
|
865
843
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (initData.stage !== SyncStage.init) {
|
|
869
|
-
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
|
|
870
|
-
}
|
|
871
|
-
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
872
|
-
if (!initData || !initData.knowledgeVector) {
|
|
873
|
-
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
874
|
-
}
|
|
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...`); }
|
|
875
846
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
// Stones Analysis (Constants / Non-TJPs)
|
|
886
|
-
const stones = initData.stones || [];
|
|
887
|
-
if (stones.length > 0) {
|
|
888
|
-
if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
|
|
889
|
-
// Check if we have these stones
|
|
890
|
-
const resStones = await getFromSpace({ space: destSpace, addrs: stones });
|
|
891
|
-
const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
|
|
892
|
-
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
893
|
-
if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
|
|
894
|
-
addrsNotFound.forEach(addr => {
|
|
895
|
-
if (!deltaReqAddrs.includes(addr)) {
|
|
896
|
-
deltaReqAddrs.push(addr);
|
|
897
|
-
}
|
|
898
|
-
});
|
|
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)`);
|
|
899
855
|
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const remoteKV = initData.knowledgeVector;
|
|
903
|
-
if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
|
|
904
|
-
const remoteTjps = Object.keys(remoteKV);
|
|
905
|
-
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
906
|
-
if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
|
|
907
|
-
|
|
908
|
-
// 1. Get Local Latest Addrs for all TJPs
|
|
909
|
-
let localKV: { [tjp: string]: string | null } = {};
|
|
910
|
-
if (remoteTjps.length > 0) {
|
|
911
|
-
// Batch get latest addrs for the TJPs
|
|
912
|
-
const resGetLatestAddrs = await getLatestAddrs({
|
|
913
|
-
space: destSpace,
|
|
914
|
-
tjpAddrs: remoteTjps,
|
|
915
|
-
});
|
|
916
|
-
if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
|
|
917
|
-
if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
|
|
918
|
-
localKV = resGetLatestAddrs.data.latestAddrsMap;
|
|
919
|
-
console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
|
|
920
|
-
if (logalot) { console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`); }
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// 2. Gap Analysis
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
for (const tjp of remoteTjps) {
|
|
927
|
-
const remoteAddr = remoteKV[tjp];
|
|
928
|
-
const localAddr = localKV[tjp];
|
|
929
856
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
+
}
|
|
935
880
|
}
|
|
936
881
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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)`); }
|
|
941
904
|
}
|
|
942
|
-
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
943
|
-
|
|
944
|
-
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
945
|
-
// (Sender has older version, Receiver has newer) -> Receiver Offers Push
|
|
946
|
-
const isRemoteInPast = await isPastFrame({
|
|
947
|
-
olderAddr: remoteAddr,
|
|
948
|
-
newerAddr: localAddr,
|
|
949
|
-
space: destSpace,
|
|
950
|
-
});
|
|
951
905
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
// Remote is not in our past.
|
|
957
|
-
// 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];
|
|
958
910
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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,
|
|
964
931
|
});
|
|
965
932
|
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
deltaReqAddrs.push(remoteAddr);
|
|
933
|
+
if (isRemoteInPast) {
|
|
934
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
935
|
+
pushOfferAddrs.push(localAddr);
|
|
970
936
|
} else {
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
tjpAddr: tjp,
|
|
981
|
-
localAddr: localAddr!,
|
|
982
|
-
remoteAddr,
|
|
983
|
-
timelineAddrs: [], // Not needed for abort
|
|
984
|
-
reason: 'divergence',
|
|
985
|
-
terminal: true
|
|
986
|
-
});
|
|
987
|
-
} else if (conflictStrategy === 'optimistic') {
|
|
988
|
-
// Optimistic: We want to resolving this.
|
|
989
|
-
// We need to send our history to the Sender so they can Merge.
|
|
990
|
-
|
|
991
|
-
// Fetch Full History for Local Timeline
|
|
992
|
-
// Note: We might optimize this to only send "recent" history if we had a KV?
|
|
993
|
-
// But for now, get full past.
|
|
994
|
-
// Optimization: localKV might not have full history.
|
|
995
|
-
// We need to inspect the 'past' of the local tip.
|
|
996
|
-
|
|
997
|
-
// We need the ACTUAL object to get the past.
|
|
998
|
-
// We have localAddr.
|
|
999
|
-
const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr! });
|
|
1000
|
-
const localTip = resLocalTip.ibGibs?.[0];
|
|
1001
|
-
if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`); }
|
|
1002
|
-
|
|
1003
|
-
const timelineAddrs = [localAddr!, ...(localTip.rel8ns?.past || [])];
|
|
1004
|
-
|
|
1005
|
-
conflicts.push({
|
|
1006
|
-
tjpAddr: tjp,
|
|
1007
|
-
localAddr: localAddr!,
|
|
1008
|
-
remoteAddr,
|
|
1009
|
-
timelineAddrs,
|
|
1010
|
-
reason: 'divergence',
|
|
1011
|
-
terminal: false
|
|
1012
|
-
});
|
|
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
|
+
});
|
|
1013
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);
|
|
1014
951
|
} else {
|
|
1015
|
-
|
|
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
|
+
}
|
|
1016
1001
|
}
|
|
1017
1002
|
}
|
|
1018
1003
|
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Check if we should ABORT (if any conflict is terminal)
|
|
1022
|
-
const hasTerminalConflicts = conflicts.some(c => c.terminal);
|
|
1023
1004
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
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);
|
|
1027
1007
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
// Let's stick to that for purely terminal cases to be safe/explicit?
|
|
1032
|
-
// 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)}`); }
|
|
1033
1011
|
|
|
1034
|
-
|
|
1035
|
-
// But wait, the original code below creates a Conflict Stone.
|
|
1036
|
-
// Let's preserve the explicit 'Conflict' frame for total aborts if that's easier,
|
|
1037
|
-
// OR fully switch to Ack.
|
|
1038
|
-
// Protocol states: Init -> Ack. If Ack contains terminal errors, Sender can Commit(Fail).
|
|
1039
|
-
|
|
1040
|
-
// Let's use Ack with conflicts.
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// 2. Add Push Offers (Missing in Local)
|
|
1044
|
-
// Check if we have them. If not, ask for them.
|
|
1045
|
-
for (const addr of pushOfferAddrs) {
|
|
1046
|
-
const hasIt = await getFromSpace({ addr, space: destSpace });
|
|
1047
|
-
if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
|
|
1048
|
-
// If we don't have it, we put it in `deltaReqAddrs` of the Ack.
|
|
1049
|
-
deltaReqAddrs.push(addr);
|
|
1012
|
+
throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
|
|
1050
1013
|
}
|
|
1051
|
-
}
|
|
1052
1014
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
if (
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
knowledgeVector[realTjp]
|
|
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
|
+
}
|
|
1072
1037
|
}
|
|
1073
1038
|
}
|
|
1074
1039
|
}
|
|
1075
|
-
}
|
|
1076
1040
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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; }
|
|
1096
1068
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
// SOLUTION:
|
|
1105
|
-
// The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
|
|
1106
|
-
// `SyncSagaMessageInitData_V1` extends `SyncInitData`.
|
|
1107
|
-
// `SyncInitData` has `knowledgeVector`.
|
|
1108
|
-
// If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
|
|
1109
|
-
|
|
1110
|
-
// Let's implement Sender populating `Init.knowledgeVector`.
|
|
1111
|
-
// But `SyncSagaCoordinator.startSaga` creates Init.
|
|
1112
|
-
|
|
1113
|
-
// 3. Create Ack Frame
|
|
1114
|
-
const sagaId = sagaIbGib.data!.uuid;
|
|
1115
|
-
// Create Payload Stone
|
|
1116
|
-
const ackData: SyncSagaMessageAckData_V1 = {
|
|
1117
|
-
sagaId,
|
|
1118
|
-
stage: SyncStage.ack,
|
|
1119
|
-
deltaReqAddrs,
|
|
1120
|
-
pushOfferAddrs,
|
|
1121
|
-
knowledgeVector,
|
|
1122
|
-
conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
|
|
1123
|
-
};
|
|
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)`); }
|
|
1124
1075
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
1134
|
-
prevSagaIbGib: sagaIbGib,
|
|
1135
|
-
msgStones: [ackStone],
|
|
1136
|
-
identity,
|
|
1137
|
-
space: tempSpace,
|
|
1138
|
-
metaspace,
|
|
1139
|
-
});
|
|
1076
|
+
// Evolve Saga
|
|
1077
|
+
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
1078
|
+
prevSagaIbGib: sagaIbGib,
|
|
1079
|
+
msgStones: [ackStone],
|
|
1080
|
+
sessionIdentity: identity,
|
|
1081
|
+
localSpace: mySpace,
|
|
1082
|
+
metaspace,
|
|
1083
|
+
});
|
|
1140
1084
|
|
|
1141
|
-
|
|
1142
|
-
await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
|
|
1085
|
+
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
1143
1086
|
|
|
1144
|
-
|
|
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
|
|
1092
|
+
|
|
1093
|
+
if (pushOfferAddrs.length > 0) {
|
|
1094
|
+
}
|
|
1145
1095
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1096
|
+
return {
|
|
1097
|
+
frame: ackFrame,
|
|
1098
|
+
// conflictInfos,
|
|
1099
|
+
// payloadIbGibsDomain,
|
|
1100
|
+
};
|
|
1149
1101
|
|
|
1150
|
-
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1104
|
+
throw error;
|
|
1105
|
+
} finally {
|
|
1106
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1107
|
+
}
|
|
1151
1108
|
}
|
|
1152
1109
|
|
|
1153
1110
|
/**
|
|
@@ -1181,7 +1138,7 @@ export class SyncSagaCoordinator {
|
|
|
1181
1138
|
try {
|
|
1182
1139
|
if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
|
|
1183
1140
|
|
|
1184
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({
|
|
1141
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1185
1142
|
const ackData = messageData as SyncSagaMessageAckData_V1;
|
|
1186
1143
|
|
|
1187
1144
|
if (!ackData) {
|
|
@@ -1444,28 +1401,26 @@ export class SyncSagaCoordinator {
|
|
|
1444
1401
|
|
|
1445
1402
|
const deltaStone = await this.createSyncMsgStone({
|
|
1446
1403
|
data: deltaData,
|
|
1447
|
-
|
|
1404
|
+
localSpace: tempSpace,
|
|
1448
1405
|
metaspace,
|
|
1449
1406
|
});
|
|
1450
1407
|
|
|
1451
1408
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1452
1409
|
prevSagaIbGib: sagaIbGib,
|
|
1453
1410
|
msgStones: [deltaStone],
|
|
1454
|
-
identity,
|
|
1455
|
-
|
|
1411
|
+
sessionIdentity: identity,
|
|
1412
|
+
localSpace: tempSpace,
|
|
1456
1413
|
metaspace,
|
|
1457
1414
|
});
|
|
1458
1415
|
|
|
1459
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1460
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1461
|
-
|
|
1462
1416
|
if (logalot) { console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`); }
|
|
1463
1417
|
|
|
1464
1418
|
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1465
1419
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1466
1420
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1467
1421
|
|
|
1468
|
-
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1422
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1423
|
+
return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
|
|
1469
1424
|
} catch (error) {
|
|
1470
1425
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1471
1426
|
throw error;
|
|
@@ -1502,7 +1457,7 @@ export class SyncSagaCoordinator {
|
|
|
1502
1457
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1503
1458
|
if (logalot) { console.log(`${lc} starting...`); }
|
|
1504
1459
|
|
|
1505
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({
|
|
1460
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1506
1461
|
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1507
1462
|
|
|
1508
1463
|
if (!deltaData) {
|
|
@@ -1708,26 +1663,24 @@ export class SyncSagaCoordinator {
|
|
|
1708
1663
|
|
|
1709
1664
|
const deltaStone = await this.createSyncMsgStone({
|
|
1710
1665
|
data: responseDeltaData,
|
|
1711
|
-
|
|
1666
|
+
localSpace: tempSpace,
|
|
1712
1667
|
metaspace
|
|
1713
1668
|
});
|
|
1714
1669
|
|
|
1715
1670
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1716
1671
|
prevSagaIbGib: sagaIbGib,
|
|
1717
1672
|
msgStones: [deltaStone],
|
|
1718
|
-
identity,
|
|
1719
|
-
|
|
1673
|
+
sessionIdentity: identity,
|
|
1674
|
+
localSpace: tempSpace,
|
|
1720
1675
|
metaspace
|
|
1721
1676
|
});
|
|
1722
1677
|
|
|
1723
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1724
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1725
|
-
|
|
1726
1678
|
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1727
1679
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1728
1680
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1729
1681
|
|
|
1730
|
-
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1682
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1683
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1731
1684
|
|
|
1732
1685
|
} else {
|
|
1733
1686
|
// We have nothing to send.
|
|
@@ -1742,26 +1695,24 @@ export class SyncSagaCoordinator {
|
|
|
1742
1695
|
|
|
1743
1696
|
const commitStone = await this.createSyncMsgStone({
|
|
1744
1697
|
data: commitData,
|
|
1745
|
-
|
|
1698
|
+
localSpace: tempSpace,
|
|
1746
1699
|
metaspace
|
|
1747
1700
|
});
|
|
1748
1701
|
|
|
1749
1702
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1750
1703
|
prevSagaIbGib: sagaIbGib,
|
|
1751
1704
|
msgStones: [commitStone],
|
|
1752
|
-
identity,
|
|
1753
|
-
|
|
1705
|
+
sessionIdentity: identity,
|
|
1706
|
+
localSpace: tempSpace,
|
|
1754
1707
|
metaspace
|
|
1755
1708
|
});
|
|
1756
1709
|
|
|
1757
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1758
|
-
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1759
|
-
|
|
1760
1710
|
// Build control payloads for commit
|
|
1761
1711
|
const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
|
|
1762
1712
|
if (identity) { commitCtrlPayloads.push(identity); }
|
|
1763
1713
|
|
|
1764
|
-
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1714
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1715
|
+
return { frame: commitFrame, };
|
|
1765
1716
|
|
|
1766
1717
|
} else {
|
|
1767
1718
|
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
@@ -1776,21 +1727,18 @@ export class SyncSagaCoordinator {
|
|
|
1776
1727
|
|
|
1777
1728
|
const deltaStone = await this.createSyncMsgStone({
|
|
1778
1729
|
data: responseDeltaData,
|
|
1779
|
-
|
|
1730
|
+
localSpace: tempSpace,
|
|
1780
1731
|
metaspace
|
|
1781
1732
|
});
|
|
1782
1733
|
|
|
1783
1734
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1784
1735
|
prevSagaIbGib: sagaIbGib,
|
|
1785
1736
|
msgStones: [deltaStone],
|
|
1786
|
-
identity,
|
|
1787
|
-
|
|
1737
|
+
sessionIdentity: identity,
|
|
1738
|
+
localSpace: tempSpace,
|
|
1788
1739
|
metaspace
|
|
1789
1740
|
});
|
|
1790
1741
|
|
|
1791
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1792
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1793
|
-
|
|
1794
1742
|
// Check if PEER proposed commit
|
|
1795
1743
|
if (deltaData.proposeCommit) {
|
|
1796
1744
|
if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
|
|
@@ -1805,33 +1753,32 @@ export class SyncSagaCoordinator {
|
|
|
1805
1753
|
|
|
1806
1754
|
const commitStone = await this.createSyncMsgStone({
|
|
1807
1755
|
data: commitData,
|
|
1808
|
-
|
|
1756
|
+
localSpace: tempSpace,
|
|
1809
1757
|
metaspace
|
|
1810
1758
|
});
|
|
1811
1759
|
|
|
1812
1760
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1813
1761
|
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1814
1762
|
msgStones: [commitStone],
|
|
1815
|
-
identity,
|
|
1816
|
-
|
|
1763
|
+
sessionIdentity: identity,
|
|
1764
|
+
localSpace: tempSpace,
|
|
1817
1765
|
metaspace
|
|
1818
1766
|
});
|
|
1819
1767
|
|
|
1820
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1821
|
-
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1822
|
-
|
|
1823
1768
|
// Build control payloads for commit
|
|
1824
1769
|
const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
|
|
1825
1770
|
if (identity) { commitCtrlPayloads2.push(identity); }
|
|
1826
1771
|
|
|
1827
|
-
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1772
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1773
|
+
return { frame: commitFrame, };
|
|
1828
1774
|
}
|
|
1829
1775
|
|
|
1830
1776
|
// Build control payloads for delta propose
|
|
1831
1777
|
const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1832
1778
|
if (identity) { deltaCtrlPayloads.push(identity); }
|
|
1833
1779
|
|
|
1834
|
-
return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1780
|
+
// return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1781
|
+
return { frame: deltaFrame, };
|
|
1835
1782
|
}
|
|
1836
1783
|
}
|
|
1837
1784
|
}
|
|
@@ -1871,11 +1818,11 @@ export class SyncSagaCoordinator {
|
|
|
1871
1818
|
|
|
1872
1819
|
protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
|
|
1873
1820
|
data,
|
|
1874
|
-
|
|
1821
|
+
localSpace,
|
|
1875
1822
|
metaspace,
|
|
1876
1823
|
}: {
|
|
1877
1824
|
data: TStoneData,
|
|
1878
|
-
|
|
1825
|
+
localSpace: IbGibSpaceAny,
|
|
1879
1826
|
metaspace: MetaspaceService,
|
|
1880
1827
|
}): Promise<IbGib_V1<TStoneData>> {
|
|
1881
1828
|
const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
|
|
@@ -1890,7 +1837,7 @@ export class SyncSagaCoordinator {
|
|
|
1890
1837
|
uuid: true, // we want the stone to have its own uniqueness
|
|
1891
1838
|
});
|
|
1892
1839
|
if (logalot) { console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`); }
|
|
1893
|
-
await
|
|
1840
|
+
await metaspace.put({ ibGib: stone, space: localSpace });
|
|
1894
1841
|
await metaspace.registerNewIbGib({ ibGib: stone });
|
|
1895
1842
|
return stone as IbGib_V1<TStoneData>;
|
|
1896
1843
|
} catch (error) {
|
|
@@ -1902,67 +1849,23 @@ export class SyncSagaCoordinator {
|
|
|
1902
1849
|
}
|
|
1903
1850
|
|
|
1904
1851
|
|
|
1905
|
-
/**
|
|
1906
|
-
* Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
|
|
1907
|
-
* Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
|
|
1908
|
-
*/
|
|
1909
|
-
protected async ensureSagaFrameInBothSpaces({
|
|
1910
|
-
frame,
|
|
1911
|
-
destSpace,
|
|
1912
|
-
tempSpace,
|
|
1913
|
-
metaspace,
|
|
1914
|
-
}: {
|
|
1915
|
-
frame: SyncIbGib_V1,
|
|
1916
|
-
destSpace: IbGibSpaceAny,
|
|
1917
|
-
tempSpace: IbGibSpaceAny,
|
|
1918
|
-
metaspace: MetaspaceService,
|
|
1919
|
-
}): Promise<void> {
|
|
1920
|
-
// Frame itself (already in tempSpace from creation, need in destSpace for audit)
|
|
1921
|
-
await putInSpace({ space: destSpace, ibGib: frame });
|
|
1922
|
-
await metaspace.registerNewIbGib({ ibGib: frame });
|
|
1923
|
-
|
|
1924
|
-
// Msg stone(s) (already in tempSpace, need in destSpace for audit)
|
|
1925
|
-
const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
1926
|
-
if (msgStoneAddrs && msgStoneAddrs.length > 0) {
|
|
1927
|
-
const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
|
|
1928
|
-
if (resMsgStones.ibGibs) {
|
|
1929
|
-
for (const msgStone of resMsgStones.ibGibs) {
|
|
1930
|
-
await putInSpace({ space: destSpace, ibGib: msgStone });
|
|
1931
|
-
await metaspace.registerNewIbGib({ ibGib: msgStone });
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
// Identity (if present, already in tempSpace, need in destSpace)
|
|
1937
|
-
const identityAddrs = frame.rel8ns?.identity;
|
|
1938
|
-
if (identityAddrs && identityAddrs.length > 0) {
|
|
1939
|
-
const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
|
|
1940
|
-
if (resIdentity.ibGibs) {
|
|
1941
|
-
for (const identity of resIdentity.ibGibs) {
|
|
1942
|
-
await putInSpace({ space: destSpace, ibGib: identity });
|
|
1943
|
-
await metaspace.registerNewIbGib({ ibGib: identity });
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
1852
|
/**
|
|
1950
1853
|
* Evolves the saga timeline with a new frame.
|
|
1951
1854
|
*/
|
|
1952
1855
|
protected async evolveSyncSagaIbGib({
|
|
1953
1856
|
prevSagaIbGib,
|
|
1857
|
+
conflictStrategy,
|
|
1954
1858
|
msgStones,
|
|
1955
|
-
|
|
1956
|
-
|
|
1859
|
+
sessionIdentity,
|
|
1860
|
+
localSpace,
|
|
1957
1861
|
metaspace,
|
|
1958
|
-
conflictStrategy,
|
|
1959
1862
|
}: {
|
|
1960
1863
|
prevSagaIbGib?: SyncIbGib_V1,
|
|
1864
|
+
conflictStrategy?: SyncConflictStrategy,
|
|
1961
1865
|
msgStones: IbGib_V1[],
|
|
1962
|
-
|
|
1963
|
-
space: IbGibSpaceAny,
|
|
1866
|
+
localSpace: IbGibSpaceAny,
|
|
1964
1867
|
metaspace: MetaspaceService,
|
|
1965
|
-
|
|
1868
|
+
sessionIdentity?: KeystoneIbGib_V1,
|
|
1966
1869
|
}): Promise<SyncIbGib_V1> {
|
|
1967
1870
|
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|
|
1968
1871
|
try {
|
|
@@ -1988,10 +1891,9 @@ export class SyncSagaCoordinator {
|
|
|
1988
1891
|
}
|
|
1989
1892
|
}
|
|
1990
1893
|
|
|
1991
|
-
const identityAddr =
|
|
1894
|
+
const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
|
|
1992
1895
|
|
|
1993
1896
|
if (prevSagaIbGib) {
|
|
1994
|
-
|
|
1995
1897
|
/**
|
|
1996
1898
|
* rel8ns always include the new msg stone(s)
|
|
1997
1899
|
*/
|
|
@@ -2003,8 +1905,8 @@ export class SyncSagaCoordinator {
|
|
|
2003
1905
|
];
|
|
2004
1906
|
|
|
2005
1907
|
// if we're authenticating/signing, we'll have identity
|
|
2006
|
-
if (
|
|
2007
|
-
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [
|
|
1908
|
+
if (sessionIdentity) {
|
|
1909
|
+
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
|
|
2008
1910
|
}
|
|
2009
1911
|
|
|
2010
1912
|
// remove the existing sync msg stones' addrs
|
|
@@ -2025,7 +1927,7 @@ export class SyncSagaCoordinator {
|
|
|
2025
1927
|
rel8nInfos,
|
|
2026
1928
|
rel8nRemovalInfos,
|
|
2027
1929
|
metaspace,
|
|
2028
|
-
space,
|
|
1930
|
+
space: localSpace,
|
|
2029
1931
|
noDna: true, // Explicitly no DNA for sync frames
|
|
2030
1932
|
});
|
|
2031
1933
|
|
|
@@ -2033,33 +1935,33 @@ export class SyncSagaCoordinator {
|
|
|
2033
1935
|
return newFrame;
|
|
2034
1936
|
} else {
|
|
2035
1937
|
// Create New Timeline (Root Frame)
|
|
1938
|
+
// data
|
|
2036
1939
|
const data: SyncData_V1 = {
|
|
2037
1940
|
uuid: sagaId,
|
|
2038
|
-
// stage, // Removed from V1
|
|
2039
|
-
payload: undefined, // Data in stone
|
|
2040
1941
|
n: 0,
|
|
2041
1942
|
isTjp: true,
|
|
2042
1943
|
conflictStrategy,
|
|
2043
1944
|
};
|
|
1945
|
+
// ib
|
|
2044
1946
|
const ib = await getSyncIb({ data });
|
|
2045
|
-
|
|
1947
|
+
// rel8ns
|
|
2046
1948
|
const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
|
|
2047
|
-
const rel8ns:
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
if (identityAddr) {
|
|
2051
|
-
rel8ns.identity = [identityAddr];
|
|
2052
|
-
}
|
|
1949
|
+
const rel8ns: SyncRel8ns_V1 = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
|
|
1950
|
+
if (identityAddr) { rel8ns.identity = [identityAddr]; }
|
|
2053
1951
|
|
|
2054
1952
|
const resNew = await createTimeline({
|
|
2055
|
-
space,
|
|
1953
|
+
space: localSpace,
|
|
2056
1954
|
metaspace,
|
|
2057
1955
|
ib,
|
|
2058
1956
|
data,
|
|
2059
1957
|
rel8ns,
|
|
2060
1958
|
parentIb: SYNC_ATOM, // "sync"
|
|
1959
|
+
/**
|
|
1960
|
+
* this will squash the result into a single frame
|
|
1961
|
+
*/
|
|
2061
1962
|
noDna: true,
|
|
2062
1963
|
});
|
|
1964
|
+
if (!!resNew.intermediateIbGibs) { throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`); }
|
|
2063
1965
|
|
|
2064
1966
|
return resNew.newIbGib as SyncIbGib_V1;
|
|
2065
1967
|
}
|
|
@@ -2070,14 +1972,20 @@ export class SyncSagaCoordinator {
|
|
|
2070
1972
|
}
|
|
2071
1973
|
}
|
|
2072
1974
|
|
|
2073
|
-
protected async getStageAndPayloadFromFrame({
|
|
1975
|
+
protected async getStageAndPayloadFromFrame({
|
|
1976
|
+
sagaFrame,
|
|
1977
|
+
space
|
|
1978
|
+
}: {
|
|
1979
|
+
sagaFrame: IbGib_V1,
|
|
1980
|
+
space: IbGibSpaceAny
|
|
1981
|
+
}): Promise<{ stage: SyncStage, messageData: unknown }> {
|
|
2074
1982
|
const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
|
|
2075
1983
|
try {
|
|
2076
1984
|
if (logalot) { console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`); }
|
|
2077
1985
|
|
|
2078
|
-
if (!
|
|
1986
|
+
if (!sagaFrame.rel8ns) { throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`); }
|
|
2079
1987
|
|
|
2080
|
-
const stoneAddrs =
|
|
1988
|
+
const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
2081
1989
|
if (stoneAddrs && stoneAddrs.length > 0) {
|
|
2082
1990
|
const stoneAddr = stoneAddrs[0];
|
|
2083
1991
|
const res = await getFromSpace({ addr: stoneAddr, space });
|
|
@@ -2090,7 +1998,7 @@ export class SyncSagaCoordinator {
|
|
|
2090
1998
|
throw new Error(`couldn't get stoneAddr (${stoneAddr}) from space (${space.ib}) (E: f3c826e756f8a5c358beb88aa4a65e26)`);
|
|
2091
1999
|
}
|
|
2092
2000
|
} else {
|
|
2093
|
-
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame?
|
|
2001
|
+
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
|
|
2094
2002
|
}
|
|
2095
2003
|
} catch (error) {
|
|
2096
2004
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|