@ibgib/web-gib 0.0.57 → 0.0.59

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 (46) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/api/ibgib-api-bridge.d.mts +63 -1
  4. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  5. package/dist/api/ibgib-api-bridge.mjs +135 -0
  6. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  7. package/dist/identity/custodian-delegate-helper.d.mts +22 -0
  8. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
  9. package/dist/identity/custodian-delegate-helper.mjs +50 -0
  10. package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
  11. package/dist/identity/sso-popup-helper.d.mts +33 -0
  12. package/dist/identity/sso-popup-helper.d.mts.map +1 -0
  13. package/dist/identity/sso-popup-helper.mjs +124 -0
  14. package/dist/identity/sso-popup-helper.mjs.map +1 -0
  15. package/dist/index.d.mts +2 -0
  16. package/dist/index.d.mts.map +1 -1
  17. package/dist/index.mjs +3 -0
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  20. package/dist/ui/component/identity/add-device/add-device.mjs +5 -1
  21. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  22. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
  23. package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
  24. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
  25. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  26. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
  27. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  28. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +445 -73
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  31. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  32. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
  33. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  34. package/package.json +4 -4
  35. package/src/AUTO-GENERATED-version.mts +1 -1
  36. package/src/api/ibgib-api-bridge.mts +195 -1
  37. package/src/identity/custodian-delegate-helper.mts +61 -0
  38. package/src/identity/sso-popup-helper.mts +155 -0
  39. package/src/index.mts +3 -0
  40. package/src/ui/component/identity/add-device/add-device.mts +5 -1
  41. package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
  42. package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  43. package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  44. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +507 -77
  45. package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
  46. package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +3 -0
@@ -11,6 +11,7 @@ import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-servi
11
11
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
12
12
  import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
13
13
  import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
+ import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
14
15
  import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
15
16
  import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
16
17
  import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
@@ -23,8 +24,10 @@ import {
23
24
  } from "../../component-types.mjs";
24
25
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
25
26
  import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
27
+ import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
28
+ import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
26
29
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
27
- import { shadowRoot_getElementById } from "../../../../helpers.web.mjs";
30
+ import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
28
31
 
29
32
  export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
30
33
 
@@ -164,6 +167,59 @@ export class KeystoneCreatorComponentInstance
164
167
  }
165
168
 
166
169
  private initHandlers() {
170
+ // Mode Tab Switching (Quick vs Password-based)
171
+ const tabQuick = this.shadowRoot?.getElementById('tab-quick') as HTMLButtonElement | null;
172
+ const tabPassword = this.shadowRoot?.getElementById('tab-password') as HTMLButtonElement | null;
173
+ const panelQuick = this.shadowRoot?.getElementById('panel-quick') as HTMLDivElement | null;
174
+ const panelPassword = this.shadowRoot?.getElementById('panel-password') as HTMLDivElement | null;
175
+ const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick') as HTMLParagraphElement | null;
176
+ const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password') as HTMLParagraphElement | null;
177
+
178
+ tabQuick?.addEventListener('click', () => {
179
+ tabQuick.classList.add('active');
180
+ tabPassword?.classList.remove('active');
181
+ panelQuick?.classList.remove('hidden');
182
+ panelPassword?.classList.add('hidden');
183
+ subtitleQuick?.classList.remove('hidden');
184
+ subtitlePassword?.classList.add('hidden');
185
+ });
186
+
187
+ tabPassword?.addEventListener('click', () => {
188
+ tabPassword.classList.add('active');
189
+ tabQuick?.classList.remove('active');
190
+ panelPassword?.classList.remove('hidden');
191
+ panelQuick?.classList.add('hidden');
192
+ subtitlePassword?.classList.remove('hidden');
193
+ subtitleQuick?.classList.add('hidden');
194
+ });
195
+
196
+ const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso') as HTMLButtonElement | null;
197
+ btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
198
+
199
+ const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso') as HTMLButtonElement | null;
200
+ btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
201
+
202
+ // Email Sign-In Info Button
203
+ const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info') as HTMLButtonElement | null;
204
+ btnEmailInfo?.addEventListener('click', (e) => {
205
+ e.preventDefault();
206
+ alertUser({
207
+ title: 'Custom Email Sign-In',
208
+ msg: 'When you enter your email address, we dispatch a 6-digit one-time verification code to your inbox.\n\nEntering the code creates your cryptographic identity delegate on your device and signs you in seamlessly without needing a password!'
209
+ });
210
+ });
211
+
212
+ // Quick Email Verification Code Handlers
213
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
214
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
215
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
216
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
217
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
218
+
219
+ btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
220
+
221
+ btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
222
+
167
223
  this.elements!.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
168
224
 
169
225
  this.shadowRoot!.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
@@ -176,16 +232,16 @@ export class KeystoneCreatorComponentInstance
176
232
  });
177
233
 
178
234
  // Clear invalid styling when user inputs text
179
- this.elements!.inputUsername.addEventListener('input', () => {
235
+ this.elements!.inputUsername?.addEventListener('input', () => {
180
236
  this.elements!.inputUsername.classList.remove('invalid');
181
237
  });
182
- this.elements!.inputDescription.addEventListener('input', () => {
238
+ this.elements!.inputDescription?.addEventListener('input', () => {
183
239
  this.elements!.inputDescription.classList.remove('invalid');
184
240
  });
185
- this.elements!.inputEmail.addEventListener('input', () => {
241
+ this.elements!.inputEmail?.addEventListener('input', () => {
186
242
  this.elements!.inputEmail.classList.remove('invalid');
187
243
  });
188
- this.elements!.inputSecret.addEventListener('input', () => {
244
+ this.elements!.inputSecret?.addEventListener('input', () => {
189
245
  this.elements!.inputSecret.classList.remove('invalid');
190
246
  });
191
247
  }
@@ -198,6 +254,292 @@ export class KeystoneCreatorComponentInstance
198
254
  }
199
255
  }
200
256
 
257
+ /**
258
+ * Triggered by Google / GitHub SSO buttons.
259
+ * Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
260
+ * registers delegate on server, and finalizes onboarding.
261
+ */
262
+ protected async handleSsoClick(providerId: 'google' | 'github'): Promise<void> {
263
+ const lc = `${this.lc}[${this.handleSsoClick.name}]`;
264
+ const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
265
+
266
+ this.setStatus(`Opening ${providerName} SSO authorization window...`, 'info');
267
+ this.showBusy({
268
+ isBusy: true,
269
+ title: `Connecting to ${providerName}...`,
270
+ msg: `Opening ${providerName} SSO authorization window...\nPlease complete authorization in the popup window.`,
271
+ animationEmoji: '🌐'
272
+ });
273
+
274
+ try {
275
+ const resPopup = await executeSsoOAuthPopup({ providerId });
276
+ if (!resPopup.success || !resPopup.code) {
277
+ this.setStatus(resPopup.message || `${providerName} sign-in cancelled.`, 'error');
278
+ return;
279
+ }
280
+
281
+ this.setStatus(`Exchanging ${providerName} authorization code...`, 'info');
282
+ this.showBusy({
283
+ isBusy: true,
284
+ title: 'Exchanging SSO Code...',
285
+ msg: `Validating ${providerName} authorization code with custodian server...`,
286
+ animationEmoji: '🔐'
287
+ });
288
+
289
+ const apiBridge = getApiBridge();
290
+ const resLogin = await apiBridge.postSsoLogin({
291
+ code: resPopup.code,
292
+ providerId,
293
+ redirectUri: resPopup.redirectUri!
294
+ });
295
+
296
+ if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
297
+ this.setStatus(resLogin.message || `${providerName} login failed.`, 'error');
298
+ return;
299
+ }
300
+
301
+ const { primaryTjpAddr, custodianReqs } = resLogin;
302
+ this.setStatus('Deriving local device delegate keys...', 'info');
303
+ this.showBusy({
304
+ isBusy: true,
305
+ title: 'Creating Sync Delegate...',
306
+ msg: 'Generating cryptographic sync delegate keys...',
307
+ animationEmoji: '🔑'
308
+ });
309
+
310
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
311
+ const email = resLogin.userInfo?.email || '';
312
+ const username = email ? email.split('@')[0] : 'user';
313
+ const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
314
+ if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
315
+ throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
316
+ }
317
+
318
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim() as any)
319
+ .withUsername(`${username}-sync`)
320
+ .withDescription(`Sync Delegate for ${username}`)
321
+ .withDetails({
322
+ client: 'space-gib-web',
323
+ role: 'sync-delegate',
324
+ primaryTjpAddr,
325
+ custodianReqs,
326
+ });
327
+
328
+ const configs = await delegateBuilder.compileConfigs();
329
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
330
+ const space = await metaspace.getLocalUserSpace({ lock: false });
331
+ if (!space) {
332
+ this.setStatus('Could not access local user space.', 'error');
333
+ return;
334
+ }
335
+
336
+ const keystoneService = new KeystoneService_V1();
337
+ const delegateKeystone = await keystoneService.genesis({
338
+ masterSecret: delegateSecret,
339
+ configs,
340
+ metaspace,
341
+ space,
342
+ frameDetails: delegateBuilder.getFrameDetails(),
343
+ isPrimary: false
344
+ });
345
+
346
+ this.setStatus('Registering device delegate with custodian server...', 'info');
347
+ this.showBusy({
348
+ isBusy: true,
349
+ title: 'Registering Delegate...',
350
+ msg: 'Registering local delegate with custodian server...',
351
+ animationEmoji: '📝'
352
+ });
353
+
354
+ const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
355
+ if (!resRegister.success) {
356
+ this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
357
+ return;
358
+ }
359
+
360
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
361
+ await this.finalizeIdentityOnboarding({
362
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
363
+ primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
364
+ delegateKeystone,
365
+ primaryTjpAddr,
366
+ delegateSecret,
367
+ makeActive: true
368
+ });
369
+
370
+ } catch (error) {
371
+ const emsg = extractErrorMsg(error);
372
+ console.error(`${lc} ${emsg}`, error);
373
+ this.setStatus(`SSO sign-in error: ${emsg}`, 'error');
374
+ } finally {
375
+ this.showBusy({ isBusy: false });
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Triggered by the "Send 6-Digit Verification Code" button.
381
+ * Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
382
+ */
383
+ protected async handleQuickSendCode(): Promise<void> {
384
+ const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
385
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
386
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
387
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
388
+
389
+ const email = (inputQuickEmail?.value || '').trim();
390
+ if (!email || !validateEmail(email)) {
391
+ this.setStatus("Please enter a valid email address.", "error");
392
+ return;
393
+ }
394
+
395
+ if (btnQuickSendCode) {
396
+ btnQuickSendCode.disabled = true;
397
+ btnQuickSendCode.textContent = "Sending Verification Code...";
398
+ }
399
+ this.setStatus("Requesting verification code...", "info");
400
+
401
+ try {
402
+ const resCode = await getApiBridge().postVerificationCode({ email });
403
+ if (resCode.success) {
404
+ secQuickOtp?.classList.remove('hidden');
405
+ this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
406
+ } else {
407
+ this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
408
+ }
409
+ } catch (error) {
410
+ const emsg = extractErrorMsg(error);
411
+ console.error(`${lc} ${emsg}`);
412
+ this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
413
+ } finally {
414
+ if (btnQuickSendCode) {
415
+ btnQuickSendCode.disabled = false;
416
+ btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
417
+ }
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Triggered by the "Verify Code" button.
423
+ * Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
424
+ * registers delegate on server, and finalizes onboarding.
425
+ */
426
+ protected async handleQuickVerifyCode(): Promise<void> {
427
+ const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
428
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
429
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
430
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
431
+
432
+ const email = (inputQuickEmail?.value || '').trim();
433
+ const code = (inputQuickOtp?.value || '').trim();
434
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
435
+ this.setStatus("Please enter a valid 6-digit verification code.", "error");
436
+ return;
437
+ }
438
+
439
+ if (btnQuickVerifyCode) {
440
+ btnQuickVerifyCode.disabled = true;
441
+ btnQuickVerifyCode.textContent = "Verifying...";
442
+ }
443
+ this.setStatus("Verifying code & completing onboarding...", "info");
444
+ this.showBusy({
445
+ isBusy: true,
446
+ title: 'Verifying Code...',
447
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
448
+ animationEmoji: '🔐'
449
+ });
450
+
451
+ try {
452
+ const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
453
+ if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
454
+ this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
455
+ return;
456
+ }
457
+
458
+ const primaryTjpAddr = resGenesis.primaryTjpAddr;
459
+ const custodianReqs = resGenesis.custodianReqs;
460
+
461
+ // Construct local delegate keystone
462
+ this.setStatus("Constructing local sync delegate...", "info");
463
+ this.showBusy({
464
+ isBusy: true,
465
+ title: 'Creating Sync Delegate...',
466
+ msg: 'Generating cryptographic sync delegate keys...',
467
+ animationEmoji: '🔑'
468
+ });
469
+
470
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
471
+ const username = email ? email.split('@')[0] : 'user';
472
+ const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
473
+ if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
474
+ throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
475
+ }
476
+
477
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim() as any)
478
+ .withUsername(`${username}-sync`)
479
+ .withDescription(`Sync Delegate for ${username}`)
480
+ .withDetails({
481
+ client: 'space-gib-web',
482
+ role: 'sync-delegate',
483
+ primaryTjpAddr,
484
+ custodianReqs,
485
+ });
486
+
487
+ const configs = await delegateBuilder.compileConfigs();
488
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
489
+ const space = await metaspace.getLocalUserSpace({ lock: false });
490
+ if (!space) {
491
+ this.setStatus("Failed to access local user space.", "error");
492
+ return;
493
+ }
494
+
495
+ const keystoneService = new KeystoneService_V1();
496
+ const delegateKeystone = await keystoneService.genesis({
497
+ masterSecret: delegateSecret,
498
+ configs,
499
+ metaspace,
500
+ space,
501
+ frameDetails: delegateBuilder.getFrameDetails(),
502
+ isPrimary: false
503
+ });
504
+
505
+ // Register delegate with server
506
+ this.setStatus("Registering device delegate with custodian server...", "info");
507
+ this.showBusy({
508
+ isBusy: true,
509
+ title: 'Registering Delegate...',
510
+ msg: 'Registering local delegate with custodian server...',
511
+ animationEmoji: '📝'
512
+ });
513
+
514
+ const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
515
+ if (!resRegister.success) {
516
+ this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
517
+ return;
518
+ }
519
+
520
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
521
+ await this.finalizeIdentityOnboarding({
522
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
523
+ primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
524
+ delegateKeystone,
525
+ primaryTjpAddr,
526
+ delegateSecret,
527
+ makeActive: true
528
+ });
529
+
530
+ } catch (error) {
531
+ const emsg = extractErrorMsg(error);
532
+ console.error(`${lc} ${emsg}`, error);
533
+ this.setStatus(`Error: ${emsg}`, "error");
534
+ } finally {
535
+ this.showBusy({ isBusy: false });
536
+ if (btnQuickVerifyCode) {
537
+ btnQuickVerifyCode.disabled = false;
538
+ btnQuickVerifyCode.textContent = "Verify Code";
539
+ }
540
+ }
541
+ }
542
+
201
543
  /**
202
544
  * Triggered by the "Generate" button.
203
545
  * Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
@@ -282,13 +624,14 @@ export class KeystoneCreatorComponentInstance
282
624
  animationEmoji: '⚙️'
283
625
  });
284
626
 
285
- // 1. Prepare Primary/Domain Keystone Configs
286
- const isProd = process.env.NODE_ENV === 'production';
287
- const requestedProfile = (process.env.KEYSTONE_PROFILE || 'domain').toLowerCase();
288
- const profileName = (isProd && requestedProfile === 'test') ? 'domain' : requestedProfile;
289
- console.log(`${lc} Using keystone policy profile '${profileName}' (I: 90123456789abcdef0123456789abcde)`);
627
+ // 1. Prepare Primary Keystone Configs
628
+ const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
629
+ if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
630
+ throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing. Expected valid profile name like "primary-profile.dev" or "primary-profile.prod". (E: 3c4d5e6f7890123456789abcdef3)');
631
+ }
632
+ console.log(`${lc} Using sovereign primary keystone policy profile '${sovereignPrimaryProfileName}' (I: 90123456789abcdef0123456789abcde)`);
290
633
 
291
- const builder = KeystoneProfileBuilder.buildKeystone(profileName as any)
634
+ const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim() as any)
292
635
  .withUsername(username)
293
636
  .withDescription(description);
294
637
  if (email) {
@@ -300,10 +643,13 @@ export class KeystoneCreatorComponentInstance
300
643
  });
301
644
  const configs = await builder.compileConfigs();
302
645
 
303
- // 2. Get local services
646
+ // 2. Prepare Metaspace/Space
304
647
  const metaspace = await getGlobalMetaspace_waitIfNeeded();
305
- const space = await metaspace.getLocalUserSpace({});
306
- if (!space) { throw new Error("No default space found in metaspace."); }
648
+ const space = await metaspace.getLocalUserSpace({ lock: false });
649
+ if (!space) {
650
+ this.setStatus("Failed to access local user space.", "error");
651
+ return;
652
+ }
307
653
 
308
654
  // 3. Perform Genesis (Local)
309
655
  this.showBusy({
@@ -338,7 +684,12 @@ export class KeystoneCreatorComponentInstance
338
684
  });
339
685
 
340
686
  const delegateSecret = await deriveDelegateSecret({ masterSecret });
341
- const delegateBuilder = KeystoneProfileBuilder.buildKeystone(profileName as any)
687
+ const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
688
+ if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
689
+ throw new Error('KEYSTONE_PROFILE_SOVEREIGN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 5e6f7890123456789abcdef5)');
690
+ }
691
+
692
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim() as any)
342
693
  .withUsername(`${username}-sync`)
343
694
  .withDescription(`Sync Delegate for ${username}`)
344
695
  .withDetails({
@@ -385,7 +736,7 @@ export class KeystoneCreatorComponentInstance
385
736
  this.showBusy({
386
737
  isBusy: true,
387
738
  title: 'Syncing to Server...',
388
- msg: 'Quarantining staging genesis keystone on server...',
739
+ msg: 'Staging pending genesis keystone on server...',
389
740
  animationEmoji: '🔄'
390
741
  });
391
742
  const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
@@ -510,69 +861,16 @@ export class KeystoneCreatorComponentInstance
510
861
  }
511
862
  }
512
863
 
513
- // 5. Register with the keystones special index
514
- this.showBusy({
515
- isBusy: true,
516
- title: 'Registering Identity...',
517
- msg: 'Server sync succeeded!\nUpdating local identity indexes and scope settings...',
518
- animationEmoji: '💾'
519
- });
520
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
521
- await updateSpecialIndex({
522
- type: "keystones",
523
- rel8nInfos: [
524
- {
525
- rel8nName: "keystone",
526
- ibGibs: [
527
- toDto({ ibGib: evolvedParentKeystoneIbGib }),
528
- toDto({ ibGib: delegateKeystoneIbGib })
529
- ],
530
- },
531
- ],
532
- metaspace,
533
- space,
864
+ // 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
865
+ await this.finalizeIdentityOnboarding({
866
+ primaryKeystone: evolvedParentKeystoneIbGib,
867
+ delegateKeystone: delegateKeystoneIbGib,
868
+ primaryTjpAddr: evolvedParentAddr,
869
+ masterSecret,
870
+ delegateSecret,
871
+ makeActive: this.elements!.checkboxMakeActive?.checked ?? true
534
872
  });
535
873
 
536
- // Expose for dev tools
537
- (window as any).dev_domainI = evolvedParentKeystoneIbGib;
538
- (window as any).dev_domainIMasterSecret = masterSecret;
539
- (window as any).dev_syncDelegate = delegateKeystoneIbGib;
540
- (window as any).dev_syncDelegateSecret = delegateSecret;
541
-
542
- // 6. Request to make the new identity active if the checkbox is checked
543
- const makeActive = this.elements!.checkboxMakeActive?.checked;
544
- if (makeActive) {
545
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
546
- detail: { activeIdentityAddr: evolvedParentAddr },
547
- bubbles: true,
548
- composed: true
549
- }));
550
- }
551
-
552
- // 7. Success
553
- const successMsg = "Identity successfully created and activated! Just a sec...";
554
- this.showBusy({
555
- isBusy: true,
556
- title: 'Success!',
557
- msg: successMsg,
558
- animationEmoji: '✅'
559
- });
560
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
561
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
562
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
563
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
564
- this.elements!.keystoneAddrEl.textContent = evolvedParentAddr;
565
- this.elements!.statusArea.classList.remove('hidden');
566
- this.setStatus(successMsg, "success");
567
- console.log(`${lc} ✓ ${successMsg} (I: 3396d8261978d7027895b71e27e9ab26)`);
568
-
569
- // Dispatch UI message to close keystone-creator and show identity-manager
570
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
571
- detail: { action: 'show-identity-manager', addr: evolvedParentAddr },
572
- bubbles: true,
573
- composed: true
574
- }));
575
-
576
874
  } catch (error) {
577
875
  const emsg = extractErrorMsg(error);
578
876
  console.error(`${lc} ${emsg}`, error);
@@ -586,6 +884,138 @@ export class KeystoneCreatorComponentInstance
586
884
  }
587
885
  }
588
886
 
887
+ /**
888
+ * DRY helper to register keystones in the local keystones special index,
889
+ * set the active identity, and navigate to the Identity Manager component.
890
+ */
891
+ private async finalizeIdentityOnboarding({
892
+ primaryKeystone,
893
+ primaryGraph,
894
+ delegateKeystone,
895
+ primaryTjpAddr,
896
+ masterSecret,
897
+ delegateSecret,
898
+ makeActive = true,
899
+ }: {
900
+ primaryKeystone?: IbGib_V1;
901
+ primaryGraph?: FlatIbGibGraph;
902
+ delegateKeystone: IbGib_V1;
903
+ primaryTjpAddr: string;
904
+ masterSecret?: string;
905
+ delegateSecret?: string;
906
+ makeActive?: boolean;
907
+ }): Promise<void> {
908
+ const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
909
+ const humanEyeDelay = 500;
910
+
911
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
912
+ if (!metaspace) {
913
+ throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
914
+ }
915
+
916
+ const space = await metaspace.getLocalUserSpace({});
917
+ if (!space) {
918
+ throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
919
+ }
920
+
921
+ // 0. Persist all frames of primary identity graph into local browser space
922
+ if (primaryGraph) {
923
+ const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
924
+ for (const frame of graphIbGibs) {
925
+ await metaspace.put({ ibGibs: [frame], space });
926
+ await metaspace.registerNewIbGib({ ibGib: frame, space });
927
+ }
928
+ } else if (primaryKeystone) {
929
+ await metaspace.put({ ibGibs: [primaryKeystone], space });
930
+ await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
931
+ }
932
+
933
+ // 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
934
+ const keystoneService = new KeystoneService_V1();
935
+ try {
936
+ await keystoneService.getLatestKeystone({
937
+ addr: primaryTjpAddr,
938
+ metaspace,
939
+ space
940
+ });
941
+ } catch (error) {
942
+ const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
943
+ console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
944
+ throw new Error(emsg);
945
+ }
946
+
947
+ // 1. Register with the local keystones special index
948
+ this.showBusy({
949
+ isBusy: true,
950
+ title: 'Registering Identity...',
951
+ msg: 'Updating local identity indexes and scope settings...',
952
+ animationEmoji: '💾'
953
+ });
954
+ const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })];
955
+
956
+ if (!primaryKeystone && !primaryGraph) { throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`); }
957
+ if (!delegateKeystone) { throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`); }
958
+
959
+ if (primaryKeystone) {
960
+ ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
961
+ }
962
+ await updateSpecialIndex({
963
+ type: "keystones",
964
+ rel8nInfos: [
965
+ {
966
+ rel8nName: "keystone",
967
+ ibGibs: ibGibsToRegister,
968
+ },
969
+ ],
970
+ metaspace,
971
+ space,
972
+ });
973
+
974
+ // Expose for dev tools debugging if available
975
+ if (masterSecret) {
976
+ (window as any).dev_domainI = primaryKeystone;
977
+ (window as any).dev_domainIMasterSecret = masterSecret;
978
+ (window as any).dev_syncDelegate = delegateKeystone;
979
+ (window as any).dev_syncDelegateSecret = delegateSecret;
980
+ }
981
+
982
+ // 2. Request to make the new identity active
983
+ if (makeActive) {
984
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
985
+ detail: { activeIdentityAddr: primaryTjpAddr },
986
+ bubbles: true,
987
+ composed: true
988
+ }));
989
+ }
990
+
991
+ // 3. Display success status
992
+ const successMsg = "Identity successfully created and activated!";
993
+ this.showBusy({
994
+ isBusy: true,
995
+ title: 'Success!',
996
+ msg: successMsg,
997
+ animationEmoji: '✅'
998
+ });
999
+ await delay(humanEyeDelay);
1000
+ if (this.elements?.keystoneAddrEl) {
1001
+ this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
1002
+ }
1003
+ if (this.elements?.statusArea) {
1004
+ this.elements.statusArea.classList.remove('hidden');
1005
+ }
1006
+ this.setStatus(successMsg, "success");
1007
+ console.log(`${lc} ✓ ${successMsg}`);
1008
+
1009
+ // 4. Dispatch UI navigation message to close creator and open identity manager
1010
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
1011
+ detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
1012
+ bubbles: true,
1013
+ composed: true
1014
+ }));
1015
+
1016
+ this.showBusy({ isBusy: false });
1017
+ }
1018
+
589
1019
  private setFormDisabled(disabled: boolean): void {
590
1020
  const root = this.shadowRoot!;
591
1021
  const inputIds = [