# ActiveDid Migration Plan - Implementation Guide **Author**: Matthew Raymer **Date**: 2025-08-29T08:03Z **Status**: 🎯 **IMPLEMENTATION** - Ready for development ## 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 provides the specific implementation steps required to complete the ActiveDid migration with all necessary code changes. ## Use/Run Follow this implementation checklist step-by-step to complete the migration. ## Context & Scope - **In scope**: Database migration, API updates, component updates, testing - **Out of scope**: UI changes, authentication flow changes, MASTER_SETTINGS_KEY elimination (future improvement) ## Implementation Checklist ### Phase 1: Database Migration ✅ - [x] Add migration to MIGRATIONS array - [x] Create active_identity table with constraints ### Phase 2: API Layer Updates ❌ - [ ] Implement `$getActiveIdentity()` method - [ ] Update `$accountSettings()` to use new table - [ ] Update `$updateActiveDid()` with dual-write pattern ### Phase 3: Component Updates ❌ - [ ] Update 35+ components to use `$getActiveIdentity()` - [ ] Replace `this.activeDid = settings.activeDid` pattern - [ ] Test each component individually ### Phase 4: Testing ❌ - [ ] Test all platforms (Web, Electron, iOS, Android) - [ ] Test migration rollback scenarios - [ ] Test data corruption recovery ## Required Code Changes ### 1. Database Migration ```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 != ''); `, }, ``` ### 2. Missing API Method Implementation ```typescript // Add to PlatformServiceMixin.ts async $getActiveIdentity(): Promise<{ activeDid: string }> { try { const result = await this.$dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (result?.values?.length) { const activeDid = result.values[0][0] as string; // Validate activeDid exists in accounts if (activeDid) { const accountExists = await this.$dbQuery( "SELECT did FROM accounts WHERE did = ?", [activeDid] ); if (accountExists?.values?.length) { return { activeDid }; } else { // Clear corrupted activeDid await this.$dbExec( "UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1" ); return { activeDid: "" }; } } } return { activeDid: "" }; } catch (error) { logger.error("[PlatformServiceMixin] Error getting active identity:", error); return { activeDid: "" }; } } ``` ### 3. Updated $accountSettings Method ```typescript // Update in PlatformServiceMixin.ts async $accountSettings(did?: string, defaults: Settings = {}): Promise { try { // Get settings without activeDid (unchanged logic) const settings = await this._getSettingsWithoutActiveDid(); if (!settings) { return defaults; } // Get activeDid from new table (new logic) const activeIdentity = await this.$getActiveIdentity(); // Return combined result (maintains backward compatibility) return { ...settings, activeDid: activeIdentity.activeDid }; } catch (error) { logger.error("[Settings Trace] ❌ Error in $accountSettings:", error); return defaults; } } ``` ### 4. Component Updates Required **35+ components need this pattern change:** ```typescript // CURRENT PATTERN (replace in all components): this.activeDid = settings.activeDid || ""; // NEW PATTERN (use in all components): const activeIdentity = await this.$getActiveIdentity(); this.activeDid = activeIdentity.activeDid || ""; ``` **Components requiring updates:** #### Views (25 components) - `src/views/DIDView.vue` (line 378) - `src/views/TestView.vue` (line 654) - `src/views/ContactAmountsView.vue` (line 226) - `src/views/HomeView.vue` (line 517) - `src/views/UserProfileView.vue` (line 185) - `src/views/ClaimView.vue` (line 730) - `src/views/OfferDetailsView.vue` (line 435) - `src/views/QuickActionBvcEndView.vue` (line 229) - `src/views/SharedPhotoView.vue` (line 178) - `src/views/ClaimReportCertificateView.vue` (line 56) - `src/views/ProjectsView.vue` (line 393) - `src/views/ClaimAddRawView.vue` (line 114) - `src/views/ContactQRScanShowView.vue` (line 288) - `src/views/InviteOneAcceptView.vue` (line 122) - `src/views/RecentOffersToUserView.vue` (line 118) - `src/views/NewEditProjectView.vue` (line 380) - `src/views/GiftedDetailsView.vue` (line 443) - `src/views/ProjectViewView.vue` (line 782) - `src/views/ContactsView.vue` (line 296) - `src/views/ContactQRScanFullView.vue` (line 267) - `src/views/NewActivityView.vue` (line 204) - `src/views/ClaimCertificateView.vue` (line 42) - `src/views/ContactGiftingView.vue` (line 166) - `src/views/RecentOffersToUserProjectsView.vue` (line 126) - `src/views/InviteOneView.vue` (line 285) - `src/views/IdentitySwitcherView.vue` (line 202) - `src/views/AccountViewView.vue` (line 1052) - `src/views/ConfirmGiftView.vue` (line 549) - `src/views/ContactImportView.vue` (line 342) #### Components (10 components) - `src/components/OfferDialog.vue` (line 177) - `src/components/PhotoDialog.vue` (line 270) - `src/components/GiftedDialog.vue` (line 223) - `src/components/MembersList.vue` (line 234) - `src/components/OnboardingDialog.vue` (line 272) - `src/components/ImageMethodDialog.vue` (line 502) - `src/components/FeedFilters.vue` (line 89) **Implementation Strategy:** 1. **Systematic Replacement**: Use grep search to find all instances 2. **Pattern Matching**: Replace `this.activeDid = settings.activeDid` with new pattern 3. **Error Handling**: Ensure proper error handling in each component 4. **Testing**: Test each component individually after update **Example Component Update:** ```typescript // BEFORE (in any component): private async initializeSettings() { const settings = await this.$accountSettings(); this.activeDid = settings.activeDid || ""; this.apiServer = settings.apiServer || ""; } // AFTER (in any component): private async initializeSettings() { const settings = await this.$accountSettings(); const activeIdentity = await this.$getActiveIdentity(); this.activeDid = activeIdentity.activeDid || ""; this.apiServer = settings.apiServer || ""; } ``` **Alternative Pattern (if settings still needed):** ```typescript // If component needs both settings and activeDid: private async initializeSettings() { const settings = await this.$accountSettings(); const activeIdentity = await this.$getActiveIdentity(); // Use activeDid from new table this.activeDid = activeIdentity.activeDid || ""; // Use other settings from settings table this.apiServer = settings.apiServer || ""; this.partnerApiServer = settings.partnerApiServer || ""; // ... other settings } ``` ### 5. Data Migration Function ```typescript // Add to migration.ts async function migrateActiveDidToSeparateTable(): Promise { const result: MigrationResult = { success: false, errors: [], warnings: [], dataMigrated: 0 }; try { // 1. Get current activeDid from settings (legacy approach) const currentSettings = await retrieveSettingsForDefaultAccount(); const activeDid = currentSettings.activeDid; if (!activeDid) { result.warnings.push("No activeDid found in current settings"); return result; } // 2. Validate activeDid exists in accounts table const accountExists = await dbQuery( "SELECT did FROM accounts WHERE did = ?", [activeDid] ); if (!accountExists?.values?.length) { result.errors.push(`ActiveDid ${activeDid} not found in accounts table - data corruption detected`); return result; } // 3. Update active_identity table (new system) await dbExec( "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", [activeDid] ); // 4. Ensure legacy settings.activeDid stays in sync (backward compatibility) await dbExec( "UPDATE settings SET activeDid = ? WHERE id = ?", [activeDid, MASTER_SETTINGS_KEY] ); result.dataMigrated = 1; result.warnings.push(`Successfully migrated activeDid: ${activeDid}`); } catch (error) { logger.error("[PlatformServiceMixin] Error getting active identity:", error); throw error; } } ``` ## What Works (Evidence) - ✅ **Current activeDid storage** in settings table - **Time**: 2025-08-29T08:03Z - **Evidence**: `src/db-sql/migration.ts:67` - activeDid field exists in initial migration - **Verify at**: Current database schema and Settings type definition - ✅ **PlatformServiceMixin integration** with activeDid - **Time**: 2025-08-29T08:03Z - **Evidence**: `src/utils/PlatformServiceMixin.ts:108` - activeDid tracking - **Verify at**: Component usage across all platforms - ✅ **Database migration infrastructure** exists - **Time**: 2025-08-29T08:03Z - **Evidence**: `src/db-sql/migration.ts:31` - migration system in place - **Verify at**: Existing migration scripts and database versioning ## What Doesn't (Evidence & Hypotheses) - ❌ **No separate active_identity table** exists - **Time**: 2025-08-29T08:03Z - **Evidence**: Database schema only shows settings table - **Hypothesis**: Table needs to be created as part of migration - **Next probe**: Add migration to existing MIGRATIONS array - ❌ **Missing $getActiveIdentity() method** in PlatformServiceMixin - **Time**: 2025-08-29T08:03Z - **Evidence**: Method referenced in plan but not implemented - **Hypothesis**: Method needs to be added to PlatformServiceMixin - **Next probe**: Implement method with proper error handling - ❌ **35+ components need updates** to use new API - **Time**: 2025-08-29T08:03Z - **Evidence**: Grep search found 35+ instances of `this.activeDid = settings.activeDid` - **Hypothesis**: All components need to be updated to use `$getActiveIdentity()` - **Next probe**: Update each component individually and test ## Risks, Limits, Assumptions - **Data Loss Risk**: Migration failure could lose activeDid values - **Breaking Changes**: API updates required in PlatformServiceMixin - **Testing Overhead**: All platforms must be tested with new structure - **Component Updates**: 35+ components need individual updates and testing ## Rollback Strategy ### Schema Rollback ```sql -- If migration fails, restore original schema DROP TABLE IF EXISTS active_identity; ``` ### Data Rollback ```typescript // Rollback function to restore activeDid to settings table async function rollbackActiveDidMigration(): Promise { try { const activeIdentityResult = await dbQuery( "SELECT activeDid FROM active_identity WHERE id = 1" ); if (activeIdentityResult?.values?.length) { const activeDid = activeIdentityResult.values[0][0] as string; await dbExec( "UPDATE settings SET activeDid = ? WHERE id = ?", [activeDid, MASTER_SETTINGS_KEY] ); return true; } return false; } catch (error) { logger.error("[Rollback] Failed to restore activeDid:", error); return false; } } ``` ## Next Steps | Owner | Task | Exit Criteria | Target Date (UTC) | |-------|------|---------------|-------------------| | Development Team | Add migration to MIGRATIONS array | Migration script integrated | 2025-08-30 | | Development Team | Implement $getActiveIdentity() method | Method added to PlatformServiceMixin | 2025-08-30 | | Development Team | Update $accountSettings method | Method updated and tested | 2025-08-30 | | Development Team | Update 35+ components | All components use new API | 2025-08-31 | | QA Team | Platform testing | All platforms tested and verified | 2025-09-01 | | Development Team | Deploy migration | Production deployment successful | 2025-09-02 | ## Future Improvement: MASTER_SETTINGS_KEY Elimination **Not critical for this task** but logged for future improvement: ```typescript // Current: WHERE id = "1" // Future: WHERE accountDid IS NULL // This eliminates the confusing concept of "master" settings // and uses a cleaner pattern for default settings ``` ## References - [Database Migration Guide](./database-migration-guide.md) - [Dexie to SQLite Mapping](./dexie-to-sqlite-mapping.md) - [PlatformServiceMixin Documentation](./component-communication-guide.md) ## Competence Hooks - *Why this works*: Separates concerns between identity selection and user preferences, prevents data corruption with foreign key constraints - *Common pitfalls*: Forgetting to update all 35+ components, not implementing $getActiveIdentity() method, missing data validation during migration - *Next skill unlock*: Systematic component updates with grep search and testing - *Teach-back*: Explain why all components need updates and how to systematically find and replace the pattern ## Collaboration Hooks - **Reviewers**: Database team, PlatformServiceMixin maintainers, QA team - **Sign-off checklist**: - [ ] Migration script integrated with existing MIGRATIONS array - [ ] $getActiveIdentity() method implemented and tested - [ ] All 35+ components updated to use new API - [ ] Rollback procedures validated - [ ] All platforms tested - [ ] All stakeholders approve deployment timeline