# ActiveDid Migration Plan - Separate Table Architecture **Author**: Matthew Raymer **Date**: 2025-08-29T15:00Z **Status**: 🎯 **IMPLEMENTATION READY** - Streamlined for existing migration service ## Objective Move the `activeDid` field from the `settings` table to a dedicated `active_identity` table to improve database architecture, prevent data corruption, and separate identity selection from user preferences. ## Result This document serves as the focused implementation guide for the ActiveDid migration, integrated with the existing IndexedDB migration service. ## Use/Run Reference this document during implementation to ensure all migration steps are followed correctly. **Critical**: This plan integrates with the existing `indexedDBMigrationService.ts` and maintains backward compatibility. ## Context & Scope - **In scope**: - Database schema modification for active_identity table with proper constraints - Migration of existing activeDid data with validation - Updates to PlatformServiceMixin API layer - Integration with existing IndexedDB migration service - **Out of scope**: - Changes to user interface for identity selection - Modifications to identity creation logic - Changes to authentication flow - Updates to individual components (handled by API layer) ## Environment & Preconditions - **OS/Runtime**: All platforms (Web, Electron, iOS, Android) - **Versions/Builds**: Current development branch, SQLite database - **Services/Endpoints**: Local database, PlatformServiceMixin, indexedDBMigrationService - **Auth mode**: Existing authentication system unchanged ## Architecture / Process Overview The migration integrates with the existing IndexedDB migration service: ```mermaid flowchart TD A[Current State
activeDid in settings] --> B[Phase 1: Schema Creation
Add active_identity table with constraints] B --> C[Phase 2: Data Migration
Copy activeDid data with validation] C --> D[Phase 3: API Updates
Update PlatformServiceMixin methods] D --> E[Final State
Separate active_identity table + dual-write] F[Existing IndexedDB Migration] --> G[Enhanced with active_identity support] G --> H[Maintains backward compatibility] H --> I[Preserves existing migration paths] ``` ## Current Codebase Assessment ### ✅ What's Already Implemented - **Database Schema**: `activeDid` field exists in `settings` table (`src/db-sql/migration.ts:67`) - **Constants**: `MASTER_SETTINGS_KEY = "1"` is properly defined (`src/db/tables/settings.ts:88`) - **Types**: Settings type includes `activeDid?: string` (`src/db/tables/settings.ts:25`) - **Migration Infrastructure**: SQLite migration system exists (`src/db-sql/migration.ts:31`) - **IndexedDB Migration Service**: Complete service exists (`src/services/indexedDBMigrationService.ts`) - **PlatformServiceMixin**: Basic structure exists with `$updateActiveDid()` method ### ❌ What Needs Implementation - **Missing Table**: `active_identity` table doesn't exist in current schema - **Missing API Methods**: Core PlatformServiceMixin methods need implementation - **Missing Types**: `ActiveIdentity` interface needs creation ## Required Changes ### **1. Database Schema via migration.ts** Add migration 003 to existing MIGRATIONS array: ```typescript // Add to MIGRATIONS array in src/db-sql/migration.ts { name: "003_active_did_separate_table", sql: ` -- Create new active_identity table with proper constraints CREATE TABLE IF NOT EXISTS active_identity ( id INTEGER PRIMARY KEY CHECK (id = 1), activeDid TEXT NOT NULL, lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE CASCADE ); -- Add performance indexes CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(activeDid); CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id); -- Insert default record (will be updated during migration) INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now')); -- MIGRATE EXISTING DATA: Copy activeDid from settings to active_identity -- This prevents data loss when migration runs on existing databases UPDATE active_identity SET activeDid = (SELECT activeDid FROM settings WHERE id = 1), lastUpdated = datetime('now') WHERE id = 1 AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''); `, }, ``` **Critical Data Migration Logic**: This migration includes data transfer to prevent users from losing their active identity selection when the migration runs on existing databases. ### **2. Type Definitions** Create ActiveIdentity interface in `src/db/tables/activeIdentity.ts`: ```typescript // Create new file: src/db/tables/activeIdentity.ts export interface ActiveIdentity { id: number; activeDid: string; lastUpdated: string; } ``` **Note**: This maintains separation of concerns by keeping active identity types separate from settings types, following the project's architectural pattern. ### **3. PlatformServiceMixin Methods** Implement required methods in `src/utils/PlatformServiceMixin.ts`: ```typescript // Add to PlatformServiceMixin methods section async $getActiveIdentity(): Promise { try { const result = await this.$dbQuery( "SELECT id, activeDid, lastUpdated FROM active_identity WHERE id = 1" ); if (result?.values?.length) { const [id, activeDid, lastUpdated] = result.values[0]; return { id: id as number, activeDid: activeDid as string, lastUpdated: lastUpdated as string }; } // Return default if no record exists return { id: 1, activeDid: '', lastUpdated: new Date().toISOString() }; } catch (error) { logger.error("[PlatformServiceMixin] Error getting active identity:", error); throw error; } } async $accountSettings(did?: string, defaults: Settings = {}): Promise { try { // Get settings without activeDid const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults); if (!settings) { return defaults; } // Get activeDid from new table> // Enhanced update activeDid method with dual-write pattern async $updateActiveDid(newDid: string | null): Promise { try { if (newDid === null) { // Clear active identity in both tables await this.$dbExec( "UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1" ); // Keep legacy field in sync (backward compatibility) await this.$dbExec( "UPDATE settings SET activeDid = '' WHERE id = ?", [MASTER_SETTINGS_KEY] ); } else { // Validate DID exists before setting const accountExists = await this.$dbQuery( "SELECT did FROM accounts WHERE did = ?", [newDid] ); if (!accountExists?.values?.length) { logger.error(`[PlatformServiceMixin] Cannot set activeDid to non-existent DID: ${newDid}`); return false; } // Update active identity in new table await this.$dbExec( "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", [newDid] ); // Keep legacy field in sync (backward compatibility) await this.$dbExec( "UPDATE settings SET activeDid = ? WHERE id = ?", [newDid, MASTER_SETTINGS_KEY] ); } // Update internal tracking (existing functionality) await this._updateInternalActiveDid(newDid); return true; } catch (error) { logger.error("[PlatformServiceMixin] Error updating activeDid:", error); return false; } } ``` ### **4. Integration with Existing IndexedDB Migration Service** The existing `indexedDBMigrationService.ts` already handles activeDid migration from Dexie to SQLite. This plan adds the separate table architecture while maintaining compatibility. **No changes needed** to the existing migration service - it will continue to work with the dual-write pattern. ### **5. Data Migration Strategy** The migration 003 includes **automatic data migration** to prevent data loss: 1. **Schema Creation**: Creates `active_identity` table with proper constraints 2. **Data Transfer**: Automatically copies existing `activeDid` from `settings` table 3. **Validation**: Ensures data exists before attempting migration 4. **Atomic Operation**: Schema and data migration happen together **Benefits of Single Migration Approach**: - **No Data Loss**: Existing users keep their active identity selection - **Atomic Operation**: If it fails, nothing is partially migrated - **Simpler Tracking**: Only one migration to track and manage - **Rollback Safety**: Complete rollback if issues arise ## What Doesn't Need to Change - **All Vue components** - API layer handles migration transparently - **Platform services** - Use PlatformServiceMixin, no direct access - **User interface** - No changes to identity selection UI - **Authentication flow** - Existing system unchanged - **Component logic** - All activeDid handling through API methods - **Migration system** - Use existing migration.ts approach - **IndexedDB migration service** - Continues working unchanged - **Existing database operations** - All current queries continue working ## Implementation Steps ### **Step 1: Add Migration** - Add migration 003 to `MIGRATIONS` array in `src/db-sql/migration.ts` - Deploy migration to create `active_identity` table ### **Step 2: Implement API Methods** - Create `ActiveIdentity` interface in `src/db/tables/settings.ts` - Implement `$getActiveIdentity()` method in PlatformServiceMixin - Implement `$accountSettings()` method in PlatformServiceMixin - Enhance `$updateActiveDid()` method with dual-write pattern ### **Step 3: Test Integration** - Test new methods with existing components - Verify dual-write pattern works correctly - Validate backward compatibility ## Backward Compatibility ### **Critical Requirements** - **IndexedDB Migration Service**: Must continue working unchanged - **MASTER_SETTINGS_KEY = "1"**: Must be preserved for legacy support - **Dual-Write Pattern**: Ensures both old and new systems stay in sync - **Existing Queries**: All current database operations continue working ### **Migration Strategy** - **Phase 1**: Add new table alongside existing system - **Phase 2**: Use dual-write pattern during transition - **Phase 3**: Future cleanup (not in current scope) ## Rollback Strategy If migration fails, the existing `activeDid` field in settings table remains functional: ```sql -- Rollback: Remove new table DROP TABLE IF EXISTS active_identity; ``` No data loss risk - the legacy field continues working unchanged. ## Success Criteria - [ ] `active_identity` table created with proper constraints - [ ] All new PlatformServiceMixin methods implemented and tested - [ ] Dual-write pattern working correctly - [ ] Existing IndexedDB migration service continues working - [ ] No breaking changes to existing functionality - [ ] All platforms tested and verified - [ ] Data migration validation successful (existing activeDid data preserved) ## Risks & Mitigation ### **Low Risk: Migration Failure** - **Mitigation**: Rollback removes new table, legacy system continues working - **Impact**: No data loss, no service interruption - **Data Safety**: Existing activeDid data is preserved in settings table ### **Low Risk: Data Loss** - **Mitigation**: Migration 003 includes automatic data transfer from settings to active_identity - **Impact**: Users maintain their active identity selection - **Validation**: Migration only runs if data exists and is valid ### **Low Risk: API Changes** - **Mitigation**: Dual-write pattern maintains backward compatibility - **Impact**: Existing components continue working unchanged ### **Low Risk: Performance Impact** - **Mitigation**: Proper indexing and minimal additional queries - **Impact**: Negligible performance change ## Summary This migration plan: 1. **Adds new architecture** without breaking existing functionality 2. **Integrates seamlessly** with existing IndexedDB migration service 3. **Maintains full backward compatibility** during transition 4. **Requires minimal changes** to existing codebase 5. **Provides clear rollback path** if issues arise The plan focuses only on necessary changes while preserving all existing functionality and migration paths.