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-31T03:34Z Status: 🎯 IMPLEMENTATION - API Layer Incomplete

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 READY

  • Add migration to MIGRATIONS array in src/db-sql/migration.ts
  • Create active_identity table with constraints
  • Include data migration from settings to active_identity table

Phase 2: API Layer Updates INCOMPLETE

  • Implement $getActiveIdentity() method (exists but wrong return type)
  • Fix $getActiveIdentity() return type to match documented interface
  • Update $accountSettings() to use new method
  • Update $updateActiveDid() with dual-write pattern

Status: Return type fixed. Method now returns { activeDid: string } as documented. Need to update other API methods.

Phase 3: Component Updates BLOCKED

  • Update 35+ components to use $getActiveIdentity()
  • Replace this.activeDid = settings.activeDid pattern
  • Test each component individually

Status: Blocked until API layer is complete. 35 components identified via grep search.

Phase 4: Testing NOT STARTED

  • Test all platforms (Web, Electron, iOS, Android)
  • Test migration rollback scenarios
  • Test data corruption recovery

Required Code Changes

1. Database Migration COMPLETE

// Already added 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. Fix $getActiveIdentity() Return Type

// Update in PlatformServiceMixin.ts - Change return type to match documented interface
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. Update $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. Update $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
}

What Works (Evidence)

  • Migration code exists in MIGRATIONS array

    • Time: 2025-08-31T03:34Z
    • Evidence: src/db-sql/migration.ts:125 - 003_active_did_separate_table migration defined
    • Verify at: Migration script contains proper table creation and data migration
  • $getActiveIdentity() method exists in PlatformServiceMixin

    • Time: 2025-08-31T03:34Z
    • Evidence: src/utils/PlatformServiceMixin.ts:555 - Method implemented with correct return type
    • Verify at: Method returns { activeDid: string } as documented
  • Database migration infrastructure exists and mature

    • Time: 2025-08-31T03:34Z
    • Evidence: src/db-sql/migration.ts:31 - migration system in place
    • Verify at: Existing migration scripts and database versioning

What Doesn't (Evidence & Hypotheses)

  • $getActiveIdentity() return type fixed - now returns { activeDid: string } as documented

    • Time: 2025-08-31T03:34Z
    • Evidence: src/utils/PlatformServiceMixin.ts:555 - Method updated with correct return type
    • Status: Ready for use in component updates
  • $accountSettings() not updated to use new $getActiveIdentity() method

    • Time: 2025-08-31T03:34Z
    • Evidence: src/utils/PlatformServiceMixin.ts:817 - Method still uses legacy pattern
    • Hypothesis: Components will continue using old activeDid from settings
    • Next probe: Update $accountSettings to call $getActiveIdentity and combine results
  • $updateActiveDid() not implemented with dual-write pattern

    • Time: 2025-08-31T03:34Z
    • Evidence: src/utils/PlatformServiceMixin.ts:200 - Method only updates internal tracking
    • Hypothesis: Database updates not happening, only in-memory changes
    • Next probe: Implement dual-write to both active_identity and settings tables
  • 35 components still use old pattern this.activeDid = settings.activeDid

    • Time: 2025-08-31T03:34Z
    • Evidence: Grep search found 35 instances across views and components
    • Hypothesis: Components need updates but are blocked until API layer is ready
    • Next probe: Update components after API layer is implemented
  • Database state unknown - IndexedDB database not inspected

    • Time: 2025-08-31T03:34Z
    • Evidence: Cannot check IndexedDB database without running application
    • Hypothesis: Migration exists in code but may not have run in IndexedDB
    • Next probe: Start application and check IndexedDB for active_identity table

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

Task Exit Criteria Priority
Fix $getActiveIdentity() return type Method returns { activeDid: string } as documented COMPLETE
Update $accountSettings() method Method calls $getActiveIdentity and combines with settings 🔴 HIGH
Implement $updateActiveDid() dual-write Method updates both active_identity and settings tables 🔴 HIGH
Start application in browser Application loads and initializes IndexedDB database 🟡 MEDIUM
Inspect IndexedDB via DevTools Verify active_identity table exists and contains data 🟡 MEDIUM
Update first component One component successfully uses new API pattern 🟢 LOW
Systematic component updates All 35 components use new API pattern 🟢 LOW

Critical Blocker: Need to update $accountSettings() and $updateActiveDid() methods before component updates can proceed.

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: Method signature mismatches, forgetting dual-write pattern, not testing database state
  • Next skill unlock: Systematic API updates with backward compatibility
  • Teach-back: Explain why dual-write pattern is needed during migration transition

Collaboration Hooks

  • Reviewers: Database team, PlatformServiceMixin maintainers, QA team
  • Sign-off checklist:
    • Migration script integrated with existing MIGRATIONS array
    • $getActiveIdentity() method returns correct type
    • $accountSettings() method updated to use new API
    • $updateActiveDid() method implements dual-write pattern
    • All 35+ components updated to use new API
    • Rollback procedures validated
    • All platforms tested
    • All stakeholders approve deployment timeline