@ibgib/core-gib 0.1.26 → 0.1.28
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/sync/sync-helpers.d.mts +23 -2
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +121 -4
- package/dist/sync/sync-helpers.mjs.map +1 -1
- 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 +30 -9
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +1 -0
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- 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 +17 -3
- 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 +8 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +30 -153
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +585 -406
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +13 -18
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +36 -6
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +32 -0
- package/dist/sync/sync-types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/sync/sync-helpers.mts +113 -6
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +30 -8
- package/src/sync/sync-peer/sync-peer-v1.mts +2 -0
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +14 -2
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +13 -5
- package/src/sync/sync-saga-coordinator.mts +656 -463
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +27 -32
- package/src/sync/sync-types.mts +45 -8
|
@@ -19,21 +19,26 @@ import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space
|
|
|
19
19
|
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
|
-
import {
|
|
23
|
-
|
|
22
|
+
import {
|
|
23
|
+
SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN,
|
|
24
|
+
} from "./sync-constants.mjs";
|
|
25
|
+
import {
|
|
26
|
+
appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo
|
|
27
|
+
} from "../timeline/timeline-api.mjs";
|
|
24
28
|
import {
|
|
25
29
|
SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
|
|
26
30
|
SyncRel8ns_V1, DomainIbGibAnalysisInfo, NextSagaFrameInfo,
|
|
27
|
-
SYNC_CONFLICT_STRATEGY_VALID_VALUES,
|
|
28
|
-
|
|
31
|
+
SYNC_CONFLICT_STRATEGY_VALID_VALUES, HandleSagaResponseContextResult,
|
|
32
|
+
SyncExecutionContext,
|
|
33
|
+
SYNC_EXECUTION_CONTEXT_VALID_VALUES,
|
|
34
|
+
SyncSagaFrameDependencyGraph,
|
|
29
35
|
} from "./sync-types.mjs";
|
|
30
|
-
import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
|
|
36
|
+
import { getExecutionContext, getFullSyncSagaHistory, getSyncIb, getTempSpaceName, isPastFrame, putInSpace_dnasThenNonDnas, validateFullSyncSagaHistory } from "./sync-helpers.mjs";
|
|
31
37
|
import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
|
|
32
38
|
import {
|
|
33
39
|
SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
|
|
34
40
|
SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
|
|
35
|
-
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
|
|
36
|
-
SyncSagaPushOfferInfo,
|
|
41
|
+
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo, SyncSagaPushOfferInfo,
|
|
37
42
|
SyncSagaRequestAddrInfo,
|
|
38
43
|
} from "./sync-saga-message/sync-saga-message-types.mjs";
|
|
39
44
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
@@ -49,14 +54,12 @@ import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
|
49
54
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
50
55
|
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
51
56
|
import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
|
|
52
|
-
import {
|
|
53
|
-
IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
|
|
54
|
-
} from "../witness/space/space-types.mjs";
|
|
57
|
+
import { IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns } from "../witness/space/space-types.mjs";
|
|
55
58
|
import { FlatIbGibGraph } from "../common/other/graph-types.mjs";
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
59
|
-
const logalot =
|
|
62
|
+
const logalot = true;
|
|
60
63
|
const logalotControlDomain = true;
|
|
61
64
|
const lcControlDomain = '[ControlDomain]';
|
|
62
65
|
|
|
@@ -74,10 +77,10 @@ const lcControlDomain = '[ControlDomain]';
|
|
|
74
77
|
* to a specific Saga session, not fixed node identities.
|
|
75
78
|
*/
|
|
76
79
|
export class SyncSagaCoordinator {
|
|
77
|
-
|
|
80
|
+
private lc: string = `[${SyncSagaCoordinator.name}]`;
|
|
78
81
|
|
|
79
82
|
constructor(
|
|
80
|
-
|
|
83
|
+
private keystone: KeystoneService_V1
|
|
81
84
|
) {
|
|
82
85
|
|
|
83
86
|
}
|
|
@@ -202,7 +205,7 @@ export class SyncSagaCoordinator {
|
|
|
202
205
|
* @returns next context result if another round, else if commit returns
|
|
203
206
|
* null
|
|
204
207
|
*/
|
|
205
|
-
public async
|
|
208
|
+
public async continueSync({
|
|
206
209
|
sagaContext,
|
|
207
210
|
mySpace,
|
|
208
211
|
myTempSpace,
|
|
@@ -223,7 +226,7 @@ export class SyncSagaCoordinator {
|
|
|
223
226
|
identitySecret?: string,
|
|
224
227
|
metaspace: MetaspaceService,
|
|
225
228
|
}): Promise<SyncSagaContextIbGib_V1 | null> {
|
|
226
|
-
const lc = `${this.lc}[${this.
|
|
229
|
+
const lc = `${this.lc}[${this.continueSync.name}]`;
|
|
227
230
|
try {
|
|
228
231
|
if (logalot) { console.log(`${lc} starting... (I: f64e08bf77d1425378601f380384ec26)`); }
|
|
229
232
|
|
|
@@ -238,7 +241,11 @@ export class SyncSagaCoordinator {
|
|
|
238
241
|
|
|
239
242
|
if (!contextResult) {
|
|
240
243
|
if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`); }
|
|
241
|
-
|
|
244
|
+
// does this ever hit now?
|
|
245
|
+
return null; /* <<<< returns early */
|
|
246
|
+
} else if (contextResult.nextFrameInfo?.sagaComplete) {
|
|
247
|
+
// this is the current proper workflow I believe as of 01/22/2026
|
|
248
|
+
return null; /* <<<< returns early */
|
|
242
249
|
}
|
|
243
250
|
|
|
244
251
|
// #region error conditions throw
|
|
@@ -246,8 +253,6 @@ export class SyncSagaCoordinator {
|
|
|
246
253
|
throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: 7b41a183cf3cb58a5859c803800cf826)`);
|
|
247
254
|
} else if (!contextResult.nextFrameInfo) {
|
|
248
255
|
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
|
|
249
|
-
} else if (contextResult.nextFrameInfo?.responseWasNull) {
|
|
250
|
-
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
|
|
251
256
|
}
|
|
252
257
|
// #endregion error conditions throw
|
|
253
258
|
|
|
@@ -276,7 +281,7 @@ export class SyncSagaCoordinator {
|
|
|
276
281
|
}
|
|
277
282
|
}
|
|
278
283
|
|
|
279
|
-
|
|
284
|
+
private async getSessionIdentity({
|
|
280
285
|
sagaId,
|
|
281
286
|
metaspace,
|
|
282
287
|
tempSpace,
|
|
@@ -327,7 +332,7 @@ export class SyncSagaCoordinator {
|
|
|
327
332
|
* the NEXT request context.
|
|
328
333
|
* When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
|
|
329
334
|
*/
|
|
330
|
-
|
|
335
|
+
private async executeSagaLoop({
|
|
331
336
|
initFrame,
|
|
332
337
|
initDomainGraph,
|
|
333
338
|
peer,
|
|
@@ -509,8 +514,8 @@ export class SyncSagaCoordinator {
|
|
|
509
514
|
throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
|
|
510
515
|
} else if (!contextResult.nextFrameInfo) {
|
|
511
516
|
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
|
|
512
|
-
} else if (contextResult.nextFrameInfo?.
|
|
513
|
-
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.
|
|
517
|
+
} else if (contextResult.nextFrameInfo?.sagaComplete) {
|
|
518
|
+
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.sagaComplete? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
|
|
514
519
|
}
|
|
515
520
|
// #endregion error conditions throw
|
|
516
521
|
|
|
@@ -608,7 +613,7 @@ export class SyncSagaCoordinator {
|
|
|
608
613
|
}
|
|
609
614
|
}
|
|
610
615
|
|
|
611
|
-
|
|
616
|
+
private async analyzeDomainIbGibs({
|
|
612
617
|
domainIbGibs,
|
|
613
618
|
space,
|
|
614
619
|
}: {
|
|
@@ -654,7 +659,7 @@ export class SyncSagaCoordinator {
|
|
|
654
659
|
* Generates the first frame containing the Knowledge Vector of the Local Space.
|
|
655
660
|
* This is sent to the Receiver to begin Gap Analysis.
|
|
656
661
|
*/
|
|
657
|
-
|
|
662
|
+
private async createInitFrame({
|
|
658
663
|
sagaId,
|
|
659
664
|
sessionIdentity,
|
|
660
665
|
domainIbGibs,
|
|
@@ -720,7 +725,7 @@ export class SyncSagaCoordinator {
|
|
|
720
725
|
localSpace,
|
|
721
726
|
});
|
|
722
727
|
|
|
723
|
-
|
|
728
|
+
if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
724
729
|
|
|
725
730
|
return { initFrame: sagaFrame, initDomainGraph: fullGraph };
|
|
726
731
|
} catch (error) {
|
|
@@ -737,7 +742,7 @@ export class SyncSagaCoordinator {
|
|
|
737
742
|
*
|
|
738
743
|
* @returns when all {@link expectedAddrs} are done being transmitted.
|
|
739
744
|
*/
|
|
740
|
-
|
|
745
|
+
private async pollForDomainPayloads({
|
|
741
746
|
expectedAddrs,
|
|
742
747
|
pollIntervalMs,
|
|
743
748
|
domainPayloadsMap,
|
|
@@ -822,7 +827,7 @@ export class SyncSagaCoordinator {
|
|
|
822
827
|
*
|
|
823
828
|
* This is a one-off on the receiver.
|
|
824
829
|
*/
|
|
825
|
-
|
|
830
|
+
private async handleResponseSagaContext({
|
|
826
831
|
sagaContext,
|
|
827
832
|
initDomainGraph,
|
|
828
833
|
mySpace,
|
|
@@ -860,7 +865,7 @@ export class SyncSagaCoordinator {
|
|
|
860
865
|
if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
|
|
861
866
|
|
|
862
867
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
863
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space:
|
|
868
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
864
869
|
|
|
865
870
|
if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
|
|
866
871
|
|
|
@@ -875,39 +880,44 @@ export class SyncSagaCoordinator {
|
|
|
875
880
|
nextFrameInfo = await this.handleInitFrame({
|
|
876
881
|
sagaIbGib,
|
|
877
882
|
messageData: messageData as SyncSagaMessageInitData_V1,
|
|
878
|
-
metaspace,
|
|
879
|
-
|
|
880
|
-
myTempSpace: myTempSpace,
|
|
881
|
-
identity,
|
|
882
|
-
identitySecret
|
|
883
|
+
metaspace, mySpace, myTempSpace,
|
|
884
|
+
identity, identitySecret
|
|
883
885
|
});
|
|
884
886
|
break;
|
|
885
887
|
|
|
886
888
|
case SyncStage.ack:
|
|
887
889
|
if (!initDomainGraph) { throw new Error(`(UNEXPECTED) initDomainGraph falsy on the sender? (E: a3d758ad954829aba88663188eafc826)`); }
|
|
888
890
|
nextFrameInfo = await this.handleAckFrame({
|
|
891
|
+
sagaContext,
|
|
889
892
|
sagaIbGib,
|
|
890
|
-
srcGraph,
|
|
891
893
|
initDomainGraph,
|
|
892
|
-
metaspace,
|
|
893
|
-
destSpace: mySpace,
|
|
894
|
-
tempSpace: myTempSpace,
|
|
894
|
+
metaspace, mySpace, myTempSpace,
|
|
895
895
|
identity,
|
|
896
896
|
});
|
|
897
897
|
break;
|
|
898
898
|
|
|
899
899
|
case SyncStage.delta:
|
|
900
|
-
nextFrameInfo = await this.handleDeltaFrame({
|
|
900
|
+
nextFrameInfo = await this.handleDeltaFrame({
|
|
901
|
+
sagaContext,
|
|
902
|
+
sagaIbGib,
|
|
903
|
+
srcGraph,
|
|
904
|
+
metaspace,
|
|
905
|
+
mySpace,
|
|
906
|
+
myTempSpace,
|
|
907
|
+
identity,
|
|
908
|
+
});
|
|
901
909
|
break;
|
|
902
910
|
|
|
903
911
|
case SyncStage.commit:
|
|
904
|
-
nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace,
|
|
912
|
+
nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, mySpace: mySpace, myTempSpace: myTempSpace, identity, });
|
|
905
913
|
break;
|
|
906
914
|
|
|
907
915
|
default:
|
|
908
916
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
909
917
|
}
|
|
910
918
|
|
|
919
|
+
if (logalot) { console.log(`${lc} nextFrameInfo: ${nextFrameInfo ? pretty(nextFrameInfo) : 'undefined'} (I: a8ad281ca8e89385686d18327a105726)`); }
|
|
920
|
+
|
|
911
921
|
return { errorMsg: undefined, nextFrameInfo, }
|
|
912
922
|
|
|
913
923
|
} catch (error) {
|
|
@@ -930,10 +940,10 @@ export class SyncSagaCoordinator {
|
|
|
930
940
|
* The Receiver performs Gap Analysis here:
|
|
931
941
|
* 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
|
|
932
942
|
* 2. Identifies what Sender needs (`pushOfferAddrs`).
|
|
933
|
-
* 3. Identifies what Receiver needs (`
|
|
943
|
+
* 3. Identifies what Receiver needs (`deltaRequestAddrInfos`).
|
|
934
944
|
* 4. Returns an `Ack` frame containing these lists.
|
|
935
945
|
*/
|
|
936
|
-
|
|
946
|
+
private async handleInitFrame({
|
|
937
947
|
sagaIbGib,
|
|
938
948
|
messageData,
|
|
939
949
|
mySpace,
|
|
@@ -1183,7 +1193,7 @@ export class SyncSagaCoordinator {
|
|
|
1183
1193
|
|
|
1184
1194
|
const ackStone = await this.createSyncMsgStone({
|
|
1185
1195
|
data: ackData,
|
|
1186
|
-
localSpace:
|
|
1196
|
+
localSpace: mySpace,
|
|
1187
1197
|
metaspace,
|
|
1188
1198
|
});
|
|
1189
1199
|
if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
|
|
@@ -1274,22 +1284,29 @@ export class SyncSagaCoordinator {
|
|
|
1274
1284
|
* **Execution Context**: **Sender (Local)**.
|
|
1275
1285
|
*
|
|
1276
1286
|
* The Sender reacts to the Receiver's requirements:
|
|
1277
|
-
* 1.
|
|
1278
|
-
*
|
|
1287
|
+
* 1. `deltaRequestAddrInfos`: Receiver wants this data. Sender takes this
|
|
1288
|
+
* into account, plus gathers it from `initDomainGraph` and puts them in
|
|
1289
|
+
* `payloadIbGibs` (for next frame).
|
|
1290
|
+
* 2. `pushOfferAddrs`: Receiver has newer data. these should have been
|
|
1291
|
+
* included in the incoming context from the receiver..
|
|
1279
1292
|
*
|
|
1280
1293
|
* Returns a `Delta` frame.
|
|
1281
1294
|
*/
|
|
1282
|
-
|
|
1295
|
+
private async handleAckFrame({
|
|
1296
|
+
sagaContext,
|
|
1283
1297
|
sagaIbGib,
|
|
1284
|
-
srcGraph,
|
|
1285
1298
|
initDomainGraph,
|
|
1286
|
-
|
|
1287
|
-
tempSpace,
|
|
1288
|
-
metaspace,
|
|
1299
|
+
mySpace, myTempSpace, metaspace,
|
|
1289
1300
|
identity,
|
|
1290
1301
|
}: {
|
|
1302
|
+
/**
|
|
1303
|
+
* todo: figure out if we need to do something about incoming push offer payload domain ibgibs
|
|
1304
|
+
* I'm adding this because we should be checking incoming payloads
|
|
1305
|
+
* right? for push offers? This is when the receiver had new ibgibs.
|
|
1306
|
+
* Not sure if I need to put this here though...
|
|
1307
|
+
*/
|
|
1308
|
+
sagaContext: SyncSagaContextIbGib_V1,
|
|
1291
1309
|
sagaIbGib: SyncIbGib_V1,
|
|
1292
|
-
srcGraph: { [addr: string]: IbGib_V1 },
|
|
1293
1310
|
/**
|
|
1294
1311
|
* This is the initial dependency graph of all domain ibgibs passed in
|
|
1295
1312
|
* to the original {@link sync} call.
|
|
@@ -1297,8 +1314,8 @@ export class SyncSagaCoordinator {
|
|
|
1297
1314
|
* if we're executing on the sender, this will be populated
|
|
1298
1315
|
*/
|
|
1299
1316
|
initDomainGraph: FlatIbGibGraph,
|
|
1300
|
-
|
|
1301
|
-
|
|
1317
|
+
mySpace: IbGibSpaceAny,
|
|
1318
|
+
myTempSpace: IbGibSpaceAny,
|
|
1302
1319
|
metaspace: MetaspaceService,
|
|
1303
1320
|
identity?: KeystoneIbGib_V1,
|
|
1304
1321
|
}): Promise<NextSagaFrameInfo> {
|
|
@@ -1306,9 +1323,10 @@ export class SyncSagaCoordinator {
|
|
|
1306
1323
|
try {
|
|
1307
1324
|
if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
|
|
1308
1325
|
|
|
1309
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space:
|
|
1326
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
1310
1327
|
const ackData = messageData as SyncSagaMessageAckData_V1;
|
|
1311
1328
|
|
|
1329
|
+
// #region sanity/validation
|
|
1312
1330
|
if (!ackData) {
|
|
1313
1331
|
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
1314
1332
|
}
|
|
@@ -1316,6 +1334,8 @@ export class SyncSagaCoordinator {
|
|
|
1316
1334
|
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
1317
1335
|
}
|
|
1318
1336
|
if (logalot) { console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`); }
|
|
1337
|
+
if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 385e389610282aa9c5dbe4083adbde26)`); }
|
|
1338
|
+
// #region sanity/validation
|
|
1319
1339
|
|
|
1320
1340
|
// 1. Check for Conflicts
|
|
1321
1341
|
const conflicts = ackData.conflicts || [];
|
|
@@ -1334,7 +1354,12 @@ export class SyncSagaCoordinator {
|
|
|
1334
1354
|
|
|
1335
1355
|
// at this point, if we have conflicts, they are non-terminal
|
|
1336
1356
|
|
|
1337
|
-
|
|
1357
|
+
/**
|
|
1358
|
+
* at this point, we only request ibgibs for conflicted timelines.
|
|
1359
|
+
* If the receiver had known of any ibgibs this sender needed, it
|
|
1360
|
+
* would have been in the push offer.
|
|
1361
|
+
*/
|
|
1362
|
+
const outgoingDeltaAddrRequestInfos: SyncSagaRequestAddrInfo[] = []; // Additional requests for merging
|
|
1338
1363
|
|
|
1339
1364
|
if (conflicts.length > 0) {
|
|
1340
1365
|
console.log(`${lc} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
|
|
@@ -1346,8 +1371,8 @@ export class SyncSagaCoordinator {
|
|
|
1346
1371
|
// 4. (Later in Delta Phase) Perform Merge.
|
|
1347
1372
|
|
|
1348
1373
|
// BUT: The Delta Phase is usually generic "Send me these Addrs".
|
|
1349
|
-
// If we just add to `
|
|
1350
|
-
// wait. `ackData.
|
|
1374
|
+
// If we just add to `deltaRequestAddrInfos` (which are requests for Sender to send to Receiver?),
|
|
1375
|
+
// wait. `ackData.deltaRequestAddrInfos` are what RECEIVER wants from SENDER.
|
|
1351
1376
|
|
|
1352
1377
|
// We (Sender) are processing the Ack.
|
|
1353
1378
|
// We need to request data FROM Receiver.
|
|
@@ -1375,6 +1400,7 @@ export class SyncSagaCoordinator {
|
|
|
1375
1400
|
|
|
1376
1401
|
throw new Error(`conflicts not (re)implemented yet (E: 3b7d0819f83842a6de3ae988819bc826)`);
|
|
1377
1402
|
|
|
1403
|
+
|
|
1378
1404
|
// const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
1379
1405
|
|
|
1380
1406
|
// // Sender History
|
|
@@ -1432,8 +1458,8 @@ export class SyncSagaCoordinator {
|
|
|
1432
1458
|
// // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
|
|
1433
1459
|
// for (const addr of receiverOnlyAddrs) {
|
|
1434
1460
|
// // Add the frame itself first
|
|
1435
|
-
// if (!
|
|
1436
|
-
//
|
|
1461
|
+
// if (!outgoingDeltaAddrRequestInfos.includes(addr)) {
|
|
1462
|
+
// outgoingDeltaAddrRequestInfos.push(addr);
|
|
1437
1463
|
// }
|
|
1438
1464
|
|
|
1439
1465
|
// // Get the frame's delta dependencies (skip LCA's deps)
|
|
@@ -1452,8 +1478,8 @@ export class SyncSagaCoordinator {
|
|
|
1452
1478
|
// if (frameDeltaDeps) {
|
|
1453
1479
|
// // Add all delta dependencies (Object.keys gives us the addresses)
|
|
1454
1480
|
// Object.keys(frameDeltaDeps).forEach(depAddr => {
|
|
1455
|
-
// if (!
|
|
1456
|
-
//
|
|
1481
|
+
// if (!outgoingDeltaAddrRequestInfos.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
|
|
1482
|
+
// outgoingDeltaAddrRequestInfos.push(depAddr);
|
|
1457
1483
|
// }
|
|
1458
1484
|
// });
|
|
1459
1485
|
// }
|
|
@@ -1463,119 +1489,59 @@ export class SyncSagaCoordinator {
|
|
|
1463
1489
|
// }
|
|
1464
1490
|
// }
|
|
1465
1491
|
|
|
1466
|
-
// console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${
|
|
1492
|
+
// console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${outgoingDeltaAddrRequestInfos.length}`);
|
|
1467
1493
|
// } else {
|
|
1468
1494
|
// console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
|
|
1469
1495
|
// }
|
|
1470
1496
|
}
|
|
1471
1497
|
|
|
1472
|
-
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts.
|
|
1498
|
+
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. outgoingDeltaAddrRequestInfos: ${outgoingDeltaAddrRequestInfos.length}`);
|
|
1473
1499
|
} else {
|
|
1474
1500
|
console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
|
|
1475
1501
|
}
|
|
1476
1502
|
|
|
1477
1503
|
// 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
|
|
1478
1504
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
// 2. Process Delta Requests (Push Payload)
|
|
1492
|
-
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
1493
|
-
// const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
|
|
1494
|
-
const skipAddrs = new Set<string>();
|
|
1495
|
-
if (ackData.knowledgeVector) {
|
|
1496
|
-
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
1497
|
-
// addrs.forEach(a => skipAddrs.add(a));
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
const payloadIbGibs: IbGib_V1[] = [];
|
|
1502
|
-
// Gather all tips to sync first
|
|
1503
|
-
const tipsToSync: IbGib_V1[] = [];
|
|
1504
|
-
for (const addr of deltaReqAddrs) {
|
|
1505
|
-
// let ibGib = srcGraph[addr];
|
|
1506
|
-
// if (!ibGib) {
|
|
1507
|
-
// const res = await getFromSpace({ addr, space: destSpace });
|
|
1508
|
-
// if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1509
|
-
// ibGib = res.ibGibs[0];
|
|
1510
|
-
// }
|
|
1511
|
-
// }
|
|
1512
|
-
// if (ibGib) {
|
|
1513
|
-
// tipsToSync.push(ibGib);
|
|
1514
|
-
// } else {
|
|
1515
|
-
// throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
1516
|
-
// }
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
1520
|
-
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
1521
|
-
// `getDependencyGraph` takes a single ibGib.
|
|
1522
|
-
// We can optimize by doing it for each tip and unioning the result?
|
|
1523
|
-
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
1524
|
-
// We will loop.
|
|
1525
|
-
|
|
1526
|
-
const allDepsSet = new Set<string>();
|
|
1527
|
-
|
|
1528
|
-
for (const tip of tipsToSync) {
|
|
1529
|
-
// Always include the tip itself
|
|
1530
|
-
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
1531
|
-
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
1532
|
-
// But detailed deps might be skipped.
|
|
1533
|
-
|
|
1534
|
-
// Get Graph with Skips
|
|
1535
|
-
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
1536
|
-
const deps = await getDependencyGraph({
|
|
1537
|
-
ibGib: tip,
|
|
1538
|
-
space: destSpace,
|
|
1539
|
-
skipAddrs: Array.from(skipAddrs)
|
|
1540
|
-
});
|
|
1541
|
-
|
|
1542
|
-
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
1543
|
-
let tipIncluded = false;
|
|
1544
|
-
|
|
1545
|
-
if (deps) {
|
|
1546
|
-
Object.values(deps).forEach(d => {
|
|
1547
|
-
const dAddr = getIbGibAddr({ ibGib: d });
|
|
1548
|
-
if (!allDepsSet.has(dAddr)) {
|
|
1549
|
-
allDepsSet.add(dAddr);
|
|
1550
|
-
payloadIbGibs.push(d);
|
|
1551
|
-
}
|
|
1552
|
-
if (dAddr === tipAddr) { tipIncluded = true; }
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
if (!tipIncluded && !skipAddrs.has(tipAddr)) {
|
|
1557
|
-
if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
|
|
1558
|
-
if (!allDepsSet.has(tipAddr)) {
|
|
1559
|
-
allDepsSet.add(tipAddr);
|
|
1560
|
-
payloadIbGibs.push(tip);
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1505
|
+
/**
|
|
1506
|
+
* these were requested addrs on the INCOMING frame (the ack data).
|
|
1507
|
+
*
|
|
1508
|
+
* This is in contrast to any OUTGOING requests we make in this
|
|
1509
|
+
* method.
|
|
1510
|
+
*/
|
|
1511
|
+
const payloadIbGibsDomain = await this.getPayloadsForRequestedInfos({
|
|
1512
|
+
deltaRequestAddrInfos: ackData.deltaRequestAddrInfos || [],
|
|
1513
|
+
mySpace,
|
|
1514
|
+
});
|
|
1564
1515
|
|
|
1565
1516
|
// 3. Create Delta Frame
|
|
1566
|
-
const sagaId = ackData.sagaId;
|
|
1567
1517
|
const deltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1568
|
-
sagaId: sagaIbGib.data
|
|
1518
|
+
sagaId: sagaIbGib.data.uuid,
|
|
1569
1519
|
stage: SyncStage.delta,
|
|
1570
|
-
|
|
1571
|
-
|
|
1520
|
+
/**
|
|
1521
|
+
* we're sending these domain ibgibs as payload (they were
|
|
1522
|
+
* requested by receiver)
|
|
1523
|
+
*/
|
|
1524
|
+
payloadAddrsDomain: payloadIbGibsDomain.length > 0 ?
|
|
1525
|
+
payloadIbGibsDomain.map(x => getIbGibAddr({ ibGib: x })) :
|
|
1526
|
+
undefined,
|
|
1527
|
+
/**
|
|
1528
|
+
* we're asking for these addrs
|
|
1529
|
+
*/
|
|
1530
|
+
deltaRequestAddrInfos: outgoingDeltaAddrRequestInfos.length > 0 ?
|
|
1531
|
+
outgoingDeltaAddrRequestInfos :
|
|
1532
|
+
undefined,
|
|
1533
|
+
/**
|
|
1534
|
+
* if we have no changes and request none, propose commit to
|
|
1535
|
+
* finish the sync transaction.
|
|
1536
|
+
*/
|
|
1537
|
+
proposeCommit: payloadIbGibsDomain.length === 0 && outgoingDeltaAddrRequestInfos.length === 0,
|
|
1572
1538
|
};
|
|
1573
1539
|
|
|
1574
1540
|
if (logalot) { console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`); }
|
|
1575
1541
|
|
|
1576
1542
|
const deltaStone = await this.createSyncMsgStone({
|
|
1577
1543
|
data: deltaData,
|
|
1578
|
-
localSpace:
|
|
1544
|
+
localSpace: mySpace,
|
|
1579
1545
|
metaspace,
|
|
1580
1546
|
});
|
|
1581
1547
|
|
|
@@ -1583,7 +1549,7 @@ export class SyncSagaCoordinator {
|
|
|
1583
1549
|
prevSagaIbGib: sagaIbGib,
|
|
1584
1550
|
msgStones: [deltaStone],
|
|
1585
1551
|
sessionIdentity: identity,
|
|
1586
|
-
localSpace:
|
|
1552
|
+
localSpace: mySpace,
|
|
1587
1553
|
metaspace,
|
|
1588
1554
|
});
|
|
1589
1555
|
|
|
@@ -1593,9 +1559,7 @@ export class SyncSagaCoordinator {
|
|
|
1593
1559
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1594
1560
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1595
1561
|
|
|
1596
|
-
|
|
1597
|
-
// return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
|
|
1598
|
-
throw new Error(`not implemented (E: 62e1e2a408e8bfa2982b2f87e8843826)`);
|
|
1562
|
+
return { frame: deltaFrame, payloadIbGibsDomain, };
|
|
1599
1563
|
} catch (error) {
|
|
1600
1564
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1601
1565
|
throw error;
|
|
@@ -1614,389 +1578,550 @@ export class SyncSagaCoordinator {
|
|
|
1614
1578
|
* 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
|
|
1615
1579
|
* 3. **Completion**: If no more requests, transitions to `Commit`.
|
|
1616
1580
|
*/
|
|
1617
|
-
|
|
1581
|
+
private async handleDeltaFrame({
|
|
1582
|
+
sagaContext,
|
|
1618
1583
|
sagaIbGib,
|
|
1619
1584
|
srcGraph,
|
|
1620
|
-
|
|
1621
|
-
|
|
1585
|
+
mySpace,
|
|
1586
|
+
myTempSpace,
|
|
1622
1587
|
metaspace,
|
|
1623
1588
|
identity,
|
|
1624
1589
|
}: {
|
|
1590
|
+
sagaContext: SyncSagaContextIbGib_V1,
|
|
1625
1591
|
sagaIbGib: SyncIbGib_V1,
|
|
1626
1592
|
srcGraph: { [addr: string]: IbGib_V1 },
|
|
1627
|
-
|
|
1628
|
-
|
|
1593
|
+
mySpace: IbGibSpaceAny,
|
|
1594
|
+
myTempSpace: IbGibSpaceAny,
|
|
1629
1595
|
metaspace: MetaspaceService,
|
|
1630
1596
|
identity?: KeystoneIbGib_V1,
|
|
1631
1597
|
}): Promise<NextSagaFrameInfo> {
|
|
1632
1598
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1636
|
-
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1637
|
-
|
|
1638
|
-
if (!deltaData) {
|
|
1639
|
-
throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
1640
|
-
}
|
|
1641
|
-
if (deltaData.stage !== SyncStage.delta) {
|
|
1642
|
-
throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
|
|
1643
|
-
}
|
|
1644
|
-
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
|
|
1645
|
-
|
|
1646
|
-
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1647
|
-
|
|
1648
|
-
const payloadAddrs = deltaData.payloadAddrs || [];
|
|
1649
|
-
const peerRequests = deltaData.requests || [];
|
|
1650
|
-
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
1651
|
-
|
|
1652
|
-
// 1. Process Received Payload (Ingest)
|
|
1653
|
-
const receivedPayloadIbGibs: IbGib_V1[] = [];
|
|
1654
|
-
if (payloadAddrs.length > 0) {
|
|
1655
|
-
// We use `payloadAddrs` as the manifest.
|
|
1656
|
-
// The ACTUAL collection of ibGibs should be available via `getFromSpace`
|
|
1657
|
-
// assuming the "Transport" layer put them there implicitly?
|
|
1658
|
-
// OR, if we are local-only, we just get them.
|
|
1659
|
-
// The `handleDeltaFrame` contract assumes data is reachable in `space`.
|
|
1660
|
-
|
|
1661
|
-
const res = await getFromSpace({
|
|
1662
|
-
addrs: payloadAddrs,
|
|
1663
|
-
space: tempSpace, // Incoming data is in tempSpace
|
|
1664
|
-
});
|
|
1665
|
-
if (res.ibGibs) {
|
|
1666
|
-
receivedPayloadIbGibs.push(...res.ibGibs);
|
|
1667
|
-
// Also put them? `getFromSpace` retrieves. If they are in space, they are persisted.
|
|
1668
|
-
// If this is a Temp Space, they are safe.
|
|
1669
|
-
} else {
|
|
1670
|
-
console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1599
|
+
try {
|
|
1600
|
+
if (logalot) { console.log(`${lc} starting... (I: a1d0a85eb4189466f86dfd61e3df2626)`); }
|
|
1673
1601
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
const outgoingAddrsSet = new Set<string>(); // Track what we've added
|
|
1602
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
1603
|
+
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1677
1604
|
|
|
1678
|
-
|
|
1605
|
+
// #region validate/sanity
|
|
1606
|
+
if (!deltaData) { throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`); }
|
|
1607
|
+
if (deltaData.stage !== SyncStage.delta) { throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`); }
|
|
1608
|
+
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
|
|
1609
|
+
// #endregion validate/sanity
|
|
1679
1610
|
|
|
1680
|
-
|
|
1681
|
-
// Get the requested ibGib
|
|
1682
|
-
let ibGib = srcGraph[addr];
|
|
1683
|
-
if (!ibGib) {
|
|
1684
|
-
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1685
|
-
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1686
|
-
ibGib = res.ibGibs[0];
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1611
|
+
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1689
1612
|
|
|
1690
|
-
|
|
1691
|
-
// Add the requested ibGib itself
|
|
1692
|
-
const ibGibAddr = getIbGibAddr({ ibGib });
|
|
1693
|
-
if (!outgoingAddrsSet.has(ibGibAddr)) {
|
|
1694
|
-
outgoingPayload.push(ibGib);
|
|
1695
|
-
outgoingAddrsSet.add(ibGibAddr);
|
|
1696
|
-
}
|
|
1613
|
+
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
1697
1614
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
ibGib,
|
|
1703
|
-
space: destSpace,
|
|
1704
|
-
});
|
|
1615
|
+
/**
|
|
1616
|
+
* these are already in the local temp space
|
|
1617
|
+
*/
|
|
1618
|
+
const receivedPayloadIbGibs: IbGib_V1[] = sagaContext.payloadIbGibsDomain ?? [];
|
|
1705
1619
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1620
|
+
// 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
|
|
1621
|
+
console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${(deltaData.deltaRequestAddrInfos || []).length} peer requests`);
|
|
1622
|
+
const outgoingPayload = await this.getPayloadsForRequestedInfos({
|
|
1623
|
+
deltaRequestAddrInfos: deltaData.deltaRequestAddrInfos || [],
|
|
1624
|
+
mySpace,
|
|
1625
|
+
});
|
|
1626
|
+
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1627
|
+
|
|
1628
|
+
// 3. Execute Merges (If applicable)
|
|
1629
|
+
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1630
|
+
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1631
|
+
// Optimization: Do this only if we received payloads.
|
|
1632
|
+
const mergeResultIbGibs: IbGib_V1[] = [];
|
|
1633
|
+
|
|
1634
|
+
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1635
|
+
|
|
1636
|
+
if (receivedPayloadIbGibs.length > 0) {
|
|
1637
|
+
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1638
|
+
// Find the Ack frame in history to get conflicts
|
|
1639
|
+
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1640
|
+
// V1 timelines carry full history in `past`.
|
|
1641
|
+
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1642
|
+
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1643
|
+
|
|
1644
|
+
const sagaHistory = await getFullSyncSagaHistory({
|
|
1645
|
+
sagaIbGib,
|
|
1646
|
+
space: mySpace,
|
|
1647
|
+
});
|
|
1648
|
+
// #region validate/sanity sagaHistory
|
|
1649
|
+
if (sagaHistory.length === 0) { throw new Error(`(UNEXPECTED) sagaHistory.length is 0? We're in handleDeltaFrame. So we should have at least init and ack. (E: 815735c9cf756b275719bf434f180826)`); }
|
|
1650
|
+
if (sagaHistory.length === 1) { throw new Error(`(UNEXPECTED) sagaHistory.length is 1? We're in handleDeltaFrame. So we should have at least init and ack. (E: 0e4ef8e3ed088e83b2cdcd18ecea9826)`); }
|
|
1651
|
+
// #endregion validate/sanity sagaHistory
|
|
1652
|
+
|
|
1653
|
+
const ackGraphs = sagaHistory.filter(x =>
|
|
1654
|
+
x.msgStones.at(0)!.data!.stage === SyncStage.ack
|
|
1655
|
+
);
|
|
1656
|
+
// #region validate/sanity ackGraphs
|
|
1657
|
+
if (ackGraphs.length > 1) {
|
|
1658
|
+
throw new Error(`(UNEXPECTED) more than one ack stage found in sagaHistory? we're expecting exactly one. (E: 7150983bb3a841caf8acd60826ba4f26)`);
|
|
1659
|
+
} else if (ackGraphs.length === 0) {
|
|
1660
|
+
throw new Error(`(UNEXPECTED) couldn't find ack stage in sagaHistory? (E: 7d2da859196b86de28e7c8183af1e826)`);
|
|
1717
1661
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
// 3. Execute Merges (If applicable)
|
|
1727
|
-
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1728
|
-
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1729
|
-
// Optimization: Do this only if we received payloads.
|
|
1730
|
-
const mergeResultIbGibs: IbGib_V1[] = [];
|
|
1731
|
-
|
|
1732
|
-
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1733
|
-
|
|
1734
|
-
if (receivedPayloadIbGibs.length > 0) {
|
|
1735
|
-
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1736
|
-
// Find the Ack frame in history to get conflicts
|
|
1737
|
-
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1738
|
-
// V1 timelines carry full history in `past`.
|
|
1739
|
-
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1740
|
-
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1741
|
-
let ackData: SyncSagaMessageAckData_V1 | undefined;
|
|
1742
|
-
|
|
1743
|
-
if (pastAddrs.length > 0) {
|
|
1744
|
-
// Batch fetch all past frames
|
|
1745
|
-
const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
|
|
1746
|
-
if (resPast.success && resPast.ibGibs) {
|
|
1747
|
-
// Iterate backwards (most recent first) to find the latest Ack
|
|
1748
|
-
for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
|
|
1749
|
-
const pastFrame = resPast.ibGibs[i];
|
|
1750
|
-
const messageStone = await getSyncSagaMessageFromFrame({
|
|
1751
|
-
frameIbGib: pastFrame,
|
|
1752
|
-
space: tempSpace
|
|
1753
|
-
});
|
|
1754
|
-
if (messageStone?.data?.stage === SyncStage.ack) {
|
|
1755
|
-
ackData = messageStone.data as SyncSagaMessageAckData_V1;
|
|
1756
|
-
console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
|
|
1757
|
-
break;
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1662
|
+
if (ackGraphs[0].msgStones.length !== 1) {
|
|
1663
|
+
throw new Error(`(UNEXPECTED) ackGraph has more than one msg stone? only one expected right now. (E: f07d388abff8d26f98cbf6e8d3932826)`);
|
|
1760
1664
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
//
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1665
|
+
// #endregion validate/sanity ackGraphs
|
|
1666
|
+
|
|
1667
|
+
const ackData = ackGraphs[0].msgStones[0].data as SyncSagaMessageAckData_V1;
|
|
1668
|
+
if (!ackData) { throw new Error(`(UNEXPECTED) ackData falsy? (E: f9af88427f66a8db1ba868b810e8b826)`); }
|
|
1669
|
+
|
|
1670
|
+
if (ackData && ackData.conflicts) {
|
|
1671
|
+
const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
|
|
1672
|
+
for (const conflict of optimisticConflicts) {
|
|
1673
|
+
const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
1674
|
+
// We are Sender (usually) here if we are merging.
|
|
1675
|
+
// Check if we have the history needed (timelineAddrs).
|
|
1676
|
+
// Specifically, we needed the `receiverOnly` parts.
|
|
1677
|
+
|
|
1678
|
+
// We blindly attempt merge if we have both tips accessible?
|
|
1679
|
+
// We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
|
|
1680
|
+
|
|
1681
|
+
// Check if we have receiverTip in space
|
|
1682
|
+
console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
|
|
1683
|
+
const resRecTip = await getFromSpace({ addr: receiverTip, space: myTempSpace }); // Check myTempSpace for incoming data
|
|
1684
|
+
console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in myTempSpace: ${!!resRecTip.ibGibs?.[0]}`);
|
|
1685
|
+
if (resRecTip.success && resRecTip.ibGibs?.[0]) {
|
|
1686
|
+
// We have the tip!
|
|
1687
|
+
// Do we have the full history?
|
|
1688
|
+
// `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
|
|
1689
|
+
// If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
|
|
1690
|
+
|
|
1691
|
+
// Perform Merge!
|
|
1692
|
+
try {
|
|
1693
|
+
const mergeResult = await mergeDivergentTimelines({
|
|
1694
|
+
tipA: (await getFromSpace({ addr: senderTip, space: mySpace })).ibGibs![0], // Our tip from destSpace
|
|
1695
|
+
tipB: resRecTip.ibGibs[0], // Their tip (from myTempSpace)
|
|
1696
|
+
space: myTempSpace, // Merge uses myTempSpace
|
|
1697
|
+
metaspace,
|
|
1698
|
+
});
|
|
1699
|
+
if (mergeResult) {
|
|
1700
|
+
console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1701
|
+
if (logalot) { console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`); }
|
|
1702
|
+
mergeResultIbGibs.push(mergeResult);
|
|
1703
|
+
outgoingPayload.push(mergeResult); // Send result to peer
|
|
1704
|
+
}
|
|
1705
|
+
} catch (e) {
|
|
1706
|
+
console.error(`${lc} Merge failed: ${e}`);
|
|
1707
|
+
// If merge fails, we might Abort or just continue?
|
|
1797
1708
|
}
|
|
1798
|
-
} catch (e) {
|
|
1799
|
-
console.error(`${lc} Merge failed: ${e}`);
|
|
1800
|
-
// If merge fails, we might Abort or just continue?
|
|
1801
1709
|
}
|
|
1802
1710
|
}
|
|
1803
1711
|
}
|
|
1804
1712
|
}
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
// 4. Determine Next Action
|
|
1808
|
-
// We have `outgoingPayload` (Requests + Merge Results).
|
|
1809
|
-
// Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
|
|
1810
|
-
// Do WE have outstanding requests?
|
|
1811
|
-
// We might if `mergeResult` requires further sync? Usually no, result is complete.
|
|
1812
|
-
|
|
1813
|
-
const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
|
|
1814
|
-
|
|
1815
|
-
const hasOutgoing = outgoingPayload.length > 0;
|
|
1816
|
-
const hasMyRequests = myRequests.length > 0;
|
|
1817
|
-
|
|
1818
|
-
if (hasOutgoing || hasMyRequests) {
|
|
1819
|
-
// We have business to attend to -> Send Delta
|
|
1820
|
-
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1821
|
-
sagaId: deltaData.sagaId,
|
|
1822
|
-
stage: SyncStage.delta,
|
|
1823
|
-
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
1824
|
-
requests: hasMyRequests ? myRequests : undefined,
|
|
1825
|
-
proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
|
|
1826
|
-
// Wait. If we send data, we are NOT committing yet.
|
|
1827
|
-
// We are sending data. The OTHER side must ingest it.
|
|
1828
|
-
// So proposeCommit = true?
|
|
1829
|
-
// "Here is the data. I'm done. If you are good, let's commit."
|
|
1830
|
-
// Yes.
|
|
1831
|
-
};
|
|
1832
|
-
|
|
1833
|
-
// BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
|
|
1834
|
-
// We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
|
|
1835
|
-
|
|
1836
|
-
// So yes, proposeCommit = true.
|
|
1837
|
-
responseDeltaData.proposeCommit = true;
|
|
1838
|
-
|
|
1839
|
-
const deltaStone = await this.createSyncMsgStone({
|
|
1840
|
-
data: responseDeltaData,
|
|
1841
|
-
localSpace: tempSpace,
|
|
1842
|
-
metaspace
|
|
1843
|
-
});
|
|
1844
1713
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
metaspace
|
|
1851
|
-
});
|
|
1852
|
-
|
|
1853
|
-
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1854
|
-
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1855
|
-
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1856
|
-
|
|
1857
|
-
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1858
|
-
// return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1859
|
-
throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
|
|
1860
|
-
|
|
1861
|
-
} else {
|
|
1862
|
-
// We have nothing to send.
|
|
1863
|
-
|
|
1864
|
-
if (peerProposesCommit) {
|
|
1865
|
-
// Peer is done. We are done. -> Commit.
|
|
1866
|
-
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
1867
|
-
sagaId: deltaData.sagaId,
|
|
1868
|
-
stage: SyncStage.commit,
|
|
1869
|
-
success: true,
|
|
1870
|
-
};
|
|
1871
|
-
|
|
1872
|
-
const commitStone = await this.createSyncMsgStone({
|
|
1873
|
-
data: commitData,
|
|
1874
|
-
localSpace: tempSpace,
|
|
1875
|
-
metaspace
|
|
1876
|
-
});
|
|
1877
|
-
|
|
1878
|
-
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1879
|
-
prevSagaIbGib: sagaIbGib,
|
|
1880
|
-
msgStones: [commitStone],
|
|
1881
|
-
sessionIdentity: identity,
|
|
1882
|
-
localSpace: tempSpace,
|
|
1883
|
-
metaspace
|
|
1884
|
-
});
|
|
1714
|
+
// 4. Determine Next Action
|
|
1715
|
+
// We have `outgoingPayload` (Requests + Merge Results).
|
|
1716
|
+
// Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
|
|
1717
|
+
// Do WE have outstanding requests?
|
|
1718
|
+
// We might if `mergeResult` requires further sync? Usually no, result is complete.
|
|
1885
1719
|
|
|
1886
|
-
|
|
1887
|
-
const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
|
|
1888
|
-
if (identity) { commitCtrlPayloads.push(identity); }
|
|
1720
|
+
const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
|
|
1889
1721
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
|
|
1722
|
+
const hasOutgoing = outgoingPayload.length > 0;
|
|
1723
|
+
const hasMyRequests = myRequests.length > 0;
|
|
1893
1724
|
|
|
1894
|
-
|
|
1895
|
-
//
|
|
1896
|
-
// But we are empty.
|
|
1897
|
-
// So WE propose commit.
|
|
1725
|
+
if (hasOutgoing || hasMyRequests) {
|
|
1726
|
+
// We have business to attend to -> Send Delta
|
|
1898
1727
|
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1899
1728
|
sagaId: deltaData.sagaId,
|
|
1900
1729
|
stage: SyncStage.delta,
|
|
1901
|
-
|
|
1902
|
-
|
|
1730
|
+
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
1731
|
+
requests: hasMyRequests ? myRequests : undefined,
|
|
1903
1732
|
};
|
|
1904
1733
|
|
|
1905
1734
|
const deltaStone = await this.createSyncMsgStone({
|
|
1906
1735
|
data: responseDeltaData,
|
|
1907
|
-
localSpace:
|
|
1908
|
-
metaspace
|
|
1736
|
+
localSpace: mySpace,
|
|
1737
|
+
metaspace,
|
|
1909
1738
|
});
|
|
1910
1739
|
|
|
1911
1740
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1912
1741
|
prevSagaIbGib: sagaIbGib,
|
|
1913
1742
|
msgStones: [deltaStone],
|
|
1914
1743
|
sessionIdentity: identity,
|
|
1915
|
-
localSpace:
|
|
1916
|
-
metaspace
|
|
1744
|
+
localSpace: mySpace,
|
|
1745
|
+
metaspace,
|
|
1917
1746
|
});
|
|
1918
1747
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1748
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1749
|
+
} else {
|
|
1750
|
+
// We have nothing to send.
|
|
1751
|
+
|
|
1752
|
+
if (peerProposesCommit) {
|
|
1753
|
+
// Peer is done. We are done. -> Commit.
|
|
1754
|
+
// one last validate entire history?
|
|
1755
|
+
const history = await getFullSyncSagaHistory({
|
|
1756
|
+
sagaIbGib,
|
|
1757
|
+
space: mySpace,
|
|
1758
|
+
});
|
|
1759
|
+
const validationErrors = await validateFullSyncSagaHistory({
|
|
1760
|
+
history,
|
|
1761
|
+
});
|
|
1762
|
+
if (validationErrors.length > 0) {
|
|
1763
|
+
const errorCommitFrame = await this.createCommitFrame({
|
|
1764
|
+
sagaIbGib,
|
|
1765
|
+
errors: validationErrors,
|
|
1766
|
+
metaspace, mySpace,
|
|
1767
|
+
identity,
|
|
1768
|
+
});
|
|
1769
|
+
return { frame: errorCommitFrame, }; /* <<<< returns early */
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
await this.executeLocalCommit({
|
|
1773
|
+
sagaHistory: history,
|
|
1774
|
+
deltaFrame: sagaIbGib,
|
|
1775
|
+
localTempSpace: myTempSpace,
|
|
1776
|
+
localSpace: mySpace,
|
|
1777
|
+
metaspace,
|
|
1778
|
+
})
|
|
1779
|
+
|
|
1780
|
+
const commitFrame = await this.createCommitFrame({
|
|
1781
|
+
sagaIbGib,
|
|
1782
|
+
errors: undefined,
|
|
1783
|
+
metaspace,
|
|
1784
|
+
mySpace,
|
|
1785
|
+
identity,
|
|
1786
|
+
});
|
|
1787
|
+
|
|
1788
|
+
return { frame: commitFrame, };
|
|
1924
1789
|
|
|
1925
|
-
|
|
1790
|
+
} else {
|
|
1791
|
+
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
1792
|
+
// But we are empty.
|
|
1793
|
+
// So WE propose commit.
|
|
1794
|
+
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1926
1795
|
sagaId: deltaData.sagaId,
|
|
1927
|
-
stage: SyncStage.
|
|
1928
|
-
|
|
1796
|
+
stage: SyncStage.delta,
|
|
1797
|
+
proposeCommit: true,
|
|
1929
1798
|
};
|
|
1930
1799
|
|
|
1931
|
-
const
|
|
1932
|
-
data:
|
|
1933
|
-
localSpace:
|
|
1800
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
1801
|
+
data: responseDeltaData,
|
|
1802
|
+
localSpace: mySpace,
|
|
1934
1803
|
metaspace
|
|
1935
1804
|
});
|
|
1936
1805
|
|
|
1937
|
-
const
|
|
1938
|
-
prevSagaIbGib:
|
|
1939
|
-
msgStones: [
|
|
1806
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1807
|
+
prevSagaIbGib: sagaIbGib,
|
|
1808
|
+
msgStones: [deltaStone],
|
|
1940
1809
|
sessionIdentity: identity,
|
|
1941
|
-
localSpace:
|
|
1810
|
+
localSpace: mySpace,
|
|
1942
1811
|
metaspace
|
|
1943
1812
|
});
|
|
1944
1813
|
|
|
1945
|
-
//
|
|
1946
|
-
|
|
1947
|
-
|
|
1814
|
+
// Check if PEER proposed commit
|
|
1815
|
+
if (deltaData.proposeCommit) {
|
|
1816
|
+
if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
|
|
1817
|
+
// Peer wants to commit and has no more requests.
|
|
1818
|
+
// We should Commit.
|
|
1819
|
+
|
|
1820
|
+
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
1821
|
+
sagaId: deltaData.sagaId,
|
|
1822
|
+
stage: SyncStage.commit,
|
|
1823
|
+
success: true,
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
const commitStone = await this.createSyncMsgStone({
|
|
1827
|
+
data: commitData,
|
|
1828
|
+
localSpace: mySpace,
|
|
1829
|
+
metaspace
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1833
|
+
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1834
|
+
msgStones: [commitStone],
|
|
1835
|
+
sessionIdentity: identity,
|
|
1836
|
+
localSpace: mySpace,
|
|
1837
|
+
metaspace
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
// Build control payloads for commit
|
|
1841
|
+
const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
|
|
1842
|
+
if (identity) { commitCtrlPayloads2.push(identity); }
|
|
1843
|
+
|
|
1844
|
+
return { frame: commitFrame, };
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Build control payloads for delta propose
|
|
1848
|
+
const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1849
|
+
if (identity) { deltaCtrlPayloads.push(identity); }
|
|
1850
|
+
|
|
1851
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1856
|
+
throw error;
|
|
1857
|
+
} finally {
|
|
1858
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* should throw if fails
|
|
1864
|
+
*/
|
|
1865
|
+
private async executeLocalCommit({
|
|
1866
|
+
deltaFrame,
|
|
1867
|
+
sagaHistory,
|
|
1868
|
+
metaspace, localSpace, localTempSpace,
|
|
1869
|
+
identity,
|
|
1870
|
+
}: {
|
|
1871
|
+
deltaFrame: SyncIbGib_V1;
|
|
1872
|
+
sagaHistory: SyncSagaFrameDependencyGraph[];
|
|
1873
|
+
metaspace: MetaspaceService;
|
|
1874
|
+
localSpace: IbGibSpaceAny;
|
|
1875
|
+
localTempSpace: IbGibSpaceAny;
|
|
1876
|
+
identity?: KeystoneIbGib_V1;
|
|
1877
|
+
}): Promise<void> {
|
|
1878
|
+
const lc = `${this.lc}[${this.executeLocalCommit.name}]`;
|
|
1879
|
+
try {
|
|
1880
|
+
if (logalot) { console.log(`${lc} starting... (I: 6734980446b86a63c1af6e2e206de826)`); }
|
|
1881
|
+
|
|
1882
|
+
// #region validate/sanity
|
|
1883
|
+
if (!deltaFrame.data) { throw new Error(`(UNEXPECTED) deltaFrame.data falsy? (E: a8be68d48668d93d992d793834823826)`); }
|
|
1884
|
+
// #endregion validate/sanity
|
|
1948
1885
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1886
|
+
// * move all payload addrs from temp space to local space
|
|
1887
|
+
// * register each and every iteration of each timeline (including
|
|
1888
|
+
// stones, which are like their own sealed timeline of length 1)
|
|
1889
|
+
|
|
1890
|
+
const allPayloadAddrsDomainTransferred: IbGibAddr[] = [];
|
|
1891
|
+
const fnAddPayloadAddr = (addr: IbGibAddr) => {
|
|
1892
|
+
if (!allPayloadAddrsDomainTransferred.includes(addr)) {
|
|
1893
|
+
allPayloadAddrsDomainTransferred.push(addr);
|
|
1952
1894
|
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
sagaHistory.forEach(x => {
|
|
1898
|
+
if (!x.sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 34d7f8cdee14717ce828878d98f89826)`); }
|
|
1899
|
+
x.msgStones.forEach(msgStone => {
|
|
1900
|
+
if (!msgStone.data) { throw new Error(`(UNEXPECTED) msgStone.data falsy? (E: 21406101f91847514cc759c8a7382f26)`); }
|
|
1901
|
+
if (msgStone.data.stage === SyncStage.ack) {
|
|
1902
|
+
const ackData = msgStone.data as SyncSagaMessageAckData_V1;
|
|
1903
|
+
if (ackData.pushOfferInfos && ackData.pushOfferInfos.length > 0) {
|
|
1904
|
+
ackData.pushOfferInfos.forEach(info => {
|
|
1905
|
+
info.addrs.forEach(addr => fnAddPayloadAddr(addr));
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
} else if (msgStone.data.stage === SyncStage.delta) {
|
|
1909
|
+
const deltaData = msgStone.data as SyncSagaMessageDeltaData_V1;
|
|
1910
|
+
if (deltaData.payloadAddrsDomain && deltaData.payloadAddrsDomain.length > 0) {
|
|
1911
|
+
deltaData.payloadAddrsDomain.forEach(addr => fnAddPayloadAddr(addr));
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
})
|
|
1915
|
+
});
|
|
1916
|
+
|
|
1917
|
+
// at this point, we have a list of ALL payload addrs retrieved.
|
|
1918
|
+
let allPayloadIbGibsDomainTransferred: IbGib_V1[] = [];
|
|
1919
|
+
const resGetAllTransferred =
|
|
1920
|
+
await getFromSpace({ addrs: allPayloadAddrsDomainTransferred, space: localTempSpace });
|
|
1921
|
+
if (resGetAllTransferred.success && resGetAllTransferred.ibGibs && resGetAllTransferred.ibGibs.length === allPayloadAddrsDomainTransferred.length) {
|
|
1922
|
+
allPayloadIbGibsDomainTransferred = resGetAllTransferred.ibGibs.concat();
|
|
1923
|
+
} else {
|
|
1924
|
+
// errored out, gather info
|
|
1925
|
+
if (!resGetAllTransferred.rawResultIbGib) { throw new Error(`(UNEXPECTED) !resGetAllTransferred.rawResultIbGib falsy? (E: dc6cf8729668f4fbe8c024f887d97a26)`); }
|
|
1926
|
+
const { addrsNotFound, addrsErrored, errors } = resGetAllTransferred.rawResultIbGib.data as IbGibSpaceResultData;
|
|
1927
|
+
throw new Error(`(UNEXPECTED) we couldn't get all addrs transferred throughout the saga from the tempspace (${localTempSpace.ib})? addrsNotFound: ${addrsNotFound ?? []}. addrsErrored: ${addrsErrored ?? []}. errors: ${errors ?? []}(E: 222e341e634862b4d88ae7282584d826)`);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// now we have all ibgibs transferred, first put them all the local space
|
|
1931
|
+
|
|
1932
|
+
const { payload_Dnas, payload_NonDnas } =
|
|
1933
|
+
await putInSpace_dnasThenNonDnas({
|
|
1934
|
+
ibGibs: allPayloadIbGibsDomainTransferred,
|
|
1935
|
+
space: localSpace
|
|
1936
|
+
});
|
|
1953
1937
|
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1938
|
+
const { mapWithTjp_NoDna, mapWithTjp_YesDna, mapWithoutTjps } =
|
|
1939
|
+
splitPerTjpAndOrDna({ ibGibs: payload_NonDnas, filterPrimitives: true });
|
|
1940
|
+
|
|
1941
|
+
// first register all non-tjp stones
|
|
1942
|
+
const nontjps = Object.values(mapWithoutTjps);
|
|
1943
|
+
for (const nontjp of nontjps) {
|
|
1944
|
+
await metaspace.registerNewIbGib({
|
|
1945
|
+
ibGib: nontjp,
|
|
1946
|
+
space: localSpace,
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// next register each timeline in order...
|
|
1951
|
+
|
|
1952
|
+
// ...first the ones without dna...
|
|
1953
|
+
const timelinesByTjpAddr_NoDna =
|
|
1954
|
+
getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_NoDna) });
|
|
1955
|
+
const tjpAddrs_NoDna = Object.keys(timelinesByTjpAddr_NoDna);
|
|
1956
|
+
for (const tjpAddr_NoDna of tjpAddrs_NoDna) {
|
|
1957
|
+
const timelineIbGibs = timelinesByTjpAddr_NoDna[tjpAddr_NoDna];
|
|
1958
|
+
for (const ibGib of timelineIbGibs) {
|
|
1959
|
+
await metaspace.registerNewIbGib({
|
|
1960
|
+
ibGib,
|
|
1961
|
+
space: localSpace,
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1957
1965
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1966
|
+
// ...then the ones WITH dna.
|
|
1967
|
+
const timelinesByTjpAddr_YesDna =
|
|
1968
|
+
getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_YesDna) });
|
|
1969
|
+
const tjpAddrs_YesDna = Object.keys(timelinesByTjpAddr_YesDna);
|
|
1970
|
+
for (const tjpAddr_YesDna of tjpAddrs_YesDna) {
|
|
1971
|
+
const timelineIbGibs = timelinesByTjpAddr_YesDna[tjpAddr_YesDna];
|
|
1972
|
+
for (const ibGib of timelineIbGibs) {
|
|
1973
|
+
await metaspace.registerNewIbGib({
|
|
1974
|
+
ibGib,
|
|
1975
|
+
space: localSpace,
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1961
1978
|
}
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
} catch (error) {
|
|
1982
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1983
|
+
throw error;
|
|
1984
|
+
} finally {
|
|
1985
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1962
1986
|
}
|
|
1963
1987
|
}
|
|
1964
1988
|
|
|
1989
|
+
private async createCommitFrame({
|
|
1990
|
+
sagaIbGib,
|
|
1991
|
+
errors,
|
|
1992
|
+
metaspace,
|
|
1993
|
+
mySpace,
|
|
1994
|
+
identity,
|
|
1995
|
+
}: {
|
|
1996
|
+
sagaIbGib: SyncIbGib_V1,
|
|
1997
|
+
/**
|
|
1998
|
+
* if truthy and non-empty, will create an errored commit frame (data
|
|
1999
|
+
* has `success: false`)
|
|
2000
|
+
*/
|
|
2001
|
+
errors?: string[],
|
|
2002
|
+
metaspace: MetaspaceService,
|
|
2003
|
+
mySpace: IbGibSpaceAny,
|
|
2004
|
+
identity: KeystoneIbGib_V1 | undefined,
|
|
2005
|
+
}): Promise<SyncIbGib_V1> {
|
|
2006
|
+
const lc = `[${this.createCommitFrame.name}]`;
|
|
2007
|
+
try {
|
|
2008
|
+
if (logalot) { console.log(`${lc} starting... (I: 48fc57c023f80122135a284855757526)`); }
|
|
2009
|
+
const commitData: SyncSagaMessageCommitData_V1 = errors && errors.length > 0 ?
|
|
2010
|
+
{
|
|
2011
|
+
// errored
|
|
2012
|
+
sagaId: sagaIbGib.data!.uuid,
|
|
2013
|
+
stage: SyncStage.commit,
|
|
2014
|
+
success: false,
|
|
2015
|
+
errors,
|
|
2016
|
+
} :
|
|
2017
|
+
{
|
|
2018
|
+
// not errored
|
|
2019
|
+
sagaId: sagaIbGib.data!.uuid,
|
|
2020
|
+
stage: SyncStage.commit,
|
|
2021
|
+
success: true,
|
|
2022
|
+
};
|
|
2023
|
+
|
|
2024
|
+
const commitStone = await this.createSyncMsgStone({
|
|
2025
|
+
data: commitData,
|
|
2026
|
+
localSpace: mySpace,
|
|
2027
|
+
metaspace
|
|
2028
|
+
});
|
|
2029
|
+
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
2030
|
+
prevSagaIbGib: sagaIbGib,
|
|
2031
|
+
msgStones: [commitStone],
|
|
2032
|
+
sessionIdentity: identity,
|
|
2033
|
+
localSpace: mySpace,
|
|
2034
|
+
metaspace,
|
|
2035
|
+
});
|
|
2036
|
+
return commitFrame;
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
2039
|
+
throw error;
|
|
2040
|
+
} finally {
|
|
2041
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1965
2044
|
|
|
1966
|
-
|
|
2045
|
+
private async handleCommitFrame({
|
|
1967
2046
|
sagaIbGib,
|
|
1968
|
-
|
|
1969
|
-
|
|
2047
|
+
mySpace,
|
|
2048
|
+
myTempSpace,
|
|
1970
2049
|
metaspace,
|
|
1971
2050
|
identity,
|
|
1972
2051
|
}: {
|
|
1973
2052
|
sagaIbGib: SyncIbGib_V1,
|
|
1974
|
-
|
|
1975
|
-
|
|
2053
|
+
mySpace: IbGibSpaceAny,
|
|
2054
|
+
myTempSpace: IbGibSpaceAny,
|
|
1976
2055
|
metaspace: MetaspaceService,
|
|
1977
2056
|
identity?: KeystoneIbGib_V1,
|
|
1978
2057
|
}): Promise<NextSagaFrameInfo> {
|
|
1979
2058
|
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
1980
|
-
|
|
2059
|
+
try {
|
|
2060
|
+
if (logalot) { console.log(`${lc} starting... (I: e179573bdd881202f8ba3168da1c3826)`); }
|
|
2061
|
+
|
|
2062
|
+
let resNextSagaFrameInfo: NextSagaFrameInfo;
|
|
2063
|
+
|
|
2064
|
+
// Sender Logic (Finalizing):
|
|
2065
|
+
// If we are here, we received a Commit frame from the Peer.
|
|
2066
|
+
// This implies the Peer has successfully committed.
|
|
2067
|
+
// We should now:
|
|
2068
|
+
// 1. Validate (implicitly done by receiving valid frame)
|
|
2069
|
+
// 2. Perform our own cleanup (Temp -> Dest, if applicable)
|
|
2070
|
+
// 3. Return saga completion.
|
|
2071
|
+
|
|
2072
|
+
// one last validate entire history?
|
|
2073
|
+
const history = await getFullSyncSagaHistory({
|
|
2074
|
+
sagaIbGib,
|
|
2075
|
+
space: mySpace,
|
|
2076
|
+
});
|
|
2077
|
+
const validationErrors = await validateFullSyncSagaHistory({
|
|
2078
|
+
history,
|
|
2079
|
+
});
|
|
2080
|
+
if (validationErrors.length > 0) {
|
|
2081
|
+
const errorCommitFrame = await this.createCommitFrame({
|
|
2082
|
+
sagaIbGib,
|
|
2083
|
+
metaspace,
|
|
2084
|
+
mySpace,
|
|
2085
|
+
identity,
|
|
2086
|
+
errors: validationErrors,
|
|
2087
|
+
});
|
|
2088
|
+
return { frame: errorCommitFrame, }; /* <<<< returns early */
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
await this.executeLocalCommit({
|
|
2092
|
+
deltaFrame: sagaIbGib,
|
|
2093
|
+
sagaHistory: history,
|
|
2094
|
+
localSpace: mySpace,
|
|
2095
|
+
localTempSpace: myTempSpace,
|
|
2096
|
+
metaspace,
|
|
2097
|
+
});
|
|
1981
2098
|
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
// We should now:
|
|
1986
|
-
// 1. Validate (implicitly done by receiving valid frame)
|
|
1987
|
-
// 2. Perform our own cleanup (Temp -> Dest, if applicable)
|
|
1988
|
-
// 3. Return null to signal saga completion.
|
|
2099
|
+
// todo: implement explicit cleanup logic here and in peer
|
|
2100
|
+
console.error(`${lc} NAG ERROR (NOT THROWN): implement cleanup logic, including add a cleanup method to the peer (E: 3a9a24befb98a981a88fbdbf52920e26)`);
|
|
2101
|
+
if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
|
|
1989
2102
|
|
|
1990
|
-
|
|
2103
|
+
// the holy grail!
|
|
2104
|
+
return { sagaComplete: true };
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
const emsg = `${lc} ${extractErrorMsg(error)}`;
|
|
2107
|
+
console.error(emsg);
|
|
2108
|
+
const errorCommitFrame = await this.createCommitFrame({
|
|
2109
|
+
sagaIbGib,
|
|
2110
|
+
errors: [emsg],
|
|
2111
|
+
metaspace,
|
|
2112
|
+
mySpace,
|
|
2113
|
+
identity,
|
|
2114
|
+
});
|
|
2115
|
+
return { frame: errorCommitFrame }
|
|
2116
|
+
} finally {
|
|
2117
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
2118
|
+
}
|
|
1991
2119
|
|
|
1992
|
-
if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
|
|
1993
|
-
// return { responseWasNull: true };
|
|
1994
|
-
throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
|
|
1995
2120
|
}
|
|
1996
2121
|
|
|
1997
2122
|
// #endregion Handlers
|
|
1998
2123
|
|
|
1999
|
-
|
|
2124
|
+
private async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
|
|
2000
2125
|
data,
|
|
2001
2126
|
localSpace,
|
|
2002
2127
|
metaspace,
|
|
@@ -2028,11 +2153,75 @@ export class SyncSagaCoordinator {
|
|
|
2028
2153
|
}
|
|
2029
2154
|
}
|
|
2030
2155
|
|
|
2156
|
+
private async getPayloadsForRequestedInfos({
|
|
2157
|
+
deltaRequestAddrInfos,
|
|
2158
|
+
mySpace,
|
|
2159
|
+
}: {
|
|
2160
|
+
deltaRequestAddrInfos: SyncSagaRequestAddrInfo[];
|
|
2161
|
+
mySpace: IbGibSpaceAny;
|
|
2162
|
+
}): Promise<IbGib_V1[]> {
|
|
2163
|
+
const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
|
|
2164
|
+
try {
|
|
2165
|
+
if (logalot) { console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`); }
|
|
2166
|
+
/**
|
|
2167
|
+
* graph of ibgibs we will send to the receiver. addr-based, so will
|
|
2168
|
+
* already be unique (no need to call `unique` on the domains
|
|
2169
|
+
* ibgibs)
|
|
2170
|
+
*/
|
|
2171
|
+
const outgoingPayloadIbGibsDomainGraph: FlatIbGibGraph = {};
|
|
2172
|
+
for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
|
|
2173
|
+
let deltaDepGraph: FlatIbGibGraph;
|
|
2174
|
+
if (latestAddrAlreadyHave) {
|
|
2175
|
+
// already has some, so only get the delta
|
|
2176
|
+
// remember: if we didn't have the other's latest addr, then
|
|
2177
|
+
// this would be in the conflicts not requested addrs
|
|
2178
|
+
deltaDepGraph = await getDeltaDependencyGraph({
|
|
2179
|
+
ibGibAddr: addr,
|
|
2180
|
+
latestCommonFrameAddr: latestAddrAlreadyHave,
|
|
2181
|
+
space: mySpace,
|
|
2182
|
+
live: true,
|
|
2183
|
+
});
|
|
2184
|
+
} else {
|
|
2185
|
+
// doesn't have anything, so get the entire dependency graph
|
|
2186
|
+
// INEFFICIENT: we've already gotten all of the domain
|
|
2187
|
+
// dependencies in initDomainGraph, but getDependencyGraph
|
|
2188
|
+
// only works against a space so we are going to rerun this.
|
|
2189
|
+
// an optimization would be to adapt/create a new
|
|
2190
|
+
// getDependencyGraph to work against an existing flat ibgib
|
|
2191
|
+
// map.
|
|
2192
|
+
deltaDepGraph = await getDependencyGraph({
|
|
2193
|
+
ibGibAddr: addr,
|
|
2194
|
+
space: mySpace,
|
|
2195
|
+
live: true,
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
const depGraphSize = Object.keys(deltaDepGraph).length;
|
|
2200
|
+
if (depGraphSize === 0) {
|
|
2201
|
+
throw new Error(`(UNEXPECTED) couldn't get requested addrs in mySpace (${mySpace.ib})? How did the receiver know to ask for these addrs if they weren't in our original graph? (E: 281eaebcdf77dd73e8245b2872100826)`);
|
|
2202
|
+
} else {
|
|
2203
|
+
// we have dependencies!
|
|
2204
|
+
Object.values(deltaDepGraph).forEach(x => {
|
|
2205
|
+
outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
const result = Object.values(outgoingPayloadIbGibsDomainGraph);
|
|
2211
|
+
return result;
|
|
2212
|
+
} catch (error) {
|
|
2213
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
2214
|
+
throw error;
|
|
2215
|
+
} finally {
|
|
2216
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2031
2220
|
|
|
2032
2221
|
/**
|
|
2033
2222
|
* Evolves the saga timeline with a new frame.
|
|
2034
2223
|
*/
|
|
2035
|
-
|
|
2224
|
+
private async evolveSyncSagaIbGib({
|
|
2036
2225
|
prevSagaIbGib,
|
|
2037
2226
|
conflictStrategy,
|
|
2038
2227
|
msgStones,
|
|
@@ -2045,6 +2234,10 @@ export class SyncSagaCoordinator {
|
|
|
2045
2234
|
msgStones: IbGib_V1[],
|
|
2046
2235
|
localSpace: IbGibSpaceAny,
|
|
2047
2236
|
metaspace: MetaspaceService,
|
|
2237
|
+
/**
|
|
2238
|
+
* does NOT evolve the keystone. this should be done by the caller.
|
|
2239
|
+
* (NOT IMPLEMENTED YET ANYWAY)
|
|
2240
|
+
*/
|
|
2048
2241
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
2049
2242
|
}): Promise<SyncIbGib_V1> {
|
|
2050
2243
|
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|
|
@@ -2152,7 +2345,7 @@ export class SyncSagaCoordinator {
|
|
|
2152
2345
|
}
|
|
2153
2346
|
}
|
|
2154
2347
|
|
|
2155
|
-
|
|
2348
|
+
private async getStageAndPayloadFromFrame({
|
|
2156
2349
|
sagaFrame,
|
|
2157
2350
|
space
|
|
2158
2351
|
}: {
|
|
@@ -2188,7 +2381,7 @@ export class SyncSagaCoordinator {
|
|
|
2188
2381
|
}
|
|
2189
2382
|
}
|
|
2190
2383
|
|
|
2191
|
-
|
|
2384
|
+
private sortTimelinesTopologically(timelines: { [tjp: string]: IbGib_V1[] }): string[] {
|
|
2192
2385
|
const lc = `${this.lc}[${this.sortTimelinesTopologically.name}]`;
|
|
2193
2386
|
const tjps = Object.keys(timelines);
|
|
2194
2387
|
if (tjps.length === 0) { return []; }
|