| name | safe-prod-deploy |
| description | Безопасный выкат Movie Planner в прод: ветка → self-review → PR → merge → smoke. НЕ пушить код напрямую в main. Использовать при любом деплое, push, «в прод», Railway, правках bot/site; триггеры: задеплой, пушь, выкати, PR. |
Safe prod deploy — полный runbook для агента
Это единственный канонический процесс выката кода в прод для агентов.
Краткое alwaysApply-правило: .cursor/rules/safe-prod-deploy.mdc.
Длинный док для людей: docs/SAFE_PROD_DEPLOY.md.
Зачем
У сервиса есть живые пользователи. main = прод (Railway / кабинет).
Прямой push в main = сразу прод без буфера ревью. Агент обязан:
- не ломать прод спешкой;
- сам ревьюить свой diff перед merge;
- сам доводить PR до merge (кроме risk-gate);
- сам гонять smoke после деплоя.
Репозитории в scope
| Репо на диске | GitHub | Прод |
|---|
movie_planner_bot | zapnikita95/movie_planner_bot | Railway |
movie_planner_bot/movie_planner_site (отдельный git) | zapnikita95/movie_planner_site | статика кабинета (proxy с Railway) |
movieplanner-mobile: тот же дух (ветка → PR → main), но сторы не деплоятся от merge автоматически; сборка — по mobile-local-build-default / EAS skill. Не путать с Railway.
Поток (обязательный)
flowchart TD
start[Задача с кодом] --> branch[Ветка от origin/main]
branch --> work[Коммиты в ветку]
work --> review[Self-review чеклист]
review --> risk{Risk class?}
risk -->|RISKY| wait[Стоп: PR + отчёт, ждать ок владельца]
wait --> ok{Владелец сказал ок?}
ok -->|нет| stop[Не мержить]
ok -->|да| pushBr[Push ветки]
risk -->|NORMAL| pushBr
pushBr --> pr[gh pr create]
pr --> ci[Дождаться CI если есть]
ci --> merge[gh pr merge squash]
merge --> deploy[Railway / site proxy]
deploy --> smoke[smoke_home_prod.sh при web/API]
smoke --> done[Отчёт: PR URL + hash + smoke]
Шаг 0 — перед началом правок
cd /path/to/movie_planner_bot
git fetch origin
git checkout main
git pull --ff-only origin main
Если на main уже есть чужие незакоммиченные/локальные коммиты не по задаче — не мешать: новая ветка только для своей задачи; чужое не коммитить.
Шаг 1 — ветка
Имена:
| Тип | Префикс | Пример |
|---|
| Фича | feat/ | feat/ai-assistant-timeout |
| Фикс | fix/ | fix/rails-poster-fast-path |
| Хотфикс | hotfix/ | hotfix/auth-session-timeout |
| Инфра/docs | chore/ | chore/safe-prod-deploy |
| Рефактор | refactor/ | refactor/db-pool-comments |
git checkout -b fix/short-topic
Работать и коммитить только в этой ветке. Не копить коммиты на локальном main.
Шаг 2 — коммиты
git add только файлы задачи.
- Никогда:
.env, секреты, .cursor/tmp_*, личные дампы, lock-файлы воркеров без нужды, огромные .local/.
- Сообщение: зачем, не «fix stuff».
- Не
--no-verify, не force-push в main.
Шаг 3 — Self-review (ОБЯЗАТЕЛЬНО до PR / до merge)
Агент сам читает diff и проходит чеклист. Без этого merge запрещён.
git fetch origin
git diff origin/main...HEAD
git log origin/main..HEAD --oneline
Чеклист саморевью (отметить мысленно / в PR body)
Продукт / UI
Главная / ленты (если трогали dashboard, rails, постеры списков)
БД / старт / фон
Auth / РФ / apex
Постеры
Worker
Тесты
Объём
Заполнить risk-класс (ниже) и кратко описать в теле PR: что меняется, риск, как откатить.
Шаг 4 — Risk class
NORMAL (агент мержит сам)
Копирайт, мелкий UI, статьи, FAQ, изолированный багфикс с тестами, i18n строк, не трогающий hot path главной/auth/БД.
RISKY (СТОП — PR создать можно, merge только после «ок» владельца)
Любое из:
db_connection.py, db_pool.py, pool env (DB_POOL_*)
web_app.py startup, _defer_startup_task, scheduler на старте
init_database / миграции / CREATE INDEX / DDL
- sitemap,
db_zombie_guard, тяжёлые COUNT
- auth, OAuth,
site_sessions, JWT bridge
home_rails, dashboard lite/full, kp_film_media hot path
MP_SERVICE_ROLE, harvest/enrich policy, 24h флаги
- gunicorn/worker config, Railway JSON, то что может уложить
/health или главную
При RISKY в ответе пользователю до merge:
- Ссылка на PR.
- 3–6 буллетов: что меняется и почему риск.
- Явный вопрос: «мержить в прод?»
- Не вызывать
gh pr merge, пока нет явного «ок» / «мержи» / «в прод».
Шаг 5 — Push ветки (не main)
Из корня нужного репо:
git push -u origin HEAD
Если SSH Permission denied — см. skill push-to-github-myself (HTTPS / token), но цель push — имя feature-ветки, не main.
Для movie_planner_bot с токеном:
export GITHUB_TOKEN=$(grep '^GITHUB_TOKEN=' .env | cut -d= -f2-)
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/zapnikita95/movie_planner_bot.git"
git push -u origin HEAD
git remote set-url origin git@github.com:zapnikita95/movie_planner_bot.git
Шаг 6 — Pull Request
gh pr create --base main --title "краткий заголовок" --body "$(cat <<'EOF'
## Summary
- что сделано (1–3 буллета)
## Risk
- NORMAL | RISKY
- почему
## Self-review
- чеклист пройден: да
- тесты: что гонял / почему не гонял
## Test plan
- [ ] локальные тесты / smoke после merge
## Rollback
- revert этого PR / предыдущий deploy
EOF
)"
Для site — тот же шаблон из каталога movie_planner_site.
Шаг 7 — CI
На movie_planner_bot workflow .github/workflows/tests.yml бежит на pull_request.
gh pr checks <N> --watch
gh run watch
- NORMAL: дождаться green или если checks ещё не стартовали >2 мин — один раз перепроверить; при red — не merge, чинить в той же ветке.
- RISKY: всё равно ждать «ок» владельца + green CI.
- Локально перед PR желательно прогнать узкий набор тестов по изменённым модулям.
Не использовать gh pr merge --admin чтобы обойти красный CI по умолчанию.
Исключения (явно написать в отчёте пользователю):
- Владелец сказал «мержи несмотря на CI».
- PR только docs/rules/skills (ноль runtime-кода), а красный
test — заранее известный / явно не связан с diff (git diff origin/main...HEAD + failed jobs). Тогда допустим --admin после green lint/e2e, если они есть.
- На GitHub пока не включены required status checks на
main (job test иногда красный из‑за старых unittest) — агент всё равно смотрит checks и не игнорирует red на своих изменениях кода.
Когда CI стабильно green — включить required check test (3.11) в branch protection и убрать исключения #2/#3.
Шаг 8 — Merge
Только после self-review (+ ок при RISKY) и зелёного CI (если checks есть и относятся к изменениям):
gh pr merge <N> --squash --delete-branch
Если GitHub требует другую стратегию — --merge допустим; предпочтение squash (чистая история main).
Branch protection: прямой git push origin main отклонён (GH006 / «Changes must be made through a pull request»), в т.ч. для admin token (enforce_admins).
После merge:
git checkout main
git pull --ff-only origin main
Шаг 9 — Пост-деплой (агент сам)
Railway забирает main сам. Подождать ~1–3 мин (или пока /health не ответит актуально).
Если затронуты web / API / auth / dashboard / rails / sitemap / startup / pool:
bash scripts/smoke_home_prod.sh
Ожидание: всё OK, health быстро, rails с items (см. incident-2026-07-12-db-outage).
Дополнительно по задаче:
curl -sS -m 15 "https://movie-planner.ru/health"
curl -sS -m 20 "https://movie-planner.ru/api/app/release?refresh=1" | python3 -m json.tool
При подозрении на БД — pg_stat_activity (long active / idle in xact), не следующий деплой вслепую.
Для только movie_planner_site (копирайт/CSS без API): smoke_home не обязателен; проверить релевантную страницу кабинета/статьи curl или открыть URL.
Шаг 10 — Отчёт пользователю
Кратко:
- PR URL (merged)
- репо + короткий hash на
main
- risk class
- результат smoke (OK / skip почему)
Hotfix
- Ветка
hotfix/... от свежего main.
- Минимальный fix + self-review (риск часто RISKY — всё равно спросить, если auth/БД/главная).
- PR → CI → merge squashed как обычно.
- Smoke сразу.
Прямой git push origin main запрещён, пока владелец не написал явно:
- «пушь напрямую в main», или
- «bypass protection», или
- «без PR».
Даже тогда: один маленький коммит, сразу smoke, в ответе предупредить что был bypass.
Branch protection (ожидаемое состояние GitHub)
На main у movie_planner_bot и movie_planner_site:
- Require a pull request before merging
- Enforce for admins (даже агент с admin token не пушит в main в обход)
- Force push / delete branch
main — запрещены
- Required approving reviews — 0 (соло + agent self-merge)
- Required status checks — по возможности job
test; агент всё равно обязан смотреть gh pr checks
Если protection мешает легитимному hotfix и владелец просит bypass — временно ослабить через GitHub Settings только по явной просьбе, затем вернуть.
Связь со старыми скиллами
| Скилл / правило | Как читать теперь |
|---|
deploy-prod.mdc | Пуш = пуш ветки + merge PR; не прямой main |
always-deploy-prod-after-code | «Всегда довести до прода» = этот процесс, не push main |
verify-push-deploy | Проверить что PR merged и main на origin содержит коммит |
deploy-movie-planner | Railway/токены/smoke; путь выката — этот skill |
push-to-github-myself | Техника push remote/token; цель — feature branch |
При конфликте формулировок побеждает safe-prod-deploy.
Чего никогда не делать
git push origin main с коммитами кода без PR (кроме явного bypass от владельца).
- Merge RISKY без «ок».
- Merge при red CI без явной команды владельца.
- Коммит
.env / секретов.
- Второй деплой «наугад» при pool exhausted / zombies.
- Обещать «на проде», пока PR не merged и (для web) smoke не зелёный или честно не применим.
- Регистрировать API-роуты в фоне «чтобы health быстрее» — см. db-connections / incident notes.
Быстрая шпаргалка команд
git fetch origin && git checkout main && git pull --ff-only origin main
git checkout -b fix/my-fix
git add <files> && git commit -m "fix: why"
git push -u origin HEAD
gh pr create --base main --title "fix: ..." --body "..."
gh pr checks --watch
gh pr merge --squash --delete-branch
git checkout main && git pull --ff-only origin main
bash scripts/smoke_home_prod.sh