Browse Source

chore: update plan for handling MASTER_SETTINGS_KEY

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

253
doc/activeDid-migration-plan.md

@ -1,7 +1,7 @@
# ActiveDid Migration Plan - Separate Table Architecture
**Author**: Matthew Raymer
**Date**: 2025-08-29T07:24Z
**Date**: 2025-08-29T08:03Z
**Status**: 🎯 **PLANNING** - Active migration planning phase
## Objective
@ -85,29 +85,35 @@ flowchart TD
## Repro: End-to-End Procedure
### Phase 1: Enhanced Schema Creation
### Phase 1: Enhanced Schema Creation via migration.ts
```sql
-- Create new active_identity table with proper constraints
CREATE TABLE active_identity (
```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 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);
-- 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 INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
-- Insert default record (will be updated during migration)
INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
`,
},
```
### Phase 2: Enhanced Data Migration with Validation
```typescript
// Enhanced migration script with comprehensive validation
// Enhanced migration function with comprehensive validation
async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
const result: MigrationResult = {
success: false,
@ -117,7 +123,7 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
};
try {
// 1. Get current activeDid from settings
// 1. Get current activeDid from settings (legacy approach)
const currentSettings = await retrieveSettingsForDefaultAccount();
const activeDid = currentSettings.activeDid;
@ -137,27 +143,20 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
return result;
}
// 3. Check if active_identity table already has data
const existingActiveIdentity = await dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
if (existingActiveIdentity?.values?.length) {
// Update existing record
// 3. Update active_identity table (new system)
await dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[activeDid]
);
} else {
// Insert new record
// 4. Ensure legacy settings.activeDid stays in sync (backward compatibility)
// This maintains compatibility with IndexedDB migration service
await dbExec(
"INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, ?, datetime('now'))",
[activeDid]
"UPDATE settings SET activeDid = ? WHERE id = ?",
[activeDid, MASTER_SETTINGS_KEY]
);
}
result.success = true;
result.dataMigrated = 1;
dataMigrated = 1;
result.warnings.push(`Successfully migrated activeDid: ${activeDid}`);
} catch (error) {
@ -167,17 +166,9 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
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
// Updated PlatformServiceMixin method - maintains backward compatibility
@ -201,93 +192,20 @@ async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings>
}
}
// New method for active identity management
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
// Enhanced update activeDid method with dual-write pattern
async $updateActiveDid(newDid: string | null): Promise<boolean> {
try {
if (newDid === null) {
// Clear active identity
// 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(
@ -300,11 +218,17 @@ async $updateActiveDid(newDid: string | null): Promise<boolean> {
return false;
}
// Update active identity
// 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
@ -431,30 +355,30 @@ async $getMergedSettings(defaultKey: string, accountDid?: string, defaultFallbac
## What Works (Evidence)
- ✅ **Current activeDid storage** in settings table
- **Time**: 2025-08-29T07:24Z
- **Evidence**: `src/db/tables/settings.ts:25` - activeDid field exists
- **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-29T07:24Z
- **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-29T07:24Z
- **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-29T07:24Z
- **Time**: 2025-08-29T08:03Z
- **Evidence**: Database schema only shows settings table
- **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
- **Time**: 2025-08-29T07:24Z
- **Time**: 2025-08-29T08:03Z
- **Evidence**: `IdentitySwitcherView.vue:175` - `hasCorruptedIdentity` detection
- **Hypothesis**: Current schema allows activeDid to point to non-existent accounts
- **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) |
|-------|------|---------------|-------------------|
| 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 PlatformServiceMixin | Core methods updated and tested | 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
- **Sign-off checklist**:
- [ ] Migration script tested on development database
- [ ] Migration script integrated with existing MIGRATIONS array
- [ ] Foreign key constraints implemented and tested
- [ ] PlatformServiceMixin updated and tested
- [ ] Rollback procedures validated
@ -571,31 +495,33 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
## 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
- 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**
- `$accountSettings()` - integrate with new table
- `$saveSettings()` - handle activeDid in new table
- `$updateActiveDid()` - validate and update new table
- `$accountSettings()` - integrate with new table while maintaining backward compatibility
- `$saveSettings()` - handle activeDid in new table, sync with legacy field
- `$updateActiveDid()` - validate and update new table, sync with legacy field
- `$getActiveIdentity()` - new method for identity management
### **3. Master Settings Functions**
- `retrieveSettingsForDefaultAccount()` - integrate with new table
- `$getMergedSettings()` - integrate with new table
- `retrieveSettingsForDefaultAccount()` - integrate with new table while preserving legacy support
- `$getMergedSettings()` - integrate with new table while preserving legacy support
### **4. Migration Scripts**
- Create migration script with validation
- Implement rollback procedures
- Add data corruption detection
### **5. Type Definitions**
- Update Settings type to remove activeDid
### **4. Type Definitions**
- **Keep `activeDid` in Settings type temporarily** for backward compatibility
- Create ActiveIdentity type for new table
- 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
- **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
- **Authentication flow** - Existing system unchanged
- **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