docs(): added RUNBOOK, compliance matrix, ADR, refactored C4 and
repository map zavoz
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# 1. Цели архитектуры
|
||||||
|
|
||||||
|
1. Обеспечить воспроизводимый критичный поток `decide -> event -> report/guardrail` без ручных допущений в runtime.
|
||||||
|
2. Гарантировать безопасный lifecycle эксперимента: ревью, статусные переходы, блокировка конфликтных запусков.
|
||||||
|
3. Сохранить корректность данных для аналитики: идемпотентный приём событий, атрибуция через `decision_id`, обработка out-of-order через `PendingEvent`.
|
||||||
|
4. Поддержать операционную проверяемость: `health/readiness`, метрики, структурированные логи, фоновые задачи.
|
||||||
|
5. Дать артефакты, по которым жюри может пройти критерии `B1..B10` и выбранные `FX` без домысливания.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 2. Контекст и границы
|
||||||
|
|
||||||
|
## Внутри системы
|
||||||
|
|
||||||
|
- Один backend-контейнер (Django + Django Ninja API).
|
||||||
|
- Доменные модули в `apps/*`, HTTP-слой в `api/v1/*`.
|
||||||
|
- Хранение: реляционная БД (PostgreSQL в целевом окружении, SQLite по умолчанию локально).
|
||||||
|
- Кэш/брокер: Redis/Valkey в целевом окружении, LocMem cache локально/в тестах.
|
||||||
|
- Фоновые задачи: Celery worker/beat (guardrails, notifications, cleanup pending).
|
||||||
|
|
||||||
|
## Внешние акторы
|
||||||
|
|
||||||
|
- Product client: вызывает `POST /api/v1/decide`, отправляет `POST /api/v1/events`.
|
||||||
|
- Experimenter/Approver/Admin/Viewer: управляют экспериментами и смотрят результаты через API.
|
||||||
|
- Каналы уведомлений: Telegram API и SMTP.
|
||||||
|
- Система наблюдаемости: скрейпинг `/metrics`, чтение структурированных логов.
|
||||||
|
|
||||||
|
## Служебные контракты
|
||||||
|
|
||||||
|
- `GET /health`: liveness.
|
||||||
|
- `GET /ready`: readiness (cache, db, storage, celery ping).
|
||||||
|
- `GET /metrics`: prometheus endpoint.
|
||||||
|
|
||||||
|
## Явная граница фактов
|
||||||
|
|
||||||
|
Документация привязана к текущей реализации в `src/backend`.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 3. Драйверы и NFR
|
||||||
|
|
||||||
|
## Корректность и воспроизводимость
|
||||||
|
|
||||||
|
- Детерминированная раздача варианта через SHA-256 hash по `subject_id` и `experiment_id`.
|
||||||
|
- Один активный эксперимент на флаг (`running/paused`) через DB constraint.
|
||||||
|
- Строгие переходы lifecycle и блокировка недопустимых переходов.
|
||||||
|
- Идемпотентность событий по `event_id`.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Guardrails с порогом, окном наблюдения и автоматическим действием (`pause` или `rollback`).
|
||||||
|
- Ограничение участия пользователя: max concurrent + cooldown.
|
||||||
|
- Conflict domains для детерминированного разрешения коллизий экспериментов.
|
||||||
|
|
||||||
|
## Целостность аналитики
|
||||||
|
|
||||||
|
- Атрибуция через `decision_id`.
|
||||||
|
- Для событий с `requires_exposure=True`: без exposure событие уходит в `PendingEvent` и промотируется позже.
|
||||||
|
- Отчёт строится по вариантам и выбранным метрикам эксперимента.
|
||||||
|
|
||||||
|
## Эксплуатация
|
||||||
|
|
||||||
|
- Health/readiness probes.
|
||||||
|
- Prometheus-инструментация HTTP и бизнес-счётчиков.
|
||||||
|
- Structured logging с correlation id.
|
||||||
|
- Регламентные фоновые задачи Celery.
|
||||||
|
|
||||||
|
## Производительность
|
||||||
|
|
||||||
|
Реализовано:
|
||||||
|
- кэш флагов и активных экспериментов;
|
||||||
|
- кэш результата `decide` (TTL через `DECISION_RESULT_CACHE_TTL_SECONDS`);
|
||||||
|
- режим записи `Decision`: `sync|async|disabled` (`DECISION_WRITE_MODE`) с принудительным sync для `experiment_assigned`;
|
||||||
|
- async persistence через `events.persist_decision`;
|
||||||
|
- `reports` считает `average` через `Avg`, `percentile` через DB aggregate (`PERCENTILE_CONT` для PostgreSQL);
|
||||||
|
- в `reports` убрана materialization `decision_ids`, используется `Subquery`.
|
||||||
|
|
||||||
|
Текущие ограничения:
|
||||||
|
- в режиме `async` устойчивость записи зависит от здоровья Celery worker/broker;
|
||||||
|
- SQL-ветка percentile зависит от СУБД (PostgreSQL/не-PostgreSQL fallback);
|
||||||
|
- отдельный нагрузочный benchmark-артефакт не приложен.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# 4. Ключевые архитектурные решения
|
||||||
|
|
||||||
|
## Базовые решения
|
||||||
|
|
||||||
|
| ID | Решение | Зачем | Последствия |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ADR-01 | Модульный monolith на Django + Django Ninja | Быстрый delivery и единая транзакционная модель | Вертикальное масштабирование, не микросервисы |
|
||||||
|
| ADR-02 | Разделение на `apps/*` и `api/v1/*` | Чёткие границы между domain и transport | Нужна дисциплина зависимостей |
|
||||||
|
| ADR-03 | Hash-based deterministic assignment в `decide` | Выполнение B2-4 (стабильность результата) | Чувствительность к качеству `subject_id` |
|
||||||
|
| ADR-04 | Review policy: `ApproverGroup` + fallback `ReviewSettings` | Выполнение требований по ревью и fallback | Политика плоская, без орг-иерархий |
|
||||||
|
| ADR-05 | Lifecycle как state machine + validators + DB constraint | Невалидные переходы и двойной запуск на флаг блокируются | Правила статусов централизованы |
|
||||||
|
| ADR-06 | Event ingestion с дедупом и `PendingEvent` | Идемпотентность и out-of-order атрибуция | Дополнительная сложность обработки pending |
|
||||||
|
| ADR-07 | Guardrails как first-class модели + auto action | Авто-реакция на деградацию и аудит триггера | Риск ложных срабатываний при шумных данных |
|
||||||
|
| ADR-08 | Conflict domains (`mutual_exclusion`, `priority`) | Детерминированное разрешение пересечений экспериментов | Дополнительные проверки на старте и в decide |
|
||||||
|
| ADR-09 | Базовая observability (`health`, `ready`, `/metrics`, JSON logs) | Проверяемость B9 и эксплуатационная диагностика | SLA readiness подтверждается только live-demo |
|
||||||
|
| ADR-10 | Проверка качества через `just` и автотесты | Воспроизводимая верификация B1/B8/B10 | - |
|
||||||
|
| ADR-11 | Кэш результата `decide` в cache backend (Redis/Valkey в целевом окружении) | Снизить CPU/DB в hot-path | Ниже latency на повторяющихся запросах; риск stale-ответа в рамках TTL |
|
||||||
|
| ADR-12 | Режим записи `Decision`: `sync\|async\|disabled`; async через `events.persist_decision`; `experiment_assigned` всегда sync | Снять write-pressure с hot-path без потери атрибуции | В `async` режиме нужна стабильность broker/worker и мониторинг очереди |
|
||||||
|
| ADR-13 | RBAC через роли `admin/experimenter/approver/viewer` + JWT bearer + endpoint guards | Выполнение требований доступа и ревью-ответственности из раздела 0 ТЗ | Вся авторизация централизована в role guards; нужен контроль качества секретов JWT в окружении |
|
||||||
|
| ADR-14 | Типизированные feature flags (`string/boolean/integer`) и публичный контракт обновления только `default_value` | Исключить несогласованные значения флагов и обеспечить безопасные переключения без релиза | Смена `key/value_type` решается созданием нового флага и миграцией использования |
|
||||||
|
| ADR-15 | Каталог типов событий (`EventType`) с `required_fields`, `is_exposure`, `requires_exposure` | Явно отделить схему событий от runtime-обработки и обеспечить корректную атрибуцию | Требуется дисциплина ведения каталога; неактивные типы событий блокируют ingestion |
|
||||||
|
| ADR-16 | Каталог метрик (`MetricDefinition`) + явная привязка метрик к эксперименту (`ExperimentMetric`) | Выполнение требований 5.4 ТЗ и единый расчёт для reports/guardrails | Изменение `calculation_rule` влияет на интерпретацию отчётов и требует управляемых изменений |
|
||||||
|
| ADR-17 | Завершение эксперимента через отдельную сущность `ExperimentOutcome` с обязательным `rationale` и валидируемыми outcome-сценариями | Выполнение требований 2.6 и B6-4/B6-5 | Решение по rollout/rollback/no_effect становится аудитируемым и воспроизводимым |
|
||||||
|
| ADR-18 | Уведомления как rule-based pipeline: `NotificationRule` -> `NotificationLog` -> async flush, с dedup/rate-limit по `event_key` | Закрыть FX notifications без “шторма” сообщений | Ограничение по каналам (`telegram`, `smtp`), требуется мониторинг failed delivery |
|
||||||
|
| ADR-19 | Поиск по эвристике среди Learnings | Реализовать поиск по базе | Поиск эвристический (веса/Jaccard), не ML и требует аккуратной интерпретации |
|
||||||
|
|
||||||
|
## Неочевидные инварианты
|
||||||
|
|
||||||
|
- В один момент времени на один `FeatureFlag` разрешён только один активный эксперимент (`running|paused`) через partial `UniqueConstraint`.
|
||||||
|
- После старта эксперимента заморожены поля, влияющие на раздачу (`traffic_allocation`, `targeting_rules`, `flag`), и запрещены изменения вариантов вне `draft`.
|
||||||
|
- Перед отправкой на review проверяются инварианты состава вариантов: минимум 2, ровно 1 control, сумма весов строго равна `traffic_allocation`.
|
||||||
|
- Один approver может оставить только одно одобрение по эксперименту (`unique_approval_per_user`).
|
||||||
|
- Для `requires_exposure=True` событие без exposure не атрибутируется сразу: уходит в `PendingEvent`, промотируется после прихода exposure, затем очищается по TTL.
|
||||||
|
- Одна метрика не может быть прикреплена к эксперименту дважды (`unique_experiment_metric`).
|
||||||
|
- `Learning` хранится в one-to-one связи с экспериментом.
|
||||||
|
|
||||||
|
## Ключевые риски
|
||||||
|
|
||||||
|
| Риск | Проявление | Смягчение | Остаток |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Stale ответ из кэша `decide` | Кратковременный возврат устаревшего результата в пределах TTL | Revision-aware cache key + короткий TTL | Низкий/средний |
|
||||||
|
| Потеря throughput при проблемах Celery в `async` | Очередь растёт, запись `Decision` отстаёт | Режимы `sync|async|disabled`, force-sync для `experiment_assigned`, fallback на sync при ошибке enqueue | Средний |
|
||||||
|
| Тяжёлые запросы отчётов на больших данных | Рост latency для percentile/агрегаций | DB aggregate + `Subquery`, фильтрация attributed событий, индексы | Средний |
|
||||||
|
| Потеря отложенных атрибуций | `PendingEvent` истекает до прихода exposure | TTL 7 дней + cleanup + промоция при exposure | Средний |
|
||||||
|
| Сбой каналов уведомлений | Отправка не доходит в Telegram/SMTP | `NotificationLog` со статусами и ошибками | Средний |
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 5. Критичный путь
|
||||||
|
|
||||||
|
## Decide (`apps/decision/services.py`)
|
||||||
|
|
||||||
|
1. `flag` из cache/DB.
|
||||||
|
2. `active_experiment` из cache/DB.
|
||||||
|
3. Формируется cache key результата (`flag`, `subject`, digest атрибутов, ревизии `flag/experiment`).
|
||||||
|
4. При cache hit возвращается тот же outcome/reason/value с новым `decision_id`.
|
||||||
|
5. При cache miss выполняются проверки: running experiment, targeting, participation limits, domain conflicts, traffic allocation.
|
||||||
|
6. Если назначен вариант, выбирается детерминированно по hash + weights.
|
||||||
|
7. Результат может быть закэширован (по whitelist reason).
|
||||||
|
8. Запись `Decision` зависит от `DECISION_WRITE_MODE`: `sync|async|disabled`; для `experiment_assigned` запись всегда sync.
|
||||||
|
|
||||||
|
Reason-коды: `flag_not_found`, `no_active_experiment`, `targeting_mismatch`, `participation_limit`, `domain_conflict`, `outside_traffic_allocation`, `no_variants`, `experiment_assigned`.
|
||||||
|
|
||||||
|
## Events (`apps/events/services.py`)
|
||||||
|
|
||||||
|
1. Валидация типа события и payload.
|
||||||
|
2. Дедуп по `event_id`.
|
||||||
|
3. Exposure: `Exposure` + `Event`, затем промоция `PendingEvent`.
|
||||||
|
4. Conversion: при `requires_exposure=True` без exposure -> `PendingEvent` (TTL 7 дней), иначе attributed `Event`.
|
||||||
|
|
||||||
|
## Reports (`apps/reports/services.py`)
|
||||||
|
|
||||||
|
- По каждому варианту: `exposures`, `unique_subjects`, значения выбранных метрик.
|
||||||
|
- Поддерживается период `start/end`.
|
||||||
|
- `average` считает DB `Avg`, `percentile` - DB aggregate (`PERCENTILE_CONT` в PostgreSQL).
|
||||||
|
- Связь событий с экспозициями строится через `Subquery`, без materialization списка `decision_ids` в Python.
|
||||||
|
|
||||||
|
## Guardrails (`apps/guardrails/services.py`)
|
||||||
|
|
||||||
|
- Celery проверяет running эксперименты.
|
||||||
|
- При breach: `GuardrailTrigger` + действие (`pause`/`rollback`) + `ExperimentLog` + enqueue уведомления.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 6. Эксплуатация и наблюдаемость
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
- `/health` -> liveness (`health_check.Memory`).
|
||||||
|
- `/ready` -> readiness (`Cache`, `Database`, `Storage`, `Celery Ping`).
|
||||||
|
- `/metrics` -> Prometheus metrics.
|
||||||
|
|
||||||
|
SLA `ready <= 180s` подтверждается только live-demo.
|
||||||
|
|
||||||
|
## Метрики и логи
|
||||||
|
|
||||||
|
- Инфраструктурные метрики через `django-prometheus` middleware.
|
||||||
|
- Бизнес-счётчики: `lotty_decide_requests_total`, `lotty_events_ingested_total`.
|
||||||
|
- Production logs: JSON + `Correlation-ID` (`django-guid`).
|
||||||
|
|
||||||
|
## Celery задачи
|
||||||
|
|
||||||
|
Периодические:
|
||||||
|
- `guardrails.check_all` - 60s.
|
||||||
|
- `notifications.flush_pending` - 30s.
|
||||||
|
- `events.cleanup_expired_pending` - 3600s.
|
||||||
|
|
||||||
|
По запросу:
|
||||||
|
- `events.persist_decision` - асинхронная запись `Decision` при `DECISION_WRITE_MODE=async`.
|
||||||
|
|
||||||
|
## Runtime knobs
|
||||||
|
|
||||||
|
- `DECISION_RESULT_CACHE_TTL_SECONDS` (по умолчанию `60`).
|
||||||
|
- `DECISION_WRITE_MODE` (`sync|async|disabled`, по умолчанию `sync`).
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
`cd src/backend && just run|test|test-coverage|lint|format`
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# 7. Явные упрощения
|
||||||
|
|
||||||
|
1. Один backend-сервис (модульный monolith), без выделения микросервисов по доменам.
|
||||||
|
2. Отдельный statistical significance engine не реализован; решения фиксируются на метриках и guardrails.
|
||||||
|
3. Отчёты считаются синхронно в запросе, без pre-aggregation pipeline и materialized views (потому что нету ClickHouse).
|
||||||
|
4. Режим `DECISION_WRITE_MODE=sync` используется по умолчанию; `async`/`disabled` - эксплуатационные режимы.
|
||||||
|
5. Кэш результата `decide` основан на TTL и revision-aware ключе, без отдельной ручной инвалидции.
|
||||||
|
6. Similar learnings считаются эвристикой (Jaccard/веса), без ML-ранжирования.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 8. Проверяемость
|
||||||
|
|
||||||
|
## Базовый набор
|
||||||
|
|
||||||
|
1. `cd src/backend && just --list`
|
||||||
|
2. `cd src/backend && just test`
|
||||||
|
3. `cd src/backend && just test-coverage && just show-coverage`
|
||||||
|
|
||||||
|
## Точечные проверки по доменам
|
||||||
|
|
||||||
|
1. `cd src/backend && uv run python manage.py test apps.decision.tests.test_decide`
|
||||||
|
2. `cd src/backend && uv run python manage.py test apps.events.tests.test_services tests.integration.test_events`
|
||||||
|
3. `cd src/backend && uv run python manage.py test apps.reports.tests.test_reports apps.guardrails.tests.test_guardrails`
|
||||||
|
4. `cd src/backend && uv run python manage.py test apps.reviews.tests.test_reviews_policy apps.experiments.tests.test_services`
|
||||||
|
5. `cd src/backend && uv run python manage.py test apps.conflicts.tests.test_conflicts apps.notifications.tests.test_notifications apps.learnings.tests.test_learnings`
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
- `GET /health` -> `200`
|
||||||
|
- `GET /ready` -> `200` после готовности зависимостей
|
||||||
|
- `GET /metrics` -> есть инфраструктурные и бизнес метрики
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# ADR - LOTTY Backend
|
||||||
|
|
||||||
|
## Состав
|
||||||
|
|
||||||
|
| Файл | Содержание |
|
||||||
|
|---|---|
|
||||||
|
| `01-goals.md` | Цели архитектуры |
|
||||||
|
| `02-context.md` | Границы системы и акторы |
|
||||||
|
| `03-drivers-nfr.md` | Драйверы и NFR |
|
||||||
|
| `04-decisions.md` | Ключевые решения, неочевидные инварианты и ключевые риски |
|
||||||
|
| `05-critical-path.md` | Критичный поток `decide -> event -> report/guardrail` |
|
||||||
|
| `06-operations.md` | Эксплуатация и наблюдаемость |
|
||||||
|
| `07-simplifications.md` | Явные упрощения |
|
||||||
|
| `08-verification.md` | Как проверять критерии |
|
||||||
|
|
||||||
|
Связанная матрица соответствия: [compliance-matrix.md](../compliance-matrix.md).
|
||||||
@@ -6,15 +6,17 @@
|
|||||||
- Django settings: [src/backend/config/settings](./src/backend/config/settings)
|
- Django settings: [src/backend/config/settings](./src/backend/config/settings)
|
||||||
- URL root: [src/backend/config/urls.py](./src/backend/config/urls.py)
|
- URL root: [src/backend/config/urls.py](./src/backend/config/urls.py)
|
||||||
- API wiring: [src/backend/api/urls.py](./src/backend/api/urls.py)
|
- API wiring: [src/backend/api/urls.py](./src/backend/api/urls.py)
|
||||||
- API v1 router: [src/backend/api/v1/router.py](./src/backend/api/v1/router.py)
|
- API v1 роутер: [src/backend/api/v1/router.py](./src/backend/api/v1/router.py)
|
||||||
- Команды и проверки: [src/backend/justfile](./src/backend/justfile)
|
- Команды и проверки: [src/backend/justfile](./src/backend/justfile)
|
||||||
|
- Файлы конфигурации деплоя: [deploy](./deploy)
|
||||||
|
- Файлы конфигурации для сервисов: [infrastructure/configs](./infrastructure/configs)
|
||||||
|
|
||||||
## 2. Основные доменные модули ([src/backend/apps](./src/backend/apps))
|
## 2. Основные доменные модули ([src/backend/apps](./src/backend/apps))
|
||||||
|
|
||||||
- [flags](./src/backend/apps/flags) - feature flags и типизация значений.
|
- [flags](./src/backend/apps/flags) - feature flags и типизация значений.
|
||||||
- [experiments](./src/backend/apps/experiments) - жизненный цикл эксперимента, варианты.
|
- [experiments](./src/backend/apps/experiments) - жизненный цикл эксперимента, варианты.
|
||||||
- [reviews](./src/backend/apps/reviews) - группы аппруверов, дефолтная политика апрува.
|
- [reviews](./src/backend/apps/reviews) - группы аппруверов, дефолтная политика апрува.
|
||||||
- [decision](./src/backend/apps/decision) - логика выбора варианта для флага (детерминизм, таргетинг, лимиты участия, конфликт-домены).
|
- [decision](./src/backend/apps/decision) - логика выбора варианта для флага (детерминизм, таргетинг, лимиты участия).
|
||||||
- [events](./src/backend/apps/events) - обработка эвентов: валидация, дедуп, атрибуция, pending events.
|
- [events](./src/backend/apps/events) - обработка эвентов: валидация, дедуп, атрибуция, pending events.
|
||||||
- [metrics](./src/backend/apps/metrics) - каталог и правила вычисления метрик.
|
- [metrics](./src/backend/apps/metrics) - каталог и правила вычисления метрик.
|
||||||
- [reports](./src/backend/apps/reports) - отчёты по экспериментам в разрезе вариантов/периода.
|
- [reports](./src/backend/apps/reports) - отчёты по экспериментам в разрезе вариантов/периода.
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ Service for managing A/B testing experiments. Drive your tests without breaking
|
|||||||
|
|
||||||
### Diagrams
|
### Diagrams
|
||||||
|
|
||||||
|
#### C4 Context
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Source: [C4 context](./assets/plantuml/raw/c4-context.puml)
|
||||||
|
|
||||||
|
#### C4 Container
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Source: [C4 container](./assets/plantuml/raw/c4-container.puml)
|
||||||
|
|
||||||
|
#### C4 Component
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Source: [C4 component](./assets/plantuml/raw/c4-component-critical-path.puml)
|
||||||
|
|
||||||
### Testing report
|
### Testing report
|
||||||
|
|
||||||
Can be seen in GitLab CI/CD pipeline run
|
Can be seen in GitLab CI/CD pipeline run
|
||||||
|
|||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
# Runbook
|
||||||
|
|
||||||
|
Этот runbook задаёт основной сценарий проверки по критериям `B1..B10` и выбранным `FX`.
|
||||||
|
|
||||||
|
## 1. Что проверяем
|
||||||
|
|
||||||
|
Сквозной поток:
|
||||||
|
- `decide` -> `events` -> `report`;
|
||||||
|
- lifecycle/review/guardrails;
|
||||||
|
- observability (`/health`, `/ready`, `/metrics`);
|
||||||
|
- инженерная дисциплина (tests, lint, format);
|
||||||
|
- выбранные FX: notifications, learnings, conflicts.
|
||||||
|
|
||||||
|
Артефакты:
|
||||||
|
- `compliance-matrix.md`
|
||||||
|
- `ADR/*`
|
||||||
|
- `MAP.md`
|
||||||
|
- `assets/plantuml/raw/c4-context.puml`
|
||||||
|
- `assets/plantuml/raw/c4-container.puml`
|
||||||
|
- `assets/plantuml/raw/c4-component-critical-path.puml`
|
||||||
|
|
||||||
|
## 2. Предусловия
|
||||||
|
|
||||||
|
### 2.1 Основной сценарий (по умолчанию): Docker Compose
|
||||||
|
|
||||||
|
Нужно:
|
||||||
|
- Docker
|
||||||
|
- Docker Compose
|
||||||
|
|
||||||
|
Файлы:
|
||||||
|
- `compose.yaml` - базовый стек
|
||||||
|
- `compose.prod.yaml` - базовый стек + observability
|
||||||
|
- Детали по compose-конфигурации: `README.md` -> `Setup with docker compose`.
|
||||||
|
|
||||||
|
Порты по умолчанию (см. `.env.template`):
|
||||||
|
- reverse proxy: `80`
|
||||||
|
- backend direct: `14609`
|
||||||
|
- static direct: `14610`
|
||||||
|
|
||||||
|
### 2.2 Дополнительный локальный сценарий (для тестов/линтинга)
|
||||||
|
|
||||||
|
Нужно:
|
||||||
|
- Python `>=3.13,<3.15`
|
||||||
|
- `uv`
|
||||||
|
- `just`
|
||||||
|
|
||||||
|
Рабочая директория:
|
||||||
|
- `cd src/backend`
|
||||||
|
|
||||||
|
## 3. Основной сценарий запуска (Docker Compose)
|
||||||
|
|
||||||
|
### 3.1 Выбор стека
|
||||||
|
|
||||||
|
Базовый стек:
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Полный стек (с observability):
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.prod.yaml --profile observability up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Проверка, что сервисы поднялись
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml ps
|
||||||
|
# или
|
||||||
|
docker compose -f compose.prod.yaml --profile observability ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаем `running/healthy` для `postgresql`, `valkey`, `backend`, `backend-celery-worker`, `backend-celery-beat`.
|
||||||
|
|
||||||
|
### 3.3 Runtime sanity-check
|
||||||
|
|
||||||
|
Через reverse proxy (`80`):
|
||||||
|
```bash
|
||||||
|
curl -i http://127.0.0.1/health
|
||||||
|
curl -i http://127.0.0.1/ready
|
||||||
|
curl -s http://127.0.0.1/metrics | head -n 40
|
||||||
|
```
|
||||||
|
|
||||||
|
Альтернатива напрямую в backend (`14609`):
|
||||||
|
```bash
|
||||||
|
curl -i http://127.0.0.1:14609/health
|
||||||
|
curl -i http://127.0.0.1:14609/ready
|
||||||
|
curl -s http://127.0.0.1:14609/metrics | head -n 40
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml logs -f backend backend-celery-worker backend-celery-beat
|
||||||
|
# или
|
||||||
|
docker compose -f compose.prod.yaml --profile observability logs -f backend backend-celery-worker backend-celery-beat
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Проверка критериев B1..B10
|
||||||
|
|
||||||
|
### B1. Запуск и воспроизводимость
|
||||||
|
|
||||||
|
Compose runtime-подтверждение:
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml up -d
|
||||||
|
docker compose -f compose.yaml ps
|
||||||
|
curl -i http://127.0.0.1/health
|
||||||
|
curl -i http://127.0.0.1/ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Показываем:
|
||||||
|
- сервисы запускаются без ручных скрытых шагов;
|
||||||
|
- backend жив и готов после зависимостей.
|
||||||
|
|
||||||
|
### B2-B6, B8, B10. Функциональность, отчёты, тестирование и дисциплина
|
||||||
|
|
||||||
|
Эти проверки выполняются локально из `src/backend` (dev-зависимости):
|
||||||
|
```bash
|
||||||
|
cd src/backend
|
||||||
|
just --list
|
||||||
|
just test
|
||||||
|
just test-coverage
|
||||||
|
just show-coverage
|
||||||
|
just lint
|
||||||
|
just format
|
||||||
|
```
|
||||||
|
|
||||||
|
Точечные наборы:
|
||||||
|
```bash
|
||||||
|
cd src/backend
|
||||||
|
uv run python manage.py test apps.decision.tests.test_decide
|
||||||
|
uv run python manage.py test apps.events.tests.test_services tests.integration.test_events
|
||||||
|
uv run python manage.py test apps.reports.tests.test_reports apps.guardrails.tests.test_guardrails
|
||||||
|
uv run python manage.py test apps.reviews.tests.test_reviews_policy apps.experiments.tests.test_services
|
||||||
|
uv run python manage.py test tests.integration.test_negative tests.integration.test_happy_path tests.integration.test_api_contract
|
||||||
|
```
|
||||||
|
|
||||||
|
### B7. Архитектурные артефакты
|
||||||
|
|
||||||
|
Проверяем:
|
||||||
|
- `compliance-matrix.md`
|
||||||
|
- `ADR/README.md`
|
||||||
|
- `ADR/04-decisions.md`
|
||||||
|
- `MAP.md`
|
||||||
|
- `assets/plantuml/raw/c4-context.puml`
|
||||||
|
- `assets/plantuml/raw/c4-container.puml`
|
||||||
|
- `assets/plantuml/raw/c4-component-critical-path.puml`
|
||||||
|
|
||||||
|
### B9. Эксплуатационная готовность и наблюдаемость
|
||||||
|
|
||||||
|
Runtime через compose:
|
||||||
|
```bash
|
||||||
|
curl -i http://127.0.0.1/health
|
||||||
|
curl -i http://127.0.0.1/ready
|
||||||
|
curl -s http://127.0.0.1/metrics | head -n 40
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Проверка выбранных допфич (FX)
|
||||||
|
|
||||||
|
Локально из `src/backend`:
|
||||||
|
|
||||||
|
FX: Notifications
|
||||||
|
```bash
|
||||||
|
uv run python manage.py test apps.notifications.tests.test_notifications api.v1.notifications.tests.test_notifications_api
|
||||||
|
```
|
||||||
|
|
||||||
|
FX: Learnings
|
||||||
|
```bash
|
||||||
|
uv run python manage.py test apps.learnings.tests.test_learnings api.v1.learnings.tests.test_learnings_api
|
||||||
|
```
|
||||||
|
|
||||||
|
FX: Conflicts
|
||||||
|
```bash
|
||||||
|
uv run python manage.py test apps.conflicts.tests.test_conflicts api.v1.conflicts.tests.test_conflicts_api
|
||||||
|
```
|
||||||
|
|
||||||
|
Объединённый прогон:
|
||||||
|
```bash
|
||||||
|
uv run python manage.py test \
|
||||||
|
apps.notifications.tests.test_notifications \
|
||||||
|
apps.learnings.tests.test_learnings \
|
||||||
|
apps.conflicts.tests.test_conflicts \
|
||||||
|
api.v1.notifications.tests.test_notifications_api \
|
||||||
|
api.v1.learnings.tests.test_learnings_api \
|
||||||
|
api.v1.conflicts.tests.test_conflicts_api
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Остановка и очистка
|
||||||
|
|
||||||
|
Базовый стек:
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
Полный стек:
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.prod.yaml --profile observability down
|
||||||
|
```
|
||||||
|
|
||||||
|
С удалением томов:
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yaml down -v
|
||||||
|
# или
|
||||||
|
docker compose -f compose.prod.yaml --profile observability down -v
|
||||||
|
```
|
||||||
@@ -7,20 +7,21 @@ title LOTTY Backend - C4 Component
|
|||||||
|
|
||||||
Container_Boundary(api, "Backend Container (Django + Django Ninja)") {
|
Container_Boundary(api, "Backend Container (Django + Django Ninja)") {
|
||||||
Component(decision_endpoint, "Decision Endpoint", "api/v1/decision/endpoints.py", "POST /api/v1/decide")
|
Component(decision_endpoint, "Decision Endpoint", "api/v1/decision/endpoints.py", "POST /api/v1/decide")
|
||||||
Component(decision_service, "Decision Service", "apps/decision/services.py", "Flag lookup, targeting DSL, participation limits, hash-based variant assignment")
|
Component(decision_service, "Decision Service", "apps/decision/services.py", "Flag lookup, targeting DSL, limits/conflicts, hash-based assignment, decide-result cache, sync/async decision persistence")
|
||||||
|
|
||||||
Component(events_endpoint, "Events Endpoint", "api/v1/events/endpoints.py", "POST /api/v1/events")
|
Component(events_endpoint, "Events Endpoint", "api/v1/events/endpoints.py", "POST /api/v1/events")
|
||||||
Component(events_service, "Events Service", "apps/events/services.py", "Validation, dedup, exposure/conversion attribution, pending promotion")
|
Component(events_service, "Events Service", "apps/events/services.py", "Validation, dedup, exposure/conversion attribution, pending promotion")
|
||||||
|
Component(events_tasks, "Events Tasks", "apps/events/tasks.py", "persist_decision, cleanup pending")
|
||||||
|
|
||||||
Component(reports_endpoint, "Reports Endpoint", "api/v1/reports/endpoints.py", "GET /reports/{experiment_id}")
|
Component(reports_endpoint, "Reports Endpoint", "api/v1/reports/endpoints.py", "GET /reports/{experiment_id}")
|
||||||
Component(reports_service, "Reports Service", "apps/reports/services.py", "Per-variant metric calculation (ratio, count, average, percentile)")
|
Component(reports_service, "Reports Service", "apps/reports/services.py", "Per-variant metrics (ratio/count/average/percentile)")
|
||||||
|
|
||||||
Component(guardrails_service, "Guardrails Service", "apps/guardrails/services.py", "Threshold checks, auto pause/rollback")
|
Component(guardrails_service, "Guardrails Service", "apps/guardrails/services.py", "Threshold checks, auto pause/rollback")
|
||||||
}
|
}
|
||||||
|
|
||||||
ContainerDb(db, "Relational DB", "PostgreSQL / SQLite", "Flags, experiments, variants, decisions, events, exposures")
|
ContainerDb(db, "Relational DB", "PostgreSQL / SQLite", "Flags, experiments, variants, decisions, events, exposures")
|
||||||
Container(cache, "Cache", "Valkey / LocMem", "Flag and active experiment cache")
|
Container(cache, "Cache + Broker", "Valkey / Redis / LocMem", "Flag/active experiment/decide-result cache, Celery broker")
|
||||||
Container(worker, "Celery Beat", "Celery", "Periodic guardrail checks (60s)")
|
Container(worker, "Celery Worker / Beat", "Celery", "Periodic guardrails/notifications/cleanup + async decision persistence")
|
||||||
|
|
||||||
Rel(decision_endpoint, decision_service, "Delegates")
|
Rel(decision_endpoint, decision_service, "Delegates")
|
||||||
Rel(decision_service, cache, "Reads cached flag / active experiment")
|
Rel(decision_service, cache, "Reads cached flag / active experiment")
|
||||||
@@ -28,6 +29,7 @@ Rel(decision_service, db, "Reads variants, writes Decision")
|
|||||||
|
|
||||||
Rel(events_endpoint, events_service, "Delegates batch")
|
Rel(events_endpoint, events_service, "Delegates batch")
|
||||||
Rel(events_service, db, "Writes Event / Exposure / PendingEvent")
|
Rel(events_service, db, "Writes Event / Exposure / PendingEvent")
|
||||||
|
Rel(events_tasks, db, "Writes Decision async")
|
||||||
|
|
||||||
Rel(reports_endpoint, reports_service, "Builds report")
|
Rel(reports_endpoint, reports_service, "Builds report")
|
||||||
Rel(reports_service, db, "Reads attributed events / exposures")
|
Rel(reports_service, db, "Reads attributed events / exposures")
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Person(ops_user, "Experimenter/Approver/Admin/Viewer")
|
|||||||
System_Boundary(lotty_boundary, "LOTTY Backend") {
|
System_Boundary(lotty_boundary, "LOTTY Backend") {
|
||||||
Container(api, "Backend API", "Django + Ninja", "REST endpoints: flags, experiments, decide, events, reports, guardrails, notifications, conflicts, metrics, learnings, reviews, users, auth")
|
Container(api, "Backend API", "Django + Ninja", "REST endpoints: flags, experiments, decide, events, reports, guardrails, notifications, conflicts, metrics, learnings, reviews, users, auth")
|
||||||
ContainerDb(db, "PostgreSQL / SQLite", "Relational DB", "Experiments, variants, decisions, events, exposures, outcomes, guardrails, notifications, conflicts, logs")
|
ContainerDb(db, "PostgreSQL / SQLite", "Relational DB", "Experiments, variants, decisions, events, exposures, outcomes, guardrails, notifications, conflicts, logs")
|
||||||
Container(cache, "Valkey / LocMem Cache", "Cache + Celery broker", "Flag/experiment cache, Celery task broker")
|
Container(cache, "Valkey / Redis / LocMem Cache", "Cache + Celery broker", "Flag/experiment/decide-result cache, Celery task broker")
|
||||||
Container(worker, "Celery Worker / Beat", "Background worker", "Periodic: guardrails check, notifications flush, pending events cleanup")
|
Container(worker, "Celery Worker / Beat", "Background worker", "Periodic jobs + async decision persistence")
|
||||||
}
|
}
|
||||||
|
|
||||||
System_Ext(notifications, "Notification Channels", "Telegram / SMTP")
|
System_Ext(notifications, "Notification Channels", "Telegram / SMTP")
|
||||||
@@ -20,9 +20,9 @@ System_Ext(notifications, "Notification Channels", "Telegram / SMTP")
|
|||||||
Rel(product_client, api, "Gets flag decisions, sends events", "HTTP/JSON")
|
Rel(product_client, api, "Gets flag decisions, sends events", "HTTP/JSON")
|
||||||
Rel(ops_user, api, "Admin/experiment/review calls", "HTTP/JSON")
|
Rel(ops_user, api, "Admin/experiment/review calls", "HTTP/JSON")
|
||||||
Rel(api, db, "Read/write application state", "SQL")
|
Rel(api, db, "Read/write application state", "SQL")
|
||||||
Rel(api, cache, "Read/write cache", "Redis protocol")
|
Rel(api, cache, "Read/write cache, enqueue async tasks", "Redis protocol")
|
||||||
Rel(worker, db, "Read/write application state", "SQL")
|
Rel(worker, db, "Read/write application state", "SQL")
|
||||||
Rel(worker, cache, "Receives tasks, reads cached state", "Redis protocol")
|
Rel(worker, cache, "Consumes tasks, reads cached state", "Redis protocol")
|
||||||
Rel(worker, notifications, "Delivers queued notifications", "HTTP / SMTP")
|
Rel(worker, notifications, "Delivers queued notifications", "HTTP / SMTP")
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,65 @@
|
|||||||
|
# Матрица соответствия: задание -> критерий -> реализация
|
||||||
|
|
||||||
|
Статусы:
|
||||||
|
- `подтверждено` - есть прямая реализация и проверка тестами/командами.
|
||||||
|
- `частично (live-demo)` - реализация есть, но критерий окончательно закрывается только на живом запуске.
|
||||||
|
|
||||||
|
| ID задания | ID критерия | Проблема/риск | Где реализовано | Как проверяется | Какие данные нужны | Статус |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| `D.4` | `B1-1` | Без предусловий жюри не сможет воспроизвести запуск | `RUNBOOK.md`, `src/backend/README.md` | Ручная проверка разделов предусловий | Python, uv, just, рабочая директория `src/backend` | подтверждено |
|
||||||
|
| `D.4` | `B1-2` | Неоднозначные команды старта ломают проверку | `RUNBOOK.md`, `src/backend/justfile` | `cd src/backend && just --list` | Доступ к `just` | подтверждено |
|
||||||
|
| `D.3` | `B1-3` | Скрытые ручные шаги делают запуск невоспроизводимым | `RUNBOOK.md`, `compose.yaml`, `compose.prod.yaml` | Полный прогон по runbook на стенде | Чистое окружение и runtime-сервисы | частично (live-demo) |
|
||||||
|
| `3.7` | `B1-4` | Сервис может стартовать, но быть неготовым к запросам | `src/backend/api/urls.py` (`/health`, `/ready`) | Runtime `curl /health` и `curl /ready` | Поднятый backend и зависимости | частично (live-demo) |
|
||||||
|
| `D.5` | `B1-5` | Без e2e happy-path нельзя доказать работоспособность | `src/backend/tests/integration/test_happy_path.py`, `src/backend/tests/integration/test_api_contract.py` | `cd src/backend && just test` | Тестовые фикстуры и встроенный test DB | подтверждено |
|
||||||
|
| `1.3, 3.4` | `B2-1` | Возврат не-default без активного эксперимента искажает контроль | `src/backend/apps/decision/services.py` | `apps.decision.tests.test_decide.DecideForFlagTest.test_no_active_experiment` | Флаг с default и без running эксперимента | подтверждено |
|
||||||
|
| `1.3, 2.7` | `B2-2` | Пользователь вне таргетинга не должен получать variant | `src/backend/apps/decision/services.py`, `src/backend/libs/dsl/*` | `apps.decision.tests.test_decide.TargetingRulesTest.test_targeting_fail_returns_default` | Эксперимент с targeting rules и mismatching subject | подтверждено |
|
||||||
|
| `1.3, 3.4` | `B2-3` | При применимом эксперименте нужен variant, а не default | `src/backend/apps/decision/services.py` | `apps.decision.tests.test_decide.DecideForFlagTest.test_running_experiment_assigns_variant` | Running experiment с вариантами | подтверждено |
|
||||||
|
| `3.5.1` | `B2-4` | Нестабильная выдача ломает статистику и UX | `src/backend/apps/decision/services.py` (`_hash_subject`) | `apps.decision.tests.test_decide.DecideForFlagTest.test_deterministic_assignment` | Повторные вызовы для одного subject | подтверждено |
|
||||||
|
| `2.2` | `B2-5` | Игнорирование weights делает тест нерепрезентативным | `src/backend/apps/decision/services.py` (`_select_variant`) | `apps.decision.tests.test_decide.SelectVariantTest.test_selects_by_weight` | Варианты с разными весами | подтверждено |
|
||||||
|
| `2.4` | `B3-1` | Без перехода в review нет управляемого процесса запуска | `src/backend/apps/experiments/services.py` (`experiment_submit_for_review`) | `apps.experiments.tests.test_services` | Draft experiment с валидными вариантами | подтверждено |
|
||||||
|
| `2.4` | `B3-2` | Без авто-перехода в approved ревью-процесс зависает | `src/backend/apps/experiments/services.py` (`experiment_approve`) | `apps.experiments.tests.test_services` | Approver + достигнутый порог approvals | подтверждено |
|
||||||
|
| `0.3, 2.4` | `B3-3` | Запуск без порога approvals делает процесс небезопасным | `src/backend/apps/experiments/services.py`, `src/backend/tests/integration/test_negative.py` | `InvalidLifecycleTransitionsTest.test_cannot_start_without_enough_approvals` | `default_min_approvals > 1`, недостаток approve | подтверждено |
|
||||||
|
| `2.5` | `B3-4` | Невалидные lifecycle переходы ломают состояние | `src/backend/apps/experiments/models.py` (`ALLOWED_TRANSITIONS`) | `src/backend/tests/integration/test_negative.py` | Эксперимент и попытка запрещённого перехода | подтверждено |
|
||||||
|
| `0.2, 0.3` | `B3-5` | Неназначенный пользователь не должен влиять на review | `src/backend/apps/reviews/selectors.py`, `src/backend/apps/experiments/services.py` | `apps.reviews.tests.test_reviews_policy`, `test_negative.ReviewPolicyEnforcementTest` | Approver group / fallback настройки | подтверждено |
|
||||||
|
| `4.5` | `B4-1` | Невалидные типы событий портят аналитику | `src/backend/apps/events/services.py` (`_validate_event_payload`) | `apps.events.tests.test_services` | Batch с ошибочными типами полей | подтверждено |
|
||||||
|
| `4.5` | `B4-2` | Отсутствие обязательных полей делает события непригодными | `src/backend/apps/events/services.py`, `src/backend/apps/events/models.py` | `apps.events.tests.test_services`, `test_negative.EventValidationIntegrationTest` | События без required base/properties | подтверждено |
|
||||||
|
| `4.3` | `B4-3` | Дубли событий завышают метрики | `src/backend/apps/events/services.py` (`_is_duplicate`) | `apps.events.tests.test_services`, `tests.integration.test_events` | Два события с одним `event_id` | подтверждено |
|
||||||
|
| `4.1, 4.4` | `B4-4` | Без связи exposure с decision теряется атрибуция | `src/backend/apps/events/models.py` (`Exposure.decision_id`) | `apps.events.tests.test_services` | Валидный `decision_id` и exposure event | подтверждено |
|
||||||
|
| `4.4.1` | `B4-5` | Конверсия без exposure не должна попадать в отчёт | `src/backend/apps/events/services.py` (`requires_exposure`, `PendingEvent`) | `apps.events.tests.test_services`, `tests.integration.test_events` | Conversion до exposure и последующая промоция | подтверждено |
|
||||||
|
| `6.2` | `B5-1` | Guardrail без metric_key нефункционален | `src/backend/apps/guardrails/models.py` (`metric`) | `apps.guardrails.tests.test_guardrails` | Guardrail с привязанной метрикой | подтверждено |
|
||||||
|
| `6.2` | `B5-2` | Guardrail без threshold не может сработать | `src/backend/apps/guardrails/models.py` (`threshold`) | `apps.guardrails.tests.test_guardrails` | Guardrail с заданным порогом | подтверждено |
|
||||||
|
| `6.3` | `B5-3` | Превышение порога должно фиксироваться автоматически | `src/backend/apps/guardrails/services.py` | `apps.guardrails.tests.test_guardrails`, `tests.integration.test_guardrails` | Метрика выше threshold в окне | подтверждено |
|
||||||
|
| `6.4` | `B5-4` | После trigger должно выполняться действие безопасности | `src/backend/apps/guardrails/services.py` (`pause`, `rollback`) | `apps.guardrails.tests.test_guardrails` | Running experiment + breach | подтверждено |
|
||||||
|
| `6.5` | `B5-5` | Без аудита триггеров нельзя объяснить остановку | `src/backend/apps/guardrails/models.py` (`GuardrailTrigger`), `apps/experiments/models.py` (`ExperimentLog`) | `apps.guardrails.tests.test_guardrails` | Triggered guardrail | подтверждено |
|
||||||
|
| `3.6` | `B5-6` | Один пользователь не должен постоянно быть в экспериментах | `src/backend/apps/decision/services.py` (`MAX_CONCURRENT_EXPERIMENTS`, `COOLDOWN_DAYS`) | `apps.decision.tests.test_decide.ParticipationLimitsTest` | Несколько running/completed экспериментов на одного subject | подтверждено |
|
||||||
|
| `5.2` | `B6-1` | Отчёт без периода неуправляем и спорный | `src/backend/apps/reports/services.py` | `apps.reports.tests.test_reports.CalculateMetricValueTest.test_period_filter` | События внутри/вне окна периода | подтверждено |
|
||||||
|
| `5.3` | `B6-2` | Без разреза по вариантам нельзя сравнивать A/B | `src/backend/apps/reports/services.py` (`variant_reports`) | `apps.reports.tests.test_reports.BuildExperimentReportTest` | Эксперимент минимум с двумя вариантами | подтверждено |
|
||||||
|
| `5.4` | `B6-3` | Отчёт должен показывать именно выбранные метрики эксперимента | `src/backend/apps/metrics/models.py`, `src/backend/apps/reports/services.py` | `apps.reports.tests.test_reports` | `ExperimentMetric` привязки | подтверждено |
|
||||||
|
| `2.6` | `B6-4` | Нужна фиксация исхода (`rollout/rollback/no_effect`) | `src/backend/apps/experiments/services.py` (`experiment_complete`) | `apps.experiments.tests.test_services` | Completed experiment и выбранный outcome | подтверждено |
|
||||||
|
| `2.6` | `B6-5` | Решение без rationale теряет объяснимость | `src/backend/apps/experiments/services.py` (валидация `rationale`) | `tests.integration.test_negative.InvalidLifecycleTransitionsTest.test_cannot_complete_without_rationale` | Пустой rationale при complete | подтверждено |
|
||||||
|
| `D.5(B7)` | `B7-1` | Неясный нейминг усложняет поддержку и демо | `src/backend/apps/*`, `src/backend/api/v1/*` | Архитектурный walkthrough | Репозиторий проекта | подтверждено |
|
||||||
|
| `D.5(B7)` | `B7-2` | Без границ модулей растёт связность и регрессии | `src/backend/apps/*`, `src/backend/api/v1/*` | Проверка структуры директорий и зависимостей | Репозиторий проекта | подтверждено |
|
||||||
|
| `D.4, D.6` | `B7-3` | Без матрицы тяжело трассировать критерии | `compliance-matrix.md` | Проверка заполнения всех ID | Этот файл | подтверждено |
|
||||||
|
| `D.4` | `B7-4` | Без ADR сложно доказать осознанные trade-off | `ADR/04-decisions.md` | Сверка решений с кодом | ADR и код `src/backend` | подтверждено |
|
||||||
|
| `D.4` | `B7-5` | Нужна контекстная диаграмма границ системы | `assets/plantuml/raw/c4-context.puml` | Просмотр диаграммы | PlantUML файл | подтверждено |
|
||||||
|
| `D.4` | `B7-6` | Нужна container-диаграмма взаимодействий | `assets/plantuml/raw/c4-container.puml` | Просмотр диаграммы | PlantUML файл | подтверждено |
|
||||||
|
| `D.4` | `B7-7` | Нужна component-диаграмма по критичному пути | `assets/plantuml/raw/c4-component-critical-path.puml` | Просмотр диаграммы | PlantUML файл | подтверждено |
|
||||||
|
| `D.4` | `B7-8` | Без карты репозитория навигация медленная | `MAP.md` | Проверка ссылок на точки входа | Файл `MAP.md` | подтверждено |
|
||||||
|
| `D.7` | `B7-9` | Неявные упрощения искажают ожидания жюри | `ADR/07-simplifications.md` | Проверка явного списка ограничений | ADR пакет | подтверждено |
|
||||||
|
| `D.5(B8)` | `B8-1` | Без негативных тестов edge-cases не покрыты | `src/backend/tests/integration/test_negative.py` | `cd src/backend && just test` | Тестовая БД и фикстуры | подтверждено |
|
||||||
|
| `D.5(B8)` | `B8-2` | Критичный поток нужен в интеграционных/контрактных тестах | `src/backend/tests/integration/test_happy_path.py`, `src/backend/tests/integration/test_api_contract.py` | `cd src/backend && just test` | Тестовые фикстуры | подтверждено |
|
||||||
|
| `D.4` | `B8-4` | Нужен измеримый отчёт по покрытию | `src/backend/justfile`, `src/backend/pyproject.toml` | `cd src/backend && just test-coverage && just show-coverage` | Coverage tool из dev dependencies | подтверждено |
|
||||||
|
| `3.7` | `B9-1` | Readiness должен быть однозначным и проверяемым | `src/backend/api/urls.py` (`/ready`) | Runtime: `curl -i /ready` после старта | Поднятые cache/db/storage/celery | частично (live-demo) |
|
||||||
|
| `3.7` | `B9-2` | Нужен отдельный liveness probe | `src/backend/api/urls.py` (`/health`) | Runtime: `curl -i /health` | Поднятый backend | подтверждено |
|
||||||
|
| `D.5(B9)` | `B9-3` | Без метрик нет наблюдаемости hot-path | `src/backend/config/settings/base.py`, `src/backend/apps/decision/services.py`, `src/backend/api/v1/events/endpoints.py` | Runtime: `curl /metrics` | Поднятый backend | подтверждено |
|
||||||
|
| `D.5(B9)` | `B9-4` | Неструктурированные логи сложны для алертов и анализа | `src/backend/config/settings/base.py` (json formatter, django-guid) | Запуск в non-debug и просмотр stdout | Конфигурация `DJANGO_DEBUG=false` | подтверждено |
|
||||||
|
| `D.5(B9)` | `B9-6` | Рост трафика/данных может деградировать latency даже после оптимизаций | `src/backend/apps/decision/services.py`, `src/backend/apps/reports/services.py`, `src/backend/apps/events/tasks.py`, `ADR/04-decisions.md` (P1-P4) | Код-ревью + live-demo под нагрузкой | Сценарий увеличенного трафика и наблюдение очереди/latency | частично (live-demo) |
|
||||||
|
| `D.5(B9)` | `B9-7` | Без индексов и оптимизаций горячие запросы дорожают | `src/backend/apps/experiments/models.py`, `src/backend/apps/events/models.py`, `src/backend/apps/guardrails/models.py`, `src/backend/apps/notifications/models.py`, `src/backend/apps/learnings/models.py` | Схема моделей и миграций | БД-схема проекта | подтверждено |
|
||||||
|
| `D.5(B10)` | `B10-1` | Отсутствие автоматического линтинга снижает качество | `src/backend/justfile`, `src/backend/pyproject.toml`, `.gitlab-ci.yml` | `cd src/backend && just lint` | Dev dependencies | подтверждено |
|
||||||
|
| `D.5(B10)` | `B10-2` | Отсутствие форматирования повышает шум в diff | `src/backend/justfile`, `src/backend/pyproject.toml`, `.gitlab-ci.yml` | `cd src/backend && just format` | Dev dependencies | подтверждено |
|
||||||
|
| `7` | `FX-1` | Без рабочего сценария уведомления бесполезны | `src/backend/apps/notifications/*`, `src/backend/api/v1/notifications/*` | `cd src/backend && uv run python manage.py test apps.notifications.tests.test_notifications api.v1.notifications.tests.test_notifications_api` | Notification channels/rules/log fixtures | подтверждено |
|
||||||
|
| `7` | `FX-2` | Нужны явные ограничения по каналам и шумоподавлению | `ADR/07-simplifications.md`, `ADR/04-decisions.md`, `src/backend/apps/notifications/services.py` | Сверка docs и реализации rate-limit/dedup | Документация + код notifications | подтверждено |
|
||||||
|
| `9` | `FX-1` | Без рабочего knowledge base повторяются эксперименты | `src/backend/apps/learnings/*`, `src/backend/api/v1/learnings/*` | `cd src/backend && uv run python manage.py test apps.learnings.tests.test_learnings api.v1.learnings.tests.test_learnings_api` | Learning fixtures, experiments, tags | подтверждено |
|
||||||
|
| `9` | `FX-2` | Нужны явные ограничения алгоритма похожести | `ADR/07-simplifications.md`, `ADR/04-decisions.md`, `src/backend/apps/learnings/services.py` | Сверка docs и реализации similarity scoring | Документация + код learnings | подтверждено |
|
||||||
|
| `11` | `FX-1` | Без резолва конфликтов метрики неинтерпретируемы | `src/backend/apps/conflicts/*`, `src/backend/api/v1/conflicts/*`, `src/backend/apps/decision/services.py` | `cd src/backend && uv run python manage.py test apps.conflicts.tests.test_conflicts api.v1.conflicts.tests.test_conflicts_api` | Эксперименты в доменах с policy `mutual_exclusion/priority` | подтверждено |
|
||||||
|
| `11` | `FX-2` | Нужны явные границы конфликтных политик | `ADR/07-simplifications.md`, `ADR/04-decisions.md`, `src/backend/apps/conflicts/services.py` | Сверка docs и `resolve_domain_conflict` | Документация + код conflicts | подтверждено |
|
||||||
Reference in New Issue
Block a user