@ibgib/core-gib 0.1.19 → 0.1.21

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.
Files changed (81) hide show
  1. package/dist/common/other/ibgib-helper.d.mts +13 -0
  2. package/dist/common/other/ibgib-helper.d.mts.map +1 -1
  3. package/dist/common/other/ibgib-helper.mjs +44 -0
  4. package/dist/common/other/ibgib-helper.mjs.map +1 -1
  5. package/dist/sync/graft-info/graft-info-constants.d.mts +5 -0
  6. package/dist/sync/graft-info/graft-info-constants.d.mts.map +1 -0
  7. package/dist/sync/graft-info/graft-info-constants.mjs +5 -0
  8. package/dist/sync/graft-info/graft-info-constants.mjs.map +1 -0
  9. package/dist/sync/graft-info/graft-info-helpers.d.mts +49 -0
  10. package/dist/sync/graft-info/graft-info-helpers.d.mts.map +1 -0
  11. package/dist/sync/graft-info/graft-info-helpers.mjs +236 -0
  12. package/dist/sync/graft-info/graft-info-helpers.mjs.map +1 -0
  13. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts +2 -0
  14. package/dist/sync/graft-info/graft-info-helpers.respec.d.mts.map +1 -0
  15. package/dist/sync/graft-info/graft-info-helpers.respec.mjs +70 -0
  16. package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -0
  17. package/dist/sync/graft-info/graft-info-types.d.mts +31 -0
  18. package/dist/sync/graft-info/graft-info-types.d.mts.map +1 -0
  19. package/dist/sync/graft-info/graft-info-types.mjs +2 -0
  20. package/dist/sync/graft-info/graft-info-types.mjs.map +1 -0
  21. package/dist/sync/strategies/conflict-optimistic.d.mts +37 -0
  22. package/dist/sync/strategies/conflict-optimistic.d.mts.map +1 -0
  23. package/dist/sync/strategies/conflict-optimistic.mjs +112 -0
  24. package/dist/sync/strategies/conflict-optimistic.mjs.map +1 -0
  25. package/dist/sync/sync-conflict.respec.d.mts +8 -0
  26. package/dist/sync/sync-conflict.respec.d.mts.map +1 -0
  27. package/dist/sync/sync-conflict.respec.mjs +277 -0
  28. package/dist/sync/sync-conflict.respec.mjs.map +1 -0
  29. package/dist/sync/sync-constants.d.mts +1 -3
  30. package/dist/sync/sync-constants.d.mts.map +1 -1
  31. package/dist/sync/sync-constants.mjs +0 -2
  32. package/dist/sync/sync-constants.mjs.map +1 -1
  33. package/dist/sync/sync-innerspace-constants.respec.mjs +2 -2
  34. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  35. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +0 -1
  36. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  37. package/dist/sync/sync-innerspace.respec.mjs +1 -1
  38. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  39. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts +5 -2
  40. package/dist/sync/sync-peer/sync-peer-innerspace-v1.d.mts.map +1 -1
  41. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs +70 -7
  42. package/dist/sync/sync-peer/sync-peer-innerspace-v1.mjs.map +1 -1
  43. package/dist/sync/sync-saga-coordinator.d.mts +45 -27
  44. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  45. package/dist/sync/sync-saga-coordinator.mjs +811 -253
  46. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  47. package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts +11 -0
  48. package/dist/sync/sync-saga-message/sync-saga-message-helpers.d.mts.map +1 -1
  49. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs +25 -0
  50. package/dist/sync/sync-saga-message/sync-saga-message-helpers.mjs.map +1 -1
  51. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +24 -12
  52. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  53. package/dist/sync/sync-types.d.mts +31 -4
  54. package/dist/sync/sync-types.d.mts.map +1 -1
  55. package/dist/sync/sync-types.mjs.map +1 -1
  56. package/ibgib-foundations.md +147 -0
  57. package/package.json +1 -1
  58. package/roadmap.md +59 -0
  59. package/src/common/other/ibgib-helper.mts +52 -0
  60. package/src/keystone/README.md +13 -155
  61. package/src/keystone/docs/architecture.md +55 -0
  62. package/src/sync/README.md +37 -42
  63. package/src/sync/docs/architecture.md +69 -0
  64. package/src/sync/docs/verification.md +43 -0
  65. package/src/sync/graft-info/graft-info-constants.mts +4 -0
  66. package/src/sync/graft-info/graft-info-helpers.mts +308 -0
  67. package/src/sync/graft-info/graft-info-helpers.respec.mts +83 -0
  68. package/src/sync/graft-info/graft-info-types.mts +33 -0
  69. package/src/sync/strategies/conflict-optimistic.mts +149 -0
  70. package/src/sync/sync-conflict.respec.mts +330 -0
  71. package/src/sync/sync-constants.mts +1 -4
  72. package/src/sync/sync-innerspace-constants.respec.mts +1 -1
  73. package/src/sync/sync-innerspace-deep-updates.respec.mts +0 -1
  74. package/src/sync/sync-innerspace.respec.mts +1 -1
  75. package/src/sync/sync-peer/sync-peer-innerspace-v1.mts +85 -12
  76. package/src/sync/sync-saga-coordinator.mts +905 -268
  77. package/src/sync/sync-saga-message/sync-saga-message-helpers.mts +43 -0
  78. package/src/sync/sync-saga-message/sync-saga-message-types.mts +23 -11
  79. package/src/sync/sync-types.mts +33 -4
  80. package/test_output.log +0 -0
  81. package/tmp.md +44 -426
@@ -0,0 +1,308 @@
1
+ import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
2
+ import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
3
+ import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
4
+ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { rel8 } from "@ibgib/ts-gib/dist/V1/transforms/rel8.mjs";
6
+
7
+ import { IbGibSpaceAny } from "../../witness/space/space-base-v1.mjs";
8
+ import { getFromSpace, putInSpace } from "../../witness/space/space-helper.mjs";
9
+ import { applyTransforms } from "../../witness/space/reconciliation-space/reconciliation-space-helper.mjs";
10
+ import {
11
+ GRAFT_INFO_ATOM,
12
+ GRAFT_BASE_REL8N_NAME,
13
+ GRAFT_ORPHAN_REL8N_NAME,
14
+ GRAFT_INFO_REL8N_NAME
15
+ } from "./graft-info-constants.mjs";
16
+ import { GraftInfoData_V1, GraftInfoIbGib_V1, GraftInfoIb_V1 } from "./graft-info-types.mjs";
17
+
18
+ const lc = `[graft-info-helpers]`;
19
+
20
+ /**
21
+ * Validates and constructs the `ib` object for a Graft Info ibGib.
22
+ */
23
+ export async function getGraftInfoIb({
24
+ data
25
+ }: {
26
+ data: GraftInfoData_V1
27
+ }): Promise<string> {
28
+ if (!data.algo) { throw new Error(`(UNEXPECTED) data.algo required for GraftInfoIb (E: 8a9b0c1d2e3f4g5h)`); }
29
+
30
+ // graft_info algo
31
+ return [GRAFT_INFO_ATOM, data.algo].join(' ');
32
+ }
33
+
34
+ /**
35
+ * Parses a standard Graft Info 'ib' string.
36
+ */
37
+ export async function parseGraftInfoIb({
38
+ ib
39
+ }: {
40
+ ib: string
41
+ }): Promise<GraftInfoIb_V1> {
42
+ try {
43
+ const parts = ib.split(' ');
44
+ if (parts[0] !== GRAFT_INFO_ATOM) { throw new Error(`Atom mismatch. Expected ${GRAFT_INFO_ATOM} (E: 8f03c92a95144b618821915632599266)`); }
45
+ if (parts.length < 2) { throw new Error(`Invalid graft info ib. Expected at least 2 parts. (E: 9c2b4c8a6d34469f8263544710183355)`); }
46
+
47
+ const algo = parts.slice(1).join(' '); // allow spaces in algo? usually single word but safe to join.
48
+
49
+ return {
50
+ atom: GRAFT_INFO_ATOM,
51
+ algo,
52
+ };
53
+ } catch (error) {
54
+ console.error(`${lc} ${extractErrorMsg(error)}`);
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Creates a new GraftInfo ibGib.
61
+ */
62
+ export async function createGraftInfo({
63
+ algo,
64
+ details,
65
+ }: {
66
+ algo: string,
67
+ details?: string,
68
+ }): Promise<GraftInfoIbGib_V1> {
69
+ const data: GraftInfoData_V1 = {
70
+ algo,
71
+ details,
72
+ };
73
+
74
+ const ib = await getGraftInfoIb({ data });
75
+
76
+ const res = await Factory_V1.stone({
77
+ ib,
78
+ data,
79
+ rel8ns: {}, // No default rel8ns (no 'ancestors' anymore)
80
+ parentPrimitiveIb: GRAFT_INFO_ATOM,
81
+ uuid: true,
82
+ });
83
+
84
+ return res as GraftInfoIbGib_V1;
85
+ }
86
+
87
+ /**
88
+ * Naive line-by-line text merge using LCS.
89
+ * Renamed to mergeTextLCS for clarity, but logic is generic LCS.
90
+ */
91
+ export async function mergeTextLCS({
92
+ textA,
93
+ textB,
94
+ }: {
95
+ textA: string,
96
+ textB: string,
97
+ }): Promise<string> {
98
+ if (textA === textB) { return textA; }
99
+
100
+ const linesA = textA.split('\n');
101
+ const linesB = textB.split('\n');
102
+
103
+ const common = longestCommonSubsequence(linesA, linesB); // Array of { line, indexA, indexB }
104
+
105
+ let resultLines: string[] = [];
106
+ let currentA = 0;
107
+ let currentB = 0;
108
+
109
+ for (const match of common) {
110
+ // Add unique lines from A (before this match)
111
+ while (currentA < match.indexA) {
112
+ resultLines.push(linesA[currentA]);
113
+ currentA++;
114
+ }
115
+ // Add unique lines from B (before this match)
116
+ while (currentB < match.indexB) {
117
+ resultLines.push(linesB[currentB]);
118
+ currentB++;
119
+ }
120
+ // Add the common line
121
+ resultLines.push(match.line);
122
+ currentA++;
123
+ currentB++;
124
+ }
125
+
126
+ // Add remaining unique lines from A
127
+ while (currentA < linesA.length) {
128
+ resultLines.push(linesA[currentA]);
129
+ currentA++;
130
+ }
131
+ // Add remaining unique lines from B
132
+ while (currentB < linesB.length) {
133
+ resultLines.push(linesB[currentB]);
134
+ currentB++;
135
+ }
136
+
137
+ return resultLines.join('\n');
138
+ }
139
+
140
+ interface LcsMatch {
141
+ line: string;
142
+ indexA: number;
143
+ indexB: number;
144
+ }
145
+
146
+ function longestCommonSubsequence(a: string[], b: string[]): LcsMatch[] {
147
+ const m = a.length;
148
+ const n = b.length;
149
+ const dp: number[][] = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
150
+
151
+ // Fill DP table
152
+ for (let i = 1; i <= m; i++) {
153
+ for (let j = 1; j <= n; j++) {
154
+ if (a[i - 1] === b[j - 1]) {
155
+ dp[i][j] = dp[i - 1][j - 1] + 1;
156
+ } else {
157
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
158
+ }
159
+ }
160
+ }
161
+
162
+ // Backtrack to find LCS
163
+ let i = m, j = n;
164
+ const lcs: LcsMatch[] = [];
165
+ while (i > 0 && j > 0) {
166
+ if (a[i - 1] === b[j - 1]) {
167
+ lcs.unshift({ line: a[i - 1], indexA: i - 1, indexB: j - 1 });
168
+ i--;
169
+ j--;
170
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
171
+ i--;
172
+ } else {
173
+ j--;
174
+ }
175
+ }
176
+ return lcs;
177
+ }
178
+
179
+ /**
180
+ * Grafts two divergent timelines by splicing missing DNA from one branch (Orphan) onto another (Base).
181
+ *
182
+ * Logic:
183
+ * 1. Checks timestamps of tips. Earlier = Base, Later = Orphan.
184
+ * 2. Fetches DNA from Orphan's branch (since LCA).
185
+ * 3. Replays Orphan's DNA onto Base.
186
+ * 4. Creates GraftInfo linking Base and Orphan.
187
+ * 5. Rel8s the Result to GraftInfo.
188
+ */
189
+ export async function graftTimelines({
190
+ tipA,
191
+ branchA,
192
+ tipB,
193
+ branchB,
194
+ space,
195
+ metaspace,
196
+ }: {
197
+ tipA: IbGib_V1,
198
+ branchA: IbGib_V1[],
199
+ tipB: IbGib_V1,
200
+ branchB: IbGib_V1[],
201
+ space: IbGibSpaceAny,
202
+ metaspace: any,
203
+ }): Promise<IbGib_V1> {
204
+ const lc = `[${graftTimelines.name}]`;
205
+ console.log(`${lc} [TEST DEBUG] starting... Metaspace present: ${!!metaspace}`);
206
+
207
+ // 1. Determine Base and Orphan based on timestamp
208
+ let baseTip: IbGib_V1;
209
+ let orphanTip: IbGib_V1; // The one providing the DNA to be replayed
210
+ let orphanBranch: IbGib_V1[];
211
+
212
+ const getTs = (ibGib: IbGib_V1) => {
213
+ const ms = ibGib.data?.timestampMs ?? 0;
214
+ if (!ms && ibGib.data?.timestamp) { return new Date(ibGib.data.timestamp).getTime(); }
215
+ return ms;
216
+ }
217
+
218
+ const tsA = getTs(tipA);
219
+ const tsB = getTs(tipB);
220
+
221
+ if (tsA <= tsB) {
222
+ // A is earlier (or equal), use A as base
223
+ baseTip = tipA;
224
+ orphanTip = tipB;
225
+ orphanBranch = branchB;
226
+ } else {
227
+ // B is earlier, use B as base
228
+ baseTip = tipB;
229
+ orphanTip = tipA;
230
+ orphanBranch = branchA;
231
+ }
232
+
233
+ const baseAddr = getIbGibAddr({ ibGib: baseTip });
234
+ const orphanAddr = getIbGibAddr({ ibGib: orphanTip });
235
+
236
+ // 2. Gather DNA addresses from the Orphan branch
237
+ const dnaAddrsToApply: string[] = [];
238
+ for (const ibGib of orphanBranch) {
239
+ const dnas = ibGib.rel8ns?.dna || [];
240
+ dnaAddrsToApply.push(...dnas);
241
+ }
242
+
243
+ if (dnaAddrsToApply.length === 0) {
244
+ return baseTip;
245
+ }
246
+
247
+ // 3. Fetch the DNA stones
248
+ const dnaRes = await getFromSpace({ space, addrs: dnaAddrsToApply });
249
+ if (!dnaRes.success || !dnaRes.ibGibs) {
250
+ throw new Error(`Failed to fetch DNA stones for replay. (E: 3a2b1c4d5e6f7g8h)`);
251
+ }
252
+ const allDnaIbGibs = dnaRes.ibGibs;
253
+
254
+ // 4. Replay Transforms onto Base
255
+ const createdIbGibs_Running: IbGib_V1[] = [];
256
+
257
+ const mergedTip = await applyTransforms({
258
+ src: baseTip,
259
+ dnaAddrsToApplyToStoreVersion: [...dnaAddrsToApply],
260
+ allLocalIbGibs: allDnaIbGibs,
261
+ createdIbGibs_Running,
262
+ });
263
+
264
+ // 5. Persist the new stones
265
+ await putInSpace({ space, ibGibs: createdIbGibs_Running });
266
+
267
+ // 6. Create Graft Info
268
+ const graftInfo = await createGraftInfo({
269
+ algo: 'optimistic_dna_replay',
270
+ details: `Replayed ${dnaAddrsToApply.length} transforms from orphan (${orphanAddr}) onto base (${baseAddr}).`,
271
+ });
272
+
273
+ // Add relations via rel8
274
+ const graftInfoRel8nsToAdd = {
275
+ [GRAFT_BASE_REL8N_NAME]: [baseAddr],
276
+ [GRAFT_ORPHAN_REL8N_NAME]: [orphanAddr],
277
+ };
278
+
279
+ const { newIbGib: graftInfoWithRel8ns } = await rel8({
280
+ src: graftInfo,
281
+ rel8nsToAddByAddr: graftInfoRel8nsToAdd,
282
+ dna: true,
283
+ });
284
+ // Persist this update
285
+ await putInSpace({ space, ibGibs: [graftInfoWithRel8ns] });
286
+
287
+ // 7. Link GraftInfo to Result via 'graftinfo' rel8n
288
+ const graftInfoAddr = getIbGibAddr({ ibGib: graftInfoWithRel8ns });
289
+ const { newIbGib: finalIbGib, dnas: rel8Dnas, intermediateIbGibs } = await rel8({
290
+ src: mergedTip,
291
+ rel8nsToAddByAddr: {
292
+ [GRAFT_INFO_REL8N_NAME]: [graftInfoAddr],
293
+ },
294
+ dna: true, // Auto-link 'dna'
295
+ nCounter: true,
296
+ });
297
+
298
+ // Persist final result
299
+ const finalStones = [finalIbGib, ...(rel8Dnas || []), ...(intermediateIbGibs || [])];
300
+ await putInSpace({ space, ibGibs: finalStones });
301
+
302
+ // 8. Register New Tip in Metaspace (CRITICAL for updating Timeline Index)
303
+ if (metaspace && metaspace.registerNewIbGib) {
304
+ await metaspace.registerNewIbGib({ ibGib: finalIbGib, space });
305
+ }
306
+
307
+ return finalIbGib;
308
+ }
@@ -0,0 +1,83 @@
1
+ import { iReckon, respecfully, ifWe, ifWeMight } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
2
+ import { mergeTextLCS } from './graft-info-helpers.mjs';
3
+
4
+ const maam = `[${import.meta.url}]`;
5
+ const sir = maam;
6
+
7
+ await respecfully(sir, 'Graft Info Helpers', async () => {
8
+
9
+ await respecfully(sir, 'mergeTextLCS', async () => {
10
+
11
+ await ifWe(sir, 'mergeTextLCS should interleave unique lines (A then B)', async () => {
12
+ const textA = "Line 1\nLine A\nLine 3";
13
+ const textB = "Line 1\nLine B\nLine 3";
14
+ const res = await mergeTextLCS({ textA, textB });
15
+ const expected = "Line 1\nLine A\nLine B\nLine 3";
16
+ iReckon(sir, res).asTo('result').isGonnaBe(expected);
17
+ });
18
+
19
+ await ifWe(sir, 'mergeTextLCS should handle insertions', async () => {
20
+ const textA = "Line 1\nLine 3";
21
+ const textB = "Line 1\nLine 2\nLine 3";
22
+ const res = await mergeTextLCS({ textA, textB });
23
+ const expected = "Line 1\nLine 2\nLine 3";
24
+ iReckon(sir, res).asTo('result').isGonnaBe(expected);
25
+ });
26
+
27
+ await ifWe(sir, 'mergeTextLCS should handle appends', async () => {
28
+ const textA = "Line 1";
29
+ const textB = "Line 1\nLine 2";
30
+ const res = await mergeTextLCS({ textA, textB });
31
+ const expected = "Line 1\nLine 2";
32
+ iReckon(sir, res).asTo('result').isGonnaBe(expected);
33
+ });
34
+
35
+ await ifWe(sir, 'mergeTextLCS should handle simultaneous list additions', async () => {
36
+ const textA = "- Item 1\n- Item 2\n- Item A";
37
+ const textB = "- Item 1\n- Item 2\n- Item B";
38
+ const res = await mergeTextLCS({ textA, textB });
39
+
40
+ // Expected: Common (1, 2) then A then B.
41
+ const expected = "- Item 1\n- Item 2\n- Item A\n- Item B";
42
+ iReckon(sir, res).asTo('result').isGonnaBe(expected);
43
+ });
44
+
45
+ await ifWe(sir, 'mergeTextLCS should handle distinct modifications in code block', async () => {
46
+ const textA = `function foo() {
47
+ console.log("start");
48
+ doA();
49
+ console.log("end");
50
+ }`;
51
+ const textB = `function foo() {
52
+ console.log("start");
53
+ doB();
54
+ console.log("end");
55
+ }`;
56
+ const res = await mergeTextLCS({ textA, textB });
57
+
58
+ const expected = `function foo() {
59
+ console.log("start");
60
+ doA();
61
+ doB();
62
+ console.log("end");
63
+ }`;
64
+ iReckon(sir, res).asTo('result').isGonnaBe(expected);
65
+ });
66
+
67
+ await ifWe(sir, 'mergeTextLCS should return same string if identical', async () => {
68
+ const textA = "Hello";
69
+ const res = await mergeTextLCS({ textA, textB: textA });
70
+ iReckon(sir, res).asTo('result').isGonnaBe(textA);
71
+ });
72
+
73
+ });
74
+
75
+ await respecfully(sir, 'graftTimelines', async () => {
76
+ // We need a more complex setup to test this properly (mock space, etc.)
77
+ // For now, this placeholder ensures the test file runs.
78
+ await ifWe(sir, 'graftTimelines placeholders', async () => {
79
+ iReckon(sir, true).isGonnaBeTrue();
80
+ });
81
+ });
82
+
83
+ });
@@ -0,0 +1,33 @@
1
+ import { IbGib_V1, IbGibData_V1, IbGibRel8ns_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
2
+ import { GRAFT_INFO_ATOM } from "./graft-info-constants.mjs";
3
+
4
+ export interface GraftInfoIb_V1 {
5
+ atom: typeof GRAFT_INFO_ATOM;
6
+ algo: string; // e.g. "optimistic_dna_replay", "text_lcs", "manual"
7
+ }
8
+
9
+ export interface GraftInfoData_V1 extends IbGibData_V1 {
10
+ /**
11
+ * The algorithm or strategy used to perform the graft.
12
+ */
13
+ algo: string;
14
+ /**
15
+ * Optional details about the graft, e.g. validation results or specifics about conflicts resolved.
16
+ */
17
+ details?: string;
18
+ }
19
+
20
+ export interface GraftInfoRel8ns_V1 extends IbGibRel8ns_V1 {
21
+ /**
22
+ * The 'base' of the graft (the earlier tip, usually).
23
+ * This is the timeline we are grafting ONTO.
24
+ */
25
+ graftbase?: string[];
26
+ /**
27
+ * The 'orphan' tip (the later tip, usually) effectively grafted into the base.
28
+ * This is the branch that ends here, its DNA spliced into the base.
29
+ */
30
+ graftorphan?: string[];
31
+ }
32
+
33
+ export interface GraftInfoIbGib_V1 extends IbGib_V1<GraftInfoData_V1, GraftInfoRel8ns_V1> { }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @module conflict-optimistic
3
+ *
4
+ * Implements the Optimistic Conflict Resolution Strategy.
5
+ *
6
+ * Logic:
7
+ * 1. findLCA: Traverses `past` to find common ancestor.
8
+ * 2. mergeDivergentTimelines: Replays DNA to create a merged tip.
9
+ */
10
+
11
+ import { IbGib_V1, IbGibRel8ns_V1, IbGibData_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
12
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
13
+ import { IbGibSpaceAny } from '../../witness/space/space-base-v1.mjs';
14
+ import { getFromSpace, putInSpace } from '../../witness/space/space-helper.mjs';
15
+ import { mut8Timeline } from '../../timeline/timeline-api.mjs';
16
+
17
+ const lc = `[conflict-optimistic]`;
18
+
19
+ export interface LCAResult {
20
+ lcaAddr: string;
21
+ branchA: IbGib_V1[]; // Path from LCA to Tip A (exclusive of LCA)
22
+ branchB: IbGib_V1[]; // Path from LCA to Tip B (exclusive of LCA)
23
+ }
24
+
25
+ /**
26
+ * Finds the Last Common Ancestor (LCA) between two divergent timelines.
27
+ *
28
+ * Note: simplistic implementation assuming simple linear divergence.
29
+ * Complex DAGs might require more robust graph traversal.
30
+ */
31
+ export async function findLCA({
32
+ tipA,
33
+ tipB,
34
+ space,
35
+ }: {
36
+ tipA: IbGib_V1,
37
+ tipB: IbGib_V1,
38
+ space: IbGibSpaceAny,
39
+ }): Promise<LCAResult> {
40
+ // 1. Load history for both
41
+ // Optimization: Just load 'past' array if available.
42
+ // Ideally we assume 'past' is full history for V1.
43
+
44
+ // Check if one is ancestor of another first?
45
+ const pastA = tipA.rel8ns?.past || [];
46
+ const pastB = tipB.rel8ns?.past || [];
47
+ const addrA = getIbGibAddr({ ibGib: tipA });
48
+ const addrB = getIbGibAddr({ ibGib: tipB });
49
+
50
+ // Is B in A's past?
51
+ if (pastA.includes(addrB)) { return { lcaAddr: addrB, branchA: [], branchB: [] }; } // Not a divergence, just an update.
52
+ // Is A in B's past?
53
+ if (pastB.includes(addrA)) { return { lcaAddr: addrA, branchA: [], branchB: [] }; }
54
+
55
+ // Find intersection
56
+ let lcaAddr: string | undefined;
57
+
58
+ // Iterate backwards through A's past
59
+ // The *latest* entry in A's past that is also in B's past (or is B itself, but we checked that)
60
+ // Actually, 'past' is usually ordered? V1 convention is accretive, so order matches evolution.
61
+ // But 'past' array order isn't strictly guaranteed by schema to be chronological, but robust implementations do so.
62
+ // Let's assume Set intersection.
63
+
64
+ const setPastB = new Set(pastB);
65
+ // Find the *latest* item in pastA that is in setPastB.
66
+ // We assume pastA is ordered [older -> newer].
67
+ for (let i = pastA.length - 1; i >= 0; i--) {
68
+ const candidate = pastA[i];
69
+ if (setPastB.has(candidate)) {
70
+ lcaAddr = candidate;
71
+ break;
72
+ }
73
+ }
74
+
75
+ if (!lcaAddr) {
76
+ // Fallback: check if they share the SAME TJP (they should if they are same timeline).
77
+ // If they share TJP but no common past, the TJP itself is the LCA.
78
+ const tjpA = tipA.rel8ns?.tjp?.[0];
79
+ const tjpB = tipB.rel8ns?.tjp?.[0];
80
+ if (tjpA && tjpA === tjpB) {
81
+ lcaAddr = tjpA;
82
+ } else {
83
+ throw new Error(`No LCA found for divergences. Are they the same timeline? (E: 81aa17f0e34b4138ad1743a60a76f27b)`);
84
+ }
85
+ }
86
+
87
+ // 2. Reconstruct Branches
88
+ // We need the actual ibGib objects from LCA to Tips to inspect DNA.
89
+ // This requires fetching from Space.
90
+
91
+ // Helper to get path from LCA to Tip
92
+ const getPath = async (tip: IbGib_V1, past: string[], lca: string): Promise<IbGib_V1[]> => {
93
+ const pathAddrs: string[] = [];
94
+ let foundLca = false;
95
+ for (const addr of past) {
96
+ if (foundLca) {
97
+ pathAddrs.push(addr);
98
+ } else if (addr === lca) {
99
+ foundLca = true;
100
+ }
101
+ }
102
+ pathAddrs.push(getIbGibAddr({ ibGib: tip }));
103
+
104
+ // Fetch all
105
+ const res = await getFromSpace({ space, addrs: pathAddrs });
106
+ if (!res.success || !res.ibGibs) { throw new Error(`Failed to fetch history branch. (E: 7904097405be4a27b871c5031631553c)`); }
107
+ return res.ibGibs;
108
+ };
109
+
110
+ const branchA = await getPath(tipA, pastA, lcaAddr);
111
+ const branchB = await getPath(tipB, pastB, lcaAddr);
112
+
113
+ return { lcaAddr, branchA, branchB };
114
+ }
115
+
116
+ import { graftTimelines, mergeTextLCS } from '../graft-info/graft-info-helpers.mjs';
117
+
118
+ /**
119
+ * Optimistically grafts two divergent timelines by inspecting DNA and replaying transforms.
120
+ */
121
+ export async function mergeDivergentTimelines({
122
+ tipA,
123
+ tipB,
124
+ space,
125
+ metaspace,
126
+ }: {
127
+ tipA: IbGib_V1,
128
+ tipB: IbGib_V1,
129
+ space: IbGibSpaceAny,
130
+ metaspace: any, // MetaspaceService type
131
+ }): Promise<IbGib_V1> {
132
+ const lc = `[${mergeDivergentTimelines.name}]`;
133
+
134
+ // 1. Find LCA & Branches
135
+ const { lcaAddr, branchA, branchB } = await findLCA({ tipA, tipB, space });
136
+
137
+ // 2. Delegate to Graft Engine
138
+ // The graft engine handles timestamp verification, DNA fetching, replay, and Stone creation.
139
+ const result = await graftTimelines({
140
+ tipA,
141
+ branchA,
142
+ tipB,
143
+ branchB,
144
+ space,
145
+ metaspace,
146
+ });
147
+
148
+ return result;
149
+ }