# Migration Verification Report

**Environment**: Node.js v25.9.0 | MySQL 9.7.1 | Database: `topiq`  
**Execution Date**: 2026-10-09  
**Status**: Verification Completed (Read-only on primary database, isolated tests on disposable database)

---

## 1. Migration Files Discovered

The version-controlled migration directory (`migrations/`) was inspected. The runner applies deterministic sorting (`.endsWith('.sql')` sorted alphabetically):

1. `migrations/001_baseline.sql` (Size: 2,776 bytes)
   - **SHA-256 Checksum**: `43a77bea004826847abffcdac3b249ed493fc8fbafa099197734d21837f0949e`
   - **Purpose**: Defines foundational schema (`users`, `areas`, `asm_profiles`, `coordinator_profiles`, `student_profiles`, `refresh_sessions`, `referral_codes`).
2. `migrations/002_add_referral_events_classes_audit.sql` (Size: 1,434 bytes)
   - **SHA-256 Checksum**: `8311533d6dab533f134b59ccb75e198d8623a4b07bec47e21008ac31f015eb03`
   - **Purpose**: Defines secondary structures (`classes`, `referral_events`, `otp_challenges`, `audit_logs`).

*Note: Legacy scripts (`init-db.js`, `setup-tables.js`, `setup-auth-tables.js`) residing in `migrations/` were ignored by the `.sql` filter as intended and left unmodified.*

---

## 2. Migration Status Output

Execution of `npm run migration:status` (`node scripts/migration-status.js`):

```text
=== Database Migration Status ===

┌─────────┬────┬─────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────┬──────────────────────────┐
│ (index) │ id │ filename                                    │ checksum                                                           │ executed_at              │
├─────────┼────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┼──────────────────────────┤
│ 0       │ 1  │ '001_baseline.sql'                          │ '43a77bea004826847abffcdac3b249ed493fc8fbafa099197734d21837f0949e' │ 2026-10-09T08:47:44.000Z │
│ 1       │ 2  │ '002_add_referral_events_classes_audit.sql' │ '8311533d6dab533f134b59ccb75e198d8623a4b07bec47e21008ac31f015eb03' │ 2026-10-09T08:47:44.000Z │
└─────────┴────┴─────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────┴──────────────────────────┘
```

Both migrations are confirmed applied, with stored checksums matching current file contents exactly.

---

## 3. Verification Categories

### A. VERIFIED

1. **Deterministic Execution Order**:
   - Migrations are sorted using standard string collation, ensuring `001_...` runs before `002_...`.
2. **SHA-256 Immutability & Modification Detection**:
   - **Test**: Modified checksum record in disposable database `topiq_test_verify`.
   - **Result**: Runner aborted immediately with exit code 1 and logged:
     `CRITICAL: Checksum mismatch for already applied migration: 001_baseline.sql`.
3. **Idempotence / Re-execution Protection**:
   - **Test**: Ran `migrate.js` against an already fully migrated database (`topiq_test_verify`).
   - **Result**: Runner identified both migrations as previously executed, skipped them safely, and logged:
     `Skipping applied migration: 001_baseline.sql`
     `Skipping applied migration: 002_add_referral_events_classes_audit.sql`.
4. **Untracked Database Preflight Detection**:
   - **Test**: Simulated an existing database with table `users` present but `migration_history` missing (`topiq_test_untracked`).
   - **Result**: Runner identified the discrepancy, prevented execution, exited with code 1, and logged:
     `CRITICAL: Existing non-tracked database detected. The users table exists, but migration_history does not.`
5. **Advisory Lock Concurrency Protection**:
   - **Test**: Acquired lock `topiq_migration_lock` in a parent process connection and spawned `scripts/migrate.js`.
   - **Result**: `migrate.js` blocked on `SELECT GET_LOCK('topiq_migration_lock', 10)` until timeout, preventing parallel migration execution.
6. **Fresh Database Execution**:
   - **Test**: Executed migrations against a clean, newly created disposable database (`topiq_test_verify`).
   - **Result**: Created all 11 application tables and `migration_history`, and exited with status 0.
7. **Referential Integrity Constraints**:
   - **Duplicate Mobile/Email**: Verified rejection via `users.mobile_normalized` unique index.
   - **Area Orphan Prevention**: Verified rejection when creating an `asm_profiles` entry with an invalid `area_id`.
   - **Hierarchy Cascade vs Restrict**:
     - Verified `users` deletion cascades to `coordinator_profiles` if no students exist.
     - Verified `users` deletion of a Coordinator is strictly blocked (`ON DELETE RESTRICT`) when active `student_profiles` records are linked to that coordinator.

---

### B. FAILED

None. All safety gates and constraint validations functioned as designed.

---

### C. NOT TESTED

1. **Production Deployment Under Active Load**:
   - Tested exclusively in local development environment against MySQL 9.7.1. Production load, connection pool starvation, and remote network latency were not tested.
2. **Application OTP Lifecycle Endpoints**:
   - The `otp_challenges` table schema was verified, but API endpoints for generating/consuming OTPs were not tested (deferred per Phase 1 scope instructions).

---

### D. BLOCKED

None.

---

## 4. Existing Schema vs. Baseline Comparison

A complete read-only inspection of `information_schema` was performed on database `topiq`.

| Table | Status in Baseline | Column Count | Primary Key | Foreign Keys | Status |
|---|---|---|---|---|---|
| `users` | `001_baseline.sql` | 10 | `id` | None | Matched |
| `areas` | `001_baseline.sql` | 4 | `id` | `parent_area_id` -> `areas.id` | Matched |
| `asm_profiles` | `001_baseline.sql` | 4 | `user_id` | `user_id` -> `users.id`, `area_id` -> `areas.id` | Matched |
| `coordinator_profiles` | `001_baseline.sql` | 4 | `user_id` | `user_id` -> `users.id`, `asm_id` -> `users.id`, `area_id` -> `areas.id` | Matched |
| `student_profiles` | `001_baseline.sql` | 6 | `user_id` | `user_id` -> `users.id`, `coordinator_id` -> `users.id`, `asm_id` -> `users.id` | Matched |
| `refresh_sessions` | `001_baseline.sql` | 7 | `id` | `user_id` -> `users.id` | Matched |
| `referral_codes` | `001_baseline.sql` | 8 | `id` | `owner_user_id` -> `users.id` | Matched |
| `classes` | `002_add_referral_events...` | 4 | `id` | None | Matched |
| `referral_events` | `002_add_referral_events...` | 6 | `id` | `referral_code_id`, `referrer_user_id`, `referred_user_id` | Matched |
| `otp_challenges` | `002_add_referral_events...` | 7 | `id` | None | Matched |
| `audit_logs` | `002_add_referral_events...` | 6 | `id` | `actor_user_id` -> `users.id` | Matched |
| `migration_history` | Runner Internal | 4 | `id` | None | Matched |

**Row Counts in Current Database (`topiq`)**:
- All application tables currently contain **0 rows** (freshly initialized clean environment). No user data was altered.

---

## 5. Failure and Recovery Test Results

1. **Non-Transactional DDL in MySQL Engine**:
   - **Empirical Test**: Executed a transaction containing one valid DDL statement (`CREATE TABLE`) followed by an invalid syntax statement.
   - **Result**: Even after calling `rollback()`, the newly created table remained in MySQL due to implicit commits inherent to MySQL DDL operations.
   - **Implication**: If a multi-statement migration file fails on statement 2, statement 1 cannot be rolled back via MySQL transaction rollback. The runner correctly halts without recording the migration in `migration_history`. Recovery requires manual cleanup of the uncommitted partial DDL before retrying.

---

## 6. Issues Found & Recommended Smallest Safe Fixes (Status: Resolved in Step 4)

### Issue 1: Student `asm_id` Disagreement with Coordinator's ASM
- **Observation**: In `student_profiles`, `coordinator_id` and `asm_id` were separate foreign keys pointing to `users.id`.
- **Resolution (Step 4)**: Applied in `migrations/003_enforce_hierarchy_composite_fk.sql`:
  - Added unique index `uq_coordinator_asm` on `coordinator_profiles (user_id, asm_id)`.
  - Added composite foreign key `fk_student_coordinator_asm` on `student_profiles (coordinator_id, asm_id)` referencing `coordinator_profiles (user_id, asm_id)` with `ON DELETE RESTRICT`.

### Issue 2: Missing Foreign Key on `student_profiles.class_id`
- **Observation**: `student_profiles` had column `class_id INT NULL`, but lacked a foreign key constraint referencing `classes(id)`.
- **Resolution (Step 4)**: Applied in `migrations/003_enforce_hierarchy_composite_fk.sql`:
  - Added constraint `fk_student_class` referencing `classes(id)` with `ON DELETE SET NULL`.

### Issue 3: Exit Code in Runner `finally` Block
- **Observation**: `scripts/migrate.js` called `process.exit(0)` inside the `finally` block.
- **Resolution (Step 4)**: Refactored `scripts/migrate.js` to use an explicit `let exitCode = 0;` pattern, safe advisory lock release, and proper `pool.end()` lifecycle cleanup before exiting with the appropriate code.

---

## 7. Adoption Safety Assessment

**Conclusion**: The existing development database is **SAFE TO ADOPT**.
- The schema is clean and matches the version-controlled migrations 100%.
- Both `001_baseline.sql` and `002_add_referral_events_classes_audit.sql` are recorded in `migration_history` with exact SHA-256 checksums matching files on disk.
- There are no un-migrated tables or orphaned records.
- No schema changes were made during this verification step.

---

## 8. Commands Executed During Verification

```bash
# 1. MySQL Version and Read-Only Table Check
node -e "import('./src/config/db.js').then(async m => { ... })"

# 2. Check Migration Status
npm run migration:status

# 3. Read-Only Schema Inspection (information_schema)
node -e "import('./src/config/db.js').then(async m => { ... })"

# 4. Fresh Database Migration Test (topiq_test_verify)
DB_NAME=topiq_test_verify node scripts/migrate.js

# 5. Checksum Mismatch Detection Test
node -e "UPDATE migration_history SET checksum = 'altered_checksum'..."
DB_NAME=topiq_test_verify node scripts/migrate.js

# 6. Untracked Database Preflight Detection Test
node -e "CREATE DATABASE topiq_test_untracked; CREATE TABLE users..."
DB_NAME=topiq_test_untracked node scripts/migrate.js

# 7. Advisory Lock Concurrency Test
node -e "SELECT GET_LOCK('topiq_migration_lock', 0)..."

# 8. Automated Database Integrity Tests
npm run test
```
