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