import { extractErrorMsg, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs'; import { Factory_V1 as factory } from '@ibgib/ts-gib/dist/V1/factory.mjs'; import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs'; import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs'; import { KeystoneIbGib_V1 } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs'; import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs"; import { mut8Timeline } from '@ibgib/core-gib/dist/timeline/timeline-api.mjs'; import { getTjpAddr } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs'; import { GRAFT_BASE_REL8N_NAME, GRAFT_ORPHAN_REL8N_NAME, GRAFT_INFO_REL8N_NAME } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-constants.mjs'; import { SpaceGibApiBridge } from '../api/space-gib-api-bridge.mjs'; import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs'; import { debugState, devLog, lc, setupDomainAndDelegateIdentities, runSyncPass, runSyncPassFromSpace, copyIdentitiesToSpace } from './common.mjs'; interface Phase49State { domainI_latest?: KeystoneIbGib_V1; testRoot?: any; targetAlphaV1Source?: any; } const state: Phase49State = {}; const INITIAL_TEXT = "This is the initial quiz question.\nIt has multiple lines.\nStudents will answer this."; const SOURCE_APPEND = "\nInstructor added hint."; const DEST_PREPEND = "Student note: Confusing!\n"; export function init4_9bSetupButton(): void { const btn = document.getElementById('btn-4-9b-setup') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', async () => { try { btn.disabled = true; devLog('4.9B Setup: Setting up identities, seeding text history, and creating divergent branches...'); const metaspace = await getGlobalMetaspace_waitIfNeeded(); const space = await metaspace.getLocalUserSpace({}) as any; if (!space) { throw new Error("No default space."); } const apiBridge = new SpaceGibApiBridge(); // 1. Create domain + delegate identities (DRY helper) await setupDomainAndDelegateIdentities({ masterSecret: 'test-sender-secret-phase4-9b', syncSalt: 'senderidentitysyncsaltphase4-9b', manageSalt: 'senderidentitymanagesaltphase4-9b', metaspace, space, apiBridge, phaseText: '4.9B' }); const domainI = debugState.domainI!; state.domainI_latest = domainI; const domainAddr = getIbGibAddr({ ibGib: domainI }); // 2. Create the divergent "remote" space devLog('4.9B Setup: Creating temporary remote space...'); const remoteSpace = await metaspace.createNewLocalSpace({ opts: { allowCancel: false, spaceName: 'remote_space_4_9b', getFnPrompt: metaspace.getFnPrompt! } }) as any; await remoteSpace.initialized; // Copy user identity and delegate to remote space await copyIdentitiesToSpace({ domainI, metaspace, fromSpace: space, toSpace: remoteSpace }); // 3. Seed common history root -> v1 (with initial text) locally const resRoot = await factory.firstGen({ parentIbGib: ROOT, ib: 'timeline_root_text_conflict_4_9b', data: { text: INITIAL_TEXT, label: 'TextRoot', random: Math.random() }, dna: true, nCounter: true, tjp: { uuid: true, timestamp: true } }); const testRoot = resRoot.newIbGib; await metaspace.persistTransformResult({ resTransform: resRoot, space }); await metaspace.registerNewIbGib({ ibGib: testRoot, space }); state.testRoot = testRoot; // 4. Copy common history to remote space const graphRoot = await metaspace.getDependencyGraph({ ibGibAddr: getIbGibAddr({ ibGib: testRoot }), space }); await metaspace.put({ ibGibs: Object.values(graphRoot), space: remoteSpace }); await metaspace.registerNewIbGib({ ibGib: testRoot, space: remoteSpace }); // 5. Initial sync of testRoot to the server so server has it devLog('4.9B Setup: Syncing testRoot to server...'); await runSyncPass({ domainIbGibs: [testRoot], passLabel: 'Seed testRoot', metaspace, space }); devLog('4.9B Setup: ✓ Seeding complete on server.'); // Copy evolved delegate and domain tip to remoteSpace await copyIdentitiesToSpace({ domainI: debugState.domainI!, metaspace, fromSpace: space, toSpace: remoteSpace }); // 6. Create Divergence: // Client (default space) appends to end const alpha_v1_source = await mut8Timeline({ timeline: testRoot, mut8Opts: { mut8Ib: testRoot.ib + '_appended', dataToAddOrPatch: { text: INITIAL_TEXT + SOURCE_APPEND } }, metaspace, space, }); state.targetAlphaV1Source = alpha_v1_source; devLog(`4.9B Setup: ✓ Created client appended edit: ${getIbGibAddr({ ibGib: alpha_v1_source })}`); // Remote Space prepends to beginning const alpha_v1_remote = await mut8Timeline({ timeline: testRoot, mut8Opts: { mut8Ib: testRoot.ib + '_prepended', dataToAddOrPatch: { text: DEST_PREPEND + INITIAL_TEXT } }, metaspace, space: remoteSpace, }); devLog(`4.9B Setup: ✓ Created remote prepended edit: ${getIbGibAddr({ ibGib: alpha_v1_remote })}`); // 7. Sync remote prepended edit to server, so server tip becomes the prepended edit devLog('4.9B Setup: Pushing remote prepended edit to server...'); await runSyncPassFromSpace({ domainIbGibs: [alpha_v1_remote], passLabel: 'Push remote prepended edit', metaspace, space: remoteSpace, primarySpace: space, domainI: debugState.domainI!, masterSecret: 'test-sender-secret-phase4-9b' }); state.domainI_latest = debugState.domainI; devLog('4.9B Setup: ✓ Server tip is now the prepended edit. Evolved identity copied back.'); devLog('✓ 4.9B Setup Complete! Ready for 4.9B Sync.'); btn.textContent = '✓ 4.9B Setup Complete'; const syncBtn = document.getElementById('btn-4-9b-sync') as HTMLButtonElement | null; if (syncBtn) { syncBtn.disabled = false; } } catch (error) { devLog(`✗ 4.9B Setup FAILED: ${extractErrorMsg(error)}`); console.error(error); btn.disabled = false; } }); } export function init4_9bSyncButton(): void { const btn = document.getElementById('btn-4-9b-sync') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', async () => { try { btn.disabled = true; devLog('4.9B Sync: Initiating WebSocket Sync for divergent edit (LCS text merge)...'); const domainI = state.domainI_latest; const targetAlphaV1Source = state.targetAlphaV1Source; if (!domainI || !targetAlphaV1Source) { devLog('⚠ 4.9B Sync: Missing setup state. Please run Setup first.'); btn.disabled = false; return; } const metaspace = await getGlobalMetaspace_waitIfNeeded(); const space = await metaspace.getLocalUserSpace({}) as any; if (!space) { throw new Error("No default space."); } await runSyncPass({ domainIbGibs: [targetAlphaV1Source], passLabel: 'Sync divergent edit and merge', metaspace, space }); state.domainI_latest = debugState.domainI; devLog('✓ 4.9B Sync Execution Complete! Ready for 4.9B Check.'); btn.textContent = '✓ 4.9B Sync Run'; const checkBtn = document.getElementById('btn-4-9b-check') as HTMLButtonElement | null; if (checkBtn) { checkBtn.disabled = false; } } catch (error) { devLog(`✗ 4.9B Sync FAILED: ${extractErrorMsg(error)}`); console.error(error); btn.disabled = false; } }); } export function init4_9bCheckButton(): void { const btn = document.getElementById('btn-4-9b-check') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', async () => { try { btn.disabled = true; devLog('4.9B Check: Verifying LCS merged text on client and server...'); const domainI = state.domainI_latest; const testRoot = state.testRoot; const targetAlphaV1Source = state.targetAlphaV1Source; if (!domainI || !testRoot || !targetAlphaV1Source) { devLog('⚠ 4.9B Check: Missing state. Did Setup and Sync run?'); btn.disabled = false; return; } const domainAddr = getIbGibAddr({ ibGib: domainI }); const tjpAddr = getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: testRoot }); const metaspace = await getGlobalMetaspace_waitIfNeeded(); const space = await metaspace.getLocalUserSpace({}) as any; if (!space) { throw new Error("No default space."); } // 1. Check client default space tip const clientKV = await metaspace.getLocalUserSpace({}).then((space: any) => { return new SyncSagaCoordinator().getKnowledgeMap({ space, metaspace, domainIbGibs: [testRoot] }); }); const clientTipAddr = clientKV[tjpAddr]; devLog(`4.9B Check: Client tip address: ${clientTipAddr}`); if (!clientTipAddr) { throw new Error("Client default space is missing the timeline tip."); } const resTip = await metaspace.get({ addr: clientTipAddr, space }); if (!resTip.success || !resTip.ibGibs || resTip.ibGibs.length === 0) { throw new Error("Client tip not found in default local space."); } const clientTip = resTip.ibGibs[0]; const expectedText = DEST_PREPEND + INITIAL_TEXT + SOURCE_APPEND; if (clientTip.data?.text !== expectedText) { throw new Error(`Merged client tip text mismatch! Expected:\n${expectedText}\nActual:\n${clientTip.data?.text}`); } devLog('4.9B Check: ✓ Client tip text contains both prepend and append edits merged correctly.'); // Verify graftinfo rel8n exists const graftInfoRel = clientTip.rel8ns?.[GRAFT_INFO_REL8N_NAME]; if (!graftInfoRel?.[0]) { throw new Error("Client tip is missing graftinfo relations."); } const resGraft = await metaspace.get({ addr: graftInfoRel[0], space }); if (!resGraft.success || !resGraft.ibGibs || resGraft.ibGibs.length === 0) { throw new Error("GraftInfo not found in local space."); } const graftInfo = resGraft.ibGibs[0]; const baseRel = graftInfo.rel8ns?.[GRAFT_BASE_REL8N_NAME]; const orphanRel = graftInfo.rel8ns?.[GRAFT_ORPHAN_REL8N_NAME]; if (!baseRel || !orphanRel) { throw new Error("GraftInfo is missing graftbase or graftorphan relations."); } devLog('4.9B Check: ✓ Client graft base and orphan relations are correctly set.'); // 2. Check server tip const apiBridge = new SpaceGibApiBridge(); devLog('4.9B Check: Fetching server timeline graph...'); const resServerGraph = await apiBridge.getIbGibGraph(domainAddr, clientTipAddr, true); if (!resServerGraph.success || !resServerGraph.graph) { throw new Error(`Failed to fetch merged graph from server: ${resServerGraph.message}`); } const serverTip = resServerGraph.graph[clientTipAddr]; if (!serverTip) { throw new Error("Merged tip is not present on the server!"); } if (serverTip.data?.text !== expectedText) { throw new Error(`Server tip text mismatch! Expected:\n${expectedText}\nActual:\n${serverTip.data?.text}`); } devLog('4.9B Check: ✓ Server tip text matches the client-side merged text.'); devLog('✓ 4.9B Check SUCCESS: LCS text field merge conflict resolved and verified on both client and server!'); btn.textContent = '✓ 4.9B Check Success'; } catch (error) { devLog(`✗ 4.9B Check FAILED: ${extractErrorMsg(error)}`); console.error(error); btn.disabled = false; } }); }