@ibgib/space-gib 0.0.2 → 0.0.3

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.
@@ -19,16 +19,18 @@ import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-servi
19
19
  import { KeystoneIbGib_V1, KeystoneReplenishStrategy } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
20
20
  import { createManagePoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-config-builder.mjs';
21
21
  import { KeystoneStrategyFactory } from '@ibgib/core-gib/dist/keystone/strategy/keystone-strategy-factory.mjs';
22
- import { SyncPeerWebSocketSender_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs';
22
+ import { SyncPeerWebSocketSender_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs';
23
23
  import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
24
24
  import { KEYSTONE_VERB_CONNECT, POOL_ID_CONNECT, POOL_ID_SYNC } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
25
25
  import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
26
+ import { graphsAreEquivalent } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
26
27
 
27
28
  import { GLOBAL_LOG_A_LOT } from './constants.mjs';
28
29
  import {
29
30
  SESSION_KEYSTONE_POLICY, getConnectChallenge, getSpaceGibPoolConfig
30
31
  } from "../common/keystone-policies.mjs";
31
32
  import { SpaceGibApiBridge } from './api/space-gib-api-bridge.mjs';
33
+ import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
32
34
 
33
35
  const logalot = GLOBAL_LOG_A_LOT
34
36
  const lc = '[dev-tools]';
@@ -120,7 +122,7 @@ function initWsTestButton(): void {
120
122
 
121
123
  function initPrintStateButton(): void {
122
124
  const btn = document.getElementById('btn-print-dev-state') as HTMLButtonElement | null;
123
- if (!btn) return;
125
+ if (!btn) { return; }
124
126
  btn.addEventListener('click', () => {
125
127
  devLog(`Printing debug state to console...`);
126
128
  console.dir(debugState);
@@ -134,7 +136,7 @@ function initPrintStateButton(): void {
134
136
  function initCreateDomainKeystoneButton(): void {
135
137
  const lc_fn = `${lc}[initCreateDomainKeystoneButton]`;
136
138
  const btn = document.getElementById('btn-create-domain-keystone') as HTMLButtonElement | null;
137
- if (!btn) return;
139
+ if (!btn) { return; }
138
140
 
139
141
  btn.addEventListener('click', async () => {
140
142
  try {
@@ -183,7 +185,7 @@ function initCreateDomainKeystoneButton(): void {
183
185
 
184
186
  // Enable next step
185
187
  const nextBtn = document.getElementById('btn-create-test-ibgib') as HTMLButtonElement;
186
- if (nextBtn) nextBtn.disabled = false;
188
+ if (nextBtn) { nextBtn.disabled = false; }
187
189
 
188
190
  } catch (error) {
189
191
  devLog(`✗ ${extractErrorMsg(error)}`);
@@ -225,7 +227,7 @@ function initCreateTestIbGibButton(): void {
225
227
 
226
228
  // Enable next step
227
229
  const nextBtn = document.getElementById('btn-create-session-keystone') as HTMLButtonElement;
228
- if (nextBtn) nextBtn.disabled = false;
230
+ if (nextBtn) { nextBtn.disabled = false; }
229
231
  } catch (error) {
230
232
  devLog(`✗ ${extractErrorMsg(error)}`);
231
233
  console.error(`${lc_fn} ${extractErrorMsg(error)}`);
@@ -315,7 +317,7 @@ function initCreateSessionKeystoneButton(): void {
315
317
 
316
318
  // Enable next step
317
319
  const nextBtn = document.getElementById('btn-evolve-domain-keystone') as HTMLButtonElement;
318
- if (nextBtn) nextBtn.disabled = false;
320
+ if (nextBtn) { nextBtn.disabled = false; }
319
321
  } catch (error) {
320
322
  devLog(`✗ ${extractErrorMsg(error)}`);
321
323
  console.error(`${lc_fn} ${extractErrorMsg(error)}`);
@@ -389,7 +391,7 @@ function initEvolveDomainKeystoneButton(): void {
389
391
 
390
392
  // Enable next step
391
393
  const nextBtn = document.getElementById('btn-perform-sync') as HTMLButtonElement;
392
- if (nextBtn) nextBtn.disabled = false;
394
+ if (nextBtn) { nextBtn.disabled = false; }
393
395
 
394
396
  } catch (error) {
395
397
  devLog(`✗ ${extractErrorMsg(error)}`);
@@ -406,7 +408,7 @@ function initEvolveDomainKeystoneButton(): void {
406
408
  function initPerformSyncButton(): void {
407
409
  const lc_fn = `${lc}[initPerformSyncButton]`;
408
410
  const btn = document.getElementById('btn-perform-sync') as HTMLButtonElement | null;
409
- if (!btn) return;
411
+ if (!btn) { return; }
410
412
 
411
413
  btn.addEventListener('click', async () => {
412
414
  try {
@@ -527,161 +529,212 @@ function initPerformSyncButton(): void {
527
529
  // Phase 1B: Standardized Setup / Sync / Check testing loop
528
530
  // ---------------------------------------------------------------------------
529
531
 
530
- function init1bSetupButton(): void {
531
- const lc_fn = `${lc}[init1bSetupButton]`;
532
- const btn = document.getElementById('btn-1b-setup') as HTMLButtonElement | null;
533
- if (!btn) return;
532
+ interface PhaseSetupOptions {
533
+ phaseText: '1B' | '2B' | '3B';
534
+ btnId: string;
535
+ nextBtnId: string;
536
+ masterSecret: string;
537
+ syncSalt: string;
538
+ manageSalt: string;
539
+ ib: string;
540
+ data: any;
541
+ }
534
542
 
535
- btn.addEventListener('click', async () => {
536
- try {
537
- btn.disabled = true;
538
- devLog('1B Setup: Generating Alice\'s long-lived Domain Keystone (I) and Target (X)...');
543
+ async function performPhaseSetup(opts: PhaseSetupOptions): Promise<void> {
544
+ const lc_fn = `${lc}[performPhaseSetup(${opts.phaseText})]`;
545
+ const btn = document.getElementById(opts.btnId) as HTMLButtonElement | null;
546
+ if (!btn) { return; }
539
547
 
540
- const metaspace = await getGlobalMetaspace_waitIfNeeded();
541
- const space = await metaspace.getLocalUserSpace({});
542
- if (!space) { throw new Error("No default space."); }
548
+ try {
549
+ btn.disabled = true;
550
+ devLog(`${opts.phaseText} Setup: Generating Alice's long-lived Domain Keystone (I) and Target (X)...`);
551
+
552
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
553
+ const space = await metaspace.getLocalUserSpace({});
554
+ if (!space) { throw new Error("No default space found in metaspace."); }
555
+
556
+ const keystoneService = new KeystoneService_V1();
557
+
558
+ // 1. Create Genesis Domain Keystone (I^Itjp) in local space
559
+ const domainI = await keystoneService.genesis({
560
+ masterSecret: opts.masterSecret,
561
+ configs: [
562
+ getSpaceGibPoolConfig('sync', opts.syncSalt),
563
+ getSpaceGibPoolConfig('manage', opts.manageSalt),
564
+ ],
565
+ metaspace,
566
+ space,
567
+ frameDetails: { client: 'space-gib-web-dev', timestamp: getTimestamp() }
568
+ });
569
+
570
+ debugState.domainI = domainI;
571
+ debugState.domainIMasterSecret = opts.masterSecret;
572
+
573
+ devLog(`${opts.phaseText} Setup: ✓ Domain Keystone (I) created locally: ${getIbGibAddr({ ibGib: domainI })}`);
574
+ devLog(`${opts.phaseText} Setup: Starting postGenesisKeystone...`);
575
+
576
+ // 2. Post Genesis Domain Keystone to receiver/server
577
+ const apiBridge = new SpaceGibApiBridge();
578
+ const resGenesis = await apiBridge.postGenesisKeystone(domainI);
579
+ if (resGenesis.success) {
580
+ devLog(`${opts.phaseText} Setup: ✓ Domain Keystone registered and stored on receiver (server).`);
581
+ } else {
582
+ devLog(`${opts.phaseText} Setup: X postGenesisKeystone failed: ${resGenesis.message}.`);
583
+ throw new Error(`${opts.phaseText} Setup: Server rejected genesis domain keystone: ${resGenesis.message}`);
584
+ }
543
585
 
544
- const masterSecret = 'test-sender-secret-phase1';
545
- const keystoneService = new KeystoneService_V1();
586
+ // 3. Create Test IbGib (X) locally
587
+ const resX = await factory.firstGen({
588
+ parentIbGib: ROOT,
589
+ ib: opts.ib,
590
+ data: opts.data,
591
+ dna: true,
592
+ nCounter: true,
593
+ tjp: { uuid: true, timestamp: true }
594
+ });
595
+ const xIbGib = resX.newIbGib;
546
596
 
547
- // 1. Create Genesis Domain Keystone (I^Itjp) in local sourceSpace
548
- const domainI = await keystoneService.genesis({
549
- masterSecret,
550
- configs: [
551
- getSpaceGibPoolConfig('sync', 'senderidentitysyncsaltphase1'),
552
- getSpaceGibPoolConfig('manage', 'senderidentitymanagesaltphase1'),
553
- ],
554
- metaspace,
555
- space,
556
- frameDetails: { client: 'space-gib-web-dev', timestamp: getTimestamp() }
557
- });
597
+ // Persist target X to local space so coordinator can build the sync history
598
+ await metaspace.persistTransformResult({ resTransform: resX, space });
599
+ await metaspace.registerNewIbGib({ ibGib: xIbGib });
558
600
 
559
- debugState.domainI = domainI;
560
- debugState.domainIMasterSecret = masterSecret;
601
+ debugState.targetX = xIbGib;
602
+ devLog(`${opts.phaseText} Setup: ✓ Target (X) created and persisted locally: ${getIbGibAddr({ ibGib: xIbGib })}`);
561
603
 
562
- devLog(`1B Setup: Domain Keystone (I) created locally: ${getIbGibAddr({ ibGib: domainI })}`);
563
- devLog(`1B Setup: Starting postGenesisKeystone...`);
604
+ devLog(`✓ ${opts.phaseText} Setup Complete! Ready for ${opts.phaseText} Sync.`);
605
+ btn.textContent = `✓ ${opts.phaseText} Setup Complete`;
564
606
 
565
- // 2. Post Genesis Domain Keystone to receiver/server
566
- const apiBridge = new SpaceGibApiBridge();
567
- const resGenesis = await apiBridge.postGenesisKeystone(domainI);
568
- if (resGenesis.success) {
569
- devLog(`1B Setup: ✓ Domain Keystone registered and stored on receiver (server).`);
570
- } else {
571
- devLog(`1B Setup: X postGenesisKeystone failed: ${resGenesis.message}.`);
572
- throw new Error(`1B Setup: Server rejected genesis domain keystone: ${resGenesis.message}`);
573
- }
607
+ // Enable next button in the phase flow
608
+ const syncBtn = document.getElementById(opts.nextBtnId) as HTMLButtonElement | null;
609
+ if (syncBtn) { syncBtn.disabled = false; }
574
610
 
575
- // 3. Create Test IbGib (X) locally
576
- const resX = await factory.firstGen({
577
- parentIbGib: ROOT,
578
- ib: 'test data',
579
- data: { hello: 'world', random: Math.random() },
580
- dna: true,
581
- nCounter: true,
582
- tjp: { uuid: true, timestamp: true }
583
- });
584
- debugState.targetX = resX.newIbGib;
585
- devLog(`1B Setup: ✓ Target (X) created locally: ${getIbGibAddr({ ibGib: resX.newIbGib })}`);
611
+ } catch (error) {
612
+ devLog(`✗ ${opts.phaseText} Setup FAILED: ${extractErrorMsg(error)}`);
613
+ console.error(`${lc_fn} ${opts.phaseText} Setup error:`, error);
614
+ btn.disabled = false;
615
+ }
616
+ }
586
617
 
587
- devLog('✓ 1B Setup Complete! Ready for 1B Sync.');
588
- btn.textContent = '1B Setup Complete';
618
+ interface PhaseSyncOptions {
619
+ phaseText: '1B' | '2B' | '3B';
620
+ btnId: string;
621
+ nextBtnId: string;
622
+ expectSyncFailure?: boolean;
623
+ failureMessageSuffix?: string;
624
+ }
589
625
 
590
- // Enable next button in the phase flow
591
- const syncBtn = document.getElementById('btn-1b-sync') as HTMLButtonElement | null;
592
- if (syncBtn) syncBtn.disabled = false;
626
+ async function performPhaseSync(opts: PhaseSyncOptions): Promise<void> {
627
+ const lc_fn = `${lc}[performPhaseSync(${opts.phaseText})]`;
628
+ const btn = document.getElementById(opts.btnId) as HTMLButtonElement | null;
629
+ if (!btn) { return; }
593
630
 
594
- } catch (error) {
595
- devLog(`✗ 1B Setup FAILED: ${extractErrorMsg(error)}`);
596
- console.error(`${lc_fn} 1B Setup error:`, error);
631
+ try {
632
+ btn.disabled = true;
633
+ devLog(`${opts.phaseText} Sync: Executing senderCoordinator.sync(...) using WebSocket Peer...`);
634
+
635
+ const domainI = debugState.domainI;
636
+ const targetX = debugState.targetX;
637
+ if (!domainI || !targetX) {
638
+ devLog(`⚠ ${opts.phaseText} Sync: Missing setup state. Please run Setup first.`);
597
639
  btn.disabled = false;
640
+ return;
598
641
  }
599
- });
600
- }
601
642
 
602
- function init1bSyncButton(): void {
603
- const lc_fn = `${lc}[init1bSyncButton]`;
604
- const btn = document.getElementById('btn-1b-sync') as HTMLButtonElement | null;
605
- if (!btn) return;
643
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
644
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
645
+ const space = await metaspace.getLocalUserSpace({});
646
+ if (!space) { throw new Error("No default space."); }
647
+
648
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
649
+ const senderPeer = new SyncPeerWebSocketSender_V1({
650
+ classname: 'SyncPeerWebSocketSender_V1',
651
+ httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
652
+ wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
653
+ });
654
+
655
+ const coordinator = new SyncSagaCoordinator();
656
+ const sagaId = await getUUID();
657
+ debugState.sagaId = sagaId;
658
+ debugState.senderPeer = senderPeer;
659
+
660
+ await senderPeer.initializeOpts({
661
+ localMetaspace: metaspace,
662
+ localSpace: space,
663
+ senderIdentity: domainI,
664
+ fnSenderSecret: async () => debugState.domainIMasterSecret!,
665
+ sagaId,
666
+ sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
667
+ sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
668
+ targetAddrs: [domainAddr]
669
+ });
606
670
 
607
- btn.addEventListener('click', async () => {
608
671
  try {
609
- btn.disabled = true;
610
- devLog('1B Sync: Executing senderCoordinator.sync(...) using WebSocket Peer...');
611
-
612
- const domainI = debugState.domainI;
613
- const targetX = debugState.targetX;
614
- if (!domainI || !targetX) {
615
- devLog('⚠ 1B Sync: Missing setup state. Please run Setup first.');
616
- btn.disabled = false;
617
- return;
618
- }
619
-
620
- const domainAddr = getIbGibAddr({ ibGib: domainI });
621
- const metaspace = await getGlobalMetaspace_waitIfNeeded();
622
- const space = await metaspace.getLocalUserSpace({});
623
- if (!space) { throw new Error("No default space."); }
624
-
625
- // Instantiate WebSocket sender peer
626
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
627
- const senderPeer = new SyncPeerWebSocketSender_V1({
628
- classname: 'SyncPeerWebSocketSender_V1',
629
- httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
630
- wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
631
- });
632
-
633
- const coordinator = new SyncSagaCoordinator();
634
- const sagaId = await getUUID();
635
- debugState.sagaId = sagaId;
636
- debugState.senderPeer = senderPeer;
637
-
638
- await senderPeer.initializeOpts({
639
- localMetaspace: metaspace,
640
- localSpace: space,
672
+ const syncSaga = await coordinator.sync({
673
+ domainIbGibs: [targetX],
641
674
  senderIdentity: domainI,
642
675
  fnSenderSecret: async () => debugState.domainIMasterSecret!,
643
- sagaId,
644
- sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
645
- sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
646
- targetAddrs: [domainAddr]
676
+ peer: senderPeer,
677
+ localSpace: space,
678
+ metaspace,
679
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
647
680
  });
648
-
649
- try {
650
- // Execute sync saga — triggers establishSessionIdentity internally!
651
- const syncSaga = await coordinator.sync({
652
- domainIbGibs: [targetX],
653
- senderIdentity: domainI,
654
- fnSenderSecret: async () => debugState.domainIMasterSecret!,
655
- peer: senderPeer,
656
- localSpace: space,
657
- metaspace,
658
- conflictStrategy: 'optimisticWithLCS' as any
659
- });
660
- await syncSaga.done;
661
- } catch (err) {
662
- // connection/sync error is fine/expected right now since connection is in Phase 2
663
- devLog(`1B Sync: Coordinator sync executed (establish phase successfully run; connection/sync errors expected in 1B): ${extractErrorMsg(err)}`);
681
+ await syncSaga.done;
682
+ } catch (err) {
683
+ if (opts.expectSyncFailure) {
684
+ devLog(`${opts.phaseText} Sync: Coordinator sync executed (${opts.failureMessageSuffix ?? 'errors expected'}): ${extractErrorMsg(err)}`);
685
+ } else {
686
+ throw err;
664
687
  }
688
+ }
665
689
 
666
- devLog('✓ 1B Sync Execution Complete! Ready for State Checks.');
667
- btn.textContent = '✓ 1B Sync Run';
690
+ devLog(`✓ ${opts.phaseText} Sync Execution Complete! Ready for State Checks.`);
691
+ btn.textContent = `✓ ${opts.phaseText} Sync Run`;
668
692
 
669
- // Enable check button
670
- const checkBtn = document.getElementById('btn-1b-check') as HTMLButtonElement | null;
671
- if (checkBtn) checkBtn.disabled = false;
693
+ const checkBtn = document.getElementById(opts.nextBtnId) as HTMLButtonElement | null;
694
+ if (checkBtn) { checkBtn.disabled = false; }
672
695
 
673
- } catch (error) {
674
- devLog(`✗ 1B Sync FAILED: ${extractErrorMsg(error)}`);
675
- console.error(`${lc_fn} 1B Sync error:`, error);
676
- btn.disabled = false;
677
- }
696
+ } catch (error) {
697
+ devLog(`✗ ${opts.phaseText} Sync FAILED: ${extractErrorMsg(error)}`);
698
+ console.error(`${lc_fn} ${opts.phaseText} Sync error:`, error);
699
+ btn.disabled = false;
700
+ }
701
+ }
702
+
703
+ function init1bSetupButton(): void {
704
+ const btn = document.getElementById('btn-1b-setup') as HTMLButtonElement | null;
705
+ if (!btn) { return; }
706
+ btn.addEventListener('click', () => {
707
+ performPhaseSetup({
708
+ phaseText: '1B',
709
+ btnId: 'btn-1b-setup',
710
+ nextBtnId: 'btn-1b-sync',
711
+ masterSecret: 'test-sender-secret-phase1',
712
+ syncSalt: 'senderidentitysyncsaltphase1',
713
+ manageSalt: 'senderidentitymanagesaltphase1',
714
+ ib: 'test data',
715
+ data: { hello: 'world', random: Math.random() }
716
+ });
717
+ });
718
+ }
719
+
720
+ function init1bSyncButton(): void {
721
+ const btn = document.getElementById('btn-1b-sync') as HTMLButtonElement | null;
722
+ if (!btn) { return; }
723
+ btn.addEventListener('click', () => {
724
+ performPhaseSync({
725
+ phaseText: '1B',
726
+ btnId: 'btn-1b-sync',
727
+ nextBtnId: 'btn-1b-check',
728
+ expectSyncFailure: true,
729
+ failureMessageSuffix: 'establish phase successfully run; connection/sync errors expected in 1B'
730
+ });
678
731
  });
679
732
  }
680
733
 
681
734
  function init1bCheckButton(): void {
682
735
  const lc_fn = `${lc}[init1bCheckButton]`;
683
736
  const btn = document.getElementById('btn-1b-check') as HTMLButtonElement | null;
684
- if (!btn) return;
737
+ if (!btn) { return; }
685
738
 
686
739
  btn.addEventListener('click', async () => {
687
740
  try {
@@ -784,18 +837,18 @@ function init1bCheckButton(): void {
784
837
  devLog(`✗ Check Server: Failed to fetch session keystone from server: ${resGetSessionSFromServer.message}`);
785
838
  }
786
839
 
787
- if (hasI) devLog('✓ Check Server: Genesis Domain Keystone (I) registered on server.');
840
+ if (hasI) { devLog('✓ Check Server: Genesis Domain Keystone (I) registered on server.'); }
788
841
  else devLog('✗ Check Server: Genesis Domain Keystone (I) missing on server!');
789
- if (hasI1) devLog('✓ Check Server: Evolved Domain Keystone (I1) accepted and stored on server.');
842
+ if (hasI1) { devLog('✓ Check Server: Evolved Domain Keystone (I1) accepted and stored on server.'); }
790
843
  else devLog('✗ Check Server: Evolved Domain Keystone (I1) missing on server!');
791
- if (hasS) devLog('✓ Check Server: Session Keystone (S) accepted and stored on server.');
844
+ if (hasS) { devLog('✓ Check Server: Session Keystone (S) accepted and stored on server.'); }
792
845
  else devLog('✗ Check Server: Session Keystone (S) missing on server!');
793
846
  if (hasI && hasI1 && hasS) {
794
847
  devLog('🎉 ALL PHASE 1B ESTABLISH CHECKS PASSED FLAWLESSLY! ✓');
795
848
  btn.textContent = '✓ 1B All Passed';
796
849
 
797
850
  const btn2bSetup = document.getElementById('btn-2b-setup') as HTMLButtonElement | null;
798
- if (btn2bSetup) btn2bSetup.disabled = false;
851
+ if (btn2bSetup) { btn2bSetup.disabled = false; }
799
852
  }
800
853
 
801
854
  // #endregion 3. Verify Server-Side (receiver) registrations via GET REST API
@@ -812,160 +865,40 @@ function init1bCheckButton(): void {
812
865
  // ---------------------------------------------------------------------------
813
866
 
814
867
  function init2bSetupButton(): void {
815
- const lc_fn = `${lc}[init2bSetupButton]`;
816
868
  const btn = document.getElementById('btn-2b-setup') as HTMLButtonElement | null;
817
- if (!btn) return;
818
-
819
- btn.addEventListener('click', async () => {
820
- try {
821
- btn.disabled = true;
822
- devLog('2B Setup: Generating Alice\'s long-lived Domain Keystone (I) and Target (X)...');
823
-
824
- const metaspace = await getGlobalMetaspace_waitIfNeeded();
825
- const space = await metaspace.getLocalUserSpace({});
826
- if (!space) { throw new Error("No default space."); }
827
-
828
- const masterSecret = 'test-sender-secret-phase2';
829
- const keystoneService = new KeystoneService_V1();
830
-
831
- // 1. Create Genesis Domain Keystone (I^Itjp) in local sourceSpace
832
- const domainI = await keystoneService.genesis({
833
- masterSecret,
834
- configs: [
835
- getSpaceGibPoolConfig('sync', 'senderidentitysyncsaltphase2'),
836
- getSpaceGibPoolConfig('manage', 'senderidentitymanagesaltphase2'),
837
- ],
838
- metaspace,
839
- space,
840
- frameDetails: { client: 'space-gib-web-dev', timestamp: getTimestamp() }
841
- });
842
-
843
- debugState.domainI = domainI;
844
- debugState.domainIMasterSecret = masterSecret;
845
-
846
- devLog(`2B Setup: ✓ Domain Keystone (I) created locally: ${getIbGibAddr({ ibGib: domainI })}`);
847
- devLog(`2B Setup: Starting postGenesisKeystone...`);
848
-
849
- // 2. Post Genesis Domain Keystone to receiver/server
850
- const apiBridge = new SpaceGibApiBridge();
851
- const resGenesis = await apiBridge.postGenesisKeystone(domainI);
852
- if (resGenesis.success) {
853
- devLog(`2B Setup: ✓ Domain Keystone registered and stored on receiver (server).`);
854
- } else {
855
- devLog(`2B Setup: X postGenesisKeystone failed: ${resGenesis.message}.`);
856
- throw new Error(`2B Setup: Server rejected genesis domain keystone: ${resGenesis.message}`);
857
- }
858
-
859
- // 3. Create Test IbGib (X) locally
860
- const resX = await factory.firstGen({
861
- parentIbGib: ROOT,
862
- ib: 'test data',
863
- data: { hello: 'world2', random: Math.random() },
864
- dna: true,
865
- nCounter: true,
866
- tjp: { uuid: true, timestamp: true }
867
- });
868
- debugState.targetX = resX.newIbGib;
869
- devLog(`2B Setup: ✓ Target (X) created locally: ${getIbGibAddr({ ibGib: resX.newIbGib })}`);
870
-
871
- devLog('✓ 2B Setup Complete! Ready for 2B Sync.');
872
- btn.textContent = '✓ 2B Setup Complete';
873
-
874
- // Enable next button in the phase flow
875
- const syncBtn = document.getElementById('btn-2b-sync') as HTMLButtonElement | null;
876
- if (syncBtn) syncBtn.disabled = false;
877
-
878
- } catch (error) {
879
- devLog(`✗ 2B Setup FAILED: ${extractErrorMsg(error)}`);
880
- console.error(`${lc_fn} 2B Setup error:`, error);
881
- btn.disabled = false;
882
- }
869
+ if (!btn) { return; }
870
+ btn.addEventListener('click', () => {
871
+ performPhaseSetup({
872
+ phaseText: '2B',
873
+ btnId: 'btn-2b-setup',
874
+ nextBtnId: 'btn-2b-sync',
875
+ masterSecret: 'test-sender-secret-phase2',
876
+ syncSalt: 'senderidentitysyncsaltphase2',
877
+ manageSalt: 'senderidentitymanagesaltphase2',
878
+ ib: 'test data',
879
+ data: { hello: 'world2', random: Math.random() }
880
+ });
883
881
  });
884
882
  }
885
883
 
886
884
  function init2bSyncButton(): void {
887
- const lc_fn = `${lc}[init2bSyncButton]`;
888
885
  const btn = document.getElementById('btn-2b-sync') as HTMLButtonElement | null;
889
- if (!btn) return;
890
-
891
- btn.addEventListener('click', async () => {
892
- try {
893
- btn.disabled = true;
894
- devLog('2B Sync: Executing senderCoordinator.sync(...) using WebSocket Peer...');
895
-
896
- const domainI = debugState.domainI;
897
- const targetX = debugState.targetX;
898
- if (!domainI || !targetX) {
899
- devLog('⚠ 2B Sync: Missing setup state. Please run Setup first.');
900
- btn.disabled = false;
901
- return;
902
- }
903
-
904
- const domainAddr = getIbGibAddr({ ibGib: domainI });
905
- const metaspace = await getGlobalMetaspace_waitIfNeeded();
906
- const space = await metaspace.getLocalUserSpace({});
907
- if (!space) { throw new Error("No default space."); }
908
-
909
- // Instantiate WebSocket sender peer
910
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
911
- const senderPeer = new SyncPeerWebSocketSender_V1({
912
- classname: 'SyncPeerWebSocketSender_V1',
913
- httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
914
- wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
915
- });
916
-
917
- const coordinator = new SyncSagaCoordinator();
918
- const sagaId = await getUUID();
919
- debugState.sagaId = sagaId;
920
- debugState.senderPeer = senderPeer;
921
-
922
- await senderPeer.initializeOpts({
923
- localMetaspace: metaspace,
924
- localSpace: space,
925
- senderIdentity: domainI,
926
- fnSenderSecret: async () => debugState.domainIMasterSecret!,
927
- sagaId,
928
- sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
929
- sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
930
- targetAddrs: [domainAddr]
931
- });
932
-
933
- try {
934
- // Execute sync saga — triggers establishSessionIdentity and connectImpl internally!
935
- const syncSaga = await coordinator.sync({
936
- domainIbGibs: [targetX],
937
- senderIdentity: domainI,
938
- fnSenderSecret: async () => debugState.domainIMasterSecret!,
939
- peer: senderPeer,
940
- localSpace: space,
941
- metaspace,
942
- conflictStrategy: 'optimisticWithLCS' as any
943
- });
944
- await syncSaga.done;
945
- } catch (err) {
946
- // connection/sync error is fine/expected right now since sync protocol (Phase 3) might be incomplete
947
- devLog(`2B Sync: Coordinator sync executed (connect phase successfully run; subsequent sync errors expected in 2B): ${extractErrorMsg(err)}`);
948
- }
949
-
950
- devLog('✓ 2B Sync Execution Complete! Ready for State Checks.');
951
- btn.textContent = '✓ 2B Sync Run';
952
-
953
- // Enable check button
954
- const checkBtn = document.getElementById('btn-2b-check') as HTMLButtonElement | null;
955
- if (checkBtn) checkBtn.disabled = false;
956
-
957
- } catch (error) {
958
- devLog(`✗ 2B Sync FAILED: ${extractErrorMsg(error)}`);
959
- console.error(`${lc_fn} 2B Sync error:`, error);
960
- btn.disabled = false;
961
- }
886
+ if (!btn) { return; }
887
+ btn.addEventListener('click', () => {
888
+ performPhaseSync({
889
+ phaseText: '2B',
890
+ btnId: 'btn-2b-sync',
891
+ nextBtnId: 'btn-2b-check',
892
+ expectSyncFailure: true,
893
+ failureMessageSuffix: 'connect phase successfully run; subsequent sync errors expected in 2B'
894
+ });
962
895
  });
963
896
  }
964
897
 
965
898
  function init2bCheckButton(): void {
966
899
  const lc_fn = `${lc}[init2bCheckButton]`;
967
900
  const btn = document.getElementById('btn-2b-check') as HTMLButtonElement | null;
968
- if (!btn) return;
901
+ if (!btn) { return; }
969
902
 
970
903
  btn.addEventListener('click', async () => {
971
904
  try {
@@ -1070,7 +1003,7 @@ function init2bCheckButton(): void {
1070
1003
 
1071
1004
  // Enable Phase 3B setup
1072
1005
  const p3SetupBtn = document.getElementById('btn-3b-setup') as HTMLButtonElement | null;
1073
- if (p3SetupBtn) p3SetupBtn.disabled = false;
1006
+ if (p3SetupBtn) { p3SetupBtn.disabled = false; }
1074
1007
 
1075
1008
  } catch (error) {
1076
1009
  devLog(`✗ 2B Check FAILED: ${extractErrorMsg(error)}`);
@@ -1079,20 +1012,186 @@ function init2bCheckButton(): void {
1079
1012
  });
1080
1013
  }
1081
1014
 
1082
- function init3bPlaceholder(): void {
1083
- const btnSetup = document.getElementById('btn-3b-setup') as HTMLButtonElement | null;
1084
- const btnSync = document.getElementById('btn-3b-sync') as HTMLButtonElement | null;
1085
- const btnCheck = document.getElementById('btn-3b-check') as HTMLButtonElement | null;
1015
+ function init3bSetupButton(): void {
1016
+ const btn = document.getElementById('btn-3b-setup') as HTMLButtonElement | null;
1017
+ if (!btn) { return; }
1018
+ btn.addEventListener('click', () => {
1019
+ performPhaseSetup({
1020
+ phaseText: '3B',
1021
+ btnId: 'btn-3b-setup',
1022
+ nextBtnId: 'btn-3b-sync',
1023
+ masterSecret: 'test-sender-secret-phase3',
1024
+ syncSalt: 'senderidentitysyncsaltphase3',
1025
+ manageSalt: 'senderidentitymanagesaltphase3',
1026
+ ib: 'test data 3b',
1027
+ data: { hello: 'world3', random: Math.random() }
1028
+ });
1029
+ });
1030
+ }
1086
1031
 
1087
- if (btnSetup) {
1088
- btnSetup.addEventListener('click', () => devLog('3B Setup: Placeholders ready for Phase 3B (Transactional Sync).'));
1089
- }
1090
- if (btnSync) {
1091
- btnSync.addEventListener('click', () => devLog('3B Sync: Placeholders ready for Phase 3B (Transactional Sync).'));
1092
- }
1093
- if (btnCheck) {
1094
- btnCheck.addEventListener('click', () => devLog('3B Check: Placeholders ready for Phase 3B (Transactional Sync).'));
1095
- }
1032
+ function init3bSyncButton(): void {
1033
+ const btn = document.getElementById('btn-3b-sync') as HTMLButtonElement | null;
1034
+ if (!btn) { return; }
1035
+ btn.addEventListener('click', () => {
1036
+ performPhaseSync({
1037
+ phaseText: '3B',
1038
+ btnId: 'btn-3b-sync',
1039
+ nextBtnId: 'btn-3b-check'
1040
+ });
1041
+ });
1042
+ }
1043
+
1044
+ function init3bCheckButton(): void {
1045
+ const lc_fn = `${lc}[init3bCheckButton]`;
1046
+ const btn = document.getElementById('btn-3b-check') as HTMLButtonElement | null;
1047
+ if (!btn) { return; }
1048
+
1049
+ btn.addEventListener('click', async () => {
1050
+ try {
1051
+ debugger; // allow for walkthrough 3B check
1052
+ devLog('3B Check: Asserting cryptographic and WebSocket state expectations...');
1053
+
1054
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
1055
+ const space = await metaspace.getLocalUserSpace({});
1056
+ if (!space) { throw new Error("No default space."); }
1057
+
1058
+ const domainI = debugState.domainI!;
1059
+ const targetX = debugState.targetX!;
1060
+ const xAddr = getIbGibAddr({ ibGib: targetX });
1061
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
1062
+
1063
+ // 1. Assert Target X exists locally (with evolved timeline) and get latest local graph
1064
+ const latestLocalXAddr = await metaspace.getLatestAddr({
1065
+ addr: xAddr,
1066
+ space
1067
+ });
1068
+ if (!latestLocalXAddr) {
1069
+ devLog('✗ Check: Evolved target X NOT found in local space!');
1070
+ return;
1071
+ }
1072
+ devLog(`✓ Check: Evolved target X found locally at: ${latestLocalXAddr}`);
1073
+
1074
+ const localXGraph = await metaspace.getDependencyGraph({
1075
+ ibGibAddr: latestLocalXAddr,
1076
+ space
1077
+ });
1078
+ if (!localXGraph || Object.keys(localXGraph).length === 0) {
1079
+ devLog('✗ Check: Failed to load target X dependency graph locally.');
1080
+ return;
1081
+ }
1082
+ devLog(`✓ Check: Target X local graph loaded (${Object.keys(localXGraph).length} nodes).`);
1083
+
1084
+ // 2. Fetch server's Target X graph via API bridge
1085
+ const apiBridge = new SpaceGibApiBridge();
1086
+ const serverGetRes = await apiBridge.getIbGibGraph(domainAddr, xAddr, true);
1087
+ if (!serverGetRes.success || !serverGetRes.graph) {
1088
+ devLog(`✗ Check: Failed to fetch target X graph from server: ${serverGetRes.message}`);
1089
+ return;
1090
+ }
1091
+ const serverXGraph = serverGetRes.graph;
1092
+ devLog(`✓ Check: Target X server graph loaded (${Object.keys(serverXGraph).length} nodes).`);
1093
+
1094
+ // 3. Verify graphs are equivalent using graphsAreEquivalent
1095
+ const equal = graphsAreEquivalent({ graphA: localXGraph, graphB: serverXGraph });
1096
+ if (equal) {
1097
+ devLog('✓ Check: Target X dependency graphs are equivalent on client and server.');
1098
+ } else {
1099
+ devLog('✗ Check: Target X dependency graphs mismatch between client and server!');
1100
+ return;
1101
+ }
1102
+
1103
+ // 4. Assert evolved sender identity I1 exists locally and on server
1104
+ const latestLocalIAddr = await metaspace.getLatestAddr({
1105
+ addr: domainAddr,
1106
+ space
1107
+ });
1108
+ if (!latestLocalIAddr) {
1109
+ devLog('✗ Check: Evolved domain keystone tip I1 NOT found in local space!');
1110
+ return;
1111
+ }
1112
+ devLog(`✓ Check: Evolved Domain Keystone (I1) exists locally.`);
1113
+
1114
+ const getRes = await metaspace.get({ addrs: [latestLocalIAddr], space });
1115
+ const localI = getRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
1116
+ if (!localI) {
1117
+ devLog('✗ Check: Failed to load I1 from local space.');
1118
+ return;
1119
+ }
1120
+
1121
+ const syncProof = localI.data?.proofs?.find(p => p.claim?.verb === 'sync');
1122
+ const sessionIdentityTjpAddr = syncProof?.claim?.target;
1123
+ if (!sessionIdentityTjpAddr) {
1124
+ devLog('✗ Check: I1 sync claim does not target a session keystone.');
1125
+ return;
1126
+ }
1127
+
1128
+ const serverIGetRes = await apiBridge.getIbGib(domainAddr, latestLocalIAddr);
1129
+ if (!serverIGetRes.success || !serverIGetRes.ibGib) {
1130
+ devLog(`✗ Check: Evolved Domain Keystone (I1) NOT found on server!`);
1131
+ return;
1132
+ }
1133
+ devLog(`✓ Check: Evolved Domain Keystone (I1) exists on server.`);
1134
+
1135
+ // 5. Assert session identity S tip exists locally and on server
1136
+ const latestLocalSAddr = await metaspace.getLatestAddr({
1137
+ addr: sessionIdentityTjpAddr,
1138
+ space
1139
+ });
1140
+ if (!latestLocalSAddr) {
1141
+ devLog('✗ Check: Latest Session Keystone (S) NOT found in local space.');
1142
+ return;
1143
+ }
1144
+ devLog(`✓ Check: Found latest session keystone (S) locally: ${latestLocalSAddr}`);
1145
+
1146
+ const localSRes = await metaspace.get({ addrs: [latestLocalSAddr], space });
1147
+ const sessionS = localSRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
1148
+ if (!sessionS) {
1149
+ devLog('✗ Check: Failed to load latest Session Keystone (S) locally.');
1150
+ return;
1151
+ }
1152
+
1153
+ // Verify S evolved to correct n (n === 3 or 4)
1154
+ const n = sessionS.data?.n;
1155
+ devLog(`✓ Check: S tip evolved to n = ${n}.`);
1156
+ if (n !== 3 && n !== 4) {
1157
+ devLog(`⚠ Check: Expected S to evolve to n = 3 or 4, got n = ${n}. Continuing anyway...`);
1158
+ }
1159
+
1160
+ const serverSGetRes = await apiBridge.getIbGib(domainAddr, latestLocalSAddr);
1161
+ if (!serverSGetRes.success || !serverSGetRes.ibGib) {
1162
+ devLog(`✗ Check: Latest Session Keystone (S) NOT found on server!`);
1163
+ return;
1164
+ }
1165
+ devLog(`✓ Check: Latest Session Keystone (S) exists on server.`);
1166
+
1167
+ // 6. Assert S graph is identical on client and server
1168
+ const localSGraph = await metaspace.getDependencyGraph({
1169
+ ibGibAddr: latestLocalSAddr,
1170
+ space
1171
+ });
1172
+ const serverSGetGraphRes = await apiBridge.getIbGibGraph(domainAddr, sessionIdentityTjpAddr, true);
1173
+ if (!serverSGetGraphRes.success || !serverSGetGraphRes.graph) {
1174
+ devLog(`✗ Check: Failed to fetch session S graph from server: ${serverSGetGraphRes.message}`);
1175
+ return;
1176
+ }
1177
+ const serverSGraph = serverSGetGraphRes.graph;
1178
+
1179
+ const sGraphsEqual = graphsAreEquivalent({ graphA: localSGraph, graphB: serverSGraph });
1180
+ if (sGraphsEqual) {
1181
+ devLog('✓ Check: Session Keystone (S) dependency graphs are equivalent on client and server.');
1182
+ } else {
1183
+ devLog('✗ Check: Session Keystone (S) dependency graphs mismatch between client and server!');
1184
+ return;
1185
+ }
1186
+
1187
+ devLog('🎉 ALL PHASE 3B TRANSACTION SYNC CHECKS PASSED FLAWLESSLY! ✓');
1188
+ btn.textContent = '✓ 3B All Passed';
1189
+
1190
+ } catch (error) {
1191
+ devLog(`✗ 3B Check FAILED: ${extractErrorMsg(error)}`);
1192
+ console.error(`${lc_fn} 3B Check error:`, error);
1193
+ }
1194
+ });
1096
1195
  }
1097
1196
 
1098
1197
  // ---------------------------------------------------------------------------
@@ -1118,7 +1217,9 @@ export function initDevTools(): void {
1118
1217
  init2bSetupButton();
1119
1218
  init2bSyncButton();
1120
1219
  init2bCheckButton();
1121
- init3bPlaceholder();
1220
+ init3bSetupButton();
1221
+ init3bSyncButton();
1222
+ init3bCheckButton();
1122
1223
  } catch (error) {
1123
1224
  console.error(`${lc_fn} ${extractErrorMsg(error)}`);
1124
1225
  }