Browse Source

chore: update plan for handling MASTER_SETTINGS_KEY

pull/188/head
Matthew Raymer 3 weeks ago
parent
commit
fad7093fbd
  1. 241
      doc/activeDid-migration-plan.md

241
doc/activeDid-migration-plan.md

@ -1,7 +1,7 @@
# ActiveDid Migration Plan - Separate Table Architecture # ActiveDid Migration Plan - Separate Table Architecture
**Author**: Matthew Raymer **Author**: Matthew Raymer
**Date**: 2025-08-29T07:24Z **Date**: 2025-08-29T08:03Z
**Status**: 🎯 **PLANNING** - Active migration planning phase **Status**: 🎯 **PLANNING** - Active migration planning phase
## Objective ## Objective
@ -85,11 +85,15 @@ flowchart TD
## Repro: End-to-End Procedure ## Repro: End-to-End Procedure
### Phase 1: Enhanced Schema Creation ### Phase 1: Enhanced Schema Creation via migration.ts
```sql ```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 new active_identity table with proper constraints
CREATE TABLE active_identity ( CREATE TABLE IF NOT EXISTS active_identity (
id INTEGER PRIMARY KEY CHECK (id = 1), id INTEGER PRIMARY KEY CHECK (id = 1),
activeDid TEXT NOT NULL, activeDid TEXT NOT NULL,
lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), lastUpdated TEXT NOT NULL DEFAULT (datetime('now')),
@ -101,13 +105,15 @@ CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(acti
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id); CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id);
-- Insert default record (will be updated during migration) -- Insert default record (will be updated during migration)
INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now')); INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
`,
},
``` ```
### Phase 2: Enhanced Data Migration with Validation ### Phase 2: Enhanced Data Migration with Validation
```typescript ```typescript
// Enhanced migration script with comprehensive validation // Enhanced migration function with comprehensive validation
async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> { async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
const result: MigrationResult = { const result: MigrationResult = {
success: false, success: false,
@ -117,7 +123,7 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
}; };
try { try {
// 1. Get current activeDid from settings // 1. Get current activeDid from settings (legacy approach)
const currentSettings = await retrieveSettingsForDefaultAccount(); const currentSettings = await retrieveSettingsForDefaultAccount();
const activeDid = currentSettings.activeDid; const activeDid = currentSettings.activeDid;
@ -137,27 +143,20 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
return result; return result;
} }
// 3. Check if active_identity table already has data // 3. Update active_identity table (new system)
const existingActiveIdentity = await dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
if (existingActiveIdentity?.values?.length) {
// Update existing record
await dbExec( await dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[activeDid] [activeDid]
); );
} else {
// Insert new record // 4. Ensure legacy settings.activeDid stays in sync (backward compatibility)
// This maintains compatibility with IndexedDB migration service
await dbExec( await dbExec(
"INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, ?, datetime('now'))", "UPDATE settings SET activeDid = ? WHERE id = ?",
[activeDid] [activeDid, MASTER_SETTINGS_KEY]
); );
}
result.success = true; dataMigrated = 1;
result.dataMigrated = 1;
result.warnings.push(`Successfully migrated activeDid: ${activeDid}`); result.warnings.push(`Successfully migrated activeDid: ${activeDid}`);
} catch (error) { } catch (error) {
@ -167,17 +166,9 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
return result; return result;
} }
// Migration result interface
interface MigrationResult {
success: boolean;
errors: string[];
warnings: string[];
dataMigrated: number;
}
``` ```
### Phase 3: Focused API Updates ### Phase 3: Focused API Updates with Dual-Write Pattern
```typescript ```typescript
// Updated PlatformServiceMixin method - maintains backward compatibility // Updated PlatformServiceMixin method - maintains backward compatibility
@ -201,93 +192,20 @@ async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings>
} }
} }
// New method for active identity management // Enhanced update activeDid method with dual-write pattern
async $getActiveIdentity(): Promise<{ activeDid: string | null }> {
try {
const result = await this.$dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
if (!result?.values?.length) {
return { activeDid: null };
}
return { activeDid: result.values[0][0] as string };
} catch (error) {
logger.error("[Settings Trace] ❌ Failed to get active identity:", error);
return { activeDid: null };
}
}
// Enhanced method to get settings without activeDid
async _getSettingsWithoutActiveDid(): Promise<Settings> {
const result = await this.$dbQuery(
"SELECT id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible, " +
"finishedOnboarding, firstName, hideRegisterPromptOnNewContact, isRegistered, " +
"lastName, lastAckedOfferToUserJwtId, lastAckedOfferToUserProjectsJwtId, " +
"lastNotifiedClaimId, lastViewedClaimId, notifyingNewActivityTime, " +
"notifyingReminderMessage, notifyingReminderTime, partnerApiServer, " +
"passkeyExpirationMinutes, profileImageUrl, searchBoxes, showContactGivesInline, " +
"showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer, warnIfTestServer, " +
"webPushServer FROM settings WHERE id = ?",
[MASTER_SETTINGS_KEY]
);
if (!result?.values?.length) {
return DEFAULT_SETTINGS;
}
return this._mapColumnsToValues(result.columns, result.values)[0] as Settings;
}
// Enhanced save settings method
async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
try {
// Remove fields that shouldn't be updated
const { accountDid, id, activeDid, ...safeChanges } = changes;
if (Object.keys(safeChanges).length > 0) {
// Convert settings for database storage
const convertedChanges = this._convertSettingsForStorage(safeChanges);
const setParts: string[] = [];
const params: unknown[] = [];
Object.entries(convertedChanges).forEach(([key, value]) => {
if (value !== undefined) {
setParts.push(`${key} = ?`);
params.push(value);
}
});
if (setParts.length > 0) {
params.push(MASTER_SETTINGS_KEY);
await this.$dbExec(
`UPDATE settings SET ${setParts.join(", ")} WHERE id = ?`,
params,
);
}
}
// Handle activeDid separately in new table
if (changes.activeDid !== undefined) {
await this.$updateActiveDid(changes.activeDid);
}
return true;
} catch (error) {
logger.error("[PlatformServiceMixin] Error saving settings:", error);
return false;
}
}
// Enhanced update activeDid method
async $updateActiveDid(newDid: string | null): Promise<boolean> { async $updateActiveDid(newDid: string | null): Promise<boolean> {
try { try {
if (newDid === null) { if (newDid === null) {
// Clear active identity // Clear active identity in both tables
await this.$dbExec( await this.$dbExec(
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1" "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 { } else {
// Validate DID exists before setting // Validate DID exists before setting
const accountExists = await this.$dbQuery( const accountExists = await this.$dbQuery(
@ -300,11 +218,17 @@ async $updateActiveDid(newDid: string | null): Promise<boolean> {
return false; return false;
} }
// Update active identity // Update active identity in new table
await this.$dbExec( await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[newDid] [newDid]
); );
// Keep legacy field in sync (backward compatibility)
await this.$dbExec(
"UPDATE settings SET activeDid = ? WHERE id = ?",
[newDid, MASTER_SETTINGS_KEY]
);
} }
// Update internal tracking // Update internal tracking
@ -431,30 +355,30 @@ async $getMergedSettings(defaultKey: string, accountDid?: string, defaultFallbac
## What Works (Evidence) ## What Works (Evidence)
- ✅ **Current activeDid storage** in settings table - ✅ **Current activeDid storage** in settings table
- **Time**: 2025-08-29T07:24Z - **Time**: 2025-08-29T08:03Z
- **Evidence**: `src/db/tables/settings.ts:25` - activeDid field exists - **Evidence**: `src/db-sql/migration.ts:67` - activeDid field exists in initial migration
- **Verify at**: Current database schema and Settings type definition - **Verify at**: Current database schema and Settings type definition
- ✅ **PlatformServiceMixin integration** with activeDid - ✅ **PlatformServiceMixin integration** with activeDid
- **Time**: 2025-08-29T07:24Z - **Time**: 2025-08-29T08:03Z
- **Evidence**: `src/utils/PlatformServiceMixin.ts:108` - activeDid tracking - **Evidence**: `src/utils/PlatformServiceMixin.ts:108` - activeDid tracking
- **Verify at**: Component usage across all platforms - **Verify at**: Component usage across all platforms
- ✅ **Database migration infrastructure** exists - ✅ **Database migration infrastructure** exists
- **Time**: 2025-08-29T07:24Z - **Time**: 2025-08-29T08:03Z
- **Evidence**: `src/db-sql/migration.ts:31` - migration system in place - **Evidence**: `src/db-sql/migration.ts:31` - migration system in place
- **Verify at**: Existing migration scripts and database versioning - **Verify at**: Existing migration scripts and database versioning
## What Doesn't (Evidence & Hypotheses) ## What Doesn't (Evidence & Hypotheses)
- ❌ **No separate active_identity table** exists - ❌ **No separate active_identity table** exists
- **Time**: 2025-08-29T07:24Z - **Time**: 2025-08-29T08:03Z
- **Evidence**: Database schema only shows settings table - **Evidence**: Database schema only shows settings table
- **Hypothesis**: Table needs to be created as part of migration - **Hypothesis**: Table needs to be created as part of migration
- **Next probe**: Create migration script for new table - **Next probe**: Add migration to existing MIGRATIONS array
- ❌ **Data corruption issues** with orphaned activeDid references - ❌ **Data corruption issues** with orphaned activeDid references
- **Time**: 2025-08-29T07:24Z - **Time**: 2025-08-29T08:03Z
- **Evidence**: `IdentitySwitcherView.vue:175` - `hasCorruptedIdentity` detection - **Evidence**: `IdentitySwitcherView.vue:175` - `hasCorruptedIdentity` detection
- **Hypothesis**: Current schema allows activeDid to point to non-existent accounts - **Hypothesis**: Current schema allows activeDid to point to non-existent accounts
- **Next probe**: Implement foreign key constraints in new table - **Next probe**: Implement foreign key constraints in new table
@ -522,7 +446,7 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
| Owner | Task | Exit Criteria | Target Date (UTC) | | Owner | Task | Exit Criteria | Target Date (UTC) |
|-------|------|---------------|-------------------| |-------|------|---------------|-------------------|
| Development Team | Create enhanced migration script | Migration script with validation and rollback | 2025-08-30 | | Development Team | Add migration to existing MIGRATIONS array | Migration script integrated with existing system | 2025-08-30 |
| Development Team | Update type definitions | Settings type updated, ActiveIdentity type created | 2025-08-30 | | Development Team | Update type definitions | Settings type updated, ActiveIdentity type created | 2025-08-30 |
| Development Team | Update PlatformServiceMixin | Core methods updated and tested | 2025-08-31 | | Development Team | Update PlatformServiceMixin | Core methods updated and tested | 2025-08-31 |
| Development Team | Implement foreign key constraints | Schema validation prevents corruption | 2025-08-31 | | Development Team | Implement foreign key constraints | Schema validation prevents corruption | 2025-08-31 |
@ -552,7 +476,7 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
## Collaboration Hooks ## Collaboration Hooks
- **Sign-off checklist**: - **Sign-off checklist**:
- [ ] Migration script tested on development database - [ ] Migration script integrated with existing MIGRATIONS array
- [ ] Foreign key constraints implemented and tested - [ ] Foreign key constraints implemented and tested
- [ ] PlatformServiceMixin updated and tested - [ ] PlatformServiceMixin updated and tested
- [ ] Rollback procedures validated - [ ] Rollback procedures validated
@ -571,31 +495,33 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
## What Needs to Change ## What Needs to Change
### **1. Database Schema** ### **1. Database Schema via migration.ts**
- Add migration to existing MIGRATIONS array in `src/db-sql/migration.ts`
- Create `active_identity` table with foreign key constraints - Create `active_identity` table with foreign key constraints
- Add performance indexes - Add performance indexes
- Remove `activeDid` field from `settings` table - **Keep `activeDid` field in `settings` table temporarily** for backward compatibility
- **Preserve `MASTER_SETTINGS_KEY = "1"`** for legacy migration support
### **2. PlatformServiceMixin Methods** ### **2. PlatformServiceMixin Methods**
- `$accountSettings()` - integrate with new table - `$accountSettings()` - integrate with new table while maintaining backward compatibility
- `$saveSettings()` - handle activeDid in new table - `$saveSettings()` - handle activeDid in new table, sync with legacy field
- `$updateActiveDid()` - validate and update new table - `$updateActiveDid()` - validate and update new table, sync with legacy field
- `$getActiveIdentity()` - new method for identity management - `$getActiveIdentity()` - new method for identity management
### **3. Master Settings Functions** ### **3. Master Settings Functions**
- `retrieveSettingsForDefaultAccount()` - integrate with new table - `retrieveSettingsForDefaultAccount()` - integrate with new table while preserving legacy support
- `$getMergedSettings()` - integrate with new table - `$getMergedSettings()` - integrate with new table while preserving legacy support
### **4. Migration Scripts** ### **4. Type Definitions**
- Create migration script with validation - **Keep `activeDid` in Settings type temporarily** for backward compatibility
- Implement rollback procedures
- Add data corruption detection
### **5. Type Definitions**
- Update Settings type to remove activeDid
- Create ActiveIdentity type for new table - Create ActiveIdentity type for new table
- Update related interfaces - Update related interfaces
### **5. Legacy Compatibility**
- **Preserve `MASTER_SETTINGS_KEY = "1"`** for IndexedDB migration service
- **Maintain dual-write pattern** during transition period
- **Ensure legacy clients can still migrate** from Dexie to SQLite
## What Doesn't Need to Change ## What Doesn't Need to Change
- **All Vue components** - API layer handles migration transparently - **All Vue components** - API layer handles migration transparently
@ -603,3 +529,48 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
- **User interface** - No changes to identity selection UI - **User interface** - No changes to identity selection UI
- **Authentication flow** - Existing system unchanged - **Authentication flow** - Existing system unchanged
- **Component logic** - All activeDid handling through API methods - **Component logic** - All activeDid handling through API methods
- **Migration system** - Use existing migration.ts approach, not separate files
- **IndexedDB migration service** - Must continue working for legacy clients
## Enhanced Architecture: Dual-Write Pattern
### **Phase 1: Add New Table (Current)**
```typescript
// Create active_identity table
// Keep existing settings.activeDid for backward compatibility
// Use dual-write pattern during transition
```
### **Phase 2: Dual-Write Pattern**
```typescript
// When updating activeDid:
// 1. Update active_identity table (new system)
// 2. Update settings.activeDid (legacy compatibility)
// 3. Ensure both stay in sync
```
### **Phase 3: Future Cleanup (Not in Current Scope)**
```typescript
// Eventually:
// 1. Remove activeDid from settings table
// 2. Deprecate MASTER_SETTINGS_KEY
// 3. Use pure accountDid IS NULL pattern
// 4. Update IndexedDB migration service
```
## Backward Compatibility Requirements
### **Critical: IndexedDB Migration Service**
- **Must continue working** for users migrating from Dexie
- **Must recognize `id = "1"`** as master settings
- **Must preserve existing migration paths**
### **Important: Legacy Database Operations**
- **Must continue working** for existing SQLite databases
- **Must handle both old and new patterns**
- **Must not break existing queries**
### **Desired: Cleaner Architecture**
- **New operations** use `accountDid IS NULL` pattern
- **Legacy operations** continue using `MASTER_SETTINGS_KEY`
- **Gradual migration** toward cleaner patterns
Loading…
Cancel
Save