# ActiveDid Migration Plan - Separate Table Architecture **Author**: Matthew Raymer **Date**: 2025-01-27T18:30Z **Status**: 🎯 **PLANNING** - Active migration planning phase ## Objective Move the `activeDid` field from the `settings` table to a dedicated `active_identity` table to improve database architecture and separate identity selection from user preferences. ## Result This document serves as the comprehensive planning and implementation guide for the ActiveDid migration. ## Use/Run Reference this document during implementation to ensure all migration steps are followed correctly and all stakeholders are aligned on the approach. ## Context & Scope - **In scope**: - Database schema modification for active_identity table - Migration of existing activeDid data - Updates to all platform services and mixins - Type definition updates - Testing across all platforms - **Out of scope**: - Changes to user interface for identity selection - Modifications to identity creation logic - Changes to authentication flow ## Environment & Preconditions - **OS/Runtime**: All platforms (Web, Electron, iOS, Android) - **Versions/Builds**: Current development branch, SQLite database - **Services/Endpoints**: Local database, PlatformServiceMixin - **Auth mode**: Existing authentication system unchanged ## Architecture / Process Overview The migration follows a phased approach to minimize risk and ensure data integrity: ```mermaid flowchart TD A[Current State
activeDid in settings] --> B[Phase 1: Schema Creation
Add active_identity table] B --> C[Phase 2: Data Migration
Copy activeDid data] C --> D[Phase 3: API Updates
Update all access methods] D --> E[Phase 4: Cleanup
Remove activeDid from settings] E --> F[Final State
Separate active_identity table] G[Rollback Plan
Keep old field until verified] --> H[Data Validation
Verify integrity at each step] H --> I[Platform Testing
Test all platforms] I --> J[Production Deployment
Gradual rollout] ``` ## Interfaces & Contracts ### Database Schema Changes | Table | Current Schema | New Schema | Migration Required | |-------|----------------|------------|-------------------| | `settings` | `activeDid TEXT` | Field removed | Yes - data migration | | `active_identity` | Does not exist | New table with `activeDid TEXT` | Yes - table creation | ### API Contract Changes | Method | Current Behavior | New Behavior | Breaking Change | |---------|------------------|--------------|-----------------| | `$accountSettings()` | Returns settings with activeDid | Returns settings without activeDid | No - backward compatible | | `$saveSettings()` | Updates settings.activeDid | Updates active_identity.activeDid | Yes - requires updates | | `$updateActiveDid()` | Updates internal tracking | Updates active_identity table | Yes - requires updates | ## Repro: End-to-End Procedure ### Phase 1: Schema Creation ```sql -- Create new active_identity table CREATE TABLE active_identity ( id INTEGER PRIMARY KEY CHECK (id = 1), activeDid TEXT NOT NULL, lastUpdated TEXT NOT NULL DEFAULT (datetime('now')) ); -- Insert default record (will be updated during migration) INSERT INTO active_identity (id, activeDid) VALUES (1, ''); ``` ### Phase 2: Data Migration ```typescript // Migration script to copy existing activeDid values async function migrateActiveDidToSeparateTable(): Promise { // Get current activeDid from settings const currentSettings = await retrieveSettingsForDefaultAccount(); const activeDid = currentSettings.activeDid; if (activeDid) { // Insert into new table await dbExec( "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", [activeDid] ); } } ``` ### Phase 3: API Updates ```typescript // Updated PlatformServiceMixin method async $accountSettings(did?: string, defaults: Settings = {}): Promise { // Get settings without activeDid const settings = await this._getSettingsWithoutActiveDid(); // Get activeDid from separate table const activeIdentity = await this._getActiveIdentity(); return { ...settings, activeDid: activeIdentity.activeDid }; } // New method to get settings without activeDid async _getSettingsWithoutActiveDid(): Promise { const result = await this.$dbQuery( "SELECT id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible, " + "finishedOnboarding, firstName, hideRegisterPromptOnNewContact, isRegistered, " + "lastName, lastAckedOfferToUserJwtId, lastAckedOfferToUserProjectsJwtId, " + "lastNotifiedClaimId, lastViewedClaimId, notifyingNewActivityTime, " + "notifyingReminderMessage, notifyingReminderTime, partnerApiServer, " + "passkeyExpirationMinutes, profileImageUrl, searchBoxes, showContactGivesInline, " + "showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer, warnIfTestServer, " + "webPushServer FROM settings WHERE id = ?", [MASTER_SETTINGS_KEY] ); if (!result?.values?.length) { return DEFAULT_SETTINGS; } return this._mapColumnsToValues(result.columns, result.values)[0] as Settings; } // New method to get active identity async _getActiveIdentity(): Promise<{ activeDid: string | null }> { const result = await this.$dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (!result?.values?.length) { return { activeDid: null }; } return { activeDid: result.values[0][0] as string }; } ``` ### **Master Settings Functions Implementation Strategy** #### **1. Update `retrieveSettingsForDefaultAccount()`** ```typescript // Current implementation in src/db/databaseUtil.ts:148 export async function retrieveSettingsForDefaultAccount(): Promise { const platform = PlatformServiceFactory.getInstance(); const sql = "SELECT * FROM settings WHERE id = ?"; const result = await platform.dbQuery(sql, [MASTER_SETTINGS_KEY]); // ... rest of implementation } // Updated implementation export async function retrieveSettingsForDefaultAccount(): Promise { const platform = PlatformServiceFactory.getInstance(); // Get settings without activeDid const sql = "SELECT id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible, " + "finishedOnboarding, firstName, hideRegisterPromptOnNewContact, isRegistered, " + "lastName, lastAckedOfferToUserJwtId, lastAckedOfferToUserProjectsJwtId, " + "lastNotifiedClaimId, lastViewedClaimId, notifyingNewActivityTime, " + "notifyingReminderMessage, notifyingReminderTime, partnerApiServer, " + "passkeyExpirationMinutes, profileImageUrl, searchBoxes, showContactGivesInline, " + "showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer, warnIfTestServer, " + "webPushServer FROM settings WHERE id = ?"; const result = await platform.dbQuery(sql, [MASTER_SETTINGS_KEY]); if (!result) { return DEFAULT_SETTINGS; } else { const settings = mapColumnsToValues(result.columns, result.values)[0] as Settings; // Handle JSON parsing if (settings.searchBoxes) { settings.searchBoxes = JSON.parse(settings.searchBoxes); } // Get activeDid from separate table const activeIdentityResult = await platform.dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (activeIdentityResult?.values?.length) { settings.activeDid = activeIdentityResult.values[0][0] as string; } return settings; } } ``` #### **2. Update `$getMergedSettings()` Method** ```typescript // Current implementation in PlatformServiceMixin.ts:485 async $getMergedSettings(defaultKey: string, accountDid?: string, defaultFallback: Settings = {}): Promise { // Get default settings const defaultSettings = await this.$getSettings(defaultKey, defaultFallback); // ... rest of implementation } // Updated implementation async $getMergedSettings(defaultKey: string, accountDid?: string, defaultFallback: Settings = {}): Promise { try { // Get default settings (now without activeDid) const defaultSettings = await this.$getSettings(defaultKey, defaultFallback); // If no account DID, return defaults with activeDid from separate table if (!accountDid) { if (defaultSettings) { // Get activeDid from separate table const activeIdentityResult = await this.$dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (activeIdentityResult?.values?.length) { defaultSettings.activeDid = activeIdentityResult.values[0][0] as string; } } return defaultSettings || defaultFallback; } // ... rest of existing implementation for account-specific settings } catch (error) { logger.error(`[Settings Trace] ❌ Failed to get merged settings:`, { defaultKey, accountDid, error }); return defaultFallback; } } ``` ## What Works (Evidence) - ✅ **Current activeDid storage** in settings table - **Time**: 2025-01-27T18:30Z - **Evidence**: `src/db/tables/settings.ts:25` - activeDid field exists - **Verify at**: Current database schema and Settings type definition - ✅ **PlatformServiceMixin integration** with activeDid - **Time**: 2025-01-27T18:30Z - **Evidence**: `src/utils/PlatformServiceMixin.ts:108` - activeDid tracking - **Verify at**: Component usage across all platforms - ✅ **Database migration infrastructure** exists - **Time**: 2025-01-27T18:30Z - **Evidence**: `src/db-sql/migration.ts:31` - migration system in place - **Verify at**: Existing migration scripts and database versioning - ✅ **Master settings functions architecture** supports migration - **Time**: 2025-01-27T18:30Z - **Evidence**: Functions use explicit field selection, not `SELECT *` - **Verify at**: `src/db/databaseUtil.ts:148` and `src/utils/PlatformServiceMixin.ts:442` ## What Doesn't (Evidence & Hypotheses) - ❌ **No separate active_identity table** exists - **Time**: 2025-01-27T18:30Z - **Evidence**: Database schema only shows settings table - **Hypothesis**: Table needs to be created as part of migration - **Next probe**: Create migration script for new table - ❌ **Platform services hardcoded** to settings table - **Time**: 2025-01-27T18:30Z - **Evidence**: `src/services/platforms/*.ts` - direct settings table access - **Hypothesis**: All platform services need updates - **Next probe**: Audit all platform service files for activeDid usage ## Risks, Limits, Assumptions - **Data Loss Risk**: Migration failure could lose activeDid values - **Breaking Changes**: API updates required across all platform services - **Rollback Complexity**: Schema changes make rollback difficult - **Testing Overhead**: All platforms must be tested with new structure - **Performance Impact**: Additional table join for activeDid retrieval - **Migration Timing**: Must be coordinated with other database changes ## Next Steps | Owner | Task | Exit Criteria | Target Date (UTC) | |-------|------|---------------|-------------------| | Development Team | Create migration script | Migration script tested and validated | 2025-01-28 | | Development Team | Update type definitions | Settings type updated, ActiveIdentity type created | 2025-01-28 | | Development Team | Update platform services | All services use new active_identity table | 2025-01-29 | | Development Team | Update PlatformServiceMixin | Mixin methods updated and tested | 2025-01-29 | | QA Team | Platform testing | All platforms tested and verified | 2025-01-30 | | Development Team | Deploy migration | Production deployment successful | 2025-01-31 | ## References - [Database Migration Guide](./database-migration-guide.md) - [Dexie to SQLite Mapping](./dexie-to-sqlite-mapping.md) - [PlatformServiceMixin Documentation](./component-communication-guide.md) - [Migration Templates](./migration-templates/) ## Competence Hooks - *Why this works*: Separates concerns between identity selection and user preferences, improves database normalization, enables future identity management features - *Common pitfalls*: Forgetting to update all platform services, not testing rollback scenarios, missing data validation during migration - *Next skill unlock*: Advanced database schema design and migration planning - *Teach-back*: Explain the four-phase migration approach and why each phase is necessary ## Collaboration Hooks - **Sign-off checklist**: - [ ] Migration script tested on development database - [ ] All platform services updated and tested - [ ] Rollback plan validated - [ ] Performance impact assessed - [ ] All stakeholders approve deployment timeline ## Assumptions & Limits - Current activeDid values are valid and should be preserved - All platforms can handle the additional database table - Migration can be completed without user downtime - Rollback to previous schema is acceptable if needed - Performance impact of additional table join is minimal ## Component & View Impact Analysis ### **High Impact Components** 1. **`IdentitySection.vue`** - Receives `activeDid` as prop - **Current**: Uses `activeDid` from parent component via prop - **Impact**: **NO CHANGES REQUIRED** - Parent component handles migration - **Risk**: **LOW** - No direct database access **Current Implementation:** ```vue ``` **Required Changes:** ```vue ``` **Key Insight**: This component requires **zero changes** since it receives `activeDid` as a prop. The parent component that provides this prop will handle the migration automatically through the API layer updates. 2. **`DIDView.vue`** - Heavy activeDid usage - **Current**: Initializes `activeDid` in `mounted()` lifecycle - **Impact**: Must update initialization logic to use new table - **Risk**: **HIGH** - Primary DID viewing component **Current Implementation:** ```vue ``` **Required Changes:** ```vue ``` 3. **`HomeView.vue`** - ActiveDid change detection - **Current**: Has `onActiveDidChanged()` watcher method - **Impact**: Watcher logic needs updates for new data source - **Risk**: **MEDIUM** - Core navigation component **Current Implementation:** ```vue ``` **Required Changes:** ```vue ``` **Key Insight**: HomeView will require minimal changes since it already uses the `$accountSettings()` method, which will be updated to handle the new table structure transparently. ### **Medium Impact Components** 1. **`InviteOneAcceptView.vue`** - Identity fallback logic - **Current**: Creates identity if no `activeDid` exists - **Impact**: Fallback logic needs to check new table - **Risk**: **MEDIUM** - Invite processing component **Current Implementation:** ```vue ``` **Required Changes:** ```vue ``` **Key Insight**: This component will work automatically since it uses `$accountSettings()`. The fallback logic doesn't need changes. 2. **`ClaimView.vue`** - Settings retrieval - **Current**: Gets `activeDid` from `$accountSettings()` - **Impact**: Will automatically work if API is updated - **Risk**: **LOW** - Depends on API layer updates **Current Implementation:** ```vue ``` **Required Changes:** ```vue ``` **Key Insight**: This component requires zero changes since it already uses the proper API method. It's the lowest risk component. 3. **`ContactAmountsView.vue`** - Uses phased-out method - **Current**: Uses `$getSettings(MASTER_SETTINGS_KEY)` (being phased out) - **Impact**: **NO CHANGES REQUIRED** - Will be updated when migrating to `getMasterSettings` - **Risk**: **LOW** - Part of planned refactoring, not migration-specific **Note**: This component will be updated as part of the broader refactoring to replace `$getSettings(MASTER_SETTINGS_KEY)` with `getMasterSettings()`, which is separate from the activeDid migration. The migration plan focuses only on components that require immediate changes for the active_identity table. ### **Service Layer Impact** 1. **`WebPlatformService.ts`** - **Current**: Direct SQL queries to settings table - **Impact**: Must add `active_identity` table queries - **Risk**: **HIGH** - Core web platform service 2. **`CapacitorPlatformService.ts`** - **Current**: Similar direct SQL access - **Impact**: Same updates as web service - **Risk**: **HIGH** - Mobile platform service 3. **`PlatformServiceMixin.ts`** - **Current**: Core methods like `$accountSettings()`, `$saveSettings()` - **Impact**: Major refactoring required - **Risk**: **CRITICAL** - Used by 50+ components ### **API Contract Changes** 1. **`$saveSettings()` method** - **Current**: Updates `settings.activeDid` - **New**: Updates `active_identity.activeDid` - **Impact**: All components using this method 2. **`$updateActiveDid()` method** - **Current**: Internal tracking only - **New**: Database persistence required - **Impact**: Identity switching logic ### **Master Settings Functions Impact** 1. **`retrieveSettingsForDefaultAccount()` function** - **Current**: Returns settings with `activeDid` field from master settings - **New**: Returns settings without `activeDid` field - **Impact**: **HIGH** - Used by migration scripts and core database utilities - **Location**: `src/db/databaseUtil.ts:148` 2. **`$getMergedSettings()` method** - **Current**: Merges default and account settings, includes `activeDid` from defaults - **New**: Merges settings without `activeDid`, adds from `active_identity` table - **Impact**: **HIGH** - Core method used by `$accountSettings()` - **Location**: `src/utils/PlatformServiceMixin.ts:485` **Note**: `$getSettings(MASTER_SETTINGS_KEY)` is being phased out in favor of `getMasterSettings`, so it doesn't require updates for this migration. ### **New `getMasterSettings()` Function** Since we're phasing out `$getSettings(MASTER_SETTINGS_KEY)`, this migration provides an opportunity to implement the new `getMasterSettings()` function that will handle the active_identity table integration from the start: ```typescript // New getMasterSettings function to replace phased-out $getSettings async getMasterSettings(): Promise { try { // Get master settings without activeDid const result = await this.$dbQuery( "SELECT id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible, " + "finishedOnboarding, firstName, hideRegisterPromptOnNewContact, isRegistered, " + "lastName, lastAckedOfferToUserJwtId, lastAckedOfferToUserProjectsJwtId, " + "lastNotifiedClaimId, lastViewedClaimId, notifyingNewActivityTime, " + "notifyingReminderMessage, notifyingReminderTime, partnerApiServer, " + "passkeyExpirationMinutes, profileImageUrl, searchBoxes, showContactGivesInline, " + "showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer, warnIfTestServer, " + "webPushServer FROM settings WHERE id = ?", [MASTER_SETTINGS_KEY] ); if (!result?.values?.length) { return DEFAULT_SETTINGS; } const settings = this._mapColumnsToValues(result.columns, result.values)[0] as Settings; // Handle JSON field parsing if (settings.searchBoxes) { settings.searchBoxes = this._parseJsonField(settings.searchBoxes, []); } // Get activeDid from separate table const activeIdentityResult = await this.$dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (activeIdentityResult?.values?.length) { settings.activeDid = activeIdentityResult.values[0][0] as string; } return settings; } catch (error) { logger.error(`[Settings Trace] ❌ Failed to get master settings:`, { error }); return DEFAULT_SETTINGS; } } ``` ### **Testing Impact** 1. **Unit Tests** - All platform service methods - PlatformServiceMixin methods - Database migration scripts 2. **Integration Tests** - Component behavior with new data source - Identity switching workflows - Settings persistence 3. **Platform Tests** - Web, Electron, iOS, Android - Cross-platform data consistency - Migration success on all platforms ### **Performance Impact** 1. **Additional Table Join** - Settings queries now require active_identity table - Potential performance impact on frequent operations - Need for proper indexing 2. **Caching Considerations** - ActiveDid changes trigger cache invalidation - Component re-rendering on identity switches - Memory usage for additional table data ### **Risk Assessment by Component Type** - **Critical Risk**: PlatformServiceMixin, Platform Services - **High Risk**: Identity-related components, views using `$accountSettings()` - **Medium Risk**: Components with direct settings access, identity management - **Low Risk**: Components using only basic settings, utility components, prop-based components, components using phased-out methods ### **Migration Timeline Impact** - **Phase 1**: Schema Creation - No component impact - **Phase 2**: Data Migration - No component impact - **Phase 3**: API Updates - All components affected - **Phase 4**: Cleanup - No component impact ### **Update Priority Order** 1. **PlatformServiceMixin** - Core dependency for most components 2. **Platform Services** - Ensure data access layer works 3. **Identity Components** - Verify core functionality 4. **Settings-Dependent Views** - Update in dependency order 5. **Utility Components** - Final cleanup and testing ## Deferred for depth - Advanced identity management features enabled by this change - Performance optimization strategies for the new table structure - Future schema evolution planning - Advanced rollback and recovery procedures