@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,330 @@
1
+ /**
2
+ * @module sync-conflict.respec
3
+ *
4
+ * Verifies Conflict Resolution strategies in SyncSagaCoordinator.
5
+ * Reproduces divergence scenarios and asserts resolution behavior.
6
+ */
7
+
8
+ import {
9
+ respecfully, lastOfAll, ifWe, iReckon,
10
+ ifWeMight
11
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
12
+ const maam = `[${import.meta.url}]`, sir = maam;
13
+ import { delay, extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
14
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
15
+
16
+ import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
17
+ import { 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 { getTjpAddr, toDto } from '../common/other/ibgib-helper.mjs';
25
+ import { SyncConflictStrategy, SyncSagaInfo } from './sync-types.mjs';
26
+ import { IbGibData_V1, IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
27
+ import { fnObs } from '../common/pubsub/observer/observer-helper.mjs';
28
+ import { getDependencyGraph } from '../common/other/graph-helper.mjs';
29
+ import { ErrorIbGib_V1 } from '../common/error/error-types.mjs';
30
+
31
+ interface TestData extends IbGibData_V1 {
32
+ text?: string;
33
+ fieldA?: string;
34
+ fieldB?: string;
35
+ }
36
+
37
+ const logalot = true;
38
+ const lc = `[sync-conflict.respec]`;
39
+
40
+ await respecfully(sir, `Sync Conflict Resolution`, async () => {
41
+
42
+ let metaspace: Metaspace_Innerspace;
43
+ let sourceSpace: InnerSpace_V1;
44
+ let destSpace: InnerSpace_V1;
45
+
46
+ await respecfully(sir, `Scenario: Simple Divergence (Field A vs Field B)`, async () => {
47
+ // 1. Setup Spaces
48
+ metaspace = new Metaspace_Innerspace(undefined);
49
+ await metaspace.initialize({
50
+ getFnAlert: () => async ({ title, msg }) => { console.log(`[Alert] ${title}: ${msg}`); },
51
+ getFnPrompt: () => async ({ title, msg }) => { console.log(`[Prompt] ${title}: ${msg}`); return ''; },
52
+ getFnPromptPassword: () => async (title, msg) => { console.log(`[PromptPwd] ${title}: ${msg}`); return null; },
53
+ });
54
+ while (!metaspace.initialized) { await delay(10); }
55
+
56
+ const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
57
+ await defaultLocalUserSpace!.initialized;
58
+
59
+ sourceSpace = new InnerSpace_V1({
60
+ ...DEFAULT_INNER_SPACE_DATA_V1,
61
+ name: 'source',
62
+ uuid: 'source_uuid',
63
+ description: 'source test space',
64
+ });
65
+ await sourceSpace.initialized;
66
+
67
+ destSpace = new InnerSpace_V1({
68
+ ...DEFAULT_INNER_SPACE_DATA_V1,
69
+ name: 'dest',
70
+ uuid: 'dest_uuid',
71
+ description: 'dest test space',
72
+ });
73
+ await destSpace.initialized;
74
+
75
+ // 2. Seed Common History (V0 -> V1)
76
+ console.log(`${lc} Creating Common History...`);
77
+ const testRoot = await createTimelineRootHelper<TestData>({
78
+ ib: 'timeline_root',
79
+ data: { type: 'testRoot', text: 'v0' },
80
+ space: sourceSpace,
81
+ });
82
+ // Create V1 (Common)
83
+ const v1_Common = await mut8Timeline<TestData>({
84
+ timeline: testRoot,
85
+ mut8Opts: { dataToAddOrPatch: { text: 'v1_common' } },
86
+ metaspace,
87
+ space: sourceSpace,
88
+ });
89
+ const tjpAddr =
90
+ getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ??
91
+ getIbGibAddr({ ibGib: testRoot });
92
+ console.log(`${lc} [TEST DEBUG] tjpAddr: ${tjpAddr}`)
93
+ // Sync testRoot to dest immediately so they start synced
94
+ // must get the entire dependency graph for testRoot
95
+ const depGraph_testRootAndV1Common = await getDependencyGraph({
96
+ ibGibAddrs: [tjpAddr],
97
+ space: sourceSpace,
98
+ live: true,
99
+ }) ?? {};
100
+ console.log(`${lc} depGraph_testRootAndV1Common: ${pretty(depGraph_testRootAndV1Common)}`)
101
+ if (Object.keys(depGraph_testRootAndV1Common).length === 0) {
102
+ throw new Error(`(UNEXPECTED) depGraph_testRootAndV1Common empty? (E: 39b4d855ffa65476084b4123786da826)`);
103
+ }
104
+ // put the entire graph into the destspace, but...
105
+ await metaspace.put({ ibGibs: Object.values(depGraph_testRootAndV1Common), space: destSpace });
106
+ // ...only register the main ibgibs
107
+ await metaspace.registerNewIbGib({ ibGib: testRoot, space: destSpace });
108
+ await metaspace.registerNewIbGib({ ibGib: v1_Common, space: destSpace });
109
+
110
+ const resGetDest = await getFromSpace({ space: destSpace, addr: getIbGibAddr({ ibGib: v1_Common }) });
111
+ if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
112
+ throw new Error(`Failed to retrieve v1_Dest from destSpace. (E: a1b2c3d4e5f6g7h8i9j0)`);
113
+ }
114
+ const v1_Dest = resGetDest.ibGibs[0];
115
+
116
+
117
+ // 3. Create Divergence (V2a vs V2b)
118
+ console.log(`${lc} Creating Divergence...`);
119
+
120
+ // Source: V1 -> V2a (Edit Field A)
121
+ const v2a = await mut8Timeline<TestData>({
122
+ timeline: v1_Common,
123
+ mut8Opts: { dataToAddOrPatch: { fieldA: 'source_edit' } },
124
+ metaspace,
125
+ space: sourceSpace,
126
+ });
127
+
128
+ // Dest: V1 -> V2b (Edit Field B)
129
+ const v2b = await mut8Timeline<TestData>({
130
+ timeline: v1_Dest,
131
+ mut8Opts: { dataToAddOrPatch: { fieldB: 'dest_edit' } },
132
+ metaspace,
133
+ space: destSpace,
134
+ });
135
+
136
+ // Verify Divergence
137
+ console.log(`${lc} Divergence created.`);
138
+
139
+
140
+ // 4. Run Sync (Optimistic)
141
+ console.log(`${lc} Setting up Coordinators...`);
142
+ const mockKeystone = await getTestKeystoneServiceHelper();
143
+ const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
144
+ const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
145
+
146
+
147
+ // Create sender's tempSpace for transactional payload transfer
148
+ const senderTempSpaceName = `tmp_sender_test_${Date.now()}`;
149
+ const senderTempSpace = await metaspace.createNewLocalSpace({
150
+ opts: {
151
+ allowCancel: false,
152
+ spaceName: senderTempSpaceName,
153
+ getFnPrompt: metaspace.getFnPrompt!,
154
+ }
155
+ });
156
+ if (!senderTempSpace) { throw new Error(`(UNEXPECTED) senderTempSpace falsy? (E: ef4388c87e98f0211852eefa22414f26)`); }
157
+ await senderTempSpace.initialized;
158
+
159
+ const peer = new SyncPeerInnerspace_V1({
160
+ senderSpace: sourceSpace,
161
+ senderTempSpace, // Pass sender's tempSpace for payload transfer
162
+ receiverSpace: destSpace,
163
+ receiverCoordinator,
164
+ receiverMetaspace: metaspace,
165
+ });
166
+ await peer.initialized;
167
+
168
+ // Verify Receiver has correct KV (Pre-Sync Check)
169
+ // This ensures the conflict precondition exists.
170
+ await ifWeMight(sir, 'verify receiver KV pre-sync', async () => {
171
+ try {
172
+ const destKV = await receiverCoordinator.getKnowledgeVector({
173
+ space: destSpace,
174
+ metaspace,
175
+ domainIbGibs: [v1_Dest]
176
+ });
177
+ const v1TjpAddr = getTjpAddr({ ibGib: v1_Dest, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: v1_Dest });
178
+ console.log(`[TEST DEBUG] v1_Dest: ${JSON.stringify(v1_Dest)}`);
179
+ console.log(`[TEST DEBUG] v1TjpAddr: ${v1TjpAddr}`);
180
+ console.log(`[TEST DEBUG] destKV: ${JSON.stringify(destKV)}`);
181
+ console.log(`[TEST DEBUG] v1_Dest.rel8ns.tjp: ${v1_Dest.rel8ns?.tjp?.join(', ')}`);
182
+
183
+ // Actually v1 has tjp rel8n.
184
+ const destTip = destKV[v1TjpAddr];
185
+ iReckon(sir, !!destTip).asTo(`Dest KV has timeline tip for ${v1TjpAddr}`).isGonnaBeTruthy();
186
+ if (!destTip) {
187
+ throw new Error(`Test Setup Fail: Dest Space does not have index for timeline ${v1TjpAddr}. Seeding failed. (E: c194a80b4e4877b77826c37a1753b826)`);
188
+ }
189
+
190
+ } catch (error) {
191
+ console.error(`${lc} ${extractErrorMsg(error)}`);
192
+ iReckon(sir, true).asTo('getKnowledgeVector errored out').isGonnaBeFalse();
193
+ }
194
+ });
195
+
196
+ console.log(`${lc} Running Sync (ConflictStrategy: optimistic)...`);
197
+
198
+ let resSync: SyncSagaInfo | undefined;
199
+ try {
200
+ resSync = await senderCoordinator.sync({
201
+ peer,
202
+ source: sourceSpace,
203
+ dest: destSpace,
204
+ metaspace,
205
+ domainIbGibs: [v2a],
206
+ conflictStrategy: SyncConflictStrategy.optimistic,
207
+ // identity: undefined as any, // Not needed? If removed, it uses session identity.
208
+ // identitySecret: undefined as any,
209
+ // explicit useSessionIdentity: true is default
210
+ });
211
+
212
+ const sublc = `${lc}[updates$]`;
213
+ /**
214
+ * I have added this so you can see how to subscribe to an ibgib
215
+ * observable using {@link fnObs}.
216
+ */
217
+ const subscription = await resSync.updates$.subscribe(fnObs({
218
+ next: async (ctxIbGib) => {
219
+ console.log(`${sublc} next fired. ${pretty(ctxIbGib)}`);
220
+ },
221
+ error: async (e: ErrorIbGib_V1) => {
222
+ if (e.data) {
223
+ console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
224
+ } else {
225
+ console.dir(e);
226
+ console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
227
+ }
228
+ },
229
+ complete: async () => {
230
+ console.log(`${sublc} complete fired`);
231
+ },
232
+ }));
233
+
234
+ console.log(`${lc} awaiting resSync.done`)
235
+ await resSync.done;
236
+
237
+ console.log(`${lc} Sync Complete.`);
238
+ } catch (e) {
239
+ console.error(`${lc} Sync Failed with Error:`, e);
240
+ iReckon(sir, false).asTo(`Sync failed with error: ${e}`).isGonnaBeTruthy();
241
+ return; // Exit test early
242
+ }
243
+
244
+ // 5. Verification
245
+ // Expectation: Both Spaces should now have a NEW tip (V3) that merges V2a and V2b.
246
+
247
+ const resSourceTip = await getFromSpace({ space: sourceSpace, addr: getIbGibAddr({ ibGib: v2a }) });
248
+
249
+ await ifWeMight(sir, `verify merge happened`, async () => {
250
+ // Retrieve updated KV from Source (or check what happened to v2a)
251
+ // Ideally, we just check the source space for the timeline tip
252
+ // The timeline tip for testRoot/v0 should now be NEW.
253
+
254
+ try {
255
+ // Get the KV for the Source Space
256
+ const sourceKV = await senderCoordinator.getKnowledgeVector({
257
+ space: sourceSpace,
258
+ metaspace,
259
+ domainIbGibs: [testRoot] // We want to know the tip of this timeline
260
+ });
261
+ const tjpAddr =
262
+ getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ??
263
+ getIbGibAddr({ ibGib: testRoot });
264
+
265
+ if (logalot) { console.log(`${lc} getKnowledgeVector returned. sourceKV: ${pretty(sourceKV)} (I: e8780cda37c8b2a46eeb85786874e926)`); }
266
+
267
+ const sourceTipAddr = sourceKV[tjpAddr];
268
+ if (!sourceTipAddr) {
269
+ throw new Error(`Source Space missing timeline tip for ${tjpAddr} (E: ec95980b9c980c5c5870812e15e43826)`);
270
+ }
271
+ console.log(`${lc} [TEST DEBUG] sourceTipAddr: ${sourceTipAddr}`);
272
+ console.log(`${lc} [TEST DEBUG] v2a: ${getIbGibAddr({ ibGib: v2a })}`);
273
+ console.log(`${lc} [TEST DEBUG] v2b: ${getIbGibAddr({ ibGib: v2b })}`);
274
+
275
+
276
+ iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be v2a`).not.isGonnaBe(getIbGibAddr({ ibGib: v2a }));
277
+ iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be v2b`).not.isGonnaBe(getIbGibAddr({ ibGib: v2b }));
278
+
279
+ // Fetch the new tip
280
+ const resTip = await getFromSpace({ space: sourceSpace, addr: sourceTipAddr });
281
+ const newTip = resTip.ibGibs![0] as IbGib_V1<TestData>;
282
+ console.log(`${lc} [TEST DEBUG] newTip: ${JSON.stringify(newTip)}`);
283
+
284
+
285
+ // Check Data: Should have BOTH edits
286
+ iReckon(sir, newTip.data!.fieldA).asTo('New Tip Data Field A').isGonnaBe('source_edit');
287
+ iReckon(sir, newTip.data!.fieldB).asTo('New Tip Data Field B').isGonnaBe('dest_edit');
288
+
289
+ // Check Graft Structure
290
+ // Look for 'graftinfo' rel8n
291
+ const graftInfoRel = newTip.rel8ns?.graftinfo;
292
+ iReckon(sir, graftInfoRel).asTo('New Tip should have graftinfo rel8n').isGonnaBeTruthy();
293
+ if (graftInfoRel) {
294
+ iReckon(sir, graftInfoRel!.length).asTo('Only 1 graftinfo').isGonnaBe(1);
295
+
296
+ // Fetch Graft Info Stone
297
+ const resGraft = await getFromSpace({ space: sourceSpace, addr: graftInfoRel![0] });
298
+ const graftInfo = resGraft.ibGibs![0];
299
+ console.log(`${lc} [TEST DEBUG] graftInfo: ${JSON.stringify(graftInfo)}`);
300
+
301
+ // Check Graft Relations (graftbase, graftorphan)
302
+ const baseRel = graftInfo.rel8ns?.graftbase;
303
+ const orphanRel = graftInfo.rel8ns?.graftorphan;
304
+
305
+ iReckon(sir, baseRel).asTo('Graft Base exists').isGonnaBeTruthy();
306
+ iReckon(sir, orphanRel).asTo('Graft Orphan exists').isGonnaBeTruthy();
307
+
308
+
309
+ // Verify Base/Orphan identity (one should be v2a, one v2b)
310
+ const addrV2a = getIbGibAddr({ ibGib: v2a });
311
+ const addrV2b = getIbGibAddr({ ibGib: v2b });
312
+
313
+ const bases = baseRel || [];
314
+ const orphans = orphanRel || [];
315
+
316
+ const isV2aInvolved = bases.includes(addrV2a) || orphans.includes(addrV2a);
317
+ const isV2bInvolved = bases.includes(addrV2b) || orphans.includes(addrV2b);
318
+
319
+ iReckon(sir, isV2aInvolved).asTo('V2a is involved in graft').isGonnaBeTrue();
320
+ iReckon(sir, isV2bInvolved).asTo('V2b is involved in graft').isGonnaBeTrue();
321
+ }
322
+ } catch (error) {
323
+ console.error(`${lc} ${extractErrorMsg(error)}`);
324
+ iReckon(sir, true).asTo('getKnowledgeVector errored out').isGonnaBeFalse();
325
+ }
326
+
327
+ });
328
+ });
329
+
330
+ });
@@ -13,15 +13,13 @@ export const SYNC_STAGE_ACK = "ack";
13
13
  export const SYNC_STAGE_REQUEST = "request";
14
14
  export const SYNC_STAGE_DELTA = "delta";
15
15
  export const SYNC_STAGE_COMMIT = "commit";
16
- export const SYNC_STAGE_CONFLICT = "conflict";
17
16
 
18
17
  export type SyncStage =
19
18
  | typeof SYNC_STAGE_INIT
20
19
  | typeof SYNC_STAGE_ACK
21
20
  | typeof SYNC_STAGE_REQUEST
22
21
  | typeof SYNC_STAGE_DELTA
23
- | typeof SYNC_STAGE_COMMIT
24
- | typeof SYNC_STAGE_CONFLICT;
22
+ | typeof SYNC_STAGE_COMMIT;
25
23
 
26
24
  export const SyncStage = {
27
25
  init: SYNC_STAGE_INIT,
@@ -29,5 +27,4 @@ export const SyncStage = {
29
27
  request: SYNC_STAGE_REQUEST,
30
28
  delta: SYNC_STAGE_DELTA,
31
29
  commit: SYNC_STAGE_COMMIT,
32
- conflict: SYNC_STAGE_CONFLICT,
33
30
  } as const;
@@ -25,7 +25,7 @@ const lc = `[sync-innerspace-constants.respec]`;
25
25
 
26
26
  await respecfully(sir, `Sync Constants (No TJP)`, async () => {
27
27
 
28
- await ifWeMight(sir, `Verify Constants Sync`, async () => {
28
+ await ifWe(sir, `Verify Constants Sync`, async () => {
29
29
  // 1. Setup Spaces
30
30
  const metaspace = new Metaspace_Innerspace(undefined);
31
31
  await metaspace.initialize({
@@ -118,7 +118,6 @@ await respecfully(sir, `Sync InnerSpaces (Deep Updates)`, async () => {
118
118
  localSpace: sourceSpace,
119
119
  metaspace: metaspace,
120
120
  domainIbGibs: [commentV2], // Sync the TIP
121
- useSessionIdentity: false,
122
121
  });
123
122
 
124
123
  await resSync.done;
@@ -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 ifWe(sir, `Basic Push Sync (Source -> Dest)`, async () => {
36
+ await respecfully(sir, `Basic Push Sync (Source -> Dest)`, async () => {
37
37
  // 1. Setup Spaces
38
38
  metaspace = new Metaspace_Innerspace(undefined);
39
39
  await metaspace.initialize({
@@ -7,20 +7,22 @@ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
7
7
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
8
8
 
9
9
  import { GLOBAL_LOG_A_LOT } from '../../core-constants.mjs';
10
- import { SYNC_ATOM } from '../sync-constants.mjs';
10
+ import { SYNC_ATOM, SYNC_MSG_REL8N_NAME } from '../sync-constants.mjs';
11
11
  import { IbGibSpaceAny } from '../../witness/space/space-base-v1.mjs';
12
- import { SyncSagaContextIbGib_V1 } from '../sync-saga-context/sync-saga-context-types.mjs';
13
- import { SyncPeer_V1 } from './sync-peer-v1.mjs';
14
12
  import { SyncSagaCoordinator } from '../sync-saga-coordinator.mjs';
15
- import { createSyncSagaContext } from '../sync-saga-context/sync-saga-context-helpers.mjs';
16
- import { getFromSpace, putInSpace } from '../../witness/space/space-helper.mjs';
17
13
  import { MetaspaceService } from '../../witness/space/metaspace/metaspace-types.mjs';
14
+ import { SyncPeer_V1 } from './sync-peer-v1.mjs';
15
+ import { getFromSpace, putInSpace } from '../../witness/space/space-helper.mjs';
16
+ import { SyncSagaContextIbGib_V1 } from '../sync-saga-context/sync-saga-context-types.mjs';
17
+ import { createSyncSagaContext } from '../sync-saga-context/sync-saga-context-helpers.mjs';
18
18
 
19
19
  export interface SyncPeerInnerspaceOptions {
20
- senderSpace: IbGibSpaceAny;
21
- receiverSpace: IbGibSpaceAny;
20
+ senderSpace: IbGibSpaceAny; // Durable space for audit trail
21
+ senderTempSpace?: IbGibSpaceAny; // Transactional space - will use if provided
22
+ receiverSpace: IbGibSpaceAny; // Durable space for receiver audit trail
22
23
  receiverCoordinator: SyncSagaCoordinator;
23
24
  receiverMetaspace: MetaspaceService; // Need this for receiver execution
25
+ receiverTempSpace?: IbGibSpaceAny; // Optional: will be created if not provided
24
26
  }
25
27
 
26
28
  const logalot = GLOBAL_LOG_A_LOT || true;
@@ -41,6 +43,26 @@ export class SyncPeerInnerspace_V1 extends SyncPeer_V1 {
41
43
  super(opts.receiverSpace.data!); // Use receiver space data as initial phantom data? Or empty.
42
44
  }
43
45
 
46
+ private async ensureReceiverTempSpace(): Promise<IbGibSpaceAny> {
47
+ if (!this.opts.receiverTempSpace) {
48
+ const { receiverMetaspace } = this.opts;
49
+ const uuid = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36);
50
+ const tempSpaceName = `tmp_sync_recv_${uuid.substring(0, 8)}`;
51
+ this.opts.receiverTempSpace = await receiverMetaspace.createNewLocalSpace({
52
+ opts: {
53
+ allowCancel: false,
54
+ spaceName: tempSpaceName,
55
+ getFnPrompt: receiverMetaspace.getFnPrompt!,
56
+ logalot
57
+ }
58
+ });
59
+ if (this.opts.receiverTempSpace) {
60
+ await this.opts.receiverTempSpace.initialized;
61
+ }
62
+ }
63
+ return this.opts.receiverTempSpace!; // Non-null assertion since we just created it
64
+ }
65
+
44
66
  protected async getLocalIbGib(addr: string): Promise<IbGib_V1 | undefined> {
45
67
  const res = await getFromSpace({ space: this.opts.senderSpace, addr });
46
68
  return res.ibGibs?.[0];
@@ -69,6 +91,27 @@ export class SyncPeerInnerspace_V1 extends SyncPeer_V1 {
69
91
  const sagaFrame = resFrame.ibGibs?.[0] as any;
70
92
  if (!sagaFrame) { throw new Error(`${lc} sagaFrame not found in receiver space: ${sagaFrameAddr} (E: 2c0190bd04ea408c909618193850029b)`); }
71
93
 
94
+ // Ensure receiverTempSpace exists
95
+ const receiverTempSpace = await this.ensureReceiverTempSpace();
96
+
97
+ // Store saga frame in tempSpace for easy queries during transaction
98
+ // (Control ibgibs like saga frames and msg stones go in BOTH spaces:
99
+ // destSpace for audit trail, tempSpace for easy queries)
100
+ await putInSpace({ space: receiverTempSpace, ibGib: sagaFrame });
101
+ await receiverMetaspace.registerNewIbGib({ ibGib: sagaFrame });
102
+
103
+ // Also store the message stone (if present) in tempSpace
104
+ const msgStoneAddrs = sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
105
+ if (msgStoneAddrs && msgStoneAddrs.length > 0) {
106
+ const resMsgStone = await getFromSpace({ space: receiverSpace, addrs: msgStoneAddrs });
107
+ if (resMsgStone.ibGibs) {
108
+ for (const msgStone of resMsgStone.ibGibs) {
109
+ await putInSpace({ space: receiverTempSpace, ibGib: msgStone });
110
+ await receiverMetaspace.registerNewIbGib({ ibGib: msgStone });
111
+ }
112
+ }
113
+ }
114
+
72
115
  // 2. Execute Receiver Logic
73
116
  // The Peer "Network" triggers the Receiver Coordinator.
74
117
  if (logalot) { console.log(`${lc} Invoking Receiver Coordinator...`); }
@@ -78,7 +121,8 @@ export class SyncPeerInnerspace_V1 extends SyncPeer_V1 {
78
121
  const result = await receiverCoordinator.handleSagaFrame({
79
122
  sagaIbGib: sagaFrame,
80
123
  srcGraph: {},
81
- space: receiverSpace,
124
+ destSpace: receiverSpace, // Query existing data
125
+ tempSpace: receiverTempSpace, // Transaction space
82
126
  metaspace: receiverMetaspace,
83
127
  });
84
128
 
@@ -91,10 +135,31 @@ export class SyncPeerInnerspace_V1 extends SyncPeer_V1 {
91
135
 
92
136
  if (logalot) { console.log(`${lc} responseFrame addr: ${getIbGibAddr({ ibGib: responseFrame })}`); }
93
137
 
94
- // 3. Persist Response Data to Receiver Space (Output Buffer)
95
- // The Receiver created new data (Frame, Payload). It must be saved in Receiver Space
96
- // so the "Network" can PULL it back to Sender.
97
- const ibGibsToPersist = [responseFrame, ...(payloadIbGibs || [])];
138
+ // 3. Persist Response Control IbGibs (Frame + Msg Stones) to BOTH Spaces
139
+ // Control ibgibs (saga frames, msg stones, identity) go in BOTH spaces:
140
+ // - receiverSpace (destSpace): Audit trail
141
+ // - receiverTempSpace: Already there from creation in tempSpace
142
+
143
+ // Ensure response frame is in receiverSpace for audit trail
144
+ await putInSpace({ space: receiverSpace, ibGib: responseFrame });
145
+ await receiverMetaspace.registerNewIbGib({ ibGib: responseFrame });
146
+
147
+ // Ensure response msg stone is in receiverSpace for audit trail
148
+ const responseMsgStoneAddrs = responseFrame.rel8ns?.[SYNC_MSG_REL8N_NAME];
149
+ if (responseMsgStoneAddrs && responseMsgStoneAddrs.length > 0) {
150
+ const resResponseMsgStone = await getFromSpace({ space: receiverTempSpace, addrs: responseMsgStoneAddrs });
151
+ if (resResponseMsgStone.ibGibs) {
152
+ for (const msgStone of resResponseMsgStone.ibGibs) {
153
+ await putInSpace({ space: receiverSpace, ibGib: msgStone });
154
+ await receiverMetaspace.registerNewIbGib({ ibGib: msgStone });
155
+ }
156
+ }
157
+ }
158
+
159
+ // 4. Persist Domain Payload IbGibs
160
+ // Domain ibgibs stay in tempSpace ONLY until commit
161
+ // (they're already in the appropriate space from coordinator's work)
162
+ const ibGibsToPersist = [...(payloadIbGibs || [])];
98
163
 
99
164
  // Note: payloadIbGibs might have deep dependencies.
100
165
  // Since they were generated/fetched by the Receiver Coordinator,
@@ -107,6 +172,14 @@ export class SyncPeerInnerspace_V1 extends SyncPeer_V1 {
107
172
 
108
173
  await putInSpace({ space: receiverSpace, ibGibs: ibGibsToPersist });
109
174
 
175
+ // CRITICAL: Also put payloadIbGibs into sender's tempSpace
176
+ // Sender needs these for merge logic in handleDeltaFrame (tempSpace queries)
177
+ // Do NOT put in sender's durable space - these are transactional!
178
+ if (this.opts.senderTempSpace && ibGibsToPersist.length > 0) {
179
+ console.log(`${lc} [CONFLICT DEBUG] Transferring ${ibGibsToPersist.length} payload ibgibs to sender's tempSpace`);
180
+ await putInSpace({ space: this.opts.senderTempSpace, ibGibs: ibGibsToPersist });
181
+ }
182
+
110
183
  // 4. Create Response Context
111
184
  const responsePayloadAddrs = payloadIbGibs?.map(p => getIbGibAddr({ ibGib: p }));
112
185
  const responseCtx = await createSyncSagaContext({