@ibgib/core-gib 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/sync/sync-innerspace-constants.respec.d.mts +8 -0
- package/dist/sync/sync-innerspace-constants.respec.d.mts.map +1 -0
- package/dist/sync/sync-innerspace-constants.respec.mjs +116 -0
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -0
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +3 -3
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.d.mts +7 -0
- package/dist/sync/sync-innerspace-partial-update.respec.d.mts.map +1 -0
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +116 -0
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -0
- package/dist/sync/sync-innerspace.respec.mjs +5 -5
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +159 -8
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +7 -0
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/package.json +1 -1
- package/src/sync/sync-innerspace-constants.respec.mts +133 -0
- package/src/sync/sync-innerspace-deep-updates.respec.mts +1 -1
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +2 -2
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +1 -1
- package/src/sync/sync-innerspace-partial-update.respec.mts +150 -0
- package/src/sync/sync-innerspace.respec.mts +4 -4
- package/src/sync/sync-saga-coordinator.mts +173 -9
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +5 -0
- package/tmp.md +426 -375
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sync-innerspace-partial-update.respec
|
|
3
|
+
*
|
|
4
|
+
* Verifies Sync Scenarios where the Destination has partial history.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
respecfully, lastOfAll, ifWe, iReckon,
|
|
9
|
+
ifWeMight
|
|
10
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
11
|
+
const maam = `[${import.meta.url}]`, sir = maam;
|
|
12
|
+
import { delay, extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
13
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
14
|
+
import { getDependencyGraph } from '../common/other/graph-helper.mjs';
|
|
15
|
+
|
|
16
|
+
import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
|
|
17
|
+
import { putInSpace, getFromSpace } from '../witness/space/space-helper.mjs';
|
|
18
|
+
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
|
|
19
|
+
import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
|
|
20
|
+
import { createTimelineRootHelper, getTestKeystoneServiceHelper } from '../agent-helpers.mjs';
|
|
21
|
+
import { mut8Timeline } from '../timeline/timeline-api.mjs';
|
|
22
|
+
import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace-v1.mjs';
|
|
23
|
+
import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
|
|
24
|
+
import { toDto } from '../common/other/ibgib-helper.mjs';
|
|
25
|
+
|
|
26
|
+
const logalot = true;
|
|
27
|
+
const lc = `[sync-innerspace-partial-update.respec]`;
|
|
28
|
+
|
|
29
|
+
await respecfully(sir, `Sync InnerSpaces (Partial Update)`, async () => {
|
|
30
|
+
|
|
31
|
+
let metaspace: Metaspace_Innerspace;
|
|
32
|
+
let sourceSpace: InnerSpace_V1;
|
|
33
|
+
let destSpace: InnerSpace_V1;
|
|
34
|
+
|
|
35
|
+
interface TestData {
|
|
36
|
+
type: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
uuid?: string;
|
|
39
|
+
n?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await respecfully(sir, `Sender Newer (Partial History)`, async () => {
|
|
43
|
+
// 1. Setup Spaces
|
|
44
|
+
metaspace = new Metaspace_Innerspace(undefined);
|
|
45
|
+
await metaspace.initialize({
|
|
46
|
+
getFnAlert: () => async ({ title, msg }) => { },
|
|
47
|
+
getFnPrompt: () => async ({ title, msg }) => { return ''; },
|
|
48
|
+
getFnPromptPassword: () => async (title, msg) => { return null; },
|
|
49
|
+
});
|
|
50
|
+
while (!metaspace.initialized) { await delay(10); }
|
|
51
|
+
|
|
52
|
+
const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
|
|
53
|
+
await defaultLocalUserSpace!.initialized;
|
|
54
|
+
|
|
55
|
+
sourceSpace = new InnerSpace_V1({
|
|
56
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
57
|
+
name: 'source',
|
|
58
|
+
uuid: 'source_uuid',
|
|
59
|
+
description: 'source test space',
|
|
60
|
+
});
|
|
61
|
+
await sourceSpace.initialized;
|
|
62
|
+
|
|
63
|
+
destSpace = new InnerSpace_V1({
|
|
64
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
65
|
+
name: 'dest',
|
|
66
|
+
uuid: 'dest_uuid',
|
|
67
|
+
description: 'dest test space',
|
|
68
|
+
});
|
|
69
|
+
await destSpace.initialized;
|
|
70
|
+
|
|
71
|
+
// 2. See Data
|
|
72
|
+
// Root -> V1 (Shared) -> V2 (Source Only)
|
|
73
|
+
|
|
74
|
+
const root = await createTimelineRootHelper<TestData>({
|
|
75
|
+
ib: 'timeline_root_partial',
|
|
76
|
+
data: { type: 'root', label: 'Root' },
|
|
77
|
+
space: sourceSpace,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// V1
|
|
81
|
+
const v1 = await mut8Timeline<TestData>({
|
|
82
|
+
timeline: root,
|
|
83
|
+
mut8Opts: { dataToAddOrPatch: { type: 'comment', label: 'V1' } },
|
|
84
|
+
metaspace,
|
|
85
|
+
space: sourceSpace,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// V2 (Source Only)
|
|
89
|
+
const v2 = await mut8Timeline<TestData>({
|
|
90
|
+
timeline: v1,
|
|
91
|
+
mut8Opts: { dataToAddOrPatch: { type: 'comment', label: 'V2' } },
|
|
92
|
+
metaspace,
|
|
93
|
+
space: sourceSpace,
|
|
94
|
+
});
|
|
95
|
+
const addrV2 = getIbGibAddr({ ibGib: v2 });
|
|
96
|
+
|
|
97
|
+
// Transfer Root & V1 to Dest (Simulate previous sync)
|
|
98
|
+
await putInSpace({ space: destSpace, ibGibs: [root, v1] });
|
|
99
|
+
|
|
100
|
+
await ifWe(sir, 'verify setup', async () => {
|
|
101
|
+
// Verify Dest has V1
|
|
102
|
+
const checkV1 = await getFromSpace({ space: destSpace, addr: getIbGibAddr({ ibGib: v1 }) });
|
|
103
|
+
iReckon(sir, checkV1.success).asTo('Dest has V1').isGonnaBeTrue();
|
|
104
|
+
|
|
105
|
+
// Verify Dest does NOT have V2
|
|
106
|
+
const checkV2 = await getFromSpace({ space: destSpace, addr: addrV2 });
|
|
107
|
+
iReckon(sir, checkV2.success && !!checkV2.ibGibs?.length).asTo('Dest has V2').isGonnaBeFalse();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 3. Setup Sync
|
|
111
|
+
const mockKeystone = await getTestKeystoneServiceHelper();
|
|
112
|
+
const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
|
|
113
|
+
const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
|
|
114
|
+
|
|
115
|
+
const peer = new SyncPeerInnerspace_V1({
|
|
116
|
+
senderSpace: sourceSpace,
|
|
117
|
+
receiverSpace: destSpace,
|
|
118
|
+
receiverCoordinator: receiverCoordinator,
|
|
119
|
+
receiverMetaspace: metaspace,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// 4. Run Sync (Source Pushes V2)
|
|
123
|
+
console.log(`${lc} Running Sync...`);
|
|
124
|
+
const resSync = await senderCoordinator.sync({
|
|
125
|
+
peer: peer,
|
|
126
|
+
localSpace: sourceSpace,
|
|
127
|
+
metaspace: metaspace,
|
|
128
|
+
domainIbGibs: [v2],
|
|
129
|
+
useSessionIdentity: false,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await resSync.done;
|
|
133
|
+
|
|
134
|
+
// 5. Verify Dest (Should now have V2)
|
|
135
|
+
console.log(`${lc} Verifying Destination...`);
|
|
136
|
+
|
|
137
|
+
await ifWe(sir, `verify dest updated`, async () => {
|
|
138
|
+
// Verify Tip (V2)
|
|
139
|
+
const getV2 = await getFromSpace({ space: destSpace, addr: addrV2 });
|
|
140
|
+
iReckon(sir, getV2.success).asTo('V2 present in Dest').isGonnaBeTrue();
|
|
141
|
+
|
|
142
|
+
// Verify V2 points to V1
|
|
143
|
+
const v2IbGib = getV2.ibGibs![0];
|
|
144
|
+
const pastAddr = v2IbGib.rel8ns?.past?.at(-1);
|
|
145
|
+
iReckon(sir, pastAddr === getIbGibAddr({ ibGib: v1 })).asTo('V2 points to V1').isGonnaBeTrue();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
});
|
|
@@ -33,7 +33,7 @@ await respecfully(sir, `Sync InnerSpaces`, async () => {
|
|
|
33
33
|
let sourceSpace: InnerSpace_V1;
|
|
34
34
|
let destSpace: InnerSpace_V1;
|
|
35
35
|
|
|
36
|
-
await
|
|
36
|
+
await ifWe(sir, `Basic Push Sync (Source -> Dest)`, async () => {
|
|
37
37
|
// 1. Setup Spaces
|
|
38
38
|
metaspace = new Metaspace_Innerspace(undefined);
|
|
39
39
|
await metaspace.initialize({
|
|
@@ -150,18 +150,18 @@ await respecfully(sir, `Sync InnerSpaces`, async () => {
|
|
|
150
150
|
try {
|
|
151
151
|
const getChildInDest = await getFromSpace({ space: destSpace, addr: childAddr });
|
|
152
152
|
|
|
153
|
-
await
|
|
153
|
+
await ifWe(sir, `verify success getChildInDest`, async () => {
|
|
154
154
|
iReckon(sir, getChildInDest.success).asTo('Child present in Dest').isGonnaBeTrue();
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
await
|
|
157
|
+
await ifWe(sir, `verify getChildInDest.ibGibs`, async () => {
|
|
158
158
|
const firstChild = getChildInDest.ibGibs?.[0];
|
|
159
159
|
console.log(`${lc} firstChild: ${pretty(firstChild)}`);
|
|
160
160
|
iReckon(sir, firstChild?.data?.n).asTo('Child content matches').isGonnaBe(2);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
} catch (error) {
|
|
164
|
-
await
|
|
164
|
+
await ifWe(sir, `doh`, async () => {
|
|
165
165
|
// hack here I'm getting tired...
|
|
166
166
|
iReckon(sir, true).asTo(`error: ${extractErrorMsg(error)}`).isGonnaBeFalse();
|
|
167
167
|
});
|
|
@@ -381,7 +381,7 @@ export class SyncSagaCoordinator {
|
|
|
381
381
|
const result = await this.handleSagaFrame({
|
|
382
382
|
sagaIbGib: remoteFrame as SyncIbGib_V1,
|
|
383
383
|
srcGraph,
|
|
384
|
-
space:
|
|
384
|
+
space: localSpace, // Must be localSpace (Source) to find domain data
|
|
385
385
|
identity: sessionIdentity,
|
|
386
386
|
metaspace
|
|
387
387
|
});
|
|
@@ -610,14 +610,35 @@ export class SyncSagaCoordinator {
|
|
|
610
610
|
if (logalot) { console.log(`${lc} starting...`); }
|
|
611
611
|
|
|
612
612
|
// Extract Init Data
|
|
613
|
-
const initData = messageData as
|
|
613
|
+
const initData = messageData as SyncSagaMessageInitData_V1; // Using renamed variable for clarity
|
|
614
614
|
if (logalot) { console.log(`${lc} initData: ${pretty(initData)} (I: 46b0f8441b96ad7a388f1ce3239dd826)`); }
|
|
615
615
|
if (!initData || !initData.knowledgeVector) {
|
|
616
616
|
throw new Error(`${lc} Invalid init frame: missing knowledgeVector (E: ed02c869e028d2d06841b9c7f80f2826)`);
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
-
//
|
|
620
|
-
|
|
619
|
+
// 2. Gap Analysis
|
|
620
|
+
const conflicts: { tjp: string, localAddr?: string, remoteAddr: string, reason: string }[] = [];
|
|
621
|
+
const conflictStrategy: SyncConflictStrategy = initData.conflictStrategy || 'abort'; // Default to abort if not specified, or we should parameterize this
|
|
622
|
+
|
|
623
|
+
const deltaReqAddrs: string[] = [];
|
|
624
|
+
const pushOfferAddrs: string[] = [];
|
|
625
|
+
|
|
626
|
+
// Stones Analysis (Constants / Non-TJPs)
|
|
627
|
+
const stones = initData.stones || [];
|
|
628
|
+
if (stones.length > 0) {
|
|
629
|
+
if (logalot) { console.log(`${lc} processing stones: ${stones.length}`); }
|
|
630
|
+
// Check if we have these stones
|
|
631
|
+
const resStones = await getFromSpace({ space, addrs: stones });
|
|
632
|
+
const addrsNotFound = resStones.rawResultIbGib?.data?.addrsNotFound;
|
|
633
|
+
if (addrsNotFound && addrsNotFound.length > 0) {
|
|
634
|
+
if (logalot) { console.log(`${lc} stones missing (requesting): ${addrsNotFound.length}`); }
|
|
635
|
+
addrsNotFound.forEach(addr => {
|
|
636
|
+
if (!deltaReqAddrs.includes(addr)) {
|
|
637
|
+
deltaReqAddrs.push(addr);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
621
642
|
|
|
622
643
|
const remoteKV = initData.knowledgeVector;
|
|
623
644
|
if (logalot) { console.log(`${lc} remoteKV: ${pretty(remoteKV)} (I: 9f957862356dfeae183c200854e86e26)`); }
|
|
@@ -639,11 +660,7 @@ export class SyncSagaCoordinator {
|
|
|
639
660
|
}
|
|
640
661
|
|
|
641
662
|
// 2. Gap Analysis
|
|
642
|
-
const conflicts: { tjp: string, localAddr?: string, remoteAddr: string, reason: string }[] = [];
|
|
643
|
-
const conflictStrategy: SyncConflictStrategy = initData.conflictStrategy || 'abort'; // Default to abort if not specified, or we should parameterize this
|
|
644
663
|
|
|
645
|
-
const deltaReqAddrs: string[] = [];
|
|
646
|
-
const pushOfferAddrs: string[] = [];
|
|
647
664
|
|
|
648
665
|
for (const tjp of remoteTjps) {
|
|
649
666
|
const remoteAddr = remoteKV[tjp];
|
|
@@ -738,6 +755,96 @@ export class SyncSagaCoordinator {
|
|
|
738
755
|
return { frame: conflictFrame };
|
|
739
756
|
}
|
|
740
757
|
|
|
758
|
+
// 2. Add Push Offers (Missing in Local)
|
|
759
|
+
// Check if we have them. If not, ask for them.
|
|
760
|
+
for (const addr of pushOfferAddrs) {
|
|
761
|
+
const hasIt = await getFromSpace({ addr, space });
|
|
762
|
+
if (!hasIt.success || !hasIt.ibGibs || hasIt.ibGibs.length === 0) {
|
|
763
|
+
// If we don't have it, we put it in `deltaReqAddrs` of the Ack.
|
|
764
|
+
deltaReqAddrs.push(addr);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 3. Build Knowledge Vector (Full History for known timelines)
|
|
769
|
+
// [NEW] Smart Diff
|
|
770
|
+
// We iterate over all relevant addresses (deltas we are requesting OR push offers we might have newer versions of).
|
|
771
|
+
// Since we are "reacting" to Init, we primarily want to tell the Sender what we DO have for the things they talked about.
|
|
772
|
+
|
|
773
|
+
const knowledgeVector: { [groupKey: string]: string[] } = {};
|
|
774
|
+
const relevantAddrs = new Set([...pushOfferAddrs, ...deltaReqAddrs]);
|
|
775
|
+
|
|
776
|
+
// [Smart Diff] Populate knowledge from timelines identified by Sender
|
|
777
|
+
for (const tjp of remoteTjps) {
|
|
778
|
+
const localAddr = localKV[tjp];
|
|
779
|
+
if (localAddr) {
|
|
780
|
+
const res = await getFromSpace({ addr: localAddr, space });
|
|
781
|
+
if (res.success && res.ibGibs?.[0]) {
|
|
782
|
+
const ibGib = res.ibGibs[0];
|
|
783
|
+
const realTjp = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib }); // Should match `tjp` if normalized
|
|
784
|
+
if (!knowledgeVector[realTjp]) {
|
|
785
|
+
const past = ibGib.rel8ns?.past || [];
|
|
786
|
+
knowledgeVector[realTjp] = [getIbGibAddr({ ibGib }), ...past];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// [Smart Diff] Also check individual requested addresses (Fall back for constants/unknown TJPs)
|
|
793
|
+
for (const addr of relevantAddrs) {
|
|
794
|
+
// Only process if not already covered by TJP logic above
|
|
795
|
+
// We can't really know if it's covered easily without resolving.
|
|
796
|
+
// But if we don't have it (requesting), we won't find it here anyway.
|
|
797
|
+
// If we DO have it (push offer), we might find it.
|
|
798
|
+
const res = await getFromSpace({ addr, space });
|
|
799
|
+
if (res.success && res.ibGibs?.[0]) {
|
|
800
|
+
const ibGib = res.ibGibs[0];
|
|
801
|
+
const tjpAddr = ibGib.rel8ns?.tjp?.[0] || getIbGibAddr({ ibGib });
|
|
802
|
+
|
|
803
|
+
if (!knowledgeVector[tjpAddr]) {
|
|
804
|
+
const past = ibGib.rel8ns?.past || [];
|
|
805
|
+
knowledgeVector[tjpAddr] = [getIbGibAddr({ ibGib }), ...past];
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
// We don't have `addr`.
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Also populate from `knowledgeVector` in Init if we want bidirectional?
|
|
813
|
+
// No, `Init` doesn't have `knowledgeVector` in `SyncSagaMessageInitData` yet (it has `SyncInitData` generic props).
|
|
814
|
+
// Let's assume standard flow:
|
|
815
|
+
// 1. Sender says "I have X"
|
|
816
|
+
// 2. Receiver says "I don't have X. But if I did have Y (ancestor), I'd tell you."
|
|
817
|
+
// Problem: Receiver doesn't know X is related to Y without X.
|
|
818
|
+
|
|
819
|
+
// SOLUTION:
|
|
820
|
+
// The *Sender* must include TJP/Past info in `Init`? Or `pushOfferAddrs` should be richer?
|
|
821
|
+
// OR: Receiver does a check.
|
|
822
|
+
// Wait, if Sender sends V2, it's just an address.
|
|
823
|
+
// If Receiver doesn't have it, it's opaque.
|
|
824
|
+
|
|
825
|
+
// REVISIT "Constant / No TJP" logic:
|
|
826
|
+
// IF we are testing "Sender Newer", meaning Sender has V2, Receiver has V1.
|
|
827
|
+
// Sender calls `sync([V2])`. Init Frame contains `stones: [V2_Address]`.
|
|
828
|
+
// Receiver checks V2_Address. Not found.
|
|
829
|
+
// Receiver requests V2.
|
|
830
|
+
// Receiver sends Ack(DeltaReq: [V2], Knowledge: {}).
|
|
831
|
+
// Sender receives Ack. Sender sends V2 *AND* its deps (V1, Root).
|
|
832
|
+
// Receiver has V1. Sender sends V1 anyway.
|
|
833
|
+
// This is "Naive Deep Sync".
|
|
834
|
+
|
|
835
|
+
// TO ACHIEVE "Smart Diff":
|
|
836
|
+
// Receiver needs to know "Oh, V2 is a timeline tip of TJP_A".
|
|
837
|
+
// If Sender doesn't send TJP info, Receiver is blind.
|
|
838
|
+
|
|
839
|
+
// Proposed Fix (Short Term):
|
|
840
|
+
// `SyncInitData` should include TJP mappings or we rely on `knowledgeVector` in `Init`?
|
|
841
|
+
// `SyncSagaMessageInitData_V1` extends `SyncInitData`.
|
|
842
|
+
// `SyncInitData` has `knowledgeVector`.
|
|
843
|
+
// If Sender populates `knowledgeVector` in `Init`, Receiver can use keys (TJPs) to look up its own state!
|
|
844
|
+
|
|
845
|
+
// Let's implement Sender populating `Init.knowledgeVector`.
|
|
846
|
+
// But `SyncSagaCoordinator.startSaga` creates Init.
|
|
847
|
+
|
|
741
848
|
// 3. Create Ack Frame
|
|
742
849
|
const sagaId = sagaIbGib.data!.uuid;
|
|
743
850
|
// Create Payload Stone
|
|
@@ -746,6 +853,7 @@ export class SyncSagaCoordinator {
|
|
|
746
853
|
stage: SyncStage.ack,
|
|
747
854
|
deltaReqAddrs,
|
|
748
855
|
pushOfferAddrs,
|
|
856
|
+
knowledgeVector,
|
|
749
857
|
};
|
|
750
858
|
|
|
751
859
|
const ackStone = await this.createSyncMsgStone({
|
|
@@ -820,7 +928,17 @@ export class SyncSagaCoordinator {
|
|
|
820
928
|
}
|
|
821
929
|
|
|
822
930
|
// 2. Process Delta Requests (Push Payload)
|
|
931
|
+
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
932
|
+
const skipAddrs = new Set<string>();
|
|
933
|
+
if (ackData.knowledgeVector) {
|
|
934
|
+
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
935
|
+
addrs.forEach(a => skipAddrs.add(a));
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
823
939
|
const payloadIbGibs: IbGib_V1[] = [];
|
|
940
|
+
// Gather all tips to sync first
|
|
941
|
+
const tipsToSync: IbGib_V1[] = [];
|
|
824
942
|
for (const addr of deltaReqAddrs) {
|
|
825
943
|
let ibGib = srcGraph[addr];
|
|
826
944
|
if (!ibGib) {
|
|
@@ -830,12 +948,58 @@ export class SyncSagaCoordinator {
|
|
|
830
948
|
}
|
|
831
949
|
}
|
|
832
950
|
if (ibGib) {
|
|
833
|
-
|
|
951
|
+
tipsToSync.push(ibGib);
|
|
834
952
|
} else {
|
|
835
953
|
throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
836
954
|
}
|
|
837
955
|
}
|
|
838
956
|
|
|
957
|
+
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
958
|
+
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
959
|
+
// `getDependencyGraph` takes a single ibGib.
|
|
960
|
+
// We can optimize by doing it for each tip and unioning the result?
|
|
961
|
+
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
962
|
+
// We will loop.
|
|
963
|
+
|
|
964
|
+
const allDepsSet = new Set<string>();
|
|
965
|
+
|
|
966
|
+
for (const tip of tipsToSync) {
|
|
967
|
+
// Always include the tip itself
|
|
968
|
+
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
969
|
+
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
970
|
+
// But detailed deps might be skipped.
|
|
971
|
+
|
|
972
|
+
// Get Graph with Skips
|
|
973
|
+
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
974
|
+
const deps = await getDependencyGraph({
|
|
975
|
+
ibGib: tip,
|
|
976
|
+
space,
|
|
977
|
+
skipAddrs: Array.from(skipAddrs)
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
981
|
+
let tipIncluded = false;
|
|
982
|
+
|
|
983
|
+
if (deps) {
|
|
984
|
+
Object.values(deps).forEach(d => {
|
|
985
|
+
const dAddr = getIbGibAddr({ ibGib: d });
|
|
986
|
+
if (!allDepsSet.has(dAddr)) {
|
|
987
|
+
allDepsSet.add(dAddr);
|
|
988
|
+
payloadIbGibs.push(d);
|
|
989
|
+
}
|
|
990
|
+
if (dAddr === tipAddr) { tipIncluded = true; }
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (!tipIncluded && !skipAddrs.has(tipAddr)) {
|
|
995
|
+
if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
|
|
996
|
+
if (!allDepsSet.has(tipAddr)) {
|
|
997
|
+
allDepsSet.add(tipAddr);
|
|
998
|
+
payloadIbGibs.push(tip);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
839
1003
|
// 3. Create Delta Frame
|
|
840
1004
|
const sagaId = ackData.sagaId;
|
|
841
1005
|
const deltaData: SyncSagaMessageDeltaData_V1 = {
|
|
@@ -42,6 +42,11 @@ export interface SyncSagaMessageAckData_V1 extends SyncSagaMessageData_V1 {
|
|
|
42
42
|
stage: typeof SyncStage.ack;
|
|
43
43
|
deltaReqAddrs: string[];
|
|
44
44
|
pushOfferAddrs: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Map of group keys (TJP or Constant Addr) to list of known addresses (Full History).
|
|
47
|
+
* Used by Sender to calculate differential payloads.
|
|
48
|
+
*/
|
|
49
|
+
knowledgeVector?: { [groupKey: string]: string[] };
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
export interface SyncSagaMessageDeltaData_V1 extends SyncSagaMessageData_V1, SyncDeltaData {
|