@ibgib/core-gib 0.1.22 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/other/graph-helper.d.mts +25 -0
- package/dist/common/other/graph-helper.d.mts.map +1 -1
- package/dist/common/other/graph-helper.mjs +75 -1
- package/dist/common/other/graph-helper.mjs.map +1 -1
- package/dist/sync/graft-info/graft-info-helpers.mjs +2 -2
- package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -1
- package/dist/sync/sync-conflict.respec.mjs +8 -12
- 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 +8 -9
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +8 -9
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +8 -9
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +8 -9
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +8 -9
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +6 -7
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts +2 -0
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs +4 -0
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.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 +30 -16
- 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 +47 -79
- 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 +40 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +47 -14
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +188 -144
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +39 -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 +137 -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 +81 -77
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +608 -597
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +154 -26
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +87 -92
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +6 -2
- 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 +79 -1
- package/src/sync/graft-info/graft-info-helpers.mts +3 -3
- package/src/sync/sync-conflict.respec.mts +8 -14
- package/src/sync/sync-constants.mts +15 -4
- package/src/sync/sync-helpers.mts +173 -101
- package/src/sync/sync-innerspace-constants.respec.mts +8 -9
- package/src/sync/sync-innerspace-deep-updates.respec.mts +8 -9
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +8 -9
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +8 -9
- package/src/sync/sync-innerspace-partial-update.respec.mts +9 -12
- package/src/sync/sync-innerspace.respec.mts +6 -7
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mts +7 -0
- 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 +72 -96
- package/src/sync/sync-peer/sync-peer-types.mts +43 -2
- package/src/sync/sync-peer/sync-peer-v1.mts +215 -142
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +145 -37
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +5 -0
- package/src/sync/sync-saga-coordinator.mts +680 -714
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +160 -24
- package/src/sync/sync-types.mts +96 -105
- 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,39 @@ 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
|
-
|
|
25
|
+
SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
|
|
26
|
+
SyncRel8ns_V1, DomainIbGibAnalysisInfo, NextSagaFrameInfo,
|
|
27
|
+
SYNC_CONFLICT_STRATEGY_VALID_VALUES,
|
|
28
|
+
HandleSagaResponseContextResult,
|
|
28
29
|
} from "./sync-types.mjs";
|
|
29
|
-
import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
|
|
30
|
-
import {
|
|
31
|
-
import { getDependencyGraph } from "../common/other/graph-helper.mjs";
|
|
30
|
+
import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
|
|
31
|
+
import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
|
|
32
32
|
import {
|
|
33
33
|
SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
|
|
34
34
|
SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
|
|
35
|
-
SyncSagaMessageCommitData_V1
|
|
35
|
+
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
|
|
36
|
+
SyncSagaPushOfferInfo,
|
|
37
|
+
SyncSagaRequestAddrInfo,
|
|
36
38
|
} from "./sync-saga-message/sync-saga-message-types.mjs";
|
|
37
39
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
38
40
|
import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
|
|
39
41
|
import { SyncSagaInfo } from "./sync-types.mjs";
|
|
42
|
+
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
|
|
40
43
|
import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
|
|
41
44
|
import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
|
|
42
|
-
import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
45
|
+
import { createSyncSagaContext, validateContextAndSagaFrame } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
43
46
|
import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
|
|
44
47
|
import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
|
|
45
|
-
|
|
46
48
|
import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
47
49
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
48
50
|
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
49
|
-
import { SubscriptionWitness } from "../common/pubsub/subscription/subscription-types.mjs";
|
|
50
51
|
import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
|
|
51
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
|
|
54
|
+
} from "../witness/space/space-types.mjs";
|
|
55
|
+
import { FlatIbGibGraph } from "../common/other/graph-types.mjs";
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
@@ -56,26 +60,16 @@ const logalot = false;
|
|
|
56
60
|
const logalotControlDomain = true;
|
|
57
61
|
const lcControlDomain = '[ControlDomain]';
|
|
58
62
|
|
|
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
63
|
/**
|
|
70
64
|
* Orchestrates the synchronization process between two spaces (Source and Destination).
|
|
71
|
-
*
|
|
65
|
+
*
|
|
72
66
|
* ## Execution Contexts
|
|
73
|
-
*
|
|
67
|
+
*
|
|
74
68
|
* The methods in this class are designed to be run on specific nodes in the sync topology:
|
|
75
|
-
*
|
|
69
|
+
*
|
|
76
70
|
* * **Sender/Local Node**: The node *initiating* the sync (calling `sync()`).
|
|
77
71
|
* * **Receiver/Remote Node**: The node *accepting* the sync request (the Peer).
|
|
78
|
-
*
|
|
72
|
+
*
|
|
79
73
|
* Note that in a peer-to-peer architecture, "Sender" and "Receiver" are roles relative
|
|
80
74
|
* to a specific Saga session, not fixed node identities.
|
|
81
75
|
*/
|
|
@@ -90,11 +84,11 @@ export class SyncSagaCoordinator {
|
|
|
90
84
|
|
|
91
85
|
/**
|
|
92
86
|
* Executes a synchronization saga using the Symmetric Sync Protocol.
|
|
93
|
-
*
|
|
87
|
+
*
|
|
94
88
|
* @remarks
|
|
95
89
|
* **Execution Context**: **Sender (Local)**.
|
|
96
90
|
* This method is the entry point for starting a sync session.
|
|
97
|
-
*
|
|
91
|
+
*
|
|
98
92
|
* @param opts.peer - The remote peer witness to communicate with.
|
|
99
93
|
* @param opts.localSpace - The local space that will be read from and written to.
|
|
100
94
|
* @param opts.metaspace - Service for creating temp spaces and managing ibgibs.
|
|
@@ -103,18 +97,16 @@ export class SyncSagaCoordinator {
|
|
|
103
97
|
*/
|
|
104
98
|
async sync({
|
|
105
99
|
peer,
|
|
106
|
-
localSpace: _localSpace,
|
|
107
|
-
source: _source,
|
|
108
|
-
metaspace,
|
|
109
100
|
domainIbGibs,
|
|
110
|
-
conflictStrategy =
|
|
101
|
+
conflictStrategy = SyncConflictStrategy.abort,
|
|
111
102
|
useSessionIdentity = true,
|
|
103
|
+
metaspace,
|
|
104
|
+
localSpace,
|
|
112
105
|
}: SyncOptions): Promise<SyncSagaInfo> {
|
|
113
106
|
const lc = `${this.lc}[${this.sync.name}]`;
|
|
114
107
|
if (logalot) { console.log(`${lc} starting...`); }
|
|
115
108
|
|
|
116
|
-
|
|
117
|
-
if (!localSpace) { throw new Error(`${lc} source (or localSpace) required (E: 8a9b0c1d)`); }
|
|
109
|
+
if (!localSpace) { throw new Error(`${lc} source (or localSpace) required (E: 25df3761f7686a1099a552f83c95d326)`); }
|
|
118
110
|
|
|
119
111
|
// 1. SETUP SAGA METADATA
|
|
120
112
|
const sagaId = await getUUID();
|
|
@@ -130,11 +122,6 @@ export class SyncSagaCoordinator {
|
|
|
130
122
|
rejectDone = reject;
|
|
131
123
|
});
|
|
132
124
|
|
|
133
|
-
async function getTempSpaceName(): Promise<string> {
|
|
134
|
-
const uuid = await getUUID();
|
|
135
|
-
return `tmp_sync_space_${uuid.substring(0, 8)}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
125
|
// WORKING CONTEXT (Transactional)
|
|
139
126
|
const tempSpaceName = await getTempSpaceName();
|
|
140
127
|
const tempSpace = await metaspace.createNewLocalSpace({
|
|
@@ -157,34 +144,24 @@ export class SyncSagaCoordinator {
|
|
|
157
144
|
(async () => {
|
|
158
145
|
try {
|
|
159
146
|
|
|
160
|
-
//
|
|
147
|
+
// BOOTSTRAP IDENTITY (Session Keystone)
|
|
161
148
|
const sessionIdentity = useSessionIdentity
|
|
162
149
|
? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
|
|
163
150
|
: undefined;
|
|
164
151
|
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
165
152
|
|
|
166
|
-
//
|
|
167
|
-
const {
|
|
153
|
+
// CREATE INITIAL FRAME (Stage.init)
|
|
154
|
+
const { initFrame, initDomainGraph } = await this.createInitFrame({
|
|
168
155
|
sagaId,
|
|
169
156
|
sessionIdentity,
|
|
170
|
-
localSpace,
|
|
171
157
|
domainIbGibs,
|
|
172
|
-
|
|
173
|
-
metaspace,
|
|
174
|
-
conflictStrategy
|
|
158
|
+
conflictStrategy,
|
|
159
|
+
metaspace, localSpace, tempSpace,
|
|
175
160
|
});
|
|
176
161
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
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,
|
|
162
|
+
// KICK OFF THE PING-PONG SAGA LOOP (FSM)
|
|
163
|
+
await this.executeSagaLoop({
|
|
164
|
+
initFrame, initDomainGraph,
|
|
188
165
|
peer,
|
|
189
166
|
sessionIdentity,
|
|
190
167
|
updates$,
|
|
@@ -193,12 +170,6 @@ export class SyncSagaCoordinator {
|
|
|
193
170
|
metaspace
|
|
194
171
|
});
|
|
195
172
|
|
|
196
|
-
// 5. MERGE RESULT (Optimistic Commit)
|
|
197
|
-
if (syncedIbGibs && syncedIbGibs.length > 0) {
|
|
198
|
-
if (logalot) { console.log(`${lc} Merging ${syncedIbGibs.length} synced ibGibs to localSpace...`); }
|
|
199
|
-
await putInSpace({ space: localSpace, ibGibs: syncedIbGibs });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
173
|
resolveDone();
|
|
203
174
|
if (!updates$.complete) { throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`); }
|
|
204
175
|
updates$.complete();
|
|
@@ -258,22 +229,22 @@ export class SyncSagaCoordinator {
|
|
|
258
229
|
|
|
259
230
|
/**
|
|
260
231
|
* Drives the FSM loop of the Saga.
|
|
261
|
-
*
|
|
232
|
+
*
|
|
262
233
|
* @remarks
|
|
263
234
|
* **Execution Context**: **Sender (Local)**.
|
|
264
|
-
*
|
|
235
|
+
*
|
|
265
236
|
* 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 `
|
|
267
|
-
*
|
|
237
|
+
* It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
|
|
238
|
+
*
|
|
268
239
|
* **Data Transport Note**:
|
|
269
240
|
* Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
|
|
270
|
-
* When `
|
|
241
|
+
* When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
|
|
271
242
|
* the NEXT request context.
|
|
272
243
|
* When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
|
|
273
244
|
*/
|
|
274
245
|
protected async executeSagaLoop({
|
|
275
|
-
|
|
276
|
-
|
|
246
|
+
initFrame,
|
|
247
|
+
initDomainGraph,
|
|
277
248
|
peer,
|
|
278
249
|
sessionIdentity,
|
|
279
250
|
updates$,
|
|
@@ -281,141 +252,98 @@ export class SyncSagaCoordinator {
|
|
|
281
252
|
tempSpace,
|
|
282
253
|
metaspace
|
|
283
254
|
}: {
|
|
284
|
-
|
|
285
|
-
|
|
255
|
+
initFrame: SyncIbGib_V1,
|
|
256
|
+
initDomainGraph: FlatIbGibGraph,
|
|
286
257
|
peer: SyncPeerWitness,
|
|
287
258
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
288
259
|
updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
|
|
260
|
+
metaspace: MetaspaceService
|
|
289
261
|
localSpace: IbGibSpaceAny,
|
|
290
262
|
tempSpace: IbGibSpaceAny,
|
|
291
|
-
|
|
292
|
-
}): Promise<IbGib_V1[]> {
|
|
263
|
+
}): Promise<void> {
|
|
293
264
|
const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
|
|
294
265
|
|
|
295
|
-
|
|
296
|
-
let currentFrame: SyncIbGib_V1 | null =
|
|
297
|
-
|
|
266
|
+
/** The current frame we just generated (e.g., Init or Delta Request) */
|
|
267
|
+
let currentFrame: SyncIbGib_V1 | null = initFrame;
|
|
268
|
+
/** The **domain** payload we need to transmit (data we are sending) */
|
|
298
269
|
let nextDomainIbGibs: IbGib_V1[] = [];
|
|
299
|
-
// Accumulator for all data we've successfully pulled from the remote
|
|
300
|
-
const allReceivedIbGibs: IbGib_V1[] = [];
|
|
301
270
|
|
|
302
271
|
while (currentFrame) {
|
|
303
|
-
//
|
|
304
|
-
// 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
|
-
// We must do this BEFORE creating the Context so we can list them
|
|
307
|
-
// all in payloadAddrsDomain and payloadAddrsControl.
|
|
308
|
-
// const depsDomainIbGibs: IbGib_V1[] = [];
|
|
309
|
-
// const depsControlIbGibs: IbGib_V1[] = [];
|
|
310
|
-
const payloadIbGibsControl: IbGib_V1[] = [];
|
|
311
|
-
const payloadIbGibsDomain: IbGib_V1[] = [];
|
|
312
|
-
|
|
313
|
-
// A. Payload (Standard Deep Deps)
|
|
314
|
-
for (const nextDomainIbGib of nextDomainIbGibs) {
|
|
315
|
-
let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
|
|
316
|
-
if (!nextDomainIbGibGraph) {
|
|
317
|
-
nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (nextDomainIbGibGraph) {
|
|
321
|
-
payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
|
|
322
|
-
} else {
|
|
323
|
-
throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (logalot) { console.log(`${lc} payloadIbGibsDomain count: ${payloadIbGibsDomain.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`); }
|
|
272
|
+
// first, prepare for context transmission...
|
|
327
273
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 2. Create Context (Envelope)
|
|
274
|
+
/**
|
|
275
|
+
* this is used later in pollForDomainPayloads **iif** payloads are
|
|
276
|
+
* received **in the response**, i.e., on the return trip. So if we
|
|
277
|
+
* request addrs, the response should have these addrs and their
|
|
278
|
+
* dependencies. The ibgibs corresponding to these addrs will be
|
|
279
|
+
* streamed and placed into this map.
|
|
280
|
+
*/
|
|
339
281
|
const domainPayloadsMap = new Map<string, IbGib_V1>();
|
|
282
|
+
// #region set up peer observable for any domainPayloadsMap
|
|
340
283
|
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
|
|
284
|
+
const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
|
|
285
|
+
next: async (ibgib: IbGib_V1) => {
|
|
286
|
+
if (logalot) { console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`); }
|
|
287
|
+
const addr = getIbGibAddr({ ibGib: ibgib });
|
|
288
|
+
if (logalotControlDomain) { console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`); }
|
|
289
|
+
domainPayloadsMap.set(addr, ibgib);
|
|
290
|
+
},
|
|
291
|
+
error: async (e: string | Error | ErrorIbGib_V1) => {
|
|
292
|
+
if (isIbGib(e)) {
|
|
293
|
+
console.error(`${sublc} error fired. error: ${JSON.stringify((e as IbGib_V1).data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
|
|
294
|
+
} else {
|
|
295
|
+
console.dir(e);
|
|
296
|
+
console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
complete: async () => {
|
|
300
|
+
if (logalot) { console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`); }
|
|
301
|
+
await subscription.unsubscribe();
|
|
302
|
+
},
|
|
303
|
+
}));
|
|
304
|
+
// #endregion set up peer observable for any domainPayloadsMap
|
|
305
|
+
|
|
306
|
+
// ...create/compose the Request Context itself...
|
|
367
307
|
const requestCtx = await createSyncSagaContext({
|
|
368
308
|
sagaFrame: currentFrame,
|
|
369
309
|
sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
|
|
370
|
-
|
|
310
|
+
/**
|
|
311
|
+
* init frame: empty
|
|
312
|
+
* ack frame: possible push offers
|
|
313
|
+
* delta frame: requested ibgibs or commit offer
|
|
314
|
+
* commit frame: empty
|
|
315
|
+
*/
|
|
316
|
+
payloadIbGibsDomain: nextDomainIbGibs,
|
|
317
|
+
localSpace,
|
|
371
318
|
});
|
|
372
319
|
|
|
373
|
-
// Log what we're sending
|
|
320
|
+
// #region Log what we're sending
|
|
374
321
|
if (logalotControlDomain) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
|
|
322
|
+
const domainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
|
|
323
|
+
console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: 5b0081803698770f0bf64992220b312)`);
|
|
378
324
|
console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
|
|
379
325
|
console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
380
|
-
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
|
|
381
326
|
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
|
|
382
327
|
}
|
|
328
|
+
// #endregion Log what we're sending
|
|
383
329
|
|
|
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
|
|
330
|
+
// update our saga listeners...
|
|
407
331
|
// if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
|
|
408
|
-
updates$.next(requestCtx); // spins off
|
|
332
|
+
updates$.next(requestCtx); // spins off for saga UI updates
|
|
409
333
|
|
|
334
|
+
// ...And send the context.
|
|
335
|
+
peer.setOptionalOpts({ senderSpace: localSpace, senderTempSpace: tempSpace, });
|
|
410
336
|
const responseCtx = await peer.witness(requestCtx);
|
|
411
337
|
|
|
412
|
-
//
|
|
338
|
+
// the send returned, but a peer can return a falsy responseCtx, if
|
|
339
|
+
// we just sent a commit to them and they're done. If so, there will
|
|
340
|
+
// necessarily be no payload to wait to receive
|
|
413
341
|
if (!responseCtx) {
|
|
414
342
|
if (currentFrame) {
|
|
415
343
|
// Check for Commit (Peer silence expected)
|
|
416
344
|
const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
|
|
417
345
|
if (msg?.data?.stage === SyncStage.commit) {
|
|
418
|
-
if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete
|
|
346
|
+
if (logalot) { console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete. (I: 26f9ee073858ca78b8284753368b5226)`); }
|
|
419
347
|
currentFrame = null;
|
|
420
348
|
break;
|
|
421
349
|
} else {
|
|
@@ -426,85 +354,86 @@ export class SyncSagaCoordinator {
|
|
|
426
354
|
}
|
|
427
355
|
}
|
|
428
356
|
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
357
|
+
// at this point, we did indeed receive a response to analyze. BUT!
|
|
358
|
+
// we have only necessarily received the response context ibgib.
|
|
359
|
+
// if there were payloads expected to be transferred, they may not
|
|
360
|
+
// be done yet.
|
|
361
|
+
|
|
432
362
|
if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
|
|
433
|
-
updates$.next(responseCtx); // spins off
|
|
363
|
+
updates$.next(responseCtx); // spins off for saga UI updating
|
|
364
|
+
|
|
365
|
+
// validate context
|
|
366
|
+
await validateContextAndSagaFrame
|
|
434
367
|
|
|
435
368
|
// Extract expected domain addresses from response context
|
|
436
369
|
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
|
|
437
370
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
// Poll for them if needed
|
|
371
|
+
// Poll for them if needed. see above jsdocs for domainPayloadsMap
|
|
441
372
|
if (responsePayloadAddrsDomain.length > 0) {
|
|
442
|
-
await this.pollForDomainPayloads({
|
|
373
|
+
responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
|
|
443
374
|
expectedAddrs: responsePayloadAddrsDomain,
|
|
375
|
+
pollIntervalMs: 20, // relatively arbitrary right now
|
|
444
376
|
domainPayloadsMap,
|
|
445
377
|
tempSpace,
|
|
446
378
|
});
|
|
447
379
|
}
|
|
448
380
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
if (!responseFrameAddr) { throw new Error(`${lc} Peer response missing sagaFrame (E: 83a0)`); }
|
|
452
|
-
|
|
453
|
-
// Log what we received back
|
|
381
|
+
// #region Log what we received back
|
|
382
|
+
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
383
|
if (logalotControlDomain) {
|
|
455
384
|
const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] as string[] || [];
|
|
456
|
-
console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I:
|
|
385
|
+
console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: 3dc76a9744d89a4fc3e2f076c2be4826)`);
|
|
457
386
|
console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
|
|
458
|
-
console.log(`${lc}${lcControlDomain} Response Frame: ${
|
|
387
|
+
console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
|
|
459
388
|
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
|
|
460
389
|
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
|
|
461
390
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
srcGraph: {},
|
|
476
|
-
destSpace: localSpace,
|
|
477
|
-
tempSpace,
|
|
391
|
+
// #endregion Log what we received back
|
|
392
|
+
|
|
393
|
+
// at this point, we have received the context AND **all** of the
|
|
394
|
+
// domain payloads (if applicable), so we are ready to do the next
|
|
395
|
+
// iteration in the saga loop
|
|
396
|
+
|
|
397
|
+
// this is the part that drives the FSM forward, i.e., when we
|
|
398
|
+
// evolve the sync saga ibgib itself to the next frame if we aren't
|
|
399
|
+
// finished/errored out.
|
|
400
|
+
const contextResult = await this.handleResponseSagaContext({
|
|
401
|
+
sagaContext: responseCtx,
|
|
402
|
+
mySpace: localSpace,
|
|
403
|
+
myTempSpace: tempSpace,
|
|
478
404
|
metaspace,
|
|
479
|
-
domainPayloadsMap,
|
|
480
|
-
expectedDomainAddrs: responsePayloadAddrsDomain,
|
|
481
405
|
});
|
|
482
406
|
|
|
483
|
-
if (!
|
|
484
|
-
if (logalot) { console.log(`${lc} Handler returned null (Saga End)
|
|
407
|
+
if (!contextResult) {
|
|
408
|
+
if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: faae22abc818ba9b28ac6d2881cd7826)`); }
|
|
485
409
|
break;
|
|
486
410
|
}
|
|
487
411
|
|
|
488
|
-
|
|
412
|
+
// #region error conditions throw
|
|
413
|
+
if (contextResult.errorMsg) {
|
|
414
|
+
throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
|
|
415
|
+
} else if (!contextResult.nextFrameInfo) {
|
|
416
|
+
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
|
|
417
|
+
} else if (contextResult.nextFrameInfo?.responseWasNull) {
|
|
418
|
+
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
|
|
419
|
+
}
|
|
420
|
+
// #endregion error conditions throw
|
|
489
421
|
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
|
|
422
|
+
// we have another frame to process and send out, with possibly
|
|
423
|
+
// payload domain ibgibs as well
|
|
424
|
+
const { frame, payloadIbGibsDomain, } = contextResult.nextFrameInfo;
|
|
425
|
+
currentFrame = frame;
|
|
426
|
+
nextDomainIbGibs = [...(payloadIbGibsDomain || [])];
|
|
493
427
|
|
|
494
|
-
// Log handler output for next iteration
|
|
428
|
+
// #region Log handler output for next iteration
|
|
495
429
|
if (logalotControlDomain) {
|
|
496
|
-
const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
|
|
497
430
|
const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
|
|
498
|
-
console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I:
|
|
431
|
+
console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: 6b0d88c4c28857ccd812381515bd7826)`);
|
|
499
432
|
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
433
|
console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
|
|
502
434
|
}
|
|
503
|
-
|
|
504
|
-
// Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
|
|
435
|
+
// #endregion Log handler output for next iteration
|
|
505
436
|
}
|
|
506
|
-
|
|
507
|
-
return allReceivedIbGibs;
|
|
508
437
|
}
|
|
509
438
|
|
|
510
439
|
/**
|
|
@@ -570,7 +499,7 @@ export class SyncSagaCoordinator {
|
|
|
570
499
|
|
|
571
500
|
const res = await getLatestAddrs({ space, tjpAddrs: tjps });
|
|
572
501
|
if (!res.data || !res.data.latestAddrsMap) {
|
|
573
|
-
throw new Error(`${lc} Failed to get latest addrs. (E:
|
|
502
|
+
throw new Error(`${lc} Failed to get latest addrs. (E: 7d395940c0e1419165c5196c39c6c826)`);
|
|
574
503
|
}
|
|
575
504
|
|
|
576
505
|
// if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
|
|
@@ -584,128 +513,121 @@ 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
|
/**
|
|
626
554
|
* Creates the Initial Saga Frame (Init Stage).
|
|
627
|
-
*
|
|
555
|
+
*
|
|
628
556
|
* @remarks
|
|
629
557
|
* **Execution Context**: **Sender (Local)**.
|
|
630
|
-
*
|
|
558
|
+
*
|
|
631
559
|
* Generates the first frame containing the Knowledge Vector of the Local Space.
|
|
632
560
|
* This is sent to the Receiver to begin Gap Analysis.
|
|
633
561
|
*/
|
|
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<{ initFrame: SyncIbGib_V1, initDomainGraph: { [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
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
584
|
+
const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
|
|
585
|
+
// if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
|
|
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 { initFrame: sagaFrame, initDomainGraph: fullGraph };
|
|
709
631
|
} catch (error) {
|
|
710
632
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
711
633
|
throw error;
|
|
@@ -717,24 +639,30 @@ 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
|
/**
|
|
737
|
-
* This needs
|
|
665
|
+
* This needs
|
|
738
666
|
*/
|
|
739
667
|
const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
|
|
740
668
|
|
|
@@ -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
|
-
|
|
766
|
-
|
|
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)`);
|
|
767
698
|
}
|
|
699
|
+
|
|
700
|
+
return resultDomainPayloads;
|
|
768
701
|
} catch (error) {
|
|
769
702
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
770
703
|
throw error;
|
|
@@ -773,56 +706,101 @@ 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 handleResponseSagaContext({
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
}): Promise<HandleSagaFrameResult | null> {
|
|
795
|
-
const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
|
|
749
|
+
}): Promise<HandleSagaResponseContextResult | null> {
|
|
750
|
+
const lc = `${this.lc}[${this.handleResponseSagaContext.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
|
+
|
|
768
|
+
let nextFrameInfo: NextSagaFrameInfo;
|
|
807
769
|
switch (stage) {
|
|
808
770
|
case SyncStage.init:
|
|
809
|
-
|
|
771
|
+
nextFrameInfo = await this.handleInitFrame({
|
|
772
|
+
sagaIbGib,
|
|
773
|
+
messageData: messageData as SyncSagaMessageInitData_V1,
|
|
774
|
+
metaspace,
|
|
775
|
+
mySpace: mySpace,
|
|
776
|
+
myTempSpace: myTempSpace,
|
|
777
|
+
identity,
|
|
778
|
+
identitySecret
|
|
779
|
+
});
|
|
780
|
+
break;
|
|
810
781
|
|
|
811
782
|
case SyncStage.ack:
|
|
812
|
-
|
|
783
|
+
nextFrameInfo = await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
|
|
784
|
+
break;
|
|
813
785
|
|
|
814
786
|
case SyncStage.delta:
|
|
815
|
-
|
|
787
|
+
nextFrameInfo = await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
788
|
+
break;
|
|
816
789
|
|
|
817
790
|
case SyncStage.commit:
|
|
818
|
-
|
|
791
|
+
nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
792
|
+
break;
|
|
819
793
|
|
|
820
794
|
default:
|
|
821
795
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
822
796
|
}
|
|
797
|
+
|
|
798
|
+
return { errorMsg: undefined, nextFrameInfo, }
|
|
799
|
+
|
|
823
800
|
} catch (error) {
|
|
824
|
-
|
|
825
|
-
|
|
801
|
+
const errorMsg = `${lc} ${extractErrorMsg(error)}`;
|
|
802
|
+
console.error(errorMsg);
|
|
803
|
+
return { errorMsg }
|
|
826
804
|
} finally {
|
|
827
805
|
if (logalot) { console.log(`${lc} complete.`); }
|
|
828
806
|
}
|
|
@@ -832,10 +810,10 @@ export class SyncSagaCoordinator {
|
|
|
832
810
|
|
|
833
811
|
/**
|
|
834
812
|
* Handles the `Init` frame.
|
|
835
|
-
*
|
|
813
|
+
*
|
|
836
814
|
* @remarks
|
|
837
815
|
* **Execution Context**: **Receiver (Remote)**.
|
|
838
|
-
*
|
|
816
|
+
*
|
|
839
817
|
* The Receiver performs Gap Analysis here:
|
|
840
818
|
* 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
|
|
841
819
|
* 2. Identifies what Sender needs (`pushOfferAddrs`).
|
|
@@ -845,321 +823,351 @@ export class SyncSagaCoordinator {
|
|
|
845
823
|
protected async handleInitFrame({
|
|
846
824
|
sagaIbGib,
|
|
847
825
|
messageData,
|
|
848
|
-
|
|
849
|
-
|
|
826
|
+
mySpace,
|
|
827
|
+
myTempSpace,
|
|
850
828
|
metaspace,
|
|
851
829
|
identity,
|
|
852
|
-
identitySecret,
|
|
830
|
+
// identitySecret,
|
|
853
831
|
}: {
|
|
854
832
|
sagaIbGib: SyncIbGib_V1,
|
|
855
|
-
messageData:
|
|
856
|
-
|
|
857
|
-
|
|
833
|
+
messageData: SyncSagaMessageInitData_V1,
|
|
834
|
+
/**
|
|
835
|
+
* Local space relative to the execution context's POV
|
|
836
|
+
*/
|
|
837
|
+
mySpace: IbGibSpaceAny,
|
|
838
|
+
/**
|
|
839
|
+
* Local temp space relative to the execution context's POV.
|
|
840
|
+
*
|
|
841
|
+
* NOTE: Since this always executes on the receiver's end, this should
|
|
842
|
+
* be the receiver's temp space.
|
|
843
|
+
*/
|
|
844
|
+
myTempSpace: IbGibSpaceAny,
|
|
858
845
|
metaspace: MetaspaceService,
|
|
859
846
|
identity?: KeystoneIbGib_V1,
|
|
860
847
|
identitySecret?: string,
|
|
861
|
-
}): Promise<
|
|
848
|
+
}): Promise<NextSagaFrameInfo> {
|
|
862
849
|
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
863
|
-
|
|
864
|
-
|
|
850
|
+
try {
|
|
851
|
+
if (logalot) { console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`); }
|
|
865
852
|
|
|
866
|
-
|
|
867
|
-
const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
|
|
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
|
-
}
|
|
853
|
+
console.log(`${lc} [TEST DEBUG] Receiver mySpace: ${mySpace.ib}`);
|
|
875
854
|
|
|
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
|
-
});
|
|
855
|
+
// Extract Init Data
|
|
856
|
+
const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
|
|
857
|
+
if (initData.stage !== SyncStage.init) {
|
|
858
|
+
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: c91be82970e4decc58f56bf8fc1ffc26)`);
|
|
859
|
+
}
|
|
860
|
+
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
861
|
+
if (!initData || !initData.knowledgeVector) {
|
|
862
|
+
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
899
863
|
}
|
|
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
864
|
|
|
923
|
-
|
|
865
|
+
// Determine Strategy from Saga Data (since V1 stores it in root)
|
|
866
|
+
const conflictStrategy = sagaIbGib.data!.conflictStrategy || SyncConflictStrategy.abort;
|
|
867
|
+
|
|
868
|
+
// 2. Gap Analysis
|
|
869
|
+
const conflicts: SyncSagaConflictInfo[] = [];
|
|
870
|
+
const deltaRequestAddrInfos: SyncSagaRequestAddrInfo[] = [];
|
|
871
|
+
const pushOfferInfos: SyncSagaPushOfferInfo[] = [];
|
|
872
|
+
|
|
873
|
+
// First do the consant stones (Non-TJPs)
|
|
874
|
+
const stones = initData.stones || [];
|
|
875
|
+
if (stones.length > 0) {
|
|
876
|
+
if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
|
|
877
|
+
// Check if we have these stones
|
|
878
|
+
const resStones = await getFromSpace({ space: mySpace, addrs: stones });
|
|
879
|
+
const rawResultIbGib = resStones.rawResultIbGib as IbGibSpaceResultIbGib<IbGib_V1, IbGibSpaceResultData, IbGibSpaceResultRel8ns>;
|
|
880
|
+
if (!rawResultIbGib.data) { throw new Error(`(UNEXPECTED) rawResultIbGib.data falsy? (E: f3c40b492adc02c3f480b998fc7a5926)`); }
|
|
881
|
+
const addrsNotFound = rawResultIbGib.data.addrsNotFound;
|
|
882
|
+
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
883
|
+
if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
|
|
884
|
+
addrsNotFound.forEach(addr => {
|
|
885
|
+
if (!deltaRequestAddrInfos.some(x => x.addr === addr)) {
|
|
886
|
+
deltaRequestAddrInfos.push({ addr }); // no tjpAddr
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}
|
|
924
891
|
|
|
892
|
+
/**
|
|
893
|
+
* "remote" is sender in this case
|
|
894
|
+
*/
|
|
895
|
+
const remoteKV = initData.knowledgeVector;
|
|
896
|
+
if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
|
|
897
|
+
const remoteTjps = Object.keys(remoteKV);
|
|
898
|
+
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
899
|
+
if (logalot) { console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`); }
|
|
900
|
+
|
|
901
|
+
// 1. Get Local Latest Addrs for all TJPs
|
|
902
|
+
let localLatestAddrsMap: { [tjp: string]: string | null } = {};
|
|
903
|
+
if (remoteTjps.length > 0) {
|
|
904
|
+
// Batch get latest addrs for the TJPs
|
|
905
|
+
const resGetLatestAddrs = await getLatestAddrs({
|
|
906
|
+
space: mySpace, // executing on receiver, so this is receiver's space
|
|
907
|
+
tjpAddrs: remoteTjps,
|
|
908
|
+
});
|
|
909
|
+
if (!resGetLatestAddrs.data) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`); }
|
|
910
|
+
if (!resGetLatestAddrs.data.latestAddrsMap) { throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`); }
|
|
911
|
+
localLatestAddrsMap = resGetLatestAddrs.data.latestAddrsMap;
|
|
912
|
+
console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localLatestAddrsMap)}`);
|
|
913
|
+
if (logalot) { console.log(`${lc} localKV: ${pretty(localLatestAddrsMap)} (I: 980975642cbccd8018cf0cd808d30826)`); }
|
|
914
|
+
}
|
|
925
915
|
|
|
926
|
-
|
|
927
|
-
const
|
|
928
|
-
|
|
916
|
+
// 2. Gap Analysis
|
|
917
|
+
for (const tjp of remoteTjps) {
|
|
918
|
+
const remoteAddr = remoteKV[tjp];
|
|
919
|
+
const localAddr = localLatestAddrsMap[tjp];
|
|
920
|
+
|
|
921
|
+
if (!localAddr) {
|
|
922
|
+
// We (Receiver) don't have this timeline at all. Request it.
|
|
923
|
+
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
924
|
+
deltaRequestAddrInfos.push({
|
|
925
|
+
addr: remoteAddr,
|
|
926
|
+
tjpAddr: tjp,
|
|
927
|
+
// we don't have this timeline at all
|
|
928
|
+
// latestAddrAlreadyHave: undefined
|
|
929
|
+
});
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
929
932
|
|
|
930
|
-
|
|
931
|
-
// We (Receiver) don't have this timeline. Request it.
|
|
932
|
-
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
933
|
-
deltaReqAddrs.push(remoteAddr);
|
|
934
|
-
continue;
|
|
935
|
-
}
|
|
933
|
+
// we do have this timeline...
|
|
936
934
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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
|
-
});
|
|
935
|
+
if (localAddr === remoteAddr) {
|
|
936
|
+
// ...already synced
|
|
937
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
951
941
|
|
|
952
|
-
|
|
953
|
-
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
954
|
-
pushOfferAddrs.push(localAddr);
|
|
955
|
-
} else {
|
|
956
|
-
// Remote is not in our past.
|
|
957
|
-
// Either Remote is ahead (Fast-Backward) OR Diverged.
|
|
942
|
+
// we have this timeline but it's not synced...
|
|
958
943
|
|
|
959
|
-
//
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
944
|
+
// We're executing on receiver. Check if Remote (Sender) is in
|
|
945
|
+
// our past, and if so, we are ahead and need to push the delta
|
|
946
|
+
// to remote
|
|
947
|
+
const remoteIsInPast = await isPastFrame({
|
|
948
|
+
olderAddr: remoteAddr,
|
|
949
|
+
newerAddr: localAddr,
|
|
950
|
+
space: mySpace,
|
|
964
951
|
});
|
|
965
952
|
|
|
966
|
-
if (
|
|
967
|
-
//
|
|
968
|
-
|
|
969
|
-
|
|
953
|
+
if (remoteIsInPast) {
|
|
954
|
+
// we're ahead, so push the delta of what the sender doesn't
|
|
955
|
+
// have (we have full knowledge)
|
|
956
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote (sender) is in past - offering push`);
|
|
957
|
+
const deltaGraph = await getDeltaDependencyGraph({
|
|
958
|
+
ibGibAddr: localAddr,
|
|
959
|
+
live: false, // always live: false right?
|
|
960
|
+
latestCommonFrameAddr: remoteAddr,
|
|
961
|
+
space: mySpace,
|
|
962
|
+
});
|
|
963
|
+
pushOfferInfos.push({ tjpAddr: tjp, addrs: Object.keys(deltaGraph), });
|
|
970
964
|
} else {
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
965
|
+
// Remote tip is not in our past. So, either Remote is
|
|
966
|
+
// ahead (we're in THEIR past, i.e., Fast-Backward) OR
|
|
967
|
+
// Diverged/conflict (we've both made edits).
|
|
968
|
+
|
|
969
|
+
// Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
|
|
970
|
+
/**
|
|
971
|
+
* we could first check for existence of remoteAddr in
|
|
972
|
+
* mySpace, but this is quick and easy. If it throws, we
|
|
973
|
+
* don't have it so it can't be in the past.
|
|
974
|
+
*/
|
|
975
|
+
let localIsInPast = false;
|
|
976
|
+
try {
|
|
977
|
+
// we could
|
|
978
|
+
localIsInPast = await isPastFrame({
|
|
979
|
+
olderAddr: localAddr,
|
|
980
|
+
newerAddr: remoteAddr,
|
|
981
|
+
space: mySpace,
|
|
986
982
|
});
|
|
987
|
-
}
|
|
988
|
-
//
|
|
989
|
-
|
|
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 || [])];
|
|
983
|
+
} catch (error) {
|
|
984
|
+
// couldn't get one of them, so localIsInPast remains false.
|
|
985
|
+
if (logalot) { console.log(`${lc} expected error if we don't have remote. verbose logging error though: ${extractErrorMsg(error)} (I: 47ea08d0b418cf4aa8a502a7bcb80826)`); }
|
|
986
|
+
}
|
|
1004
987
|
|
|
1005
|
-
|
|
988
|
+
if (localIsInPast) {
|
|
989
|
+
// Fast-Forward: We update to remote's tip.
|
|
990
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
|
|
991
|
+
deltaRequestAddrInfos.push({
|
|
992
|
+
addr: remoteAddr,
|
|
1006
993
|
tjpAddr: tjp,
|
|
1007
|
-
|
|
1008
|
-
remoteAddr,
|
|
1009
|
-
timelineAddrs,
|
|
1010
|
-
reason: 'divergence',
|
|
1011
|
-
terminal: false
|
|
994
|
+
latestAddrAlreadyHave: localAddr,
|
|
1012
995
|
});
|
|
1013
|
-
|
|
1014
996
|
} else {
|
|
1015
|
-
|
|
997
|
+
// DIVERGENCE: Both have changes the other doesn't know about.
|
|
998
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
|
|
999
|
+
|
|
1000
|
+
if (conflictStrategy === 'abort') {
|
|
1001
|
+
// Abort Strategy: We will treat this as terminal.
|
|
1002
|
+
// But for Unified Ack, we just mark it terminal in the list?
|
|
1003
|
+
// Or do we actually throw/abort the saga?
|
|
1004
|
+
// Current logic (below) aborts the saga if ANY conflict is terminal/abort.
|
|
1005
|
+
conflicts.push({
|
|
1006
|
+
tjpAddr: tjp,
|
|
1007
|
+
localAddr: localAddr,
|
|
1008
|
+
remoteAddr,
|
|
1009
|
+
timelineAddrs: [], // Not needed for abort
|
|
1010
|
+
reason: 'divergence',
|
|
1011
|
+
terminal: true
|
|
1012
|
+
});
|
|
1013
|
+
} else if (conflictStrategy === 'optimistic') {
|
|
1014
|
+
// Optimistic: We want resolve this.
|
|
1015
|
+
// We need to send our history to the Sender so they can Merge.
|
|
1016
|
+
|
|
1017
|
+
// Fetch Full History for Local Timeline
|
|
1018
|
+
// Note: We might optimize this to only send "recent" history if we had a KV?
|
|
1019
|
+
// But for now, get full past.
|
|
1020
|
+
// Optimization: localKV might not have full history.
|
|
1021
|
+
// We need to inspect the 'past' of the local tip.
|
|
1022
|
+
|
|
1023
|
+
// We need the ACTUAL object to get the past.
|
|
1024
|
+
// We have localAddr.
|
|
1025
|
+
const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
|
|
1026
|
+
if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
|
|
1027
|
+
throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: 83cb88a767e22bbda99c6788bec50526)`);
|
|
1028
|
+
}
|
|
1029
|
+
const localTip = resLocalTip.ibGibs[0];
|
|
1030
|
+
if (!localTip) { throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: c39448ad6b3a72af78339ad877a56826)`); }
|
|
1031
|
+
|
|
1032
|
+
const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
|
|
1033
|
+
|
|
1034
|
+
conflicts.push({
|
|
1035
|
+
tjpAddr: tjp,
|
|
1036
|
+
localAddr,
|
|
1037
|
+
remoteAddr,
|
|
1038
|
+
timelineAddrs,
|
|
1039
|
+
reason: 'divergence',
|
|
1040
|
+
terminal: false
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy}. Only these currently implemented: ${SYNC_CONFLICT_STRATEGY_VALID_VALUES} (E: 8f12384180f8a718a983a749fe0adf26)`);
|
|
1044
|
+
}
|
|
1016
1045
|
}
|
|
1017
1046
|
}
|
|
1018
1047
|
}
|
|
1019
|
-
}
|
|
1020
1048
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1049
|
+
// Check if we should ABORT (if any conflict is terminal)
|
|
1050
|
+
const hasTerminalConflicts = conflicts.some(c => c.terminal);
|
|
1023
1051
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1052
|
+
if (hasTerminalConflicts) {
|
|
1053
|
+
// Abort Strategy: Kill the saga.
|
|
1054
|
+
if (logalot) { console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`); }
|
|
1027
1055
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
// Original design had explicit Conflict Frame for Abort.
|
|
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.
|
|
1056
|
+
throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
|
|
1057
|
+
}
|
|
1033
1058
|
|
|
1034
|
-
//
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1059
|
+
// 3. Create Ack Frame
|
|
1060
|
+
if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 24203af4600fb226ae6c1afbde442826)`); }
|
|
1061
|
+
const sagaId = sagaIbGib.data.uuid;
|
|
1062
|
+
// Create Payload Stone
|
|
1063
|
+
const ackData: SyncSagaMessageAckData_V1 = {
|
|
1064
|
+
sagaId,
|
|
1065
|
+
stage: SyncStage.ack,
|
|
1066
|
+
deltaRequestAddrInfos,
|
|
1067
|
+
pushOfferInfos,
|
|
1068
|
+
};
|
|
1069
|
+
if (conflicts?.length > 0) { ackData.conflicts = conflicts; }
|
|
1039
1070
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1071
|
+
const ackStone = await this.createSyncMsgStone({
|
|
1072
|
+
data: ackData,
|
|
1073
|
+
localSpace: myTempSpace,
|
|
1074
|
+
metaspace,
|
|
1075
|
+
});
|
|
1076
|
+
if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
|
|
1042
1077
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1078
|
+
// Evolve Saga
|
|
1079
|
+
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
1080
|
+
prevSagaIbGib: sagaIbGib,
|
|
1081
|
+
msgStones: [ackStone],
|
|
1082
|
+
sessionIdentity: identity,
|
|
1083
|
+
localSpace: mySpace,
|
|
1084
|
+
metaspace,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
1052
1088
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
if (
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1089
|
+
/**
|
|
1090
|
+
* we want to push ibgibs to the remote/sender if we have push
|
|
1091
|
+
* offers. an ack frame's payloads, if any, are those push offers
|
|
1092
|
+
*/
|
|
1093
|
+
// let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
|
|
1094
|
+
|
|
1095
|
+
let payloadIbGibsDomain: IbGib_V1[] | undefined;
|
|
1096
|
+
if (pushOfferInfos.length > 0) {
|
|
1097
|
+
const searchSecondSpaceAddrs: IbGibAddr[] = [];
|
|
1098
|
+
payloadIbGibsDomain = [];
|
|
1099
|
+
const domainAddrs: IbGibAddr[] = pushOfferInfos.flatMap(x => x.addrs);
|
|
1100
|
+
const resGet = await getFromSpace({ addrs: domainAddrs, space: mySpace, });
|
|
1101
|
+
const resGetRawData = resGet.rawResultIbGib?.data as IbGibSpaceResultData | undefined;
|
|
1102
|
+
if (!resGetRawData) { throw new Error(`(UNEXPECTED) resGet.rawResultIbGib.data falsy? (E: 1a2cc8cb99a1ffa60837e45a8229b826)`); }
|
|
1103
|
+
const addrsNotFound = resGetRawData.addrsNotFound ?? [];
|
|
1104
|
+
const addrsErrored = resGetRawData.addrsErrored ?? [];
|
|
1105
|
+
if (resGet.ibGibs && resGet.ibGibs.length === domainAddrs.length) {
|
|
1106
|
+
// found all of them
|
|
1107
|
+
resGet.ibGibs.forEach(x => { payloadIbGibsDomain!.push(x) })
|
|
1108
|
+
} else if (resGet.ibGibs && resGet.ibGibs.length > 0) {
|
|
1109
|
+
// found some of them
|
|
1110
|
+
resGet.ibGibs.forEach(x => { payloadIbGibsDomain!.push(x) })
|
|
1111
|
+
const foundPlusNotFoundCount =
|
|
1112
|
+
payloadIbGibsDomain.length +
|
|
1113
|
+
addrsNotFound.length +
|
|
1114
|
+
addrsErrored.length;
|
|
1115
|
+
if (foundPlusNotFoundCount === domainAddrs.length) {
|
|
1116
|
+
// we can still conceivably get the remaining addrs
|
|
1117
|
+
addrsNotFound.forEach(x => searchSecondSpaceAddrs.push(x));
|
|
1118
|
+
addrsErrored.forEach(x => searchSecondSpaceAddrs.push(x));
|
|
1119
|
+
} else if (addrsNotFound.length === 0 && addrsErrored.length === 0) {
|
|
1120
|
+
throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored both empty? (E: ef1b2cf5bab8de2298ec507abe04e826)`);
|
|
1121
|
+
} else {
|
|
1122
|
+
throw new Error(`(UNEXPECTED) found some but not all addrs but addrsNotFound and addrsErrored don't contain the addrs not found? (E: ae9e015109f8c3c3a813da584cd98826)`);
|
|
1072
1123
|
}
|
|
1124
|
+
} else {
|
|
1125
|
+
// found none of them(?)
|
|
1126
|
+
console.warn(`${lc} we were expecting all domainAddrs in mySpace (${mySpace.ib}) but we couldn't get all of them. addrsNotFound: ${addrsNotFound ?? []}. addrsErrored: ${addrsErrored ?? []}. Trying temp space (${myTempSpace.ib}). (W: f61908e264f8dddd2e3fb9084463d826)`);
|
|
1127
|
+
domainAddrs.forEach(x => searchSecondSpaceAddrs.push(x));
|
|
1073
1128
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (!knowledgeVector[tjpAddr]) {
|
|
1089
|
-
const past = ibGib.rel8ns?.past || [];
|
|
1090
|
-
knowledgeVector[tjpAddr] = [getIbGibAddr({ ibGib }), ...past];
|
|
1129
|
+
if (searchSecondSpaceAddrs.length > 0) {
|
|
1130
|
+
if (logalot) { console.log(`${lc} couldn't get all addrs in mySpace (${mySpace.ib}). searchSecondSpaceAddrs: ${searchSecondSpaceAddrs} (I: 233fd954dbd84e51bca02fa8eed5f826)`); }
|
|
1131
|
+
const resGet2 = await getFromSpace({ addrs: searchSecondSpaceAddrs, space: myTempSpace, });
|
|
1132
|
+
if (resGet2.success && resGet2.ibGibs && resGet2.ibGibs.length === searchSecondSpaceAddrs.length) {
|
|
1133
|
+
// got them all the second try
|
|
1134
|
+
resGet2.ibGibs.forEach(x => payloadIbGibsDomain!.push(x));
|
|
1135
|
+
} else {
|
|
1136
|
+
resGet2.ibGibs?.forEach(x => payloadIbGibsDomain!.push(x));
|
|
1137
|
+
const addrsFailed = domainAddrs.filter(x =>
|
|
1138
|
+
!payloadIbGibsDomain!.map(pid => getIbGibAddr({ ibGib: pid })).includes(x)
|
|
1139
|
+
);
|
|
1140
|
+
throw new Error(`couldn't get some or all of addrs from either mySpace (${mySpace.ib}) or myTempSpace (${myTempSpace.ib}). addrsFailed: ${addrsFailed} (E: 1394d412c4ffa4dd085f269b43338826)`);
|
|
1141
|
+
}
|
|
1091
1142
|
}
|
|
1092
|
-
|
|
1093
|
-
// We don't have `addr`.
|
|
1143
|
+
// we have now populated payloadIbGibsDomain
|
|
1094
1144
|
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// Also populate from `knowledgeVector` in Init if we want bidirectional?
|
|
1098
|
-
// No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
|
|
1099
|
-
// Let's assume standard flow:
|
|
1100
|
-
// 1. Sender says "I have X"
|
|
1101
|
-
// 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
|
|
1102
|
-
// Problem: Receiver doesn't know X is related to Y without X.
|
|
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
|
-
};
|
|
1124
|
-
|
|
1125
|
-
const ackStone = await this.createSyncMsgStone({
|
|
1126
|
-
data: ackData,
|
|
1127
|
-
space: tempSpace,
|
|
1128
|
-
metaspace,
|
|
1129
|
-
});
|
|
1130
|
-
if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
|
|
1131
|
-
|
|
1132
|
-
// Evolve Saga
|
|
1133
|
-
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
1134
|
-
prevSagaIbGib: sagaIbGib,
|
|
1135
|
-
msgStones: [ackStone],
|
|
1136
|
-
identity,
|
|
1137
|
-
space: tempSpace,
|
|
1138
|
-
metaspace,
|
|
1139
|
-
});
|
|
1140
1145
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1146
|
+
throw new Error(`not implemented (E: ed3f98abb0988c5ae8038bb8d741fb26)`);
|
|
1147
|
+
// return {
|
|
1148
|
+
// frame: ackFrame,
|
|
1149
|
+
// // conflictInfos,
|
|
1150
|
+
// payloadIbGibsDomain,
|
|
1151
|
+
// };
|
|
1143
1152
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
return { frame: ackFrame, payloadIbGibsControl };
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1155
|
+
throw error;
|
|
1156
|
+
} finally {
|
|
1157
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1158
|
+
}
|
|
1151
1159
|
}
|
|
1152
1160
|
|
|
1153
1161
|
/**
|
|
1154
1162
|
* Handles the `Ack` frame.
|
|
1155
|
-
*
|
|
1163
|
+
*
|
|
1156
1164
|
* @remarks
|
|
1157
1165
|
* **Execution Context**: **Sender (Local)**.
|
|
1158
|
-
*
|
|
1166
|
+
*
|
|
1159
1167
|
* The Sender reacts to the Receiver's requirements:
|
|
1160
1168
|
* 1. `deltaReqAddrs`: Receiver wants this data. Sender gathers it from `srcGraph` and puts it in `payloadIbGibs` (for next frame).
|
|
1161
1169
|
* 2. `pushOfferAddrs`: Receiver has newer data. Sender acknowledges and adds them to `requests` (asking Receiver to send them).
|
|
1162
|
-
*
|
|
1170
|
+
*
|
|
1163
1171
|
* Returns a `Delta` frame.
|
|
1164
1172
|
*/
|
|
1165
1173
|
protected async handleAckFrame({
|
|
@@ -1176,12 +1184,12 @@ export class SyncSagaCoordinator {
|
|
|
1176
1184
|
tempSpace: IbGibSpaceAny,
|
|
1177
1185
|
metaspace: MetaspaceService,
|
|
1178
1186
|
identity?: KeystoneIbGib_V1,
|
|
1179
|
-
}): Promise<
|
|
1187
|
+
}): Promise<NextSagaFrameInfo> {
|
|
1180
1188
|
const lc = `${this.lc}[${this.handleAckFrame.name}]`;
|
|
1181
1189
|
try {
|
|
1182
1190
|
if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
|
|
1183
1191
|
|
|
1184
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({
|
|
1192
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1185
1193
|
const ackData = messageData as SyncSagaMessageAckData_V1;
|
|
1186
1194
|
|
|
1187
1195
|
if (!ackData) {
|
|
@@ -1204,7 +1212,7 @@ export class SyncSagaCoordinator {
|
|
|
1204
1212
|
console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
|
|
1205
1213
|
// Terminal failure. Sender should probably Commit(Fail) or just Abort.
|
|
1206
1214
|
// For now, throw to trigger abort.
|
|
1207
|
-
throw new Error(`${lc} Peer reported terminal conflicts. (E:
|
|
1215
|
+
throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
|
|
1208
1216
|
}
|
|
1209
1217
|
|
|
1210
1218
|
const optimisticConflicts = conflicts.filter(c => !c.terminal);
|
|
@@ -1232,7 +1240,7 @@ export class SyncSagaCoordinator {
|
|
|
1232
1240
|
// Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
|
|
1233
1241
|
|
|
1234
1242
|
// If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
|
|
1235
|
-
// Or we just proceed to Delta (sending what Receiver wants),
|
|
1243
|
+
// Or we just proceed to Delta (sending what Receiver wants),
|
|
1236
1244
|
// AND we piggyback our own requests?
|
|
1237
1245
|
// OR: We treat the Conflict Resolution as a sub-saga or side-effect?
|
|
1238
1246
|
|
|
@@ -1261,7 +1269,7 @@ export class SyncSagaCoordinator {
|
|
|
1261
1269
|
// We can fetch valid 'past' from space.
|
|
1262
1270
|
const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
|
|
1263
1271
|
const senderTipIbGib = resSenderTip.ibGibs?.[0];
|
|
1264
|
-
if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E:
|
|
1272
|
+
if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`); }
|
|
1265
1273
|
|
|
1266
1274
|
// Basic Diff: Find what Receiver has that we don't.
|
|
1267
1275
|
// Actually, we need to traverse OUR past to find commonality.
|
|
@@ -1346,24 +1354,25 @@ export class SyncSagaCoordinator {
|
|
|
1346
1354
|
|
|
1347
1355
|
// 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
|
|
1348
1356
|
|
|
1349
|
-
const deltaReqAddrs = ackData.
|
|
1350
|
-
const pushOfferAddrs = ackData.
|
|
1357
|
+
const deltaReqAddrs = ackData.deltaRequestAddrInfos || [];
|
|
1358
|
+
const pushOfferAddrs = ackData.pushOfferInfos || [];
|
|
1351
1359
|
|
|
1352
1360
|
// 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
|
|
1353
1361
|
const pullReqAddrs: string[] = [];
|
|
1354
1362
|
for (const addr of pushOfferAddrs) {
|
|
1355
|
-
const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
|
|
1356
|
-
if (!existing) {
|
|
1357
|
-
|
|
1358
|
-
}
|
|
1363
|
+
// const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
|
|
1364
|
+
// if (!existing) {
|
|
1365
|
+
// pullReqAddrs.push(addr);
|
|
1366
|
+
// }
|
|
1359
1367
|
}
|
|
1360
1368
|
|
|
1361
1369
|
// 2. Process Delta Requests (Push Payload)
|
|
1362
1370
|
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
1371
|
+
// const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
|
|
1363
1372
|
const skipAddrs = new Set<string>();
|
|
1364
1373
|
if (ackData.knowledgeVector) {
|
|
1365
1374
|
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
1366
|
-
addrs.forEach(a => skipAddrs.add(a));
|
|
1375
|
+
// addrs.forEach(a => skipAddrs.add(a));
|
|
1367
1376
|
});
|
|
1368
1377
|
}
|
|
1369
1378
|
|
|
@@ -1371,24 +1380,24 @@ export class SyncSagaCoordinator {
|
|
|
1371
1380
|
// Gather all tips to sync first
|
|
1372
1381
|
const tipsToSync: IbGib_V1[] = [];
|
|
1373
1382
|
for (const addr of deltaReqAddrs) {
|
|
1374
|
-
let ibGib = srcGraph[addr];
|
|
1375
|
-
if (!ibGib) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
if (ibGib) {
|
|
1382
|
-
|
|
1383
|
-
} else {
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1383
|
+
// let ibGib = srcGraph[addr];
|
|
1384
|
+
// if (!ibGib) {
|
|
1385
|
+
// const res = await getFromSpace({ addr, space: destSpace });
|
|
1386
|
+
// if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1387
|
+
// ibGib = res.ibGibs[0];
|
|
1388
|
+
// }
|
|
1389
|
+
// }
|
|
1390
|
+
// if (ibGib) {
|
|
1391
|
+
// tipsToSync.push(ibGib);
|
|
1392
|
+
// } else {
|
|
1393
|
+
// throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
1394
|
+
// }
|
|
1386
1395
|
}
|
|
1387
1396
|
|
|
1388
1397
|
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
1389
1398
|
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
1390
1399
|
// `getDependencyGraph` takes a single ibGib.
|
|
1391
|
-
// We can optimize by doing it for each tip and unioning the result?
|
|
1400
|
+
// We can optimize by doing it for each tip and unioning the result?
|
|
1392
1401
|
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
1393
1402
|
// We will loop.
|
|
1394
1403
|
|
|
@@ -1444,28 +1453,27 @@ export class SyncSagaCoordinator {
|
|
|
1444
1453
|
|
|
1445
1454
|
const deltaStone = await this.createSyncMsgStone({
|
|
1446
1455
|
data: deltaData,
|
|
1447
|
-
|
|
1456
|
+
localSpace: tempSpace,
|
|
1448
1457
|
metaspace,
|
|
1449
1458
|
});
|
|
1450
1459
|
|
|
1451
1460
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1452
1461
|
prevSagaIbGib: sagaIbGib,
|
|
1453
1462
|
msgStones: [deltaStone],
|
|
1454
|
-
identity,
|
|
1455
|
-
|
|
1463
|
+
sessionIdentity: identity,
|
|
1464
|
+
localSpace: tempSpace,
|
|
1456
1465
|
metaspace,
|
|
1457
1466
|
});
|
|
1458
1467
|
|
|
1459
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1460
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1461
|
-
|
|
1462
1468
|
if (logalot) { console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`); }
|
|
1463
1469
|
|
|
1464
1470
|
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1465
1471
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1466
1472
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1467
1473
|
|
|
1468
|
-
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1474
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1475
|
+
// return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
|
|
1476
|
+
throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
|
|
1469
1477
|
} catch (error) {
|
|
1470
1478
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1471
1479
|
throw error;
|
|
@@ -1476,10 +1484,10 @@ export class SyncSagaCoordinator {
|
|
|
1476
1484
|
|
|
1477
1485
|
/**
|
|
1478
1486
|
* Handles the `Delta` frame.
|
|
1479
|
-
*
|
|
1487
|
+
*
|
|
1480
1488
|
* @remarks
|
|
1481
1489
|
* **Execution Context**: **Both**.
|
|
1482
|
-
*
|
|
1490
|
+
*
|
|
1483
1491
|
* 1. **Ingestion**: Receives data sent by Peer (`payloadAddrs`). Resolves them from `Rel8ns.payload` (implicitly via space/context) and returns them in `receivedPayload` to be saved.
|
|
1484
1492
|
* 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
|
|
1485
1493
|
* 3. **Completion**: If no more requests, transitions to `Commit`.
|
|
@@ -1498,11 +1506,11 @@ export class SyncSagaCoordinator {
|
|
|
1498
1506
|
tempSpace: IbGibSpaceAny,
|
|
1499
1507
|
metaspace: MetaspaceService,
|
|
1500
1508
|
identity?: KeystoneIbGib_V1,
|
|
1501
|
-
}): Promise<
|
|
1509
|
+
}): Promise<NextSagaFrameInfo> {
|
|
1502
1510
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1503
1511
|
if (logalot) { console.log(`${lc} starting...`); }
|
|
1504
1512
|
|
|
1505
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({
|
|
1513
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1506
1514
|
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1507
1515
|
|
|
1508
1516
|
if (!deltaData) {
|
|
@@ -1511,7 +1519,7 @@ export class SyncSagaCoordinator {
|
|
|
1511
1519
|
if (deltaData.stage !== SyncStage.delta) {
|
|
1512
1520
|
throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
|
|
1513
1521
|
}
|
|
1514
|
-
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I:
|
|
1522
|
+
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
|
|
1515
1523
|
|
|
1516
1524
|
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1517
1525
|
|
|
@@ -1522,8 +1530,8 @@ export class SyncSagaCoordinator {
|
|
|
1522
1530
|
// 1. Process Received Payload (Ingest)
|
|
1523
1531
|
const receivedPayloadIbGibs: IbGib_V1[] = [];
|
|
1524
1532
|
if (payloadAddrs.length > 0) {
|
|
1525
|
-
// We use `payloadAddrs` as the manifest.
|
|
1526
|
-
// The ACTUAL collection of ibGibs should be available via `getFromSpace`
|
|
1533
|
+
// We use `payloadAddrs` as the manifest.
|
|
1534
|
+
// The ACTUAL collection of ibGibs should be available via `getFromSpace`
|
|
1527
1535
|
// assuming the "Transport" layer put them there implicitly?
|
|
1528
1536
|
// OR, if we are local-only, we just get them.
|
|
1529
1537
|
// The `handleDeltaFrame` contract assumes data is reachable in `space`.
|
|
@@ -1551,7 +1559,7 @@ export class SyncSagaCoordinator {
|
|
|
1551
1559
|
// Get the requested ibGib
|
|
1552
1560
|
let ibGib = srcGraph[addr];
|
|
1553
1561
|
if (!ibGib) {
|
|
1554
|
-
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1562
|
+
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1555
1563
|
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1556
1564
|
ibGib = res.ibGibs[0];
|
|
1557
1565
|
}
|
|
@@ -1647,7 +1655,7 @@ export class SyncSagaCoordinator {
|
|
|
1647
1655
|
console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
|
|
1648
1656
|
if (resRecTip.success && resRecTip.ibGibs?.[0]) {
|
|
1649
1657
|
// We have the tip!
|
|
1650
|
-
// Do we have the full history?
|
|
1658
|
+
// Do we have the full history?
|
|
1651
1659
|
// `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
|
|
1652
1660
|
// If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
|
|
1653
1661
|
|
|
@@ -1693,9 +1701,9 @@ export class SyncSagaCoordinator {
|
|
|
1693
1701
|
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
1694
1702
|
requests: hasMyRequests ? myRequests : undefined,
|
|
1695
1703
|
proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
|
|
1696
|
-
// Wait. If we send data, we are NOT committing yet.
|
|
1704
|
+
// Wait. If we send data, we are NOT committing yet.
|
|
1697
1705
|
// We are sending data. The OTHER side must ingest it.
|
|
1698
|
-
// So proposeCommit = true?
|
|
1706
|
+
// So proposeCommit = true?
|
|
1699
1707
|
// "Here is the data. I'm done. If you are good, let's commit."
|
|
1700
1708
|
// Yes.
|
|
1701
1709
|
};
|
|
@@ -1708,26 +1716,25 @@ export class SyncSagaCoordinator {
|
|
|
1708
1716
|
|
|
1709
1717
|
const deltaStone = await this.createSyncMsgStone({
|
|
1710
1718
|
data: responseDeltaData,
|
|
1711
|
-
|
|
1719
|
+
localSpace: tempSpace,
|
|
1712
1720
|
metaspace
|
|
1713
1721
|
});
|
|
1714
1722
|
|
|
1715
1723
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1716
1724
|
prevSagaIbGib: sagaIbGib,
|
|
1717
1725
|
msgStones: [deltaStone],
|
|
1718
|
-
identity,
|
|
1719
|
-
|
|
1726
|
+
sessionIdentity: identity,
|
|
1727
|
+
localSpace: tempSpace,
|
|
1720
1728
|
metaspace
|
|
1721
1729
|
});
|
|
1722
1730
|
|
|
1723
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1724
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1725
|
-
|
|
1726
1731
|
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1727
1732
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1728
1733
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1729
1734
|
|
|
1730
|
-
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1735
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1736
|
+
// return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1737
|
+
throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
|
|
1731
1738
|
|
|
1732
1739
|
} else {
|
|
1733
1740
|
// We have nothing to send.
|
|
@@ -1742,26 +1749,25 @@ export class SyncSagaCoordinator {
|
|
|
1742
1749
|
|
|
1743
1750
|
const commitStone = await this.createSyncMsgStone({
|
|
1744
1751
|
data: commitData,
|
|
1745
|
-
|
|
1752
|
+
localSpace: tempSpace,
|
|
1746
1753
|
metaspace
|
|
1747
1754
|
});
|
|
1748
1755
|
|
|
1749
1756
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1750
1757
|
prevSagaIbGib: sagaIbGib,
|
|
1751
1758
|
msgStones: [commitStone],
|
|
1752
|
-
identity,
|
|
1753
|
-
|
|
1759
|
+
sessionIdentity: identity,
|
|
1760
|
+
localSpace: tempSpace,
|
|
1754
1761
|
metaspace
|
|
1755
1762
|
});
|
|
1756
1763
|
|
|
1757
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1758
|
-
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1759
|
-
|
|
1760
1764
|
// Build control payloads for commit
|
|
1761
1765
|
const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
|
|
1762
1766
|
if (identity) { commitCtrlPayloads.push(identity); }
|
|
1763
1767
|
|
|
1764
|
-
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1768
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1769
|
+
// return { frame: commitFrame, };
|
|
1770
|
+
throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
|
|
1765
1771
|
|
|
1766
1772
|
} else {
|
|
1767
1773
|
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
@@ -1776,21 +1782,18 @@ export class SyncSagaCoordinator {
|
|
|
1776
1782
|
|
|
1777
1783
|
const deltaStone = await this.createSyncMsgStone({
|
|
1778
1784
|
data: responseDeltaData,
|
|
1779
|
-
|
|
1785
|
+
localSpace: tempSpace,
|
|
1780
1786
|
metaspace
|
|
1781
1787
|
});
|
|
1782
1788
|
|
|
1783
1789
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1784
1790
|
prevSagaIbGib: sagaIbGib,
|
|
1785
1791
|
msgStones: [deltaStone],
|
|
1786
|
-
identity,
|
|
1787
|
-
|
|
1792
|
+
sessionIdentity: identity,
|
|
1793
|
+
localSpace: tempSpace,
|
|
1788
1794
|
metaspace
|
|
1789
1795
|
});
|
|
1790
1796
|
|
|
1791
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1792
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1793
|
-
|
|
1794
1797
|
// Check if PEER proposed commit
|
|
1795
1798
|
if (deltaData.proposeCommit) {
|
|
1796
1799
|
if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
|
|
@@ -1805,33 +1808,34 @@ export class SyncSagaCoordinator {
|
|
|
1805
1808
|
|
|
1806
1809
|
const commitStone = await this.createSyncMsgStone({
|
|
1807
1810
|
data: commitData,
|
|
1808
|
-
|
|
1811
|
+
localSpace: tempSpace,
|
|
1809
1812
|
metaspace
|
|
1810
1813
|
});
|
|
1811
1814
|
|
|
1812
1815
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1813
1816
|
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1814
1817
|
msgStones: [commitStone],
|
|
1815
|
-
identity,
|
|
1816
|
-
|
|
1818
|
+
sessionIdentity: identity,
|
|
1819
|
+
localSpace: tempSpace,
|
|
1817
1820
|
metaspace
|
|
1818
1821
|
});
|
|
1819
1822
|
|
|
1820
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1821
|
-
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1822
|
-
|
|
1823
1823
|
// Build control payloads for commit
|
|
1824
1824
|
const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
|
|
1825
1825
|
if (identity) { commitCtrlPayloads2.push(identity); }
|
|
1826
1826
|
|
|
1827
|
-
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1827
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1828
|
+
// return { frame: commitFrame, };
|
|
1829
|
+
throw new Error(`not implemented (E: 27514878585889e531ef21f1abbef826)`);
|
|
1828
1830
|
}
|
|
1829
1831
|
|
|
1830
1832
|
// Build control payloads for delta propose
|
|
1831
1833
|
const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1832
1834
|
if (identity) { deltaCtrlPayloads.push(identity); }
|
|
1833
1835
|
|
|
1834
|
-
return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1836
|
+
// return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1837
|
+
// return { frame: deltaFrame, };
|
|
1838
|
+
throw new Error(`not implemented (E: ff35584696b6fcb3ad6dd7c5cade2f26)`);
|
|
1835
1839
|
}
|
|
1836
1840
|
}
|
|
1837
1841
|
}
|
|
@@ -1849,7 +1853,7 @@ export class SyncSagaCoordinator {
|
|
|
1849
1853
|
tempSpace: IbGibSpaceAny,
|
|
1850
1854
|
metaspace: MetaspaceService,
|
|
1851
1855
|
identity?: KeystoneIbGib_V1,
|
|
1852
|
-
}): Promise<
|
|
1856
|
+
}): Promise<NextSagaFrameInfo> {
|
|
1853
1857
|
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
1854
1858
|
if (logalot) { console.log(`${lc} Commit received.`); }
|
|
1855
1859
|
|
|
@@ -1864,18 +1868,19 @@ export class SyncSagaCoordinator {
|
|
|
1864
1868
|
// Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
|
|
1865
1869
|
|
|
1866
1870
|
if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
|
|
1867
|
-
return
|
|
1871
|
+
// return { responseWasNull: true };
|
|
1872
|
+
throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
|
|
1868
1873
|
}
|
|
1869
1874
|
|
|
1870
1875
|
// #endregion Handlers
|
|
1871
1876
|
|
|
1872
1877
|
protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
|
|
1873
1878
|
data,
|
|
1874
|
-
|
|
1879
|
+
localSpace,
|
|
1875
1880
|
metaspace,
|
|
1876
1881
|
}: {
|
|
1877
1882
|
data: TStoneData,
|
|
1878
|
-
|
|
1883
|
+
localSpace: IbGibSpaceAny,
|
|
1879
1884
|
metaspace: MetaspaceService,
|
|
1880
1885
|
}): Promise<IbGib_V1<TStoneData>> {
|
|
1881
1886
|
const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
|
|
@@ -1890,7 +1895,7 @@ export class SyncSagaCoordinator {
|
|
|
1890
1895
|
uuid: true, // we want the stone to have its own uniqueness
|
|
1891
1896
|
});
|
|
1892
1897
|
if (logalot) { console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`); }
|
|
1893
|
-
await
|
|
1898
|
+
await metaspace.put({ ibGib: stone, space: localSpace });
|
|
1894
1899
|
await metaspace.registerNewIbGib({ ibGib: stone });
|
|
1895
1900
|
return stone as IbGib_V1<TStoneData>;
|
|
1896
1901
|
} catch (error) {
|
|
@@ -1902,67 +1907,23 @@ export class SyncSagaCoordinator {
|
|
|
1902
1907
|
}
|
|
1903
1908
|
|
|
1904
1909
|
|
|
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
1910
|
/**
|
|
1950
1911
|
* Evolves the saga timeline with a new frame.
|
|
1951
1912
|
*/
|
|
1952
1913
|
protected async evolveSyncSagaIbGib({
|
|
1953
1914
|
prevSagaIbGib,
|
|
1915
|
+
conflictStrategy,
|
|
1954
1916
|
msgStones,
|
|
1955
|
-
|
|
1956
|
-
|
|
1917
|
+
sessionIdentity,
|
|
1918
|
+
localSpace,
|
|
1957
1919
|
metaspace,
|
|
1958
|
-
conflictStrategy,
|
|
1959
1920
|
}: {
|
|
1960
1921
|
prevSagaIbGib?: SyncIbGib_V1,
|
|
1922
|
+
conflictStrategy?: SyncConflictStrategy,
|
|
1961
1923
|
msgStones: IbGib_V1[],
|
|
1962
|
-
|
|
1963
|
-
space: IbGibSpaceAny,
|
|
1924
|
+
localSpace: IbGibSpaceAny,
|
|
1964
1925
|
metaspace: MetaspaceService,
|
|
1965
|
-
|
|
1926
|
+
sessionIdentity?: KeystoneIbGib_V1,
|
|
1966
1927
|
}): Promise<SyncIbGib_V1> {
|
|
1967
1928
|
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|
|
1968
1929
|
try {
|
|
@@ -1988,10 +1949,9 @@ export class SyncSagaCoordinator {
|
|
|
1988
1949
|
}
|
|
1989
1950
|
}
|
|
1990
1951
|
|
|
1991
|
-
const identityAddr =
|
|
1952
|
+
const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
|
|
1992
1953
|
|
|
1993
1954
|
if (prevSagaIbGib) {
|
|
1994
|
-
|
|
1995
1955
|
/**
|
|
1996
1956
|
* rel8ns always include the new msg stone(s)
|
|
1997
1957
|
*/
|
|
@@ -2003,11 +1963,11 @@ export class SyncSagaCoordinator {
|
|
|
2003
1963
|
];
|
|
2004
1964
|
|
|
2005
1965
|
// if we're authenticating/signing, we'll have identity
|
|
2006
|
-
if (
|
|
2007
|
-
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [
|
|
1966
|
+
if (sessionIdentity) {
|
|
1967
|
+
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
|
|
2008
1968
|
}
|
|
2009
1969
|
|
|
2010
|
-
// remove the existing sync msg stones' addrs
|
|
1970
|
+
// remove the existing sync msg stones' addrs
|
|
2011
1971
|
if (!prevSagaIbGib.rel8ns) { throw new Error(`(UNEXPECTED) prevSagaIbGib.rel8ns falsy? (E: 81375841aff85b1e48ea42ca218e6826)`); }
|
|
2012
1972
|
if (!prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME] || prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME].length === 0) {
|
|
2013
1973
|
throw new Error(`(UNEXPECTED) prevSagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME] falsy/empty? (E: 15156baad26fcccda80aa3a31718c726)`);
|
|
@@ -2025,7 +1985,7 @@ export class SyncSagaCoordinator {
|
|
|
2025
1985
|
rel8nInfos,
|
|
2026
1986
|
rel8nRemovalInfos,
|
|
2027
1987
|
metaspace,
|
|
2028
|
-
space,
|
|
1988
|
+
space: localSpace,
|
|
2029
1989
|
noDna: true, // Explicitly no DNA for sync frames
|
|
2030
1990
|
});
|
|
2031
1991
|
|
|
@@ -2033,33 +1993,33 @@ export class SyncSagaCoordinator {
|
|
|
2033
1993
|
return newFrame;
|
|
2034
1994
|
} else {
|
|
2035
1995
|
// Create New Timeline (Root Frame)
|
|
1996
|
+
// data
|
|
2036
1997
|
const data: SyncData_V1 = {
|
|
2037
1998
|
uuid: sagaId,
|
|
2038
|
-
// stage, // Removed from V1
|
|
2039
|
-
payload: undefined, // Data in stone
|
|
2040
1999
|
n: 0,
|
|
2041
2000
|
isTjp: true,
|
|
2042
2001
|
conflictStrategy,
|
|
2043
2002
|
};
|
|
2003
|
+
// ib
|
|
2044
2004
|
const ib = await getSyncIb({ data });
|
|
2045
|
-
|
|
2005
|
+
// rel8ns
|
|
2046
2006
|
const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
|
|
2047
|
-
const rel8ns:
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
if (identityAddr) {
|
|
2051
|
-
rel8ns.identity = [identityAddr];
|
|
2052
|
-
}
|
|
2007
|
+
const rel8ns: SyncRel8ns_V1 = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
|
|
2008
|
+
if (identityAddr) { rel8ns.identity = [identityAddr]; }
|
|
2053
2009
|
|
|
2054
2010
|
const resNew = await createTimeline({
|
|
2055
|
-
space,
|
|
2011
|
+
space: localSpace,
|
|
2056
2012
|
metaspace,
|
|
2057
2013
|
ib,
|
|
2058
2014
|
data,
|
|
2059
2015
|
rel8ns,
|
|
2060
2016
|
parentIb: SYNC_ATOM, // "sync"
|
|
2017
|
+
/**
|
|
2018
|
+
* this will squash the result into a single frame
|
|
2019
|
+
*/
|
|
2061
2020
|
noDna: true,
|
|
2062
2021
|
});
|
|
2022
|
+
if (!!resNew.intermediateIbGibs) { throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`); }
|
|
2063
2023
|
|
|
2064
2024
|
return resNew.newIbGib as SyncIbGib_V1;
|
|
2065
2025
|
}
|
|
@@ -2070,14 +2030,20 @@ export class SyncSagaCoordinator {
|
|
|
2070
2030
|
}
|
|
2071
2031
|
}
|
|
2072
2032
|
|
|
2073
|
-
protected async getStageAndPayloadFromFrame({
|
|
2033
|
+
protected async getStageAndPayloadFromFrame({
|
|
2034
|
+
sagaFrame,
|
|
2035
|
+
space
|
|
2036
|
+
}: {
|
|
2037
|
+
sagaFrame: IbGib_V1,
|
|
2038
|
+
space: IbGibSpaceAny
|
|
2039
|
+
}): Promise<{ stage: SyncStage, messageData: unknown }> {
|
|
2074
2040
|
const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
|
|
2075
2041
|
try {
|
|
2076
2042
|
if (logalot) { console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`); }
|
|
2077
2043
|
|
|
2078
|
-
if (!
|
|
2044
|
+
if (!sagaFrame.rel8ns) { throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`); }
|
|
2079
2045
|
|
|
2080
|
-
const stoneAddrs =
|
|
2046
|
+
const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
2081
2047
|
if (stoneAddrs && stoneAddrs.length > 0) {
|
|
2082
2048
|
const stoneAddr = stoneAddrs[0];
|
|
2083
2049
|
const res = await getFromSpace({ addr: stoneAddr, space });
|
|
@@ -2090,7 +2056,7 @@ export class SyncSagaCoordinator {
|
|
|
2090
2056
|
throw new Error(`couldn't get stoneAddr (${stoneAddr}) from space (${space.ib}) (E: f3c826e756f8a5c358beb88aa4a65e26)`);
|
|
2091
2057
|
}
|
|
2092
2058
|
} else {
|
|
2093
|
-
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame?
|
|
2059
|
+
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
|
|
2094
2060
|
}
|
|
2095
2061
|
} catch (error) {
|
|
2096
2062
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|