You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

16 KiB

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

  • Add migration to MIGRATIONS array
  • 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

// 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'));
  `,
},

2. Missing API Method Implementation

// 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

// Update in PlatformServiceMixin.ts
async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings> {
  try {
    // Get settings without activeDid (unchanged logic)
    const settings = await this.$getMasterSettings(defaults);

    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. Updated $updateActiveDid Method

// Update in PlatformServiceMixin.ts
async $updateActiveDid(newDid: string | null): Promise<boolean> {
  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
    await this._updateInternalActiveDid(newDid);
    return true;
  } catch (error) {
    logger.error("[PlatformServiceMixin] Error updating activeDid:", error);
    return false;
  }
}

5. Component Updates Required

35 components need this pattern change:

// 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 (28 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 (7 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:

// 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):

// 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
}

6. Data Migration Function

// Add to migration.ts
async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
  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) {
    result.errors.push(`Migration failed: ${error}`);
    logger.error("[ActiveDid Migration] Critical error during migration:", error);
  }

  return result;
}

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

-- If migration fails, restore original schema
DROP TABLE IF EXISTS active_identity;

Data Rollback

// Rollback function to restore activeDid to settings table
async function rollbackActiveDidMigration(): Promise<boolean> {
  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:

// 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

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