@ibgib/space-gib 0.0.1 → 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.
- package/dist/client/bootstrap.mjs +1 -1
- package/dist/client/bootstrap.mjs.map +1 -1
- package/dist/client/chunk-2KJC5XKE.mjs +31 -0
- package/dist/client/chunk-2KJC5XKE.mjs.map +7 -0
- package/dist/client/chunk-QNIXTRFO.mjs +235 -0
- package/dist/client/chunk-QNIXTRFO.mjs.map +7 -0
- package/dist/client/index.html +47 -9
- package/dist/client/index.mjs +1 -1
- package/dist/client/script.mjs +1 -1
- package/dist/client/style.css +26 -0
- package/dist/server/server.mjs +5752 -1010
- package/dist/server/server.mjs.map +4 -4
- package/package.json +1 -1
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/api/space-gib-api-bridge.mts +119 -8
- package/src/client/dev-tools.mts +717 -31
- package/src/client/index.html +47 -9
- package/src/client/style.css +26 -0
- package/src/common/keystone-policies.json +64 -0
- package/src/common/keystone-policies.mts +39 -86
- package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +13 -12
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +14 -167
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +6 -6
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +3 -3
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +10 -25
- package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +205 -0
- package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +13 -487
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +80 -3
- package/dist/client/chunk-CT47Z5WU.mjs +0 -21
- package/dist/client/chunk-CT47Z5WU.mjs.map +0 -7
- package/dist/client/chunk-RHEDTRKF.mjs +0 -235
- package/dist/client/chunk-RHEDTRKF.mjs.map +0 -7
- package/dist/respec-gib.node.mjs +0 -5
package/src/client/dev-tools.mts
CHANGED
|
@@ -10,21 +10,27 @@
|
|
|
10
10
|
* working end-to-end.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { extractErrorMsg, getTimestamp, getUUID,
|
|
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 {
|
|
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/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";
|
|
26
|
+
import { graphsAreEquivalent } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
|
|
22
27
|
|
|
23
28
|
import { GLOBAL_LOG_A_LOT } from './constants.mjs';
|
|
24
|
-
import {
|
|
25
|
-
|
|
29
|
+
import {
|
|
30
|
+
SESSION_KEYSTONE_POLICY, getConnectChallenge, getSpaceGibPoolConfig
|
|
31
|
+
} from "../common/keystone-policies.mjs";
|
|
26
32
|
import { SpaceGibApiBridge } from './api/space-gib-api-bridge.mjs';
|
|
27
|
-
import {
|
|
33
|
+
import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
|
|
28
34
|
|
|
29
35
|
const logalot = GLOBAL_LOG_A_LOT
|
|
30
36
|
const lc = '[dev-tools]';
|
|
@@ -35,6 +41,8 @@ interface DebugState {
|
|
|
35
41
|
targetX?: IbGib_V1;
|
|
36
42
|
sessionS?: KeystoneIbGib_V1;
|
|
37
43
|
sessionSecret?: string;
|
|
44
|
+
senderPeer?: SyncPeerWebSocketSender_V1;
|
|
45
|
+
sagaId?: string;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export const debugState: DebugState = {};
|
|
@@ -114,7 +122,7 @@ function initWsTestButton(): void {
|
|
|
114
122
|
|
|
115
123
|
function initPrintStateButton(): void {
|
|
116
124
|
const btn = document.getElementById('btn-print-dev-state') as HTMLButtonElement | null;
|
|
117
|
-
if (!btn) return;
|
|
125
|
+
if (!btn) { return; }
|
|
118
126
|
btn.addEventListener('click', () => {
|
|
119
127
|
devLog(`Printing debug state to console...`);
|
|
120
128
|
console.dir(debugState);
|
|
@@ -128,7 +136,7 @@ function initPrintStateButton(): void {
|
|
|
128
136
|
function initCreateDomainKeystoneButton(): void {
|
|
129
137
|
const lc_fn = `${lc}[initCreateDomainKeystoneButton]`;
|
|
130
138
|
const btn = document.getElementById('btn-create-domain-keystone') as HTMLButtonElement | null;
|
|
131
|
-
if (!btn) return;
|
|
139
|
+
if (!btn) { return; }
|
|
132
140
|
|
|
133
141
|
btn.addEventListener('click', async () => {
|
|
134
142
|
try {
|
|
@@ -177,7 +185,7 @@ function initCreateDomainKeystoneButton(): void {
|
|
|
177
185
|
|
|
178
186
|
// Enable next step
|
|
179
187
|
const nextBtn = document.getElementById('btn-create-test-ibgib') as HTMLButtonElement;
|
|
180
|
-
if (nextBtn) nextBtn.disabled = false;
|
|
188
|
+
if (nextBtn) { nextBtn.disabled = false; }
|
|
181
189
|
|
|
182
190
|
} catch (error) {
|
|
183
191
|
devLog(`✗ ${extractErrorMsg(error)}`);
|
|
@@ -219,7 +227,7 @@ function initCreateTestIbGibButton(): void {
|
|
|
219
227
|
|
|
220
228
|
// Enable next step
|
|
221
229
|
const nextBtn = document.getElementById('btn-create-session-keystone') as HTMLButtonElement;
|
|
222
|
-
if (nextBtn) nextBtn.disabled = false;
|
|
230
|
+
if (nextBtn) { nextBtn.disabled = false; }
|
|
223
231
|
} catch (error) {
|
|
224
232
|
devLog(`✗ ${extractErrorMsg(error)}`);
|
|
225
233
|
console.error(`${lc_fn} ${extractErrorMsg(error)}`);
|
|
@@ -276,19 +284,19 @@ function initCreateSessionKeystoneButton(): void {
|
|
|
276
284
|
allowedVerbs: [],
|
|
277
285
|
},
|
|
278
286
|
{
|
|
279
|
-
id: SESSION_KEYSTONE_POLICY.
|
|
287
|
+
id: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
280
288
|
salt: await getUUID(),
|
|
281
289
|
type: SESSION_KEYSTONE_POLICY.COMMON.TYPE,
|
|
282
290
|
algo: SESSION_KEYSTONE_POLICY.COMMON.ALGO,
|
|
283
291
|
rounds: SESSION_KEYSTONE_POLICY.COMMON.ROUNDS,
|
|
284
292
|
behavior: {
|
|
285
|
-
size: SESSION_KEYSTONE_POLICY.
|
|
293
|
+
size: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SIZE,
|
|
286
294
|
replenish: SESSION_KEYSTONE_POLICY.COMMON.REPLENISH,
|
|
287
|
-
selectSequentially: SESSION_KEYSTONE_POLICY.
|
|
288
|
-
selectRandomly: SESSION_KEYSTONE_POLICY.
|
|
289
|
-
targetBindingChars: SESSION_KEYSTONE_POLICY.
|
|
295
|
+
selectSequentially: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SELECT_SEQUENTIALLY,
|
|
296
|
+
selectRandomly: SESSION_KEYSTONE_POLICY.CONNECT_POOL.SELECT_RANDOMLY,
|
|
297
|
+
targetBindingChars: SESSION_KEYSTONE_POLICY.CONNECT_POOL.TARGET_BINDING_CHARS,
|
|
290
298
|
},
|
|
291
|
-
allowedVerbs: [SESSION_KEYSTONE_POLICY.
|
|
299
|
+
allowedVerbs: [SESSION_KEYSTONE_POLICY.CONNECT_POOL.VERB],
|
|
292
300
|
}
|
|
293
301
|
],
|
|
294
302
|
metaspace,
|
|
@@ -309,7 +317,7 @@ function initCreateSessionKeystoneButton(): void {
|
|
|
309
317
|
|
|
310
318
|
// Enable next step
|
|
311
319
|
const nextBtn = document.getElementById('btn-evolve-domain-keystone') as HTMLButtonElement;
|
|
312
|
-
if (nextBtn) nextBtn.disabled = false;
|
|
320
|
+
if (nextBtn) { nextBtn.disabled = false; }
|
|
313
321
|
} catch (error) {
|
|
314
322
|
devLog(`✗ ${extractErrorMsg(error)}`);
|
|
315
323
|
console.error(`${lc_fn} ${extractErrorMsg(error)}`);
|
|
@@ -383,7 +391,7 @@ function initEvolveDomainKeystoneButton(): void {
|
|
|
383
391
|
|
|
384
392
|
// Enable next step
|
|
385
393
|
const nextBtn = document.getElementById('btn-perform-sync') as HTMLButtonElement;
|
|
386
|
-
if (nextBtn) nextBtn.disabled = false;
|
|
394
|
+
if (nextBtn) { nextBtn.disabled = false; }
|
|
387
395
|
|
|
388
396
|
} catch (error) {
|
|
389
397
|
devLog(`✗ ${extractErrorMsg(error)}`);
|
|
@@ -394,19 +402,19 @@ function initEvolveDomainKeystoneButton(): void {
|
|
|
394
402
|
}
|
|
395
403
|
|
|
396
404
|
// ---------------------------------------------------------------------------
|
|
397
|
-
// Perform Sync
|
|
405
|
+
// Perform Sync Connect (S) button
|
|
398
406
|
// ---------------------------------------------------------------------------
|
|
399
407
|
|
|
400
408
|
function initPerformSyncButton(): void {
|
|
401
409
|
const lc_fn = `${lc}[initPerformSyncButton]`;
|
|
402
410
|
const btn = document.getElementById('btn-perform-sync') as HTMLButtonElement | null;
|
|
403
|
-
if (!btn) return;
|
|
411
|
+
if (!btn) { return; }
|
|
404
412
|
|
|
405
413
|
btn.addEventListener('click', async () => {
|
|
406
414
|
try {
|
|
407
415
|
debugger; // walk through sync
|
|
408
416
|
btn.disabled = true;
|
|
409
|
-
devLog('Initiating Sync
|
|
417
|
+
devLog('Initiating Sync Connect via WebSocket...');
|
|
410
418
|
|
|
411
419
|
const sessionS = debugState.sessionS;
|
|
412
420
|
const sessionSecret = debugState.sessionSecret;
|
|
@@ -423,17 +431,17 @@ function initPerformSyncButton(): void {
|
|
|
423
431
|
if (!space) { throw new Error("No space."); }
|
|
424
432
|
|
|
425
433
|
devLog('Pre-solving upfront picket-fence challenge...');
|
|
426
|
-
const
|
|
427
|
-
if (!
|
|
428
|
-
throw new Error(`Session keystone missing "${SESSION_KEYSTONE_POLICY.
|
|
434
|
+
const connectPool = (sessionS.data?.challengePools ?? []).find(p => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
|
|
435
|
+
if (!connectPool) {
|
|
436
|
+
throw new Error(`Session keystone missing "${SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID}" pool`);
|
|
429
437
|
}
|
|
430
438
|
|
|
431
|
-
const { challengeId } =
|
|
432
|
-
const strategy = KeystoneStrategyFactory.create({ config:
|
|
439
|
+
const { challengeId } = getConnectChallenge(sessionS);
|
|
440
|
+
const strategy = KeystoneStrategyFactory.create({ config: connectPool.config });
|
|
433
441
|
const poolSecret = await strategy.derivePoolSecret({ masterSecret: sessionSecret });
|
|
434
442
|
const solution = await strategy.generateSolution({
|
|
435
443
|
poolSecret,
|
|
436
|
-
poolId: SESSION_KEYSTONE_POLICY.
|
|
444
|
+
poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
437
445
|
challengeId
|
|
438
446
|
});
|
|
439
447
|
|
|
@@ -464,16 +472,16 @@ function initPerformSyncButton(): void {
|
|
|
464
472
|
}));
|
|
465
473
|
} else if (msg.type === 'auth-challenge') {
|
|
466
474
|
const { challengeUuid, demandedIds } = msg;
|
|
467
|
-
devLog(`→ server demanded ${demandedIds.length} ids. Signing
|
|
475
|
+
devLog(`→ server demanded ${demandedIds.length} ids. Signing connect proof...`);
|
|
468
476
|
|
|
469
477
|
const keystoneService = new KeystoneService_V1();
|
|
470
478
|
const proofFrame = await keystoneService.sign({
|
|
471
479
|
latestKeystone: sessionS,
|
|
472
480
|
masterSecret: sessionSecret,
|
|
473
|
-
poolId:
|
|
481
|
+
poolId: POOL_ID_CONNECT,
|
|
474
482
|
requiredChallengeIds: demandedIds,
|
|
475
483
|
claim: {
|
|
476
|
-
verb:
|
|
484
|
+
verb: KEYSTONE_VERB_CONNECT,
|
|
477
485
|
target: challengeUuid
|
|
478
486
|
},
|
|
479
487
|
metaspace,
|
|
@@ -486,10 +494,10 @@ function initPerformSyncButton(): void {
|
|
|
486
494
|
proofFrame
|
|
487
495
|
}));
|
|
488
496
|
} else if (msg.type === 'auth-ok') {
|
|
489
|
-
devLog('✓
|
|
497
|
+
devLog('✓ Connect SUCCESS! Sync session authorized.');
|
|
490
498
|
btn.textContent = '✓ Sync Authorized';
|
|
491
499
|
} else if (msg.type === 'auth-fail') {
|
|
492
|
-
devLog(`✗
|
|
500
|
+
devLog(`✗ Connect FAILED: ${msg.message}`);
|
|
493
501
|
btn.disabled = false;
|
|
494
502
|
}
|
|
495
503
|
} catch (error) {
|
|
@@ -517,6 +525,675 @@ function initPerformSyncButton(): void {
|
|
|
517
525
|
});
|
|
518
526
|
}
|
|
519
527
|
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Phase 1B: Standardized Setup / Sync / Check testing loop
|
|
530
|
+
// ---------------------------------------------------------------------------
|
|
531
|
+
|
|
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
|
+
}
|
|
542
|
+
|
|
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; }
|
|
547
|
+
|
|
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
|
+
}
|
|
585
|
+
|
|
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;
|
|
596
|
+
|
|
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 });
|
|
600
|
+
|
|
601
|
+
debugState.targetX = xIbGib;
|
|
602
|
+
devLog(`${opts.phaseText} Setup: ✓ Target (X) created and persisted locally: ${getIbGibAddr({ ibGib: xIbGib })}`);
|
|
603
|
+
|
|
604
|
+
devLog(`✓ ${opts.phaseText} Setup Complete! Ready for ${opts.phaseText} Sync.`);
|
|
605
|
+
btn.textContent = `✓ ${opts.phaseText} Setup Complete`;
|
|
606
|
+
|
|
607
|
+
// Enable next button in the phase flow
|
|
608
|
+
const syncBtn = document.getElementById(opts.nextBtnId) as HTMLButtonElement | null;
|
|
609
|
+
if (syncBtn) { syncBtn.disabled = false; }
|
|
610
|
+
|
|
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
|
+
}
|
|
617
|
+
|
|
618
|
+
interface PhaseSyncOptions {
|
|
619
|
+
phaseText: '1B' | '2B' | '3B';
|
|
620
|
+
btnId: string;
|
|
621
|
+
nextBtnId: string;
|
|
622
|
+
expectSyncFailure?: boolean;
|
|
623
|
+
failureMessageSuffix?: string;
|
|
624
|
+
}
|
|
625
|
+
|
|
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; }
|
|
630
|
+
|
|
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.`);
|
|
639
|
+
btn.disabled = false;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
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
|
+
});
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
const syncSaga = await coordinator.sync({
|
|
673
|
+
domainIbGibs: [targetX],
|
|
674
|
+
senderIdentity: domainI,
|
|
675
|
+
fnSenderSecret: async () => debugState.domainIMasterSecret!,
|
|
676
|
+
peer: senderPeer,
|
|
677
|
+
localSpace: space,
|
|
678
|
+
metaspace,
|
|
679
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
680
|
+
});
|
|
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;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
devLog(`✓ ${opts.phaseText} Sync Execution Complete! Ready for State Checks.`);
|
|
691
|
+
btn.textContent = `✓ ${opts.phaseText} Sync Run`;
|
|
692
|
+
|
|
693
|
+
const checkBtn = document.getElementById(opts.nextBtnId) as HTMLButtonElement | null;
|
|
694
|
+
if (checkBtn) { checkBtn.disabled = false; }
|
|
695
|
+
|
|
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
|
+
});
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function init1bCheckButton(): void {
|
|
735
|
+
const lc_fn = `${lc}[init1bCheckButton]`;
|
|
736
|
+
const btn = document.getElementById('btn-1b-check') as HTMLButtonElement | null;
|
|
737
|
+
if (!btn) { return; }
|
|
738
|
+
|
|
739
|
+
btn.addEventListener('click', async () => {
|
|
740
|
+
try {
|
|
741
|
+
devLog('1B Check: Asserting cryptographic and durable state expectations from respec...');
|
|
742
|
+
|
|
743
|
+
debugger; // allow for walkthrough 1B check
|
|
744
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
745
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
746
|
+
if (!space) { throw new Error("No default space."); }
|
|
747
|
+
|
|
748
|
+
const domainI = debugState.domainI!;
|
|
749
|
+
const targetX = debugState.targetX!;
|
|
750
|
+
const xAddr = getIbGibAddr({ ibGib: targetX });
|
|
751
|
+
|
|
752
|
+
// 1. Resolve evolved sender identity I1 locally
|
|
753
|
+
const newSenderIdentityAddr = await metaspace.getLatestAddr({
|
|
754
|
+
addr: getIbGibAddr({ ibGib: domainI }),
|
|
755
|
+
space
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (!newSenderIdentityAddr) {
|
|
759
|
+
devLog('✗ Check: Evolved domain keystone tip I1 NOT found in local space!');
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
devLog(`✓ Check: Evolved Domain Keystone (I1) created and stored locally.`);
|
|
763
|
+
|
|
764
|
+
const getRes = await metaspace.get({ addrs: [newSenderIdentityAddr], space });
|
|
765
|
+
const newSenderIdentity = getRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
|
|
766
|
+
|
|
767
|
+
if (!newSenderIdentity) {
|
|
768
|
+
devLog('✗ Check: Failed to load I1 from local space.');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const syncProof = newSenderIdentity.data?.proofs?.find(p => p.claim?.verb === 'sync');
|
|
773
|
+
if (!syncProof) {
|
|
774
|
+
devLog('✗ Check: I1 is missing sync-verb claim proof!');
|
|
775
|
+
} else {
|
|
776
|
+
devLog('✓ Check: I1 contains valid sync-verb claim proof.');
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const sessionIdentityTjpAddr = syncProof?.claim?.target;
|
|
780
|
+
if (!sessionIdentityTjpAddr) {
|
|
781
|
+
devLog('✗ Check: I1 sync claim does not target a session keystone.');
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
devLog(`✓ Check: Sync claim targets session keystone: ${sessionIdentityTjpAddr}`);
|
|
785
|
+
|
|
786
|
+
// 2. Load Session Keystone (S) locally
|
|
787
|
+
const sRes = await metaspace.get({ addrs: [sessionIdentityTjpAddr], space });
|
|
788
|
+
const sessionIdentity = sRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
|
|
789
|
+
|
|
790
|
+
if (!sessionIdentity) {
|
|
791
|
+
devLog('✗ Check: Session Keystone (S) NOT found in local space.');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
devLog('✓ Check: Session Keystone (S) successfully created locally.');
|
|
795
|
+
|
|
796
|
+
const sPools = sessionIdentity.data?.challengePools ?? [];
|
|
797
|
+
const hasConnectPool = sPools.some(p => p.id === POOL_ID_CONNECT);
|
|
798
|
+
const hasSyncPool = sPools.some(p => p.id === POOL_ID_SYNC);
|
|
799
|
+
|
|
800
|
+
if (hasConnectPool && hasSyncPool) {
|
|
801
|
+
devLog('✓ Check: S contains both connect and sync challenge pools.');
|
|
802
|
+
} else {
|
|
803
|
+
devLog(`✗ Check: S is missing challenge pools! (connect: ${hasConnectPool}, sync: ${hasSyncPool})`);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// we don't check this yet until sync ping pong
|
|
807
|
+
// const targetAddrs = sessionIdentity.data?.frameDetails?.targetAddrs ?? [];
|
|
808
|
+
// if (targetAddrs.includes(xAddr)) {
|
|
809
|
+
// devLog('✓ Check: S is securely bound to target domain X in frameDetails.');
|
|
810
|
+
// } else {
|
|
811
|
+
// devLog('✗ Check: S is not bound to target domain X!');
|
|
812
|
+
// }
|
|
813
|
+
|
|
814
|
+
// #region 3. Verify Server-Side (receiver) registrations via GET REST API
|
|
815
|
+
devLog('Check: Verifying registrations on server (receiver metaspace)...');
|
|
816
|
+
const apiBridge = new SpaceGibApiBridge();
|
|
817
|
+
|
|
818
|
+
// Check if I, evolved I1 and session S exist on server
|
|
819
|
+
let hasI = false;
|
|
820
|
+
let hasI1 = false;
|
|
821
|
+
let hasS = false;
|
|
822
|
+
|
|
823
|
+
const domainAddr = getIbGibAddr({ ibGib: domainI });
|
|
824
|
+
const resGetDomainIGraphFromServer = await apiBridge.getKeystoneGraph(domainAddr);
|
|
825
|
+
if (resGetDomainIGraphFromServer.success && resGetDomainIGraphFromServer.graph) {
|
|
826
|
+
const domainGraph = resGetDomainIGraphFromServer.graph;
|
|
827
|
+
hasI = domainGraph[domainAddr] !== undefined;
|
|
828
|
+
hasI1 = domainGraph[newSenderIdentityAddr] !== undefined;
|
|
829
|
+
} else {
|
|
830
|
+
devLog(`✗ Check Server: Failed to fetch keystone graph from server: ${resGetDomainIGraphFromServer.message}`);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const resGetSessionSFromServer = await apiBridge.getIbGib(domainAddr, sessionIdentityTjpAddr);
|
|
834
|
+
if (resGetSessionSFromServer.success && resGetSessionSFromServer.ibGib) {
|
|
835
|
+
hasS = true;
|
|
836
|
+
} else {
|
|
837
|
+
devLog(`✗ Check Server: Failed to fetch session keystone from server: ${resGetSessionSFromServer.message}`);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (hasI) { devLog('✓ Check Server: Genesis Domain Keystone (I) registered on server.'); }
|
|
841
|
+
else devLog('✗ Check Server: Genesis Domain Keystone (I) missing on server!');
|
|
842
|
+
if (hasI1) { devLog('✓ Check Server: Evolved Domain Keystone (I1) accepted and stored on server.'); }
|
|
843
|
+
else devLog('✗ Check Server: Evolved Domain Keystone (I1) missing on server!');
|
|
844
|
+
if (hasS) { devLog('✓ Check Server: Session Keystone (S) accepted and stored on server.'); }
|
|
845
|
+
else devLog('✗ Check Server: Session Keystone (S) missing on server!');
|
|
846
|
+
if (hasI && hasI1 && hasS) {
|
|
847
|
+
devLog('🎉 ALL PHASE 1B ESTABLISH CHECKS PASSED FLAWLESSLY! ✓');
|
|
848
|
+
btn.textContent = '✓ 1B All Passed';
|
|
849
|
+
|
|
850
|
+
const btn2bSetup = document.getElementById('btn-2b-setup') as HTMLButtonElement | null;
|
|
851
|
+
if (btn2bSetup) { btn2bSetup.disabled = false; }
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// #endregion 3. Verify Server-Side (receiver) registrations via GET REST API
|
|
855
|
+
|
|
856
|
+
} catch (error) {
|
|
857
|
+
devLog(`✗ 1B Check FAILED: ${extractErrorMsg(error)}`);
|
|
858
|
+
console.error(`${lc_fn} 1B Check error:`, error);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// ---------------------------------------------------------------------------
|
|
864
|
+
// Phase 2B Implementation
|
|
865
|
+
// ---------------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
function init2bSetupButton(): void {
|
|
868
|
+
const btn = document.getElementById('btn-2b-setup') as HTMLButtonElement | null;
|
|
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
|
+
});
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function init2bSyncButton(): void {
|
|
885
|
+
const btn = document.getElementById('btn-2b-sync') as HTMLButtonElement | null;
|
|
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
|
+
});
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function init2bCheckButton(): void {
|
|
899
|
+
const lc_fn = `${lc}[init2bCheckButton]`;
|
|
900
|
+
const btn = document.getElementById('btn-2b-check') as HTMLButtonElement | null;
|
|
901
|
+
if (!btn) { return; }
|
|
902
|
+
|
|
903
|
+
btn.addEventListener('click', async () => {
|
|
904
|
+
try {
|
|
905
|
+
debugger; // allow for walkthrough 2B check
|
|
906
|
+
devLog('2B Check: Asserting cryptographic and WebSocket state expectations...');
|
|
907
|
+
|
|
908
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
909
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
910
|
+
if (!space) { throw new Error("No default space."); }
|
|
911
|
+
|
|
912
|
+
const domainI = debugState.domainI!;
|
|
913
|
+
const targetX = debugState.targetX!;
|
|
914
|
+
const xAddr = getIbGibAddr({ ibGib: targetX });
|
|
915
|
+
|
|
916
|
+
// 1. Resolve evolved sender identity I1 locally
|
|
917
|
+
const domainAddr = getIbGibAddr({ ibGib: domainI });
|
|
918
|
+
const newSenderIdentityAddr = await metaspace.getLatestAddr({
|
|
919
|
+
addr: domainAddr,
|
|
920
|
+
space
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
if (!newSenderIdentityAddr) {
|
|
924
|
+
devLog('✗ Check: Evolved domain keystone tip I1 NOT found in local space!');
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
devLog(`✓ Check: Evolved Domain Keystone (I1) created and stored locally.`);
|
|
928
|
+
|
|
929
|
+
const getRes = await metaspace.get({ addrs: [newSenderIdentityAddr], space });
|
|
930
|
+
const newSenderIdentity = getRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
|
|
931
|
+
|
|
932
|
+
if (!newSenderIdentity) {
|
|
933
|
+
devLog('✗ Check: Failed to load I1 from local space.');
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const syncProof = newSenderIdentity.data?.proofs?.find(p => p.claim?.verb === 'sync');
|
|
938
|
+
const sessionIdentityTjpAddr = syncProof?.claim?.target;
|
|
939
|
+
if (!sessionIdentityTjpAddr) {
|
|
940
|
+
devLog('✗ Check: I1 sync claim does not target a session keystone.');
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
devLog(`✓ Check: Sync claim targets session keystone: ${sessionIdentityTjpAddr}`);
|
|
944
|
+
|
|
945
|
+
// 2. Load Session Keystone (S) locally and get latest tip
|
|
946
|
+
const latestSessionSAddr = await metaspace.getLatestAddr({
|
|
947
|
+
addr: sessionIdentityTjpAddr,
|
|
948
|
+
space
|
|
949
|
+
});
|
|
950
|
+
if (!latestSessionSAddr) {
|
|
951
|
+
devLog('✗ Check: Latest Session Keystone (S) NOT found in local space.');
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
devLog(`✓ Check: Found latest session keystone (S) locally.`);
|
|
955
|
+
|
|
956
|
+
const sRes = await metaspace.get({ addrs: [latestSessionSAddr], space });
|
|
957
|
+
const sessionIdentity = sRes.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
|
|
958
|
+
|
|
959
|
+
if (!sessionIdentity) {
|
|
960
|
+
devLog('✗ Check: Failed to load latest Session Keystone (S).');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Verify S contains connect and sync pools
|
|
965
|
+
const sPools = sessionIdentity.data?.challengePools ?? [];
|
|
966
|
+
const connectPool = sPools.find(p => p.id === POOL_ID_CONNECT);
|
|
967
|
+
const syncPool = sPools.find(p => p.id === POOL_ID_SYNC);
|
|
968
|
+
|
|
969
|
+
if (connectPool && syncPool) {
|
|
970
|
+
devLog('✓ Check: S contains both connect and sync challenge pools.');
|
|
971
|
+
} else {
|
|
972
|
+
devLog(`✗ Check: S is missing challenge pools! (connect: ${!!connectPool}, sync: ${!!syncPool})`);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Verify S was successfully evolved (depleted connect pool)
|
|
977
|
+
const remainingChallenges = Object.keys(connectPool.challenges).length;
|
|
978
|
+
if (remainingChallenges === 0) {
|
|
979
|
+
devLog('✓ Check: S connect pool successfully depleted (0 challenges remaining).');
|
|
980
|
+
} else {
|
|
981
|
+
devLog(`✗ Check: S connect pool not depleted! (${remainingChallenges} challenges remaining)`);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// we don't check this yet until sync ping pong
|
|
986
|
+
// const targetAddrs = sessionIdentity.data?.frameDetails?.targetAddrs ?? [];
|
|
987
|
+
// if (targetAddrs.includes(xAddr)) {
|
|
988
|
+
// devLog('✓ Check: S is securely bound to target domain X in frameDetails.');
|
|
989
|
+
// } else {
|
|
990
|
+
// devLog('✗ Check: S is not bound to target domain X!');
|
|
991
|
+
// }
|
|
992
|
+
|
|
993
|
+
// 3. Verify WebSocket Connection is open and active
|
|
994
|
+
if (debugState.senderPeer && debugState.senderPeer.isSocketOpen) {
|
|
995
|
+
devLog('✓ Check: WebSocket connection is active and OPEN.');
|
|
996
|
+
} else {
|
|
997
|
+
devLog('✗ Check: WebSocket connection is NOT open or peer is missing!');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
devLog('🎉 ALL PHASE 2B CONNECT CHECKS PASSED FLAWLESSLY! ✓');
|
|
1002
|
+
btn.textContent = '✓ 2B All Passed';
|
|
1003
|
+
|
|
1004
|
+
// Enable Phase 3B setup
|
|
1005
|
+
const p3SetupBtn = document.getElementById('btn-3b-setup') as HTMLButtonElement | null;
|
|
1006
|
+
if (p3SetupBtn) { p3SetupBtn.disabled = false; }
|
|
1007
|
+
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
devLog(`✗ 2B Check FAILED: ${extractErrorMsg(error)}`);
|
|
1010
|
+
console.error(`${lc_fn} 2B Check error:`, error);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
|
|
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
|
+
}
|
|
1031
|
+
|
|
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
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
|
|
520
1197
|
// ---------------------------------------------------------------------------
|
|
521
1198
|
// Dev Tools UI Init
|
|
522
1199
|
// ---------------------------------------------------------------------------
|
|
@@ -534,6 +1211,15 @@ export function initDevTools(): void {
|
|
|
534
1211
|
initCreateSessionKeystoneButton();
|
|
535
1212
|
initEvolveDomainKeystoneButton();
|
|
536
1213
|
initPerformSyncButton();
|
|
1214
|
+
init1bSetupButton();
|
|
1215
|
+
init1bSyncButton();
|
|
1216
|
+
init1bCheckButton();
|
|
1217
|
+
init2bSetupButton();
|
|
1218
|
+
init2bSyncButton();
|
|
1219
|
+
init2bCheckButton();
|
|
1220
|
+
init3bSetupButton();
|
|
1221
|
+
init3bSyncButton();
|
|
1222
|
+
init3bCheckButton();
|
|
537
1223
|
} catch (error) {
|
|
538
1224
|
console.error(`${lc_fn} ${extractErrorMsg(error)}`);
|
|
539
1225
|
}
|