Sprint 0: хранилище состояния (SQLite WAL), миграции, audit log, атомарный apply/rollback #20

Closed
opened 2026-10-21 23:24:17 +03:00 by embeddevio · 5 comments
Owner

Owner: backend-engineer · Est: 5 SP · Priority: P0 · Sprint: 0 · Deps: —
Refs #15

Что сделать

  • SQLite (WAL) в /data/awg-dash.db: правила маршрутизации, исключения, режим, расписание GeoIP, метаданные конфигов, устройства, audit log.
  • Механизм миграций (alembic или собственный versioned-runnable) с проверкой версии схемы при старте.
  • Аудит: каждая мутация (конфиг, правила, режим, GeoIP) пишет журнал «кто/когда/что/old→new».
  • Атомарное применение: validate → stage → apply → rollback (хранение предыдущего состояния конфига/правил).

Acceptance criteria

  • GIVEN пустой /data, WHEN старт приложения, THEN миграции применяются и создаётся схема v1 без ручных действий.
  • GIVEN изменение правила через сервисный слой, WHEN операция завершена, THEN в audit_log есть запись с old/new значениями и временем.
  • GIVEN ошибочный apply (мок падения iptables), WHEN выполнен apply, THEN состояние возвращается к предыдущему и API отвечает ошибкой, а не частично применённой конфигурацией.
  • GIVEN рестарт контейнера, WHEN он завершён, THEN все правила и конфиг на месте (persistence в /data, /config).

DoD

Тесты на новый код ≥ 70 %, CI green, PR с ревью владельца роли.

**Owner:** backend-engineer · **Est:** 5 SP · **Priority:** P0 · **Sprint:** 0 · **Deps:** — Refs #15 ### Что сделать - SQLite (WAL) в `/data/awg-dash.db`: правила маршрутизации, исключения, режим, расписание GeoIP, метаданные конфигов, устройства, audit log. - Механизм миграций (alembic или собственный versioned-runnable) с проверкой версии схемы при старте. - Аудит: каждая мутация (конфиг, правила, режим, GeoIP) пишет журнал «кто/когда/что/old→new». - Атомарное применение: `validate → stage → apply → rollback` (хранение предыдущего состояния конфига/правил). ### Acceptance criteria - GIVEN пустой `/data`, WHEN старт приложения, THEN миграции применяются и создаётся схема v1 без ручных действий. - GIVEN изменение правила через сервисный слой, WHEN операция завершена, THEN в `audit_log` есть запись с old/new значениями и временем. - GIVEN ошибочный apply (мок падения iptables), WHEN выполнен apply, THEN состояние возвращается к предыдущему и API отвечает ошибкой, а не частично применённой конфигурацией. - GIVEN рестарт контейнера, WHEN он завершён, THEN все правила и конфиг на месте (persistence в `/data`, `/config`). ### DoD Тесты на новый код ≥ 70 %, CI green, PR с ревью владельца роли.
embeddevio added this to the Sprint 0 — Фундамент: монорепо, CI, Docker-образ milestone 2026-10-21 23:24:17 +03:00
embeddevio added the backendstatus/ready labels 2026-10-21 23:24:17 +03:00
Author
Owner

Backend Engineer: реализовано в PR #50 (ветка feat/20-storage, от main).

Что закрыто по этому issue

  • SQLite (WAL) /data/awg-dash.db: routing_rules, rule_exceptions, routing_mode, geoip_schedule, configs, devices, audit_log, state_snapshots, schema_migrations (backend/awg_api/store/).
  • Версионированные миграции + verify_schema() при старте (новее ожидаемой / не мигрирована → SchemaError; откат схемы не автоматизируется — ADR-006).
  • Аудит «кто/когда/что/old→new» на все мутации сервисного слоя; запись и мутация — одна транзакция.
  • Атомарные validate → stage → apply → rollback: снимок в state_snapshots, падение внешнего применителя (мок iptables) → откат БД + Applier.revert(previous) + ApplyError(restored=True).

Доказательства (локальный полный гейт): ruff check — All checks passed; ruff format --check — 15 files formatted; mypy strict — no issues in 20 source files; pytest — 100 passed, coverage 97.95 % (порог 70 %). Содержимое ветки сверено с локальным по git-blob SHA: 18/18 файлов совпадают.

Блокеры гейта «CI green»

  1. #17 (CI) не влит — пайплайна в main нет, на ветке он тоже отсутствует.
  2. #16 / PR #47 не влит — в ветке нет pyproject.toml (конфиг ruff/mypy/pytest и порог покрытия). После влития #47 ветку можно подтянуть.
  3. Найден дефект конфига из #16: RUF001/RUF002 (включены через RUF) ломаются на любых русскоязычных docstring, включая backend/awg_agent/__init__.py из самого #16. Лечится строкой ignore = ["S101", "RUF001", "RUF002"] в pyproject.toml.

Запрос на ревью отправлен в Gitea не может быть оформлен технически: единственный пользователь в Gitea — embeddevio, он же автор PR («poster of pr can't be reviewer»). Ревью владельца роли прошу провести через Kanban (карточка t_2dec61bb).

Backend Engineer: реализовано в PR #50 (ветка `feat/20-storage`, от `main`). **Что закрыто по этому issue** - SQLite (WAL) `/data/awg-dash.db`: `routing_rules`, `rule_exceptions`, `routing_mode`, `geoip_schedule`, `configs`, `devices`, `audit_log`, `state_snapshots`, `schema_migrations` (`backend/awg_api/store/`). - Версионированные миграции + `verify_schema()` при старте (новее ожидаемой / не мигрирована → `SchemaError`; откат схемы не автоматизируется — ADR-006). - Аудит «кто/когда/что/old→new» на все мутации сервисного слоя; запись и мутация — одна транзакция. - Атомарные `validate → stage → apply → rollback`: снимок в `state_snapshots`, падение внешнего применителя (мок iptables) → откат БД + `Applier.revert(previous)` + `ApplyError(restored=True)`. **Доказательства** (локальный полный гейт): `ruff check` — All checks passed; `ruff format --check` — 15 files formatted; `mypy` strict — no issues in 20 source files; `pytest` — 100 passed, coverage 97.95 % (порог 70 %). Содержимое ветки сверено с локальным по git-blob SHA: 18/18 файлов совпадают. **Блокеры гейта «CI green»** 1. #17 (CI) не влит — пайплайна в `main` нет, на ветке он тоже отсутствует. 2. #16 / PR #47 не влит — в ветке нет `pyproject.toml` (конфиг ruff/mypy/pytest и порог покрытия). После влития #47 ветку можно подтянуть. 3. Найден дефект конфига из #16: `RUF001`/`RUF002` (включены через `RUF`) ломаются на любых русскоязычных docstring, включая `backend/awg_agent/__init__.py` из самого #16. Лечится строкой `ignore = ["S101", "RUF001", "RUF002"]` в `pyproject.toml`. Запрос на ревью отправлен в Gitea не может быть оформлен технически: единственный пользователь в Gitea — `embeddevio`, он же автор PR («poster of pr can't be reviewer»). Ревью владельца роли прошу провести через Kanban (карточка t_2dec61bb).
embeddevio added status/review and removed status/ready labels 2026-10-21 23:46:23 +03:00
Author
Owner

Backend Engineer — исправление ранее отправленного комментария (пункт 3).

Я ошибочно написал, что RUF001/RUF002 из конфига #16 ломают модуль backend/awg_agent/__init__.py. Проверка показала обратное:

$ ruff check backend/awg_agent/__init__.py
All checks passed!
$ ruff check backend
All checks passed!

Эти правила срабатывают только на кириллических токенах, целиком состоящих из «конфузных» латиноподобных букв (одиночные а/с, слова Все, его, ОС); обычные русские фразы с другими буквами проходят. Код PR #50 поднят до чистого ruff check под неизменённым конфигом #16 — правка pyproject.toml не требуется, предложение внести RUF001/RUF002 в ignore снято.

Остальные пункты комментария выше в силе (зависимость от #17 для CI green и от #47 для каркаса/конфигов).

Backend Engineer — **исправление ранее отправленного комментария** (пункт 3). Я ошибочно написал, что `RUF001`/`RUF002` из конфига #16 ломают модуль `backend/awg_agent/__init__.py`. Проверка показала обратное: ``` $ ruff check backend/awg_agent/__init__.py All checks passed! $ ruff check backend All checks passed! ``` Эти правила срабатывают только на кириллических токенах, **целиком** состоящих из «конфузных» латиноподобных букв (одиночные `а`/`с`, слова `Все`, `его`, `ОС`); обычные русские фразы с другими буквами проходят. Код PR #50 поднят до чистого `ruff check` под неизменённым конфигом #16 — правка `pyproject.toml` не требуется, предложение внести `RUF001`/`RUF002` в `ignore` снято. Остальные пункты комментария выше в силе (зависимость от #17 для CI green и от #47 для каркаса/конфигов).
Author
Owner

TechLead — разбор блокировки + переназначение ревьюера (22.10, 00:14).

Что произошло. Первая попытка закрылась штатно: run #6 → review_requested, PR #50 создан от main (18 файлов, +3282), локальный гейт зелёный (ruff/format/mypy strict/pytest 100 passed, coverage 97.95 %). Последующие run #8 и #12 — это уже review-раны, и они упали rc=1 из-за исчерпанного дневного лимита ключа polza (эскалация #51, каскад «квота → невозможность сжатия контекста → краш → auto-block»).

Дефект процесса, который был рядом: ревью диспатчилось на того же backend-engineer, который реализовал PR (self-review), потому что ревьюер при review_requested не был задан. Это не гейт.

Сделано:

  • Карточка t_2dec61bb — статус review, ревьюер переназначен на qa-engineer (claim самопроверки снят с записью причины в событиях карточки).
  • Ревьюеру передан summary с точками проверки: AC #20 (пустой /data → схема v1; запись в audit_log; откат при падении внешнего применителя; сохранность после рестарта), отсутствие частичного применения, соответствие ADR-006 (нет авто-отката схемы, нет ротации audit_log — это #28).

Что осталось для DoD: CI green — нужен runner (#52) и влитие PR #47 (каркас + pyproject.toml) до #50; docker/netns-прогон в агентском окружении невозможен.

Таймлайн для памяти: не считать этот issue закрытым до ревью qa-engineer — статус status/review остаётся.

**TechLead — разбор блокировки + переназначение ревьюера (22.10, 00:14).** **Что произошло.** Первая попытка закрылась штатно: run #6 → `review_requested`, PR #50 создан от `main` (18 файлов, +3282), локальный гейт зелёный (ruff/format/mypy strict/pytest 100 passed, coverage 97.95 %). Последующие run #8 и #12 — это уже **review-раны**, и они упали `rc=1` из-за исчерпанного дневного лимита ключа polza (эскалация #51, каскад «квота → невозможность сжатия контекста → краш → auto-block»). **Дефект процесса, который был рядом:** ревью диспатчилось на **того же** `backend-engineer`, который реализовал PR (self-review), потому что ревьюер при `review_requested` не был задан. Это не гейт. **Сделано:** - Карточка `t_2dec61bb` — статус `review`, ревьюер переназначен на `qa-engineer` (claim самопроверки снят с записью причины в событиях карточки). - Ревьюеру передан summary с точками проверки: AC #20 (пустой `/data` → схема v1; запись в `audit_log`; откат при падении внешнего применителя; сохранность после рестарта), отсутствие частичного применения, соответствие ADR-006 (нет авто-отката схемы, нет ротации `audit_log` — это #28). **Что осталось для DoD:** CI green — нужен runner (#52) и влитие PR #47 (каркас + `pyproject.toml`) до #50; docker/netns-прогон в агентском окружении невозможен. **Таймлайн для памяти:** не считать этот issue закрытым до ревью `qa-engineer` — статус `status/review` остаётся.
Author
Owner

QA / ревью-ран t_2dec61bb по PR #50: FAIL — changes requested, PR помечен blocking (status/blocked).

Воспроизведено независимо на отпечатке ветки (blob SHA 18/18): ruff/format чисто; pytest 98 passed на файлах PR (глобально 100 — с 2 тестами из #16), coverage 97.94 %; mypy проходит только в дереве с файлами #16 (на чистой ветке падает из-за отсутствия backend/awg_api/__init__.py из #47).

Блокирует merge дефект конкурентного доступа в слое хранения: чтения Store идут по общему соединению без блокировки — TypeError/IndexError в list_rules() при 4 потоках (3/5 прогонов, потеря 4–38 мутаций), видимость незафиксированных строк и отдача наружу состояния, которое было откачено падающим apply (AC3).

  • Баг #54 (critical/blocker) — воспроизведения и требования к правке.
  • Баг #55 — BaseException в apply без revert/failed-записи; TOCTOU в set_geoip_schedule.

Открытый пункт DoD «CI green» остаётся недостижимым до влития #47 → #17 (в ветке нет pyproject.toml, пайплайна в main нет); это не дефект кода. Полный отчёт — в карточке t_2dec61bb (qa_report_pr50.md).

QA / ревью-ран t_2dec61bb по PR #50: **FAIL — changes requested, PR помечен blocking** (`status/blocked`). Воспроизведено независимо на отпечатке ветки (blob SHA 18/18): ruff/format чисто; pytest **98 passed** на файлах PR (глобально 100 — с 2 тестами из #16), coverage **97.94 %**; `mypy` проходит только в дереве с файлами #16 (на чистой ветке падает из-за отсутствия `backend/awg_api/__init__.py` из #47). Блокирует merge дефект конкурентного доступа в слое хранения: чтения `Store` идут по общему соединению без блокировки — `TypeError/IndexError` в `list_rules()` при 4 потоках (3/5 прогонов, потеря 4–38 мутаций), видимость незафиксированных строк и отдача наружу состояния, которое было откачено падающим `apply` (AC3). - Баг #54 (critical/blocker) — воспроизведения и требования к правке. - Баг #55 — `BaseException` в `apply` без `revert`/`failed`-записи; TOCTOU в `set_geoip_schedule`. Открытый пункт DoD «CI green» остаётся недостижимым до влития #47 → #17 (в ветке нет `pyproject.toml`, пайплайна в `main` нет); это не дефект кода. Полный отчёт — в карточке t_2dec61bb (`qa_report_pr50.md`).
Author
Owner

TechLead — решение по ревью и возврат в работу (28.10, 20:5x).

QA-ревью (раунд 1, run 5) принято как валидное: вердикт CHANGES REQUESTED, PR #50 помечен blocking, дефекты вынесены в #54 (critical) и #55. Карточка t_2dec61bb переведена из review обратно в реализацию (ready), исполнитель — backend-engineer (тот же, кто делал PR).

Что должно быть доведено в PR #50 (ветка feat/20-storage):

  1. #54 (critical): сериализовать чтения (RLock) ИЛИ соединение на поток ИЛИ явный запрет многопоточности — плюс честная документация контракта в README модуля. Сейчас: TypeError/IndexError в list_rules (3/5 прогонов, потеря 4–38 мутаций), видимость незафиксированных строк, отдача наружу состояния, которое было откачено падающим apply (нарушение AC «нет частично применённой конфигурации»).
  2. #55 D-1: except Exception вокруг apply не покрывает BaseException/CancelledError → нет Applier.revert и нет failed-записи в аудите (остаётся staged без исхода).
  3. #55 D-2: TOCTOU в set_geoip_schedule — чтение old_value перенести внутрь транзакции.

Обязательные тесты (требование ревьюера): N потоков × M upsert со сверкой rules == audit_rows == N*M и нулём ошибок; «чтение не видит незафиксированных строк»; BaseException → revert вызван + failed в аудите; конкурентные set_geoip_schedule с корректным old_value.

Приёмка: новый head-SHA ветки, локальный гейт (ruff/format/mypy/pytest+coverage) с выводом в комментарии, отчёт в #54/#55 со ссылкой на коммит, затем повторное ревью у qa-engineer. Мерж — только после зелёного ревью и апрува владельца; порядок влития: #47 → #49 → #50.

**TechLead — решение по ревью и возврат в работу (28.10, 20:5x).** QA-ревью (раунд 1, run 5) принято как валидное: вердикт **CHANGES REQUESTED**, PR #50 помечен blocking, дефекты вынесены в #54 (critical) и #55. Карточка `t_2dec61bb` переведена из `review` обратно в реализацию (`ready`), исполнитель — `backend-engineer` (тот же, кто делал PR). **Что должно быть доведено в PR #50 (ветка `feat/20-storage`):** 1. **#54 (critical):** сериализовать чтения (RLock) ИЛИ соединение на поток ИЛИ явный запрет многопоточности — плюс честная документация контракта в README модуля. Сейчас: `TypeError`/`IndexError` в `list_rules` (3/5 прогонов, потеря 4–38 мутаций), видимость незафиксированных строк, отдача наружу состояния, которое было откачено падающим apply (нарушение AC «нет частично применённой конфигурации»). 2. **#55 D-1:** `except Exception` вокруг apply не покрывает `BaseException`/`CancelledError` → нет `Applier.revert` и нет `failed`-записи в аудите (остаётся `staged` без исхода). 3. **#55 D-2:** TOCTOU в `set_geoip_schedule` — чтение `old_value` перенести внутрь транзакции. **Обязательные тесты (требование ревьюера):** N потоков × M upsert со сверкой `rules == audit_rows == N*M` и нулём ошибок; «чтение не видит незафиксированных строк»; `BaseException` → `revert` вызван + `failed` в аудите; конкурентные `set_geoip_schedule` с корректным `old_value`. **Приёмка:** новый head-SHA ветки, локальный гейт (ruff/format/mypy/pytest+coverage) с выводом в комментарии, отчёт в #54/#55 со ссылкой на коммит, затем повторное ревью у `qa-engineer`. Мерж — только после зелёного ревью и апрува владельца; порядок влития: #47 → #49 → #50.
embeddevio added status/in-progress and removed status/review labels 2026-10-28 20:57:08 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: embeddevio/awg-dash#20