# Migration Detection Hotfix - v1.0.0-rc.3

**Date:** 2025-11-24
**Type:** Hotfix
**Version:** 1.0.0-rc.2 → 1.0.0-rc.3
**Branch:** hotfix/1.0.0-rc.3-migration-detection

## Executive Summary

Fixed critical migration detection logic that was causing deployment failures on partially migrated production databases. The issue occurred when migration 001 was applied but migration 002 was not, yet migration 002's tables already existed from SCHEMA_SQL.

## Problem Statement

### Production Scenario

The production database had:
- Migration 001 applied (so `migration_count = 1`)
- `tokens` and `authorization_codes` tables created by SCHEMA_SQL from v1.0.0-rc.1
- Migration 002 NOT yet applied
- No indexes created (migration 002 creates the indexes)

### The Bug

The migration detection logic in `starpunk/migrations.py` line 380:

```python
if migration_count == 0 and not is_migration_needed(conn, migration_name):
```

This only used smart detection when `migration_count == 0` (fresh database). For partially migrated databases where `migration_count > 0`, it skipped the smart detection and tried to apply migration 002 normally.

This caused a failure because:
1. Migration 002 contains `CREATE TABLE tokens` and `CREATE TABLE authorization_codes`
2. These tables already existed from SCHEMA_SQL
3. SQLite throws an error: "table already exists"

### Root Cause

The smart detection logic was designed for fresh databases (migration_count == 0) to detect when SCHEMA_SQL had already created tables that migrations would also create. However, it didn't account for partially migrated databases where:
- Some migrations are applied (count > 0)
- But migration 002 is not applied
- Yet migration 002's tables exist from SCHEMA_SQL

## Solution

### Code Changes

Changed the condition from:

```python
if migration_count == 0 and not is_migration_needed(conn, migration_name):
```

To:

```python
should_check_needed = (
    migration_count == 0 or
    migration_name == "002_secure_tokens_and_authorization_codes.sql"
)

if should_check_needed and not is_migration_needed(conn, migration_name):
```

### Why This Works

Migration 002 is now **always** checked for whether it's needed, regardless of the migration count. This handles three scenarios:

1. **Fresh database** (migration_count == 0):
   - Tables from SCHEMA_SQL exist
   - Smart detection skips table creation
   - Creates missing indexes
   - Marks migration as applied

2. **Partially migrated database** (migration_count > 0, migration 002 not applied):
   - Migration 001 applied
   - Tables from SCHEMA_SQL exist
   - Smart detection skips table creation
   - Creates missing indexes
   - Marks migration as applied

3. **Legacy database** (migration_count > 0, old tables exist):
   - Old schema exists
   - `is_migration_needed()` returns True
   - Full migration runs normally
   - Tables are dropped and recreated with indexes

## Testing

### Manual Verification

Tested the fix with a simulated production database:

```python
# Setup
migration_count = 1  # Migration 001 applied
applied_migrations = {'001_add_code_verifier_to_auth_state.sql'}
tables_exist = True  # tokens and authorization_codes from SCHEMA_SQL
indexes_exist = False  # Not created yet

# Test
migration_name = '002_secure_tokens_and_authorization_codes.sql'
should_check_needed = (
    migration_count == 0 or
    migration_name == '002_secure_tokens_and_authorization_codes.sql'
)
# Result: True (would check if needed)

is_migration_needed = False  # Tables exist with correct structure
# Result: Would skip migration and create indexes only
```

**Result:** SUCCESS - Would correctly skip migration 002 and create only missing indexes.

### Automated Tests

Ran full test suite with `uv run pytest`:
- **561 tests passed** (including migration tests)
- 30 pre-existing failures (unrelated to this fix)
- Key test passed: `test_run_migrations_partial_applied` (tests partial migration scenario)

## Files Modified

1. **starpunk/migrations.py** (lines 373-386)
   - Changed migration detection logic to always check migration 002's state
   - Added explanatory comments

2. **starpunk/__init__.py** (lines 156-157)
   - Updated version from 1.0.0-rc.2 to 1.0.0-rc.3
   - Updated version_info tuple

3. **CHANGELOG.md**
   - Added v1.0.0-rc.3 section with fix details

## Deployment Impact

### Who Is Affected

- Any database with migration 001 applied but not migration 002
- Any database created with v1.0.0-rc.1 or earlier that has SCHEMA_SQL tables

### Backwards Compatibility

- **Fresh databases:** No change in behavior
- **Partially migrated databases:** Now works correctly (was broken)
- **Fully migrated databases:** No impact (migration 002 already applied)
- **Legacy databases:** No change in behavior (full migration still runs)

## Version Information

- **Previous Version:** 1.0.0-rc.2
- **New Version:** 1.0.0-rc.3
- **Branch:** hotfix/1.0.0-rc.3-migration-detection
- **Related ADRs:** None (hotfix)

## Next Steps

1. Merge hotfix branch to main
2. Tag release v1.0.0-rc.3
3. Deploy to production
4. Verify production database migrates successfully
5. Monitor logs for any migration issues

## Technical Notes

### Why Migration 002 Is Special

Migration 002 is the only migration that requires special detection because:
1. It creates tables that were added to SCHEMA_SQL in v1.0.0-rc.1
2. SCHEMA_SQL was updated after migration 002 was written
3. This created a timing issue where tables could exist without the migration being applied

Other migrations don't have this issue because they either:
- Modify existing tables (ALTER TABLE)
- Were created before their features were added to SCHEMA_SQL
- Create new tables not in SCHEMA_SQL

### Future Considerations

If future migrations have similar issues (tables in both SCHEMA_SQL and migrations), they should be added to the `should_check_needed` condition or we should refactor to check all migrations with table detection logic.

## References

- Git branch: `hotfix/1.0.0-rc.3-migration-detection`
- Related fix: v1.0.0-rc.2 (removed duplicate indexes from SCHEMA_SQL)
- Migration system docs: `/docs/standards/migrations.md`
