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