17 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 ✅ READY
- Add migration to MIGRATIONS array in
src/db-sql/migration.ts
- Create active_identity table with constraints
Phase 2: API Layer Updates ✅ COMPLETE
- Implement
$getActiveIdentity()
with validation - Update
$accountSettings()
to use new method - Update
$updateActiveDid()
with dual-write pattern
Status: Successfully implemented dual-write pattern with validation. Ready for testing.
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:
- Systematic Replacement: Use grep search to find all instances
- Pattern Matching: Replace
this.activeDid = settings.activeDid
with new pattern - Error Handling: Ensure proper error handling in each component
- 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)
-
✅ Migration code exists in MIGRATIONS array
- Time: 2025-08-29T08:03Z
- Evidence:
src/db-sql/migration.ts:125
-003_active_did_separate_table
migration defined - Verify at: Migration script contains proper table creation and constraints
-
✅ Current activeDid storage in settings table works
- 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 (reverted to working state)
- Time: 2025-08-29T08:03Z
- Evidence:
src/utils/PlatformServiceMixin.ts:108
- activeDid tracking restored after test failures - Verify at: Component usage across all platforms works again after reversion
-
✅ Database migration infrastructure exists and is mature
- 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)
-
❌ Database state unknown - IndexedDB database not inspected
- Time: 2025-08-29T08:03Z
- Evidence: Tests failed before application could start and initialize IndexedDB database
- Hypothesis: Database may or may not exist in IndexedDB, migration may or may not have run
- Next probe: Start application in browser and inspect IndexedDB storage via DevTools
-
❌ active_identity table status unknown in IndexedDB database
- Time: 2025-08-29T08:03Z
- 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
-
❌ 35+ components still use old pattern
this.activeDid = settings.activeDid
- Time: 2025-08-29T08:03Z
- 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
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) | Priority |
---|---|---|---|---|
Development Team | CRITICAL: Start application in browser | Application loads and initializes IndexedDB | 2025-08-29 | 🔴 HIGH |
Development Team | Inspect IndexedDB via DevTools | Verify database exists and check for active_identity table | 2025-08-29 | 🔴 HIGH |
Development Team | Implement $getActiveIdentity() method | Method added to PlatformServiceMixin | 2025-08-30 | 🟡 MEDIUM |
Development Team | Update $accountSettings method | Method updated and tested | 2025-08-30 | 🟡 MEDIUM |
Development Team | Update $updateActiveDid method | Method updated with dual-write pattern | 2025-08-30 | 🟡 MEDIUM |
Development Team | Update 35+ components | All components use new API | 2025-08-31 | 🟢 LOW |
QA Team | Platform testing | All platforms tested and verified | 2025-09-01 | 🟢 LOW |
Development Team | Deploy migration | Production deployment successful | 2025-09-02 | 🟢 LOW |
Critical Blocker: Need to start application in browser to check if IndexedDB database exists and migration has run.
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