> ⛔ **ЗАМЕНЁН 25.06.2026.** Владелец выбрал **Путь А** (управляемая база Яндекса) после разбора по коду — швы оказались узкими, портал уже подготовлен. Актуальный план: [2026-06-25-db-migration-path-a-managed.md](2026-06-25-db-migration-path-a-managed.md). Этот файл оставлен для истории; идея «копии на сторону» поглощена автокопиями управляемой базы (3 ЦОД).

# База данных — безопасность (Путь Б) Implementation Plan [ЗАМЕНЁН → Путь А]

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Убрать риск потери денег/данных клиентов: копии базы — «на сторону» (вне сервера базы) с проверкой восстановления, плюс живая запасная копия (реплика) и, отдельной поздней фазой, вынос базы на свой сервер. Без переписывания изоляции клиентов (RLS/роли остаются как есть).

**Architecture:** PostgreSQL остаётся самоуправляемым (наши 5 ролей, BYPASSRLS, anon 3.0.13, session_replication_role — работают как сейчас, ничего не переписываем). Добавляем: (1) выгрузку ночных дампов в Yandex Object Storage (S3) с шифрованием и ротацией; (2) еженедельную проверку «дамп реально восстанавливается»; (3) алерт, если свежий дамп не появился; (4) потоковую реплику на втором хосте для подстраховки; (5) [поздняя фаза] перенос базы на выделенный сервер с repoint приложения и сверкой денег.

**Tech Stack:** PostgreSQL 16, `pg_dump -Fc`, Yandex Object Storage (S3-совместимое, endpoint `storage.yandexcloud.net`), `aws-cli` (или `s3cmd`), GPG/SSE для шифрования, streaming replication (`pg_basebackup` + standby), GitHub Actions (`artisan-run.yml` / новый workflow) для прод-операций, существующий `prod-deploy-validator` агент для GO/NO-GO.

---

## ⚠️ Правила выполнения (не нарушать)

1. **Любой шаг на боевом liderra.ru — только после явного «выкатывай» владельца** и прогона `prod-deploy-validator` (GO). Шаги ниже это помечают как `[PROD-GATE]`.
2. **Деньги-инвариант:** перед и после каждого шага, который касается данных, сверять `tenant 2` — **должно остаться 1 836 400.00 ₽ / 1013 сделок** (точный запрос фиксируется в Phase 0). Любое расхождение = СТОП.
3. **Ничего не удаляем** (старые копии, cron) пока новый механизм не проверен на восстановление.
4. Доступ к боевому — `ssh liderra-prod` через бастион (внутр. `10.128.0.15`, ProxyJump `liderra-bastion`).

---

## 📋 Для владельца — что делаем простым языком

1. **Сначала — копии «на сторону».** Сейчас ночные копии базы лежат на том же диске, что и сама база. Делаем так, чтобы каждая ночная копия дополнительно улетала в отдельное защищённое хранилище Яндекса (зашифрованной). Если сервер умрёт — копии целы. **Это закрывает главную боль.**
2. **Проверка, что копия реально оживает.** Раз в неделю автоматически берём свежую копию и проверяем, что из неё база действительно поднимается (а не «копия есть, но битая»).
3. **Сторож.** Если ночью копия не сделалась — приходит сигнал, а не тишина.
4. **Запасная живая база (реплика).** Вторая копия базы, которая всё время повторяет основную — на случай аварии.
5. **[Позже] Своя база на отдельном сервере** — чтобы база и портал не делили одну машину. Делается после п.1–4, отдельно.

**Что от вас нужно до старта (это в облаке Яндекса, через консоль — ваши руки или доступ для меня):** создать сервисный аккаунт + ключ + бакет (хранилище) для копий. Детали — в «Prerequisites» ниже.

---

## Prerequisites (действия владельца в консоли Yandex Cloud)

Без них Phase 1 не стартует. Это разовая настройка доступа к хранилищу.

- [ ] **P1. Создать бакет Object Storage** для копий, напр. `liderra-db-backups`, класс STANDARD, **доступ приватный**, регион `ru-central1`.
- [ ] **P2. Создать сервисный аккаунт** (напр. `liderra-backup-sa`) с ролью `storage.uploader` (или `storage.editor`) **только на этот бакет**.
- [ ] **P3. Создать статический ключ доступа** для этого сервисного аккаунта (Access Key ID + Secret) — отдать мне для размещения на сервере (в защищённом виде, не в git).
- [ ] **P4. Включить версионирование бакета + lifecycle** (хранить 30 дней, потом удалять) — закрывает ransomware/случайное удаление.
- [ ] **P5 (рекоменд.).** Включить **Object Lock** (неизменяемость на 14 дней) на бакете — чтобы копии нельзя было перезаписать/удалить даже с ключом.

> Ключ из P3 размещается на сервере в `/etc/liderra/s3-backup.env` (`chmod 600`, владелец root), **в git не попадает** (gitleaks). Долгосрочно — переложить в YC Lockbox.

---

## Phase 0 — Discovery (подтвердить текущее состояние, без изменений)

### Task 0: Снять факты с боевого

**Files:** только чтение по SSH; результат — заметка `docs/superpowers/findings/2026-06-25-db-safety/phase0-state.md`.

- [ ] **Step 1: Подтвердить деньги-инвариант (зафиксировать точный запрос)**

Run (на боевом, read-only):
```bash
ssh liderra-prod "sudo -u postgres psql -d liderra -At -c \
  \"SELECT 'balance', balance FROM tenants WHERE id=2
    UNION ALL SELECT 'deals', count(*)::text FROM deals WHERE tenant_id=2;\""
```
Expected: `balance|1836400.00` и `deals|1013`. **Записать точные имена таблицы/колонки баланса** (если баланс не в `tenants`, найти реальную: `\d tenants`, либо `balances`/`saas_*`). Этот запрос дальше = «money-check».

- [ ] **Step 2: Снять параметры текущего бэкап-механизма**

Run:
```bash
ssh liderra-prod "cat /usr/local/bin/liderra-backup.sh; echo '---CRON---'; \
  cat /etc/cron.d/liderra-backup; echo '---LS---'; ls -lh /home/ubuntu/backups/ | tail; \
  echo '---LOG---'; tail -20 /var/log/liderra-backup.log"
```
Expected: увидеть `pg_dump -Fc`, расписание 03:30, retention 14, наличие свежих файлов.

- [ ] **Step 3: Снять параметры сервера/диска/PG**

Run:
```bash
ssh liderra-prod "df -h /; free -m; psql --version 2>/dev/null; \
  sudo -u postgres psql -d liderra -At -c 'show wal_level;'; \
  sudo -u postgres psql -d liderra -At -c 'select pg_size_pretty(pg_database_size(current_database()));'"
```
Expected: свободное место на `/`, объём базы, `wal_level` (нужен `replica`/`logical` для Phase 3), версия PG.

- [ ] **Step 4: Зафиксировать находки**

Записать в `phase0-state.md`: реальный money-check запрос, объём базы, свободный диск, текущий retention, `wal_level`, наличие `aws`/`s3cmd` на сервере (`which aws s3cmd`). **Commit** (docs-only):
```bash
git add docs/superpowers/findings/2026-06-25-db-safety/phase0-state.md
git commit -m "docs(db-safety): снимок состояния перед Путь Б (Phase 0)"
```

---

## Phase 1 — Копии «на сторону» в Object Storage (СРОЧНОЕ, закрывает главный риск)

### Task 1: Скрипт выгрузки дампа в S3 (готовим и тестируем ЛОКАЛЬНО логику, ставим на прод за GATE)

**Files:**
- Create: `deploy/db-backup/upload-to-s3.sh`
- Create: `deploy/db-backup/README.md`
- Modify (на сервере, через GATE): `/usr/local/bin/liderra-backup.sh` (добавить вызов выгрузки)

- [ ] **Step 1: Написать скрипт выгрузки**

`deploy/db-backup/upload-to-s3.sh`:
```bash
#!/usr/bin/env bash
# Выгрузка свежего дампа PostgreSQL в Yandex Object Storage (S3), зашифрованно.
set -euo pipefail

source /etc/liderra/s3-backup.env   # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET
ENDPOINT="https://storage.yandexcloud.net"
BACKUP_DIR="/home/ubuntu/backups"
GPG_RECIPIENT_KEY="/etc/liderra/backup-pubkey.asc"   # публичный ключ для шифрования

# взять самый свежий дамп
LATEST="$(ls -t "${BACKUP_DIR}"/*.dump 2>/dev/null | head -1)"
[ -z "${LATEST}" ] && { echo "FATAL: нет дампов в ${BACKUP_DIR}"; exit 1; }

STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
ENC="${LATEST}.${STAMP}.gpg"

# зашифровать дамп публичным ключом (расшифровать сможет только владелец приватного ключа)
gpg --batch --yes --trust-model always --recipient-file "${GPG_RECIPIENT_KEY}" \
    --output "${ENC}" --encrypt "${LATEST}"

# выгрузить в бакет
aws --endpoint-url "${ENDPOINT}" s3 cp "${ENC}" \
    "s3://${S3_BUCKET}/daily/$(basename "${ENC}")" \
    --only-show-errors

# проверить, что объект реально лёг (size > 0)
SIZE="$(aws --endpoint-url "${ENDPOINT}" s3 ls "s3://${S3_BUCKET}/daily/$(basename "${ENC}")" | awk '{print $3}')"
[ "${SIZE:-0}" -gt 0 ] || { echo "FATAL: объект в S3 пустой/не загрузился"; exit 1; }

rm -f "${ENC}"
echo "OK: ${ENC} → s3://${S3_BUCKET}/daily/ (${SIZE} bytes) @ ${STAMP}"
```

- [ ] **Step 2: Написать README с порядком установки**

`deploy/db-backup/README.md`: описать `/etc/liderra/s3-backup.env` (3 переменные, `chmod 600`), генерацию пары GPG-ключей (приватный хранит владелец **вне сервера**, публичный — на сервере), endpoint `storage.yandexcloud.net`, что выгрузка вызывается из `liderra-backup.sh` ПОСЛЕ успешного `pg_dump`.

- [ ] **Step 3: Commit (код, без прод-выката)**
```bash
git add deploy/db-backup/upload-to-s3.sh deploy/db-backup/README.md
git commit -m "feat(db-safety): скрипт выгрузки дампа базы в Object Storage (зашифровано)"
```

### Task 2: Установка на боевой `[PROD-GATE]`

- [ ] **Step 1: Запросить GO** — `prod-deploy-validator` + явное «выкатывай» владельца. NO-GO → стоп.

- [ ] **Step 2: Разместить секреты и ключ** (на сервере)
```bash
# s3-backup.env (значения из Prerequisites P3) и публичный GPG-ключ
ssh liderra-prod "sudo install -m 600 -o root -g root /dev/stdin /etc/liderra/s3-backup.env" < local-s3-backup.env
ssh liderra-prod "sudo install -m 644 /dev/stdin /etc/liderra/backup-pubkey.asc" < backup-pubkey.asc
ssh liderra-prod "which aws || sudo apt-get install -y awscli"
```

- [ ] **Step 3: Поставить скрипт и подцепить в существующий бэкап**
```bash
ssh liderra-prod "sudo install -m 755 /dev/stdin /usr/local/bin/liderra-upload-s3.sh" < deploy/db-backup/upload-to-s3.sh
# добавить вызов в конец liderra-backup.sh (после успешного pg_dump)
ssh liderra-prod "grep -q liderra-upload-s3 /usr/local/bin/liderra-backup.sh || \
  echo '/usr/local/bin/liderra-upload-s3.sh >> /var/log/liderra-backup.log 2>&1' | \
  sudo tee -a /usr/local/bin/liderra-backup.sh"
```

- [ ] **Step 4: Прогнать вручную и проверить, что объект появился в бакете**
```bash
ssh liderra-prod "sudo /usr/local/bin/liderra-upload-s3.sh"
ssh liderra-prod "source /etc/liderra/s3-backup.env; aws --endpoint-url https://storage.yandexcloud.net s3 ls s3://\$S3_BUCKET/daily/ | tail"
```
Expected: строка `OK: ... → s3://.../daily/ (N bytes)` и видимый объект в листинге.

- [ ] **Step 5: Money-check (ничего не должно поменяться)** — повторить запрос из Task 0 Step 1. Expected: `1836400.00 / 1013`.

- [ ] **Step 6: Обновить снимок** — `ПИЛОТ.md` строка про бэкапы: «Off-site (YC Object Storage) — **есть**» (по команде владельца «обнови пилот»).

---

## Phase 2 — Проверка восстановимости + сторож свежести

### Task 3: Еженедельная авто-проверка «дамп оживает»

**Files:**
- Create: `deploy/db-backup/restore-test.sh`
- Create (на сервере, GATE): cron `/etc/cron.d/liderra-restore-test` (еженедельно)

- [ ] **Step 1: Написать скрипт проверки восстановления**

`deploy/db-backup/restore-test.sh` — берёт последний объект из S3, расшифровывает, `pg_restore` во **временную** базу `liderra_restore_test`, проверяет ключевые числа, затем дропает временную базу:
```bash
#!/usr/bin/env bash
set -euo pipefail
source /etc/liderra/s3-backup.env
ENDPOINT="https://storage.yandexcloud.net"
TMP="/tmp/restore-test"; mkdir -p "$TMP"
KEY="$(aws --endpoint-url "$ENDPOINT" s3 ls "s3://${S3_BUCKET}/daily/" | sort | tail -1 | awk '{print $4}')"
aws --endpoint-url "$ENDPOINT" s3 cp "s3://${S3_BUCKET}/daily/${KEY}" "$TMP/latest.gpg" --only-show-errors
gpg --batch --yes --output "$TMP/latest.dump" --decrypt "$TMP/latest.gpg"
sudo -u postgres dropdb --if-exists liderra_restore_test
sudo -u postgres createdb liderra_restore_test
sudo -u postgres pg_restore --no-owner --no-privileges -d liderra_restore_test "$TMP/latest.dump"
# sanity: число сделок tenant 2 не нулевое
CNT="$(sudo -u postgres psql -d liderra_restore_test -At -c 'select count(*) from deals where tenant_id=2;')"
sudo -u postgres dropdb liderra_restore_test
rm -rf "$TMP"
[ "${CNT:-0}" -ge 1000 ] || { echo "FATAL: восстановленная база подозрительна (deals=$CNT)"; exit 1; }
echo "OK: restore-test прошёл, deals(tenant2)=${CNT} @ $(date -u +%FT%TZ)"
```

- [ ] **Step 2: Commit**
```bash
git add deploy/db-backup/restore-test.sh
git commit -m "feat(db-safety): еженедельная проверка восстановимости дампа из S3"
```

- [ ] **Step 3: `[PROD-GATE]` Поставить + cron еженедельно (воскр. 04:30) + прогнать раз вручную**
```bash
ssh liderra-prod "sudo install -m 755 /dev/stdin /usr/local/bin/liderra-restore-test.sh" < deploy/db-backup/restore-test.sh
ssh liderra-prod "echo '30 4 * * 0 root /usr/local/bin/liderra-restore-test.sh >> /var/log/liderra-restore-test.log 2>&1' | sudo tee /etc/cron.d/liderra-restore-test"
ssh liderra-prod "sudo /usr/local/bin/liderra-restore-test.sh"
```
Expected: `OK: restore-test прошёл, deals(tenant2)=1013`.

### Task 4: Сторож свежести копии (алерт, если ночью копии нет)

**Files:** Create: `deploy/db-backup/backup-freshness-check.sh` + cron.

- [ ] **Step 1: Написать проверку свежести** — если в бакете нет объекта за последние 26 часов → ненулевой выход (алерт уйдёт по существующему каналу ops-email/GitHub Actions, как у `CsvReconcileJob`-алерта):
```bash
#!/usr/bin/env bash
set -euo pipefail
source /etc/liderra/s3-backup.env
ENDPOINT="https://storage.yandexcloud.net"
LAST="$(aws --endpoint-url "$ENDPOINT" s3 ls "s3://${S3_BUCKET}/daily/" | sort | tail -1 | awk '{print $1" "$2}')"
LAST_TS="$(date -u -d "$LAST" +%s 2>/dev/null || echo 0)"
NOW="$(date -u +%s)"
AGE_H=$(( (NOW - LAST_TS) / 3600 ))
[ "$AGE_H" -le 26 ] || { echo "ALERT: свежей копии базы в S3 нет ${AGE_H}ч (последняя: ${LAST})"; exit 1; }
echo "OK: свежая копия ${AGE_H}ч назад"
```

- [ ] **Step 2: Commit + `[PROD-GATE]` поставить, cron ежедневно 06:00** (после ночного бэкапа 03:30):
```bash
git add deploy/db-backup/backup-freshness-check.sh
git commit -m "feat(db-safety): сторож свежести копии базы в S3 (алерт при пропуске)"
# на сервере:
ssh liderra-prod "sudo install -m 755 /dev/stdin /usr/local/bin/liderra-backup-freshness.sh" < deploy/db-backup/backup-freshness-check.sh
ssh liderra-prod "echo '0 6 * * * root /usr/local/bin/liderra-backup-freshness.sh || (echo backup-stale | mail -s \"liderra: backup stale\" ops@liderra.ru)' | sudo tee /etc/cron.d/liderra-backup-freshness"
```

> **После Phase 2 главный риск закрыт:** копии «на стороне», зашифрованы, ротация 30 дней, проверены на восстановление, и есть сигнал если что-то сломалось. Phases 3–4 — усиление, можно делать спокойно.

---

## Phase 3 — Запасная живая база (потоковая реплика)

### Task 5: Поднять standby-реплику на втором хосте `[PROD-GATE, крупная операция]`

**Files:** Create: `deploy/db-replica/setup-standby.md` (runbook), правки `postgresql.conf`/`pg_hba.conf` на мастере.

> Требует второго сервера (provisioning — Prerequisites-аналог в YC, действие владельца). Реплика **не** трогает данные мастера (только читает WAL).

- [ ] **Step 1: Убедиться, что мастер готов к репликации** — из Phase 0 `wal_level >= replica`. Если нет — план правки `postgresql.conf` (`wal_level=replica`, `max_wal_senders=10`, `wal_keep_size=1GB`) + рестарт PG `[PROD-GATE]` (короткий рестарт, в окно).

- [ ] **Step 2: Создать роль репликации на мастере**
```sql
-- от postgres на мастере
CREATE ROLE crm_replica WITH REPLICATION LOGIN PASSWORD '<из Lockbox>';
```
+ строка в `pg_hba.conf`: `host replication crm_replica <ip-реплики>/32 scram-sha-256`, затем `SELECT pg_reload_conf();`.

- [ ] **Step 3: Снять base backup на реплику**
```bash
# на сервере-реплике
sudo -u postgres pg_basebackup -h <master-ip> -U crm_replica -D /var/lib/postgresql/16/main -Fp -Xs -P -R
```
(`-R` создаёт `standby.signal` + `primary_conninfo`.)

- [ ] **Step 4: Запустить реплику и проверить, что догоняет мастер**
```bash
# на реплике
sudo systemctl start postgresql
sudo -u postgres psql -At -c "select status, sender_host from pg_stat_wal_receiver;"
# на мастере
sudo -u postgres psql -At -c "select client_addr, state, sync_state from pg_stat_replication;"
```
Expected: на мастере видно реплику `state=streaming`; на реплике `pg_stat_wal_receiver` = `streaming`.

- [ ] **Step 5: Money-check на мастере** (реплика не должна влиять): `1836400.00 / 1013`.

- [ ] **Step 6: Runbook переключения (failover)** — записать в `setup-standby.md` **ручную** процедуру promote (`pg_ctl promote` / `SELECT pg_promote();`) + переключение `.env` приложения на новый адрес. Авто-failover (Patroni) — **отдельный поздний пункт**, не сейчас.

- [ ] **Step 7: Commit (docs/runbook)**
```bash
git add deploy/db-replica/setup-standby.md
git commit -m "docs(db-safety): runbook standby-реплики + ручной failover (Phase 3)"
```

---

## Phase 4 — Вынос базы на выделенный сервер (поздняя фаза, после Phase 1–3)

> Это про производительность под сотни клиентов (база и приложение перестают делить одну машину), а не про срочную безопасность. Делать отдельным окном с простоем/в тихое время.

### Task 6: Перенос базы на отдельный сервер `[PROD-GATE, с коротким простоем]`

**Files:** Create: `deploy/db-move/runbook.md`; Modify (на сервере): `app/.env` (`DB_HOST`), `pgbouncer.ini`.

- [ ] **Step 1: Подготовить новый сервер БД** — провижен ВМ (4 ядра/8–16 ГБ/SSD с запасом), установить PostgreSQL 16 + все наши расширения (pgcrypto, pg_trgm, btree_gin, pgaudit, anon 3.0.13 — собрать как на текущем по `docs/security/pgaudit-anonymizer-setup.md`), создать 5 ролей (`db/00_create_roles.sql`), `pg_hba.conf` пускает только приложение-сервер.

- [ ] **Step 2: Вариант переноса — через реплику (минимальный простой):** если Phase 3 уже дала реплику на этом сервере — в окно: остановить приложение (maintenance), дождаться нулевого лага, `promote` реплику, переключить `app/.env` `DB_HOST` на новый сервер, поднять приложение. Иначе — `pg_dump`/`pg_restore` в окно.

- [ ] **Step 3: Money-check ДО и ПОСЛЕ** на НОВОМ сервере: `1836400.00 / 1013`. Расхождение → откат на старый `DB_HOST` (старая база нетронута).

- [ ] **Step 4: Verify портал** — HTTP 200 (главная + `/login`), очередь активна, свежих ошибок нет, RLS-изоляция жива (зайти двумя тенантами — не видят чужого).

- [ ] **Step 5: Бэкап-скрипты Phase 1–2 перенацелить на новый сервер**; старый сервер БД оставить выключенным как горячий откат на 7 дней, потом погасить.

- [ ] **Step 6: Commit runbook + обновить `ПИЛОТ.md`** (по команде «обнови пилот»).

---

## Phase 5 — Мелкая чистка аудита (необязательно для Б; нужно для будущего Пути А)

### Task 7: Пересчёт аудит-цепочки через отдельный superuser-коннекшен (TDD)

> На Пути Б это **не блокер** — на своём сервере у нас есть `postgres`. Делаем «по-человечески», чтобы команда `audit:rebuild-chain` работала без ручного `sudo -u postgres`. Полный redesign (`SECURITY DEFINER`) откладываем до Пути А.

**Files:**
- Modify: `app/config/database.php` (добавить connection `pgsql_postgres`)
- Modify: `app/app/Console/Commands/AuditRebuildChain.php` (использовать `pgsql_postgres`)
- Test: `app/tests/Feature/AuditRebuildChainConnectionTest.php`

- [ ] **Step 1: Написать падающий тест** — команда `audit:rebuild-chain` должна использовать connection с правом `session_replication_role` (superuser), а не `crm_supplier_worker`:
```php
public function test_rebuild_uses_superuser_connection(): void
{
    $cmd = app(\App\Console\Commands\AuditRebuildChain::class);
    $ref = new \ReflectionMethod($cmd, 'connectionName');
    $this->assertSame('pgsql_postgres', $ref->invoke($cmd));
}
```

- [ ] **Step 2: Прогнать — должен упасть** (`connectionName` нет / возвращает `pgsql_supplier`).
Run: `composer test -- --filter=AuditRebuildChainConnectionTest`
Expected: FAIL.

- [ ] **Step 3: Добавить connection + метод** — в `config/database.php` блок `pgsql_postgres` (env `DB_POSTGRES_USERNAME`/`PASSWORD`, на проде = `postgres`); в команде — `protected function connectionName(): string { return 'pgsql_postgres'; }` и использовать его в местах с `session_replication_role`.

- [ ] **Step 4: Прогнать — PASS.**
Run: `composer test -- --filter=AuditRebuildChainConnectionTest`
Expected: PASS. Затем `composer pint && composer stan`.

- [ ] **Step 5: Commit**
```bash
git add app/config/database.php app/app/Console/Commands/AuditRebuildChain.php app/tests/Feature/AuditRebuildChainConnectionTest.php
git commit -m "fix(audit): rebuild-chain через выделенный superuser-коннекшен (без ручного sudo)"
```

---

## Self-Review (выполнено автором плана)

- **Покрытие спеки assessment §4 (Путь Б):** копии «на сторону» → Phase 1; реплика → Phase 3; отдельный сервер → Phase 4; починка аудита → Phase 5. ✅ Все 4 вехи покрыты.
- **Деньги-инвариант** проверяется в Task 0/2/5/6 (1 836 400.00 / 1013). ✅
- **Прод-гейты** проставлены на всех боевых шагах (`[PROD-GATE]` + `prod-deploy-validator`). ✅
- **Имена-консистентность:** money-check запрос определяется один раз в Task 0 и переиспользуется; `S3_BUCKET`/`s3-backup.env` единообразны во всех скриптах. ✅
- **Открытый риск:** точная таблица/колонка баланса подтверждается в Phase 0 (не выдумана) — Task 0 Step 1 это фиксирует. ✅
```
