@ibgib/core-gib 0.1.12 → 0.1.14
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/keystone/keystone-helpers.mjs +3 -3
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +4 -1
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +3 -0
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +18 -2
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +84 -3
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.d.mts +0 -6
- package/dist/sync/sync-innerspace.respec.d.mts.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +395 -241
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts +31 -0
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-types.mjs +5 -0
- package/dist/sync/sync-peer/sync-peer-types.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +22 -0
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-v1.mjs +13 -0
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-constants.d.mts +8 -0
- package/dist/sync/sync-saga-context/sync-saga-context-constants.d.mts.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-constants.mjs +8 -0
- package/dist/sync/sync-saga-context/sync-saga-context-constants.mjs.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +54 -0
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +87 -0
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +66 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.mjs +12 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.d.mts +136 -91
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +563 -287
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-coordinator.respec.mjs +7 -7
- package/dist/sync/sync-saga-coordinator.respec.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-constants.d.mts +2 -0
- package/dist/sync/sync-saga-message/sync-saga-message-constants.d.mts.map +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-constants.mjs +2 -0
- package/dist/sync/sync-saga-message/sync-saga-message-constants.mjs.map +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts +15 -0
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts.map +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs +43 -0
- package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs.map +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +39 -0
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -0
- package/dist/sync/sync-saga-message/sync-saga-message-types.mjs +2 -0
- package/dist/sync/sync-saga-message/sync-saga-message-types.mjs.map +1 -0
- package/dist/sync/sync-types.d.mts +81 -3
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +27 -1
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/timeline/timeline-api.d.mts +16 -3
- package/dist/timeline/timeline-api.d.mts.map +1 -1
- package/dist/timeline/timeline-api.mjs +7 -7
- package/dist/timeline/timeline-api.mjs.map +1 -1
- package/dist/witness/space/inner-space/inner-space-v1.d.mts.map +1 -1
- package/dist/witness/space/inner-space/inner-space-v1.mjs +3 -4
- package/dist/witness/space/inner-space/inner-space-v1.mjs.map +1 -1
- package/dist/witness/space/outer-space/outer-space-types.d.mts +2 -0
- package/dist/witness/space/outer-space/outer-space-types.d.mts.map +1 -1
- package/dist/witness/space/space-base-v1.d.mts +19 -1
- package/dist/witness/space/space-base-v1.d.mts.map +1 -1
- package/dist/witness/space/space-base-v1.mjs +66 -6
- package/dist/witness/space/space-base-v1.mjs.map +1 -1
- package/dist/witness/space/space-helper.d.mts +14 -0
- package/dist/witness/space/space-helper.d.mts.map +1 -1
- package/dist/witness/space/space-helper.mjs +44 -1
- package/dist/witness/space/space-helper.mjs.map +1 -1
- package/dist/witness/space/space-respec-helper.d.mts.map +1 -1
- package/dist/witness/space/space-respec-helper.mjs +1 -1
- package/dist/witness/space/space-respec-helper.mjs.map +1 -1
- package/dist/witness/space/space-types.d.mts +12 -1
- package/dist/witness/space/space-types.d.mts.map +1 -1
- package/dist/witness/space/space-types.mjs +4 -0
- package/dist/witness/space/space-types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/keystone/keystone-helpers.mts +3 -3
- package/src/sync/README.md +275 -0
- package/src/sync/sync-constants.mts +5 -0
- package/src/sync/sync-helpers.mts +105 -6
- package/src/sync/sync-innerspace.respec.mts +458 -289
- package/src/sync/sync-peer/sync-peer-types.mts +43 -0
- package/src/sync/sync-peer/sync-peer-v1.mts +28 -0
- package/src/sync/sync-saga-context/sync-saga-context-constants.mts +8 -0
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +147 -0
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +80 -0
- package/src/sync/sync-saga-coordinator.mts +762 -329
- package/src/sync/sync-saga-coordinator.respec.mts +7 -7
- package/src/sync/sync-saga-message/sync-saga-message-constants.mts +1 -0
- package/src/sync/sync-saga-message/sync-saga-message-helpers.mts +59 -0
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +53 -0
- package/src/sync/sync-types.mts +103 -3
- package/src/timeline/timeline-api.mts +20 -4
- package/src/witness/space/inner-space/inner-space-v1.mts +3 -2
- package/src/witness/space/reconciliation-space/reconciliation-space-base.mts.OLD.md +884 -0
- package/src/witness/space/reconciliation-space/reconciliation-space-helper.mts.OLD.md +125 -0
- package/src/witness/space/space-base-v1.mts +62 -12
- package/src/witness/space/space-helper.mts +50 -1
- package/src/witness/space/space-respec-helper.mts +2 -1
- package/src/witness/space/space-types.mts +13 -1
- package/tmp.md +3 -9
|
@@ -2,104 +2,51 @@ import {
|
|
|
2
2
|
extractErrorMsg,
|
|
3
3
|
getUUID, // so our uuid's are uniform across all ibgib code
|
|
4
4
|
getTimestamp, // so our timestamp strings are uniform
|
|
5
|
-
getTimestampInTicks // so our timestamp in ticks as a string are uniform
|
|
5
|
+
getTimestampInTicks, // so our timestamp in ticks as a string are uniform
|
|
6
|
+
pretty,
|
|
7
|
+
clone
|
|
6
8
|
} from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
7
9
|
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
8
10
|
import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from "../common/other/ibgib-helper.mjs";
|
|
9
11
|
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
|
|
10
|
-
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
12
|
+
import { IbGib_V1, IbGibRel8ns_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
11
13
|
import { isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
|
|
12
14
|
|
|
13
15
|
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
|
|
14
16
|
import { IbGibSpaceAny } from "../witness/space/space-base-v1.mjs";
|
|
15
|
-
import { putInSpace, lockSpace, unlockSpace, getSpaceArgMetadata, getLatestAddrs } from "../witness/space/space-helper.mjs";
|
|
17
|
+
import { putInSpace, lockSpace, unlockSpace, getSpaceArgMetadata, getLatestAddrs, getFromSpace } from "../witness/space/space-helper.mjs";
|
|
16
18
|
import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
|
|
17
19
|
import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
|
|
18
|
-
import {
|
|
20
|
+
import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
|
|
21
|
+
import { SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME } from "./sync-constants.mjs";
|
|
22
|
+
import { appendToTimeline, createTimeline } from "../timeline/timeline-api.mjs";
|
|
19
23
|
import {
|
|
20
24
|
SyncData_V1, SyncIbGib_V1, SyncInitData, SyncDeltaData, SyncCommitData,
|
|
25
|
+
SyncConflictStrategy, SyncOptions,
|
|
26
|
+
SyncMode,
|
|
21
27
|
} from "./sync-types.mjs";
|
|
22
|
-
import { getSyncIb } from "./sync-helpers.mjs";
|
|
28
|
+
import { getSyncIb, isPastFrame } from "./sync-helpers.mjs";
|
|
23
29
|
import { getDependencyGraph } from "../common/other/graph-helper.mjs";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* The secret for the identity (to sign the commit).
|
|
42
|
-
*/
|
|
43
|
-
identitySecret: string;
|
|
44
|
-
}
|
|
30
|
+
import {
|
|
31
|
+
SyncSagaMessageData_V1,
|
|
32
|
+
SyncSagaMessageInitData_V1,
|
|
33
|
+
SyncSagaMessageAckData_V1,
|
|
34
|
+
SyncSagaMessageDeltaData_V1,
|
|
35
|
+
SyncSagaMessageCommitData_V1
|
|
36
|
+
} from "./sync-saga-message/sync-saga-message-types.mjs";
|
|
37
|
+
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
38
|
+
import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-constants.mjs";
|
|
39
|
+
import { Subject_V1 } from "../common/pubsub/subject/subject-v1.mjs";
|
|
40
|
+
import { SyncSagaInfo } from "./sync-types.mjs";
|
|
41
|
+
import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
|
|
42
|
+
import { SyncSagaContextIbGib_V1, SyncSagaContextCmd } from "./sync-saga-context/sync-saga-context-types.mjs";
|
|
43
|
+
import { createSyncSagaContext } from "./sync-saga-context/sync-saga-context-helpers.mjs";
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
49
|
* Orchestrates the synchronization process between two spaces (Source and Destination).
|
|
48
|
-
*
|
|
49
|
-
* ## Architecture: Dependency Graph Synchronization
|
|
50
|
-
*
|
|
51
|
-
* Instead of a naive file-by-file sync or a holistic "Space" sync, this coordinator operates
|
|
52
|
-
* on a **Dependency Graph** derived from specific "Domain Roots" (e.g., a specific tag,
|
|
53
|
-
* folder, or application root).
|
|
54
|
-
*
|
|
55
|
-
* ### Workflow Pipeline
|
|
56
|
-
*
|
|
57
|
-
* 1. **Graph Generation**:
|
|
58
|
-
* * Generates a `FlatIbGibGraph` using `getDependencyGraph({ live: true })` starting
|
|
59
|
-
* from the provided `domainIbGibs`.
|
|
60
|
-
* * This ensures we capture the *latest* reachable state of all relevant timelines.
|
|
61
|
-
*
|
|
62
|
-
* 2. **Classification (`splitPerTjpAndOrDna`)**:
|
|
63
|
-
* * **Stones**: Immutable, non-living ibGibs (no TJP/DNA). Trivial to sync (copy if missing).
|
|
64
|
-
* * **Living**: Evolving timelines (TJP + DNA). Complex to sync (require ordering & merging).
|
|
65
|
-
*
|
|
66
|
-
* 3. **Timeline Ordering (`getTimelinesGroupedByTjp`)**:
|
|
67
|
-
* * Living ibGibs are grouped into timelines.
|
|
68
|
-
* * A "Timeline Dependency Graph" is built. Use Case: If a Comment Timeline refers to a
|
|
69
|
-
* Post Timeline, the Post Timeline must be synced *before* the Comment Timeline to
|
|
70
|
-
* ensure referential integrity at the destination.
|
|
71
|
-
* * **Topological Sort** determines the execution order. Circular dependencies are
|
|
72
|
-
* treated as siblings.
|
|
73
|
-
*
|
|
74
|
-
* 4. **Saga Execution ("Smart Coordinator, Dumb Space")**:
|
|
75
|
-
* * The Coordinator (running on the Client/Source) drives the entire process via a
|
|
76
|
-
* "Pull-Merge-Push" strategy to resolve conflicts.
|
|
77
|
-
*
|
|
78
|
-
* * **Phase 1: Knowledge Exchange (Init)**
|
|
79
|
-
* * Generates a "Knowledge Vector" (Map<Tjp, LatestAddr>) of the Source's graph.
|
|
80
|
-
* * Sends `SyncStage.Init` to Dest.
|
|
81
|
-
* * Dest responds with its own Knowledge Vector for overlapping timelines.
|
|
82
|
-
*
|
|
83
|
-
* * **Phase 2: Gap Analysis & Conflict Resolution**
|
|
84
|
-
* * Coordinator compares Source vs. Dest knowledge.
|
|
85
|
-
* * **Fast-Forward**: Source is strictly ahead of Dest. Mark new frames for PUSH.
|
|
86
|
-
* * **Fast-Backward**: Dest is strictly ahead of Source. Mark frames for PULL (to update Local).
|
|
87
|
-
* * **Conflict/Divergence**: Both have new frames from a common ancestor.
|
|
88
|
-
* * **LOCK**: `lockSpace({ scope: tjpGib })` on Dest to prevent race conditions.
|
|
89
|
-
* * **PULL**: Download conflicting branch from Dest.
|
|
90
|
-
* * **MERGE**: Execute merge logic locally (creating a new merge frame `A_merge`).
|
|
91
|
-
* * **PUSH**: Mark `A_merge` (and dependencies) for PUSH.
|
|
92
|
-
* * **UNLOCK**: Release Dest lock.
|
|
93
|
-
*
|
|
94
|
-
* * **Phase 3: Batch Streaming (Delta)**
|
|
95
|
-
* * **Stones**: Batch `putInSpace` all missing "Stone" ibGibs first.
|
|
96
|
-
* * **Timelines**: Batch `putInSpace` Living Timelines in topological order.
|
|
97
|
-
* * *Note*: The `SyncFrame` (Init/Delta/Commit) tracks protocol state, but data transfer
|
|
98
|
-
* happens via standard `putInSpace`.
|
|
99
|
-
*
|
|
100
|
-
* * **Phase 4: Commit**
|
|
101
|
-
* * Update Local Index (register new latests).
|
|
102
|
-
* * Send `SyncStage.Commit` to Dest to finalize session.
|
|
103
50
|
*/
|
|
104
51
|
export class SyncSagaCoordinator {
|
|
105
52
|
protected lc: string = `[${SyncSagaCoordinator.name}]`;
|
|
@@ -109,214 +56,341 @@ export class SyncSagaCoordinator {
|
|
|
109
56
|
) { }
|
|
110
57
|
|
|
111
58
|
/**
|
|
112
|
-
* Executes a synchronization saga.
|
|
113
|
-
*
|
|
114
|
-
* @param opts.source The local space (Client) driving the sync.
|
|
115
|
-
* @param opts.dest The remote space (Server/Other Peer) to sync with.
|
|
116
|
-
* @param opts.identity The Keystone Identity performing the sync.
|
|
117
|
-
* @param opts.domainIbGibs The root ibGibs that define the scope of the sync (the "Dependency Graph").
|
|
118
|
-
* @param opts.identitySecret Optional secret if needed (usually handled by `keystone` service).
|
|
59
|
+
* Executes a synchronization saga using the Symmetric Sync Protocol.
|
|
119
60
|
*/
|
|
120
61
|
async sync({
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
62
|
+
peer,
|
|
63
|
+
localSpace,
|
|
64
|
+
metaspace,
|
|
65
|
+
primaryIdentity,
|
|
66
|
+
primaryIdentitySecret,
|
|
124
67
|
domainIbGibs,
|
|
125
|
-
identitySecret
|
|
126
68
|
}: {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
69
|
+
peer: SyncPeerWitness,
|
|
70
|
+
localSpace: IbGibSpaceAny,
|
|
71
|
+
metaspace: MetaspaceService,
|
|
72
|
+
primaryIdentity: KeystoneIbGib_V1,
|
|
73
|
+
primaryIdentitySecret: string,
|
|
130
74
|
domainIbGibs: IbGib_V1[],
|
|
131
|
-
|
|
132
|
-
}): Promise<void> {
|
|
75
|
+
}): Promise<SyncSagaInfo> {
|
|
133
76
|
const lc = `${this.lc}[${this.sync.name}]`;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const validGraph = graph && Object.keys(graph).length > 0;
|
|
156
|
-
if (logalot) { console.log(`${lc} graph generated. nodes: ${validGraph ? Object.keys(graph).length : 0}`); }
|
|
157
|
-
|
|
158
|
-
// 2. Classification
|
|
159
|
-
// We differentiate between "Stones" (Immutable, no timeline) and "Living" (Timelines).
|
|
160
|
-
// Stones can be synced in bulk without ordering.
|
|
161
|
-
// Living ibGibs must be grouped by timeline and ordered.
|
|
162
|
-
const ibGibs = validGraph ? Object.values(graph) : [];
|
|
163
|
-
const { mapWithTjp_YesDna, mapWithTjp_NoDna, mapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs });
|
|
164
|
-
|
|
165
|
-
// "Stones" come from mapWithoutTjps
|
|
166
|
-
const stones = Object.values(mapWithoutTjps);
|
|
167
|
-
|
|
168
|
-
// "Living" come from both TJP maps
|
|
169
|
-
// We combine them to group by TJP
|
|
170
|
-
const living = [...Object.values(mapWithTjp_YesDna), ...Object.values(mapWithTjp_NoDna)];
|
|
171
|
-
|
|
172
|
-
if (logalot) {
|
|
173
|
-
console.log(`${lc} classification results (count):`);
|
|
174
|
-
console.log(`stones: ${stones.length}`);
|
|
175
|
-
console.log(`living: ${living.length}`);
|
|
77
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
78
|
+
|
|
79
|
+
// 1. SETUP SAGA METADATA
|
|
80
|
+
const sagaId = await getUUID();
|
|
81
|
+
|
|
82
|
+
// Setup Observable & Promise
|
|
83
|
+
const updates$ = new Subject_V1<SyncSagaContextIbGib_V1>();
|
|
84
|
+
let resolveDone!: () => void;
|
|
85
|
+
let rejectDone!: (err: any) => void;
|
|
86
|
+
const done = new Promise<void>((resolve, reject) => {
|
|
87
|
+
resolveDone = resolve;
|
|
88
|
+
rejectDone = reject;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// WORKING CONTEXT (Transactional)
|
|
92
|
+
const tempSpace = await metaspace.createNewLocalSpace({
|
|
93
|
+
opts: {
|
|
94
|
+
allowCancel: false,
|
|
95
|
+
spaceName: undefined,
|
|
96
|
+
getFnPrompt: metaspace.getFnPrompt!,
|
|
97
|
+
logalot
|
|
176
98
|
}
|
|
99
|
+
});
|
|
100
|
+
if (!tempSpace) { throw new Error(`Failed to create temp space (E: 8f4e2f3d6c1b4b1a8f4e2f3d6c1b4b1a)`); }
|
|
177
101
|
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// todo: perform topological sort on timelinesMap based on inter-timeline references.
|
|
184
|
-
// For now, we sync independent timelines.
|
|
185
|
-
|
|
186
|
-
const payloadAddrs: string[] = [];
|
|
187
|
-
payloadAddrs.push(...stones.map(x => getIbGibAddr({ ibGib: x })));
|
|
188
|
-
// 3. Timeline Ordering (Topological Sort)
|
|
189
|
-
// Living ibGibs are grouped by timeline (TJP).
|
|
190
|
-
// We need to order these timelines based on dependencies.
|
|
191
|
-
const sortedTjps = this.sortTimelinesTopologically(timelinesMap);
|
|
192
|
-
|
|
193
|
-
// Add ordered timelines to payload
|
|
194
|
-
sortedTjps.forEach(tjp => {
|
|
195
|
-
const timeline = timelinesMap[tjp];
|
|
196
|
-
payloadAddrs.push(...timeline.map(x => getIbGibAddr({ ibGib: x })));
|
|
197
|
-
});
|
|
102
|
+
const cleanup = async () => {
|
|
103
|
+
// In-memory temp space cleanup if needed (e.g. if we used a specialized disposable space)
|
|
104
|
+
// await tempSpace.destroy();
|
|
105
|
+
};
|
|
198
106
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
107
|
+
// Async execution wrapper
|
|
108
|
+
(async () => {
|
|
109
|
+
try {
|
|
110
|
+
// 2. BOOTSTRAP IDENTITY (Session Keystone)
|
|
111
|
+
const config: any = {
|
|
112
|
+
id: 'default',
|
|
113
|
+
type: 'hash-reveal-v1',
|
|
114
|
+
salt: sagaId,
|
|
115
|
+
behavior: { size: 10, replenish: 'top-up', selectSequentially: 1, selectRandomly: 1, targetBindingChars: 0 },
|
|
116
|
+
algo: 'SHA-256', rounds: 1
|
|
117
|
+
};
|
|
118
|
+
const sessionIdentity = await this.keystone.genesis({
|
|
119
|
+
masterSecret: sagaId,
|
|
120
|
+
configs: [config],
|
|
121
|
+
metaspace,
|
|
122
|
+
space: tempSpace
|
|
123
|
+
});
|
|
206
124
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
125
|
+
// 3. CREATE INITIAL FRAME (Stage.init)
|
|
126
|
+
const { sagaFrame: initFrame, srcGraph } = await this.createInitFrame({
|
|
127
|
+
sagaId,
|
|
128
|
+
sessionIdentity,
|
|
129
|
+
localSpace,
|
|
130
|
+
domainIbGibs,
|
|
131
|
+
tempSpace,
|
|
132
|
+
metaspace,
|
|
133
|
+
});
|
|
212
134
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Fast-Forward: Dest tip IS in our graph, so we are ahead.
|
|
229
|
-
// No action needed, proceed to push.
|
|
230
|
-
} else {
|
|
231
|
-
// Conflict: Dest has a tip we don't know about.
|
|
232
|
-
conflicts.push(tjp);
|
|
233
|
-
}
|
|
135
|
+
// 4. EXECUTE SAGA LOOP (FSM)
|
|
136
|
+
const syncedIbGibs = await this.executeSagaLoop({
|
|
137
|
+
initialFrame: initFrame,
|
|
138
|
+
srcGraph,
|
|
139
|
+
peer,
|
|
140
|
+
sessionIdentity,
|
|
141
|
+
updates$,
|
|
142
|
+
tempSpace,
|
|
143
|
+
metaspace
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// 5. MERGE RESULT (Optimistic Commit)
|
|
147
|
+
if (syncedIbGibs && syncedIbGibs.length > 0) {
|
|
148
|
+
if (logalot) { console.log(`${lc} Merging ${syncedIbGibs.length} synced ibGibs to localSpace...`); }
|
|
149
|
+
await putInSpace({ space: localSpace, ibGibs: syncedIbGibs });
|
|
234
150
|
}
|
|
151
|
+
|
|
152
|
+
resolveDone();
|
|
153
|
+
updates$.complete();
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
157
|
+
rejectDone(error);
|
|
158
|
+
updates$.error(error);
|
|
159
|
+
} finally {
|
|
160
|
+
await cleanup();
|
|
235
161
|
}
|
|
162
|
+
})();
|
|
236
163
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
scope: tjp,
|
|
244
|
-
secondsValid: 60,
|
|
245
|
-
instanceId: uuid,
|
|
246
|
-
});
|
|
164
|
+
return {
|
|
165
|
+
sagaId,
|
|
166
|
+
updates$,
|
|
167
|
+
done
|
|
168
|
+
};
|
|
169
|
+
}
|
|
247
170
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Drives the FSM loop of the Saga.
|
|
173
|
+
* Use "Ping Pong" style: Send Request -> Wait Response -> React (Handle) -> Repeat.
|
|
174
|
+
*/
|
|
175
|
+
protected async executeSagaLoop({
|
|
176
|
+
initialFrame,
|
|
177
|
+
srcGraph,
|
|
178
|
+
peer,
|
|
179
|
+
sessionIdentity,
|
|
180
|
+
updates$,
|
|
181
|
+
tempSpace,
|
|
182
|
+
metaspace
|
|
183
|
+
}: {
|
|
184
|
+
initialFrame: SyncIbGib_V1,
|
|
185
|
+
srcGraph: { [addr: string]: IbGib_V1 },
|
|
186
|
+
peer: SyncPeerWitness,
|
|
187
|
+
sessionIdentity: KeystoneIbGib_V1,
|
|
188
|
+
updates$: Subject_V1<SyncSagaContextIbGib_V1>,
|
|
189
|
+
tempSpace: IbGibSpaceAny,
|
|
190
|
+
metaspace: MetaspaceService
|
|
191
|
+
}): Promise<IbGib_V1[]> {
|
|
192
|
+
const lc = `${this.lc}[${this.executeSagaLoop.name}]`;
|
|
193
|
+
|
|
194
|
+
let currentFrame: SyncIbGib_V1 | null = initialFrame;
|
|
195
|
+
let nextPayload: IbGib_V1[] = [];
|
|
196
|
+
const allReceivedIbGibs: IbGib_V1[] = [];
|
|
197
|
+
|
|
198
|
+
while (currentFrame) {
|
|
199
|
+
// A. Create Context (Request)
|
|
200
|
+
const payloadAddrs = nextPayload.map(p => getIbGibAddr({ ibGib: p }));
|
|
201
|
+
|
|
202
|
+
const requestCtx = await createSyncSagaContext({
|
|
203
|
+
cmd: SyncSagaContextCmd.process,
|
|
204
|
+
sagaFrame: currentFrame,
|
|
205
|
+
msgs: [],
|
|
206
|
+
sessionKeystones: [sessionIdentity],
|
|
207
|
+
payload: payloadAddrs.length > 0 ? payloadAddrs : undefined,
|
|
208
|
+
});
|
|
252
209
|
|
|
253
|
-
|
|
210
|
+
// B. Transmit
|
|
211
|
+
updates$.next(requestCtx);
|
|
212
|
+
const responseCtx = await peer.witness(requestCtx);
|
|
254
213
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
214
|
+
// C. Handle Response
|
|
215
|
+
if (!responseCtx) {
|
|
216
|
+
console.warn(`${lc} Peer returned no response context. Ending loop.`);
|
|
217
|
+
currentFrame = null;
|
|
218
|
+
break;
|
|
260
219
|
}
|
|
261
220
|
|
|
262
|
-
|
|
263
|
-
uuid,
|
|
264
|
-
stage: SyncStage.init,
|
|
265
|
-
payload: initData,
|
|
266
|
-
identity
|
|
267
|
-
});
|
|
221
|
+
updates$.next(responseCtx);
|
|
268
222
|
|
|
269
|
-
//
|
|
270
|
-
|
|
223
|
+
// D. Extract Remote Frame & React
|
|
224
|
+
const remoteFrameAddr = responseCtx.rel8ns?.sagaFrame?.[0];
|
|
225
|
+
if (!remoteFrameAddr) {
|
|
226
|
+
console.warn(`${lc} Peer response has no sagaFrame. Ending loop.`);
|
|
227
|
+
currentFrame = null;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
271
230
|
|
|
272
|
-
|
|
273
|
-
|
|
231
|
+
const remoteFrame = await getFromSpace({ addr: remoteFrameAddr, space: tempSpace });
|
|
232
|
+
if (!remoteFrame) { throw new Error(`Could not resolve remote frame: ${remoteFrameAddr}`); }
|
|
274
233
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
identity
|
|
234
|
+
// React (Reducer)
|
|
235
|
+
const result = await this.handleSagaFrame({
|
|
236
|
+
sagaIbGib: remoteFrame as SyncIbGib_V1,
|
|
237
|
+
srcGraph,
|
|
238
|
+
space: tempSpace,
|
|
239
|
+
identity: sessionIdentity,
|
|
240
|
+
metaspace
|
|
283
241
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const BATCH_SIZE = 20;
|
|
290
|
-
for (let i = 0; i < payloadAddrs.length; i += BATCH_SIZE) {
|
|
291
|
-
const chunkAddrs = payloadAddrs.slice(i, i + BATCH_SIZE);
|
|
292
|
-
const chunkIbGibs = chunkAddrs
|
|
293
|
-
.map(addr => {
|
|
294
|
-
const ibGib = graph[addr];
|
|
295
|
-
if (!ibGib) { throw new Error(`(UNEXPECTED) addr not found in graph: ${addr} (E: 8a402324057849318625aa85721665a5)`); }
|
|
296
|
-
return ibGib;
|
|
297
|
-
})
|
|
298
|
-
.filter(ibGib => !isPrimitive({ ibGib }));
|
|
299
|
-
|
|
300
|
-
if (chunkIbGibs.length > 0) {
|
|
301
|
-
if (logalot) { console.log(`${lc} streaming batch ${Math.ceil(i / BATCH_SIZE) + 1} (${chunkIbGibs.length} ibGibs)...`); }
|
|
302
|
-
// We don't set isDna: true here because the batch might contain mixed types.
|
|
303
|
-
// The Space implementation should handle routing based on internal metadata if needed.
|
|
304
|
-
await putInSpace({ space: dest, ibGibs: chunkIbGibs });
|
|
305
|
-
}
|
|
242
|
+
|
|
243
|
+
currentFrame = result?.frame || null;
|
|
244
|
+
nextPayload = result?.payload || [];
|
|
245
|
+
if (result?.receivedPayload) {
|
|
246
|
+
allReceivedIbGibs.push(...result.receivedPayload);
|
|
306
247
|
}
|
|
248
|
+
}
|
|
307
249
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
250
|
+
return allReceivedIbGibs;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
protected async analyzeTimelines({
|
|
254
|
+
domainIbGibs,
|
|
255
|
+
space,
|
|
256
|
+
}: {
|
|
257
|
+
domainIbGibs: IbGib_V1[],
|
|
258
|
+
space: IbGibSpaceAny,
|
|
259
|
+
}): Promise<{
|
|
260
|
+
srcStones: IbGib_V1[],
|
|
261
|
+
srcTimelinesMap: { [tjp: string]: IbGib_V1[] },
|
|
262
|
+
srcSortedTjps: string[],
|
|
263
|
+
srcGraph: { [addr: string]: IbGib_V1 },
|
|
264
|
+
}> {
|
|
265
|
+
const lc = `${this.lc}[${this.analyzeTimelines.name}]`;
|
|
266
|
+
const srcGraph = await getDependencyGraph({
|
|
267
|
+
ibGibs: domainIbGibs,
|
|
268
|
+
live: true,
|
|
269
|
+
space,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const srcGraphIsValid = srcGraph && Object.keys(srcGraph).length > 0;
|
|
273
|
+
if (logalot) { console.log(`${lc} graph generated. nodes: ${srcGraphIsValid ? Object.keys(srcGraph).length : 0}`); }
|
|
274
|
+
|
|
275
|
+
const srcIbGibs = srcGraphIsValid ? Object.values(srcGraph) : [];
|
|
276
|
+
const {
|
|
277
|
+
mapWithTjp_YesDna: srcMapWithTjp_YesDna,
|
|
278
|
+
mapWithTjp_NoDna: srcMapWithTjp_NoDna,
|
|
279
|
+
mapWithoutTjps: src_MapWithoutTjps
|
|
280
|
+
} = splitPerTjpAndOrDna({ ibGibs: srcIbGibs });
|
|
281
|
+
|
|
282
|
+
const srcStones = Object.values(src_MapWithoutTjps);
|
|
283
|
+
const srcLiving = [...Object.values(srcMapWithTjp_YesDna), ...Object.values(srcMapWithTjp_NoDna)];
|
|
319
284
|
|
|
285
|
+
const srcTimelinesMap = getTimelinesGroupedByTjp({ ibGibs: srcLiving });
|
|
286
|
+
const srcSortedTjps = this.sortTimelinesTopologically(srcTimelinesMap);
|
|
287
|
+
|
|
288
|
+
return { srcStones, srcTimelinesMap, srcSortedTjps, srcGraph };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
protected async resolveConflicts(): Promise<void> {
|
|
292
|
+
throw new Error("resolveConflicts is no longer supported.");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
protected async processBatchStream(): Promise<void> {
|
|
296
|
+
// Deprecated by payload in SyncSagaContext
|
|
297
|
+
throw new Error("processBatchStream is no longer supported.");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Creates the Initial Saga Frame (Init Stage).
|
|
302
|
+
*/
|
|
303
|
+
protected async createInitFrame({
|
|
304
|
+
sagaId,
|
|
305
|
+
sessionIdentity,
|
|
306
|
+
localSpace,
|
|
307
|
+
domainIbGibs,
|
|
308
|
+
tempSpace,
|
|
309
|
+
metaspace
|
|
310
|
+
}: {
|
|
311
|
+
sagaId: string,
|
|
312
|
+
sessionIdentity: KeystoneIbGib_V1,
|
|
313
|
+
localSpace: IbGibSpaceAny,
|
|
314
|
+
domainIbGibs: IbGib_V1[],
|
|
315
|
+
tempSpace: IbGibSpaceAny,
|
|
316
|
+
metaspace: MetaspaceService
|
|
317
|
+
}): Promise<{ sagaFrame: SyncIbGib_V1, srcGraph: { [addr: string]: IbGib_V1 } }> {
|
|
318
|
+
const initData: SyncSagaMessageInitData_V1 = {
|
|
319
|
+
sagaId,
|
|
320
|
+
stage: SyncStage.init,
|
|
321
|
+
knowledgeVector: {},
|
|
322
|
+
identity: sessionIdentity, // KeystoneIbGib is already public data
|
|
323
|
+
mode: SyncMode.sync,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Populate Knowledge Vector
|
|
327
|
+
const { srcTimelinesMap, srcGraph } = await this.analyzeTimelines({ domainIbGibs, space: localSpace });
|
|
328
|
+
Object.keys(srcTimelinesMap).forEach(tjp => {
|
|
329
|
+
const timeline = srcTimelinesMap[tjp];
|
|
330
|
+
const tip = timeline.at(-1)!;
|
|
331
|
+
initData.knowledgeVector[tjp] = getIbGibAddr({ ibGib: tip });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const initStone = await this.createSyncMsgStone({
|
|
335
|
+
data: initData,
|
|
336
|
+
space: tempSpace,
|
|
337
|
+
metaspace
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const sagaFrame = await this.evolveSyncSagaIbGib({
|
|
341
|
+
msgStones: [initStone],
|
|
342
|
+
identity: sessionIdentity,
|
|
343
|
+
space: tempSpace,
|
|
344
|
+
metaspace
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return { sagaFrame, srcGraph };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Reacts to an incoming saga frame and dispatches to appropriate handler.
|
|
352
|
+
*/
|
|
353
|
+
async handleSagaFrame({
|
|
354
|
+
sagaIbGib,
|
|
355
|
+
srcGraph,
|
|
356
|
+
space,
|
|
357
|
+
identity,
|
|
358
|
+
identitySecret,
|
|
359
|
+
metaspace,
|
|
360
|
+
}: {
|
|
361
|
+
sagaIbGib: SyncIbGib_V1,
|
|
362
|
+
srcGraph: { [addr: string]: IbGib_V1 },
|
|
363
|
+
space: IbGibSpaceAny,
|
|
364
|
+
identity: KeystoneIbGib_V1,
|
|
365
|
+
identitySecret?: string,
|
|
366
|
+
metaspace: MetaspaceService,
|
|
367
|
+
}): Promise<{ frame: SyncIbGib_V1, payload?: IbGib_V1[], receivedPayload?: IbGib_V1[] } | null> {
|
|
368
|
+
const lc = `${this.lc}[${this.handleSagaFrame.name}]`;
|
|
369
|
+
try {
|
|
370
|
+
if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
|
|
371
|
+
|
|
372
|
+
if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 71b938adf1d87c2527bfd4f86dfd0826)`); }
|
|
373
|
+
if (logalot) { console.log(`${lc} handling frame stage: ${sagaIbGib.data.stage}`); }
|
|
374
|
+
|
|
375
|
+
// Get Stage from Stone (or Frame for Init fallback)
|
|
376
|
+
const { stage, payload } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
377
|
+
|
|
378
|
+
switch (stage) {
|
|
379
|
+
case SyncStage.init:
|
|
380
|
+
return await this.handleInitFrame({ sagaIbGib, payload, space, metaspace, identity, identitySecret });
|
|
381
|
+
|
|
382
|
+
case SyncStage.ack:
|
|
383
|
+
return await this.handleAckFrame({ sagaIbGib, srcGraph, space, metaspace, identity });
|
|
384
|
+
|
|
385
|
+
case SyncStage.delta:
|
|
386
|
+
return await this.handleDeltaFrame({ sagaIbGib, srcGraph, space, identity, metaspace });
|
|
387
|
+
|
|
388
|
+
case SyncStage.commit:
|
|
389
|
+
return await this.handleCommitFrame({ sagaIbGib, space });
|
|
390
|
+
|
|
391
|
+
default:
|
|
392
|
+
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
393
|
+
}
|
|
320
394
|
} catch (error) {
|
|
321
395
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
322
396
|
throw error;
|
|
@@ -325,93 +399,451 @@ export class SyncSagaCoordinator {
|
|
|
325
399
|
}
|
|
326
400
|
}
|
|
327
401
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
402
|
+
// #region Handlers
|
|
403
|
+
|
|
404
|
+
protected async handleInitFrame({
|
|
405
|
+
sagaIbGib,
|
|
331
406
|
payload,
|
|
332
|
-
|
|
407
|
+
space,
|
|
408
|
+
metaspace,
|
|
409
|
+
identity,
|
|
410
|
+
identitySecret,
|
|
333
411
|
}: {
|
|
334
|
-
|
|
335
|
-
stage: any, // SyncStage
|
|
412
|
+
sagaIbGib: SyncIbGib_V1,
|
|
336
413
|
payload: any,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
414
|
+
space: IbGibSpaceAny,
|
|
415
|
+
metaspace: MetaspaceService,
|
|
416
|
+
identity: KeystoneIbGib_V1,
|
|
417
|
+
identitySecret?: string,
|
|
418
|
+
}): Promise<{ frame: SyncIbGib_V1, payload?: IbGib_V1[] } | null> {
|
|
419
|
+
const lc = `${this.lc}[${this.handleInitFrame.name}]`;
|
|
420
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
421
|
+
|
|
422
|
+
// Extract Init Data
|
|
423
|
+
const initData = sagaIbGib.data!.payload as SyncInitData;
|
|
424
|
+
if (!initData || !initData.knowledgeVector) {
|
|
425
|
+
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
426
|
+
}
|
|
427
|
+
const remoteKV = initData.knowledgeVector;
|
|
428
|
+
const remoteTjps = Object.keys(remoteKV);
|
|
429
|
+
|
|
430
|
+
// 1. Get Local Latest Addrs for all TJPs
|
|
431
|
+
let localKV: { [tjp: string]: string | null } = {};
|
|
432
|
+
if (remoteTjps.length > 0) {
|
|
433
|
+
// Batch get latest addrs for the TJPs
|
|
434
|
+
const arg = await space.argy({
|
|
435
|
+
argData: {
|
|
436
|
+
cmd: 'get',
|
|
437
|
+
cmdModifiers: ['latest', 'tjps'], // interprets input addrs as TJPs
|
|
438
|
+
ibGibAddrs: remoteTjps,
|
|
439
|
+
} as any
|
|
440
|
+
});
|
|
441
|
+
const res = await space.witness(arg);
|
|
442
|
+
if (res?.data?.latestAddrsMap) {
|
|
443
|
+
localKV = res.data.latestAddrsMap;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 2. Gap Analysis
|
|
448
|
+
const deltaReqAddrs: string[] = [];
|
|
449
|
+
const pushOfferAddrs: string[] = [];
|
|
450
|
+
// const divergentTjps: string[] = []; // Handle later
|
|
451
|
+
|
|
452
|
+
for (const tjp of remoteTjps) {
|
|
453
|
+
const remoteAddr = remoteKV[tjp];
|
|
454
|
+
const localAddr = localKV[tjp];
|
|
455
|
+
|
|
456
|
+
if (!localAddr) {
|
|
457
|
+
deltaReqAddrs.push(remoteAddr);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (localAddr === remoteAddr) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Check if Remote is in Local's PAST (Local is Ahead -> Push Offer)
|
|
466
|
+
const isRemoteInPast = await isPastFrame({
|
|
467
|
+
olderAddr: remoteAddr,
|
|
468
|
+
newerAddr: localAddr,
|
|
469
|
+
space,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (isRemoteInPast) {
|
|
473
|
+
pushOfferAddrs.push(localAddr);
|
|
474
|
+
} else {
|
|
475
|
+
deltaReqAddrs.push(remoteAddr);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 3. Create Ack Frame
|
|
480
|
+
const sagaId = sagaIbGib.data!.uuid;
|
|
481
|
+
// Create Payload Stone
|
|
482
|
+
const ackData: SyncSagaMessageAckData_V1 = {
|
|
483
|
+
sagaId,
|
|
484
|
+
stage: SyncStage.ack,
|
|
485
|
+
deltaReqAddrs,
|
|
486
|
+
pushOfferAddrs,
|
|
343
487
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
* single, one-off timeline (not to be used for any future merging),
|
|
359
|
-
* so we don't need dna.
|
|
360
|
-
*/
|
|
361
|
-
dna: false,
|
|
362
|
-
tjp: {
|
|
363
|
-
timestamp: true,
|
|
364
|
-
/**
|
|
365
|
-
* we'll use the uuid passed in
|
|
366
|
-
*/
|
|
367
|
-
uuid: false
|
|
368
|
-
},
|
|
369
|
-
nCounter: true,
|
|
488
|
+
|
|
489
|
+
const ackStone = await this.createSyncMsgStone({
|
|
490
|
+
data: ackData,
|
|
491
|
+
space,
|
|
492
|
+
metaspace,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Evolve Saga
|
|
496
|
+
const ackFrame = await this.evolveSyncSagaIbGib({
|
|
497
|
+
prevSagaIbGib: sagaIbGib,
|
|
498
|
+
msgStones: [ackStone],
|
|
499
|
+
identity,
|
|
500
|
+
space,
|
|
501
|
+
metaspace,
|
|
370
502
|
});
|
|
371
|
-
|
|
503
|
+
|
|
504
|
+
return { frame: ackFrame };
|
|
372
505
|
}
|
|
373
506
|
|
|
374
|
-
protected async
|
|
507
|
+
protected async handleAckFrame({
|
|
508
|
+
sagaIbGib,
|
|
509
|
+
srcGraph,
|
|
375
510
|
space,
|
|
376
|
-
|
|
511
|
+
metaspace,
|
|
512
|
+
identity,
|
|
377
513
|
}: {
|
|
514
|
+
sagaIbGib: SyncIbGib_V1,
|
|
515
|
+
srcGraph: { [addr: string]: IbGib_V1 },
|
|
378
516
|
space: IbGibSpaceAny,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
517
|
+
metaspace: MetaspaceService,
|
|
518
|
+
identity: KeystoneIbGib_V1,
|
|
519
|
+
}): Promise<{ frame: SyncIbGib_V1, payload?: IbGib_V1[] } | null> {
|
|
520
|
+
const lc = `${this.lc}[${this.handleAckFrame.name}]`;
|
|
521
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
384
522
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
523
|
+
const { payload } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
524
|
+
const ackData = payload as SyncSagaMessageAckData_V1;
|
|
525
|
+
|
|
526
|
+
if (!ackData || ackData.stage !== SyncStage.ack) {
|
|
527
|
+
throw new Error(`${lc} Invalid ack frame (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const deltaReqAddrs = ackData.deltaReqAddrs || [];
|
|
531
|
+
const pushOfferAddrs = ackData.pushOfferAddrs || [];
|
|
532
|
+
|
|
533
|
+
// 1. Process Push Offers (Pull Requests) (Naive: Accept all if missing)
|
|
534
|
+
const pullReqAddrs: string[] = [];
|
|
535
|
+
for (const addr of pushOfferAddrs) {
|
|
536
|
+
const existing = srcGraph[addr] || (await getFromSpace({ addr, space })).ibGibs?.[0];
|
|
537
|
+
if (!existing) {
|
|
538
|
+
pullReqAddrs.push(addr);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 2. Process Delta Requests (Push Payload)
|
|
543
|
+
const payloadIbGibs: IbGib_V1[] = [];
|
|
544
|
+
for (const addr of deltaReqAddrs) {
|
|
545
|
+
let ibGib = srcGraph[addr];
|
|
546
|
+
if (!ibGib) {
|
|
547
|
+
const res = await getFromSpace({ addr, space });
|
|
548
|
+
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
549
|
+
ibGib = res.ibGibs[0];
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (ibGib) {
|
|
553
|
+
payloadIbGibs.push(ibGib);
|
|
554
|
+
} else {
|
|
555
|
+
console.warn(`${lc} Requested addr not found: ${addr}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// 3. Create Delta Frame
|
|
560
|
+
const sagaId = ackData.sagaId;
|
|
561
|
+
const deltaData: SyncSagaMessageDeltaData_V1 = {
|
|
562
|
+
sagaId,
|
|
563
|
+
stage: SyncStage.delta,
|
|
564
|
+
payloadAddrs: payloadIbGibs.map(p => getIbGibAddr({ ibGib: p })),
|
|
565
|
+
requests: pullReqAddrs.length > 0 ? pullReqAddrs : undefined,
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
569
|
+
data: deltaData,
|
|
570
|
+
space,
|
|
571
|
+
metaspace,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
575
|
+
prevSagaIbGib: sagaIbGib,
|
|
576
|
+
msgStones: [deltaStone],
|
|
577
|
+
identity,
|
|
578
|
+
space,
|
|
579
|
+
metaspace,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return { frame: deltaFrame, payload: payloadIbGibs };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
protected async handleDeltaFrame({
|
|
586
|
+
sagaIbGib,
|
|
587
|
+
srcGraph,
|
|
588
|
+
space,
|
|
589
|
+
metaspace,
|
|
590
|
+
identity,
|
|
591
|
+
}: {
|
|
592
|
+
sagaIbGib: SyncIbGib_V1,
|
|
593
|
+
srcGraph: { [addr: string]: IbGib_V1 },
|
|
594
|
+
space: IbGibSpaceAny,
|
|
595
|
+
metaspace: MetaspaceService,
|
|
596
|
+
identity: KeystoneIbGib_V1,
|
|
597
|
+
}): Promise<{ frame: SyncIbGib_V1, payload?: IbGib_V1[], receivedPayload?: IbGib_V1[] } | null> {
|
|
598
|
+
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
599
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
600
|
+
|
|
601
|
+
const { payload } = await this.getStageAndPayloadFromFrame({ ibGib: sagaIbGib, space });
|
|
602
|
+
const deltaData = payload as SyncSagaMessageDeltaData_V1;
|
|
603
|
+
|
|
604
|
+
if (!deltaData || deltaData.stage !== SyncStage.delta) {
|
|
605
|
+
throw new Error(`${lc} Invalid delta frame (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const payloadAddrs = deltaData.payloadAddrs || [];
|
|
609
|
+
const requests = deltaData.requests || [];
|
|
610
|
+
|
|
611
|
+
// 1. Process Received Payload
|
|
612
|
+
const receivedPayload: IbGib_V1[] = [];
|
|
613
|
+
if (payloadAddrs.length > 0) {
|
|
614
|
+
const res = await space.witness({
|
|
615
|
+
attribute: 'ibGibs',
|
|
616
|
+
arg: {
|
|
617
|
+
ibGibAddrs: payloadAddrs,
|
|
389
618
|
cmd: 'get',
|
|
390
|
-
|
|
391
|
-
|
|
619
|
+
} as any
|
|
620
|
+
});
|
|
621
|
+
if (res.data?.ibGibs) {
|
|
622
|
+
receivedPayload.push(...res.data.ibGibs);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// 2. Fulfill Requests (Outgoing Payload)
|
|
627
|
+
const outgoingPayload: IbGib_V1[] = [];
|
|
628
|
+
for (const addr of requests) {
|
|
629
|
+
let ibGib = srcGraph[addr];
|
|
630
|
+
if (!ibGib) {
|
|
631
|
+
const res = await getFromSpace({ addr, space });
|
|
632
|
+
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
633
|
+
ibGib = res.ibGibs[0];
|
|
392
634
|
}
|
|
635
|
+
}
|
|
636
|
+
if (ibGib) {
|
|
637
|
+
outgoingPayload.push(ibGib);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// 3. Determine Next Stage
|
|
642
|
+
if (requests.length > 0) {
|
|
643
|
+
// They requested more data -> Send Delta
|
|
644
|
+
const sagaId = deltaData.sagaId;
|
|
645
|
+
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
646
|
+
sagaId,
|
|
647
|
+
stage: SyncStage.delta,
|
|
648
|
+
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
652
|
+
data: responseDeltaData,
|
|
653
|
+
space,
|
|
654
|
+
metaspace
|
|
393
655
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
656
|
+
|
|
657
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
658
|
+
prevSagaIbGib: sagaIbGib,
|
|
659
|
+
msgStones: [deltaStone],
|
|
660
|
+
identity,
|
|
661
|
+
space,
|
|
662
|
+
metaspace
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
return { frame: deltaFrame, payload: outgoingPayload, receivedPayload };
|
|
666
|
+
|
|
667
|
+
} else {
|
|
668
|
+
// No requests -> Commit
|
|
669
|
+
const sagaId = deltaData.sagaId;
|
|
670
|
+
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
671
|
+
sagaId,
|
|
672
|
+
stage: SyncStage.commit,
|
|
673
|
+
success: true,
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const commitStone = await this.createSyncMsgStone({
|
|
677
|
+
data: commitData,
|
|
678
|
+
space,
|
|
679
|
+
metaspace
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
683
|
+
prevSagaIbGib: sagaIbGib,
|
|
684
|
+
msgStones: [commitStone],
|
|
685
|
+
identity,
|
|
686
|
+
space,
|
|
687
|
+
metaspace
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
return { frame: commitFrame, receivedPayload };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
protected async handleCommitFrame({
|
|
695
|
+
sagaIbGib,
|
|
696
|
+
space,
|
|
697
|
+
}: {
|
|
698
|
+
sagaIbGib: SyncIbGib_V1,
|
|
699
|
+
space: IbGibSpaceAny,
|
|
700
|
+
}): Promise<{ frame: SyncIbGib_V1, payload?: IbGib_V1[] } | null> {
|
|
701
|
+
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
702
|
+
if (logalot) { console.log(`${lc} Commit received. Saga complete.`); }
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// #endregion Handlers
|
|
707
|
+
|
|
708
|
+
protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
|
|
709
|
+
data,
|
|
710
|
+
space,
|
|
711
|
+
metaspace,
|
|
712
|
+
}: {
|
|
713
|
+
data: TStoneData,
|
|
714
|
+
space: IbGibSpaceAny,
|
|
715
|
+
metaspace: MetaspaceService,
|
|
716
|
+
}): Promise<IbGib_V1<TStoneData>> {
|
|
717
|
+
const ib = await getSyncSagaMessageIb({ data });
|
|
718
|
+
const stone = await Factory_V1.stone({
|
|
719
|
+
ib,
|
|
720
|
+
parentPrimitiveIb: SYNC_SAGA_MSG_ATOM,
|
|
721
|
+
data,
|
|
722
|
+
uuid: true, // we want the stone to have its own uniqueness
|
|
723
|
+
});
|
|
724
|
+
await putInSpace({ space, ibGib: stone });
|
|
725
|
+
await metaspace.registerNewIbGib({ ibGib: stone });
|
|
726
|
+
return stone as IbGib_V1<TStoneData>;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Evolves the saga timeline with a new frame.
|
|
732
|
+
*/
|
|
733
|
+
protected async evolveSyncSagaIbGib({
|
|
734
|
+
prevSagaIbGib,
|
|
735
|
+
msgStones,
|
|
736
|
+
identity,
|
|
737
|
+
space,
|
|
738
|
+
metaspace,
|
|
739
|
+
}: {
|
|
740
|
+
prevSagaIbGib?: SyncIbGib_V1,
|
|
741
|
+
msgStones: IbGib_V1[],
|
|
742
|
+
identity: KeystoneIbGib_V1,
|
|
743
|
+
space: IbGibSpaceAny,
|
|
744
|
+
metaspace: MetaspaceService,
|
|
745
|
+
}): Promise<SyncIbGib_V1> {
|
|
746
|
+
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|
|
747
|
+
try {
|
|
748
|
+
// Validation
|
|
749
|
+
if (!msgStones || msgStones.length === 0) {
|
|
750
|
+
throw new Error(`${lc} msgStones required (E: d13f8afcfb8816aeb26baae7df3ef726)`);
|
|
751
|
+
}
|
|
752
|
+
const firstStoneData = msgStones[0].data as SyncSagaMessageData_V1;
|
|
753
|
+
if (!firstStoneData?.sagaId || !firstStoneData?.stage) {
|
|
754
|
+
throw new Error(`${lc} Invalid stone data (missing sagaId or stage) (E: c004f85ce8f8958b11839f8d68c38826)`);
|
|
755
|
+
}
|
|
756
|
+
const sagaId = firstStoneData.sagaId;
|
|
757
|
+
const stage = firstStoneData.stage;
|
|
758
|
+
|
|
759
|
+
// Ensure all stones match sagaId and stage
|
|
760
|
+
for (const stone of msgStones) {
|
|
761
|
+
const d = stone.data as SyncSagaMessageData_V1;
|
|
762
|
+
if (d.sagaId !== sagaId) {
|
|
763
|
+
throw new Error(`${lc} Mismatched sagaId in stones. Expected ${sagaId}, got ${d.sagaId} (E: 29e02719e426ecbbf8ee66887a2be226)`);
|
|
764
|
+
}
|
|
765
|
+
if (d.stage !== stage) {
|
|
766
|
+
throw new Error(`${lc} Mismatched stage in stones. Expected ${stage}, got ${d.stage} (E: d12c6571b0882f762921b60880c3f826)`);
|
|
406
767
|
}
|
|
407
768
|
}
|
|
408
|
-
|
|
769
|
+
|
|
770
|
+
const identityAddr = getIbGibAddr({ ibGib: identity });
|
|
771
|
+
|
|
772
|
+
if (prevSagaIbGib) {
|
|
773
|
+
// Append to existing timeline
|
|
774
|
+
const resAppend = await appendToTimeline({
|
|
775
|
+
timeline: prevSagaIbGib,
|
|
776
|
+
rel8nInfos: [
|
|
777
|
+
{
|
|
778
|
+
rel8nName: SYNC_MSG_REL8N_NAME,
|
|
779
|
+
ibGibs: msgStones,
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
rel8nName: 'identity',
|
|
783
|
+
ibGibs: [identity],
|
|
784
|
+
}
|
|
785
|
+
],
|
|
786
|
+
metaspace,
|
|
787
|
+
space,
|
|
788
|
+
noDna: true, // Explicitly no DNA for sync frames
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const newFrame = resAppend as SyncIbGib_V1;
|
|
792
|
+
return newFrame;
|
|
793
|
+
} else {
|
|
794
|
+
// Create New Timeline (Root Frame)
|
|
795
|
+
const data: SyncData_V1 = {
|
|
796
|
+
uuid: sagaId,
|
|
797
|
+
stage,
|
|
798
|
+
payload: undefined, // Data in stone
|
|
799
|
+
};
|
|
800
|
+
const ib = await getSyncIb({ data });
|
|
801
|
+
|
|
802
|
+
const stoneAddrs = msgStones.map(s => getIbGibAddr({ ibGib: s }));
|
|
803
|
+
|
|
804
|
+
const resNew = await createTimeline({
|
|
805
|
+
space,
|
|
806
|
+
metaspace,
|
|
807
|
+
ib,
|
|
808
|
+
data,
|
|
809
|
+
rel8ns: {
|
|
810
|
+
[SYNC_MSG_REL8N_NAME]: stoneAddrs,
|
|
811
|
+
identity: [identityAddr],
|
|
812
|
+
},
|
|
813
|
+
parentIb: SYNC_ATOM, // "sync"
|
|
814
|
+
noDna: true,
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
return resNew.newIbGib as SyncIbGib_V1;
|
|
818
|
+
}
|
|
819
|
+
|
|
409
820
|
} catch (error) {
|
|
410
821
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
411
|
-
|
|
822
|
+
throw error;
|
|
412
823
|
}
|
|
413
824
|
}
|
|
414
825
|
|
|
826
|
+
protected async getStageAndPayloadFromFrame({ ibGib, space }: { ibGib: IbGib_V1, space: IbGibSpaceAny }): Promise<{ stage: SyncStage, payload: any }> {
|
|
827
|
+
// 1. Try Stone (Primary)
|
|
828
|
+
const stoneAddrs = ibGib.rel8ns?.[SYNC_MSG_REL8N_NAME];
|
|
829
|
+
if (stoneAddrs && stoneAddrs.length > 0) {
|
|
830
|
+
const stoneAddr = stoneAddrs[0];
|
|
831
|
+
const res = await getFromSpace({ addr: stoneAddr, space });
|
|
832
|
+
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
833
|
+
const stone = res.ibGibs[0];
|
|
834
|
+
if (stone.data && stone.data.stage) {
|
|
835
|
+
return { stage: stone.data.stage, payload: stone.data };
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// 2. Fallback to Frame Data (Legacy/Init compatibility)
|
|
841
|
+
if (ibGib.data && ibGib.data.stage) {
|
|
842
|
+
return { stage: ibGib.data.stage, payload: ibGib.data.payload };
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
throw new Error(`Could not determine stage from frame ${getIbGibAddr({ ibGib })}`);
|
|
846
|
+
}
|
|
415
847
|
protected sortTimelinesTopologically(timelines: { [tjp: string]: IbGib_V1[] }): string[] {
|
|
416
848
|
const lc = `${this.lc}[${this.sortTimelinesTopologically.name}]`;
|
|
417
849
|
const tjps = Object.keys(timelines);
|
|
@@ -474,4 +906,5 @@ export class SyncSagaCoordinator {
|
|
|
474
906
|
|
|
475
907
|
return sorted;
|
|
476
908
|
}
|
|
909
|
+
|
|
477
910
|
}
|