@ibgib/core-gib 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/other/graph-helper.d.mts +8 -0
- package/dist/common/other/graph-helper.d.mts.map +1 -1
- package/dist/common/other/graph-helper.mjs +31 -1
- package/dist/common/other/graph-helper.mjs.map +1 -1
- package/dist/sync/sync-conflict.respec.mjs +31 -28
- package/dist/sync/sync-conflict.respec.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +21 -11
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +18 -6
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +24 -15
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +163 -92
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace-constants.respec.mjs +12 -7
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +12 -7
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +15 -10
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +13 -7
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +15 -7
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +13 -7
- 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 +65 -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 +44 -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 +183 -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 +28 -0
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +51 -9
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +244 -26
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +26 -10
- 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 +119 -30
- 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 +31 -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 +81 -87
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +627 -571
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +105 -22
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +56 -76
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +5 -0
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/timeline/timeline-api.d.mts.map +1 -1
- package/dist/timeline/timeline-api.mjs +15 -8
- package/dist/timeline/timeline-api.mjs.map +1 -1
- package/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/package.json +1 -1
- package/src/common/other/graph-helper.mts +26 -1
- package/src/sync/README.md +31 -22
- package/src/sync/sync-conflict.respec.mts +31 -26
- package/src/sync/sync-constants.mts +19 -9
- package/src/sync/sync-helpers.mts +173 -97
- package/src/sync/sync-innerspace-constants.respec.mts +12 -7
- package/src/sync/sync-innerspace-deep-updates.respec.mts +12 -7
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +14 -9
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +13 -7
- package/src/sync/sync-innerspace-partial-update.respec.mts +15 -7
- package/src/sync/sync-innerspace.respec.mts +13 -7
- 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 +72 -0
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +193 -0
- package/src/sync/sync-peer/sync-peer-types.mts +30 -1
- package/src/sync/sync-peer/sync-peer-v1.mts +229 -30
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +140 -43
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +34 -30
- package/src/sync/sync-saga-coordinator.mts +678 -660
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +106 -22
- package/src/sync/sync-types.mts +59 -87
- package/src/timeline/timeline-api.mts +17 -10
- 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 +0 -0
- package/tmp.md +62 -44
- 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 -42
- 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 -194
- 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/sync-local-spaces.respec.mts +0 -200
- package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +0 -240
- package/src/sync/sync-saga-coordinator.respec.mts +0 -52
|
@@ -1,24 +1,25 @@
|
|
|
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, delay, } 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";
|
|
5
4
|
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
|
|
6
|
-
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
|
|
7
5
|
import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
|
|
8
|
-
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME } from "./sync-constants.mjs";
|
|
6
|
+
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from "./sync-constants.mjs";
|
|
9
7
|
import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
|
|
10
|
-
import { SyncMode, } from "./sync-types.mjs";
|
|
11
|
-
import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
|
|
12
|
-
import {
|
|
13
|
-
import { getDependencyGraph } from "../common/other/graph-helper.mjs";
|
|
8
|
+
import { SyncConflictStrategy, SyncMode, } from "./sync-types.mjs";
|
|
9
|
+
import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
|
|
10
|
+
import { getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
|
|
14
11
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
15
12
|
import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
|
|
16
|
-
import {
|
|
13
|
+
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib } from "../common/other/ibgib-helper.mjs";
|
|
17
14
|
import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
18
|
-
import { newupSubject } from "../common/pubsub/subject/subject-helper.mjs";
|
|
15
|
+
import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
|
|
19
16
|
import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
20
17
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
21
|
-
|
|
18
|
+
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
19
|
+
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
20
|
+
const logalot = false;
|
|
21
|
+
const logalotControlDomain = true;
|
|
22
|
+
const lcControlDomain = '[ControlDomain]';
|
|
22
23
|
/**
|
|
23
24
|
* Orchestrates the synchronization process between two spaces (Source and Destination).
|
|
24
25
|
*
|
|
@@ -51,14 +52,13 @@ export class SyncSagaCoordinator {
|
|
|
51
52
|
* @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
|
|
52
53
|
* @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
|
|
53
54
|
*/
|
|
54
|
-
async sync({ peer,
|
|
55
|
+
async sync({ peer, domainIbGibs, conflictStrategy = SyncConflictStrategy.abort, useSessionIdentity = true, metaspace, localSpace, }) {
|
|
55
56
|
const lc = `${this.lc}[${this.sync.name}]`;
|
|
56
57
|
if (logalot) {
|
|
57
58
|
console.log(`${lc} starting...`);
|
|
58
59
|
}
|
|
59
|
-
const localSpace = (_source || _localSpace);
|
|
60
60
|
if (!localSpace) {
|
|
61
|
-
throw new Error(`${lc} source (or localSpace) required (E:
|
|
61
|
+
throw new Error(`${lc} source (or localSpace) required (E: 25df3761f7686a1099a552f83c95d326)`);
|
|
62
62
|
}
|
|
63
63
|
// 1. SETUP SAGA METADATA
|
|
64
64
|
const sagaId = await getUUID();
|
|
@@ -74,10 +74,6 @@ export class SyncSagaCoordinator {
|
|
|
74
74
|
resolveDone = resolve;
|
|
75
75
|
rejectDone = reject;
|
|
76
76
|
});
|
|
77
|
-
async function getTempSpaceName() {
|
|
78
|
-
const uuid = await getUUID();
|
|
79
|
-
return `tmp_sync_space_${uuid.substring(0, 8)}`;
|
|
80
|
-
}
|
|
81
77
|
// WORKING CONTEXT (Transactional)
|
|
82
78
|
const tempSpaceName = await getTempSpaceName();
|
|
83
79
|
const tempSpace = await metaspace.createNewLocalSpace({
|
|
@@ -105,33 +101,24 @@ export class SyncSagaCoordinator {
|
|
|
105
101
|
: undefined;
|
|
106
102
|
// if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
|
|
107
103
|
// 3. CREATE INITIAL FRAME (Stage.init)
|
|
108
|
-
const { sagaFrame: initFrame,
|
|
104
|
+
const { sagaFrame: initFrame, initialDomainGraph } = await this.createInitFrame({
|
|
109
105
|
sagaId,
|
|
110
106
|
sessionIdentity,
|
|
111
|
-
localSpace,
|
|
112
107
|
domainIbGibs,
|
|
113
|
-
|
|
114
|
-
metaspace,
|
|
115
|
-
conflictStrategy
|
|
116
|
-
});
|
|
117
|
-
// 4. EXECUTE SAGA LOOP (FSM)
|
|
118
|
-
const syncedIbGibs = await this.executeSagaLoop({
|
|
119
|
-
initialFrame: initFrame,
|
|
120
|
-
srcGraph,
|
|
121
|
-
peer,
|
|
122
|
-
sessionIdentity,
|
|
123
|
-
updates$,
|
|
124
|
-
localSpace,
|
|
125
|
-
tempSpace,
|
|
126
|
-
metaspace
|
|
108
|
+
conflictStrategy,
|
|
109
|
+
metaspace, localSpace, tempSpace,
|
|
127
110
|
});
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
111
|
+
// 4. KICK OFF THE PING-PONG SAGA LOOP (FSM)
|
|
112
|
+
// commented to compile
|
|
113
|
+
// await this.executeSagaLoop({
|
|
114
|
+
// initialFrame: initFrame,
|
|
115
|
+
// peer,
|
|
116
|
+
// sessionIdentity,
|
|
117
|
+
// updates$,
|
|
118
|
+
// localSpace,
|
|
119
|
+
// tempSpace,
|
|
120
|
+
// metaspace
|
|
121
|
+
// });
|
|
135
122
|
resolveDone();
|
|
136
123
|
if (!updates$.complete) {
|
|
137
124
|
throw new Error(`(UNEXPECTED) updates$.complete falsy? (E: d24cd82184aec130c89a320819b39126)`);
|
|
@@ -194,87 +181,110 @@ export class SyncSagaCoordinator {
|
|
|
194
181
|
* **Execution Context**: **Sender (Local)**.
|
|
195
182
|
*
|
|
196
183
|
* This method manages the "Ping Pong" request-response cycle on the Sender.
|
|
197
|
-
* It sends frames via the Peer Witness and processes the responses using `
|
|
184
|
+
* It sends frames via the Peer Witness and processes the responses using `handleSagaResponseContext`.
|
|
198
185
|
*
|
|
199
186
|
* **Data Transport Note**:
|
|
200
187
|
* Actual ibGib data (payloads) are transported via `SyncSagaContext.rel8ns.payload`.
|
|
201
|
-
* When `
|
|
188
|
+
* When `handleSagaResponseContext` returns a `nextPayloadIbGibs` (data to send), this loop injects it into
|
|
202
189
|
* the NEXT request context.
|
|
203
190
|
* When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
|
|
204
191
|
*/
|
|
205
|
-
async executeSagaLoop({ initialFrame,
|
|
192
|
+
async executeSagaLoop({ initialFrame, initialDomainGraph, peer, sessionIdentity, updates$, localSpace, tempSpace, metaspace }) {
|
|
206
193
|
const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
|
|
207
194
|
// The current frame we just generated (e.g., Init or Delta Request)
|
|
208
195
|
let currentFrame = initialFrame;
|
|
209
196
|
// The payload we need to attach to the message (data we are sending)
|
|
210
|
-
let
|
|
211
|
-
//
|
|
212
|
-
|
|
197
|
+
let nextDomainIbGibs = [];
|
|
198
|
+
// First, inject local/tempSpace into peer so it can pull as
|
|
199
|
+
// needed...code smell?
|
|
200
|
+
peer.senderSpace = localSpace;
|
|
201
|
+
peer.senderTempSpace = tempSpace;
|
|
213
202
|
while (currentFrame) {
|
|
214
203
|
// A. Create Context (Request)
|
|
215
204
|
// 1. Calculate Full Dependency Graph (including Ancestors/DNA)
|
|
216
|
-
// We must do this BEFORE creating the Context so we can list them
|
|
217
|
-
|
|
205
|
+
// We must do this BEFORE creating the Context so we can list them
|
|
206
|
+
// all in payloadAddrsDomain and payloadAddrsControl.
|
|
207
|
+
const nextDomainIbGibsAndDeps = [];
|
|
218
208
|
// A. Payload (Standard Deep Deps)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
209
|
+
// TODO: THIS IS EXTREMELY INEFFICIENT. adjust this algorithm to
|
|
210
|
+
// only do the dependencies that the other end doesn't need (diff
|
|
211
|
+
// between tip and LCA). This can be done using the information in
|
|
212
|
+
// the ack's conflicts array.
|
|
213
|
+
for (const nextDomainIbGib of nextDomainIbGibs) {
|
|
214
|
+
let nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: localSpace });
|
|
215
|
+
if (!nextDomainIbGibGraph) {
|
|
216
|
+
nextDomainIbGibGraph = await getDependencyGraph({ ibGib: nextDomainIbGib, space: tempSpace });
|
|
223
217
|
}
|
|
224
|
-
if (
|
|
225
|
-
|
|
218
|
+
if (nextDomainIbGibGraph) {
|
|
219
|
+
nextDomainIbGibsAndDeps.push(...Object.values(nextDomainIbGibGraph));
|
|
226
220
|
}
|
|
227
221
|
else {
|
|
228
|
-
|
|
222
|
+
throw new Error(`(UNEXPECTED) we couldn't get the graph for a known domain ibgib? nextDomainIbGib addr: ${getIbGibAddr({ ibGib: nextDomainIbGib })} (E: 01b3e4db8768b5b77db72e486f4f7826)`);
|
|
229
223
|
}
|
|
230
224
|
}
|
|
231
225
|
if (logalot) {
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
// B. Frames (Shallow Sync Deps)
|
|
235
|
-
if (currentFrame) {
|
|
236
|
-
const deps = await getSyncSagaDependencyGraph({ ibGib: currentFrame, space: tempSpace });
|
|
237
|
-
if (deps)
|
|
238
|
-
allDeps.push(...deps);
|
|
226
|
+
console.log(`${lc} payloadIbGibsDomain count: ${nextDomainIbGibsAndDeps.length} (I: 2beda8ca7dc5ac0f48ed9e25e704b826)`);
|
|
239
227
|
}
|
|
240
228
|
// 2. Create Context (Envelope)
|
|
241
|
-
//
|
|
242
|
-
const
|
|
229
|
+
// set up subscription for response's payload ibgibs (if any)
|
|
230
|
+
const domainPayloadsMap = new Map();
|
|
231
|
+
const sublc = `${lc}[peer.payloadIbGibsDomainReceived$]`;
|
|
232
|
+
const subscription = await peer.payloadIbGibsDomainReceived$.subscribe(fnObs({
|
|
233
|
+
next: async (ibgib) => {
|
|
234
|
+
if (logalot) {
|
|
235
|
+
console.log(`${sublc} next fired. (I: 2b4bdf502a38a90ba33d9711e7cb7826)`);
|
|
236
|
+
}
|
|
237
|
+
const addr = getIbGibAddr({ ibGib: ibgib });
|
|
238
|
+
if (logalotControlDomain) {
|
|
239
|
+
console.log(`${lc}${lcControlDomain} DOMAIN STREAM RECEIVED <- observable: ${addr} (I: d69ee80fcaece272483ec33b2d289826)`);
|
|
240
|
+
}
|
|
241
|
+
domainPayloadsMap.set(addr, ibgib);
|
|
242
|
+
},
|
|
243
|
+
error: async (e) => {
|
|
244
|
+
if (isIbGib(e)) {
|
|
245
|
+
console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.dir(e);
|
|
249
|
+
console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
complete: async () => {
|
|
253
|
+
if (logalot) {
|
|
254
|
+
console.log(`${sublc} complete fired. (I: a47218aa9e4433fdb97c068880a45826)`);
|
|
255
|
+
}
|
|
256
|
+
await subscription.unsubscribe();
|
|
257
|
+
},
|
|
258
|
+
}));
|
|
259
|
+
// Create the Request Context...
|
|
243
260
|
const requestCtx = await createSyncSagaContext({
|
|
244
|
-
cmd: SyncSagaContextCmd.process,
|
|
245
261
|
sagaFrame: currentFrame,
|
|
246
262
|
sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
|
|
247
|
-
|
|
263
|
+
/**
|
|
264
|
+
* init frame: empty
|
|
265
|
+
*
|
|
266
|
+
*/
|
|
267
|
+
payloadIbGibsDomain: nextDomainIbGibsAndDeps,
|
|
268
|
+
localSpace,
|
|
248
269
|
});
|
|
249
|
-
//
|
|
250
|
-
if (
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
allDeps.push(sessionIdentity);
|
|
259
|
-
}
|
|
260
|
-
if (allDeps.length > 0) {
|
|
261
|
-
await putInSpace({
|
|
262
|
-
space: localSpace,
|
|
263
|
-
ibGibs: allDeps
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
// B. Transmit
|
|
270
|
+
// Log what we're sending
|
|
271
|
+
if (logalotControlDomain) {
|
|
272
|
+
const domainAddrs = nextDomainIbGibsAndDeps.map(p => getIbGibAddr({ ibGib: p }));
|
|
273
|
+
console.log(`${lc}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: b3c4d5e6f7a8b9c0)`);
|
|
274
|
+
console.log(`${lc}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
|
|
275
|
+
console.log(`${lc}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
276
|
+
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(', ') || '(none)'}`);
|
|
277
|
+
}
|
|
278
|
+
// update our saga listeners...
|
|
267
279
|
// if (logalot) { console.log(`${lc} transmitting... requestCtx: ${pretty(requestCtx)} (I: 8cf20817c66899abdb1e76df50356826)`); }
|
|
268
|
-
updates$.next(requestCtx);
|
|
280
|
+
updates$.next(requestCtx); // spins off
|
|
281
|
+
// ...And send the context
|
|
269
282
|
const responseCtx = await peer.witness(requestCtx);
|
|
270
283
|
// C. Handle Response
|
|
271
284
|
if (!responseCtx) {
|
|
272
|
-
// Check if we just sent a Commit frame. If so, peer's silence is success/expected.
|
|
273
285
|
if (currentFrame) {
|
|
286
|
+
// Check for Commit (Peer silence expected)
|
|
274
287
|
const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
|
|
275
|
-
if (logalot) {
|
|
276
|
-
console.log(`${lc} Checking currentFrame stage: ${msg?.data?.stage} (Expected: ${SyncStage.commit})`);
|
|
277
|
-
}
|
|
278
288
|
if (msg?.data?.stage === SyncStage.commit) {
|
|
279
289
|
if (logalot) {
|
|
280
290
|
console.log(`${lc} Sender sent Commit. Peer returned no response. Saga Complete.`);
|
|
@@ -282,59 +292,79 @@ export class SyncSagaCoordinator {
|
|
|
282
292
|
currentFrame = null;
|
|
283
293
|
break;
|
|
284
294
|
}
|
|
295
|
+
else {
|
|
296
|
+
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)`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
|
|
285
301
|
}
|
|
286
|
-
throw new Error(`responseCtx falsy. Peer returned no response context (E: c099d8073b48d85e881f917835158f26)`);
|
|
287
|
-
// console.warn(`${lc} Peer returned no response context. Ending loop.`);
|
|
288
|
-
// currentFrame = null;
|
|
289
|
-
// break;
|
|
290
302
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
if (!
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
303
|
+
// ---------------------------------------------------------------------
|
|
304
|
+
// 2d. HANDLE RESPONSE
|
|
305
|
+
// ---------------------------------------------------------------------
|
|
306
|
+
// at this point, we have received the response context ibgib, but
|
|
307
|
+
// if there were payloads expected to be transferred, they may not
|
|
308
|
+
// be done yet.
|
|
309
|
+
if (!responseCtx.data) {
|
|
310
|
+
throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
|
|
311
|
+
}
|
|
312
|
+
updates$.next(responseCtx); // spins off -- don't remove this comment!
|
|
313
|
+
// Extract expected domain addresses from response context
|
|
314
|
+
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] || [];
|
|
315
|
+
// Poll for them if needed
|
|
316
|
+
if (responsePayloadAddrsDomain.length > 0) {
|
|
317
|
+
responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
|
|
318
|
+
expectedAddrs: responsePayloadAddrsDomain,
|
|
319
|
+
pollIntervalMs: 20, // relatively arbitrary right now
|
|
320
|
+
domainPayloadsMap,
|
|
321
|
+
tempSpace,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// at this point, we have received the context AND **all** of the
|
|
325
|
+
// domain payloads (if applicable), so we are ready to do the next
|
|
326
|
+
// iteration in the saga loop
|
|
327
|
+
// Log what we received back
|
|
328
|
+
if (!responseCtx.sagaFrame) {
|
|
329
|
+
throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`);
|
|
330
|
+
}
|
|
331
|
+
if (logalotControlDomain) {
|
|
332
|
+
const responseControlAddrs = responseCtx.data?.['@payloadAddrsControl'] || [];
|
|
333
|
+
console.log(`${lc}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: c4d5e6f7a8b9c0d1)`);
|
|
334
|
+
console.log(`${lc}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
|
|
335
|
+
console.log(`${lc}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
|
|
336
|
+
console.log(`${lc}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(', ') || '(none)'}`);
|
|
337
|
+
console.log(`${lc}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(', ') || '(none)'}`);
|
|
338
|
+
}
|
|
339
|
+
// Handle Response Frame
|
|
340
|
+
// interface HandleSagaFrameResult {
|
|
341
|
+
// frame: SyncIbGib_V1;
|
|
342
|
+
// payloadIbGibsDomain?: IbGib_V1[];
|
|
343
|
+
// conflictInfos?: SyncSagaConflictInfo;
|
|
344
|
+
// }
|
|
345
|
+
const handleResult = await this.handleSagaContext({
|
|
346
|
+
sagaContext: responseCtx,
|
|
347
|
+
mySpace: localSpace,
|
|
348
|
+
myTempSpace: tempSpace,
|
|
349
|
+
metaspace,
|
|
329
350
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
351
|
+
if (!handleResult) {
|
|
352
|
+
if (logalot) {
|
|
353
|
+
console.log(`${lc} Handler returned null (Saga End).`);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
currentFrame = handleResult.frame;
|
|
358
|
+
// Collect next DOMAIN payloads for the NEXT request
|
|
359
|
+
nextDomainIbGibs = [...(handleResult.payloadIbGibsDomain || [])];
|
|
360
|
+
// Log handler output for next iteration
|
|
361
|
+
if (logalotControlDomain) {
|
|
362
|
+
const handlerDomainAddrs = nextDomainIbGibs.map(p => getIbGibAddr({ ibGib: p }));
|
|
363
|
+
console.log(`${lc}${lcControlDomain} HANDLER RESULT -> next iteration (I: d5e6f7a8b9c0d1e2)`);
|
|
364
|
+
console.log(`${lc}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
|
|
365
|
+
console.log(`${lc}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(', ') || '(none)'}`);
|
|
335
366
|
}
|
|
336
367
|
}
|
|
337
|
-
return allReceivedIbGibs;
|
|
338
368
|
}
|
|
339
369
|
/**
|
|
340
370
|
* Helper to get Knowledge Vector for specific domain ibGibs or TJPs.
|
|
@@ -346,7 +376,7 @@ export class SyncSagaCoordinator {
|
|
|
346
376
|
if (logalot) {
|
|
347
377
|
console.log(`${lc} starting... (I: e184f8a7818666febfbbd2d841ed3826)`);
|
|
348
378
|
}
|
|
349
|
-
console.dir(space);
|
|
379
|
+
// console.dir(space);
|
|
350
380
|
if (!(domainIbGibs && domainIbGibs.length > 0) &&
|
|
351
381
|
!(tjpAddrs && tjpAddrs.length > 0)) {
|
|
352
382
|
throw new Error(`(UNEXPECTED) domainIbGibs and tjpAddrs falsy/empty? we need one or the other (E: f674285111c8648398cd79d8c08ec826)`);
|
|
@@ -361,23 +391,21 @@ export class SyncSagaCoordinator {
|
|
|
361
391
|
}
|
|
362
392
|
else if (domainIbGibs && domainIbGibs.length > 0) {
|
|
363
393
|
// Extract TJPs from domain Ibgibs
|
|
364
|
-
if (
|
|
365
|
-
console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`);
|
|
366
|
-
}
|
|
394
|
+
// if (false) { console.log(`${lc} domainIbGibs (${domainIbGibs.length}) provided. (I: a378995a0658af1f086ac1f297486c26)`); }
|
|
367
395
|
const { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs: domainIbGibs });
|
|
368
|
-
if (
|
|
396
|
+
if (false) {
|
|
369
397
|
console.log(`${lc}[TEST DEBUG] mapWithTjp_YesDna: ${JSON.stringify(mapWithTjp_YesDna)} (I: 287e22897148298e185712c8d50cfb26)`);
|
|
370
398
|
}
|
|
371
|
-
if (
|
|
399
|
+
if (false) {
|
|
372
400
|
console.log(`${lc}[TEST DEBUG] mapWithTjp_NoDna: ${JSON.stringify(mapWithTjp_NoDna)} (I: 1bdc62656294aed0f9df334647dc7326)`);
|
|
373
401
|
}
|
|
374
402
|
const allWithTjp = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
|
|
375
403
|
const timelineMap = getTimelinesGroupedByTjp({ ibGibs: allWithTjp });
|
|
376
|
-
if (
|
|
404
|
+
if (false) {
|
|
377
405
|
console.log(`${lc}[TEST DEBUG] timelineMap: ${JSON.stringify(timelineMap)} (I: 2cc04898e5f85179fb1ac7f827abc426)`);
|
|
378
406
|
}
|
|
379
407
|
tjps = Object.keys(timelineMap);
|
|
380
|
-
if (
|
|
408
|
+
if (false) {
|
|
381
409
|
console.log(`${lc}[TEST DEBUG] tjps: ${tjps} (I: 3dd548667cbd967c68e57c88dc570826)`);
|
|
382
410
|
}
|
|
383
411
|
}
|
|
@@ -390,16 +418,14 @@ export class SyncSagaCoordinator {
|
|
|
390
418
|
if (tjps.length === 0) {
|
|
391
419
|
return {};
|
|
392
420
|
}
|
|
393
|
-
if (
|
|
421
|
+
if (false) {
|
|
394
422
|
console.log(`${lc} getting latest addrs for tjps: ${tjps} (I: d4e7080b8ba8187c583b82fd91ac0626)`);
|
|
395
423
|
}
|
|
396
424
|
const res = await getLatestAddrs({ space, tjpAddrs: tjps });
|
|
397
425
|
if (!res.data || !res.data.latestAddrsMap) {
|
|
398
426
|
throw new Error(`${lc} Failed to get latest addrs. (E: 7a8b9c0d)`);
|
|
399
427
|
}
|
|
400
|
-
if (
|
|
401
|
-
console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`);
|
|
402
|
-
}
|
|
428
|
+
// if (false) { console.log(`${lc}[TEST DEBUG] res.data.latestAddrsMap: ${JSON.stringify(res.data.latestAddrsMap)} (I: a8e128bdf80898ac2e6d8021a5bff726)`); }
|
|
403
429
|
return res.data.latestAddrsMap;
|
|
404
430
|
}
|
|
405
431
|
catch (error) {
|
|
@@ -412,24 +438,27 @@ export class SyncSagaCoordinator {
|
|
|
412
438
|
}
|
|
413
439
|
}
|
|
414
440
|
}
|
|
415
|
-
async
|
|
416
|
-
const lc = `${this.lc}[${this.
|
|
417
|
-
|
|
441
|
+
async analyzeDomainIbGibs({ domainIbGibs, space, }) {
|
|
442
|
+
const lc = `${this.lc}[${this.analyzeDomainIbGibs.name}]`;
|
|
443
|
+
if (domainIbGibs.length === 0) {
|
|
444
|
+
throw new Error(`invalid domainIbGibs. expected at least one, but array is empty. (E: 820a719bcac5a16878a2af697113b826)`);
|
|
445
|
+
}
|
|
446
|
+
const fullGraph = await getDependencyGraph({
|
|
418
447
|
ibGibs: domainIbGibs,
|
|
419
448
|
live: true,
|
|
420
449
|
space,
|
|
421
450
|
});
|
|
422
|
-
const
|
|
451
|
+
const graphIsValid = fullGraph && Object.keys(fullGraph).length > 0;
|
|
423
452
|
if (logalot) {
|
|
424
|
-
console.log(`${lc} graph generated. nodes: ${
|
|
453
|
+
console.log(`${lc} graph generated. nodes: ${graphIsValid ? Object.keys(fullGraph).length : 0}`);
|
|
425
454
|
}
|
|
426
|
-
const srcIbGibs =
|
|
455
|
+
const srcIbGibs = graphIsValid ? Object.values(fullGraph) : [];
|
|
427
456
|
const { mapWithTjp_YesDna: srcMapWithTjp_YesDna, mapWithTjp_NoDna: srcMapWithTjp_NoDna, mapWithoutTjps: src_MapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs: srcIbGibs });
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
return {
|
|
457
|
+
const stones = Object.values(src_MapWithoutTjps);
|
|
458
|
+
const withTimelines = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
|
|
459
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: withTimelines });
|
|
460
|
+
const topologicallySortedTjpAddrs = this.sortTimelinesTopologically(timelinesMap);
|
|
461
|
+
return { stones, timelinesMap, topologicallySortedTjpAddrs, fullGraph };
|
|
433
462
|
}
|
|
434
463
|
/**
|
|
435
464
|
* Creates the Initial Saga Frame (Init Stage).
|
|
@@ -440,57 +469,54 @@ export class SyncSagaCoordinator {
|
|
|
440
469
|
* Generates the first frame containing the Knowledge Vector of the Local Space.
|
|
441
470
|
* This is sent to the Receiver to begin Gap Analysis.
|
|
442
471
|
*/
|
|
443
|
-
async createInitFrame({ sagaId, sessionIdentity,
|
|
472
|
+
async createInitFrame({ sagaId, sessionIdentity, domainIbGibs, conflictStrategy, metaspace, localSpace, tempSpace, }) {
|
|
444
473
|
const lc = `${this.lc}[${this.createInitFrame.name}]`;
|
|
445
474
|
try {
|
|
446
475
|
if (logalot) {
|
|
447
476
|
console.log(`${lc} starting... (I: 551af8b411ae9be712ce3358d43ee726)`);
|
|
448
477
|
}
|
|
449
478
|
// Analyze Timelines & Stones
|
|
450
|
-
const analysis = await this.
|
|
451
|
-
// this is a lot, so uncomment this only if you want even more logging specific to this analysis
|
|
479
|
+
const analysis = await this.analyzeDomainIbGibs({ domainIbGibs, space: localSpace });
|
|
452
480
|
// if (logalot) { console.log(`${lc} analysis: ${pretty(analysis)}(I: cd00e2be5eccc8976879c888ff2dfb26)`); }
|
|
453
|
-
const { srcTimelinesMap,
|
|
481
|
+
const { timelinesMap: srcTimelinesMap, fullGraph, stones: srcStones, } = analysis;
|
|
482
|
+
// we need to store the fullGraph in our tempSpace for later...
|
|
483
|
+
await putInSpace({ ibGibs: Object.values(fullGraph), space: tempSpace });
|
|
484
|
+
// populate our knowledge vector with tjp -> latest addr/tip in timeline
|
|
485
|
+
const knowledgeVector = {};
|
|
486
|
+
Object.keys(srcTimelinesMap).forEach(tjp => {
|
|
487
|
+
const timeline = srcTimelinesMap[tjp];
|
|
488
|
+
const tip = timeline.at(-1);
|
|
489
|
+
knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
490
|
+
});
|
|
454
491
|
const initData = {
|
|
455
492
|
sagaId,
|
|
456
493
|
stage: SyncStage.init,
|
|
457
|
-
knowledgeVector
|
|
494
|
+
knowledgeVector,
|
|
458
495
|
identity: sessionIdentity, // KeystoneIbGib is already public data
|
|
459
496
|
mode: SyncMode.sync,
|
|
460
497
|
stones: srcStones.map(s => getIbGibAddr({ ibGib: s })),
|
|
461
498
|
};
|
|
462
|
-
// Populate Knowledge Vector
|
|
463
|
-
Object.keys(srcTimelinesMap).forEach(tjp => {
|
|
464
|
-
const timeline = srcTimelinesMap[tjp];
|
|
465
|
-
const tip = timeline.at(-1);
|
|
466
|
-
initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
467
|
-
});
|
|
468
499
|
if (logalot) {
|
|
469
500
|
console.log(`${lc} SyncStage.init: ${SyncStage.init}, SyncStage.commit: ${SyncStage.commit}`);
|
|
470
501
|
console.log(`${lc} initData.stage: ${initData.stage}`);
|
|
471
502
|
}
|
|
503
|
+
// create the stone and save/register it
|
|
472
504
|
const initStone = await this.createSyncMsgStone({
|
|
473
505
|
data: initData,
|
|
474
|
-
|
|
475
|
-
metaspace
|
|
506
|
+
localSpace,
|
|
507
|
+
metaspace,
|
|
476
508
|
});
|
|
477
509
|
// if (logalot) { console.log(`${lc} initStone: ${pretty(initStone)} (I: 06e532f8a408549069474e96bed44826)`); }
|
|
510
|
+
// create the initial sync ibgib frame, save/register it
|
|
478
511
|
const sagaFrame = await this.evolveSyncSagaIbGib({
|
|
479
512
|
msgStones: [initStone],
|
|
480
|
-
identity: sessionIdentity,
|
|
481
|
-
space: tempSpace,
|
|
482
|
-
metaspace,
|
|
483
513
|
conflictStrategy,
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
frame: sagaFrame,
|
|
488
|
-
destSpace: localSpace, // localSpace is the Sender's destSpace
|
|
489
|
-
tempSpace,
|
|
490
|
-
metaspace
|
|
514
|
+
sessionIdentity: sessionIdentity,
|
|
515
|
+
metaspace,
|
|
516
|
+
localSpace,
|
|
491
517
|
});
|
|
492
518
|
// if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
493
|
-
return { sagaFrame,
|
|
519
|
+
return { sagaFrame, initialDomainGraph: fullGraph };
|
|
494
520
|
}
|
|
495
521
|
catch (error) {
|
|
496
522
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
@@ -503,24 +529,90 @@ export class SyncSagaCoordinator {
|
|
|
503
529
|
}
|
|
504
530
|
}
|
|
505
531
|
/**
|
|
506
|
-
*
|
|
532
|
+
* Helper to poll for streaming domain payloads and put them in the
|
|
533
|
+
* local {@link tempSpace}.
|
|
507
534
|
*
|
|
508
|
-
* @
|
|
509
|
-
|
|
535
|
+
* @returns when all {@link expectedAddrs} are done being transmitted.
|
|
536
|
+
*/
|
|
537
|
+
async pollForDomainPayloads({ expectedAddrs, pollIntervalMs, domainPayloadsMap, tempSpace, }) {
|
|
538
|
+
const lc = `${this.lc}[${this.pollForDomainPayloads.name}]`;
|
|
539
|
+
try {
|
|
540
|
+
if (logalot) {
|
|
541
|
+
console.log(`${lc} starting... (I: 26dce86bfca572939885798802d6e926)`);
|
|
542
|
+
}
|
|
543
|
+
let resultDomainPayloads = [];
|
|
544
|
+
let pending = [...expectedAddrs];
|
|
545
|
+
const start = Date.now();
|
|
546
|
+
/**
|
|
547
|
+
* This needs
|
|
548
|
+
*/
|
|
549
|
+
const timeoutMs = 5 * 60 * 1000; // 5 minutes...arbitrary at this point. This needs to be pulled out and improved eesh.
|
|
550
|
+
while (pending.length > 0) {
|
|
551
|
+
if (Date.now() - start > timeoutMs) {
|
|
552
|
+
throw new Error(`Timeout waiting for payloads: ${pending.join(', ')} (E: 46e1683c9578095261aaf798bd5e1826)`);
|
|
553
|
+
}
|
|
554
|
+
const stillPending = [];
|
|
555
|
+
const found = [];
|
|
556
|
+
for (const addr of pending) {
|
|
557
|
+
if (domainPayloadsMap.has(addr)) {
|
|
558
|
+
found.push(domainPayloadsMap.get(addr));
|
|
559
|
+
domainPayloadsMap.delete(addr);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
stillPending.push(addr);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (found.length > 0) {
|
|
566
|
+
await putInSpace({ space: tempSpace, ibGibs: found });
|
|
567
|
+
found.forEach(x => resultDomainPayloads.push(x));
|
|
568
|
+
}
|
|
569
|
+
pending = stillPending;
|
|
570
|
+
if (pending.length > 0) {
|
|
571
|
+
await delay(pollIntervalMs);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (expectedAddrs.length !== resultDomainPayloads.length) {
|
|
575
|
+
throw new Error(`(UNEXPECTED) expectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
|
|
576
|
+
}
|
|
577
|
+
return resultDomainPayloads;
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
if (logalot) {
|
|
585
|
+
console.log(`${lc} complete.`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* This is the heart of the "ping pong" transaction, where we send a context
|
|
591
|
+
* and receive a context. IOW, this drives the FSM of the sync saga ibgib as
|
|
592
|
+
* a whole.
|
|
593
|
+
*
|
|
594
|
+
* This is called in two places:
|
|
595
|
+
*
|
|
596
|
+
* ## 1. Sender
|
|
597
|
+
*
|
|
598
|
+
* On the sender, this is called within the {@link executeSagaLoop} which
|
|
599
|
+
* initiates and drives the sync.
|
|
600
|
+
*
|
|
601
|
+
* ## 2. Receiver
|
|
510
602
|
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
603
|
+
* On the receiver, this is called directly by the receiving endpoint. That
|
|
604
|
+
* endpoint's job is basically to get these things collocated and prepared
|
|
605
|
+
* to make this call.
|
|
513
606
|
*
|
|
514
|
-
*
|
|
515
|
-
* * If running on **Sender**: Handles `Ack` (via `handleAckFrame`).
|
|
516
|
-
* * If running on **Either**: Handles `Delta` (via `handleDeltaFrame`) or `Commit`.
|
|
607
|
+
* This is a one-off on the receiver.
|
|
517
608
|
*/
|
|
518
|
-
async
|
|
519
|
-
const lc = `${this.lc}[${this.
|
|
609
|
+
async handleSagaContext({ sagaContext, mySpace, myTempSpace, identity, identitySecret, metaspace, }) {
|
|
610
|
+
const lc = `${this.lc}[${this.handleSagaContext.name}]`;
|
|
520
611
|
try {
|
|
521
612
|
if (logalot) {
|
|
522
613
|
console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`);
|
|
523
614
|
}
|
|
615
|
+
const sagaIbGib = sagaContext.sagaFrame;
|
|
524
616
|
if (!sagaIbGib.data) {
|
|
525
617
|
throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`);
|
|
526
618
|
}
|
|
@@ -528,19 +620,31 @@ export class SyncSagaCoordinator {
|
|
|
528
620
|
console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`);
|
|
529
621
|
}
|
|
530
622
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
531
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({
|
|
623
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: myTempSpace });
|
|
532
624
|
if (logalot) {
|
|
533
625
|
console.log(`${lc} handling frame stage: ${stage}`);
|
|
534
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* don't like this name, need to refactor
|
|
629
|
+
*/
|
|
630
|
+
const srcGraph = toFlatGraph({ ibGibs: sagaContext.payloadIbGibsDomain }) ?? {};
|
|
535
631
|
switch (stage) {
|
|
536
632
|
case SyncStage.init:
|
|
537
|
-
return await this.handleInitFrame({
|
|
633
|
+
return await this.handleInitFrame({
|
|
634
|
+
sagaIbGib,
|
|
635
|
+
messageData: messageData,
|
|
636
|
+
metaspace,
|
|
637
|
+
mySpace: mySpace,
|
|
638
|
+
myTempSpace: myTempSpace,
|
|
639
|
+
identity,
|
|
640
|
+
identitySecret
|
|
641
|
+
});
|
|
538
642
|
case SyncStage.ack:
|
|
539
|
-
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity });
|
|
643
|
+
return await this.handleAckFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity });
|
|
540
644
|
case SyncStage.delta:
|
|
541
|
-
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace, tempSpace, identity, });
|
|
645
|
+
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
542
646
|
case SyncStage.commit:
|
|
543
|
-
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace, tempSpace, identity, });
|
|
647
|
+
return await this.handleCommitFrame({ sagaIbGib, metaspace, destSpace: mySpace, tempSpace: myTempSpace, identity, });
|
|
544
648
|
default:
|
|
545
649
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
546
650
|
}
|
|
@@ -568,277 +672,278 @@ export class SyncSagaCoordinator {
|
|
|
568
672
|
* 3. Identifies what Receiver needs (`deltaReqAddrs`).
|
|
569
673
|
* 4. Returns an `Ack` frame containing these lists.
|
|
570
674
|
*/
|
|
571
|
-
async handleInitFrame({ sagaIbGib, messageData,
|
|
675
|
+
async handleInitFrame({ sagaIbGib, messageData, mySpace, myTempSpace, metaspace, identity,
|
|
676
|
+
// identitySecret,
|
|
677
|
+
}) {
|
|
572
678
|
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
573
|
-
|
|
574
|
-
if (logalot) {
|
|
575
|
-
console.log(`${lc} starting...`);
|
|
576
|
-
}
|
|
577
|
-
// Extract Init Data
|
|
578
|
-
const initData = messageData; // Using renamed variable for clarity
|
|
579
|
-
if (initData.stage !== SyncStage.init) {
|
|
580
|
-
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
|
|
581
|
-
}
|
|
582
|
-
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
583
|
-
if (!initData || !initData.knowledgeVector) {
|
|
584
|
-
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
585
|
-
}
|
|
586
|
-
// Determine Strategy from Saga Data (since V1 stores it in root)
|
|
587
|
-
const conflictStrategy = sagaIbGib.data.conflictStrategy || 'abort';
|
|
588
|
-
// 2. Gap Analysis
|
|
589
|
-
const conflicts = [];
|
|
590
|
-
const deltaReqAddrs = [];
|
|
591
|
-
const pushOfferAddrs = [];
|
|
592
|
-
// Stones Analysis (Constants / Non-TJPs)
|
|
593
|
-
const stones = initData.stones || [];
|
|
594
|
-
if (stones.length > 0) {
|
|
679
|
+
try {
|
|
595
680
|
if (logalot) {
|
|
596
|
-
console.log(`${lc}
|
|
681
|
+
console.log(`${lc} starting... (I: 9d88dcad0408c029e898a4bcf3b08426)`);
|
|
597
682
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
683
|
+
console.log(`${lc} [TEST DEBUG] Received destSpace: ${mySpace.data?.name || mySpace.ib} (uuid: ${mySpace.data?.uuid || '[no uuid]'})`);
|
|
684
|
+
if (logalot) {
|
|
685
|
+
console.log(`${lc} starting...`);
|
|
686
|
+
}
|
|
687
|
+
// Extract Init Data
|
|
688
|
+
const initData = messageData; // Using renamed variable for clarity
|
|
689
|
+
if (initData.stage !== SyncStage.init) {
|
|
690
|
+
throw new Error(`${lc} Invalid init frame: initData.stage !== SyncStage.init (E: 8a2b3c4d5e6f7g8h)`);
|
|
691
|
+
}
|
|
692
|
+
// if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
693
|
+
if (!initData || !initData.knowledgeVector) {
|
|
694
|
+
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
695
|
+
}
|
|
696
|
+
// Determine Strategy from Saga Data (since V1 stores it in root)
|
|
697
|
+
const conflictStrategy = sagaIbGib.data.conflictStrategy || SyncConflictStrategy.abort;
|
|
698
|
+
// 2. Gap Analysis
|
|
699
|
+
const conflicts = [];
|
|
700
|
+
const deltaReqAddrs = [];
|
|
701
|
+
const pushOfferAddrs = [];
|
|
702
|
+
// First do the consant stones (Non-TJPs)
|
|
703
|
+
const stones = initData.stones || [];
|
|
704
|
+
if (stones.length > 0) {
|
|
602
705
|
if (logalot) {
|
|
603
|
-
console.log(`${lc} stones
|
|
706
|
+
console.log(`${lc} processing stones: ${stones.length}`);
|
|
707
|
+
}
|
|
708
|
+
// Check if we have these stones
|
|
709
|
+
const resStones = await getFromSpace({ space: mySpace, addrs: stones });
|
|
710
|
+
const rawResultIbGib = resStones.rawResultIbGib;
|
|
711
|
+
if (!rawResultIbGib.data) {
|
|
712
|
+
throw new Error(`(UNEXPECTED) rawResultIbGib.data falsy? (E: f3c40b492adc02c3f480b998fc7a5926)`);
|
|
604
713
|
}
|
|
605
|
-
addrsNotFound
|
|
606
|
-
|
|
607
|
-
|
|
714
|
+
const addrsNotFound = rawResultIbGib.data.addrsNotFound;
|
|
715
|
+
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
716
|
+
if (logalot) {
|
|
717
|
+
console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`);
|
|
608
718
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
const remoteTjps = Object.keys(remoteKV);
|
|
617
|
-
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
618
|
-
if (logalot) {
|
|
619
|
-
console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
|
|
620
|
-
}
|
|
621
|
-
// 1. Get Local Latest Addrs for all TJPs
|
|
622
|
-
let localKV = {};
|
|
623
|
-
if (remoteTjps.length > 0) {
|
|
624
|
-
// Batch get latest addrs for the TJPs
|
|
625
|
-
const resGetLatestAddrs = await getLatestAddrs({
|
|
626
|
-
space: destSpace,
|
|
627
|
-
tjpAddrs: remoteTjps,
|
|
628
|
-
});
|
|
629
|
-
if (!resGetLatestAddrs.data) {
|
|
630
|
-
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`);
|
|
631
|
-
}
|
|
632
|
-
if (!resGetLatestAddrs.data.latestAddrsMap) {
|
|
633
|
-
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
|
|
719
|
+
addrsNotFound.forEach(addr => {
|
|
720
|
+
if (!deltaReqAddrs.includes(addr)) {
|
|
721
|
+
deltaReqAddrs.push(addr);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
634
725
|
}
|
|
635
|
-
|
|
636
|
-
|
|
726
|
+
/**
|
|
727
|
+
* "remote" local to receiver's context is the sender
|
|
728
|
+
*/
|
|
729
|
+
const remoteKV = initData.knowledgeVector;
|
|
637
730
|
if (logalot) {
|
|
638
|
-
console.log(`${lc}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
// 2. Gap Analysis
|
|
642
|
-
for (const tjp of remoteTjps) {
|
|
643
|
-
const remoteAddr = remoteKV[tjp];
|
|
644
|
-
const localAddr = localKV[tjp];
|
|
645
|
-
if (!localAddr) {
|
|
646
|
-
// We (Receiver) don't have this timeline. Request it.
|
|
647
|
-
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
648
|
-
deltaReqAddrs.push(remoteAddr);
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
if (localAddr === remoteAddr) {
|
|
652
|
-
// Synced
|
|
653
|
-
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
657
|
-
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
658
|
-
// (Sender has older version, Receiver has newer) -> Receiver Offers Push
|
|
659
|
-
const isRemoteInPast = await isPastFrame({
|
|
660
|
-
olderAddr: remoteAddr,
|
|
661
|
-
newerAddr: localAddr,
|
|
662
|
-
space: destSpace,
|
|
663
|
-
});
|
|
664
|
-
if (isRemoteInPast) {
|
|
665
|
-
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
666
|
-
pushOfferAddrs.push(localAddr);
|
|
731
|
+
console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`);
|
|
667
732
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
733
|
+
const remoteTjps = Object.keys(remoteKV);
|
|
734
|
+
console.log(`${lc} [TEST DEBUG] remoteTjps: ${JSON.stringify(remoteTjps)}`);
|
|
735
|
+
if (logalot) {
|
|
736
|
+
console.log(`${lc} remoteTjps: ${pretty(remoteTjps)} (I: 86ea4c53db0dc184c8b253386c402126)`);
|
|
737
|
+
}
|
|
738
|
+
// 1. Get Local Latest Addrs for all TJPs
|
|
739
|
+
let localKV = {};
|
|
740
|
+
if (remoteTjps.length > 0) {
|
|
741
|
+
// Batch get latest addrs for the TJPs
|
|
742
|
+
const resGetLatestAddrs = await getLatestAddrs({
|
|
743
|
+
space: mySpace, // executing on receiver, so this is receiver's space
|
|
744
|
+
tjpAddrs: remoteTjps,
|
|
676
745
|
});
|
|
677
|
-
if (
|
|
678
|
-
|
|
679
|
-
|
|
746
|
+
if (!resGetLatestAddrs.data) {
|
|
747
|
+
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: b180d813c088042b38e1e02e06a16926)`);
|
|
748
|
+
}
|
|
749
|
+
if (!resGetLatestAddrs.data.latestAddrsMap) {
|
|
750
|
+
throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 16bc386dd51d0ff53a49620b1e641826)`);
|
|
751
|
+
}
|
|
752
|
+
localKV = resGetLatestAddrs.data.latestAddrsMap;
|
|
753
|
+
console.log(`${lc} [TEST DEBUG] localKV: ${JSON.stringify(localKV)}`);
|
|
754
|
+
if (logalot) {
|
|
755
|
+
console.log(`${lc} localKV: ${pretty(localKV)} (I: 980975642cbccd8018cf0cd808d30826)`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
// 2. Gap Analysis
|
|
759
|
+
for (const tjp of remoteTjps) {
|
|
760
|
+
const remoteAddr = remoteKV[tjp];
|
|
761
|
+
const localAddr = localKV[tjp];
|
|
762
|
+
if (!localAddr) {
|
|
763
|
+
// We (Receiver) don't have this timeline at all. Request it.
|
|
764
|
+
console.log(`${lc} [TEST DEBUG] Missing local timeline for TJP: ${tjp}. Requesting remoteAddr: ${remoteAddr}`);
|
|
680
765
|
deltaReqAddrs.push(remoteAddr);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (localAddr === remoteAddr) {
|
|
769
|
+
// Synced
|
|
770
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Synced (localAddr === remoteAddr)`);
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: localAddr=${localAddr}, remoteAddr=${remoteAddr} - checking for divergence...`);
|
|
774
|
+
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
775
|
+
// (Sender has older version, Receiver has newer) -> Receiver Offers Push
|
|
776
|
+
const isRemoteInPast = await isPastFrame({
|
|
777
|
+
olderAddr: remoteAddr,
|
|
778
|
+
newerAddr: localAddr,
|
|
779
|
+
space: mySpace,
|
|
780
|
+
});
|
|
781
|
+
if (isRemoteInPast) {
|
|
782
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Remote is in past - offering push`);
|
|
783
|
+
pushOfferAddrs.push(localAddr);
|
|
681
784
|
}
|
|
682
785
|
else {
|
|
683
|
-
//
|
|
684
|
-
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
reason: 'divergence',
|
|
696
|
-
terminal: true
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
else if (conflictStrategy === 'optimistic') {
|
|
700
|
-
// Optimistic: We want to resolving this.
|
|
701
|
-
// We need to send our history to the Sender so they can Merge.
|
|
702
|
-
// Fetch Full History for Local Timeline
|
|
703
|
-
// Note: We might optimize this to only send "recent" history if we had a KV?
|
|
704
|
-
// But for now, get full past.
|
|
705
|
-
// Optimization: localKV might not have full history.
|
|
706
|
-
// We need to inspect the 'past' of the local tip.
|
|
707
|
-
// We need the ACTUAL object to get the past.
|
|
708
|
-
// We have localAddr.
|
|
709
|
-
const resLocalTip = await getFromSpace({ space: destSpace, addr: localAddr });
|
|
710
|
-
const localTip = resLocalTip.ibGibs?.[0];
|
|
711
|
-
if (!localTip) {
|
|
712
|
-
throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
|
|
713
|
-
}
|
|
714
|
-
const timelineAddrs = [localAddr, ...(localTip.rel8ns?.past || [])];
|
|
715
|
-
conflicts.push({
|
|
716
|
-
tjpAddr: tjp,
|
|
717
|
-
localAddr: localAddr,
|
|
718
|
-
remoteAddr,
|
|
719
|
-
timelineAddrs,
|
|
720
|
-
reason: 'divergence',
|
|
721
|
-
terminal: false
|
|
722
|
-
});
|
|
786
|
+
// Remote is not in our past.
|
|
787
|
+
// Either Remote is ahead (Fast-Backward) OR Diverged.
|
|
788
|
+
// Check if Local is in Remote's PAST (Remote is Ahead -> Delta Request)
|
|
789
|
+
const isLocalInPast = await isPastFrame({
|
|
790
|
+
olderAddr: localAddr,
|
|
791
|
+
newerAddr: remoteAddr,
|
|
792
|
+
space: mySpace,
|
|
793
|
+
});
|
|
794
|
+
if (isLocalInPast) {
|
|
795
|
+
// Fast-Forward: We update to remote's tip.
|
|
796
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: Local is in past - requesting delta`);
|
|
797
|
+
deltaReqAddrs.push(remoteAddr);
|
|
723
798
|
}
|
|
724
799
|
else {
|
|
725
|
-
|
|
800
|
+
// DIVERGENCE: Both have changes the other doesn't know about.
|
|
801
|
+
console.log(`${lc} [TEST DEBUG] TJP ${tjp}: DIVERGENCE DETECTED! conflictStrategy=${conflictStrategy}`);
|
|
802
|
+
if (conflictStrategy === 'abort') {
|
|
803
|
+
// Abort Strategy: We will treat this as terminal.
|
|
804
|
+
// But for Unified Ack, we just mark it terminal in the list?
|
|
805
|
+
// Or do we actually throw/abort the saga?
|
|
806
|
+
// Current logic (below) aborts the saga if ANY conflict is terminal/abort.
|
|
807
|
+
conflicts.push({
|
|
808
|
+
tjpAddr: tjp,
|
|
809
|
+
localAddr: localAddr,
|
|
810
|
+
remoteAddr,
|
|
811
|
+
timelineAddrs: [], // Not needed for abort
|
|
812
|
+
reason: 'divergence',
|
|
813
|
+
terminal: true
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
else if (conflictStrategy === 'optimistic') {
|
|
817
|
+
// Optimistic: We want to resolving this.
|
|
818
|
+
// We need to send our history to the Sender so they can Merge.
|
|
819
|
+
// Fetch Full History for Local Timeline
|
|
820
|
+
// Note: We might optimize this to only send "recent" history if we had a KV?
|
|
821
|
+
// But for now, get full past.
|
|
822
|
+
// Optimization: localKV might not have full history.
|
|
823
|
+
// We need to inspect the 'past' of the local tip.
|
|
824
|
+
// We need the ACTUAL object to get the past.
|
|
825
|
+
// We have localAddr.
|
|
826
|
+
const resLocalTip = await getFromSpace({ space: mySpace, addr: localAddr });
|
|
827
|
+
if (!resLocalTip.success || resLocalTip.ibGibs?.length !== 1) {
|
|
828
|
+
throw new Error(`couldn't get local tip (${localAddr}) from space (${mySpace.ib}) (E: ff06ff849fa8e8dba32ce09807411226)`);
|
|
829
|
+
}
|
|
830
|
+
const localTip = resLocalTip.ibGibs[0];
|
|
831
|
+
if (!localTip) {
|
|
832
|
+
throw new Error(`${lc} Failed to load local tip for conflict resolution. (E: 8f9b2c3d4e5f6g7h)`);
|
|
833
|
+
}
|
|
834
|
+
const timelineAddrs = [...(localTip.rel8ns?.past ?? []), localAddr];
|
|
835
|
+
conflicts.push({
|
|
836
|
+
tjpAddr: tjp,
|
|
837
|
+
localAddr,
|
|
838
|
+
remoteAddr,
|
|
839
|
+
timelineAddrs,
|
|
840
|
+
reason: 'divergence',
|
|
841
|
+
terminal: false
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
throw new Error(`${lc} Unsupported conflict strategy: ${conflictStrategy} (E: 2a9b3c4d5e6f7g8h9i0j)`);
|
|
846
|
+
}
|
|
726
847
|
}
|
|
727
848
|
}
|
|
728
849
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
//
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
// 3. Build Knowledge Vector (Full History for known timelines)
|
|
759
|
-
// [NEW] Smart Diff
|
|
760
|
-
// We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
|
|
761
|
-
// Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
|
|
762
|
-
const knowledgeVector = {};
|
|
763
|
-
const relevantAddrs = new Set([...pushOfferAddrs, ...deltaReqAddrs]);
|
|
764
|
-
// [Smart Diff] Populate knowledge from timelines identified by Sender
|
|
765
|
-
for (const tjp of remoteTjps) {
|
|
766
|
-
const localAddr = localKV[tjp];
|
|
767
|
-
if (localAddr) {
|
|
768
|
-
const res = await getFromSpace({ addr: localAddr, space: destSpace });
|
|
769
|
-
if (res.success && res.ibGibs?.[0]) {
|
|
770
|
-
const ibGib = res.ibGibs[0];
|
|
771
|
-
const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
|
|
772
|
-
if (!knowledgeVector[realTjp]) {
|
|
773
|
-
const past = ibGib.rel8ns?.past || [];
|
|
774
|
-
knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
|
|
850
|
+
// Check if we should ABORT (if any conflict is terminal)
|
|
851
|
+
const hasTerminalConflicts = conflicts.some(c => c.terminal);
|
|
852
|
+
if (hasTerminalConflicts) {
|
|
853
|
+
// Abort Strategy: Kill the saga.
|
|
854
|
+
if (logalot) {
|
|
855
|
+
console.warn(`${lc} ABORTING Sync Saga due to terminal conflicts: ${JSON.stringify(conflicts)}`);
|
|
856
|
+
}
|
|
857
|
+
throw new Error(`the saga has terminal conflicts. conflicts: ${JSON.stringify(conflicts)} (E: f2edbe93cc07a63b38bfc013d2213b26)`);
|
|
858
|
+
}
|
|
859
|
+
// 3. Build Knowledge Vector (Full History for known timelines)
|
|
860
|
+
// [NEW] Smart Diff
|
|
861
|
+
// We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
|
|
862
|
+
// Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
|
|
863
|
+
/**
|
|
864
|
+
* we will put this in {@link SyncSagaMessageAckData_V1.knowledgeVector}
|
|
865
|
+
*/
|
|
866
|
+
const knowledgeVector = {};
|
|
867
|
+
// [Smart Diff] Populate knowledge from timelines identified by Sender
|
|
868
|
+
for (const tjp of remoteTjps) {
|
|
869
|
+
const localAddr = localKV[tjp];
|
|
870
|
+
if (localAddr) {
|
|
871
|
+
const res = await getFromSpace({ addr: localAddr, space: mySpace });
|
|
872
|
+
if (res.success && res.ibGibs?.[0]) {
|
|
873
|
+
const ibGib = res.ibGibs[0];
|
|
874
|
+
const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
|
|
875
|
+
if (!knowledgeVector[realTjp]) {
|
|
876
|
+
const past = ibGib.rel8ns?.past || [];
|
|
877
|
+
knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
|
|
878
|
+
}
|
|
775
879
|
}
|
|
776
880
|
}
|
|
777
881
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
882
|
+
// Also populate from `knowledgeVector` in Init if we want bidirectional?
|
|
883
|
+
// No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
|
|
884
|
+
// Let's assume standard flow:
|
|
885
|
+
// 1. Sender says "I have X"
|
|
886
|
+
// 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
|
|
887
|
+
// Problem: Receiver doesn't know X is related to Y without X.
|
|
888
|
+
// SOLUTION:
|
|
889
|
+
// The *Sender* must include TJP mappings or we rely on `knowledgeVector` in `Init`?
|
|
890
|
+
// `SyncSagaMessageInitData_V1` extends `SyncInitData`.
|
|
891
|
+
// `SyncInitData` has `knowledgeVector`.
|
|
892
|
+
// If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
|
|
893
|
+
// Let's implement Sender populating `Init.knowledgeVector`.
|
|
894
|
+
// But `SyncSagaCoordinator.startSaga` creates Init.
|
|
895
|
+
// 3. Create Ack Frame
|
|
896
|
+
const sagaId = sagaIbGib.data.uuid;
|
|
897
|
+
// Create Payload Stone
|
|
898
|
+
const ackData = {
|
|
899
|
+
sagaId,
|
|
900
|
+
stage: SyncStage.ack,
|
|
901
|
+
deltaReqAddrs,
|
|
902
|
+
pushOfferAddrs,
|
|
903
|
+
knowledgeVector,
|
|
904
|
+
};
|
|
905
|
+
if (conflicts?.length > 0) {
|
|
906
|
+
ackData.conflicts = conflicts;
|
|
793
907
|
}
|
|
794
|
-
|
|
795
|
-
|
|
908
|
+
const ackStone = await this.createSyncMsgStone({
|
|
909
|
+
data: ackData,
|
|
910
|
+
localSpace: myTempSpace,
|
|
911
|
+
metaspace,
|
|
912
|
+
});
|
|
913
|
+
if (logalot) {
|
|
914
|
+
console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`);
|
|
796
915
|
}
|
|
916
|
+
// Evolve Saga
|
|
917
|
+
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
918
|
+
prevSagaIbGib: sagaIbGib,
|
|
919
|
+
msgStones: [ackStone],
|
|
920
|
+
sessionIdentity: identity,
|
|
921
|
+
localSpace: mySpace,
|
|
922
|
+
metaspace,
|
|
923
|
+
});
|
|
924
|
+
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
925
|
+
/**
|
|
926
|
+
* we want to push ibgibs to the remote/sender if we have push
|
|
927
|
+
* offers. an ack frame's payloads, if any, are those push offers
|
|
928
|
+
*/
|
|
929
|
+
// let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
|
|
930
|
+
if (pushOfferAddrs.length > 0) {
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
frame: ackFrame,
|
|
934
|
+
// conflictInfos,
|
|
935
|
+
// payloadIbGibsDomain,
|
|
936
|
+
};
|
|
797
937
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
// `SyncSagaMessageInitData_V1` extends `SyncInitData`.
|
|
807
|
-
// `SyncInitData` has `knowledgeVector`.
|
|
808
|
-
// If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
|
|
809
|
-
// Let's implement Sender populating `Init.knowledgeVector`.
|
|
810
|
-
// But `SyncSagaCoordinator.startSaga` creates Init.
|
|
811
|
-
// 3. Create Ack Frame
|
|
812
|
-
const sagaId = sagaIbGib.data.uuid;
|
|
813
|
-
// Create Payload Stone
|
|
814
|
-
const ackData = {
|
|
815
|
-
sagaId,
|
|
816
|
-
stage: SyncStage.ack,
|
|
817
|
-
deltaReqAddrs,
|
|
818
|
-
pushOfferAddrs,
|
|
819
|
-
knowledgeVector,
|
|
820
|
-
conflicts: conflicts.length > 0 ? conflicts : undefined, // Include conflicts if any detected
|
|
821
|
-
};
|
|
822
|
-
const ackStone = await this.createSyncMsgStone({
|
|
823
|
-
data: ackData,
|
|
824
|
-
space: tempSpace,
|
|
825
|
-
metaspace,
|
|
826
|
-
});
|
|
827
|
-
if (logalot) {
|
|
828
|
-
console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`);
|
|
938
|
+
catch (error) {
|
|
939
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
finally {
|
|
943
|
+
if (logalot) {
|
|
944
|
+
console.log(`${lc} complete.`);
|
|
945
|
+
}
|
|
829
946
|
}
|
|
830
|
-
// Evolve Saga
|
|
831
|
-
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
832
|
-
prevSagaIbGib: sagaIbGib,
|
|
833
|
-
msgStones: [ackStone],
|
|
834
|
-
identity,
|
|
835
|
-
space: tempSpace,
|
|
836
|
-
metaspace,
|
|
837
|
-
});
|
|
838
|
-
// IMMEDIATELY persist to both spaces for audit trail (before any errors can occur)
|
|
839
|
-
await this.ensureSagaFrameInBothSpaces({ frame: ackFrame, destSpace, tempSpace, metaspace });
|
|
840
|
-
// if (logalot) { console.log(`${lc} ackFrame created: ${pretty(ackFrame)} (I: be24480592eec478086bb3da49286826)`); }
|
|
841
|
-
return { frame: ackFrame };
|
|
842
947
|
}
|
|
843
948
|
/**
|
|
844
949
|
* Handles the `Ack` frame.
|
|
@@ -858,7 +963,7 @@ export class SyncSagaCoordinator {
|
|
|
858
963
|
if (logalot) {
|
|
859
964
|
console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`);
|
|
860
965
|
}
|
|
861
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({
|
|
966
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
862
967
|
const ackData = messageData;
|
|
863
968
|
if (!ackData) {
|
|
864
969
|
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
@@ -938,50 +1043,7 @@ export class SyncSagaCoordinator {
|
|
|
938
1043
|
// PULL these frames from Peer into Local Space
|
|
939
1044
|
// (Validation: We trust peer for now / verification happens on put)
|
|
940
1045
|
for (const addr of receiverOnlyAddrs) {
|
|
941
|
-
|
|
942
|
-
// The Coordinator 'peer' passed in 'sync()' might be needed here?
|
|
943
|
-
// Wait, `handleAckFrame` doesn't have reference to `peer`?
|
|
944
|
-
// It only has `space`, `metaspace`.
|
|
945
|
-
// The `peer` is held by the `executeSagaLoop`.
|
|
946
|
-
// PROBLEM: `handleAckFrame` is pure logic on the Space/Data?
|
|
947
|
-
// No, it's a method on Coordinator.
|
|
948
|
-
// But `executeSagaLoop` calls it.
|
|
949
|
-
// We might need to return "Requirements" to the loop?
|
|
950
|
-
// Checking return type: `{ frame: SyncIbGib_V1, payloadIbGibs?: ... }`
|
|
951
|
-
// It returns the NEXT frame (Delta).
|
|
952
|
-
// If we need to fetch data, we are blocked.
|
|
953
|
-
// We can't easily "Pull" here without the Peer reference.
|
|
954
|
-
// OPTION A: Pass `peer` to `handleAckFrame`.
|
|
955
|
-
// OPTION B: Return a strict list of "MissingDeps" and let Loop handle it.
|
|
956
|
-
// Let's assume we can resolve this by adding `peer` to signature or using `metaspace` if it's a peer-witness?
|
|
957
|
-
// No, Peer is ephemeral connection.
|
|
958
|
-
// Let's add `peer` to `handleAckFrame` signature?
|
|
959
|
-
// It breaks the pattern of just handling frame + space.
|
|
960
|
-
// ALTERNATIVE: Use the `Delta` frame to request data?
|
|
961
|
-
// `SyncSagaMessageDeltaData` has `requests?: string[]`.
|
|
962
|
-
// Sender sends Delta Frame.
|
|
963
|
-
// Does Receiver handle Delta Requests?
|
|
964
|
-
// `handleDeltaFrame` (Receiver) -> checks `requests`.
|
|
965
|
-
// YES.
|
|
966
|
-
// So Sender puts `receiverOnlyAddrs` into `deltaFrame.requests`.
|
|
967
|
-
// Receiver sees them, fetches them, and includes them in the Response (Commit?).
|
|
968
|
-
// Wait, Init->Ack->Delta->Commit.
|
|
969
|
-
// If Receiver sends data in Commit, that's "too late" for Sender to Merge in THIS saga round?
|
|
970
|
-
// Unless Commit is not the end?
|
|
971
|
-
// Or we do a "Delta 2" loop?
|
|
972
|
-
// "Iterative Resolution Loop" from plan.
|
|
973
|
-
// If we request data in Delta, Receiver sends it in Commit (or Delta-Response).
|
|
974
|
-
// Sender gets Commit. Sees data. Merges.
|
|
975
|
-
// Then Sender needs to Send the MERGE result.
|
|
976
|
-
// Needs another Push/Delta.
|
|
977
|
-
// REFINED FLOW:
|
|
978
|
-
// 1. Sender sends Delta Frame with `requests: [receiverOnlyAddrs]`.
|
|
979
|
-
// 2. Receiver responds (Commit? or Ack 2?) with Payload (Divergent Frames).
|
|
980
|
-
// 3. Sender handles response -> Merges.
|
|
981
|
-
// 4. Sender sends Commit (containing Merge Frame).
|
|
982
|
-
// Issue: Current state machine is Init->Ack->Delta->Commit.
|
|
983
|
-
// We need to keep Saga open.
|
|
984
|
-
// If Sender sends Delta with requests, does it transition to Commit?
|
|
1046
|
+
console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
|
|
985
1047
|
}
|
|
986
1048
|
// Compute DELTA dependencies for each receiver-only frame
|
|
987
1049
|
// Find LCA to determine what dependencies we already have
|
|
@@ -1138,22 +1200,26 @@ export class SyncSagaCoordinator {
|
|
|
1138
1200
|
}
|
|
1139
1201
|
const deltaStone = await this.createSyncMsgStone({
|
|
1140
1202
|
data: deltaData,
|
|
1141
|
-
|
|
1203
|
+
localSpace: tempSpace,
|
|
1142
1204
|
metaspace,
|
|
1143
1205
|
});
|
|
1144
1206
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1145
1207
|
prevSagaIbGib: sagaIbGib,
|
|
1146
1208
|
msgStones: [deltaStone],
|
|
1147
|
-
identity,
|
|
1148
|
-
|
|
1209
|
+
sessionIdentity: identity,
|
|
1210
|
+
localSpace: tempSpace,
|
|
1149
1211
|
metaspace,
|
|
1150
1212
|
});
|
|
1151
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1152
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1153
1213
|
if (logalot) {
|
|
1154
1214
|
console.log(`${lc} Delta Frame created. Rel8ns: ${JSON.stringify(deltaFrame.rel8ns)}`);
|
|
1155
1215
|
}
|
|
1156
|
-
|
|
1216
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1217
|
+
const payloadIbGibsControl = [deltaFrame, deltaStone];
|
|
1218
|
+
if (identity) {
|
|
1219
|
+
payloadIbGibsControl.push(identity);
|
|
1220
|
+
}
|
|
1221
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: payloadIbGibs };
|
|
1222
|
+
return { frame: deltaFrame, payloadIbGibsDomain: payloadIbGibs };
|
|
1157
1223
|
}
|
|
1158
1224
|
catch (error) {
|
|
1159
1225
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
@@ -1180,7 +1246,7 @@ export class SyncSagaCoordinator {
|
|
|
1180
1246
|
if (logalot) {
|
|
1181
1247
|
console.log(`${lc} starting...`);
|
|
1182
1248
|
}
|
|
1183
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({
|
|
1249
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1184
1250
|
const deltaData = messageData;
|
|
1185
1251
|
if (!deltaData) {
|
|
1186
1252
|
throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
@@ -1366,19 +1432,23 @@ export class SyncSagaCoordinator {
|
|
|
1366
1432
|
responseDeltaData.proposeCommit = true;
|
|
1367
1433
|
const deltaStone = await this.createSyncMsgStone({
|
|
1368
1434
|
data: responseDeltaData,
|
|
1369
|
-
|
|
1435
|
+
localSpace: tempSpace,
|
|
1370
1436
|
metaspace
|
|
1371
1437
|
});
|
|
1372
1438
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1373
1439
|
prevSagaIbGib: sagaIbGib,
|
|
1374
1440
|
msgStones: [deltaStone],
|
|
1375
|
-
identity,
|
|
1376
|
-
|
|
1441
|
+
sessionIdentity: identity,
|
|
1442
|
+
localSpace: tempSpace,
|
|
1377
1443
|
metaspace
|
|
1378
1444
|
});
|
|
1379
|
-
//
|
|
1380
|
-
|
|
1381
|
-
|
|
1445
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1446
|
+
const payloadIbGibsControl = [deltaFrame, deltaStone];
|
|
1447
|
+
if (identity) {
|
|
1448
|
+
payloadIbGibsControl.push(identity);
|
|
1449
|
+
}
|
|
1450
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1451
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1382
1452
|
}
|
|
1383
1453
|
else {
|
|
1384
1454
|
// We have nothing to send.
|
|
@@ -1391,19 +1461,23 @@ export class SyncSagaCoordinator {
|
|
|
1391
1461
|
};
|
|
1392
1462
|
const commitStone = await this.createSyncMsgStone({
|
|
1393
1463
|
data: commitData,
|
|
1394
|
-
|
|
1464
|
+
localSpace: tempSpace,
|
|
1395
1465
|
metaspace
|
|
1396
1466
|
});
|
|
1397
1467
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1398
1468
|
prevSagaIbGib: sagaIbGib,
|
|
1399
1469
|
msgStones: [commitStone],
|
|
1400
|
-
identity,
|
|
1401
|
-
|
|
1470
|
+
sessionIdentity: identity,
|
|
1471
|
+
localSpace: tempSpace,
|
|
1402
1472
|
metaspace
|
|
1403
1473
|
});
|
|
1404
|
-
//
|
|
1405
|
-
|
|
1406
|
-
|
|
1474
|
+
// Build control payloads for commit
|
|
1475
|
+
const commitCtrlPayloads = [commitFrame, commitStone];
|
|
1476
|
+
if (identity) {
|
|
1477
|
+
commitCtrlPayloads.push(identity);
|
|
1478
|
+
}
|
|
1479
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1480
|
+
return { frame: commitFrame, };
|
|
1407
1481
|
}
|
|
1408
1482
|
else {
|
|
1409
1483
|
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
@@ -1417,18 +1491,16 @@ export class SyncSagaCoordinator {
|
|
|
1417
1491
|
};
|
|
1418
1492
|
const deltaStone = await this.createSyncMsgStone({
|
|
1419
1493
|
data: responseDeltaData,
|
|
1420
|
-
|
|
1494
|
+
localSpace: tempSpace,
|
|
1421
1495
|
metaspace
|
|
1422
1496
|
});
|
|
1423
1497
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1424
1498
|
prevSagaIbGib: sagaIbGib,
|
|
1425
1499
|
msgStones: [deltaStone],
|
|
1426
|
-
identity,
|
|
1427
|
-
|
|
1500
|
+
sessionIdentity: identity,
|
|
1501
|
+
localSpace: tempSpace,
|
|
1428
1502
|
metaspace
|
|
1429
1503
|
});
|
|
1430
|
-
// IMMEDIATELY persist to both spaces for audit trail
|
|
1431
|
-
await this.ensureSagaFrameInBothSpaces({ frame: deltaFrame, destSpace, tempSpace, metaspace });
|
|
1432
1504
|
// Check if PEER proposed commit
|
|
1433
1505
|
if (deltaData.proposeCommit) {
|
|
1434
1506
|
if (logalot) {
|
|
@@ -1443,21 +1515,31 @@ export class SyncSagaCoordinator {
|
|
|
1443
1515
|
};
|
|
1444
1516
|
const commitStone = await this.createSyncMsgStone({
|
|
1445
1517
|
data: commitData,
|
|
1446
|
-
|
|
1518
|
+
localSpace: tempSpace,
|
|
1447
1519
|
metaspace
|
|
1448
1520
|
});
|
|
1449
1521
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1450
1522
|
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1451
1523
|
msgStones: [commitStone],
|
|
1452
|
-
identity,
|
|
1453
|
-
|
|
1524
|
+
sessionIdentity: identity,
|
|
1525
|
+
localSpace: tempSpace,
|
|
1454
1526
|
metaspace
|
|
1455
1527
|
});
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
|
|
1528
|
+
// Build control payloads for commit
|
|
1529
|
+
const commitCtrlPayloads2 = [commitFrame, commitStone];
|
|
1530
|
+
if (identity) {
|
|
1531
|
+
commitCtrlPayloads2.push(identity);
|
|
1532
|
+
}
|
|
1533
|
+
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads2 };
|
|
1534
|
+
return { frame: commitFrame, };
|
|
1535
|
+
}
|
|
1536
|
+
// Build control payloads for delta propose
|
|
1537
|
+
const deltaCtrlPayloads = [deltaFrame, deltaStone];
|
|
1538
|
+
if (identity) {
|
|
1539
|
+
deltaCtrlPayloads.push(identity);
|
|
1459
1540
|
}
|
|
1460
|
-
return { frame: deltaFrame,
|
|
1541
|
+
// return { frame: deltaFrame, payloadIbGibsControl: deltaCtrlPayloads };
|
|
1542
|
+
return { frame: deltaFrame, };
|
|
1461
1543
|
}
|
|
1462
1544
|
}
|
|
1463
1545
|
}
|
|
@@ -1480,7 +1562,7 @@ export class SyncSagaCoordinator {
|
|
|
1480
1562
|
return null;
|
|
1481
1563
|
}
|
|
1482
1564
|
// #endregion Handlers
|
|
1483
|
-
async createSyncMsgStone({ data,
|
|
1565
|
+
async createSyncMsgStone({ data, localSpace, metaspace, }) {
|
|
1484
1566
|
const lc = `${this.lc}[${this.createSyncMsgStone.name}]`;
|
|
1485
1567
|
try {
|
|
1486
1568
|
if (logalot) {
|
|
@@ -1496,7 +1578,7 @@ export class SyncSagaCoordinator {
|
|
|
1496
1578
|
if (logalot) {
|
|
1497
1579
|
console.log(`${lc} Created stone: ${getIbGibAddr({ ibGib: stone })}`);
|
|
1498
1580
|
}
|
|
1499
|
-
await
|
|
1581
|
+
await metaspace.put({ ibGib: stone, space: localSpace });
|
|
1500
1582
|
await metaspace.registerNewIbGib({ ibGib: stone });
|
|
1501
1583
|
return stone;
|
|
1502
1584
|
}
|
|
@@ -1510,41 +1592,10 @@ export class SyncSagaCoordinator {
|
|
|
1510
1592
|
}
|
|
1511
1593
|
}
|
|
1512
1594
|
}
|
|
1513
|
-
/**
|
|
1514
|
-
* Ensures saga frame and its msg stone(s) are in BOTH spaces for audit trail.
|
|
1515
|
-
* Control ibgibs (saga frames, msg stones, identity) must be in both destSpace and tempSpace.
|
|
1516
|
-
*/
|
|
1517
|
-
async ensureSagaFrameInBothSpaces({ frame, destSpace, tempSpace, metaspace, }) {
|
|
1518
|
-
// Frame itself (already in tempSpace from creation, need in destSpace for audit)
|
|
1519
|
-
await putInSpace({ space: destSpace, ibGib: frame });
|
|
1520
|
-
await metaspace.registerNewIbGib({ ibGib: frame });
|
|
1521
|
-
// Msg stone(s) (already in tempSpace, need in destSpace for audit)
|
|
1522
|
-
const msgStoneAddrs = frame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
1523
|
-
if (msgStoneAddrs && msgStoneAddrs.length > 0) {
|
|
1524
|
-
const resMsgStones = await getFromSpace({ space: tempSpace, addrs: msgStoneAddrs });
|
|
1525
|
-
if (resMsgStones.ibGibs) {
|
|
1526
|
-
for (const msgStone of resMsgStones.ibGibs) {
|
|
1527
|
-
await putInSpace({ space: destSpace, ibGib: msgStone });
|
|
1528
|
-
await metaspace.registerNewIbGib({ ibGib: msgStone });
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
// Identity (if present, already in tempSpace, need in destSpace)
|
|
1533
|
-
const identityAddrs = frame.rel8ns?.identity;
|
|
1534
|
-
if (identityAddrs && identityAddrs.length > 0) {
|
|
1535
|
-
const resIdentity = await getFromSpace({ space: tempSpace, addrs: identityAddrs });
|
|
1536
|
-
if (resIdentity.ibGibs) {
|
|
1537
|
-
for (const identity of resIdentity.ibGibs) {
|
|
1538
|
-
await putInSpace({ space: destSpace, ibGib: identity });
|
|
1539
|
-
await metaspace.registerNewIbGib({ ibGib: identity });
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
1595
|
/**
|
|
1545
1596
|
* Evolves the saga timeline with a new frame.
|
|
1546
1597
|
*/
|
|
1547
|
-
async evolveSyncSagaIbGib({ prevSagaIbGib, msgStones,
|
|
1598
|
+
async evolveSyncSagaIbGib({ prevSagaIbGib, conflictStrategy, msgStones, sessionIdentity, localSpace, metaspace, }) {
|
|
1548
1599
|
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|
|
1549
1600
|
try {
|
|
1550
1601
|
// Validation
|
|
@@ -1567,7 +1618,7 @@ export class SyncSagaCoordinator {
|
|
|
1567
1618
|
throw new Error(`${lc} Mismatched stage in stones. Expected ${stage}, got ${d.stage} (E: d12c6571b0882f762921b60880c3f826)`);
|
|
1568
1619
|
}
|
|
1569
1620
|
}
|
|
1570
|
-
const identityAddr =
|
|
1621
|
+
const identityAddr = sessionIdentity ? getIbGibAddr({ ibGib: sessionIdentity }) : undefined;
|
|
1571
1622
|
if (prevSagaIbGib) {
|
|
1572
1623
|
/**
|
|
1573
1624
|
* rel8ns always include the new msg stone(s)
|
|
@@ -1579,8 +1630,8 @@ export class SyncSagaCoordinator {
|
|
|
1579
1630
|
}
|
|
1580
1631
|
];
|
|
1581
1632
|
// if we're authenticating/signing, we'll have identity
|
|
1582
|
-
if (
|
|
1583
|
-
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [
|
|
1633
|
+
if (sessionIdentity) {
|
|
1634
|
+
rel8nInfos.push({ rel8nName: 'identity', ibGibs: [sessionIdentity], });
|
|
1584
1635
|
}
|
|
1585
1636
|
// remove the existing sync msg stones' addrs
|
|
1586
1637
|
if (!prevSagaIbGib.rel8ns) {
|
|
@@ -1601,7 +1652,7 @@ export class SyncSagaCoordinator {
|
|
|
1601
1652
|
rel8nInfos,
|
|
1602
1653
|
rel8nRemovalInfos,
|
|
1603
1654
|
metaspace,
|
|
1604
|
-
space,
|
|
1655
|
+
space: localSpace,
|
|
1605
1656
|
noDna: true, // Explicitly no DNA for sync frames
|
|
1606
1657
|
});
|
|
1607
1658
|
const newFrame = resAppend;
|
|
@@ -1609,31 +1660,36 @@ export class SyncSagaCoordinator {
|
|
|
1609
1660
|
}
|
|
1610
1661
|
else {
|
|
1611
1662
|
// Create New Timeline (Root Frame)
|
|
1663
|
+
// data
|
|
1612
1664
|
const data = {
|
|
1613
1665
|
uuid: sagaId,
|
|
1614
|
-
// stage, // Removed from V1
|
|
1615
|
-
payload: undefined, // Data in stone
|
|
1616
1666
|
n: 0,
|
|
1617
1667
|
isTjp: true,
|
|
1618
1668
|
conflictStrategy,
|
|
1619
1669
|
};
|
|
1670
|
+
// ib
|
|
1620
1671
|
const ib = await getSyncIb({ data });
|
|
1672
|
+
// rel8ns
|
|
1621
1673
|
const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
|
|
1622
|
-
const rel8ns = {
|
|
1623
|
-
[SYNC_MSG_REL8N_NAME]: stoneAddrs,
|
|
1624
|
-
};
|
|
1674
|
+
const rel8ns = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
|
|
1625
1675
|
if (identityAddr) {
|
|
1626
1676
|
rel8ns.identity = [identityAddr];
|
|
1627
1677
|
}
|
|
1628
1678
|
const resNew = await createTimeline({
|
|
1629
|
-
space,
|
|
1679
|
+
space: localSpace,
|
|
1630
1680
|
metaspace,
|
|
1631
1681
|
ib,
|
|
1632
1682
|
data,
|
|
1633
1683
|
rel8ns,
|
|
1634
1684
|
parentIb: SYNC_ATOM, // "sync"
|
|
1685
|
+
/**
|
|
1686
|
+
* this will squash the result into a single frame
|
|
1687
|
+
*/
|
|
1635
1688
|
noDna: true,
|
|
1636
1689
|
});
|
|
1690
|
+
if (!!resNew.intermediateIbGibs) {
|
|
1691
|
+
throw new Error(`(UNEXPECTED) resNew.intermediateIbGibs? we were assuming we were creating a single frame. (E: 456818147235d991ccb4d10d0bbe4826)`);
|
|
1692
|
+
}
|
|
1637
1693
|
return resNew.newIbGib;
|
|
1638
1694
|
}
|
|
1639
1695
|
}
|
|
@@ -1642,16 +1698,16 @@ export class SyncSagaCoordinator {
|
|
|
1642
1698
|
throw error;
|
|
1643
1699
|
}
|
|
1644
1700
|
}
|
|
1645
|
-
async getStageAndPayloadFromFrame({
|
|
1701
|
+
async getStageAndPayloadFromFrame({ sagaFrame, space }) {
|
|
1646
1702
|
const lc = `${this.lc}[${this.getStageAndPayloadFromFrame.name}]`;
|
|
1647
1703
|
try {
|
|
1648
1704
|
if (logalot) {
|
|
1649
1705
|
console.log(`${lc} starting... (I: fddc287bd4d55253dc50e519fd352226)`);
|
|
1650
1706
|
}
|
|
1651
|
-
if (!
|
|
1652
|
-
throw new Error(`(UNEXPECTED)
|
|
1707
|
+
if (!sagaFrame.rel8ns) {
|
|
1708
|
+
throw new Error(`(UNEXPECTED) sagaFrame.rel8ns falsy? (E: 725bc8eb5dfe5c7058907ad8e3063826)`);
|
|
1653
1709
|
}
|
|
1654
|
-
const stoneAddrs =
|
|
1710
|
+
const stoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
1655
1711
|
if (stoneAddrs && stoneAddrs.length > 0) {
|
|
1656
1712
|
const stoneAddr = stoneAddrs[0];
|
|
1657
1713
|
const res = await getFromSpace({ addr: stoneAddr, space });
|
|
@@ -1670,7 +1726,7 @@ export class SyncSagaCoordinator {
|
|
|
1670
1726
|
}
|
|
1671
1727
|
}
|
|
1672
1728
|
else {
|
|
1673
|
-
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame?
|
|
1729
|
+
throw new Error(`(UNEXPECTED) no stone addrs rel8d to sync saga ibgib frame? sagaFrame addr: ${getIbGibAddr(sagaFrame)} (E: 43eae8579e289d016741b5526052f226)`);
|
|
1674
1730
|
}
|
|
1675
1731
|
}
|
|
1676
1732
|
catch (error) {
|