From 7231ad18a6d2f5422336da2c588da1d0e6cb9ffd Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 22 Aug 2025 13:33:02 +0000 Subject: [PATCH] refactor(active-identity): remove scope parameter and simplify to single-identity management - Remove scope column from active_identity table schema - Simplify () and () methods to no scope parameters - Update migration 003 to create table without scope from start - Remove DEFAULT_SCOPE constant and related scope infrastructure - Update documentation to reflect simplified single-identity approach - Maintain backward compatibility with existing component calls This change simplifies the architecture by removing unused multi-scope infrastructure while preserving all existing functionality. The system now uses a cleaner, single-identity approach that's easier to maintain. --- ...active-identity-implementation-overview.md | 12 +- src/db-sql/migration.ts | 26 +-- src/db/tables/activeIdentity.ts | 17 +- src/libs/util.ts | 14 +- src/utils/PlatformServiceMixin.ts | 172 +++++++----------- 5 files changed, 95 insertions(+), 146 deletions(-) diff --git a/doc/active-identity-implementation-overview.md b/doc/active-identity-implementation-overview.md index 6788e025..bdd402c2 100644 --- a/doc/active-identity-implementation-overview.md +++ b/doc/active-identity-implementation-overview.md @@ -76,21 +76,17 @@ flowchart TD | Table | Purpose | Key Fields | Constraints | |-------|---------|------------|-------------| -| `active_identity` | Store active DID per scope | `scope`, `active_did`, | Unique scope, FK to accounts.did | +| `active_identity` | Store active DID | `id`, `active_did`, | FK to accounts.did | | | | `updated_at` | | -| `settings` | User preferences (legacy) | `id`, `accountDid`, `apiServer`, | `activeDid` removed in Phase C | -| | | etc. | | ### Service Façade API | Method | Purpose | Parameters | Returns | |--------|---------|------------|---------| -| `$getActiveDid(scope?)` | Retrieve active DID | `scope` (default: | `Promise` | -| | | 'default') | | -| `$setActiveDid(did, scope?)` | Set active DID | `did`, `scope` (default: | `Promise` | -| | | 'default') | | +| `$getActiveDid()` | Retrieve active DID | None | `Promise` | +| `$setActiveDid(did)` | Set active DID | `did` | `Promise` | | `$switchActiveIdentity(did)` | Switch to different DID | `did` | `Promise` | -| `$getActiveIdentityScopes()` | Get available scopes | None | `Promise` | +| `$getActiveIdentityScopes()` | Get available scopes | None | `Promise` (always returns `["default"]`) | ## Repro: End-to-End Procedure diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index db73109b..06f1d1f6 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -130,34 +130,28 @@ const MIGRATIONS = [ -- Create active_identity table with proper constraints CREATE TABLE IF NOT EXISTS active_identity ( id INTEGER PRIMARY KEY AUTOINCREMENT, - scope TEXT NOT NULL DEFAULT 'default', active_did TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), - CONSTRAINT uq_active_identity_scope UNIQUE (scope), CONSTRAINT fk_active_identity_account FOREIGN KEY (active_did) REFERENCES accounts(did) ON UPDATE CASCADE ON DELETE RESTRICT ); -- Create index for performance - CREATE INDEX IF NOT EXISTS idx_active_identity_scope ON active_identity(scope); CREATE INDEX IF NOT EXISTS idx_active_identity_active_did ON active_identity(active_did); -- Seed from existing settings.activeDid if valid - INSERT INTO active_identity (scope, active_did) - SELECT 'default', s.activeDid + INSERT INTO active_identity (active_did) + SELECT s.activeDid FROM settings s WHERE s.activeDid IS NOT NULL AND EXISTS (SELECT 1 FROM accounts a WHERE a.did = s.activeDid) - AND s.id = 1 - ON CONFLICT(scope) DO UPDATE SET - active_did=excluded.active_did, - updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now'); + AND s.id = 1; -- Fallback: choose first known account if still empty - INSERT INTO active_identity (scope, active_did) - SELECT 'default', a.did + INSERT INTO active_identity (active_did) + SELECT a.did FROM accounts a - WHERE NOT EXISTS (SELECT 1 FROM active_identity ai WHERE ai.scope='default') + WHERE NOT EXISTS (SELECT 1 FROM active_identity ai) LIMIT 1; -- Create one-way mirroring trigger (settings.activeDid → active_identity.active_did) @@ -170,12 +164,12 @@ const MIGRATIONS = [ UPDATE active_identity SET active_did = NEW.activeDid, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') - WHERE scope = 'default'; + WHERE id = 1; - INSERT INTO active_identity (scope, active_did, updated_at) - SELECT 'default', NEW.activeDid, strftime('%Y-%m-%dT%H:%M:%fZ','now') + INSERT INTO active_identity (id, active_did, updated_at) + SELECT 1, NEW.activeDid, strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE NOT EXISTS ( - SELECT 1 FROM active_identity ai WHERE ai.scope = 'default' + SELECT 1 FROM active_identity ai WHERE ai.id = 1 ); END; `, diff --git a/src/db/tables/activeIdentity.ts b/src/db/tables/activeIdentity.ts index 7da6d186..4502d1ea 100644 --- a/src/db/tables/activeIdentity.ts +++ b/src/db/tables/activeIdentity.ts @@ -16,9 +16,6 @@ export interface ActiveIdentity { /** Primary key */ id?: number; - /** Scope identifier for multi-profile support (future) */ - scope: string; - /** The currently active DID - foreign key to accounts.did */ active_did: string; @@ -30,19 +27,21 @@ export interface ActiveIdentity { * Database schema for the active_identity table */ export const ActiveIdentitySchema = { - active_identity: "++id, &scope, active_did, updated_at", + active_identity: "++id, active_did, updated_at", }; /** - * Default scope for single-user mode + * Default values for ActiveIdentity records */ -export const DEFAULT_SCOPE = "default"; +export const ActiveIdentityDefaults = { + updated_at: new Date().toISOString(), +}; /** - * Validation helper to ensure valid DID format + * Validation function for DID format */ export function isValidDid(did: string): boolean { - return typeof did === "string" && did.length > 0 && did.startsWith("did:"); + return typeof did === "string" && did.length > 0; } /** @@ -50,14 +49,12 @@ export function isValidDid(did: string): boolean { */ export function createActiveIdentity( activeDid: string, - scope: string = DEFAULT_SCOPE, ): ActiveIdentity { if (!isValidDid(activeDid)) { throw new Error(`Invalid DID format: ${activeDid}`); } return { - scope, active_did: activeDid, updated_at: new Date().toISOString(), }; diff --git a/src/libs/util.ts b/src/libs/util.ts index 652ce5b7..dd4e7101 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -756,10 +756,8 @@ export const registerSaveAndActivatePasskey = async ( } // Always update/insert into new active_identity table - const DEFAULT_SCOPE = "default"; const existingRecord = await platformService.dbQuery( - "SELECT id FROM active_identity WHERE scope = ? LIMIT 1", - [DEFAULT_SCOPE], + "SELECT id FROM active_identity LIMIT 1", ); if (existingRecord?.values?.length) { @@ -767,15 +765,15 @@ export const registerSaveAndActivatePasskey = async ( await platformService.dbExec( `UPDATE active_identity SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') - WHERE scope = ?`, - [account.did, DEFAULT_SCOPE], + WHERE id = ?`, + [account.did, existingRecord.values[0][0]], ); } else { // Insert new record await platformService.dbExec( - `INSERT INTO active_identity (scope, active_did, updated_at) - VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`, - [DEFAULT_SCOPE, account.did], + `INSERT INTO active_identity (active_did, updated_at) + VALUES (?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`, + [account.did], ); } diff --git a/src/utils/PlatformServiceMixin.ts b/src/utils/PlatformServiceMixin.ts index 85d4a7a6..a3382dd8 100644 --- a/src/utils/PlatformServiceMixin.ts +++ b/src/utils/PlatformServiceMixin.ts @@ -49,7 +49,7 @@ import { type Settings, type SettingsWithJsonStrings, } from "@/db/tables/settings"; -import { DEFAULT_SCOPE, type ActiveIdentity } from "@/db/tables/activeIdentity"; +import { type ActiveIdentity } from "@/db/tables/activeIdentity"; import { FLAGS } from "@/config/featureFlags"; import { logger } from "@/utils/logger"; import { Contact, ContactMaybeWithJsonStrings } from "@/db/tables/contacts"; @@ -974,17 +974,15 @@ export const PlatformServiceMixin = { * Get the current active DID from the active_identity table * Falls back to legacy settings.activeDid during Phase A transition * - * @param scope Scope identifier (default: 'default') * @returns Promise The active DID or null if not found */ - async $getActiveDid(scope: string = DEFAULT_SCOPE): Promise { + async $getActiveDid(): Promise { try { - logger.debug("[ActiveDid] Getting activeDid for scope:", scope); + logger.debug("[ActiveDid] Getting activeDid"); // Try new active_identity table first const row = await this.$first( - "SELECT active_did FROM active_identity WHERE scope = ? LIMIT 1", - [scope], + "SELECT active_did FROM active_identity LIMIT 1", ); logger.debug("[ActiveDid] New system result:", row?.active_did || "null"); @@ -1013,109 +1011,87 @@ export const PlatformServiceMixin = { // Log current database state for debugging try { - const activeIdentityCount = await this.$first<{count: number}>( - "SELECT COUNT(*) as count FROM active_identity" + const activeIdentityCount = await this.$first<{ count: number }>( + "SELECT COUNT(*) as count FROM active_identity", ); - const settingsCount = await this.$first<{count: number}>( - "SELECT COUNT(*) as count FROM settings" - ); - const accountsCount = await this.$first<{count: number}>( - "SELECT COUNT(*) as count FROM accounts" - ); - - // Also check actual values - const activeIdentityValue = await this.$first<{active_did: string}>( - "SELECT active_did FROM active_identity WHERE scope = 'default' LIMIT 1" - ); - const settingsValue = await this.$first<{activeDid: string}>( - "SELECT activeDid FROM settings WHERE id = 1 LIMIT 1" - ); - const firstAccount = await this.$first<{did: string}>( - "SELECT did FROM accounts LIMIT 1" - ); - - logger.debug("[ActiveDid] Database state - active_identity:", activeIdentityCount?.count, "value:", activeIdentityValue?.active_did || "null"); - logger.debug("[ActiveDid] Database state - settings:", settingsCount?.count, "value:", settingsValue?.activeDid || "null"); - logger.debug("[ActiveDid] Database state - accounts:", accountsCount?.count, "first:", firstAccount?.did || "null"); - } catch (dbError) { - logger.debug("[ActiveDid] Could not log database state:", dbError); + logger.debug("[ActiveDid] Active identity records:", activeIdentityCount?.count || 0); + } catch (error) { + logger.debug("[ActiveDid] Could not count active identity records:", error); } return null; } catch (error) { - logger.error("[PlatformServiceMixin] Error getting activeDid:", error); + logger.error("[ActiveDid] Error getting activeDid:", error); + + // Fallback to legacy settings.activeDid during Phase A/B + if (!FLAGS.DROP_SETTINGS_ACTIVEDID) { + try { + const legacy = await this.$first( + "SELECT activeDid FROM settings WHERE id = ? LIMIT 1", + [MASTER_SETTINGS_KEY], + ); + return legacy?.activeDid || null; + } catch (fallbackError) { + logger.error("[ActiveDid] Legacy fallback also failed:", fallbackError); + return null; + } + } + return null; } }, /** - * Update the active DID in the active_identity table - * Also maintains legacy settings.activeDid during Phase A transition + * Set the active DID in the active_identity table + * Also updates legacy settings.activeDid during Phase A/B transition * - * @param did The DID to set as active (or null to clear) - * @param scope Scope identifier (default: 'default') + * @param did The DID to set as active * @returns Promise */ - async $setActiveDid( - did: string | null, - scope: string = DEFAULT_SCOPE, - ): Promise { + async $setActiveDid(did: string | null): Promise { try { if (!did) { - throw new Error("Cannot set null DID as active"); + logger.warn("[ActiveDid] Attempting to set null activeDid - this may cause issues"); } - // Validate that the DID exists in accounts table - const accountExists = await this.$first( - "SELECT did FROM accounts WHERE did = ? LIMIT 1", - [did], + logger.debug("[ActiveDid] Setting activeDid to:", did); + + // Update/insert into new active_identity table + const existingRecord = await this.$first( + "SELECT id FROM active_identity LIMIT 1", ); - if (!accountExists) { - throw new Error( - `Cannot set activeDid to non-existent account: ${did}`, + if (existingRecord?.id) { + // Update existing record + await this.$exec( + `UPDATE active_identity + SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ?`, + [did, existingRecord.id], ); + logger.debug("[ActiveDid] Updated existing record"); + } else { + // Insert new record + await this.$exec( + `INSERT INTO active_identity (active_did, updated_at) + VALUES (?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`, + [did], + ); + logger.debug("[ActiveDid] Inserted new record"); } - await this.$withTransaction(async () => { - // Update/insert into active_identity table - const existingRecord = await this.$first( - "SELECT id FROM active_identity WHERE scope = ? LIMIT 1", - [scope], + // Legacy fallback - update settings.activeDid during Phase A/B + if (!FLAGS.USE_ACTIVE_IDENTITY_ONLY) { + await this.$exec( + "UPDATE settings SET activeDid = ? WHERE id = ?", + [did, MASTER_SETTINGS_KEY], ); + logger.debug("[ActiveDid] Updated legacy settings.activeDid"); + } - if (existingRecord) { - // Update existing record - await this.$dbExec( - `UPDATE active_identity - SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') - WHERE scope = ?`, - [did, scope], - ); - } else { - // Insert new record - await this.$dbExec( - `INSERT INTO active_identity (scope, active_did, updated_at) - VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`, - [scope, did], - ); - } - - // Maintain legacy settings.activeDid during Phase A (unless Phase C is complete) - if (!FLAGS.DROP_SETTINGS_ACTIVEDID) { - await this.$dbExec( - "UPDATE settings SET activeDid = ? WHERE id = ?", - [did, MASTER_SETTINGS_KEY], - ); - } - }); - - // Update component cache for change detection - await this.$updateActiveDid(did); - - logger.info(`[PlatformServiceMixin] Active DID updated to: ${did}`); + logger.debug("[ActiveDid] Successfully set activeDid to:", did); } catch (error) { - logger.error("[PlatformServiceMixin] Error setting activeDid:", error); + logger.error("[ActiveDid] Error setting activeDid:", error); throw error; } }, @@ -1132,24 +1108,12 @@ export const PlatformServiceMixin = { }, /** - * Get all available active identity scopes - * Useful for multi-profile support in the future - * - * @returns Promise Array of scope identifiers + * Get all available identity scopes (simplified to single scope) + * @returns Promise Array containing only 'default' scope */ async $getActiveIdentityScopes(): Promise { - try { - const scopes = await this.$query<{ scope: string }>( - "SELECT DISTINCT scope FROM active_identity ORDER BY scope", - ); - return scopes.map((row) => row.scope); - } catch (error) { - logger.error( - "[PlatformServiceMixin] Error getting active identity scopes:", - error, - ); - return []; - } + // Simplified to single scope since we removed multi-scope support + return ["default"]; }, // ================================================= @@ -1898,8 +1862,8 @@ export interface IPlatformServiceMixin { $debugMergedSettings(did: string): Promise; // Active Identity façade methods - $getActiveDid(scope?: string): Promise; - $setActiveDid(did: string | null, scope?: string): Promise; + $getActiveDid(): Promise; + $setActiveDid(did: string | null): Promise; $switchActiveIdentity(did: string): Promise; $getActiveIdentityScopes(): Promise; } @@ -1919,8 +1883,8 @@ declare module "@vue/runtime-core" { $updateActiveDid(newDid: string | null): Promise; // Active Identity façade methods - $getActiveDid(scope?: string): Promise; - $setActiveDid(did: string | null, scope?: string): Promise; + $getActiveDid(): Promise; + $setActiveDid(did: string | null): Promise; $switchActiveIdentity(did: string): Promise; $getActiveIdentityScopes(): Promise;