|
| 1 | +# Restore Database Foreign Keys Design |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Restore every foreign-key relationship removed or omitted by the normalized user-schema change so SQLAlchemy can configure all mappers, PostgreSQL enforces referential integrity, migrations remain safe for databases that already applied `e73c215d7221`, and `make seed` completes successfully. |
| 6 | + |
| 7 | +## Root Cause |
| 8 | + |
| 9 | +The current ORM models declare relationship properties but no `ForeignKey` metadata. SQLAlchemy therefore cannot infer joins such as `permissions.id` to `role_permissions.permission_id`, causing mapper configuration to fail before the seed query executes. |
| 10 | + |
| 11 | +The latest normalization migration was generated from that incomplete metadata. It drops six established constraints: |
| 12 | + |
| 13 | +- `permissions.resource_id` to `authorization_resources.id` |
| 14 | +- `role_permissions.role_id` to `roles.id` |
| 15 | +- `role_permissions.permission_id` to `permissions.id` |
| 16 | +- `todos.user_id` to `users.id` |
| 17 | +- `user_has_roles.user_id` to `users.id` |
| 18 | +- `user_has_roles.role_id` to `roles.id` |
| 19 | + |
| 20 | +It also creates seven normalized user tables whose `user_id` columns are `VARCHAR(36)` even though `users.id` is UUID, and omits their intended constraints. |
| 21 | + |
| 22 | +## Design |
| 23 | + |
| 24 | +### ORM metadata |
| 25 | + |
| 26 | +Each affected model column will declare a SQLAlchemy `ForeignKey` targeting the parent table. Existing relationship names, `back_populates` pairs, collection shapes, and ORM cascade settings remain unchanged. |
| 27 | + |
| 28 | +The seven normalized user tables will use `Mapped[UUID]` and UUID-backed columns for `user_id`: |
| 29 | + |
| 30 | +- `user_profiles` |
| 31 | +- `user_security` |
| 32 | +- `user_settings` |
| 33 | +- `user_contacts` |
| 34 | +- `user_addresses` |
| 35 | +- `user_verifications` |
| 36 | +- `user_sessions` |
| 37 | + |
| 38 | +The authorization junction tables, permission resource link, todo owner link, and user-role junction table will retain their existing UUID Python types while gaining their missing `ForeignKey` declarations. |
| 39 | + |
| 40 | +No database-level delete cascade will be introduced. This restores the relationships that existed before normalization without adding new deletion behavior. |
| 41 | + |
| 42 | +### Model discovery |
| 43 | + |
| 44 | +Alembic must load every normalized user model before reading `Base.metadata`. Its environment will import the user model package, which already exports all eight user-domain models, instead of importing only `UserModel` and `UserSessionModel`. This keeps migration comparison aligned with runtime metadata. |
| 45 | + |
| 46 | +### Corrective migration |
| 47 | + |
| 48 | +A new Alembic revision after `e73c215d7221` will repair databases where the normalization migration has already run. The existing revision will not be rewritten. |
| 49 | + |
| 50 | +The corrective upgrade will: |
| 51 | + |
| 52 | +1. Convert the seven normalized `user_id` columns from `VARCHAR(36)` to UUID using an explicit PostgreSQL cast. |
| 53 | +2. Recreate the six constraints dropped by `e73c215d7221`. |
| 54 | +3. Add the seven missing normalized-user constraints. |
| 55 | + |
| 56 | +The migration will use deterministic constraint names. If an existing normalized `user_id` contains a malformed UUID or references a missing user, PostgreSQL will reject the migration. The migration must fail visibly rather than discard, rewrite, or detach invalid data. |
| 57 | + |
| 58 | +The downgrade will drop the 13 constraints added by the corrective revision and convert the seven normalized `user_id` columns back to `VARCHAR(36)`, returning the schema to the exact state represented by `e73c215d7221`. |
| 59 | + |
| 60 | +### Runtime data flow |
| 61 | + |
| 62 | +After mapper configuration succeeds, seeding keeps its current transaction flow: seed authorization resources, roles, permissions, and junction records; then seed users, normalized profile data, and role assignments. The fix changes schema metadata and integrity enforcement only. Seed definitions and idempotency behavior remain unchanged. |
| 63 | + |
| 64 | +## Testing and Verification |
| 65 | + |
| 66 | +Regression tests will import the complete model graph and assert that: |
| 67 | + |
| 68 | +- `configure_mappers()` completes without `NoForeignKeysError`. |
| 69 | +- All 13 child columns expose the expected foreign-key target in `Base.metadata`. |
| 70 | +- The seven normalized `user_id` columns use UUID rather than string types. |
| 71 | + |
| 72 | +The implementation will then run focused tests, the full test suite, Ruff, and Alembic migration checks. Against the configured local PostgreSQL service, verification will upgrade through the corrective revision and run `make seed`. A disposable database or reversible upgrade/downgrade cycle will be used for migration verification so application data is not destroyed. |
| 73 | + |
| 74 | +## Scope |
| 75 | + |
| 76 | +This change restores all affected foreign keys and corrects the normalized user identifier types. It does not change domain entities, repository APIs, seed contents, authorization policy definitions, indexes, uniqueness rules, or delete semantics. |
0 commit comments