Browse Source

fix: Resolve database migration issues and consolidate 003 migrations

- Fix $getActiveIdentity() logic flow preventing false "empty table" warnings
- Implement auto-selection of first account when activeDid is null after migration
- Consolidate 003 and 003b migrations into single 003 migration
- Re-introduce foreign key constraint for activeDid referential integrity
- Add comprehensive debug logging for migration troubleshooting
- Remove 003b validation logic and update migration name mapping

Fixes migration from master to active_did_redux branch and ensures system
always has valid activeDid for proper functionality.
pull/188/head
Matthew Raymer 5 days ago
parent
commit
616bef655a
  1. 16
      src/db-sql/migration.ts
  2. 67
      src/services/migrationService.ts
  3. 42
      src/utils/PlatformServiceMixin.ts

16
src/db-sql/migration.ts

@ -145,17 +145,25 @@ const MIGRATIONS = [
id INTEGER PRIMARY KEY CHECK (id = 1),
activeDid TEXT DEFAULT NULL, -- NULL instead of empty string
lastUpdated TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE RESTRICT
FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE SET NULL
);
-- Add performance indexes
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id);
-- Seed singleton row
INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, NULL, datetime('now'));
-- Seed singleton row (only if not already exists)
-- Use a more explicit approach to ensure the row gets inserted
INSERT INTO active_identity (id, activeDid, lastUpdated)
SELECT 1, NULL, datetime('now')
WHERE NOT EXISTS (SELECT 1 FROM active_identity WHERE id = 1);
-- Add hasBackedUpSeed field to settings (from registration-prompt-parity)
-- Add hasBackedUpSeed field to settings (consolidated from 003b)
-- This may fail if column already exists from master branch migration
-- The error handling will catch this and mark migration as applied
ALTER TABLE settings ADD COLUMN hasBackedUpSeed BOOLEAN DEFAULT FALSE;
-- Debug: Verify the row was inserted
SELECT 'DEBUG: Row count after insertion' as debug_message, COUNT(*) as row_count FROM active_identity;
`,
},
];

67
src/services/migrationService.ts

@ -280,7 +280,7 @@ async function validateMigrationApplication<T>(
error,
);
}
} else if (migration.name === "003_active_did_separation") {
} else if (migration.name === "003_active_identity_and_seed_backup") {
// Validate active_identity table exists and has correct structure
try {
// Check that active_identity table exists
@ -322,6 +322,22 @@ async function validateMigrationApplication<T>(
error,
);
}
} else if (migration.name === "003b_add_hasBackedUpSeed_to_settings") {
// Validate hasBackedUpSeed column exists in settings table
try {
await sqlQuery(`SELECT hasBackedUpSeed FROM settings LIMIT 1`);
validation.isValid = true;
validation.hasExpectedColumns = true;
} catch (error) {
validation.isValid = false;
validation.errors.push(
`Column hasBackedUpSeed missing from settings table`,
);
logger.error(
`❌ [Migration-Validation] Column hasBackedUpSeed missing:`,
error,
);
}
}
// Add validation for future migrations here
@ -385,7 +401,7 @@ async function isSchemaAlreadyPresent<T>(
// Reduced logging - only log on error
return false;
}
} else if (migration.name === "003_active_did_separation") {
} else if (migration.name === "003_active_identity_and_seed_backup") {
// Check if active_identity table exists and has correct structure
try {
// Check that active_identity table exists
@ -496,14 +512,32 @@ export async function runMigrations<T>(
);
`);
// Step 2: Get list of already applied migrations
// Step 2: Handle migration name changes (master branch compatibility)
// Map old migration names to new ones
const migrationNameMap = new Map([
// No longer needed - migrations consolidated into single 003
]);
// Update any old migration names to new ones
for (const [oldName, newName] of migrationNameMap) {
try {
await sqlExec("UPDATE migrations SET name = ? WHERE name = ?", [newName, oldName]);
if (await sqlQuery("SELECT 1 FROM migrations WHERE name = ? LIMIT 1", [newName])) {
migrationLog(`🔄 [Migration] Renamed migration: ${oldName}${newName}`);
}
} catch (error) {
// Ignore errors - migration might not exist
}
}
// Step 3: Get list of already applied migrations
const appliedMigrationsResult = await sqlQuery(
"SELECT name FROM migrations",
);
const appliedMigrations = extractMigrationNames(appliedMigrationsResult);
// Step 3: Get all registered migrations
// Step 4: Get all registered migrations
const migrations = migrationRegistry.getMigrations();
if (migrations.length === 0) {
@ -518,7 +552,7 @@ export async function runMigrations<T>(
let appliedCount = 0;
let skippedCount = 0;
// Step 4: Process each migration
// Step 5: Process each migration
for (const migration of migrations) {
// Check 1: Is it recorded as applied in migrations table?
const isRecordedAsApplied = appliedMigrations.has(migration.name);
@ -557,7 +591,26 @@ export async function runMigrations<T>(
try {
// Execute the migration SQL
await sqlExec(migration.sql);
migrationLog(`🔧 [Migration] Executing SQL for: ${migration.name}`);
migrationLog(`🔧 [Migration] SQL content: ${migration.sql}`);
const execResult = await sqlExec(migration.sql);
migrationLog(`🔧 [Migration] SQL execution result: ${JSON.stringify(execResult)}`);
// Debug: Check if active_identity table exists and has data
if (migration.name === "003_active_identity_and_seed_backup") {
try {
const tableCheck = await sqlQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='active_identity'");
migrationLog(`🔍 [Migration] Table check result: ${JSON.stringify(tableCheck)}`);
const rowCount = await sqlQuery("SELECT COUNT(*) as count FROM active_identity");
migrationLog(`🔍 [Migration] Row count in active_identity: ${JSON.stringify(rowCount)}`);
const allRows = await sqlQuery("SELECT * FROM active_identity");
migrationLog(`🔍 [Migration] All rows in active_identity: ${JSON.stringify(allRows)}`);
} catch (error) {
migrationLog(`❌ [Migration] Debug query failed: ${JSON.stringify(error)}`);
}
}
// Validate the migration was applied correctly
const validation = await validateMigrationApplication(
@ -606,6 +659,8 @@ export async function runMigrations<T>(
`⚠️ [Migration] Schema validation failed for ${migration.name}:`,
validation.errors,
);
// Don't mark as applied if validation fails
continue;
}
// Mark the migration as applied since the schema change already exists

42
src/utils/PlatformServiceMixin.ts

@ -668,6 +668,42 @@ export const PlatformServiceMixin = {
{ activeDid },
);
// Handle null activeDid (initial state after migration)
if (activeDid === null) {
logger.debug(
"[PlatformServiceMixin] Active identity is null (initial state), attempting auto-selection",
);
// Try to auto-select first available account
const availableAccounts = await this.$dbQuery(
"SELECT did FROM accounts ORDER BY dateCreated, did LIMIT 1",
);
if (availableAccounts?.values?.length) {
const firstAccountDid = availableAccounts.values[0][0] as string;
logger.debug(
"[PlatformServiceMixin] Auto-selecting first account as active:",
{ firstAccountDid },
);
// Update active_identity table with the first account
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[firstAccountDid],
);
logger.debug(
"[PlatformServiceMixin] Active identity auto-selected successfully",
);
return { activeDid: firstAccountDid };
} else {
logger.warn(
"[PlatformServiceMixin] No accounts available for auto-selection",
);
return { activeDid: "" };
}
}
// Validate activeDid exists in accounts
if (activeDid) {
const accountExists = await this.$dbQuery(
@ -690,6 +726,12 @@ export const PlatformServiceMixin = {
);
return { activeDid: "" };
}
} else {
// activeDid is empty string, return it
logger.debug(
"[PlatformServiceMixin] Active identity is empty string, returning as-is",
);
return { activeDid: "" };
}
}

Loading…
Cancel
Save