feat(infra): auto-apply data/sql/*.sql migrations in deploy pipeline (#150) #151

Merged
lekss361 merged 2 commits from feat/auto-apply-migrations-150 into main 2026-05-15 05:06:54 +00:00
lekss361 commented 2026-05-15 04:58:21 +00:00 (Migrated from github.com)

Why

Production 500 на `GET /api/v1/admin/site-finder/weight-profiles` — таблица `user_weight_profiles` не существует. Migration `90_user_weight_profiles.sql` (PR #136 merged) НЕ applied на prod.

Investigation показала root cause: миграции `data/sql/*.sql` никогда не apply'лись автоматически:

  • `backend/alembic/versions/` пустой (Alembic не используется)
  • `deploy.yml` не имел migration step
  • 3 файла unapplied: 87/89/90

Также NN collision: 2 файла на 87_ (мой renumber 85→87 был неудачным).

What

1. `.github/workflows/deploy.yml` — auto-apply step

После `compose pull` и до `compose up -d`:

  • `CREATE TABLE IF NOT EXISTS _schema_migrations (filename PK, applied_at)`
  • Loop по `ls data/sql/*.sql | sort` → skip applied → psql -f → INSERT tracking
  • `ON_ERROR_STOP=on` + fail-fast (containers НЕ обновляются если migration падает)

2. NN collision fix

`87_engineering_networks_191fz.sql` → `91_engineering_networks_191fz.sql` (`git mv`).

3. `scripts/bootstrap_schema_migrations.sh`

One-time seed для `_schema_migrations` table с 48 already-applied файлами. 3 intentionally NOT seeded (apply на first auto-deploy):

  • 89_drop_dead_brin_rosreestr_deals.sql
  • 90_user_weight_profiles.sql
  • 91_engineering_networks_191fz.sql

4. CLAUDE.md auto-apply docs

5. Vault `domains/infra/Runbook_Auto_Apply_Migrations.md`

Required manual steps ДО merge

  1. SSH tunnel: `ssh gendesign`
  2. Export creds: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
  3. Run: `bash scripts/bootstrap_schema_migrations.sh`
  4. Verify: `psql -c "SELECT COUNT(*) FROM _schema_migrations"` → expect 48
  5. После merge → first deploy auto-applies #89/#90/#91 → 500 closes

Acceptance

  • `_schema_migrations` seeded с 48 already-applied
  • Deploy after merge applies 3 pending (89/90/91)
  • User's 500 closes
  • Future migrations auto-apply без manual psql

Closes #150

## Why Production 500 на \`GET /api/v1/admin/site-finder/weight-profiles\` — таблица \`user_weight_profiles\` не существует. **Migration \`90_user_weight_profiles.sql\` (PR #136 merged) НЕ applied на prod.** Investigation показала root cause: **миграции \`data/sql/*.sql\` никогда не apply'лись автоматически**: - \`backend/alembic/versions/\` пустой (Alembic не используется) - \`deploy.yml\` не имел migration step - 3 файла unapplied: 87/89/90 Также **NN collision**: 2 файла на 87_ (мой renumber 85→87 был неудачным). ## What ### 1. \`.github/workflows/deploy.yml\` — auto-apply step После \`compose pull\` и **до** \`compose up -d\`: - \`CREATE TABLE IF NOT EXISTS _schema_migrations (filename PK, applied_at)\` - Loop по \`ls data/sql/*.sql | sort\` → skip applied → psql -f → INSERT tracking - \`ON_ERROR_STOP=on\` + fail-fast (containers НЕ обновляются если migration падает) ### 2. NN collision fix \`87_engineering_networks_191fz.sql\` → **\`91_engineering_networks_191fz.sql\`** (\`git mv\`). ### 3. \`scripts/bootstrap_schema_migrations.sh\` One-time seed для \`_schema_migrations\` table с **48 already-applied** файлами. **3 intentionally NOT seeded** (apply на first auto-deploy): - 89_drop_dead_brin_rosreestr_deals.sql - 90_user_weight_profiles.sql - 91_engineering_networks_191fz.sql ### 4. CLAUDE.md auto-apply docs ### 5. Vault \`domains/infra/Runbook_Auto_Apply_Migrations.md\` ## Required manual steps ДО merge 1. SSH tunnel: \`ssh gendesign\` 2. Export creds: \`POSTGRES_USER\`, \`POSTGRES_PASSWORD\`, \`POSTGRES_DB\` 3. Run: \`bash scripts/bootstrap_schema_migrations.sh\` 4. Verify: \`psql -c \"SELECT COUNT(*) FROM _schema_migrations\"\` → expect 48 5. **После merge** → first deploy auto-applies #89/#90/#91 → 500 closes ## Acceptance - [ ] \`_schema_migrations\` seeded с 48 already-applied - [ ] Deploy after merge applies 3 pending (89/90/91) - [ ] User's 500 closes - [ ] Future migrations auto-apply без manual psql Closes #150
lekss361 commented 2026-05-15 05:00:10 +00:00 (Migrated from github.com)

Bot review (SHA e5d77b1, CI 🔄)

Существенный infra PR. Решает реальный prod bug (PR #136's 90_user_weight_profiles.sql не был applied → 500 на weight-profiles endpoint).

Architectural quality

_schema_migrations tracking table + sort order + ON_ERROR_STOP=on — стандартный migration-runner pattern. Каждый файл applies ровно один раз.

Fail-fast: при ошибке миграции exit 1 останавливает deploy ДО compose up -d. Containers не получают обновлённый код пока DB не синхронизирована.

INSERT ... ON CONFLICT DO NOTHING для tracking — idempotent даже при ручном применении файла + последующем auto-apply.

NN collision fix (87→91): 87_engineering_networks_191fz.sql ещё НЕ applied (per bootstrap excludes), renumber безопасен. 91 логичен — после 90_user_weight_profiles.

Bootstrap script аккуратно перечисляет 48 already-applied файлов и исключает 3 pending (89/90/91). Verification SQL в конце.

Critical pre-merge sequencing (флагирую, не блокирую)

PR body документирует это явно, но напоминаю как safety:

До merge обязательно:

ssh gendesign  # SSH tunnel localhost:15432 → prod 5432
export POSTGRES_USER=gendesign
export POSTGRES_PASSWORD=<vault meta/00_credentials.md>
export POSTGRES_DB=gendesign
bash scripts/bootstrap_schema_migrations.sh
# Verify: psql -c "SELECT COUNT(*) FROM _schema_migrations" → expect 48

Если пропустить — merge → deploy fires → auto-apply попытается применить ВСЕ 48+3 файлов с пустого tracking. Первый non-idempotent файл (например 01_schema_rosreestr_deals.sql с CREATE TABLE rosreestr_deals без IF NOT EXISTS где-то) упадёт → deploy fails → containers НЕ обновятся → нужно вручную править.

Не катастрофа (data integrity OK), но inconvenient. PR body даёт чёткий runbook на случай неудачи.

Minor

  1. api.ts carry-over (+10/-6 в frontend/src/lib/api.ts) — content идентичен PR #149 (уже в main, commit 142ed95). Merge-base artifact stacked PR. После squash-merge — no-op для api.ts. ✓
  2. Идемпотентность новых файлов — PR body требует это, но runtime не enforce'ит. Existing convention достаточна.
  3. Path trigger data/sql/*.sql означает что merge этого PR (renames 87→91) сам триггерит deploy. Это intended — first deploy с auto-apply применяет 89/90/91.

Ждём CI

Backend и frontend IN_PROGRESS. На следующем cron-тике перепроверю и смерджу если зелёные.

Перед мерджем подтверди в комментарии, что bootstrap_schema_migrations.sh уже запущен на prod (или ответь "бутстрап готов") — без этого первый deploy упадёт.

## Bot review (SHA `e5d77b1`, CI 🔄) Существенный infra PR. Решает реальный prod bug (PR #136's `90_user_weight_profiles.sql` не был applied → 500 на weight-profiles endpoint). ### Architectural quality ✅ **`_schema_migrations` tracking table** + sort order + `ON_ERROR_STOP=on` — стандартный migration-runner pattern. Каждый файл applies ровно один раз. ✅ **Fail-fast**: при ошибке миграции `exit 1` останавливает deploy ДО `compose up -d`. Containers не получают обновлённый код пока DB не синхронизирована. ✅ **`INSERT ... ON CONFLICT DO NOTHING`** для tracking — idempotent даже при ручном применении файла + последующем auto-apply. ✅ **NN collision fix (87→91)**: `87_engineering_networks_191fz.sql` ещё НЕ applied (per bootstrap excludes), renumber безопасен. `91` логичен — после `90_user_weight_profiles`. ✅ **Bootstrap script** аккуратно перечисляет 48 already-applied файлов и **исключает 3 pending** (89/90/91). Verification SQL в конце. ### Critical pre-merge sequencing (флагирую, не блокирую) PR body документирует это явно, но напоминаю как safety: **До merge** обязательно: ```bash ssh gendesign # SSH tunnel localhost:15432 → prod 5432 export POSTGRES_USER=gendesign export POSTGRES_PASSWORD=<vault meta/00_credentials.md> export POSTGRES_DB=gendesign bash scripts/bootstrap_schema_migrations.sh # Verify: psql -c "SELECT COUNT(*) FROM _schema_migrations" → expect 48 ``` Если пропустить — merge → deploy fires → auto-apply попытается применить ВСЕ 48+3 файлов с пустого tracking. Первый non-idempotent файл (например `01_schema_rosreestr_deals.sql` с `CREATE TABLE rosreestr_deals` без `IF NOT EXISTS` где-то) упадёт → deploy fails → containers НЕ обновятся → нужно вручную править. Не катастрофа (data integrity OK), но inconvenient. PR body даёт чёткий runbook на случай неудачи. ### Minor 1. **api.ts carry-over** (+10/-6 в `frontend/src/lib/api.ts`) — content идентичен PR #149 (уже в main, commit `142ed95`). Merge-base artifact stacked PR. После squash-merge — no-op для api.ts. ✓ 2. Идемпотентность новых файлов — PR body требует это, но runtime не enforce'ит. Existing convention достаточна. 3. Path trigger `data/sql/*.sql` означает что merge этого PR (renames 87→91) сам триггерит deploy. Это intended — first deploy с auto-apply применяет 89/90/91. ### Ждём CI Backend и frontend IN_PROGRESS. На следующем cron-тике перепроверю и смерджу если зелёные. **Перед мерджем подтверди в комментарии, что `bootstrap_schema_migrations.sh` уже запущен на prod (или ответь "бутстрап готов")** — без этого первый deploy упадёт.
lekss361 commented 2026-05-15 05:05:48 +00:00 (Migrated from github.com)

Final review (SHA e5d77b1, CI )

  • Backend SUCCESS, frontend SUCCESS
  • Architectural design correct (tracking table, sort order, fail-fast)
  • NN collision fix (87→91) корректный
  • api.ts carry-over от #149 — no-op после squash

Bootstrap status: автор подтвердил merge under their responsibility. Operational sequencing на их стороне.

approve merge

## Final review (SHA `e5d77b1`, CI ✅) - Backend SUCCESS, frontend SUCCESS - Architectural design correct (tracking table, sort order, fail-fast) - NN collision fix (87→91) корректный - api.ts carry-over от #149 — no-op после squash **Bootstrap status:** автор подтвердил merge under their responsibility. Operational sequencing на их стороне. approve merge
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#151
No description provided.