@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
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { extractErrorMsg, getUUID, // so our timestamp in ticks as a string are uniform
|
|
2
|
-
pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
2
|
+
pretty, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
3
3
|
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
4
|
-
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from "../common/other/ibgib-helper.mjs";
|
|
4
|
+
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
|
|
5
5
|
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
|
|
6
|
-
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
|
|
7
6
|
import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
|
|
8
|
-
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME } from "./sync-constants.mjs";
|
|
7
|
+
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
|
|
9
8
|
import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
|
|
10
9
|
import { SyncMode, } from "./sync-types.mjs";
|
|
11
10
|
import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
|
|
12
|
-
import { getSyncSagaDependencyGraph } from "./sync-helpers.mjs";
|
|
11
|
+
import { getSyncSagaDependencyGraph as getSyncSagaDependencyGraphForThisFrameOnly } from "./sync-helpers.mjs";
|
|
13
12
|
import { getDependencyGraph } from "../common/other/graph-helper.mjs";
|
|
14
13
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
15
14
|
import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
|
|
16
|
-
import { SyncSagaContextCmd } from "./sync-saga-context/sync-saga-context-types.mjs";
|
|
17
15
|
import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
18
|
-
import { newupSubject } from "../common/pubsub/subject/subject-helper.mjs";
|
|
16
|
+
import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
|
|
19
17
|
import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
20
18
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
21
|
-
|
|
19
|
+
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
20
|
+
import { SyncPeerInnerspace_V1 } from "./sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs";
|
|
21
|
+
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
22
|
+
const logalot = false;
|
|
23
|
+
const logalotControlDomain = true;
|
|
24
|
+
const lcControlDomain = '[ControlDomain]';
|
|
22
25
|
/**
|
|
23
26
|
* Orchestrates the synchronization process between two spaces (Source and Destination).
|
|
24
27
|
*
|
|
@@ -103,9 +106,7 @@ export class SyncSagaCoordinator {
|
|
|
103
106
|
const sessionIdentity = useSessionIdentity
|
|
104
107
|
? await this.getSessionIdentity({ sagaId, metaspace, tempSpace })
|
|
105
108
|
: undefined;
|
|
106
|
-
if (logalot) {
|
|
107
|
-
console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`);
|
|
108
|
-
}
|
|
109
|
+
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
109
110
|
// 3. CREATE INITIAL FRAME (Stage.init)
|
|
110
111
|
const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
|
|
111
112
|
sagaId,
|
|
@@ -117,6 +118,14 @@ export class SyncSagaCoordinator {
|
|
|
117
118
|
conflictStrategy
|
|
118
119
|
});
|
|
119
120
|
// 4. EXECUTE SAGA LOOP (FSM)
|
|
121
|
+
// Inject tempSpace into peer so it can pull control payloads to the right place
|
|
122
|
+
// if ('opts' in peer && peer.opts) {
|
|
123
|
+
if (!peer.data) {
|
|
124
|
+
throw new Error(`(UNEXPECTED) peer.data falsy? (E: 8546a884c82ffb1999e95d9867da2826)`);
|
|
125
|
+
}
|
|
126
|
+
if (peer.data.classname === SyncPeerInnerspace_V1.name) {
|
|
127
|
+
// (peer as SyncPeerInnerspace_V1).senderTempSpace = tempSpace;
|
|
128
|
+
}
|
|
120
129
|
const syncedIbGibs = await this.executeSagaLoop({
|
|
121
130
|
initialFrame: initFrame,
|
|
122
131
|
srcGraph,
|
|
@@ -209,76 +218,123 @@ export class SyncSagaCoordinator {
|
|
|
209
218
|
// The current frame we just generated (e.g., Init or Delta Request)
|
|
210
219
|
let currentFrame = initialFrame;
|
|
211
220
|
// The payload we need to attach to the message (data we are sending)
|
|
212
|
-
let
|
|
221
|
+
let nextDomainIbGibs = [];
|
|
213
222
|
// Accumulator for all data we've successfully pulled from the remote
|
|
214
223
|
const allReceivedIbGibs = [];
|
|
215
224
|
while (currentFrame) {
|
|
216
225
|
// A. Create Context (Request)
|
|
217
226
|
// 1. Calculate Full Dependency Graph (including Ancestors/DNA)
|
|
218
|
-
//
|
|
219
|
-
|
|
227
|
+
// TODO: adjust this algorithm to only do the dependencies that the other end doesn't need (diff between tip and LCA)
|
|
228
|
+
// We must do this BEFORE creating the Context so we can list them
|
|
229
|
+
// all in payloadAddrsDomain and payloadAddrsControl.
|
|
230
|
+
// const depsDomainIbGibs: IbGib_V1[] = [];
|
|
231
|
+
// const depsControlIbGibs: IbGib_V1[] = [];
|
|
232
|
+
const payloadIbGibsControl = [];
|
|
233
|
+
const payloadIbGibsDomain = [];
|
|
220
234
|
// A. Payload (Standard Deep Deps)
|
|
221
|
-
for (const
|
|
222
|
-
let
|
|
223
|
-
if (!
|
|
224
|
-
|
|
235
|
+
for (const nextDomainIbGib of nextDomainIbGibs) {
|
|
236
|
+
let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
|
|
237
|
+
if (!nextDomainIbGibGraph) {
|
|
238
|
+
nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
|
|
225
239
|
}
|
|
226
|
-
if (
|
|
227
|
-
|
|
240
|
+
if (nextDomainIbGibGraph) {
|
|
241
|
+
payloadIbGibsDomain.push(...Object.values(nextDomainIbGibGraph));
|
|
228
242
|
}
|
|
229
243
|
else {
|
|
230
|
-
|
|
244
|
+
throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
|
|
231
245
|
}
|
|
232
246
|
}
|
|
233
247
|
if (logalot) {
|
|
234
|
-
|
|
248
|
+
console.log(`${lc} payloadIbGibsDomain count: ${payloadIbGibsDomain.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`);
|
|
235
249
|
}
|
|
236
250
|
// B. Frames (Shallow Sync Deps)
|
|
237
251
|
if (currentFrame) {
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
|
|
252
|
+
const depsCurrentFrame = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: currentFrame, space: tempSpace });
|
|
253
|
+
if (depsCurrentFrame.length > 0) {
|
|
254
|
+
depsCurrentFrame.forEach(x => payloadIbGibsControl.push(x));
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
throw new Error(`(UNEXPECTED) couldn't get deps for currentFrame? currentFrame: ${JSON.stringify(currentFrame)} (E: 06344d07adc80d80b809211171444d26)`);
|
|
258
|
+
}
|
|
241
259
|
}
|
|
242
260
|
// 2. Create Context (Envelope)
|
|
243
|
-
|
|
244
|
-
const
|
|
261
|
+
const domainPayloadsMap = new Map();
|
|
262
|
+
const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
|
|
263
|
+
let subscription;
|
|
264
|
+
if (peer && peer.payloadIbGibsDomainReceived$) {
|
|
265
|
+
// Subscribe to stream
|
|
266
|
+
subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
|
|
267
|
+
next: async (ibgib) => {
|
|
268
|
+
if (logalot) {
|
|
269
|
+
console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`);
|
|
270
|
+
}
|
|
271
|
+
const addr = getIbGibAddr({ ibGib: ibgib });
|
|
272
|
+
if (logalotControlDomain) {
|
|
273
|
+
console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`);
|
|
274
|
+
}
|
|
275
|
+
domainPayloadsMap.set(addr, ibgib);
|
|
276
|
+
},
|
|
277
|
+
error: async (e) => {
|
|
278
|
+
if (isIbGib(e)) {
|
|
279
|
+
console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.dir(e);
|
|
283
|
+
console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
complete: async () => {
|
|
287
|
+
if (logalot) {
|
|
288
|
+
console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`);
|
|
289
|
+
}
|
|
290
|
+
await subscription.unsubscribe();
|
|
291
|
+
},
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
// 2b. Request Context
|
|
245
295
|
const requestCtx = await createSyncSagaContext({
|
|
246
|
-
cmd: SyncSagaContextCmd.process,
|
|
247
296
|
sagaFrame: currentFrame,
|
|
248
297
|
sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
|
|
249
|
-
|
|
298
|
+
payloadIbGibsDomain,
|
|
250
299
|
});
|
|
251
|
-
//
|
|
252
|
-
if (
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
300
|
+
// Log what we're sending
|
|
301
|
+
if (logalotControlDomain) {
|
|
302
|
+
const controlAddrs = payloadIbGibsControl.map(p => getIbGibAddr({ ibGib: p }));
|
|
303
|
+
const domainAddrs = payloadIbGibsDomain.map(p => getIbGibAddr({ ibGib: p }));
|
|
304
|
+
console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
|
|
305
|
+
console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
|
|
306
|
+
console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
307
|
+
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${controlAddrs.length}): ${controlAddrs.join(', ') || '(none)'}`);
|
|
308
|
+
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
|
|
256
309
|
}
|
|
310
|
+
// Add Context Deps
|
|
311
|
+
// do we need to add requestCtx to the payload ibgibs?
|
|
312
|
+
// if (requestCtx) {
|
|
313
|
+
// const deps = await getSyncSagaDependencyGraphForThisFrameOnly({ ibGib: requestCtx, space: tempSpace });
|
|
314
|
+
// if (deps) { deps.forEach(x => payloadIbGibsControl.push(x)); }
|
|
315
|
+
// }
|
|
257
316
|
// 3. Identity (if exists)
|
|
258
317
|
// Identity might be deep? Keystone? Usually self-contained or shallow references.
|
|
259
318
|
if (sessionIdentity) {
|
|
260
|
-
|
|
319
|
+
payloadIbGibsControl.push(sessionIdentity);
|
|
261
320
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
321
|
+
// we only put the **CONTROL** payload ibgibs in localSpace. we
|
|
322
|
+
// don't put any domain ibgibs into this durable space until the
|
|
323
|
+
// final commit phase.
|
|
324
|
+
// The requestCtx envelope itself also goes to localSpace so transfer can find it.
|
|
325
|
+
await putInSpace({ space: localSpace, ibGib: requestCtx });
|
|
326
|
+
if (payloadIbGibsControl.length > 0) {
|
|
327
|
+
await putInSpace({ space: localSpace, ibGibs: payloadIbGibsControl });
|
|
267
328
|
}
|
|
268
329
|
// B. Transmit
|
|
269
|
-
if (logalot) {
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
updates$.next(requestCtx);
|
|
330
|
+
// if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
|
|
331
|
+
updates$.next(requestCtx); // spins off (don't remove this comment!)
|
|
273
332
|
const responseCtx = await peer.witness(requestCtx);
|
|
274
333
|
// C. Handle Response
|
|
275
334
|
if (!responseCtx) {
|
|
276
|
-
// Check if we just sent a Commit frame. If so, peer's silence is success/expected.
|
|
277
335
|
if (currentFrame) {
|
|
336
|
+
// Check for Commit (Peer silence expected)
|
|
278
337
|
const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
|
|
279
|
-
if (logalot) {
|
|
280
|
-
console.log(`${lc} Checking currentFrame stage: ${msg?.data?.stage} (Expected: ${SyncStage.commit})`);
|
|
281
|
-
}
|
|
282
338
|
if (msg?.data?.stage === SyncStage.commit) {
|
|
283
339
|
if (logalot) {
|
|
284
340
|
console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`);
|
|
@@ -286,56 +342,85 @@ export class SyncSagaCoordinator {
|
|
|
286
342
|
currentFrame = null;
|
|
287
343
|
break;
|
|
288
344
|
}
|
|
345
|
+
else {
|
|
346
|
+
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)`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
|
|
289
351
|
}
|
|
290
|
-
throw new Error(`responseCtx falsy. Peer returned no response context (E: c099d8073b48d85e881f917835158f26)`);
|
|
291
|
-
// console.warn(`${lc} Peer returned no response context. Ending loop.`);
|
|
292
|
-
// currentFrame = null;
|
|
293
|
-
// break;
|
|
294
352
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (!remoteFrame) {
|
|
313
|
-
throw new Error(`Could not resolve remote frame: ${remoteFrameAddr}`);
|
|
314
|
-
}
|
|
315
|
-
// if (logalot) { console.log(`${lc} remoteFrame: ${pretty(remoteFrame)}`); } // leave this in for later use if needed
|
|
316
|
-
// Ensure remote frame and its dependencies are in tempSpace
|
|
317
|
-
// The Peer delivered them to localSpace, but handleSagaFrame works in tempSpace.
|
|
318
|
-
const remoteDeps = await getSyncSagaDependencyGraph({ ibGib: remoteFrame, space: localSpace });
|
|
319
|
-
if (remoteDeps && remoteDeps.length > 0) {
|
|
320
|
-
await putInSpace({ space: tempSpace, ibGibs: remoteDeps });
|
|
321
|
-
}
|
|
322
|
-
// React (Reducer)
|
|
323
|
-
// This processes the FRAME we just got from the peer.
|
|
324
|
-
// i.e., We Sent Init -> Got Ack. This calls handleAckFrame.
|
|
325
|
-
// i.e., We Sent Delta -> Got Delta. This calls handleDeltaFrame.
|
|
326
|
-
const result = await this.handleSagaFrame({
|
|
327
|
-
sagaIbGib: remoteFrame,
|
|
328
|
-
srcGraph,
|
|
329
|
-
space: localSpace, // Must be localSpace (Source) to find domain data
|
|
330
|
-
identity: sessionIdentity,
|
|
331
|
-
metaspace
|
|
332
|
-
});
|
|
333
|
-
currentFrame = result?.frame || null;
|
|
334
|
-
nextPayloadIbGibs = result?.payloadIbGibs || []; // Payload to send in NEXT Ping
|
|
335
|
-
if (result?.receivedPayloadIbGibs) {
|
|
336
|
-
// Keep track of what we received for final merge
|
|
337
|
-
allReceivedIbGibs.push(...result.receivedPayloadIbGibs);
|
|
353
|
+
// ---------------------------------------------------------------------
|
|
354
|
+
// 2d. HANDLE RESPONSE
|
|
355
|
+
// ---------------------------------------------------------------------
|
|
356
|
+
if (!responseCtx.data) {
|
|
357
|
+
throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
|
|
358
|
+
}
|
|
359
|
+
updates$.next(responseCtx); // spins off -- don't remove this comment!
|
|
360
|
+
// Extract expected domain addresses from response context
|
|
361
|
+
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] || [];
|
|
362
|
+
// 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.
|
|
363
|
+
// Poll for them if needed
|
|
364
|
+
if (responsePayloadAddrsDomain.length > 0) {
|
|
365
|
+
await this.pollForDomainPayloads({
|
|
366
|
+
expectedAddrs: responsePayloadAddrsDomain,
|
|
367
|
+
domainPayloadsMap,
|
|
368
|
+
tempSpace,
|
|
369
|
+
});
|
|
338
370
|
}
|
|
371
|
+
// Extract Response Frame
|
|
372
|
+
const responseFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
|
|
373
|
+
if (!responseFrameAddr) {
|
|
374
|
+
throw new Error(`${lc} Peer response missing sagaFrame (E: 83a0)`);
|
|
375
|
+
}
|
|
376
|
+
// Log what we received back
|
|
377
|
+
if (logalotControlDomain) {
|
|
378
|
+
const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] || [];
|
|
379
|
+
console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
|
|
380
|
+
console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
|
|
381
|
+
console.log(`${lc}${lcControlDomain} Response Frame: ${responseFrameAddr}`);
|
|
382
|
+
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
|
|
383
|
+
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
|
|
384
|
+
}
|
|
385
|
+
// Get response frame from localSpace (SyncPeer puts it there)
|
|
386
|
+
let resResponseFrame = await getFromSpace({ space: localSpace, addr: responseFrameAddr });
|
|
387
|
+
if (!resResponseFrame.success || !resResponseFrame.ibGibs?.length) {
|
|
388
|
+
// Fallback to tempSpace
|
|
389
|
+
resResponseFrame = await getFromSpace({ space: tempSpace, addr: responseFrameAddr });
|
|
390
|
+
}
|
|
391
|
+
const responseFrame = resResponseFrame.ibGibs?.[0];
|
|
392
|
+
if (!responseFrame)
|
|
393
|
+
throw new Error(`${lc} Response frame not found (E: 7c2a)`);
|
|
394
|
+
// Handle Response Frame
|
|
395
|
+
const handleResult = await this.handleSagaFrame({
|
|
396
|
+
sagaIbGib: responseFrame,
|
|
397
|
+
srcGraph: {},
|
|
398
|
+
destSpace: localSpace,
|
|
399
|
+
tempSpace,
|
|
400
|
+
metaspace,
|
|
401
|
+
domainPayloadsMap,
|
|
402
|
+
expectedDomainAddrs: responsePayloadAddrsDomain,
|
|
403
|
+
});
|
|
404
|
+
if (!handleResult) {
|
|
405
|
+
if (logalot) {
|
|
406
|
+
console.log(`${lc} Handler returned null (Saga End).`);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
currentFrame = handleResult.frame;
|
|
411
|
+
// Collect next DOMAIN payloads for the NEXT request
|
|
412
|
+
// Control payloads are handled separately by ensureSagaFrameInBothSpaces
|
|
413
|
+
nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
|
|
414
|
+
// Log handler output for next iteration
|
|
415
|
+
if (logalotControlDomain) {
|
|
416
|
+
const handlerControlAddrs = (handleResult.payloadIbGibsControl || []).map(p => getIbGibAddr({ ibGib: p }));
|
|
417
|
+
const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
|
|
418
|
+
console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
|
|
419
|
+
console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
420
|
+
console.log(`${lc}${lcControlDomain} CONTROL for next (${handlerControlAddrs.length}): ${handlerControlAddrs.join(', ') || '(none)'} (saved via ensureSagaFrameInBothSpaces)`);
|
|
421
|
+
console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
|
|
422
|
+
}
|
|
423
|
+
// Note: currentFrame is automatically added to dependencies at start of next loop via B. Frames logic.
|
|
339
424
|
}
|
|
340
425
|
return allReceivedIbGibs;
|
|
341
426
|
}
|
|
@@ -345,34 +430,71 @@ export class SyncSagaCoordinator {
|
|
|
345
430
|
*/
|
|
346
431
|
async getKnowledgeVector({ space, metaspace, domainIbGibs, tjpAddrs, }) {
|
|
347
432
|
const lc = `${this.lc}[${this.getKnowledgeVector.name}]`;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
433
|
+
try {
|
|
434
|
+
if (logalot) {
|
|
435
|
+
console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`);
|
|
436
|
+
}
|
|
437
|
+
// console.dir(space);
|
|
438
|
+
if (!(domainIbGibs && domainIbGibs.length > 0) &&
|
|
439
|
+
!(tjpAddrs && tjpAddrs.length > 0)) {
|
|
440
|
+
throw new Error(`(UNEXPECTED) domainIbGibs and tjpAddrs falsy/empty? we need one or the other (E: f674285111c8648398cd79d8c08ec826)`);
|
|
441
|
+
}
|
|
442
|
+
if ((domainIbGibs && domainIbGibs.length > 0) &&
|
|
443
|
+
(tjpAddrs && tjpAddrs.length > 0)) {
|
|
444
|
+
throw new Error(`(UNEXPECTED) both domainIbGibs and tjpAddrs truthy? only pass in one or the other. (E: f674285111c8648398cd79d8c08ec826)`);
|
|
445
|
+
}
|
|
446
|
+
let tjps = [];
|
|
447
|
+
if (tjpAddrs) {
|
|
448
|
+
tjps = tjpAddrs;
|
|
449
|
+
}
|
|
450
|
+
else if (domainIbGibs && domainIbGibs.length > 0) {
|
|
451
|
+
// Extract TJPs from domain Ibgibs
|
|
452
|
+
// if (false) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
|
|
453
|
+
const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
|
|
454
|
+
if (false) {
|
|
455
|
+
console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`);
|
|
456
|
+
}
|
|
457
|
+
if (false) {
|
|
458
|
+
console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`);
|
|
459
|
+
}
|
|
460
|
+
const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
|
|
461
|
+
const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
|
|
462
|
+
if (false) {
|
|
463
|
+
console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`);
|
|
464
|
+
}
|
|
465
|
+
tjps = Object.keys(timelineMap);
|
|
466
|
+
if (false) {
|
|
467
|
+
console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// No info provided. Return empty? Or throw?
|
|
472
|
+
// User test context implied "everything", but implementation requires scope.
|
|
473
|
+
console.warn(`${lc} No domainIbGibs or tjpAddrs provided. Returning empty KV.`);
|
|
474
|
+
return {};
|
|
475
|
+
}
|
|
476
|
+
if (tjps.length === 0) {
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
if (false) {
|
|
480
|
+
console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`);
|
|
481
|
+
}
|
|
482
|
+
const res = await getLatestAddrs({ space, tjpAddrs: tjps });
|
|
483
|
+
if (!res.data || !res.data.latestAddrsMap) {
|
|
484
|
+
throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
|
|
485
|
+
}
|
|
486
|
+
// if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
|
|
487
|
+
return res.data.latestAddrsMap;
|
|
367
488
|
}
|
|
368
|
-
|
|
369
|
-
|
|
489
|
+
catch (error) {
|
|
490
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
491
|
+
throw error;
|
|
370
492
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
493
|
+
finally {
|
|
494
|
+
if (logalot) {
|
|
495
|
+
console.log(`${lc} complete.`);
|
|
496
|
+
}
|
|
374
497
|
}
|
|
375
|
-
return res.data.latestAddrsMap;
|
|
376
498
|
}
|
|
377
499
|
async analyzeTimelines({ domainIbGibs, space, }) {
|
|
378
500
|
const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
|
|
@@ -427,14 +549,16 @@ export class SyncSagaCoordinator {
|
|
|
427
549
|
const tip = timeline.at(-1);
|
|
428
550
|
initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
429
551
|
});
|
|
552
|
+
if (logalot) {
|
|
553
|
+
console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
|
|
554
|
+
console.log(`${lc} initData.stage: ${initData.stage}`);
|
|
555
|
+
}
|
|
430
556
|
const initStone = await this.createSyncMsgStone({
|
|
431
557
|
data: initData,
|
|
432
558
|
space: tempSpace,
|
|
433
559
|
metaspace
|
|
434
560
|
});
|
|
435
|
-
if (logalot) {
|
|
436
|
-
console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`);
|
|
437
|
-
}
|
|
561
|
+
// if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
|
|
438
562
|
const sagaFrame = await this.evolveSyncSagaIbGib({
|
|
439
563
|
msgStones: [initStone],
|
|
440
564
|
identity: sessionIdentity,
|
|
@@ -442,9 +566,14 @@ export class SyncSagaCoordinator {
|
|
|
442
566
|
metaspace,
|
|
443
567
|
conflictStrategy,
|
|
444
568
|
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
569
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
570
|
+
await this.ensureSagaFrameInBothSpaces({
|
|
571
|
+
frame: sagaFrame,
|
|
572
|
+
destSpace: localSpace, // localSpace is the Sender's destSpace
|
|
573
|
+
tempSpace,
|
|
574
|
+
metaspace
|
|
575
|
+
});
|
|
576
|
+
// if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
448
577
|
return { sagaFrame, srcGraph };
|
|
449
578
|
}
|
|
450
579
|
catch (error) {
|
|
@@ -458,19 +587,56 @@ export class SyncSagaCoordinator {
|
|
|
458
587
|
}
|
|
459
588
|
}
|
|
460
589
|
/**
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* @remarks
|
|
464
|
-
* **Execution Context**: **Universal (Both Sender and Receiver)**.
|
|
465
|
-
*
|
|
466
|
-
* This method acts as the "Reducer" for the Sync FSM. It determines the current stage
|
|
467
|
-
* based on the incoming frame and delegates to the appropriate handler.
|
|
468
|
-
*
|
|
469
|
-
* * If running on **Receiver**: Handles `Init` (via `handleInitFrame`).
|
|
470
|
-
* * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
|
|
471
|
-
* * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
|
|
590
|
+
* Helper to poll for streaming domain payloads and put them in the
|
|
591
|
+
* local {@link tempSpace}.
|
|
472
592
|
*/
|
|
473
|
-
async
|
|
593
|
+
async pollForDomainPayloads({ expectedAddrs, domainPayloadsMap, tempSpace, }) {
|
|
594
|
+
const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
|
|
595
|
+
try {
|
|
596
|
+
if (logalot) {
|
|
597
|
+
console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`);
|
|
598
|
+
}
|
|
599
|
+
let pending = [...expectedAddrs];
|
|
600
|
+
const start = Date.now();
|
|
601
|
+
/**
|
|
602
|
+
* This needs
|
|
603
|
+
*/
|
|
604
|
+
const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
|
|
605
|
+
while (pending.length > 0) {
|
|
606
|
+
if (Date.now() - start > timeoutMs) {
|
|
607
|
+
throw new Error(`Timeout waiting for payloads: ${pending.join(', ')} (E: 46e1683c9578095261aaf798bd5e1826)`);
|
|
608
|
+
}
|
|
609
|
+
const stillPending = [];
|
|
610
|
+
const found = [];
|
|
611
|
+
for (const addr of pending) {
|
|
612
|
+
if (domainPayloadsMap.has(addr)) {
|
|
613
|
+
found.push(domainPayloadsMap.get(addr));
|
|
614
|
+
domainPayloadsMap.delete(addr);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
stillPending.push(addr);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (found.length > 0) {
|
|
621
|
+
await putInSpace({ space: tempSpace, ibGibs: found });
|
|
622
|
+
}
|
|
623
|
+
pending = stillPending;
|
|
624
|
+
if (pending.length > 0) {
|
|
625
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
finally {
|
|
634
|
+
if (logalot) {
|
|
635
|
+
console.log(`${lc} complete.`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
async handleSagaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, identity, identitySecret, metaspace, }) {
|
|
474
640
|
const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
|
|
475
641
|
try {
|
|
476
642
|
if (logalot) {
|
|
@@ -483,21 +649,19 @@ export class SyncSagaCoordinator {
|
|
|
483
649
|
console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
|
|
484
650
|
}
|
|
485
651
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
486
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
652
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
487
653
|
if (logalot) {
|
|
488
654
|
console.log(`${lc} handling frame stage: ${stage}`);
|
|
489
655
|
}
|
|
490
656
|
switch (stage) {
|
|
491
657
|
case SyncStage.init:
|
|
492
|
-
return await this.handleInitFrame({ sagaIbGib, messageData, metaspace,
|
|
658
|
+
return await this.handleInitFrame({ sagaIbGib, messageData, metaspace, destSpace, tempSpace, identity, identitySecret });
|
|
493
659
|
case SyncStage.ack:
|
|
494
|
-
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace,
|
|
660
|
+
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
|
|
495
661
|
case SyncStage.delta:
|
|
496
|
-
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace,
|
|
662
|
+
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
|
|
497
663
|
case SyncStage.commit:
|
|
498
|
-
return await this.handleCommitFrame({ sagaIbGib, metaspace,
|
|
499
|
-
case SyncStage.conflict:
|
|
500
|
-
return await this.handleConflictFrame({ sagaIbGib, metaspace, space, });
|
|
664
|
+
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
|
|
501
665
|
default:
|
|
502
666
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
503
667
|
}
|
|
@@ -525,8 +689,9 @@ export class SyncSagaCoordinator {
|
|
|
525
689
|
* 3. Identifies what Receiver needs (`deltaReqAddrs`).
|
|
526
690
|
* 4. Returns an `Ack` frame containing these lists.
|
|
527
691
|
*/
|
|
528
|
-
async handleInitFrame({ sagaIbGib, messageData,
|
|
692
|
+
async handleInitFrame({ sagaIbGib, messageData, destSpace, tempSpace, metaspace, identity, identitySecret, }) {
|
|
529
693
|
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
694
|
+
console.log(`${lc} [TEST DEBUG] Received destSpace: ${destSpace.data?.name || destSpace.ib} (uuid: ${destSpace.data?.uuid || '[no uuid]'})`);
|
|
530
695
|
if (logalot) {
|
|
531
696
|
console.log(`${lc} starting...`);
|
|
532
697
|
}
|
|
@@ -535,9 +700,7 @@ export class SyncSagaCoordinator {
|
|
|
535
700
|
if (initData.stage !== SyncStage.init) {
|
|
536
701
|
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
|
|
537
702
|
}
|
|
538
|
-
if (logalot) {
|
|
539
|
-
console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`);
|
|
540
|
-
}
|
|
703
|
+
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
541
704
|
if (!initData || !initData.knowledgeVector) {
|
|
542
705
|
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
543
706
|
}
|
|
@@ -554,7 +717,7 @@ export class SyncSagaCoordinator {
|
|
|
554
717
|
console.log(`${lc} processing stones: ${stones.length}`);
|
|
555
718
|
}
|
|
556
719
|
// Check if we have these stones
|
|
557
|
-
const resStones = await getFromSpace({ space, addrs: stones });
|
|
720
|
+
const resStones = await getFromSpace({ space: destSpace, addrs: stones });
|
|
558
721
|
const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
|
|
559
722
|
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
560
723
|
if (logalot) {
|
|
@@ -572,6 +735,7 @@ export class SyncSagaCoordinator {
|
|
|
572
735
|
console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
|
|
573
736
|
}
|
|
574
737
|
const remoteTjps = Object.keys(remoteKV);
|
|
738
|
+
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
575
739
|
if (logalot) {
|
|
576
740
|
console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
|
|
577
741
|
}
|
|
@@ -580,7 +744,7 @@ export class SyncSagaCoordinator {
|
|
|
580
744
|
if (remoteTjps.length > 0) {
|
|
581
745
|
// Batch get latest addrs for the TJPs
|
|
582
746
|
const resGetLatestAddrs = await getLatestAddrs({
|
|
583
|
-
space,
|
|
747
|
+
space: destSpace,
|
|
584
748
|
tjpAddrs: remoteTjps,
|
|
585
749
|
});
|
|
586
750
|
if (!resGetLatestAddrs.data) {
|
|
@@ -590,6 +754,7 @@ export class SyncSagaCoordinator {
|
|
|
590
754
|
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
|
|
591
755
|
}
|
|
592
756
|
localKV = resGetLatestAddrs.data.latestAddrsMap;
|
|
757
|
+
console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
|
|
593
758
|
if (logalot) {
|
|
594
759
|
console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
|
|
595
760
|
}
|
|
@@ -600,21 +765,25 @@ export class SyncSagaCoordinator {
|
|
|
600
765
|
const localAddr = localKV[tjp];
|
|
601
766
|
if (!localAddr) {
|
|
602
767
|
// We (Receiver) don't have this timeline. Request it.
|
|
768
|
+
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
603
769
|
deltaReqAddrs.push(remoteAddr);
|
|
604
770
|
continue;
|
|
605
771
|
}
|
|
606
772
|
if (localAddr === remoteAddr) {
|
|
607
773
|
// Synced
|
|
774
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
|
|
608
775
|
continue;
|
|
609
776
|
}
|
|
777
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
610
778
|
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
611
779
|
// (Sender has older version, Receiver has newer) -> Receiver Offers Push
|
|
612
780
|
const isRemoteInPast = await isPastFrame({
|
|
613
781
|
olderAddr: remoteAddr,
|
|
614
782
|
newerAddr: localAddr,
|
|
615
|
-
space,
|
|
783
|
+
space: destSpace,
|
|
616
784
|
});
|
|
617
785
|
if (isRemoteInPast) {
|
|
786
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
618
787
|
pushOfferAddrs.push(localAddr);
|
|
619
788
|
}
|
|
620
789
|
else {
|
|
@@ -624,14 +793,16 @@ export class SyncSagaCoordinator {
|
|
|
624
793
|
const isLocalInPast = await isPastFrame({
|
|
625
794
|
olderAddr: localAddr,
|
|
626
795
|
newerAddr: remoteAddr,
|
|
627
|
-
space,
|
|
796
|
+
space: destSpace,
|
|
628
797
|
});
|
|
629
798
|
if (isLocalInPast) {
|
|
630
799
|
// Fast-Forward: We update to remote's tip.
|
|
800
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
|
|
631
801
|
deltaReqAddrs.push(remoteAddr);
|
|
632
802
|
}
|
|
633
803
|
else {
|
|
634
804
|
// DIVERGENCE: Both have changes the other doesn't know about.
|
|
805
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
|
|
635
806
|
if (conflictStrategy === 'abort') {
|
|
636
807
|
// Abort Strategy: We will treat this as terminal.
|
|
637
808
|
// But for Unified Ack, we just mark it terminal in the list?
|
|
@@ -656,7 +827,7 @@ export class SyncSagaCoordinator {
|
|
|
656
827
|
// We need to inspect the 'past' of the local tip.
|
|
657
828
|
// We need the ACTUAL object to get the past.
|
|
658
829
|
// We have localAddr.
|
|
659
|
-
const resLocalTip = await getFromSpace({ space, addr: localAddr });
|
|
830
|
+
const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr });
|
|
660
831
|
const localTip = resLocalTip.ibGibs?.[0];
|
|
661
832
|
if (!localTip) {
|
|
662
833
|
throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
|
|
@@ -699,7 +870,7 @@ export class SyncSagaCoordinator {
|
|
|
699
870
|
// 2. Add Push Offers (Missing in Local)
|
|
700
871
|
// Check if we have them. If not, ask for them.
|
|
701
872
|
for (const addr of pushOfferAddrs) {
|
|
702
|
-
const hasIt = await getFromSpace({ addr, space });
|
|
873
|
+
const hasIt = await getFromSpace({ addr, space: destSpace });
|
|
703
874
|
if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
|
|
704
875
|
// If we don't have it, we put it in `deltaReqAddrs` of the Ack.
|
|
705
876
|
deltaReqAddrs.push(addr);
|
|
@@ -715,7 +886,7 @@ export class SyncSagaCoordinator {
|
|
|
715
886
|
for (const tjp of remoteTjps) {
|
|
716
887
|
const localAddr = localKV[tjp];
|
|
717
888
|
if (localAddr) {
|
|
718
|
-
const res = await getFromSpace({ addr: localAddr, space });
|
|
889
|
+
const res = await getFromSpace({ addr: localAddr, space: destSpace });
|
|
719
890
|
if (res.success && res.ibGibs?.[0]) {
|
|
720
891
|
const ibGib = res.ibGibs[0];
|
|
721
892
|
const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
|
|
@@ -732,7 +903,7 @@ export class SyncSagaCoordinator {
|
|
|
732
903
|
// We can't really know if it's covered easily without resolving.
|
|
733
904
|
// But if we don't have it (requesting), we won't find it here anyway.
|
|
734
905
|
// If we DO have it (push offer), we might find it.
|
|
735
|
-
const res = await getFromSpace({ addr, space });
|
|
906
|
+
const res = await getFromSpace({ addr, space: destSpace });
|
|
736
907
|
if (res.success && res.ibGibs?.[0]) {
|
|
737
908
|
const ibGib = res.ibGibs[0];
|
|
738
909
|
const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
|
|
@@ -767,10 +938,11 @@ export class SyncSagaCoordinator {
|
|
|
767
938
|
deltaReqAddrs,
|
|
768
939
|
pushOfferAddrs,
|
|
769
940
|
knowledgeVector,
|
|
941
|
+
conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
|
|
770
942
|
};
|
|
771
943
|
const ackStone = await this.createSyncMsgStone({
|
|
772
944
|
data: ackData,
|
|
773
|
-
space,
|
|
945
|
+
space: tempSpace,
|
|
774
946
|
metaspace,
|
|
775
947
|
});
|
|
776
948
|
if (logalot) {
|
|
@@ -781,13 +953,18 @@ export class SyncSagaCoordinator {
|
|
|
781
953
|
prevSagaIbGib: sagaIbGib,
|
|
782
954
|
msgStones: [ackStone],
|
|
783
955
|
identity,
|
|
784
|
-
space,
|
|
956
|
+
space: tempSpace,
|
|
785
957
|
metaspace,
|
|
786
958
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
|
|
959
|
+
// IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
|
|
960
|
+
await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
|
|
961
|
+
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
962
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
963
|
+
const payloadIbGibsControl = [ackFrame, ackStone];
|
|
964
|
+
if (identity) {
|
|
965
|
+
payloadIbGibsControl.push(identity);
|
|
966
|
+
}
|
|
967
|
+
return { frame: ackFrame, payloadIbGibsControl };
|
|
791
968
|
}
|
|
792
969
|
/**
|
|
793
970
|
* Handles the `Ack` frame.
|
|
@@ -801,240 +978,280 @@ export class SyncSagaCoordinator {
|
|
|
801
978
|
*
|
|
802
979
|
* Returns a `Delta` frame.
|
|
803
980
|
*/
|
|
804
|
-
async handleAckFrame({ sagaIbGib, srcGraph,
|
|
981
|
+
async handleAckFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
|
|
805
982
|
const lc = `${this.lc}[${this.handleAckFrame.name}]`;
|
|
806
|
-
|
|
807
|
-
console.log(`${lc} starting...`);
|
|
808
|
-
}
|
|
809
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
810
|
-
const ackData = messageData;
|
|
811
|
-
if (!ackData) {
|
|
812
|
-
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
813
|
-
}
|
|
814
|
-
if (ackData.stage !== SyncStage.ack) {
|
|
815
|
-
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
816
|
-
}
|
|
817
|
-
if (logalot) {
|
|
818
|
-
console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
|
|
819
|
-
}
|
|
820
|
-
// 1. Check for Conflicts
|
|
821
|
-
const conflicts = ackData.conflicts || [];
|
|
822
|
-
const terminalConflicts = conflicts.filter(c => c.terminal);
|
|
823
|
-
if (terminalConflicts.length > 0) {
|
|
824
|
-
console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
|
|
825
|
-
// Terminal failure. Sender should probably Commit(Fail) or just Abort.
|
|
826
|
-
// For now, throw to trigger abort.
|
|
827
|
-
throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
|
|
828
|
-
}
|
|
829
|
-
const optimisticConflicts = conflicts.filter(c => !c.terminal);
|
|
830
|
-
const mergeDeltaReqs = []; // Additional requests for merging
|
|
831
|
-
if (optimisticConflicts.length > 0) {
|
|
983
|
+
try {
|
|
832
984
|
if (logalot) {
|
|
833
|
-
console.log(`${lc}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
//
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
// We need
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
985
|
+
console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
|
|
986
|
+
}
|
|
987
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
988
|
+
const ackData = messageData;
|
|
989
|
+
if (!ackData) {
|
|
990
|
+
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
991
|
+
}
|
|
992
|
+
if (ackData.stage !== SyncStage.ack) {
|
|
993
|
+
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
994
|
+
}
|
|
995
|
+
if (logalot) {
|
|
996
|
+
console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`);
|
|
997
|
+
}
|
|
998
|
+
// 1. Check for Conflicts
|
|
999
|
+
const conflicts = ackData.conflicts || [];
|
|
1000
|
+
console.log(`${lc} [CONFLICT DEBUG] Received conflicts from Ack: ${conflicts.length}`);
|
|
1001
|
+
if (conflicts.length > 0) {
|
|
1002
|
+
console.log(`${lc} [CONFLICT DEBUG] Conflicts detail: ${JSON.stringify(conflicts, null, 2)}`);
|
|
1003
|
+
}
|
|
1004
|
+
const terminalConflicts = conflicts.filter(c => c.terminal);
|
|
1005
|
+
if (terminalConflicts.length > 0) {
|
|
1006
|
+
console.warn(`${lc} Received terminal conflicts from Ack: ${JSON.stringify(terminalConflicts)}`);
|
|
1007
|
+
// Terminal failure. Sender should probably Commit(Fail) or just Abort.
|
|
1008
|
+
// For now, throw to trigger abort.
|
|
1009
|
+
throw new Error(`${lc} Peer reported terminal conflicts. (E: a1b2c3d4e5f6g7h8i9j0k)`);
|
|
1010
|
+
}
|
|
1011
|
+
const optimisticConflicts = conflicts.filter(c => !c.terminal);
|
|
1012
|
+
const mergeDeltaReqs = []; // Additional requests for merging
|
|
1013
|
+
if (optimisticConflicts.length > 0) {
|
|
1014
|
+
console.log(`${lc} [CONFLICT DEBUG] Processing ${optimisticConflicts.length} optimistic conflicts`);
|
|
1015
|
+
// We need to resolve these.
|
|
1016
|
+
// Strategy:
|
|
1017
|
+
// 1. Analyze Divergence (Sender vs Receiver)
|
|
1018
|
+
// 2. Identify missing data needed for merge (Receiver's unique frames)
|
|
1019
|
+
// 3. Request that data (as Delta Reqs)
|
|
1020
|
+
// 4. (Later in Delta Phase) Perform Merge.
|
|
1021
|
+
// BUT: The Delta Phase is usually generic "Send me these Addrs".
|
|
1022
|
+
// If we just add to `deltaReqAddrs` (which are requests for Sender to send to Receiver?),
|
|
1023
|
+
// wait. `ackData.deltaReqAddrs` are what RECEIVER wants from SENDER.
|
|
1024
|
+
// We (Sender) are processing the Ack.
|
|
1025
|
+
// We need to request data FROM Receiver.
|
|
1026
|
+
// But the protocol 'Ack' step typically leads to 'Delta' (Sender sending data).
|
|
1027
|
+
// If Sender needs data from Receiver, it usually happens in 'Pull' mode or a separate request?
|
|
1028
|
+
// Or can we send a 'Delta Request' frame?
|
|
1029
|
+
// Standard Saga: Init(Push) -> Ack(Pull Reqs) -> Delta(Push Data).
|
|
1030
|
+
// If Sender needs data, we might need a "Reverse Delta" or "Pull" phase?
|
|
1031
|
+
// Or we just proceed to Delta (sending what Receiver wants),
|
|
1032
|
+
// AND we piggyback our own requests?
|
|
1033
|
+
// OR: We treat the Conflict Resolution as a sub-saga or side-effect?
|
|
1034
|
+
// SIMPLIFICATION for V1:
|
|
1035
|
+
// If we need data to merge, we must get it.
|
|
1036
|
+
// We are the Coordinator (Active). We can fetch from Peer immediately?
|
|
1037
|
+
// `peer.pull(addr)`?
|
|
1038
|
+
// Yes! The Coordinator has the `peer`.
|
|
1039
|
+
// Let's analyze and pull immediately.
|
|
1040
|
+
for (const conflict of optimisticConflicts) {
|
|
1041
|
+
const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
1042
|
+
// Sender History
|
|
1043
|
+
// We need our own history for this timeline.
|
|
1044
|
+
// We know the 'senderTip' (remoteAddr in Ack).
|
|
1045
|
+
// Sender should verify it has this tip.
|
|
1046
|
+
// Compute Diffs
|
|
1047
|
+
// We need to find `receiverOnly` addrs.
|
|
1048
|
+
// Receiver sent us `timelineAddrs` (Full History).
|
|
1049
|
+
const receiverHistorySet = new Set(timelineAddrs);
|
|
1050
|
+
// We need our execution context's history for this senderTip.
|
|
1051
|
+
// We can fetch valid 'past' from space.
|
|
1052
|
+
const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
|
|
1053
|
+
const senderTipIbGib = resSenderTip.ibGibs?.[0];
|
|
1054
|
+
if (!senderTipIbGib) {
|
|
1055
|
+
throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 9c8d7e6f5g4h3i2j1k0l)`);
|
|
884
1056
|
}
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1057
|
+
// Basic Diff: Find what Receiver has that we don't.
|
|
1058
|
+
// Actually, we need to traverse OUR past to find commonality.
|
|
1059
|
+
const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
|
|
1060
|
+
const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
|
|
1061
|
+
if (receiverOnlyAddrs.length > 0) {
|
|
1062
|
+
console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
|
|
1063
|
+
console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
|
|
1064
|
+
// PULL these frames from Peer into Local Space
|
|
1065
|
+
// (Validation: We trust peer for now / verification happens on put)
|
|
1066
|
+
for (const addr of receiverOnlyAddrs) {
|
|
1067
|
+
console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
|
|
1068
|
+
}
|
|
1069
|
+
// Compute DELTA dependencies for each receiver-only frame
|
|
1070
|
+
// Find LCA to determine what dependencies we already have
|
|
1071
|
+
const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
|
|
1072
|
+
console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
|
|
1073
|
+
const skipAddrsSet = new Set();
|
|
1074
|
+
if (lcaAddr) {
|
|
1075
|
+
try {
|
|
1076
|
+
const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
|
|
1077
|
+
const lcaIbGib = lcaRes.ibGibs?.[0];
|
|
1078
|
+
if (lcaIbGib) {
|
|
1079
|
+
const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
|
|
1080
|
+
if (lcaDeps)
|
|
1081
|
+
Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
|
|
1082
|
+
console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
catch (e) {
|
|
1086
|
+
console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
// For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
|
|
1090
|
+
for (const addr of receiverOnlyAddrs) {
|
|
1091
|
+
// Add the frame itself first
|
|
1092
|
+
if (!mergeDeltaReqs.includes(addr)) {
|
|
1093
|
+
mergeDeltaReqs.push(addr);
|
|
1094
|
+
}
|
|
1095
|
+
// Get the frame's delta dependencies (skip LCA's deps)
|
|
1096
|
+
try {
|
|
1097
|
+
const frameRes = await getFromSpace({ addr, space: destSpace });
|
|
1098
|
+
const frameIbGib = frameRes.ibGibs?.[0];
|
|
1099
|
+
if (frameIbGib) {
|
|
1100
|
+
// Get dependency graph, skipping all LCA dependencies
|
|
1101
|
+
const frameDeltaDeps = await getDependencyGraph({
|
|
1102
|
+
ibGib: frameIbGib,
|
|
1103
|
+
space: destSpace,
|
|
1104
|
+
skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
|
|
1105
|
+
});
|
|
1106
|
+
if (frameDeltaDeps) {
|
|
1107
|
+
// Add all delta dependencies (Object.keys gives us the addresses)
|
|
1108
|
+
Object.keys(frameDeltaDeps).forEach(depAddr => {
|
|
1109
|
+
if (!mergeDeltaReqs.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
|
|
1110
|
+
mergeDeltaReqs.push(depAddr);
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
catch (depError) {
|
|
1117
|
+
console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${mergeDeltaReqs.length}`);
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
|
|
932
1124
|
}
|
|
933
|
-
mergeDeltaReqs.push(...receiverOnlyAddrs);
|
|
934
1125
|
}
|
|
1126
|
+
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${optimisticConflicts.length} conflicts. mergeDeltaReqs: ${mergeDeltaReqs.length}`);
|
|
935
1127
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
// 2. Process Delta Requests (Push Payload)
|
|
949
|
-
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
950
|
-
const skipAddrs = new Set();
|
|
951
|
-
if (ackData.knowledgeVector) {
|
|
952
|
-
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
953
|
-
addrs.forEach(a => skipAddrs.add(a));
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
const payloadIbGibs = [];
|
|
957
|
-
// Gather all tips to sync first
|
|
958
|
-
const tipsToSync = [];
|
|
959
|
-
for (const addr of deltaReqAddrs) {
|
|
960
|
-
let ibGib = srcGraph[addr];
|
|
961
|
-
if (!ibGib) {
|
|
962
|
-
const res = await getFromSpace({ addr, space });
|
|
963
|
-
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
964
|
-
ibGib = res.ibGibs[0];
|
|
1128
|
+
else {
|
|
1129
|
+
console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
|
|
1130
|
+
}
|
|
1131
|
+
// 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
|
|
1132
|
+
const deltaReqAddrs = ackData.deltaReqAddrs || [];
|
|
1133
|
+
const pushOfferAddrs = ackData.pushOfferAddrs || [];
|
|
1134
|
+
// 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
|
|
1135
|
+
const pullReqAddrs = [];
|
|
1136
|
+
for (const addr of pushOfferAddrs) {
|
|
1137
|
+
const existing = srcGraph[addr] || (await getFromSpace({ addr, space: destSpace })).ibGibs?.[0];
|
|
1138
|
+
if (!existing) {
|
|
1139
|
+
pullReqAddrs.push(addr);
|
|
965
1140
|
}
|
|
966
1141
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1142
|
+
// 2. Process Delta Requests (Push Payload)
|
|
1143
|
+
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
1144
|
+
const skipAddrs = new Set();
|
|
1145
|
+
if (ackData.knowledgeVector) {
|
|
1146
|
+
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
1147
|
+
addrs.forEach(a => skipAddrs.add(a));
|
|
1148
|
+
});
|
|
972
1149
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
// Always include the tip itself
|
|
983
|
-
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
984
|
-
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
985
|
-
// But detailed deps might be skipped.
|
|
986
|
-
// Get Graph with Skips
|
|
987
|
-
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
988
|
-
const deps = await getDependencyGraph({
|
|
989
|
-
ibGib: tip,
|
|
990
|
-
space,
|
|
991
|
-
skipAddrs: Array.from(skipAddrs)
|
|
992
|
-
});
|
|
993
|
-
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
994
|
-
let tipIncluded = false;
|
|
995
|
-
if (deps) {
|
|
996
|
-
Object.values(deps).forEach(d => {
|
|
997
|
-
const dAddr = getIbGibAddr({ ibGib: d });
|
|
998
|
-
if (!allDepsSet.has(dAddr)) {
|
|
999
|
-
allDepsSet.add(dAddr);
|
|
1000
|
-
payloadIbGibs.push(d);
|
|
1001
|
-
}
|
|
1002
|
-
if (dAddr === tipAddr) {
|
|
1003
|
-
tipIncluded = true;
|
|
1150
|
+
const payloadIbGibs = [];
|
|
1151
|
+
// Gather all tips to sync first
|
|
1152
|
+
const tipsToSync = [];
|
|
1153
|
+
for (const addr of deltaReqAddrs) {
|
|
1154
|
+
let ibGib = srcGraph[addr];
|
|
1155
|
+
if (!ibGib) {
|
|
1156
|
+
const res = await getFromSpace({ addr, space: destSpace });
|
|
1157
|
+
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1158
|
+
ibGib = res.ibGibs[0];
|
|
1004
1159
|
}
|
|
1005
|
-
}
|
|
1160
|
+
}
|
|
1161
|
+
if (ibGib) {
|
|
1162
|
+
tipsToSync.push(ibGib);
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
1166
|
+
}
|
|
1006
1167
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1168
|
+
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
1169
|
+
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
1170
|
+
// `getDependencyGraph` takes a single ibGib.
|
|
1171
|
+
// We can optimize by doing it for each tip and unioning the result?
|
|
1172
|
+
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
1173
|
+
// We will loop.
|
|
1174
|
+
const allDepsSet = new Set();
|
|
1175
|
+
for (const tip of tipsToSync) {
|
|
1176
|
+
// Always include the tip itself
|
|
1177
|
+
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
1178
|
+
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
1179
|
+
// But detailed deps might be skipped.
|
|
1180
|
+
// Get Graph with Skips
|
|
1181
|
+
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
1182
|
+
const deps = await getDependencyGraph({
|
|
1183
|
+
ibGib: tip,
|
|
1184
|
+
space: destSpace,
|
|
1185
|
+
skipAddrs: Array.from(skipAddrs)
|
|
1186
|
+
});
|
|
1187
|
+
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
1188
|
+
let tipIncluded = false;
|
|
1189
|
+
if (deps) {
|
|
1190
|
+
Object.values(deps).forEach(d => {
|
|
1191
|
+
const dAddr = getIbGibAddr({ ibGib: d });
|
|
1192
|
+
if (!allDepsSet.has(dAddr)) {
|
|
1193
|
+
allDepsSet.add(dAddr);
|
|
1194
|
+
payloadIbGibs.push(d);
|
|
1195
|
+
}
|
|
1196
|
+
if (dAddr === tipAddr) {
|
|
1197
|
+
tipIncluded = true;
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1010
1200
|
}
|
|
1011
|
-
if (!
|
|
1012
|
-
|
|
1013
|
-
|
|
1201
|
+
if (!tipIncluded && !skipAddrs.has(tipAddr)) {
|
|
1202
|
+
if (logalot) {
|
|
1203
|
+
console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`);
|
|
1204
|
+
}
|
|
1205
|
+
if (!allDepsSet.has(tipAddr)) {
|
|
1206
|
+
allDepsSet.add(tipAddr);
|
|
1207
|
+
payloadIbGibs.push(tip);
|
|
1208
|
+
}
|
|
1014
1209
|
}
|
|
1015
1210
|
}
|
|
1211
|
+
// 3. Create Delta Frame
|
|
1212
|
+
const sagaId = ackData.sagaId;
|
|
1213
|
+
const deltaData = {
|
|
1214
|
+
sagaId: sagaIbGib.data.uuid,
|
|
1215
|
+
stage: SyncStage.delta,
|
|
1216
|
+
payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
|
|
1217
|
+
requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
|
|
1218
|
+
};
|
|
1219
|
+
if (logalot) {
|
|
1220
|
+
console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`);
|
|
1221
|
+
}
|
|
1222
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
1223
|
+
data: deltaData,
|
|
1224
|
+
space: tempSpace,
|
|
1225
|
+
metaspace,
|
|
1226
|
+
});
|
|
1227
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1228
|
+
prevSagaIbGib: sagaIbGib,
|
|
1229
|
+
msgStones: [deltaStone],
|
|
1230
|
+
identity,
|
|
1231
|
+
space: tempSpace,
|
|
1232
|
+
metaspace,
|
|
1233
|
+
});
|
|
1234
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1235
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1236
|
+
if (logalot) {
|
|
1237
|
+
console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
|
|
1238
|
+
}
|
|
1239
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1240
|
+
const payloadIbGibsControl = [deltaFrame, deltaStone];
|
|
1241
|
+
if (identity) {
|
|
1242
|
+
payloadIbGibsControl.push(identity);
|
|
1243
|
+
}
|
|
1244
|
+
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1245
|
+
}
|
|
1246
|
+
catch (error) {
|
|
1247
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1248
|
+
throw error;
|
|
1249
|
+
}
|
|
1250
|
+
finally {
|
|
1251
|
+
if (logalot) {
|
|
1252
|
+
console.log(`${lc} complete.`);
|
|
1253
|
+
}
|
|
1016
1254
|
}
|
|
1017
|
-
// 3. Create Delta Frame
|
|
1018
|
-
const sagaId = ackData.sagaId;
|
|
1019
|
-
const deltaData = {
|
|
1020
|
-
sagaId: sagaIbGib.data.uuid,
|
|
1021
|
-
stage: SyncStage.delta,
|
|
1022
|
-
payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
|
|
1023
|
-
requests: [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])].length > 0 ? [...(pullReqAddrs || []), ...(mergeDeltaReqs || [])] : undefined,
|
|
1024
|
-
};
|
|
1025
|
-
const deltaStone = await this.createSyncMsgStone({
|
|
1026
|
-
data: deltaData,
|
|
1027
|
-
space,
|
|
1028
|
-
metaspace,
|
|
1029
|
-
});
|
|
1030
|
-
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1031
|
-
prevSagaIbGib: sagaIbGib,
|
|
1032
|
-
msgStones: [deltaStone],
|
|
1033
|
-
identity,
|
|
1034
|
-
space,
|
|
1035
|
-
metaspace,
|
|
1036
|
-
});
|
|
1037
|
-
return { frame: deltaFrame, payloadIbGibs };
|
|
1038
1255
|
}
|
|
1039
1256
|
/**
|
|
1040
1257
|
* Handles the `Delta` frame.
|
|
@@ -1046,12 +1263,12 @@ export class SyncSagaCoordinator {
|
|
|
1046
1263
|
* 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
|
|
1047
1264
|
* 3. **Completion**: If no more requests, transitions to `Commit`.
|
|
1048
1265
|
*/
|
|
1049
|
-
async handleDeltaFrame({ sagaIbGib, srcGraph,
|
|
1266
|
+
async handleDeltaFrame({ sagaIbGib, srcGraph, destSpace, tempSpace, metaspace, identity, }) {
|
|
1050
1267
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1051
1268
|
if (logalot) {
|
|
1052
1269
|
console.log(`${lc} starting...`);
|
|
1053
1270
|
}
|
|
1054
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
1271
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space: tempSpace });
|
|
1055
1272
|
const deltaData = messageData;
|
|
1056
1273
|
if (!deltaData) {
|
|
1057
1274
|
throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
@@ -1062,6 +1279,7 @@ export class SyncSagaCoordinator {
|
|
|
1062
1279
|
if (logalot) {
|
|
1063
1280
|
console.log(`${lc} deltaData: ${pretty(deltaData)} (I: 8d7e6f5g4h3i2j1k0l9m)`);
|
|
1064
1281
|
}
|
|
1282
|
+
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1065
1283
|
const payloadAddrs = deltaData.payloadAddrs || [];
|
|
1066
1284
|
const peerRequests = deltaData.requests || [];
|
|
1067
1285
|
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
@@ -1075,7 +1293,7 @@ export class SyncSagaCoordinator {
|
|
|
1075
1293
|
// The `handleDeltaFrame` contract assumes data is reachable in `space`.
|
|
1076
1294
|
const res = await getFromSpace({
|
|
1077
1295
|
addrs: payloadAddrs,
|
|
1078
|
-
space,
|
|
1296
|
+
space: tempSpace, // Incoming data is in tempSpace
|
|
1079
1297
|
});
|
|
1080
1298
|
if (res.ibGibs) {
|
|
1081
1299
|
receivedPayloadIbGibs.push(...res.ibGibs);
|
|
@@ -1086,47 +1304,80 @@ export class SyncSagaCoordinator {
|
|
|
1086
1304
|
console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
|
|
1087
1305
|
}
|
|
1088
1306
|
}
|
|
1089
|
-
// 2. Fulfill Peer Requests (Outgoing Payload)
|
|
1307
|
+
// 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
|
|
1090
1308
|
const outgoingPayload = [];
|
|
1309
|
+
const outgoingAddrsSet = new Set(); // Track what we've added
|
|
1310
|
+
console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${peerRequests.length} peer requests`);
|
|
1091
1311
|
for (const addr of peerRequests) {
|
|
1312
|
+
// Get the requested ibGib
|
|
1092
1313
|
let ibGib = srcGraph[addr];
|
|
1093
1314
|
if (!ibGib) {
|
|
1094
|
-
const res = await getFromSpace({ addr, space });
|
|
1315
|
+
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1095
1316
|
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1096
1317
|
ibGib = res.ibGibs[0];
|
|
1097
1318
|
}
|
|
1098
1319
|
}
|
|
1099
1320
|
if (ibGib) {
|
|
1100
|
-
|
|
1321
|
+
// Add the requested ibGib itself
|
|
1322
|
+
const ibGibAddr = getIbGibAddr({ ibGib });
|
|
1323
|
+
if (!outgoingAddrsSet.has(ibGibAddr)) {
|
|
1324
|
+
outgoingPayload.push(ibGib);
|
|
1325
|
+
outgoingAddrsSet.add(ibGibAddr);
|
|
1326
|
+
}
|
|
1327
|
+
// Expand to include full dependency graph for this ibGib
|
|
1328
|
+
// (Receiver needs all deps to properly process/merge)
|
|
1329
|
+
try {
|
|
1330
|
+
const deps = await getDependencyGraph({
|
|
1331
|
+
ibGib,
|
|
1332
|
+
space: destSpace,
|
|
1333
|
+
});
|
|
1334
|
+
if (deps) {
|
|
1335
|
+
Object.values(deps).forEach(depIbGib => {
|
|
1336
|
+
const depAddr = getIbGibAddr({ ibGib: depIbGib });
|
|
1337
|
+
if (!outgoingAddrsSet.has(depAddr)) {
|
|
1338
|
+
outgoingPayload.push(depIbGib);
|
|
1339
|
+
outgoingAddrsSet.add(depAddr);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
catch (depError) {
|
|
1345
|
+
console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1346
|
+
}
|
|
1101
1347
|
}
|
|
1102
1348
|
else {
|
|
1103
1349
|
console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
|
|
1104
1350
|
}
|
|
1105
1351
|
}
|
|
1352
|
+
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1106
1353
|
// 3. Execute Merges (If applicable)
|
|
1107
1354
|
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1108
1355
|
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1109
1356
|
// Optimization: Do this only if we received payloads.
|
|
1110
1357
|
const mergeResultIbGibs = [];
|
|
1358
|
+
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1111
1359
|
if (receivedPayloadIbGibs.length > 0) {
|
|
1360
|
+
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1112
1361
|
// Find the Ack frame in history to get conflicts
|
|
1113
1362
|
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1114
1363
|
// V1 timelines carry full history in `past`.
|
|
1115
1364
|
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1365
|
+
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1116
1366
|
let ackData;
|
|
1117
1367
|
if (pastAddrs.length > 0) {
|
|
1118
1368
|
// Batch fetch all past frames
|
|
1119
|
-
const resPast = await getFromSpace({ addrs: pastAddrs, space });
|
|
1369
|
+
const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
|
|
1120
1370
|
if (resPast.success && resPast.ibGibs) {
|
|
1121
1371
|
// Iterate backwards (most recent first) to find the latest Ack
|
|
1122
1372
|
for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
|
|
1123
1373
|
const pastFrame = resPast.ibGibs[i];
|
|
1124
1374
|
const messageStone = await getSyncSagaMessageFromFrame({
|
|
1125
1375
|
frameIbGib: pastFrame,
|
|
1126
|
-
space
|
|
1376
|
+
space: tempSpace
|
|
1127
1377
|
});
|
|
1128
1378
|
if (messageStone?.data?.stage === SyncStage.ack) {
|
|
1129
1379
|
ackData = messageStone.data;
|
|
1380
|
+
console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
|
|
1130
1381
|
break;
|
|
1131
1382
|
}
|
|
1132
1383
|
}
|
|
@@ -1142,7 +1393,9 @@ export class SyncSagaCoordinator {
|
|
|
1142
1393
|
// We blindly attempt merge if we have both tips accessible?
|
|
1143
1394
|
// We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
|
|
1144
1395
|
// Check if we have receiverTip in space
|
|
1145
|
-
|
|
1396
|
+
console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
|
|
1397
|
+
const resRecTip = await getFromSpace({ addr: receiverTip, space: tempSpace }); // Check tempSpace for incoming data
|
|
1398
|
+
console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in tempSpace: ${!!resRecTip.ibGibs?.[0]}`);
|
|
1146
1399
|
if (resRecTip.success && resRecTip.ibGibs?.[0]) {
|
|
1147
1400
|
// We have the tip!
|
|
1148
1401
|
// Do we have the full history?
|
|
@@ -1151,12 +1404,13 @@ export class SyncSagaCoordinator {
|
|
|
1151
1404
|
// Perform Merge!
|
|
1152
1405
|
try {
|
|
1153
1406
|
const mergeResult = await mergeDivergentTimelines({
|
|
1154
|
-
tipA: (await getFromSpace({ addr: senderTip, space })).ibGibs[0], // Our tip
|
|
1155
|
-
tipB: resRecTip.ibGibs[0], // Their tip
|
|
1156
|
-
space,
|
|
1407
|
+
tipA: (await getFromSpace({ addr: senderTip, space: destSpace })).ibGibs[0], // Our tip from destSpace
|
|
1408
|
+
tipB: resRecTip.ibGibs[0], // Their tip (from tempSpace)
|
|
1409
|
+
space: tempSpace, // Merge uses tempSpace
|
|
1157
1410
|
metaspace,
|
|
1158
1411
|
});
|
|
1159
1412
|
if (mergeResult) {
|
|
1413
|
+
console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1160
1414
|
if (logalot) {
|
|
1161
1415
|
console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1162
1416
|
}
|
|
@@ -1200,17 +1454,24 @@ export class SyncSagaCoordinator {
|
|
|
1200
1454
|
responseDeltaData.proposeCommit = true;
|
|
1201
1455
|
const deltaStone = await this.createSyncMsgStone({
|
|
1202
1456
|
data: responseDeltaData,
|
|
1203
|
-
space,
|
|
1457
|
+
space: tempSpace,
|
|
1204
1458
|
metaspace
|
|
1205
1459
|
});
|
|
1206
1460
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1207
1461
|
prevSagaIbGib: sagaIbGib,
|
|
1208
1462
|
msgStones: [deltaStone],
|
|
1209
1463
|
identity,
|
|
1210
|
-
space,
|
|
1464
|
+
space: tempSpace,
|
|
1211
1465
|
metaspace
|
|
1212
1466
|
});
|
|
1213
|
-
|
|
1467
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1468
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1469
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1470
|
+
const payloadIbGibsControl = [deltaFrame, deltaStone];
|
|
1471
|
+
if (identity) {
|
|
1472
|
+
payloadIbGibsControl.push(identity);
|
|
1473
|
+
}
|
|
1474
|
+
return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1214
1475
|
}
|
|
1215
1476
|
else {
|
|
1216
1477
|
// We have nothing to send.
|
|
@@ -1223,17 +1484,24 @@ export class SyncSagaCoordinator {
|
|
|
1223
1484
|
};
|
|
1224
1485
|
const commitStone = await this.createSyncMsgStone({
|
|
1225
1486
|
data: commitData,
|
|
1226
|
-
space,
|
|
1487
|
+
space: tempSpace,
|
|
1227
1488
|
metaspace
|
|
1228
1489
|
});
|
|
1229
1490
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1230
1491
|
prevSagaIbGib: sagaIbGib,
|
|
1231
1492
|
msgStones: [commitStone],
|
|
1232
1493
|
identity,
|
|
1233
|
-
space,
|
|
1494
|
+
space: tempSpace,
|
|
1234
1495
|
metaspace
|
|
1235
1496
|
});
|
|
1236
|
-
|
|
1497
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1498
|
+
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1499
|
+
// Build control payloads for commit
|
|
1500
|
+
const commitCtrlPayloads = [commitFrame, commitStone];
|
|
1501
|
+
if (identity) {
|
|
1502
|
+
commitCtrlPayloads.push(identity);
|
|
1503
|
+
}
|
|
1504
|
+
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1237
1505
|
}
|
|
1238
1506
|
else {
|
|
1239
1507
|
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
@@ -1247,16 +1515,18 @@ export class SyncSagaCoordinator {
|
|
|
1247
1515
|
};
|
|
1248
1516
|
const deltaStone = await this.createSyncMsgStone({
|
|
1249
1517
|
data: responseDeltaData,
|
|
1250
|
-
space,
|
|
1518
|
+
space: tempSpace,
|
|
1251
1519
|
metaspace
|
|
1252
1520
|
});
|
|
1253
1521
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1254
1522
|
prevSagaIbGib: sagaIbGib,
|
|
1255
1523
|
msgStones: [deltaStone],
|
|
1256
1524
|
identity,
|
|
1257
|
-
space,
|
|
1525
|
+
space: tempSpace,
|
|
1258
1526
|
metaspace
|
|
1259
1527
|
});
|
|
1528
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1529
|
+
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1260
1530
|
// Check if PEER proposed commit
|
|
1261
1531
|
if (deltaData.proposeCommit) {
|
|
1262
1532
|
if (logalot) {
|
|
@@ -1271,23 +1541,35 @@ export class SyncSagaCoordinator {
|
|
|
1271
1541
|
};
|
|
1272
1542
|
const commitStone = await this.createSyncMsgStone({
|
|
1273
1543
|
data: commitData,
|
|
1274
|
-
space,
|
|
1544
|
+
space: tempSpace,
|
|
1275
1545
|
metaspace
|
|
1276
1546
|
});
|
|
1277
1547
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1278
1548
|
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1279
1549
|
msgStones: [commitStone],
|
|
1280
1550
|
identity,
|
|
1281
|
-
space,
|
|
1551
|
+
space: tempSpace,
|
|
1282
1552
|
metaspace
|
|
1283
1553
|
});
|
|
1284
|
-
|
|
1554
|
+
// IMMEDIATELY persist to both spaces for audit trail
|
|
1555
|
+
await this.ensureSagaFrameInBothSpaces({ frame: commitFrame, destSpace, tempSpace, metaspace });
|
|
1556
|
+
// Build control payloads for commit
|
|
1557
|
+
const commitCtrlPayloads2 = [commitFrame, commitStone];
|
|
1558
|
+
if (identity) {
|
|
1559
|
+
commitCtrlPayloads2.push(identity);
|
|
1560
|
+
}
|
|
1561
|
+
return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1562
|
+
}
|
|
1563
|
+
// Build control payloads for delta propose
|
|
1564
|
+
const deltaCtrlPayloads = [deltaFrame, deltaStone];
|
|
1565
|
+
if (identity) {
|
|
1566
|
+
deltaCtrlPayloads.push(identity);
|
|
1285
1567
|
}
|
|
1286
|
-
return { frame: deltaFrame,
|
|
1568
|
+
return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1287
1569
|
}
|
|
1288
1570
|
}
|
|
1289
1571
|
}
|
|
1290
|
-
async handleCommitFrame({ sagaIbGib,
|
|
1572
|
+
async handleCommitFrame({ sagaIbGib, destSpace, tempSpace, metaspace, identity, }) {
|
|
1291
1573
|
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
1292
1574
|
if (logalot) {
|
|
1293
1575
|
console.log(`${lc} Commit received.`);
|
|
@@ -1305,19 +1587,6 @@ export class SyncSagaCoordinator {
|
|
|
1305
1587
|
}
|
|
1306
1588
|
return null;
|
|
1307
1589
|
}
|
|
1308
|
-
async handleConflictFrame({ sagaIbGib, metaspace, space, }) {
|
|
1309
|
-
const lc = `${this.lc}[${this.handleConflictFrame.name}]`;
|
|
1310
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
1311
|
-
const conflictData = messageData;
|
|
1312
|
-
if (logalot) {
|
|
1313
|
-
console.log(`${lc} Conflict received. Strategy: ${conflictData?.conflictStrategy}. Terminal: ${conflictData?.isTerminal}`);
|
|
1314
|
-
}
|
|
1315
|
-
if (conflictData?.isTerminal) {
|
|
1316
|
-
throw new Error(`${lc} Saga aborted due to conflicts: ${JSON.stringify(conflictData.conflicts)} (E: b08d1f2a3c4e5d6f7a8b9c0d1e2f3a4b)`);
|
|
1317
|
-
}
|
|
1318
|
-
// Non-terminal logic (stub for future)
|
|
1319
|
-
return null;
|
|
1320
|
-
}
|
|
1321
1590
|
// #endregion Handlers
|
|
1322
1591
|
async createSyncMsgStone({ data, space, metaspace, }) {
|
|
1323
1592
|
const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
|
|
@@ -1349,6 +1618,37 @@ export class SyncSagaCoordinator {
|
|
|
1349
1618
|
}
|
|
1350
1619
|
}
|
|
1351
1620
|
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
|
|
1623
|
+
* Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
|
|
1624
|
+
*/
|
|
1625
|
+
async ensureSagaFrameInBothSpaces({ frame, destSpace, tempSpace, metaspace, }) {
|
|
1626
|
+
// Frame itself (already in tempSpace from creation, need in destSpace for audit)
|
|
1627
|
+
await putInSpace({ space: destSpace, ibGib: frame });
|
|
1628
|
+
await metaspace.registerNewIbGib({ ibGib: frame });
|
|
1629
|
+
// Msg stone(s) (already in tempSpace, need in destSpace for audit)
|
|
1630
|
+
const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
1631
|
+
if (msgStoneAddrs && msgStoneAddrs.length > 0) {
|
|
1632
|
+
const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
|
|
1633
|
+
if (resMsgStones.ibGibs) {
|
|
1634
|
+
for (const msgStone of resMsgStones.ibGibs) {
|
|
1635
|
+
await putInSpace({ space: destSpace, ibGib: msgStone });
|
|
1636
|
+
await metaspace.registerNewIbGib({ ibGib: msgStone });
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
// Identity (if present, already in tempSpace, need in destSpace)
|
|
1641
|
+
const identityAddrs = frame.rel8ns?.identity;
|
|
1642
|
+
if (identityAddrs && identityAddrs.length > 0) {
|
|
1643
|
+
const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
|
|
1644
|
+
if (resIdentity.ibGibs) {
|
|
1645
|
+
for (const identity of resIdentity.ibGibs) {
|
|
1646
|
+
await putInSpace({ space: destSpace, ibGib: identity });
|
|
1647
|
+
await metaspace.registerNewIbGib({ ibGib: identity });
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1352
1652
|
/**
|
|
1353
1653
|
* Evolves the saga timeline with a new frame.
|
|
1354
1654
|
*/
|