@ibgib/space-gib 0.0.1 → 0.0.2

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 (34) hide show
  1. package/dist/client/bootstrap.mjs +1 -1
  2. package/dist/client/bootstrap.mjs.map +1 -1
  3. package/dist/client/chunk-BL2SGXS4.mjs +18994 -0
  4. package/dist/client/chunk-RDTAT5G4.mjs +235 -0
  5. package/dist/client/chunk-RDTAT5G4.mjs.map +7 -0
  6. package/dist/client/chunk-RE7XSMHH.mjs +31 -0
  7. package/dist/client/chunk-RE7XSMHH.mjs.map +7 -0
  8. package/dist/client/chunk-YUSGN3J4.mjs +23119 -0
  9. package/dist/client/index.html +44 -8
  10. package/dist/client/index.mjs +1 -1
  11. package/dist/client/script.mjs +1 -1
  12. package/dist/client/style.css +26 -0
  13. package/dist/server/server.mjs +5323 -1011
  14. package/dist/server/server.mjs.map +4 -4
  15. package/package.json +1 -1
  16. package/src/client/AUTO-GENERATED-version.mts +1 -1
  17. package/src/client/api/space-gib-api-bridge.mts +84 -8
  18. package/src/client/dev-tools.mts +609 -24
  19. package/src/client/index.html +44 -8
  20. package/src/client/style.css +26 -0
  21. package/src/common/keystone-policies.json +64 -0
  22. package/src/common/keystone-policies.mts +39 -86
  23. package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +13 -12
  24. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +14 -167
  25. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +6 -6
  26. package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +10 -25
  27. package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +201 -0
  28. package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +13 -487
  29. package/src/server/serve-gib/handlers/ws/ws-helper.mts +80 -3
  30. package/dist/client/chunk-CT47Z5WU.mjs +0 -21
  31. package/dist/client/chunk-CT47Z5WU.mjs.map +0 -7
  32. package/dist/client/chunk-RHEDTRKF.mjs +0 -235
  33. package/dist/client/chunk-RHEDTRKF.mjs.map +0 -7
  34. package/dist/respec-gib.node.mjs +0 -5
@@ -10,21 +10,25 @@
10
10
  * working end-to-end.
11
11
  */
12
12
 
13
- import { extractErrorMsg, getTimestamp, getUUID, HashAlgorithm } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
13
+ import { extractErrorMsg, getTimestamp, getUUID, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
14
14
  import { Factory_V1 as factory } from '@ibgib/ts-gib/dist/V1/factory.mjs';
15
15
  import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
16
16
  import { getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
17
17
  import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
18
18
  import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
19
- import { KeystoneChallengeType, KeystoneIbGib_V1, KeystoneReplenishStrategy } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
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
+ 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';
23
+ import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
24
+ import { KEYSTONE_VERB_CONNECT, POOL_ID_CONNECT, POOL_ID_SYNC } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
21
25
  import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
22
26
 
23
27
  import { GLOBAL_LOG_A_LOT } from './constants.mjs';
24
- import { SESSION_KEYSTONE_POLICY, getHandshakeChallenge } from "../common/keystone-policies.mjs";
25
- import { KeystoneStrategyFactory } from '@ibgib/core-gib/dist/keystone/strategy/keystone-strategy-factory.mjs';
28
+ import {
29
+ SESSION_KEYSTONE_POLICY, getConnectChallenge, getSpaceGibPoolConfig
30
+ } from "../common/keystone-policies.mjs";
26
31
  import { SpaceGibApiBridge } from './api/space-gib-api-bridge.mjs';
27
- import { KeystoneVerb } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
28
32
 
29
33
  const logalot = GLOBAL_LOG_A_LOT
30
34
  const lc = '[dev-tools]';
@@ -35,6 +39,8 @@ interface DebugState {
35
39
  targetX?: IbGib_V1;
36
40
  sessionS?: KeystoneIbGib_V1;
37
41
  sessionSecret?: string;
42
+ senderPeer?: SyncPeerWebSocketSender_V1;
43
+ sagaId?: string;
38
44
  }
39
45
 
40
46
  export const debugState: DebugState = {};
@@ -276,19 +282,19 @@ function initCreateSessionKeystoneButton(): void {
276
282
  allowedVerbs: [],
277
283
  },
278
284
  {
279
- id: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID,
285
+ id: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
280
286
  salt: await getUUID(),
281
287
  type: SESSION_KEYSTONE_POLICY.COMMON.TYPE,
282
288
  algo: SESSION_KEYSTONE_POLICY.COMMON.ALGO,
283
289
  rounds: SESSION_KEYSTONE_POLICY.COMMON.ROUNDS,
284
290
  behavior: {
285
- size: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.SIZE,
291
+ size: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SIZE,
286
292
  replenish: SESSION_KEYSTONE_POLICY.COMMON.REPLENISH,
287
- selectSequentially: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.SELECT_SEQUENTIALLY,
288
- selectRandomly: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.SELECT_RANDOMLY,
289
- targetBindingChars: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.TARGET_BINDING_CHARS,
293
+ selectSequentially: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SELECT_SEQUENTIALLY,
294
+ selectRandomly: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SELECT_RANDOMLY,
295
+ targetBindingChars: SESSION_KEYSTONE_POLICY.CONNECT_POOL.TARGET_BINDING_CHARS,
290
296
  },
291
- allowedVerbs: [SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.VERB],
297
+ allowedVerbs: [SESSION_KEYSTONE_POLICY.CONNECT_POOL.VERB],
292
298
  }
293
299
  ],
294
300
  metaspace,
@@ -394,7 +400,7 @@ function initEvolveDomainKeystoneButton(): void {
394
400
  }
395
401
 
396
402
  // ---------------------------------------------------------------------------
397
- // Perform Sync Handshake (S) button
403
+ // Perform Sync Connect (S) button
398
404
  // ---------------------------------------------------------------------------
399
405
 
400
406
  function initPerformSyncButton(): void {
@@ -406,7 +412,7 @@ function initPerformSyncButton(): void {
406
412
  try {
407
413
  debugger; // walk through sync
408
414
  btn.disabled = true;
409
- devLog('Initiating Sync Handshake via WebSocket...');
415
+ devLog('Initiating Sync Connect via WebSocket...');
410
416
 
411
417
  const sessionS = debugState.sessionS;
412
418
  const sessionSecret = debugState.sessionSecret;
@@ -423,17 +429,17 @@ function initPerformSyncButton(): void {
423
429
  if (!space) { throw new Error("No space."); }
424
430
 
425
431
  devLog('Pre-solving upfront picket-fence challenge...');
426
- const handshakePool = (sessionS.data?.challengePools ?? []).find(p => p.id === SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID);
427
- if (!handshakePool) {
428
- throw new Error(`Session keystone missing "${SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID}" pool`);
432
+ const connectPool = (sessionS.data?.challengePools ?? []).find(p => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
433
+ if (!connectPool) {
434
+ throw new Error(`Session keystone missing "${SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID}" pool`);
429
435
  }
430
436
 
431
- const { challengeId } = getHandshakeChallenge(sessionS);
432
- const strategy = KeystoneStrategyFactory.create({ config: handshakePool.config });
437
+ const { challengeId } = getConnectChallenge(sessionS);
438
+ const strategy = KeystoneStrategyFactory.create({ config: connectPool.config });
433
439
  const poolSecret = await strategy.derivePoolSecret({ masterSecret: sessionSecret });
434
440
  const solution = await strategy.generateSolution({
435
441
  poolSecret,
436
- poolId: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID,
442
+ poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
437
443
  challengeId
438
444
  });
439
445
 
@@ -464,16 +470,16 @@ function initPerformSyncButton(): void {
464
470
  }));
465
471
  } else if (msg.type === 'auth-challenge') {
466
472
  const { challengeUuid, demandedIds } = msg;
467
- devLog(`→ server demanded ${demandedIds.length} ids. Signing handshake proof...`);
473
+ devLog(`→ server demanded ${demandedIds.length} ids. Signing connect proof...`);
468
474
 
469
475
  const keystoneService = new KeystoneService_V1();
470
476
  const proofFrame = await keystoneService.sign({
471
477
  latestKeystone: sessionS,
472
478
  masterSecret: sessionSecret,
473
- poolId: 'handshake',
479
+ poolId: POOL_ID_CONNECT,
474
480
  requiredChallengeIds: demandedIds,
475
481
  claim: {
476
- verb: 'handshake',
482
+ verb: KEYSTONE_VERB_CONNECT,
477
483
  target: challengeUuid
478
484
  },
479
485
  metaspace,
@@ -486,10 +492,10 @@ function initPerformSyncButton(): void {
486
492
  proofFrame
487
493
  }));
488
494
  } else if (msg.type === 'auth-ok') {
489
- devLog('✓ Handshake SUCCESS! Sync session authorized.');
495
+ devLog('✓ Connect SUCCESS! Sync session authorized.');
490
496
  btn.textContent = '✓ Sync Authorized';
491
497
  } else if (msg.type === 'auth-fail') {
492
- devLog(`✗ Handshake FAILED: ${msg.message}`);
498
+ devLog(`✗ Connect FAILED: ${msg.message}`);
493
499
  btn.disabled = false;
494
500
  }
495
501
  } catch (error) {
@@ -517,6 +523,578 @@ function initPerformSyncButton(): void {
517
523
  });
518
524
  }
519
525
 
526
+ // ---------------------------------------------------------------------------
527
+ // Phase 1B: Standardized Setup / Sync / Check testing loop
528
+ // ---------------------------------------------------------------------------
529
+
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;
534
+
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)...');
539
+
540
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
541
+ const space = await metaspace.getLocalUserSpace({});
542
+ if (!space) { throw new Error("No default space."); }
543
+
544
+ const masterSecret = 'test-sender-secret-phase1';
545
+ const keystoneService = new KeystoneService_V1();
546
+
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
+ });
558
+
559
+ debugState.domainI = domainI;
560
+ debugState.domainIMasterSecret = masterSecret;
561
+
562
+ devLog(`1B Setup: ✓ Domain Keystone (I) created locally: ${getIbGibAddr({ ibGib: domainI })}`);
563
+ devLog(`1B Setup: Starting postGenesisKeystone...`);
564
+
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
+ }
574
+
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 })}`);
586
+
587
+ devLog('✓ 1B Setup Complete! Ready for 1B Sync.');
588
+ btn.textContent = '✓ 1B Setup Complete';
589
+
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;
593
+
594
+ } catch (error) {
595
+ devLog(`✗ 1B Setup FAILED: ${extractErrorMsg(error)}`);
596
+ console.error(`${lc_fn} 1B Setup error:`, error);
597
+ btn.disabled = false;
598
+ }
599
+ });
600
+ }
601
+
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;
606
+
607
+ btn.addEventListener('click', async () => {
608
+ 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,
641
+ senderIdentity: domainI,
642
+ 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]
647
+ });
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)}`);
664
+ }
665
+
666
+ devLog('✓ 1B Sync Execution Complete! Ready for State Checks.');
667
+ btn.textContent = '✓ 1B Sync Run';
668
+
669
+ // Enable check button
670
+ const checkBtn = document.getElementById('btn-1b-check') as HTMLButtonElement | null;
671
+ if (checkBtn) checkBtn.disabled = false;
672
+
673
+ } catch (error) {
674
+ devLog(`✗ 1B Sync FAILED: ${extractErrorMsg(error)}`);
675
+ console.error(`${lc_fn} 1B Sync error:`, error);
676
+ btn.disabled = false;
677
+ }
678
+ });
679
+ }
680
+
681
+ function init1bCheckButton(): void {
682
+ const lc_fn = `${lc}[init1bCheckButton]`;
683
+ const btn = document.getElementById('btn-1b-check') as HTMLButtonElement | null;
684
+ if (!btn) return;
685
+
686
+ btn.addEventListener('click', async () => {
687
+ try {
688
+ devLog('1B Check: Asserting cryptographic and durable state expectations from respec...');
689
+
690
+ debugger; // allow for walkthrough 1B check
691
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
692
+ const space = await metaspace.getLocalUserSpace({});
693
+ if (!space) { throw new Error("No default space."); }
694
+
695
+ const domainI = debugState.domainI!;
696
+ const targetX = debugState.targetX!;
697
+ const xAddr = getIbGibAddr({ ibGib: targetX });
698
+
699
+ // 1. Resolve evolved sender identity I1 locally
700
+ const newSenderIdentityAddr = await metaspace.getLatestAddr({
701
+ addr: getIbGibAddr({ ibGib: domainI }),
702
+ space
703
+ });
704
+
705
+ if (!newSenderIdentityAddr) {
706
+ devLog('✗ Check: Evolved domain keystone tip I1 NOT found in local space!');
707
+ return;
708
+ }
709
+ devLog(`✓ Check: Evolved Domain Keystone (I1) created and stored locally.`);
710
+
711
+ const getRes = await metaspace.get({ addrs: [newSenderIdentityAddr], space });
712
+ const newSenderIdentity = getRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
713
+
714
+ if (!newSenderIdentity) {
715
+ devLog('✗ Check: Failed to load I1 from local space.');
716
+ return;
717
+ }
718
+
719
+ const syncProof = newSenderIdentity.data?.proofs?.find(p => p.claim?.verb === 'sync');
720
+ if (!syncProof) {
721
+ devLog('✗ Check: I1 is missing sync-verb claim proof!');
722
+ } else {
723
+ devLog('✓ Check: I1 contains valid sync-verb claim proof.');
724
+ }
725
+
726
+ const sessionIdentityTjpAddr = syncProof?.claim?.target;
727
+ if (!sessionIdentityTjpAddr) {
728
+ devLog('✗ Check: I1 sync claim does not target a session keystone.');
729
+ return;
730
+ }
731
+ devLog(`✓ Check: Sync claim targets session keystone: ${sessionIdentityTjpAddr}`);
732
+
733
+ // 2. Load Session Keystone (S) locally
734
+ const sRes = await metaspace.get({ addrs: [sessionIdentityTjpAddr], space });
735
+ const sessionIdentity = sRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
736
+
737
+ if (!sessionIdentity) {
738
+ devLog('✗ Check: Session Keystone (S) NOT found in local space.');
739
+ return;
740
+ }
741
+ devLog('✓ Check: Session Keystone (S) successfully created locally.');
742
+
743
+ const sPools = sessionIdentity.data?.challengePools ?? [];
744
+ const hasConnectPool = sPools.some(p => p.id === POOL_ID_CONNECT);
745
+ const hasSyncPool = sPools.some(p => p.id === POOL_ID_SYNC);
746
+
747
+ if (hasConnectPool && hasSyncPool) {
748
+ devLog('✓ Check: S contains both connect and sync challenge pools.');
749
+ } else {
750
+ devLog(`✗ Check: S is missing challenge pools! (connect: ${hasConnectPool}, sync: ${hasSyncPool})`);
751
+ }
752
+
753
+ // we don't check this yet until sync ping pong
754
+ // const targetAddrs = sessionIdentity.data?.frameDetails?.targetAddrs ?? [];
755
+ // if (targetAddrs.includes(xAddr)) {
756
+ // devLog('✓ Check: S is securely bound to target domain X in frameDetails.');
757
+ // } else {
758
+ // devLog('✗ Check: S is not bound to target domain X!');
759
+ // }
760
+
761
+ // #region 3. Verify Server-Side (receiver) registrations via GET REST API
762
+ devLog('Check: Verifying registrations on server (receiver metaspace)...');
763
+ const apiBridge = new SpaceGibApiBridge();
764
+
765
+ // Check if I, evolved I1 and session S exist on server
766
+ let hasI = false;
767
+ let hasI1 = false;
768
+ let hasS = false;
769
+
770
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
771
+ const resGetDomainIGraphFromServer = await apiBridge.getKeystoneGraph(domainAddr);
772
+ if (resGetDomainIGraphFromServer.success && resGetDomainIGraphFromServer.graph) {
773
+ const domainGraph = resGetDomainIGraphFromServer.graph;
774
+ hasI = domainGraph[domainAddr] !== undefined;
775
+ hasI1 = domainGraph[newSenderIdentityAddr] !== undefined;
776
+ } else {
777
+ devLog(`✗ Check Server: Failed to fetch keystone graph from server: ${resGetDomainIGraphFromServer.message}`);
778
+ }
779
+
780
+ const resGetSessionSFromServer = await apiBridge.getIbGib(domainAddr, sessionIdentityTjpAddr);
781
+ if (resGetSessionSFromServer.success && resGetSessionSFromServer.ibGib) {
782
+ hasS = true;
783
+ } else {
784
+ devLog(`✗ Check Server: Failed to fetch session keystone from server: ${resGetSessionSFromServer.message}`);
785
+ }
786
+
787
+ if (hasI) devLog('✓ Check Server: Genesis Domain Keystone (I) registered on server.');
788
+ 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.');
790
+ 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.');
792
+ else devLog('✗ Check Server: Session Keystone (S) missing on server!');
793
+ if (hasI && hasI1 && hasS) {
794
+ devLog('🎉 ALL PHASE 1B ESTABLISH CHECKS PASSED FLAWLESSLY! ✓');
795
+ btn.textContent = '✓ 1B All Passed';
796
+
797
+ const btn2bSetup = document.getElementById('btn-2b-setup') as HTMLButtonElement | null;
798
+ if (btn2bSetup) btn2bSetup.disabled = false;
799
+ }
800
+
801
+ // #endregion 3. Verify Server-Side (receiver) registrations via GET REST API
802
+
803
+ } catch (error) {
804
+ devLog(`✗ 1B Check FAILED: ${extractErrorMsg(error)}`);
805
+ console.error(`${lc_fn} 1B Check error:`, error);
806
+ }
807
+ });
808
+ }
809
+
810
+ // ---------------------------------------------------------------------------
811
+ // Phase 2B Implementation
812
+ // ---------------------------------------------------------------------------
813
+
814
+ function init2bSetupButton(): void {
815
+ const lc_fn = `${lc}[init2bSetupButton]`;
816
+ 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
+ }
883
+ });
884
+ }
885
+
886
+ function init2bSyncButton(): void {
887
+ const lc_fn = `${lc}[init2bSyncButton]`;
888
+ 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
+ }
962
+ });
963
+ }
964
+
965
+ function init2bCheckButton(): void {
966
+ const lc_fn = `${lc}[init2bCheckButton]`;
967
+ const btn = document.getElementById('btn-2b-check') as HTMLButtonElement | null;
968
+ if (!btn) return;
969
+
970
+ btn.addEventListener('click', async () => {
971
+ try {
972
+ debugger; // allow for walkthrough 2B check
973
+ devLog('2B Check: Asserting cryptographic and WebSocket state expectations...');
974
+
975
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
976
+ const space = await metaspace.getLocalUserSpace({});
977
+ if (!space) { throw new Error("No default space."); }
978
+
979
+ const domainI = debugState.domainI!;
980
+ const targetX = debugState.targetX!;
981
+ const xAddr = getIbGibAddr({ ibGib: targetX });
982
+
983
+ // 1. Resolve evolved sender identity I1 locally
984
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
985
+ const newSenderIdentityAddr = await metaspace.getLatestAddr({
986
+ addr: domainAddr,
987
+ space
988
+ });
989
+
990
+ if (!newSenderIdentityAddr) {
991
+ devLog('✗ Check: Evolved domain keystone tip I1 NOT found in local space!');
992
+ return;
993
+ }
994
+ devLog(`✓ Check: Evolved Domain Keystone (I1) created and stored locally.`);
995
+
996
+ const getRes = await metaspace.get({ addrs: [newSenderIdentityAddr], space });
997
+ const newSenderIdentity = getRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
998
+
999
+ if (!newSenderIdentity) {
1000
+ devLog('✗ Check: Failed to load I1 from local space.');
1001
+ return;
1002
+ }
1003
+
1004
+ const syncProof = newSenderIdentity.data?.proofs?.find(p => p.claim?.verb === 'sync');
1005
+ const sessionIdentityTjpAddr = syncProof?.claim?.target;
1006
+ if (!sessionIdentityTjpAddr) {
1007
+ devLog('✗ Check: I1 sync claim does not target a session keystone.');
1008
+ return;
1009
+ }
1010
+ devLog(`✓ Check: Sync claim targets session keystone: ${sessionIdentityTjpAddr}`);
1011
+
1012
+ // 2. Load Session Keystone (S) locally and get latest tip
1013
+ const latestSessionSAddr = await metaspace.getLatestAddr({
1014
+ addr: sessionIdentityTjpAddr,
1015
+ space
1016
+ });
1017
+ if (!latestSessionSAddr) {
1018
+ devLog('✗ Check: Latest Session Keystone (S) NOT found in local space.');
1019
+ return;
1020
+ }
1021
+ devLog(`✓ Check: Found latest session keystone (S) locally.`);
1022
+
1023
+ const sRes = await metaspace.get({ addrs: [latestSessionSAddr], space });
1024
+ const sessionIdentity = sRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
1025
+
1026
+ if (!sessionIdentity) {
1027
+ devLog('✗ Check: Failed to load latest Session Keystone (S).');
1028
+ return;
1029
+ }
1030
+
1031
+ // Verify S contains connect and sync pools
1032
+ const sPools = sessionIdentity.data?.challengePools ?? [];
1033
+ const connectPool = sPools.find(p => p.id === POOL_ID_CONNECT);
1034
+ const syncPool = sPools.find(p => p.id === POOL_ID_SYNC);
1035
+
1036
+ if (connectPool && syncPool) {
1037
+ devLog('✓ Check: S contains both connect and sync challenge pools.');
1038
+ } else {
1039
+ devLog(`✗ Check: S is missing challenge pools! (connect: ${!!connectPool}, sync: ${!!syncPool})`);
1040
+ return;
1041
+ }
1042
+
1043
+ // Verify S was successfully evolved (depleted connect pool)
1044
+ const remainingChallenges = Object.keys(connectPool.challenges).length;
1045
+ if (remainingChallenges === 0) {
1046
+ devLog('✓ Check: S connect pool successfully depleted (0 challenges remaining).');
1047
+ } else {
1048
+ devLog(`✗ Check: S connect pool not depleted! (${remainingChallenges} challenges remaining)`);
1049
+ return;
1050
+ }
1051
+
1052
+ // we don't check this yet until sync ping pong
1053
+ // const targetAddrs = sessionIdentity.data?.frameDetails?.targetAddrs ?? [];
1054
+ // if (targetAddrs.includes(xAddr)) {
1055
+ // devLog('✓ Check: S is securely bound to target domain X in frameDetails.');
1056
+ // } else {
1057
+ // devLog('✗ Check: S is not bound to target domain X!');
1058
+ // }
1059
+
1060
+ // 3. Verify WebSocket Connection is open and active
1061
+ if (debugState.senderPeer && debugState.senderPeer.isSocketOpen) {
1062
+ devLog('✓ Check: WebSocket connection is active and OPEN.');
1063
+ } else {
1064
+ devLog('✗ Check: WebSocket connection is NOT open or peer is missing!');
1065
+ return;
1066
+ }
1067
+
1068
+ devLog('🎉 ALL PHASE 2B CONNECT CHECKS PASSED FLAWLESSLY! ✓');
1069
+ btn.textContent = '✓ 2B All Passed';
1070
+
1071
+ // Enable Phase 3B setup
1072
+ const p3SetupBtn = document.getElementById('btn-3b-setup') as HTMLButtonElement | null;
1073
+ if (p3SetupBtn) p3SetupBtn.disabled = false;
1074
+
1075
+ } catch (error) {
1076
+ devLog(`✗ 2B Check FAILED: ${extractErrorMsg(error)}`);
1077
+ console.error(`${lc_fn} 2B Check error:`, error);
1078
+ }
1079
+ });
1080
+ }
1081
+
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;
1086
+
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
+ }
1096
+ }
1097
+
520
1098
  // ---------------------------------------------------------------------------
521
1099
  // Dev Tools UI Init
522
1100
  // ---------------------------------------------------------------------------
@@ -534,6 +1112,13 @@ export function initDevTools(): void {
534
1112
  initCreateSessionKeystoneButton();
535
1113
  initEvolveDomainKeystoneButton();
536
1114
  initPerformSyncButton();
1115
+ init1bSetupButton();
1116
+ init1bSyncButton();
1117
+ init1bCheckButton();
1118
+ init2bSetupButton();
1119
+ init2bSyncButton();
1120
+ init2bCheckButton();
1121
+ init3bPlaceholder();
537
1122
  } catch (error) {
538
1123
  console.error(`${lc_fn} ${extractErrorMsg(error)}`);
539
1124
  }