00 — Сводный чеклист ревью реализации¶
Ревью ветки
feat/implementation-review(8 коммитов реализации, f420004..422e43a) против90-business/,91-system/,92-code/и первоисточников00-brief/,30-domain/,40-formats/,50-stack/. Дата: 2026-09-16. Метод: 10 параллельных ревьюеров-специалистов (каждый статус подтверждёнфайл:строкаили прогоном) + агент-критик: перепроверены все 77 строк «ГОТОВО» (3 понижены до «ЧАСТИЧНО», пометка «Критик:» в доказательстве) и 15 выборочных «НЕТ» (ложных не найдено). Подробности — в отчётах01-…10-, дефекты — в99-defects.md, как читать — вREADME.md.
Сводка¶
| Уровень | ГОТОВО | ЧАСТИЧНО | НЕТ | ВНЕ ОБЪЁМА | НЕ ПРОВЕРЕНО | Всего |
|---|---|---|---|---|---|---|
| Бизнес-требования (90-business + чеклист ТЗ) | 28 | 136 | 40 | 2 | 1 | 207 |
| Системные требования (91-system) | 8 | 60 | 107 | 3 | 2 | 180 |
| Архитектурные принципы A-01…A-15 (50-stack/05) | 2 | 10 | 2 | 1 | 0 | 15 |
| Кодовая база и аспекты реализации (92-code и пр.) | 36 | 18 | 41 | 2 | 12 | 109 |
| Итого | 74 | 224 | 190 | 8 | 15 | 511 |
Бизнес-требования (90-business + чеклист ТЗ)¶
| ID | Требование | Статус | Доказательство | Что доделать | Отчёт |
|---|---|---|---|---|---|
| BR-06 | Температура вне диапазона — борт не летит | НЕТ | grep -rn temperature validator/ пусто; frontend packages/domain/src/validation.ts:311-318 — статус 'pass' зашит | Реализовать отсечение по temperature_range из fleet.yaml | 01-business-trace.md |
| BR-07 | Площадка пригодна для способа взлёта/посадки (suitable_for) | НЕТ | grep -rn suitable_for src/backend/validator src/backend/planner — только тест validator/tests/test_contracts.py:26 | Добавить проверку и учёт при назначении площадки | 01-business-trace.md |
| BR-20 | Точка посадки вне запретов с учётом эллипса сноса | НЕТ | grep -rni "эллипс|drift" src/ — вхождений нет | Реализовать проверку области вероятного приземления | 01-business-trace.md |
| BR-28 | Разведение бортов (Δh ≥ 50 м либо горизонтальный интервал) | НЕТ | grep -rn "separation|разведен" src/backend пусто; во фронтенде только группа validation.ts:78 id:'separation' | Реализовать отключаемую проверку в валидаторе | 01-business-trace.md |
| FR-EXP-09 | KML под импорт в Geoscan Planner: стили, отраслевые имена элементов | НЕТ | export/kml.py:34-64; на выгрузке s01 t.count("<Style")=0, t.count("extrude")=0, имена Placemark — takeoff 0 / transit 1 / survey 1 вместо «Галс 1 (ПАФС)» (40-formats/01:155) (также в 01-business-trace.md: ЧАСТИЧНО) | Стили survey/transit + styleUrl, extrude, русские отраслевые имена, description с метриками | 04-planner-core.md |
| FR-EXP-10 | Вспомогательные слои: область съёмки, NFZ, граница ВП, ВПП, полосы захвата | НЕТ | export/geojson.py:8-41 принимает scenario и не использует его ни разу; в выгрузке только LineString этапов (также в 01-business-trace.md: ЧАСТИЧНО) | Добавить слои из сценария и полосы захвата отдельными FeatureCollection/Folder | 04-planner-core.md |
| FR-EXP-15/16 | Выгрузка .plan (QGC) и .waypoints | НЕТ | ExportFormat содержит PLAN и WAYPOINTS, export/init.py:19 бросает NotImplementedError | В FR помечены «дополнительно» — допустимо отложить, но убрать значения из enum либо документировать как нереализованные | 04-planner-core.md |
| FR-EXP-16 | Выгрузка .waypoints (QGC WPL 110) | НЕТ | grep -rn "waypoints|WPL" src/ — вхождений нет | Реализовать текстовый экспорт | 01-business-trace.md |
| FR-EXP-18 | Генерализация геометрии при импорте | НЕТ | apps/planner/src/screens/ExportScreen.tsx:179 — только текст про Дугласа-Пекера; реализации нет | Реализовать либо убрать утверждение из интерфейса | 01-business-trace.md |
| FR-MET-07 | Профиль ветра по высоте | НЕТ | scenario.py:126-131 — только скалярный ветер; потребителей профиля нет | Расширить схему и расчёт | 01-business-trace.md |
| FR-MET-08 | Температура сценария и предупреждение о выходе за диапазон | НЕТ | Wind.temperature_c есть (scenario.py:130), потребителей нет; см. BR-06 | Добавить предупреждение | 01-business-trace.md |
| FR-MON-01 | Приём состояния выполнения плана | НЕТ | packages/domain/src/store.ts:360-363 — только droppedUavIds; полей выполненных галсов/позиций/остатка заряда нет ни в сторе, ни в geoscan_contracts | Ввести модель состояния выполнения | 01-business-trace.md |
| FR-MON-03 | Бюджет борта в воздухе = остаток заряда | НЕТ | packages/domain/src/planner.ts:186-190 — борт исключается, остальные считаются с полным endurance; battery_left_frac (scenario.py:55) нигде не читается | Учесть остаток заряда при пересчёте | 01-business-trace.md |
| FR-PLN-03 | Вычитание бесполётных зон и пересечение с границей разрешённого ВП, буфер безопасности | НЕТ | grep -rn "nfz|no_fly|airspace" src/backend/planner/ → только pipeline.py:506-507 (нули в метриках); scenario.airspace в pipeline.plan() не читается. Прогон s02: transects crossing NFZ: 3 of 8, ~810 м внутри зоны, при этом violations.nfz=0 (также в 01-business-trace.md: ЧАСТИЧНО) | Реализовать coverage/airspace_clip.py: вычитание NFZ с буфером objective.safety_buffer_m, пересечение с airspace.allowed, учёт floor/ceiling и высотных границ зон | 04-planner-core.md |
| FR-PLN-04 | Декомпозиция невыпуклой области с дырами | НЕТ | coverage/transects.py:53 — используется только poly.coordinates[0]; baseline/equal_split.py:51-52 «convex MVP» (также в 04-planner-core.md: ЧАСТИЧНО) | Трапецоидальная/бустрофедонная декомпозиция с учётом дыр | 01-business-trace.md |
| FR-PLN-05 | Непокрытый остаток показан числом, покрытие не маскируется | НЕТ | pipeline.py:476,497 — planned считается из тех же work[...]["transects"] (pipeline.py:434-436), coverage_fraction тождественно 1.0. Прогон s02: reported coverage_fraction 1.0, честная цель «площадь минус NFZ» = 0.8636 | Считать покрытие как площадь объединения полос захвата ∩ полигон / площадь полигона (30-domain/03:212); swath_polygon уже есть и нигде не используется | 04-planner-core.md |
| FR-PLN-10 | Разрезание галса на подгалсы под бюджет вылета | НЕТ | planner/planner/pipeline.py:174 — вылет закрывается только при непустом taken; одиночный длинный галс берётся целиком | Резать галс; не влезающий подгалс — в unassigned с причиной | 01-business-trace.md |
| FR-PLN-12 | Между вылетами одного борта заложен оборот (замена АКБ / заряд) | НЕТ | pipeline.py:255 clock += used. Полигон 25 км²: sortie 2 start_min 18.57 при dur_min 18.57 первого, 41 вылет подряд без зарядки; turnaround_min грузится (catalog.py:327) и не используется | Ограничение C8 (30-domain/04:35, 05:158): turnaround_time_min при свободной АКБ, иначе charge_time_min; учесть spare_batteries площадки | 04-planner-core.md |
| FR-PLN-13 | Маршрутизация с несколькими ВПП, посадка на другую площадку | НЕТ | planner/planner/pipeline.py:241-251 — launch_site=home.id, landing_site=home.id; прогон дал [('h','h'),('h','h')] | Реализовать VRP с выбором площадок старта/посадки, поддержать landing_sites | 01-business-trace.md |
| FR-PLN-23 | Бюджет времени решателя и статус остановки | НЕТ | pipeline.py:489 — time_limit_s только копируется в solver; таймера и прерывания в коде нет | Ввести таймер, лучшее найденное решение и stop_reason | 01-business-trace.md |
| FR-PLN-24 | Неназначенные галсы списком с машиночитаемой причиной | НЕТ | pipeline.py:305-311 конструирует Unassigned по именам полей, schema.py:236-242 без populate_by_name. Прогон «geoscan_201 + riebo_r4»: RAISED ValidationError: 4 validation errors for Unassigned (transectId Field required / transect_id Extra inputs are not permitted) | Починить конструирование, ввести коды причин (UnassignedReason), покрыть тестом; учитывать галсы, выброшенные упаковкой | 04-planner-core.md |
| FR-RPT-08 | График масштабируемости | НЕТ | ls src/backend/scenarios — только s01-simple.json, s02-nfz.json, s03-multi-sites.json; сценариев sc-fleet/sc-area/sc-sites/sc-nfz нет | Завести сценарии масштабируемости и собрать график | 01-business-trace.md |
| FR-SCN-04, FR-EXP-17 | Импорт KML/KMZ/GeoJSON/SHP с пересчётом CRS | НЕТ | apps/shell/src/screens/StartScreen.tsx:151 — |
Реализовать разбор, определение CRS и назначение ролей слоям | 01-business-trace.md |
| FR-SCN-06 | ЛАФС: осевая линия и ширина коридора | НЕТ | grep -rni "коридор|corridor|лафс" src/ — единственное вхождение packages/domain/src/scenarios.ts:84 (имя БПЗ) | Добавить тип задания «коридор» и генерацию геометрии | 01-business-trace.md |
| FR-STE-08 | Курс захода и взлёта против ветра | НЕТ | grep -rni "заход|approach" packages/domain/src — логики нет; pipeline.py:229-239 — этап landing в точке старта | Строить маршрут посадки как функцию ветра | 01-business-trace.md |
| FR-TSK-08 | Окно времени задания учитывается при планировании | НЕТ | Поле есть в shared-py/geoscan_contracts/scenario.py:100, но grep -rn time_window src/backend/planner src/backend/validator — ни одного использования | Учесть окно в расписании и в проверке валидатора | 01-business-trace.md |
| FR-TSK-09 | Слияние RGB и мультиспектра одним проходом на Pollux | НЕТ | grep -rn rgb_capable src/ — вхождений нет | Реализовать слияние проходов | 01-business-trace.md |
| NFR-02, NFR-03 | Диапазон масштабных прогонов и график solve_time_s | НЕТ | Те же три сценария; колонка solve_time в validator/report/table.py есть, данных нет | Завести наборы sc-* и собрать таблицу | 01-business-trace.md |
| NFR-09 | Контур поднимается одной командой | НЕТ | find . -iname 'docker-compose*' — ничего; Dockerfile только у gateway и packages/api-client | Добавить docker-compose.yml и воспроизводимую инструкцию | 01-business-trace.md |
| NFR-12, NFR-13 | Локальные тайлы/DEM и завендоренные шрифты | НЕТ | src/frontend/README.md «Известные ограничения»: шрифты из системного стека, подложка — один фоновый слой; find src/frontend -name '.woff2' -o -name '.pmtiles' — пусто | Завендорить шрифты, собрать офлайн-тайлы | 01-business-trace.md |
| NFR-13 | Шрифты интерфейса вендорены локально (woff2) | НЕТ | find src/frontend -name '.woff2' -o -name '.woff' -o -name '*.ttf' (без node_modules) → пусто; packages/tokens/src/global.css:29-30 задаёт 'Inter'/'JetBrains Mono' с системным фолбэком; src/frontend/README.md:160 сам фиксирует это как ограничение | Завендорить woff2 и @font-face в packages/tokens | 10-build-ops.md |
| NFR-19 | Перезапуск не теряет сценарии и планы | НЕТ | gateway/src/app.module.ts:9-15 — модуля БД нет; plans.controller.ts:31-36 возвращает случайный UUID и «очередь — в GW-07» | Реализовать реестр планов | 01-business-trace.md |
| NFR-21 | Идемпотентность повторного расчёта | НЕТ | Idempotency-Key отправляется клиентом (packages/api-client/src/endpoints/plans.ts:101-105), на сервере не читается (grep -rn Idempotency src/backend пусто) | Реализовать на гейтвее | 01-business-trace.md |
| NFR-22 | Недоступность доменного процесса ≠ пустой план | НЕТ | validate.controller.ts:19-30 возвращает status:'stub'; маппинг UNAVAILABLE→503 есть только как хелпер shared/src/grpc/grpc-to-http.ts без потребителя | Подключить gRPC-клиентов и обработку недоступности | 01-business-trace.md |
| NFR-24 | Сквозной идентификатор запроса в журнале | НЕТ | common/interceptors/request-id.interceptor.ts есть, но структурированного журнала нет (grep -rn "pino|logger" gateway/src пусто) | Ввести журналирование с request_id | 01-business-trace.md |
| NFR-26 | Доступ к доменным процессам закрыт сервисным ключом | НЕТ | SERVICE_API_KEY только читается (config/config.service.ts:12-14); guard отсутствует (grep -rn "CanActivate|Guard" gateway/src пусто) | Добавить проверку ключа на gRPC-канале | 01-business-trace.md |
| NFR-28 | Безопасный импорт файлов (25 МБ, XXE, понятные ошибки) | НЕТ | Разбора файлов нет вовсе (см. FR-SCN-04); BODY_LIMIT_BYTES=26214400 в gateway/.env.example закрывает только размер тела | Реализовать вместе с импортом | 01-business-trace.md |
| NFR-34 | Шесть обязательных разделов документации отдельными файлами | НЕТ | find docs -iname 'user' -o -iname 'ограничени' — только 90-business/03-user-scenarios.md; поставочного комплекта нет | Собрать поставочный комплект документации | 01-business-trace.md |
| R-DOC-5 | Руководство пользователя | НЕТ | grep -rln "руководство пользователя" docs — только упоминания в ТЗ и требованиях; документа нет | Написать | 01-business-trace.md |
| R-DOC-6 | Перечень известных ограничений | НЕТ | Сводного перечня нет: ограничения рассыпаны по src/frontend/README.md и docs/task5/TODO.md | Свести в один документ | 01-business-trace.md |
| BR-01 | Допустимость плана = семь жёстких счётчиков равны нулю | ЧАСТИЧНО | Валидатор: geoscan_contracts/plan.py:42-62 (семь видов), admission.py:47-55, тест test_clean_plan_is_admitted. Планировщик пишет пять захардкоженных нулей (pipeline.py:505-511, подтверждено прогоном) | Убрать самопровозглашённые нули из плана планировщика | 01-business-trace.md |
| BR-03 | W > wind_max → борт не летит, строка «доступно N из M» | ЧАСТИЧНО | Отсечение pipeline.py:304, проверка checks/g4_weather.py:27-72; строки счётчика нет (grep «доступно» по src/frontend пусто) | Показать счётчик доступных бортов | 01-business-trace.md |
| BR-04 | |W⊥| < V_a на каждом галсе, V_g > 0 | ЧАСТИЧНО | Валидатор: checks/g4_weather.py:75-170 (тесты test_crosswind_above_airspeed_is_a_violation, test_crosswind_gain_matches_theory). Планировщик бросает Infeasible на весь прогон (geometry/wind.py:20-21) | Не назначать конкретный галс вместо падения всего расчёта | 01-business-trace.md |
| BR-05 | Совместимость борт ↔ нагрузка ↔ тип съёмки | ЧАСТИЧНО | Валидатор checks/g5_assignment.py:20-47; каталог compatibility (тест test_catalog_compatibility_matrix). Планировщик сверяет только payload.survey_type == task.survey_type (pipeline.py:302), матрицу модель↔нагрузка не проверяет | Сверять физическую совместимость модели с нагрузкой | 01-business-trace.md |
| BR-08, BR-09 | Бюджет endurance·(1−reserve) со всеми фазами | ЧАСТИЧНО | Резерв pipeline.py:123; валидатор checks/g3_energy.py:20-61 (тест test_endurance_budget_is_checked_with_reserve) и структурная проверка g3_energy.py:64-98. Маршрут посадки из трёх точек не строится | Добавить маршрут посадки в бюджет | 01-business-trace.md |
| BR-11, BR-12, BR-13 | Высоты: 100 м AGL у 201, не ниже старта, потолки 500/4000 м | ЧАСТИЧНО | Валидатор checks/g2_altitude.py:24-118 (тест test_altitude_above_ceiling_is_a_violation); планировщик geometry/optics.py:57-74. «Не ниже точки старта» сведено к altitude_m < 0; недостижимая высота роняет весь прогон, а не исключает борт | Исключать борт-кандидата; ввести превышение площадки | 01-business-trace.md |
| BR-14 | Выше 150 м — предупреждение, не блокировка | ЧАСТИЧНО | Валидатор g1_airspace.py:184-217; планировщик предупреждение не эмитирует (warnings пуст в прогоне) | Эмитировать предупреждение в плане | 01-business-trace.md |
| BR-16 | Огибание рельефа, GSD постоянен | ЧАСТИЧНО | Высоты AGL (geoscan_contracts/plan.py:112,122), проверка соответствия высоты GSD (checks/g2_altitude.py:121-162). Модели рельефа нет, и это честно объявлено: checks/g2_altitude.py:19-21 MISSING_CHECKS G2-04 | Ввести DEM и проверку разброса GSD | 01-business-trace.md |
| BR-17, BR-18, BR-19, BR-21 | Ноль в БПЗ и вне ВП, буфер, покрытие = область минус БПЗ | ЧАСТИЧНО | Валидатор закрывает все четыре: checks/g1_airspace.py:26-181, metrics.py:38-71 (покрытие по площади, тест test_lying_coverage_metric_fails). Планировщик — ни одного (зон не читает; coverage_fraction = доля числа галсов, pipeline.py:497) | Реализовать в планировщике | 01-business-trace.md |
| BR-22, BR-23 | Недостижимый тип съёмки или GSD → ошибка валидации входа | ЧАСТИЧНО | См. FR-TSK-07, FR-VAL-01, FR-VAL-02: сейчас unassigned или Infeasible по ходу расчёта, без перечня возможностей парка | Оформить как ошибку входа с матрицей возможностей | 01-business-trace.md |
| BR-25 | Галсы поперёк ветра, проигрыш иного угла показан | ЧАСТИЧНО | Автовыбор pipeline.py:69-96; теория подтверждена тестом validator/tests/test_optics.py::test_crosswind_gain_matches_theory. Показа величины проигрыша нет | Показать разницу времени против автоугла | 01-business-trace.md |
| BR-26 | Маршрут посадки обязателен и строится против ветра | ЧАСТИЧНО | Этап landing есть (pipeline.py:229-238), но это точка старта с нулевой геометрией; зависимости от ветра нет | Построить заход из трёх точек против ветра | 01-business-trace.md |
| BR-27 | При выбытии борта работа сохраняется | ЧАСТИЧНО | Только фронтенд-переброс парка (store.ts:360-363); выполненные галсы не моделируются, инварианта сохранения работы нет | См. FR-MON-01…FR-MON-03 | 01-business-trace.md |
| FR-ASP-01, FR-ASP-02 | Inclusion разрешённого ВП и exclusion БПЗ на всех этапах | ЧАСТИЧНО | Валидатор: checks/g1_airspace.py:26-102, тесты test_no_fly_zone_intrusion_is_measured_in_meters и test_outside_allowed_airspace_is_measured. Планировщик зоны и границу игнорирует (grep "no_fly|allowed" pipeline.py пусто) | Учесть при построении покрытия и перелётов | 01-business-trace.md |
| FR-ASP-03 | Рисование зон и импорт, блокировка незамкнутого контура | ЧАСТИЧНО | store.ts:177 (draw-nfz), блокировка <3 вершин — ScenarioScreen.tsx:193, AirspaceScreen.tsx:94; импорт зон отсутствует | Реализовать импорт зон | 01-business-trace.md |
| FR-ASP-04 | Объёмная зона: ограничение по высотному слою | ЧАСТИЧНО | Валидатор закрывает: validator/checks/g1_airspace.py:44-49,105-132 + context.covers_altitude; планировщик зоны не читает вовсе (grep no_fly в pipeline.py пусто) | Учесть зоны и их высотный слой при построении покрытия | 01-business-trace.md |
| FR-ASP-05 | Тип зоны и срок действия | ЧАСТИЧНО | Контракт Zone.zone_kind/valid_from/valid_to (scenario.py:103-114); валидатор учитывает срок (validator/context.py:197-199); планировщик и фронтенд — нет | Учесть в построении и показать в интерфейсе | 01-business-trace.md |
| FR-ASP-06 | Буфер безопасности от границ зон, дефолт 25 м | ЧАСТИЧНО | validator/checks/g1_airspace.py:135-181 (эквидистанта); objective.safety_buffer_m в planner/ не используется (grep пусто) | Применять буфер при генерации галсов | 01-business-trace.md |
| FR-ASP-07 | Предупреждение «выше 150 м — разрешение на ИВП» | ЧАСТИЧНО | Валидатор: checks/g1_airspace.py:184-217 (тест test_ivp_threshold_is_warning_not_violation). Планировщик считает above_ivp_threshold=True (pipeline.py:469), но warnings пуст (прогон) | Эмитировать ALT_ABOVE_150M в план | 01-business-trace.md |
| FR-ASP-08 | Таблица вершин ВП с азимутами и расстояниями, правка | ЧАСТИЧНО | apps/planner/src/screens/AirspaceScreen.tsx:15,46 — таблица с bearingDeg/distanceM; правки вершины из таблицы не найдено | Добавить редактирование из таблицы | 01-business-trace.md |
| FR-ASP-09 | Непротиворечивость входа по воздушному пространству | ЧАСТИЧНО | Предвалидации нет ни в планировщике, ни в валидаторе; во фронтенде только постфактум-группа validation.ts:205 id:'airspace' | Добавить предвалидацию входа | 01-business-trace.md |
| FR-EXP-01 | Индивидуальное ПЗ на каждый борт | ЧАСТИЧНО | Фильтр по борту есть (export/kml.py:9-11, export/geojson.py:9), но без --uav KML содержит несколько |
Сделать выгрузку набором файлов по числу бортов | 01-business-trace.md |
| FR-EXP-02 | Выгрузка полётного задания в KML | ЧАСТИЧНО | export/kml.py:16-53: namespace kml/2.2, Document/Folder/Placemark, |
XSD-валидация в CI, Point для взлёта/посадки, стили | 04-planner-core.md |
| FR-EXP-03 | Выгрузка полётного задания в GeoJSON | ЧАСТИЧНО | export/geojson.py:16-35: RFC 7946 FeatureCollection, порядок [lon, lat], altitude_frame "AGL". Но высота не третьим элементом позиции (40-formats/01:15); метаданные коллекции — только {"scenario_id": …} вместо uav_model/payload/survey_type/gsd_cm/total_time_s/wind/generated_at (40-formats/01:26-42) | 3D-координаты и метаданные уровня FeatureCollection | 04-planner-core.md |
| FR-EXP-03, FR-EXP-11 | GeoJSON по RFC 7946 и форматные проверки | ЧАСТИЧНО | export/geojson.py:26-29 — координаты 2D lon/lat; properties на уровне FeatureCollection (geojson.py:31-35) вне RFC 7946, высота выброшена, точность не нормирована (в KML нормирована — export/common.py:13-14) | Привести метаданные к RFC, добавить высоту и нормировать точность | 01-business-trace.md |
| FR-EXP-04 | Маршрут и ключевые точки с номером и типом | ЧАСТИЧНО | geoscan_contracts/plan.py:83-101 (Phase.seq, Phase.phase); экспорт сохраняет порядок (export/kml.py:38-54); round-trip-тест проходит. Критик: seq не инкрементируется между turn/transit и survey (pipeline.py:180,194) — пара этапов делит один номер, в KML «transit 1»/«survey 1»; round-trip не ловит (tests/integration/conftest.py:21-45, рукописные seq) | Инкрементировать seq на каждом этапе; round-trip на реальном плане планировщика | 01-business-trace.md |
| FR-EXP-04/06/08 | Порядковый номер, тип этапа, скорость, длительность в выгрузке | ЧАСТИЧНО | В GeoJSON у Feature только uav_id, phase, altitude_m, altitude_frame; нет seq, duration_s, speed_ms, transect_id, photos, trigger_interval. В KML атрибутов нет. Плюс seq дублируется: phase_seq не инкрементируется после transit/turn (pipeline.py:179-190) — в KML видно takeoff 0 / transit 1 / survey 1 | Перенести атрибуты этапа в properties/ExtendedData, починить нумерацию seq | 04-planner-core.md |
| FR-EXP-05 | Высота и явный фрейм высоты во всех форматах одинаково | ЧАСТИЧНО | KML: altitudeMode (export/kml.py:50, тест planner/tests/test_kml_export.py). GeoJSON: altitude_frame только в properties, высот в координатах нет (geojson.py:22-29) (также в 04-planner-core.md: ГОТОВО) | Добавить высоту третьей координатой в GeoJSON | 01-business-trace.md |
| FR-EXP-06 | Скорость в полётном задании | ЧАСТИЧНО | Фронтенд пишет speed_ms (packages/domain/src/exporters.ts:72,166); бэкенд-экспорт скорость не пишет (export/kml.py:41-53, export/geojson.py:18-24) | Добавить скорость в бэкенд-экспорт | 01-business-trace.md |
| FR-EXP-07 | Место старта и посадки в ПЗ | ЧАСТИЧНО | В плане поля есть (schema.py:196-197), в выгрузку не попадают — площадки не экспортируются (export/kml.py, export/geojson.py) | Добавить точки старта и посадки в выгрузку | 01-business-trace.md |
| FR-EXP-08 | Этап как явный атрибут каждого сегмента | ЧАСТИЧНО | GeoJSON: properties.phase (geojson.py:21); KML: этап только в |
Добавить ExtendedData в KML | 01-business-trace.md |
| FR-EXP-12 | Round-trip экспорт → импорт сохраняет геометрию и атрибуты | ЧАСТИЧНО | tests/integration/test_kml_roundtrip.py и test_geojson_roundtrip.py проходят, но сверяют только съёмочные этапы синтетического плана (tests/integration/conftest.py:21-90); своего разбора нет — парсинг внутри теста | Реализовать импорт и расширить round-trip на скорости, этапы, площадки | 01-business-trace.md |
| FR-EXP-13, R-DEMO-5 | Экспорт KML/GeoJSON из веб-интерфейса | ЧАСТИЧНО | apps/planner/src/screens/ExportScreen.tsx + packages/domain/src/exporters.ts:236 downloadText — файлы формируются в браузере из клиентского плана, не из плана сервиса | Подключить выгрузку через API | 01-business-trace.md |
| FR-EXP-14 | Программная выгрузка по идентификатору плана | ЧАСТИЧНО | Контракт есть: proto/geoscan/planner.proto:7,19-28 (ExportPlan); HTTP-маршрута и реализации нет (gateway/src/plans/plans.controller.ts — заглушки) | Реализовать маршрут и серверную часть | 01-business-trace.md |
| FR-EXP-15 | Выгрузка .plan QGroundControl с geofence и rally points | ЧАСТИЧНО | Фронтенд: packages/domain/src/exporters.ts:174 missionToQgcPlan (geofence/rally не найдены). Бэкенд: export/init.py:19 — NotImplementedError | Реализовать в бэкенде, добавить geofence и rally points | 01-business-trace.md |
| FR-FLT-01, FR-FLT-09 | Парк из трёх моделей ТЗ; reference_only не предлагать | ЧАСТИЧНО | Каталог из docs/task5/10-hardware/fleet.yaml (catalog.py, gateway/src/fleet/fleet.catalog.ts), GET /api/fleet; grep -rn reference_only src/ — вхождений нет | Отфильтровать справочные модели | 01-business-trace.md |
| FR-FLT-02, FR-FLT-04 | Борт-экземпляр с установленной нагрузкой; совместимость по установленной | ЧАСТИЧНО | Контракт Uav с единственным payload (scenario.py:46-55); валидатор checks/g5_assignment.py:20-47 (тест test_incompatible_payload_is_a_violation). Планировщик использует свою FleetUnit (schema.py:62-70), available_from игнорируется | Свести схемы, учесть время готовности | 01-business-trace.md |
| FR-FLT-05 | Включение/выключение борта, блокировка при пустом парке | ЧАСТИЧНО | pipeline.py:299-303 фильтрует по u.enabled; явной блокировки с сообщением нет — план просто пустой | Добавить явную ошибку входа | 01-business-trace.md |
| FR-FLT-06 | Лётные параметры борта как ограничения входа | ЧАСТИЧНО | Скорость/запас/ветер/потолки читаются (schema.py:19-33, pipeline.py:123,158,304); дальность не учитывается (grep по range в src/backend пусто) | Добавить ограничение по дальности | 01-business-trace.md |
| FR-FLT-07 | Минимальная безопасная высота и «не ниже точки старта» | ЧАСТИЧНО | Валидатор: checks/g2_altitude.py:56-118. Планировщик: optics.py:57-74 проверяет altitude_agl_min, а can_fly_below_launch_point сведено к altitude_m < 0 (optics.py:73-74) | Ввести рельеф/превышение площадки и сравнение с высотой старта | 01-business-trace.md |
| FR-FLT-08 | Запас хода по времени и видимый резерв 20 % | ЧАСТИЧНО | Резерв применён (pipeline.py:123), дефолт 0.20 (schema.py:121, scenario.py:139); энергетической модели по Вт·ч нет | Добавить интерфейс энергетической модели | 01-business-trace.md |
| FR-MET-03 | Отсечение по wind_max и строка «доступно N из M» | ЧАСТИЧНО | Отсечение pipeline.py:304; валидатор checks/g4_weather.py:27-72 (тест test_wind_above_uav_limit_is_a_violation). grep -rn "доступно" src/frontend — вхождений нет | Показать счётчик доступных бортов | 01-business-trace.md |
| FR-MET-05 | Автовыбор угла галсов и ручная фиксация | ЧАСТИЧНО | Автовыбор pipeline.py:69-96; ручная фиксация task.angle_deg (pipeline.py:339-343); разница по времени против автоугла не показывается | Показывать величину проигрыша (см. FR-PLN-07) | 01-business-trace.md |
| FR-MET-06 | Правка ветра с пересчётом плана | ЧАСТИЧНО | Фронтенд пересчитывает локально (store.ts:331-358); серверного пересчёта нет — гейтвей заглушка | Подключить пересчёт через API | 01-business-trace.md |
| FR-MON-02 | Пересчёт на остатке невыполненных галсов | ЧАСТИЧНО | store.ts:360-363 replanWithout — борт добавляется в droppedUavIds и план считается заново целиком; выполненные галсы не фиксируются | Реализовать пересчёт на остатке с инвариантом сохранения работы | 01-business-trace.md |
| FR-MON-04 | Новый план проходит полный набор жёстких проверок | ЧАСТИЧНО | store.ts:345 вызывает клиентский validate() на 24 проверки, а не бэкенд-валидатор | Валидировать пересчёт на сервере | 01-business-trace.md |
| FR-MON-05 | Сопоставление нового плана с исходным | ЧАСТИЧНО | Сравнение планов есть (apps/validator/src/screens/ParetoScreen.tsx — diff План A/План B); доли выполненных работ и остатка нет | Показать дельты и распределение переданных галсов | 01-business-trace.md |
| FR-MON-06 | Пересчитанный план выгружается теми же средствами | ЧАСТИЧНО | packages/domain/src/exporters.ts не различает исходный и пересчитанный план; ограничение наследуется от FR-MON-02 | Закрыть вместе с FR-MON-02 | 01-business-trace.md |
| FR-PLN-02 | Свой слой покрытия на каждое задание | ЧАСТИЧНО | Высота/шаг/перекрытия считаются на задание и на каждый борт (pipeline.py:315-330), но в TaskGeometry уходит только ctx.units[0] (pipeline.py:429-433) — при разнотипном парке отображаемые высота и шаг не соответствуют части галсов задания | Отдавать слой на пару «задание × борт» либо диапазон значений | 04-planner-core.md |
| FR-PLN-05, FR-RPT-07 | Непокрытый остаток отдельным числом | ЧАСТИЧНО | Валидатор считает покрытие по площади (validator/metrics.py:38-71), но площадь выреза отдельно не выводит (report/json_report.py:29-46); планировщик coverage_fraction = доля числа галсов (pipeline.py:497) | Выводить вырезанную и неназначенную площадь | 01-business-trace.md |
| FR-PLN-06 | Автоподбор угла галсов перебором θ=0…179 по «время галсов + развороты» | ЧАСТИЧНО | Перебор есть (pipeline.py:69-96), но в transect_time_s передаётся угол семейства, а не курс галса (transects.py:90) — разница 90°. Квадрат 1×1 км, ветер 8 м/с, V_a=10: chosen angle 3.0 → real_total 3935.5 s; angle 93.0 → 2419.6 s (в 1,63 раза быстрее), что совпадает с «+67 %» из 30-domain/03:95 (также в 01-business-trace.md: ГОТОВО) | Передавать фактический track_deg галса; добавить тест s06 «автоугол не хуже фиксированного» | 04-planner-core.md |
| FR-PLN-07 | Ручной угол и показ разницы во времени против автоугла | ЧАСТИЧНО | Ручной угол работает (pipeline.py:339-343); сравнения времени нет ни в плане, ни в отчёте | Добавить обе величины makespan в отчёт | 01-business-trace.md |
| FR-PLN-08 | Время галса по путевой скорости с учётом ветра; время плана включает развороты, перелёты, оборот | ЧАСТИЧНО | Формула V_g = sqrt(V_a²−W⊥²)+W∥ верна (geometry/wind.py:8-22). Но wind.py:33-34 усредняет полгалса туда/полгалса обратно, а pipeline.py:156-165 снова подставляет угол вместо курса: при ветре 8 м/с суммарное время съёмки в плане 2301.7 s против честных 3823.4 s (занижение в 1,66 раза); набора высоты и оборота нет | Считать по фактическому курсу каждого галса, добавить T_climb и оборот на площадке | 04-planner-core.md |
| FR-PLN-08, BR-24, NFR-33 | Время по путевой скорости с разворотами, набором высоты и оборотом | ЧАСТИЧНО | Путевая скорость geometry/wind.py:8-34; валидатор ловит наивную формулу checks/g6_coverage.py:85-113 (тест test_plan_without_turns_fails_turn_time_check). В плане turns_total=0 и turn_time_fraction=0.0 при наличии этапов turn (прогон); оборот и набор высоты не моделируются | Считать метрики разворотов, добавить оборот и набор высоты | 01-business-trace.md |
| FR-PLN-09 | Разворот как отдельный этап, два режима | ЧАСТИЧНО | Стоимость по режиму pipeline.py:61-66 (lzp/иначе), этап turn создаётся pipeline.py:178-188; геометрия разворота — прямая линия, метрики нулевые | Дать этапу собственную геометрию и метрики | 01-business-trace.md |
| FR-PLN-11 | Бюджет вылета с перелётами, маршрутом посадки и резервом | ЧАСТИЧНО | Перелёт и возврат учтены (pipeline.py:157-174,215-228), резерв применён (pipeline.py:123); маршрута посадки из трёх точек нет (pipeline.py:229-238) | Добавить маршрут посадки (BR-09, BR-26) | 01-business-trace.md |
| FR-PLN-12, BR-10 | Оборот между вылетами одного борта | ЧАСТИЧНО | Фронтенд: packages/domain/src/planner.ts:478,586. Бэкенд: pipeline.py:254 clock += used — вылеты встык. Валидатор проверку имеет (checks/g3_energy.py:101-145) | Реализовать оборот в бэкенд-планировщике | 01-business-trace.md |
| FR-PLN-14 | Галс назначается только борту с совместимой нагрузкой | ЧАСТИЧНО | Фильтр по survey_type нагрузки: pipeline.py:299-303. Матрица compatibility «борт ↔ нагрузка» (payloads.yaml:206-224, Catalog.model_covers) не проверяется; suitable_for площадки (C7) в pipeline не читается ни разу (также в 01-business-trace.md: ГОТОВО) | Проверять compatibility и пригодность площадки для типа борта | 04-planner-core.md |
| FR-PLN-15 | Борт с превышенным ветром исключается целиком и счётчик доступных | ЧАСТИЧНО | Исключение pipeline.py:304; счётчика нет (см. FR-MET-03) | Показать счётчик доступных бортов | 01-business-trace.md |
| FR-PLN-16 | Высоты в пределах ТТХ борта (мин. безопасная, не ниже старта, потолки AGL/ASL) | ЧАСТИЧНО | Все четыре проверки в geometry/optics.py:57-74, тест test_altitude_infeasible_above_max проходит. Но исключение внутри цикла по бортам (pipeline.py:321) убивает весь расчёт: парк «Gemini + 801», GSD 10 см → RAISED Infeasible: ALTITUDE_ABOVE_MAX: altitude 510.6 m > max AGL 500.0 m, хотя 801 выполняет задание на 282 м (также в 01-business-trace.md: ГОТОВО) | Исключать непригодный борт с причиной, падать только если не осталось ни одного | 04-planner-core.md |
| FR-PLN-17 | Критерий «минимум времени выполнения работ» (makespan) | ЧАСТИЧНО | Критерий доезжает до плана (pipeline.py:350,487) и меняет показатель степени весов (pipeline.py:350-358), но при однородном парке веса одинаковы: s03 даёт makespan_min 17.09 / flight_min 47.08 одинаково для makespan, total_flight_time и naive_equal_split | Настоящая целевая функция (VRP/локальный поиск) либо честная формулировка «критерий влияет только на веса полос» | 04-planner-core.md |
| FR-PLN-17, FR-PLN-18, R-OPT-1, R-OPT-2 | Критерии «минимум makespan» и «минимум суммарного налёта» | ЧАСТИЧНО | Обе метрики считаются (pipeline.py:473-474; validator/metrics.py:74-85). Критерий влияет только на показатель степени весов: pipeline.py:350-358 (exponent=2.0 для total_flight_time) — оптимизации нет | Реализовать решатель и сравнительный прогон по обоим критериям | 01-business-trace.md |
| FR-PLN-18 | Критерий «минимум суммарного налёта» | ЧАСТИЧНО | Тот же прогон s03: результат с Criterion.TOTAL_FLIGHT_TIME побитово совпадает с makespan-прогоном (flight_min 47.08) | То же, что FR-PLN-17 | 04-planner-core.md |
| FR-PLN-19, R-OPT-3, R-DEMO-4 | Выбор критерия в интерфейсе, отражение в плане и выгрузке | ЧАСТИЧНО | store.ts:322-329 (setCriterion/setLambda); solver.criterion заполняется (pipeline.py:487). В выгрузке критерия нет: export/geojson.py:31-35 — только scenario_id | Положить критерий в метаданные выгрузки | 01-business-trace.md |
| FR-PLN-20 | Фронт Парето по λ | ЧАСТИЧНО | packages/domain/src/planner.ts:662 paretoFront(scenario, steps = 7) — 7 точек вместо 11; экран apps/validator/src/screens/ParetoScreen.tsx. В бэкенде λ влияет лишь на exponent (pipeline.py:356-358) | Довести до 11 точек и реального решателя | 01-business-trace.md |
| FR-PLN-21 | Наивный бейзлайн и выигрыш в процентах | ЧАСТИЧНО | planner/planner/cli/main.py:46-56 (baseline), baseline/equal_split.py; строка выигрыша validator/report/table.py:117-136. Сквозной прогон невозможен (см. дефект D-1) | Починить сквозной путь и зафиксировать числа | 01-business-trace.md |
| FR-PLN-22, NFR-15 | Детерминизм при фиксированном seed | ЧАСТИЧНО | Источников случайности в pipeline.py нет; аргумент rng объявлен и не используется (pipeline.py:276), seed только копируется (pipeline.py:490). Теста воспроизводимости среди 150 тестов нет | Добавить регрессию «два прогона дают идентичные метрики» | 01-business-trace.md |
| FR-PLN-24, BR-02 | Неназначенные галсы с машиночитаемой причиной | ЧАСТИЧНО | Валидатор проверяет (checks/g5_assignment.py:96-153, тест test_unassigned_without_reason_fails). Планировщик пишет одну запись на задание с transect_id=-1 и свободной строкой (pipeline.py:305-313) | Формировать unassigned по каждому галсу с кодом UnassignedReason | 01-business-trace.md |
| FR-RPT-01 | Сводка метрик целиком и в разрезе бортов и вылетов | ЧАСТИЧНО | validator/metrics.py:152-166 — все метрики целиком; разреза по бортам и вылетам в отчёте нет | Добавить разрез по бортам и вылетам | 01-business-trace.md |
| FR-RPT-02 | Диаграмма Ганта по бортам, длина шкалы = makespan | ЧАСТИЧНО | apps/planner/src/components/GanttTimeline.tsx существует; данные — клиентский план, обороты в бэкенд-плане отсутствуют | Подключить к серверному плану | 01-business-trace.md |
| FR-RPT-03 | Сравнение двух планов бок о бок с дельтами | ЧАСТИЧНО | apps/validator/src/screens/ParetoScreen.tsx (diff План A/План B); в бэкенде сравнения нет | Добавить сравнение в отчёт валидатора | 01-business-trace.md |
| FR-RPT-04 | Отчёт валидатора как документ допуска | ЧАСТИЧНО | report/json_report.py:29-46 — scenario_id, criterion, validator_version, solve_time_s, вердикт, нарушения, предупреждения. Нет идентификатора плана, seed, бюджета времени, коммита, отметки времени | Дописать недостающие поля | 01-business-trace.md |
| FR-RPT-05 | Машиночитаемая сводка и таблица для презентации | ЧАСТИЧНО | JSON: cli/run.py:47 и cli/check.py:20 (--json); текстовая таблица render_table. Выгрузки таблицы в файл нет | Добавить экспорт таблицы | 01-business-trace.md |
| FR-RPT-06, NFR-06 | Одна команда на весь набор сценариев по обоим критериям | ЧАСТИЧНО | uv run python -m validator run --scenarios scenarios/ отработала, критерии по умолчанию makespan,total_time (cli/run.py:44); вывод — «нет плана» по всем трём сценариям, сценариев три из десяти |
Починить генерацию планов и довести набор до s01–s10 | 01-business-trace.md |
| FR-SCN-01 | Сценарий как единый объект заданной схемы | ЧАСТИЧНО | Контракт полон: shared-py/geoscan_contracts/scenario.py:151-167, schema/scenario.schema.json; uv run python -m validator run --scenarios scenarios/ читает файлы без преобразований. Но у планировщика вторая схема geoscan_contracts/schema.py:133-148, у фронтенда — третья (packages/domain/src/types.ts) |
Свести к одной схеме, schema.py убрать или сделать адаптером | 01-business-trace.md |
| FR-SCN-02 | Три способа создать сценарий | ЧАСТИЧНО | apps/shell/src/screens/StartScreen.tsx:131-155 — карточки «Мастер», «Импорт», таблица демо; импорт файл не разбирает | Реализовать импорт и пошаговый мастер | 01-business-trace.md |
| FR-SCN-05 | Невыпуклые полигоны, мультиполигоны, дыры | ЧАСТИЧНО | Контракт поддерживает (geoscan_contracts/geometry.py, тест tests/integration/test_geometry_contract_roundtrip.py проходит); планировщик — нет (см. FR-PLN-04) | Поддержать в построении покрытия | 01-business-trace.md |
| FR-SCN-08 | Демо-сценарии s01–s10, статус «не загружен» | ЧАСТИЧНО | src/backend/scenarios — три файла; во фронтенде карточки s01–s10, полные данные только s04 (src/frontend/README.md) | Довести набор до s01–s10 в машинной схеме | 01-business-trace.md |
| FR-SCN-09 | Валидация геометрии входа до расчёта | ЧАСТИЧНО | planner/planner/geometry/shapes.py:22-31 — make_valid/explain_validity, но не как входной контроль; диапазон координат и вырожденная площадь не проверяются | Добавить предвалидацию геометрии с явными кодами ошибок | 01-business-trace.md |
| FR-STE-01, FR-STE-02 | Произвольные ВПП и несколько ВПП с привязкой борта | ЧАСТИЧНО | LaunchSite.position произвольный (scenario.py:23-34); s03-multi-sites.json c двумя ВПП (тест test_s03_has_two_launch_sites_and_three_uavs). Планировщик стартует с u.home, выбора площадки нет | Реализовать выбор площадки (см. FR-PLN-13) | 01-business-trace.md |
| FR-STE-03 | Атрибуты ВПП: превышение, пригодность, курс взлёта | ЧАСТИЧНО | Поля есть (scenario.py:27-30), потребителей нет (см. BR-07) | Применять при назначении бортов площадкам | 01-business-trace.md |
| FR-STE-04 | Резервные площадки посадки, старт ≠ финиш | ЧАСТИЧНО | LandingSite в контракте (scenario.py:37-43) и в s01..s03; в плане всегда landing_site == launch_site (прогон) | См. FR-PLN-13 | 01-business-trace.md |
| FR-STE-05, FR-STE-09 | Оборот на площадке, запасные АКБ, зарядные посты | ЧАСТИЧНО | Контракт turnaround_time_min/spare_batteries (scenario.py:32-34); валидатор checks/g3_energy.py:101-145. Планировщик оборот не закладывает (pipeline.py:254 clock += used); числа зарядных постов в схеме нет | Реализовать оборот в планировщике, добавить число постов | 01-business-trace.md |
| FR-STE-06 | Правка/удаление площадок и блокировка без пригодной площадки | ЧАСТИЧНО | Инструмент place-site (packages/domain/src/store.ts:29); блокировки «нет пригодной площадки» нет | Добавить блокировку (зависит от BR-07) | 01-business-trace.md |
| FR-STE-07 | Площадки внутри разрешённого ВП и вне БПЗ | ЧАСТИЧНО | Точки площадок входят в построение проекции (validator/context.py:168), но отдельной проверки среди 24 нет (checks/init.py:28-35) | Добавить проверку | 01-business-trace.md |
| FR-TSK-01 | Задание = область + тип + качество + перекрытия + окно | ЧАСТИЧНО | Контракт полон (scenario.py:89-100); time_window не используется; планировщик читает задание из своей схемы schema.py:73-83 (нет quality) | Свести схемы, учесть окно | 01-business-trace.md |
| FR-TSK-02, FR-TSK-03 | Пять типов съёмки и качество по типу | ЧАСТИЧНО | geoscan_contracts/enums.py — пять SurveyType; Quality с тремя взаимоисключающими метриками (scenario.py:58-79, тест test_quality_requires_exactly_one_metric). Планировщик принимает только gsd_cm (schema.py:79); LiDAR-шаг не выводится (geometry/optics.py:94-97) | Довести LiDAR/геофизику до явной ошибки входа | 01-business-trace.md |
| FR-TSK-04 | Перекрытия по умолчанию из payloads.yaml | ЧАСТИЧНО | catalog.py overlap_defaults + тест test_catalog_overlap_defaults_are_fractions; планировщик жёстко 70/60 (schema.py:80-81) | Брать дефолты из каталога по типу съёмки | 01-business-trace.md |
| FR-TSK-06, FR-PLN-02 | Несколько заданий, свой слой покрытия с собственным шагом | ЧАСТИЧНО | planner/planner/pipeline.py:298-348 — цикл по scenario.tasks со своей высотой/шагом/углом; pipeline.py:444-471 — свой TaskGeometry на задание. Критик: work[u.id] сохраняет altitude_m/spacing_m только первого задания (pipeline.py:386-402); прогон: галсы t2 нарезаны под 306 м, летятся на 153 м → продольное перекрытие 20 % вместо 60 % | Хранить высоту/шаг по каждому заданию и прокидывать в survey-этапы | 01-business-trace.md |
| FR-TSK-07 | Недостижимое качество / нет нагрузки → ошибка валидации входа | ЧАСТИЧНО | Недостижимая высота → Infeasible (geometry/optics.py:57-74, тест test_altitude_infeasible_above_max); отсутствие совместимого борта даёт запись в unassigned (pipeline.py:305-313), без перечня возможностей парка | Разделить предвалидацию и планирование, добавить матрицу возможностей в текст ошибки | 01-business-trace.md |
| FR-VAL-01 | Вход проверяется до запуска решателя | ЧАСТИЧНО | Infeasible бросается по ходу планирования (geometry/optics.py:57-74), CLI возвращает код 3 (cli/main.py:35-37); отдельного этапа предвалидации нет | Ввести предвалидацию входа с объяснением возможностей парка | 01-business-trace.md |
| FR-VAL-02 | LiDAR/геофизика → явная ошибка с перечнем типов | ЧАСТИЧНО | Каталог знает пробелы (catalog.py, тесты test_catalog_compatibility_matrix, test_lidar_spacing_is_not_guessed); планировщик даёт unassigned, а не ошибку (pipeline.py:305-313) | Сделать ошибку входа с матрицей возможностей | 01-business-trace.md |
| FR-VAL-12 | Недопущенный план выгружается только с пометкой | ЧАСТИЧНО | Вердикт допуска есть: validator/admission.py:47-55; в export/kml.py и export/geojson.py признака недопущенного плана нет (также в 05-validator.md: ГОТОВО) | Пробросить признак в файлы и интерфейс | 01-business-trace.md |
| FR-VAL-13 | Прогон s01–s10 одной командой, все поля отчёта заполнены | ЧАСТИЧНО | ls src/backend/scenarios → s01-simple.json, s02-nfz.json, s03-multi-sites.json (3 из 10); план-фикстура только для s01 (validator/tests/fixtures.py:105 build_s01_plan); uv run python -m validator run --scenarios scenarios --criteria makespan,total_time → все три строки «нет плана» (также в 01-business-trace.md: ГОТОВО) |
Добавить сценарии s04–s10 и планы/бейзлайн под них | 05-validator.md |
| FR-VAL-15 | Разделы отчёта со счётчиками проверок | ЧАСТИЧНО | Шесть групп с фиксированным порядком: checks/base.py:28-45, рендер report/table.py (тест test_check_renders_human_table). Пять разделов ТЗ не совпадают с шестью группами; агрегатов по блокировкам и предупреждениям в JSON нет (report/json_report.py:29-46) | Добавить агрегаты по группам | 01-business-trace.md |
| FR-VAL-16 | Машинно-применимое исправление у предупреждения | ЧАСТИЧНО | Только фронтенд на моках: packages/domain/src/validation.ts:13 (suggestedFix), apps/validator/src/screens/ReportScreen.tsx:23. В CheckResult бэкенда поля исправления нет (checks/base.py:48-60) | Добавить поле исправления в контракт отчёта | 01-business-trace.md |
| FR-VAL-17 | Валидатор доступен наружу как операция над внешним планом (POST /validate) | ЧАСТИЧНО | CLI над чужим планом работает (uv run python -m validator check --scenario … --plan …), но gateway/src/validate/validate.controller.ts:24 возвращает {status:'stub'}; в validator/validator/ нет модуля rpc/; proto/geoscan/validator.proto:9-15 знает только bytes plan_json → repeated string violations (также в 01-business-trace.md: ГОТОВО) |
Поднять gRPC-сервер валидатора, расширить proto до полного отчёта, подключить GW-12 | 05-validator.md |
| FR-VAL-18 | Регрессионный набор против отката к наивным формулам | ЧАСТИЧНО | Есть: test_projected_area_matches_geodesic, test_optics.py (GSD по таблице), проверка G6-02 строгого T>L/V_g. Нет: центроид выгрузки в bbox входа, makespan не растёт с числом бортов, воспроизводимость по seed | Дописать три регрессии | 01-business-trace.md |
| NFR-01 | Расчёт демо-сценария за 10–15 с | ЧАСТИЧНО | Прогон планировщика на синтетическом полигоне 1,3 км²: solve_time 0.06 с. Демо-сценария 20–40 км² в репозитории нет, стенда нет | Померить на демо-сценарии на стенде | 01-business-trace.md |
| NFR-04 | Бюджет времени и пометка «остановлено по лимиту» | ЧАСТИЧНО | Поле stop_reason в контракте есть (geoscan_contracts/plan.py:39), заполнять некому (см. FR-PLN-23) | Реализовать вместе с бюджетом решателя | 01-business-trace.md |
| NFR-05 | Интерфейс не блокируется на время расчёта | ЧАСТИЧНО | store.ts:334 — setTimeout(...,30), расчёт в главном потоке; src/frontend/README.md: «Расчёт синхронный в главном потоке… на 200 км² заметно подвиснет» | Вынести на бэкенд или в Web Worker, добавить прогресс и отмену | 01-business-trace.md |
| NFR-07, NFR-08, R-PLT-1, R-PLT-2 | Веб-сервис в браузере ПК, единственная публичная точка входа | ЧАСТИЧНО | src/frontend/apps/*/vite.config.ts (Vite + Module Federation), gateway/src/main.ts:45 — единственный HTTP-процесс. Прогоном не проверить: Node/npm нет, docker compose отсутствует | Собрать контур и проверить на стенде | 01-business-trace.md |
| NFR-11 | Ни одного обращения к внешним ресурсам | ЧАСТИЧНО | src/frontend/README.md «Карта не ходит в сеть», при этом шрифты системные и офлайн-тайлы не собраны (NFR-12/13). Проверка сетевых запросов без сборки невозможна | Проверить после вендоринга шрифтов и тайлов | 01-business-trace.md |
| NFR-14 | Экспорт KML/GeoJSON и отчёта в офлайн-контуре | ЧАСТИЧНО | Экспорт формируется без сети и в браузере (exporters.ts:236), и в бэкенде (planner/planner/export/*.py); офлайн-прогон не выполнялся | Проверить на офлайн-стенде | 01-business-trace.md |
| NFR-16 | Сценарий несёт id, seed, expected; отчёт указывает коммит | ЧАСТИЧНО | Сценарий: scenario_id, solver.seed, expected (scenario.py:152-167, есть во всех трёх файлах scenarios/). В отчёте только validator_version (json_report.py:32); коммит отдаёт только gateway /status (shared/src/ops-http-contract.ts:10-24) | Добавить seed и коммит в отчёт валидатора | 01-business-trace.md |
| NFR-17 | Числа на экране совпадают с отчётом валидатора | ЧАСТИЧНО | Фронтенд считает метрики сам: store.ts:331-358 вызывает solve/validate/paretoFront в браузере — ровно то, что NFR запрещает | Перейти на серверные числа | 01-business-trace.md |
| NFR-20 | Честное сообщение о невыполнимости | ЧАСТИЧНО | Коды выхода CLI (validator/cli/exit_codes.py, planner/cli/main.py:33-40), исключения Infeasible/InvalidScenario (geoscan_contracts/exceptions.py); пользовательских объяснений «что парк умеет» нет | Дописать тексты и матрицу возможностей | 01-business-trace.md |
| NFR-23 | Операционный контракт каждого процесса и метрики Prometheus | ЧАСТИЧНО | gateway/src/ops/ops.controller.ts:18-56 — четыре маршрута; /metrics содержит два синтетических счётчика. У planner и validator HTTP-сервера нет вовсе (транспорт запрещён .importlinter) | Поднять ops-HTTP у Python-процессов, добавить доменные метрики | 01-business-trace.md |
| NFR-25 | Аутентификации нет, факт зафиксирован в ограничениях | ЧАСТИЧНО | Экрана входа нет (apps/shell/src/App.tsx); перечня известных ограничений как документа нет (см. R-DOC-6) | Зафиксировать письменно в перечне ограничений | 01-business-trace.md |
| NFR-27 | Секретов в исходниках нет, .env.example у каждого процесса | ЧАСТИЧНО | gateway/.env.example полон; find src/backend -name '.env.example' — один файл, у planner и validator нет. Секретов в коде не найдено (grep по password/secret/token — только имена переменных) | Добавить .env.example остальным процессам | 01-business-trace.md |
| NFR-29, NFR-30 | Русский интерфейс и отраслевая терминология | ЧАСТИЧНО | grep -rni "дрон|БПЛА" src/ — вхождений нет; локаль ru_RU (apps/shell/src/main.tsx:17). Но бэкенд-экспорт пишет английские имена этапов (export/kml.py:47), причины unassigned — английской строкой (pipeline.py:310) | Перевести пользовательские строки бэкенда | 01-business-trace.md |
| NFR-31 | Единицы СИ с отраслевой формой записи | ЧАСТИЧНО | schema/units.json и packages/domain/src/format.ts; в бэкенд-отчёте единицы смешаны: checks/g1_airspace.py:35 units="m" против checks/g2_altitude.py:78 units="шт" | Привести единицы к одному виду | 01-business-trace.md |
| NFR-35 | Перечень ограничений ведётся с первого дня | ЧАСТИЧНО | docs/task5/TODO.md и «Известные ограничения» в src/frontend/README.md есть; сводного перечня с последствиями для пользователя нет | Свести в один документ (см. R-DOC-6) | 01-business-trace.md |
| NFR-36 | Репозиторий и демо самодостаточны | ЧАСТИЧНО | src/backend/README.md:15-17 описывает python -m planner run --scenario scenarios/s01-simple.json, команда падает (No module named planner.main, затем 19 ошибок pydantic) |
Починить команду и инструкцию | 01-business-trace.md |
| NFR-37 | Разрешительные лицензии, сводка актуальна | ЧАСТИЧНО | Сводка в docs/task5/50-stack/01-libraries.md; сверка с текущими pyproject.toml и package.json не проводилась | Сверить список зависимостей со сводкой | 01-business-trace.md |
| R-DEMO-1 | Работа сервиса на тестовом сценарии | ЧАСТИЧНО | Сценарии есть (scenarios/s01..s03), сквозной прогон невозможен (дефект D-1); фронтенд демонстрирует s04 на клиентском расчёте | Починить сквозной путь | 01-business-trace.md |
| R-DEMO-2, R-DEMO-3 | Визуализация маршрутов и показ результатов оптимизации | ЧАСТИЧНО | apps/planner/src/map/MapCanvas.tsx, map/layers.ts, components/GanttTimeline.tsx, apps/viewer3d/src/screens/Viewer3DScreen.tsx; данные — клиентский расчёт | Подключить к серверному плану | 01-business-trace.md |
| R-DOC-1, R-DOC-2 | Описание архитектуры и алгоритма оптимизации | ЧАСТИЧНО | docs/task5/91-system/01-context.md, 02-services.md, docs/task5/30-domain/03-coverage-path-planning.md, 04-fleet-routing.md — внутренние спецификации; описанный VRP не соответствует реализованному делению на полосы (pipeline.py:361-404) | Написать поставочные разделы по факту реализации | 01-business-trace.md |
| R-DOC-3 | Инструкция по запуску | ЧАСТИЧНО | src/backend/README.md и src/frontend/README.md есть; команда планировщика нерабочая, сборки контура одной командой нет | См. NFR-09 и NFR-36 | 01-business-trace.md |
| R-DOC-4 | Описание API | ЧАСТИЧНО | docs/task5/91-system/03-public-api.md, Swagger на /docs (gateway/src/main.ts:32-40), packages/api-client/openapi.snapshot.json. Описан API, которого нет: /api/scenarios в клиенте (endpoints/scenarios.ts:130-140) без контроллера в app.module.ts:9-15 | Синхронизировать документ и реализацию | 01-business-trace.md |
| R-IN-3 | Область съёмки | ЧАСТИЧНО | Перекрёстно: FR-SCN-03 (ГОТОВО), FR-SCN-05/07/09 (ЧАСТИЧНО), FR-SCN-04/06 (НЕТ), FR-TSK-01 (ЧАСТИЧНО) | См. соответствующие строки FR-SCN | 01-business-trace.md |
| R-IN-4 | Тип съёмки: RGB, мультиспектр, ИК, LiDAR, геофизика | ЧАСТИЧНО | Перекрёстно: FR-TSK-02/03/06/07, FR-FLT-03 — модель данных полна, отрицательный результат по LiDAR/геофизике не оформлен как ошибка входа | См. FR-TSK-07, FR-VAL-02 | 01-business-trace.md |
| R-IN-5, R-IN-6 | Границы разрешённого ВП и бесполётные зоны | ЧАСТИЧНО | Перекрёстно: FR-ASP-01…FR-ASP-09, FR-STE-07 — валидатор ГОТОВО, планировщик НЕТ | См. FR-PLN-03 | 01-business-trace.md |
| R-IN-7 | Резервные площадки посадки | ЧАСТИЧНО | Перекрёстно: FR-STE-04/06/07 — модель есть, использования нет | См. FR-PLN-13 | 01-business-trace.md |
| R-IN-8 | Скорость ветра | ЧАСТИЧНО | Перекрёстно: FR-MET-01 (ГОТОВО), FR-MET-03/05/06 (ЧАСТИЧНО) | См. FR-MET-03 | 01-business-trace.md |
| R-OPT-1 … R-OPT-3 | Два критерия и выбор критерия в интерфейсе | ЧАСТИЧНО | Перекрёстно: FR-PLN-17/18/19 — метрики считаются, оптимизации нет (pipeline.py:350-358) | См. FR-PLN-17 | 01-business-trace.md |
| R-OUT-1 … R-OUT-8 | Состав и форматы полётного задания | ЧАСТИЧНО | Перекрёстно: FR-EXP-01…FR-EXP-12 — KML/GeoJSON формируются, но без скорости, точек старта/посадки, высота только в KML | См. строки FR-EXP | 01-business-trace.md |
| R-VEH-1, R-VEH-2 | Учёт параметров полёта и объёма заряда | ЧАСТИЧНО | Перекрёстно: FR-FLT-06/07 и FR-FLT-08/FR-STE-05 — дальность и оборот не учтены | См. FR-FLT-06, FR-PLN-12 | 01-business-trace.md |
| R-VEH-4 | Учёт скорости ветра | ЧАСТИЧНО | Перекрёстно: FR-MET-02 (ГОТОВО), FR-MET-03 (ЧАСТИЧНО), FR-MET-04 (ГОТОВО в валидаторе) | См. FR-MET-03 | 01-business-trace.md |
| R-X-1 | Резерв заряда | ЧАСТИЧНО | Перекрёстно: FR-FLT-08 — резерв по времени реализован, энергетической модели нет | См. FR-FLT-08 | 01-business-trace.md |
| R-X-2, R-X-3 | Минимальная безопасная высота и «не ниже точки старта» | ЧАСТИЧНО | Перекрёстно: FR-FLT-07, BR-11, BR-12 | См. FR-FLT-07 | 01-business-trace.md |
| R-X-4 | Совместимость «борт ↔ полезная нагрузка» | ЧАСТИЧНО | Перекрёстно: FR-FLT-03 (ГОТОВО), FR-FLT-04, BR-05 (ЧАСТИЧНО в планировщике) | См. BR-05 | 01-business-trace.md |
| R-X-5 | Ограничение по ветру у каждого борта | ЧАСТИЧНО | Перекрёстно: FR-MET-03, BR-03 — отсечение есть, счётчика доступных бортов нет | См. FR-MET-03 | 01-business-trace.md |
| R-X-6 | Учёт разворотов во времени галса | ЧАСТИЧНО | Перекрёстно: FR-PLN-08, BR-24 — стоимость разворота в бюджете есть, метрики нулевые | См. FR-PLN-08 | 01-business-trace.md |
| R-X-7 | Поведение при отказе борта в середине задания | ЧАСТИЧНО | Перекрёстно: FR-MON-01…FR-MON-06, BR-27 | См. FR-MON-01 | 01-business-trace.md |
| R-X-8 | Масштабируемость с числами | ЧАСТИЧНО | Перекрёстно: FR-PLN-23, FR-RPT-08, NFR-02/NFR-03 — все НЕТ, кроме колонки solve_time в таблице | См. FR-RPT-08 | 01-business-trace.md |
| NFR-10 | Читаемость экранов и контраст не ниже 4,5:1 | НЕ ПРОВЕРЕНО | Токены палитры есть (packages/tokens/src/tokens.ts, theme.ts), но замер контраста и проверка на 1440×900/1920×1080 требуют браузера; Node/npm в среде отсутствуют (ограничение ревью) | Замерить на стенде | 01-business-trace.md |
| FR-VAL-01, FR-VAL-02 | Валидация входа до запуска решателя | ВНЕ ОБЪЁМА | По docs/task5/90-business/05-fr-planning.md:86-87 это вход планировщика. Со стороны валидатора отказ считать оптику реализован: validator/geo/optics.py OpticsUnavailable, тесты validator/tests/test_optics.py:154,161 | 05-validator.md |
|
| R-SUB-1 … R-SUB-9 | Артефакты сдачи (концепция, архитектура, прототип, алгоритм, код, демо, презентация, документация, инструкция) | ВНЕ ОБЪЁМА | Артефакты сдачи, а не код. Из кода подтверждается R-SUB-5 (исходники в репозитории) и частично R-SUB-3 (прототип интерфейса — src/frontend/apps/*) | 01-business-trace.md |
|
| BR-24 (часть) | Инвариант T_план строго больше L/V_g | ГОТОВО | validator/checks/g6_coverage.py:85-113 + metrics.naive_survey_time_s; тест test_plan_without_turns_fails_turn_time_check | 01-business-trace.md |
|
| FR-FLT-03 | Матрица совместимости как данные, а не как код | ГОТОВО | docs/task5/10-hardware/payloads.yaml → shared-py/geoscan_contracts/catalog.py (compatibility, payload_covers, model_covers); тест validator/tests/test_contracts.py::test_catalog_compatibility_matrix проходит; GET /api/payloads (gateway/src/fleet/fleet.controller.ts:48-72) | 01-business-trace.md |
|
| FR-MET-01 | Ветер как вектор, метеоконвенция «откуда дует» | ГОТОВО | shared-py/geoscan_contracts/scenario.py:126-131; planner/planner/geometry/wind.py:15 — wind_to_deg = (wind_from_deg + 180) % 360; независимая реализация в validator/geo/optics.py | 01-business-trace.md |
|
| FR-MET-02 | Время по путевой скорости V_g = sqrt(V_a²−W⊥²)+W∥ | ГОТОВО | planner/planner/geometry/wind.py:8-34 и validator/geo/optics.py (две независимые реализации); тесты test_ground_speed_tailwind_and_headwind, test_ground_speed_crosswind_only, test_crosswind_gain_matches_theory проходят | 01-business-trace.md |
|
| FR-MET-04 | Допустимость поперечной составляющей ветра на галсе | ГОТОВО | validator/checks/g4_weather.py:75-112; тест test_crosswind_above_airspeed_is_a_violation | 01-business-trace.md |
|
| FR-PLN-01 | Галсы с шагом из качества, ширины захвата и перекрытия | ГОТОВО | pipeline.py:321-330 → geometry/optics.py:86-97 → coverage/transects.py:26-103; контрольный шаг 47 м@100 м подтверждён тестом test_pf1b_swath_spacing_at_60_percent | 01-business-trace.md |
|
| FR-PLN-19 | Выбранный критерий отражён в плане | ГОТОВО | plan.solver.criterion (pipeline.py:487) присутствует в выгруженном JSON плана /tmp/plan_s01.json | 04-planner-core.md |
|
| FR-PLN-22 | Детерминизм расчёта | ГОТОВО | Два прогона одного сценария, сравнение normalize_plan(...) → (a) deterministic: True; случайности в коде нет (аргумент rng в pipeline.py:276 не используется) | 04-planner-core.md |
|
| FR-SCN-03 | Полигон не менее 3 вершин, расчёт заблокирован до замыкания | ГОТОВО | packages/domain/src/store.ts:194,218 (draftRing.length < 3 → выход); кнопки заблокированы: apps/planner/src/screens/ScenarioScreen.tsx:193, AirspaceScreen.tsx:94 | 01-business-trace.md |
|
| FR-SCN-07 | Правка геометрии в метрической проекции | ГОТОВО | packages/domain/src/geo.ts:106-136 (rotateRing, scaleRing, translateRing, transformRings) через локальную метрическую проекцию; бэкенд-аналог planner/planner/geometry/projection.py | 01-business-trace.md |
|
| FR-TSK-05, BR-15 | Высота и шаг галсов из GSD и оптики; высота — производная от GSD | ГОТОВО | planner/planner/geometry/optics.py:39-97, validator/geo/optics.py; прогон validator/tests/test_optics.py: GSD 1,96 см@100 м, footprint 117×78 м, шаг 47 м@100 м и 70,5 м@150 м, altitude_for_gsd обратна gsd | 01-business-trace.md |
|
| FR-VAL-03 | Валидатор не использует код покрытия/распределения, геометрию считает сам | ГОТОВО | grep -rn "planner" validator/ → только комментарии (geo/init.py:3, tests/fixtures.py:3) и литералы теста; своя геометрия validator/geo/optics.py (225 стр.), geo/projection.py (136), geo/measures.py (130); uv run --with import-linter lint-imports --config .importlinter → «Валидатор не видит планировщик KEPT», при инъекции from planner.geometry import optics в validator/metrics.py → BROKEN: validator.metrics -> planner.geometry.optics (l.167) |
05-validator.md |
|
| FR-VAL-03, NFR-18 | Валидатор не переиспользует код решателя | ГОТОВО | .importlinter (контракт validator-is-independent) + AST-барьер validator/tests/test_independence.py со сканом импортов, importlib и самопроверкой; все параметризованные тесты проходят (uv run pytest -q → 150 passed) | Установить import-linter: uv run lint-imports сейчас не запускается |
01-business-trace.md |
| FR-VAL-04 | Жёсткая проверка violations.nfz длиной траектории | ГОТОВО | validator/checks/g1_airspace.py:26-64; тесты test_no_fly_zone_intrusion_is_measured_in_meters, test_zone_above_flight_level_is_not_a_violation | 01-business-trace.md |
|
| FR-VAL-05 | Жёсткая проверка violations.airspace | ГОТОВО | validator/checks/g1_airspace.py:67-102; тест test_outside_allowed_airspace_is_measured | 01-business-trace.md |
|
| FR-VAL-06 | Жёсткая проверка violations.endurance с резервом | ГОТОВО | validator/checks/g3_energy.py:20-61; тест test_endurance_budget_is_checked_with_reserve | 01-business-trace.md |
|
| FR-VAL-07 | Жёсткая проверка violations.wind | ГОТОВО | validator/checks/g4_weather.py:27-170 (три проверки); тесты test_wind_above_uav_limit_is_a_violation, test_crosswind_above_airspeed_is_a_violation | 01-business-trace.md |
|
| FR-VAL-08 | Жёсткая проверка violations.payload | ГОТОВО | validator/checks/g5_assignment.py:20-47; тест test_incompatible_payload_is_a_violation | 01-business-trace.md |
|
| FR-VAL-09 | Жёсткая проверка violations.altitude | ГОТОВО | validator/checks/g2_altitude.py:24-162 (потолок, минимум, ниже старта, соответствие GSD); тест test_altitude_above_ceiling_is_a_violation | 01-business-trace.md |
|
| FR-VAL-10 | Жёсткая проверка violations.turnaround | ГОТОВО | validator/checks/g3_energy.py:101-145; тест test_turnaround_deficit_is_measured_in_seconds | 01-business-trace.md |
|
| FR-VAL-11 | unassigned пуст либо у каждой записи есть причина | ГОТОВО | validator/checks/g5_assignment.py:96-153; тест test_unassigned_without_reason_fails | 01-business-trace.md |
|
| FR-VAL-14 | Предупреждения отделены от нарушений | ГОТОВО | validator/checks/base.py:22-26 (CheckStatus.WARN), admission.py:47-55 (предупреждения не отменяют допуск вне --strict); тест test_ivp_threshold_is_warning_not_violation | 01-business-trace.md |
|
| FR-VAL-18 (часть) | Регрессии против наивных формул: T_план > L/V_g, площадь ±0,1 %, GSD по таблице | ГОТОВО | checks/g6_coverage.py:85 (G6-02, прогон: «план 2325 с против наивных 1536 с»); validator/tests/test_projection.py:11,25,57 (AREA_TOLERANCE_FRAC=0.001, сверка с geodesic_area_m2); validator/tests/test_optics.py:33-135 | 05-validator.md |
|
| NFR-32 | Геометрия в метрической проекции, расхождение площади ≤ 0,1 % | ГОТОВО | validator/geo/projection.py (UTM с откатом на AEQD), planner/planner/geometry/projection.py; тесты test_projected_area_matches_geodesic, test_projected_length_matches_geodesic, test_s01_polygon_is_about_one_square_kilometer, test_roundtrip_keeps_lon_lat_order — проходят | 01-business-trace.md |
|
| R-IN-1 | Координаты взлётно-посадочных пунктов произвольные | ГОТОВО | shared-py/geoscan_contracts/scenario.py:23-26 — position: Coordinate без справочника; справочника площадок в коде нет | 01-business-trace.md |
|
| R-IN-2 | Перечень доступных БВС | ГОТОВО | docs/task5/10-hardware/fleet.yaml → catalog.py → GET /api/fleet (gateway/src/fleet/fleet.controller.ts:22-46, с ETag); тест test_catalog_units_are_converted | 01-business-trace.md |
|
| R-PLT-3 | Стек из рекомендованного списка | ГОТОВО | src/backend/gateway/package.json (NestJS + Fastify), src/backend/pyproject.toml (Python 3.12, pydantic, shapely, pyproj), src/frontend/package.json (React + Vite); сверка — docs/task5/50-stack/01-libraries.md | 01-business-trace.md |
|
| R-VEH-3 | Учёт скорости | ГОТОВО | Перекрёстно: FR-FLT-06 и FR-MET-02 — треугольник скоростей реализован дважды, тесты проходят | 01-business-trace.md |
Системные требования (91-system)¶
| ID | Требование | Статус | Доказательство | Что доделать | Отчёт |
|---|---|---|---|---|---|
| D-02 | Очередь — таблица plan_jobs в Postgres + диспетчер в gateway, 2/20, без брокера | НЕТ | Брокера нет (тривиально), но и таблицы с диспетчером нет: find . -name '*.sql' пусто; grep -rn plan_jobs src/backend → только заглушка gateway/src/plans/plans.controller.ts:34 | Таблица plan_jobs, диспетчер, лимиты 2/20, 429 при переполнении | 02-system-trace.md |
| D-03 | Сценарии и планы — в Postgres с самого начала, файлового хранилища нет | НЕТ | Код принял противоположное решение: хранилище — файлы. Сценарии читаются с диска (geoscan_contracts/documents.py:37-48, validator/validator/cli/run.py:53-62), планы пишутся файлами (planner/planner/cli/main.py:41-42,60); Postgres отсутствует целиком | Либо реализовать Postgres, либо зафиксировать отступление записью в 11-decisions.md | 02-system-trace.md |
| SR-API-01 | Документы сценария и плана — snake_case, гейтвеем не переименовываются | НЕТ | planner/planner/cli/main.py:40 пишет model_dump_json(by_alias=True); алиасы в shared-py/geoscan_contracts/schema.py — camelCase. Прогон: TOP KEYS ['schemaVersion','scenarioId','generatedAt',…], METRICS KEYS ['makespanS','totalFlightTimeS','coverageFraction',…]; против schema/plan.schema.json — 37 ошибок. Вход тоже camelCase-обязательный: schema.py:110-111 (windSpeedMs без populate_by_name), :129 (timeLimitS), :79 (gsdCm) (также в 06-gateway.md: ЧАСТИЧНО) | Планировщик должен читать/писать канонический snake_case-документ (geoscan_contracts.scenario/plan); schema.py удалить | 03-contracts.md |
| SR-API-03 | POST /api/scenarios → 201 + Location, сохранение в Postgres | НЕТ | Контроллера сценариев нет: ls src/backend/gateway/src → config fleet ops plans validate common | Ручка + хранилище сценариев | 02-system-trace.md |
| SR-API-03/04 | POST/GET /api/scenarios, GET /api/scenarios/{id}, засев демо s01–s10 | НЕТ | ls src/backend/gateway/src → common, config, fleet, ops, plans, validate (модуля scenarios нет); app.module.ts:9-15 — пять импортов | GW-06: модуль scenarios, хранение jsonb, demo-seed | 06-gateway.md |
| SR-API-04 | GET /api/scenarios[/{id}], демо s01–s10 сразу после развёртывания | НЕТ | Ручки нет; ls src/backend/scenarios → s01-simple.json, s02-nfz.json, s03-multi-sites.json (три вместо десяти) | Ручки + 7 недостающих демо-сценариев | 02-system-trace.md |
| SR-API-05 | Проверка тела по JSON-схеме до вызова домена (400 SCHEMA_VALIDATION_FAILED) | НЕТ | ajv и ajv-formats объявлены в gateway/package.json:21-22, но grep -rn "ajv\\|Ajv" src/backend/gateway/src → пусто. grep -rn "scenario.schema.json\\|plan.schema.json" src/ → совпадения только внутри самих схем (schema/scenario.schema.json:3, schema/plan.schema.json:3) (также в 02-system-trace.md: ЧАСТИЧНО) |
Подключить ajv в гейтвее, возвращать errors[] с path (JSON Pointer), rule, message | 03-contracts.md |
| SR-API-07 | Опрос GET /api/plans/{id} до терминального статуса; plan только при done | НЕТ | packages/state/src/api-compute.ts:38 — один getPlan сразу после createPlan, status/progress/error не разбираются; гейтвей src/backend/gateway/src/plans/plans.controller.ts:47-51 всегда отдаёт {planId,status:'queued',message} (также в 02-system-trace.md: ЧАСТИЧНО) | Добавить опрос/SSE и обработку queued|running|done|error | 07-frontend.md |
| SR-API-08 | SSE GET /api/plans/{id}/events, 4 типа событий, heartbeat 15 с | НЕТ | grep -rn 'sse|text/event-stream' src/backend/gateway/src — ноль совпадений | Поток событий progress/objective/done/error + snapshot | 02-system-trace.md |
| SR-API-10 | 422 с машиночитаемым details (GSD_UNREACHABLE, несовместимая нагрузка) | НЕТ | Код объявлен в shared/src/errors/codes.ts:12, но grep -rn GSD_UNREACHABLE src/backend/gateway/src — ноль потребителей | Обработка + details.uavs[] по каждому борту | 02-system-trace.md |
| SR-API-11 | 429 QUEUE_FULL + Retry-After, лимиты 2 running / 20 queued | НЕТ | Очереди нет; QUEUE_FULL встречается только в реестре кодов shared/src/errors/codes.ts:14 | Диспетчер очереди и лимиты | 02-system-trace.md |
| SR-API-14 | Idempotency-Key: сохранение полного ответа, 409 при конфликте тела | НЕТ | grep -rn 'Idempotency' src/backend/gateway/src — ноль; клиент заголовок шлёт (packages/api-client/src/endpoints/plans.ts:13-15) (также в 07-frontend.md: ЧАСТИЧНО) | Реестр идемпотентности в одной транзакции с постановкой задачи | 02-system-trace.md |
| SR-API-15 | Экспорт 4 форматов, ?uav=, zip, Content-Disposition | НЕТ | Ручки нет; planner/planner/export/init.py:19 — raise NotImplementedError для .plan и waypoints | Ручка + два формата + zip-архив по бортам | 02-system-trace.md |
| SR-API-15/16 | Экспорт GET /api/plans/{id}/export/{fmt}, ?uav=, X-Plan-Admitted | НЕТ | каталога gateway/src/export/ нет; в plans.controller.ts только @Post() (:16) и @Get(':id') (:38) | GW-11: трансляция Planner.ExportPlan (proto/geoscan/planner.proto:7), заголовки, 409/404 | 06-gateway.md |
| SR-API-15/FE-09 | Экспорт через GET /api/plans/{id}/export/{fmt} | НЕТ | В api-client ручки экспорта нет (endpoints/ = fleet, scenarios, plans, validate); экспорт клиентский — apps/planner/src/screens/ExportScreen.tsx:16 импортирует экспортеры из @geoscan/domain | Добавить ручку экспорта и скачивание blob'ом, включая zip и X-Plan-Admitted | 07-frontend.md |
| SR-API-16 | Экспорт только при done; X-Plan-Admitted для недопущенного плана | НЕТ | Ручки экспорта в gateway/src не существует | Ручка с проверкой статуса и заголовком допуска | 02-system-trace.md |
| SR-API-17 | POST /api/plans/{id}/replan → 202 + parentPlanId | НЕТ | Маршрута нет в gateway/src/plans/plans.controller.ts; клиент его вызывает — packages/api-client/src/endpoints/plans.ts:33 (также в 07-frontend.md: ЧАСТИЧНО) | Ручка перепланирования | 02-system-trace.md |
| SR-API-18 | POST /api/validate синхронно отдаёт отчёт валидатора | НЕТ | gateway/src/validate/validate.controller.ts:24-29 — заглушка {status:'stub'} (также в 07-frontend.md: ЧАСТИЧНО) | Прокси на Validator.Validate | 02-system-trace.md |
| SR-CFG-03 | PLAN_CONCURRENCY/PLAN_QUEUE_LIMIT/PLAN_DEFAULT_TIME_LIMIT_S/PLAN_SYNC_TIME_LIMIT_S/DEFAULT_SEED из env | НЕТ | Ни одной переменной в gateway/src/config/config.schema.ts:3-15 и в gateway/.env.example | Добавить переменные и их применение | 02-system-trace.md |
| SR-CFG-04 | Дефолты .env.example поднимают контур docker compose up офлайн | НЕТ | ls docker-compose — файла нет (также в 09-security-config.md: ЧАСТИЧНО)* | compose + безопасные дефолты | 02-system-trace.md |
| SR-CFG-06 | scripts/sync-secrets.sh создаёт Secret geoscan-secrets из creds-store | НЕТ | ls scripts → bootstrap-graphify.sh, deploy.sh | Идемпотентный скрипт синхронизации секретов | 02-system-trace.md |
| SR-CFG-07 | Процедура ротации секрета описана и проверена прогоном | НЕТ | Ни скрипта, ни инструкции в репозитории | Описать и проверить ротацию | 02-system-trace.md |
| SR-CFG-08 | Один образ на коммит, тег — 8-символьный sha, :latest запрещён | НЕТ | k3s/geoscan/40-deployment.yaml:27 — image: |
Подставить sha-теги в манифесты и compose | 02-system-trace.md |
| SR-CFG-09 | Офлайн-пакет для жюри (образы, compose, .env, PMTiles, шрифты, s01–s10, инструкция) | НЕТ | ls src/frontend/apps/shell/public → только config.json.example; образов и compose нет | Собрать самодостаточный дистрибутив | 02-system-trace.md |
| SR-CFG-12 | Базовый адрес API не конфигурируется в рантайме | НЕТ | Прямо нарушено: apps/shell/src/runtime-config.ts:29-40 — fetch('/config.json') с подстановкой apiBaseUrl; apps/shell/public/config.json.example; packages/state/src/runtime-config.ts:3-20; packages/api-client/src/config.ts:51-58 читает process.env в рантайме (также в 09-security-config.md: ЧАСТИЧНО) | Убрать рантайм-конфиг адреса либо зафиксировать пересмотр SR-CFG-12 в 11-decisions.md | 02-system-trace.md |
| SR-CTX-05 | Единственный канал выхода — GET /api/plans/{id}/export/{fmt}, 4 формата | НЕТ | В src/backend/gateway/src нет контроллера экспорта (ls src/backend/gateway/src → config fleet ops plans validate common); экспорт есть только в planner/planner/export/init.py:15-19, форматы .plan и waypoints → NotImplementedError (:19) | Ручка экспорта в gateway, форматы .plan и waypoints, zip-выгрузка без ?uav= | 02-system-trace.md |
| SR-CTX-10 | Два режима поставки: k3s и офлайн docker compose | НЕТ | ls docker-compose* — файла нет; k3s/geoscan/40-deployment.yaml:27 — image: |
docker-compose.yml, реальные манифесты, sha-теги образов | 02-system-trace.md |
| SR-CTX-11 | Весь пользовательский сценарий воспроизводим только HTTP-вызовами | НЕТ | gateway/src/plans/plans.controller.ts:31-35 возвращает заглушку {planId, status:'queued', message:'Очередь plan_jobs и диспетчер — в GW-07'} | Реальный конвейер сценарий→план→экспорт | 02-system-trace.md |
| SR-CTX-12 | Единственное состояние — Postgres (схемы gateway, airspace) | НЕТ | find . -name '.sql' -not -path './.git/' — пусто; в gateway/package.json:15-34 нет драйвера БД и ORM | Схема БД, таблицы, репозитории | 02-system-trace.md |
| SR-DAT-01 | Один Postgres, схема на сервис, свои роли БД | НЕТ | find . -name '.sql' -not -path './.git/' — пусто; в gateway/package.json нет pg/ORM | Postgres, схемы gateway/airspace, роли и гранты | 02-system-trace.md |
| SR-DAT-02 | Схема gateway — ровно scenarios/plans/plan_jobs/idempotency_keys + миграции | НЕТ | Таблиц и миграций нет (find src -type d -name migrations — пусто) | Миграции четырёх таблиц | 02-system-trace.md |
| SR-DAT-03 | scenarios.body jsonb без нормализации + метаданные | НЕТ | Таблицы нет; сценарии читаются файлами — geoscan_contracts/documents.py:37-41 | Таблица scenarios | 02-system-trace.md |
| SR-DAT-04 | plans с body/metrics/validation/fleet_snapshot/parent_plan_id | НЕТ | Таблицы нет; план CLI пишет файлом — planner/planner/cli/main.py:41-42 | Таблица plans | 02-system-trace.md |
| SR-DAT-05 | plan_jobs — единственная очередь расчётов, без брокера | НЕТ | grep -rn plan_jobs src/backend → только строка-заглушка gateway/src/plans/plans.controller.ts:34 | Таблица очереди и диспетчер | 02-system-trace.md |
| SR-DAT-06 | Диспетчер select … for update skip locked, ≤2 running, ≤20 queued | НЕТ | Диспетчера нет; переменных PLAN_CONCURRENCY/PLAN_QUEUE_LIMIT нет в config/config.schema.ts:3-15 | Диспетчер с блокировками и лимитами | 02-system-trace.md |
| SR-DAT-07 | Реестр идемпотентности {scope,key} в одной транзакции с задачей | НЕТ | Таблицы нет; заголовок Idempotency-Key на сервере не читается | Таблица idempotency_keys | 02-system-trace.md |
| SR-DAT-08 | Правка сценария с планами создаёт новую версию (group_id, version+1) | НЕТ | Хранилища сценариев нет | Версионирование сценариев | 02-system-trace.md |
| SR-DAT-09 | Строка plans неизменяема после записи (права или триггер) | НЕТ | Таблицы нет | Права/триггер на запрет update и delete | 02-system-trace.md |
| SR-DAT-10 | Статусная машина plan_jobs + подбор зависших задач при старте | НЕТ | Таблицы и диспетчера нет; SOLVER_INTERRUPTED объявлен только в shared/src/errors/codes.ts:18 | Триггер переходов + аренда задач | 02-system-trace.md |
| SR-DAT-12 | Объёмы хранилища: body ≤ 2 МБ, том ≥ 5 ГБ, авточистки нет | НЕТ | Ни compose, ни PVC — в k3s/geoscan/ нет 30-postgres.yaml | Том БД и измерение размеров | 02-system-trace.md |
| SR-DAT-13 (миграции) | Изменения схемы только миграциями, имена уникальны в пределах БД | НЕТ | find src -type d -name migrations — пусто; find . -name '*.sql' — пусто. Примечание: идентификатор SR-DAT-13 задан в спецификации дважды (05-data-model.md:346 и :360), SR-DAT-14 пропущен | Завести каталог миграций; починить нумерацию требований | 02-system-trace.md |
| SR-DEP-01 (образы planner/validator) | Отдельный образ на каждый процесс | НЕТ | find . -name 'Dockerfile*' (без node_modules/.venv) → ровно два: src/backend/gateway/Dockerfile и src/frontend/packages/api-client/Dockerfile; для planner и validator Dockerfile'ов нет | Добавить Dockerfile'ы для planner и validator | 10-build-ops.md |
| SR-DEP-01 (проверка в CI) | CI падает при :latest и при плейсхолдерах | НЕТ | ls .gitea .github .gitlab-ci.yml → 'No such file or directory' для всех; pipeline-файлов в репозитории нет. Сам grep -rn ':latest' k3s/ чист (exit 1), но проверка не автоматизирована | Завести pipeline с проверками :latest, плейсхолдеров, lint, test, сборки образов | 10-build-ops.md |
| SR-DEP-02 (multi-stage) | Сборочный тулчейн не попадает в рантайм-слой | НЕТ | src/backend/gateway/Dockerfile:16 — npm install typescript @nestjs/cli @types/node --no-save; :22 — COPY --from=gateway-build /build/gateway/node_modules ./node_modules целиком, без npm prune --omit=dev | Ставить прод-зависимости отдельно (npm ci --omit=dev) или prune перед копированием | 10-build-ops.md |
| SR-DEP-02 (не-root) | Процесс в контейнере запускается не от root | НЕТ | src/backend/gateway/Dockerfile (29 строк, прочитан целиком) — инструкции USER нет ни в одной стадии | Добавить USER node (или собственного пользователя) в рантайм-стадию | 10-build-ops.md |
| SR-DEP-02 (сборка образа) | Dockerfile сервиса собирается | НЕТ | docker build -f src/backend/gateway/Dockerfile -t geoscan-gateway:review . → EXIT=1; падение на стадии shared-build: src/backend/shared/src/grpc/client-factory.ts:44 — 'error TS2352: Conversion of type ServiceClient to type T may be a mistake' | Починить приведение типа (as unknown as T либо типизировать ServiceCtor), добиться зелёного docker build | 10-build-ops.md |
| SR-DEP-02 / SR-DEP-08 (секреты) | В образе нет секретов и .env | НЕТ | src/backend/gateway/Dockerfile:27 — ENV SERVICE_API_KEY=dev-local-key; buildkit в выводе сборки: 'SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SERVICE_API_KEY") (line 27)' | Убрать дефолт ключа из образа, брать только из secretKeyRef/env контура | 10-build-ops.md |
| SR-DEP-03 | Статика фронта в образе gateway, SPA-fallback, относительные адреса remote'ов | НЕТ | @fastify/static в gateway/package.json:17, но grep -rn 'fastify/static|useStaticAssets' gateway/src — ноль; Dockerfile не копирует фронт; дефолты remote'ов — http://localhost:517x (apps/shell/vite.config.ts:28,33,38) | Раздача статики и SPA-fallback, относительные REMOTE_* | 02-system-trace.md |
| SR-DEP-04 | Полный состав k3s/geoscan/ без плейсхолдеров, apply -k одной командой | НЕТ | Каталог — нетронутый шаблон: 40-deployment.yaml:27 — |
Написать полный набор манифестов | 02-system-trace.md |
| SR-DEP-04 (состав) | Полный состав манифестов: Postgres, Job миграций, три Deployment/Service | НЕТ | ls k3s/geoscan/ → только 00-namespace, 10-certificate, 40-deployment, 50-service, 60-ingress, kustomization; diff -r k3s/template k3s/geoscan → расхождения только в подстановке |
Добавить 30-postgres.yaml (StatefulSet+PVC), 70-migrate-job.yaml, манифесты трёх процессов | 10-build-ops.md |
| SR-DEP-07 | Postgres StatefulSet + PVC, образ с PostGIS на борту | НЕТ | ls k3s/geoscan/ — 30-postgres.yaml отсутствует | StatefulSet + PVC + образ с PostGIS | 02-system-trace.md |
| SR-DEP-08 | Конфигурация только из env, Secret geoscan-secrets из creds-store | НЕТ | k3s/geoscan/40-deployment.yaml:31-33 — блок env закомментирован; Secret нигде не упоминается | secretKeyRef + скрипт создания Secret | 02-system-trace.md |
| SR-DEP-09 | Job миграций применяется до выката нового кода | НЕТ | 70-migrate-job.yaml отсутствует в k3s/geoscan/ | Job + порядок выката | 02-system-trace.md |
| SR-DEP-09/10 | Job миграций до apply -k, аддитивность миграций | НЕТ | k3s/geoscan/70-migrate-job.yaml отсутствует (ls k3s/geoscan/); find /home/qovalenko/geoscan-wt/review/src -iname 'migrat' (без node_modules/.venv) → пусто — ни мигратора, ни каталога миграций | Реализовать мигратор и Job; решения об отсрочке в docs/task5/91-system/11-decisions.md не зафиксировано | 10-build-ops.md |
| SR-DEP-10 | Миграции обратно совместимы в пределах одного выката | НЕТ | Миграций нет (find . -name '*.sql' — пусто) | Правило и проверка при появлении миграций | 02-system-trace.md |
| SR-DEP-11 | Фиксированная последовательность выката с нуля + smoke | НЕТ | Ни инструкции выката, ни smoke-скрипта в репозитории (ls scripts → bootstrap-graphify.sh, deploy.sh) | Инструкция и smoke | 02-system-trace.md |
| SR-DEP-13 | docker-compose.yml в корне того же состава, что k3s | НЕТ | ls docker-compose* — файла нет | compose того же состава и с теми же образами | 02-system-trace.md |
| SR-DEP-14 | Выкат не завершён без scrape-target и дашборда в тот же день | НЕТ | Каталога k3s/monitoring нет; дашбордов нет (также в 10-build-ops.md: НЕ ПРОВЕРЕНО) | Цели и дашборд | 02-system-trace.md |
| SR-NFR-01 | time_limit_s ограничивает только фазу решения VRP | НЕТ | Решателя нет: grep -rn ortools --include=*.toml → только комментарий validator/pyproject.toml:7; planner/planner/pipeline.py:489 лишь переписывает time_limit_s в отчёт | Решатель и бюджет именно на фазу решения | 02-system-trace.md |
| SR-NFR-02 | По исчерпании бюджета — feasible; timeout только без допустимого решения | НЕТ | planner/planner/pipeline.py:492 жёстко ставит status='feasible' либо 'feasible_with_unassigned' — значение вне перечня optimal/feasible/infeasible/timeout | Корректные статусы решателя | 02-system-trace.md |
| SR-NFR-03 | Останов не позднее time_limit_s + 1 с, done за +5/+15 с | НЕТ | Бюджет решателя не применяется (pipeline.py:478-491 только измеряет фактическое время) | Применение и измерение бюджета | 02-system-trace.md |
| SR-NFR-04 | Детерминированный досрочный останов на малых задачах | НЕТ | Решателя и критериев останова нет | Критерий останова в конфигурации | 02-system-trace.md |
| SR-NFR-05 | Дефолт time_limit_s = 30 с, env-переменная, диапазон 1…600 | НЕТ | schema/scenario.schema.json требует solver.time_limit_s; env-переменной нет (config/config.schema.ts:3-15), проверки диапазона нет | Дефолт, env и валидация 1…600 | 02-system-trace.md |
| SR-NFR-06 | Демо-сценарий проходит полный цикл за 10–15 с | НЕТ | Полный цикл не собирается — POST /api/plans заглушка, planner не читает сценарии | Собрать цикл и замерить | 02-system-trace.md |
| SR-NFR-07 | Исчерпание бюджета в async — не ошибка транспорта | НЕТ | Асинхронного режима и очереди нет | Async-режим и семантика завершения | 02-system-trace.md |
| SR-NFR-08 | Диапазон 1…20 бортов, 1…200 км², 1…5 ВПП, 0…20 зон | НЕТ | В scenarios/ три сценария (s01 ≈1 км², s02 с одной зоной, s03 — 3 борта, 2 ВПП); наборов sc-area и sc-nfz нет | Наборы масштабных сценариев и прогоны | 02-system-trace.md |
| SR-NFR-09 | Графики времени решения и качества от размера входа + монотонность | НЕТ | Ни команды сбора, ни данных; в validator/validator/cli нет соответствующей подкоманды | Команда сбора и публикация графиков | 02-system-trace.md |
| SR-NFR-10 | ≤2 одновременных расчётов, ≤20 ожидающих, оба предела в env | НЕТ | Очереди нет (см. SR-DAT-05); переменных PLAN_CONCURRENCY/PLAN_QUEUE_LIMIT нет | Диспетчер с лимитами | 02-system-trace.md |
| SR-NFR-11 | SSE-событие не реже одного раза в 2 с | НЕТ | SSE-маршрута нет (grep -rn 'text/event-stream' src/backend/gateway/src — ноль) | Поток с keep-alive | 02-system-trace.md |
| SR-NFR-13 | requests/limits по каждому контейнеру с указанными значениями | НЕТ | k3s/geoscan/40-deployment.yaml:41-43 — обобщённые 50m/128Mi и 1 CPU/512Mi для единственного контейнера; требуемых planner 2 CPU / 2 Gi нет | Манифесты по процессам с заданными ресурсами | 02-system-trace.md |
| SR-NFR-14 | Перезапуск не теряет данные и не оставляет висящих задач | НЕТ | Состояния нет — БД отсутствует; задач нет | Хранилище + аренда задач | 02-system-trace.md |
| SR-OBS-02 | /readyz = 200 только при готовности зависимостей, иначе 503 с именем зависимости | НЕТ | gateway/src/ops/ops.controller.ts:26-29 — всегда readyzBody(true), комментарий 'Skeleton: always ready until DB and planner checks land' | Реальные проверки БД и доменов | 02-system-trace.md |
| SR-OBS-04 | Метрики planner/validator (plan_duration, plan_total, validator_violations_total) | НЕТ | grep -rn prometheus src/backend — ноль; у Python-процессов нет HTTP-сервера | Экспортёры метрик | 02-system-trace.md |
| SR-OBS-05 | Метрики gateway: http_request_duration, sse_connections_active, plan_jobs | НЕТ | ops.controller.ts:42-56 отдаёт две константы-заглушки geoscan_process_up и geoscan_http_requests_total 0 | Реальные метрики с шаблонными route-лейблами | 02-system-trace.md |
| SR-OBS-06 | Стандартные grpc_server_ / grpc_client_ с лейблом grpc_code | НЕТ | gRPC-канала в коде нет | Интерцепторы метрик обеих сторон | 02-system-trace.md |
| SR-OBS-07 | Структурированный JSON-лог в stdout с ts/level/service/request_id/msg | НЕТ | Логирование обычным console: main.ts:47, common/filters/domain-exception.filter.ts:59, config/config.schema.ts:28 | pino на TS и structlog на Python с обязательным request_id | 02-system-trace.md |
| SR-OBS-09 | История расчёта одной командой; лейбл app.kubernetes.io/part-of=geoscan | НЕТ | k3s/geoscan/40-deployment.yaml:12-14 — только labels: { app: geoscan } | Общий лейбл и префиксы источника в логах | 02-system-trace.md |
| SR-OBS-10 | Scrape-target в k3s/monitoring/prometheus.yml, job geoscan- |
НЕТ | ls k3s → geoscan, template; каталога monitoring не существует | Завести цели Prometheus | 02-system-trace.md |
| SR-OBS-11 | Дашборд Grafana geoscan минимум из 6 панелей, file-provisioned | НЕТ | Каталога с дашбордами в репозитории нет | JSON-дашборд | 02-system-trace.md |
| SR-OBS-12 | Четыре правила алертинга (ServiceDown, GatewayErrors и др.) | НЕТ | Правил алертинга в репозитории нет | Правила вместе со scrape-целью | 02-system-trace.md |
| SR-RPC-02 | common.proto: Geometry/Ring/Position, enum статусов, Violation/Warning/Unassigned | НЕТ | src/backend/proto/geoscan/common.proto — 5 строк, единственное message Empty {} | Общие типы + round-trip-тест GeoJSON↔Geometry с точностью 1e-9 | 02-system-trace.md |
| SR-RPC-04 | Planner.ExportPlan принимает план как данные, не идентификатор | НЕТ | proto/geoscan/planner.proto:19-23 — message ExportPlanRequest { string plan_id = 1; … } | Переписать на передачу плана (bytes plan_json / message Plan) | 02-system-trace.md |
| SR-RPC-05 | Validator.Validate: сценарий+план на вход, метрики/нарушения/warnings/unassigned/допуск на выход | НЕТ | proto/geoscan/validator.proto:9-15 — ValidateRequest{bytes plan_json} без сценария, ValidateResponse{repeated string violations} (также в 03-contracts.md: ЧАСТИЧНО) | Привести контракт к форме отчёта валидатора | 02-system-trace.md |
| SR-RPC-08 | buf breaking --against main в CI, reserved для удалённых полей | НЕТ | CI-конфигурации нет (каталогов .github/.gitea не существует); buf не установлен (which buf — пусто) (также в 03-contracts.md: НЕ ПРОВЕРЕНО) | CI-шаг buf breaking | 02-system-trace.md |
| SR-RPC-12 | Домен пишет x-request-id в каждую строку журнала | НЕТ | grep -rn 'logging|structlog' src/backend/planner src/backend/validator — ноль; gRPC-серверов нет (также в 03-contracts.md: ВНЕ ОБЪЁМА) | Логгер с обязательным полем request_id | 02-system-trace.md |
| SR-RPC-13 | Серверный интерцептор проверяет x-service-api-key → UNAUTHENTICATED | НЕТ | gRPC-серверов в planner/validator нет (нет ни одного grpc.aio.server / add_*_to_server) | Серверный интерцептор проверки ключа | 02-system-trace.md |
| SR-RPC-14 | Дедлайн на каждом вызове, PLAN_DEADLINE_MARGIN_S по умолчанию 30 | НЕТ | Переменной нет в gateway/src/config/config.schema.ts:3-15 и в gateway/.env.example; вызовов нет | Дедлайны + env-переменная | 02-system-trace.md |
| SR-RPC-16 | Ретраи только идемпотентных unary, до 2, backoff 200 мс с джиттером | НЕТ | gRPC-клиентов в gateway нет (grep -rn createGrpcServiceClient gateway/src — ноль) | Политика ретраев на Validate и ExportPlan | 02-system-trace.md |
| SR-SVC-01 | Контур ровно gateway+planner+validator(+postgres), объявлен в одном месте | НЕТ | Нет docker-compose.yml; k3s/geoscan/40-deployment.yaml:6 — единственный обобщённый Deployment name: geoscan | compose + k3s по процессам + барьерный тест на перечень | 02-system-trace.md |
| SR-SVC-06 | POST /api/validate принимает чужой план и отдаёт 200 с метриками | НЕТ | gateway/src/validate/validate.controller.ts:24-29 — {status:'stub', message:'Проксирование Validator.Validate будет подключено в GW-12'} | Прокси на Validator.Validate, 503 при недоступности | 02-system-trace.md |
| SR-SVC-09 | planner запускается из CLI на файле сценария (run --scenario --seed --out) | НЕТ | uv run python -m planner --help → No module named planner.main; через planner.cli — 'unrecognized arguments: --seed 42'; без --seed — ValidationError: 19 validation errors for Scenario на scenarios/s01-simple.json | main.py в корне пакета, флаг --seed, единая модель сценария | 02-system-trace.md |
| SR-SVC-11 | airspace — модуль в gateway за интерфейсом из airspace.proto, AIRSPACE_MODE | НЕТ | grep -rn 'airspace|AIRSPACE' src/backend/gateway src/backend/proto — ноль совпадений | Порт, модуль, env-переключатель local/remote | 02-system-trace.md |
| SR-SVC-13 | Минимальный контур gateway+planner+postgres проходит сквозной путь | НЕТ | grep -rn createGrpcServiceClient src/backend/gateway/src — ноль; БД нет; ручки экспорта нет | Собрать и прогнать минимальный контур на s01 | 02-system-trace.md |
| SR-TST-01 | Набор tests/barriers/ с точкой входа scripts/barriers.sh и job barriers в CI | НЕТ | ls src/backend/tests → integration, conftest.py; ls scripts → bootstrap-graphify.sh, deploy.sh; CI-конфигурации в репозитории нет | Завести набор, скрипт и CI-job | 02-system-trace.md |
| SR-TST-03 | Барьер «в домене только ops-маршруты» (статика + динамика) | НЕТ | Теста нет; доменных HTTP-серверов тоже нет | Барьер по исходникам + динамическая проверка | 02-system-trace.md |
| SR-TST-04 | Барьер сквозного контекста: ни один вызов в домен без metadata | НЕТ | Теста нет; grep -rn 'x-service-api-key' src/backend → только shared/src/grpc/metadata.ts:9 | Статический скан + перехватчик на стороне домена | 02-system-trace.md |
| SR-TST-05 | Барьер контракта ошибок: одна таблица, табличный тест по 6 кодам | НЕТ | Теста нет; таблица продублирована внутри shared/src/grpc/grpc-to-http.ts (константа :4-12 и switch :14-33) | Барьер + дедупликация таблицы | 02-system-trace.md |
| SR-TST-06 | Барьеры конфигурации и деплоя по репозиторию (Ingress, теги, литералы хостов) | НЕТ | Тестов нет; k3s/geoscan содержит плейсхолдеры, проверять нечего | Статические барьеры | 02-system-trace.md |
| SR-TST-08 (исполнение) | Контракт import-linter реально запускается (CI/Makefile) | НЕТ | grep -c "import-linter" src/backend/uv.lock → 0; cd src/backend && uv run lint-imports → error: Failed to spawn: lint-imports; src/backend/Makefile содержит только proto-цели; в корне нет .github/.gitea/.gitlab-ci.yml; ls src/backend/.venv/bin без lint-imports |
Добавить import-linter в dev-группу pyproject, зафиксировать в uv.lock, завести цель make lint и CI-job |
05-validator.md |
| SR-TST-09 | CI-job на чистом venv: только дистрибутив валидатора, прогон CLI на s01–s10 | НЕТ | CI нет; в scenarios/ три сценария вместо десяти | Job + недостающие сценарии | 02-system-trace.md |
| SR-TST-10 (5) | makespan не растёт при увеличении числа бортов (набор sc-fleet) | НЕТ | grep -rn "monoton|sc-fleet" src/backend/{tests,planner/tests,validator/tests} — пусто; фикстур sc-fleet нет | Нужен работающий решатель и набор фикстур на 1/2/3/5/10/20 бортов | 08-tests.md |
| SR-TST-10 (6) | Два прогона с одним seed дают идентичный план после нормализации | НЕТ | grep -rn "seed" по тестам даёт только константу seed=42 в фикстурах (tests/integration/conftest.py:72, planner/tests/test_kml_export.py:57); механика нормализации есть и проверена (validator/tests/test_contracts.py:59), но повторного прогона планировщика нет | Прогнать pipeline дважды на s04 и сравнить нормализованные дампы | 08-tests.md |
| SR-TST-12 | Сверка JSON-схемы и .proto по множеству полей и типов | НЕТ | Теста нет; сверять нечего — документы в proto передаются как bytes (planner.proto:11, validator.proto:10) | Типизировать proto и написать сверку со schema/ | 02-system-trace.md |
| SR-TST-13 | Контрактные тесты gateway↔planner и gateway↔validator против реальных процессов | НЕТ | docker-compose.ci.yml отсутствует; gRPC-процессов нет | CI-контур и контрактные тесты по всем RPC | 02-system-trace.md |
| SR-TST-14 | Приёмочный прогон s01–s10 по обоим критериям, viol≠0 останавливает сборку | НЕТ | uv run python -m validator run --scenarios scenarios/ --criteria makespan,total_time → три строки «нет плана», EXIT=0 (validator/validator/cli/run.py:148-154 при пустом списке отчётов возвращает EXIT_OK) (также в 08-tests.md: ЧАСТИЧНО) | Сценарии, планировщик, гейт в CI, ненулевой код при отсутствии планов | 02-system-trace.md |
| SR-TST-15 | E2E-smoke по публичному REST на контуре gateway+planner | НЕТ | Ручек сценариев, SSE и экспорта нет (см. SR-API-03…08, SR-API-15) | Smoke-скрипт | 02-system-trace.md |
| SR-TST-16 | Тест идемпотентности: один расчёт, байт-в-байт повтор, 409 на другое тело | НЕТ | Идемпотентности на сервере нет (grep -rn Idempotency gateway/src — ноль) | Реализация + тест | 02-system-trace.md |
| D-01 | Аутентификации пользователей нет; форма A-04 (metadata на доменных вызовах) сохраняется | ЧАСТИЧНО | Отрицательная часть выполнена: в gateway/package.json:15-34 нет passport/jsonwebtoken, guard'ов в gateway/src нет, процесса auth нет. Положительная часть — buildMetadata (shared/src/grpc/metadata.ts:3-14) не вызывается ниоткуда | Проставлять metadata при появлении доменных вызовов | 02-system-trace.md |
| D-04 | export/ — модуль planner; наружу RPC Planner.ExportPlan, принимающий план как данные | ЧАСТИЧНО | Модуль на месте и вызывается функцией от плана (planner/planner/export/init.py:8-19). Но контракт принимает идентификатор: proto/geoscan/planner.proto:19-23 — string plan_id = 1. Плюс вторая реализация KML на фронте: src/frontend/packages/domain/src/exporters.ts:130 | Переписать ExportPlanRequest на план как данные; убрать дубль экспорта на фронте | 02-system-trace.md |
| D-05 | PostGIS на старте не включается, расчёты Shapely/pyproj; образ Postgres — сразу с PostGIS | ЧАСТИЧНО | Расчётная половина принята именно так: validator/validator/geo/projection.py, measures.py, planner/planner/geometry/projection.py; зависимости validator/pyproject.toml:11-13 и planner/pyproject.toml:7-9; PostGIS не используется. Вторая половина не реализована — манифеста Postgres нет (ls k3s/geoscan/ без 30-postgres.yaml) | Манифест Postgres с образом, содержащим PostGIS | 02-system-trace.md |
| SR-API-02 | x-request-id в каждом ответе, включая ошибочный, и в metadata доменного вызова | ЧАСТИЧНО | common/interceptors/request-id.interceptor.ts:20-27 ставит заголовок, но интерцептор не срабатывает для несопоставленных маршрутов и для 413 (FST_ERR_CTP_BODY_TOO_LARGE обрабатывается фильтром); в metadata и в лог не уходит | Перенести на хук Fastify, прокинуть в metadata и логи | 02-system-trace.md |
| SR-API-06 | POST /api/plans → 202 {planId,status,scenarioId,queuePosition} + Location | ЧАСТИЧНО | plans.controller.ts:16-36 отдаёт 202 и planId, но без scenarioId, queuePosition и заголовка Location; очереди и синхронного режима при time_limit_s ≤ 5 нет | Диспетчер, полный конверт ответа, Location, синхронный режим | 02-system-trace.md |
| SR-API-09 | Тело ошибки {errorCode,message,details?,errors?}; таблица gRPC→HTTP в одном месте | ЧАСТИЧНО | Форма тела — shared/src/grpc/grpc-to-http.ts:41-46. Но соответствие кодов записано дважды в одном файле: GRPC_TO_HTTP (:4-12, мёртвая константа) и switch в grpcToHttp (:14-33) | Оставить один экземпляр таблицы, добавить табличный тест по 6 кодам | 02-system-trace.md |
| SR-API-09…13 | Единый разбор ошибок 400/422/429/503 в ApiError | ЧАСТИЧНО | client.ts:84-93 строит ApiError из тела с errorCode, иначе INTERNAL. Но тело парсится до проверки res.ok (client.ts:81-82) — не-JSON ответ даёт SyntaxError; Retry-After не читается; details.uavs[] (s08) не разбирается; ни один экран не показывает errorCode | Разбор по кодам, устойчивый парсинг, экран 422 | 07-frontend.md |
| SR-API-12 | 504 UPSTREAM_TIMEOUT только в синхронных режимах | ЧАСТИЧНО | grpc-to-http.ts:21-22 — DEADLINE_EXCEEDED → 504; дедлайнов, синхронных режимов и plan.solver.status='timeout' нет | Дедлайны вызовов и режимы работы | 02-system-trace.md |
| SR-API-13 | 503 UPSTREAM_UNAVAILABLE с details.service, 3 попытки → error | ЧАСТИЧНО | grpc-to-http.ts:26-27 — UNAVAILABLE → 503; details.service не заполняется, ретраев нет | gRPC-клиенты, details.service, политика 3 попыток | 02-system-trace.md |
| SR-API-19 | GET /api/fleet и /api/payloads: три борта, reference_only скрыт, ETag+Cache-Control | ЧАСТИЧНО | fleet/fleet.controller.ts:31-46 отдаёт YAML как есть с ETag; include_reference игнорируется (:33 — _includeReference); docs/task5/10-hardware/fleet.yaml:150,169 содержит reference_only: true у geoscan_401_geodesy и geoscan_701 → в ответе 5 моделей | Фильтрация reference_only, Cache-Control, сборка на этапе образа | 02-system-trace.md |
| SR-API-20 | OpenAPI из кода, /docs и /docs-json, покрывает все маршруты и заголовки | ЧАСТИЧНО | main.ts:32-40 — SwaggerModule.setup('docs', …, {jsonDocumentUrl:'docs-json'}); покрыты только 6 маршрутов; заголовки Idempotency-Key и x-request-id не описаны; packages/api-client/openapi.snapshot.json содержит 4 пути | Описать заголовки и недостающие ручки, барьер число операций = число маршрутов | 02-system-trace.md |
| SR-CFG-01 | Конфигурация только из env одним типизированным слоем, fail-fast | ЧАСТИЧНО | TS: config/config.schema.ts:3-15 (zod) + parseEnvConfig с process.exit(1) (:23-30), ConfigService. Python-слоя нет (grep -rn 'BaseSettings|pydantic_settings' src/backend — ноль); диапазон HTTP_PORT 1…65535 не задан | Settings на pydantic для planner/validator, проверка диапазонов | 02-system-trace.md |
| SR-CFG-02 | .env.example у каждого процесса + тест test_env_example | ЧАСТИЧНО | find . -name '.env.example' -not -path './.venv/*' → только ./gateway/.env.example; теста-сканера нет | .env.example для planner/validator + сканер ключей по исходникам | 02-system-trace.md |
| SR-CFG-05 | Продовые секреты только в creds-store, в git и образах — ничего | ЧАСТИЧНО | .env* в .gitignore, в репозитории только .env.example с dev-префиксом (gateway/.env.example:2); но gateway/Dockerfile:27 вшивает ENV SERVICE_API_KEY=dev-local-key в образ | Убрать ENV из Dockerfile | 02-system-trace.md |
| SR-CFG-10 | Ноль исходящих запросов из контура в рантайме | ЧАСТИЧНО | Python-домены чисты: planner/pyproject.toml:6-11, validator/pyproject.toml:8-13 (shapely/pyproj/numpy); барьер validator/tests/test_independence.py:16 прогнан — uv run pytest … -q → 56 passed in 0.15s. Декларативной части нет: compose с internal: true отсутствует, NetworkPolicy в k3s/geoscan/ нет (также в 02-system-trace.md: НЕ ПРОВЕРЕНО) | Compose-сеть internal: true и NetworkPolicy (SEC-R-13) | 09-security-config.md |
| SR-CFG-11 | Офлайн-активы (PMTiles подложка и рельеф, локальные woff2) с того же origin | ЧАСТИЧНО | git grep -nE 'https?://' -- src/frontend/apps src/frontend/packages — только комментарии, KML-namespace и localhost-дефолты REMOTE_*; самих активов нет: ls src/frontend/apps/shell/public → config.json.example | Вендорить PMTiles и шрифты в дистрибутив | 02-system-trace.md |
| SR-CTX-01 | Наружу публикуется только gateway, домены — ClusterIP | ЧАСТИЧНО | k3s/geoscan/60-ingress.yaml:24-32 — один Ingress, но на обобщённый сервис geoscan; отдельных planner/validator/postgres нет; docker-compose.yml отсутствует | Манифесты по процессам, compose, автотест на секцию ports | 02-system-trace.md |
| SR-CTX-02 | Периметр — ровно gateway/planner/validator/airspace/postgres + статика | ЧАСТИЧНО | k3s/geoscan/kustomization.yaml:5-10 — один Deployment; grep -rn airspace src/backend — ноль совпадений; перечень процессов нигде не объявлен | Объявить состав в одном месте + барьерный тест | 02-system-trace.md |
| SR-CTX-03 | Фронт с того же origin, только /api, CORS не нужен | ЧАСТИЧНО | CORS не включён (в gateway/src/main.ts нет enableCors); но packages/api-client/src/client.ts:32 разрешает абсолютные http(s)://, а packages/api-client/src/config.ts:51-60 берёт базу из VITE_GEOSCAN_API_BASE | Убрать абсолютные URL и рантайм-базу, тест на отсутствие Access-Control-Allow-Origin | 02-system-trace.md |
| SR-CTX-04 | Единственный вход — 11 маршрутов REST /api + OpenAPI на /docs | ЧАСТИЧНО | Зарегистрировано 6: plans.controller.ts:16,38; validate.controller.ts:7; fleet.controller.ts:22,48. Нет scenarios, events, export, replan | 5 недостающих маршрутов + барьер на снимок списка | 02-system-trace.md |
| SR-CTX-06 | Ноль обращений за периметр в рантайме | ЧАСТИЧНО | git grep -nE 'https?://' -- src/frontend/apps src/frontend/packages — только комментарии, KML-namespace и localhost-дефолты REMOTE_* (apps/shell/vite.config.ts:28,33,38); офлайн-прогон невозможен — контур не поднимается | Офлайн-прогон демо-сценария, политика deny egress | 02-system-trace.md |
| SR-CTX-07 | Ветер, ВП и бесполётные зоны — только из сценария | ЧАСТИЧНО | schema/scenario.schema.json содержит weather и airspace; сетевых клиентов к метео/ЕС ОрВД нет (grep -rn 'requests|httpx' src/backend — ноль) | Строки в перечне известных ограничений; сценарии s06/s09 в репозитории отсутствуют | 02-system-trace.md |
| SR-CTX-08 | Ops-контракт /healthz,/readyz,/status,/metrics у каждого процесса | ЧАСТИЧНО | gateway/src/ops/ops.controller.ts:18-56 — все четыре; grep -rn 'healthz' src/backend/planner src/backend/validator — ноль | Ops-HTTP в planner и validator | 02-system-trace.md |
| SR-CTX-09 | Секреты пересекают границу только на этапе развёртывания | ЧАСТИЧНО | .env в .gitignore, есть gateway/.env.example; но gateway/Dockerfile:27 — ENV SERVICE_API_KEY=dev-local-key вшит в рантайм-слой | Убрать ENV из Dockerfile, .env.example для planner/validator | 02-system-trace.md |
| SR-DAT-11 | Геометрия — GeoJSON в jsonb WGS84, без PostGIS, расчёты Shapely/pyproj в памяти | ЧАСТИЧНО | Расчётная часть выполнена: validator/validator/geo/projection.py:1-136 (UTM/AEQD по центроиду), validator/tests/test_projection.py:25-33 — площадь в проекции = геодезической в пределах 0,1 %; хранения нет — БД отсутствует | Хранение геометрии в jsonb | 02-system-trace.md |
| SR-DEP-01 | Отдельный образ на процесс, тег — 8-символьный sha, latest запрещён | ЧАСТИЧНО | Dockerfile есть только у gateway (src/backend/gateway/Dockerfile) и у фронтового packages/api-client/Dockerfile; у planner и validator их нет; :latest не встречается, но и реальных тегов нет | Dockerfile для Python-процессов, sha-теги | 02-system-trace.md |
| SR-DEP-01 (теги образов) | 8-символьный sha-тег, :latest запрещён | ЧАСТИЧНО | grep -rn ':latest' k3s/ → ноль вхождений (exit 1) — требование формально не нарушено; но тега нет вовсе: k3s/geoscan/40-deployment.yaml:25 — 'image: |
Подставить реальный sha-тег после починки сборки | 10-build-ops.md |
| SR-DEP-02 | Multi-stage, не от root, 0.0.0.0, без секретов и .env в образе | ЧАСТИЧНО | Multi-stage ✓ (gateway/Dockerfile:3,7,18), слушает 0.0.0.0 ✓ (main.ts:45); но USER не задан (root) и Dockerfile:27 — ENV SERVICE_API_KEY=dev-local-key | USER node, убрать секрет из образа | 02-system-trace.md |
| SR-DEP-05 | Наружу только gateway; nginx, force-ssl-redirect, whitelist, TLS | ЧАСТИЧНО | k3s/geoscan/60-ingress.yaml:8-11,17-19 — force-ssl-redirect, whitelist 100.64.0.0/10,10.42.0.0/16, класс nginx, TLS geoscan-tls; но backend — обобщённый сервис geoscan, сервисов planner/validator/postgres нет (также в 10-build-ops.md: ГОТОВО) | Сервисы по процессам, Ingress на gateway | 02-system-trace.md |
| SR-DEP-06 | livenessProbe → /healthz, readinessProbe → /readyz, порты 3000/3001/3002 | ЧАСТИЧНО | k3s/geoscan/40-deployment.yaml:32-33 — readinessProbe указывает на /healthz вместо /readyz; порт — плейсхолдер |
Исправить путь readiness, задать порты по процессам | 02-system-trace.md |
| SR-DEP-08 (.env.example) | У каждого сервиса есть .env.example с полным перечнем переменных | ЧАСТИЧНО | find src/backend -name '.env.example' (без .venv) → только src/backend/gateway/.env.example (11 переменных); для planner и validator файлов нет | Завести .env.example для planner и validator | 10-build-ops.md |
| SR-DEP-12 | Обновление и откат сменой sha-тега + ./scripts/deploy.sh с ожиданием всех Deployment | ЧАСТИЧНО | scripts/deploy.sh:26-31 делает apply -k --server-side и ждёт rollout только одного deploy/$NAME; тегов образов нет | Ожидание rollout по всем Deployment namespace, проставить теги | 02-system-trace.md |
| SR-NFR-16 | Одинаковый вход и seed → идентичный план и байты выгрузок | ЧАСТИЧНО | Инфраструктура сравнения готова: shared-py/geoscan_contracts/normalize.py:13-19 (VOLATILE_FIELDS) и тест validator/tests/test_contracts.py:59-67. Самого сравнения двух прогонов нет; seed в PlanOptions отсутствует (shared-py/geoscan_contracts/schema.py:272-278), CLI его не принимает | Прокинуть seed, тест на два прогона с побайтовым сравнением | 02-system-trace.md |
| SR-OBS-01 | Ровно четыре ops-маршрута нужной формы; посторонние — 404 | ЧАСТИЧНО | gateway/src/ops/ops.controller.ts:18,24,31,42 — четыре маршрута, Content-Type метрик верный (:54). Но тело /readyz — {ready:true} (shared/src/ops-http-contract.ts:36-38) вместо {'status':'ok'}/503; у planner и validator ops-маршрутов нет | Привести форму /readyz, добавить ops в Python-процессы | 02-system-trace.md |
| SR-OBS-03 | /status одной формы, собирается одной общей функцией на язык | ЧАСТИЧНО | TS выполнено: shared/src/ops-http-contract.ts:10-25 отдаёт {service,version,commit,bootTime,uptimeSec,timestamp}, используется в ops.controller.ts:34-39; общей функции для Python нет | Аналог в shared-py | 02-system-trace.md |
| SR-OBS-08 | x-request-id рождается на gateway, валидируется по регэкспу, уходит в metadata и лог | ЧАСТИЧНО | request-id.interceptor.ts:20-27 берёт входящий или генерирует UUIDv4 и возвращает в заголовке; регэксп ^[A-Za-z0-9_.:-]{8,128}$ не проверяется, в metadata и журнал ничего не уходит | Валидация формата, проброс в metadata и логи | 02-system-trace.md |
| SR-RPC-01 | 4 .proto в proto/geoscan, пакеты …v1, без рукописных дублей структур | ЧАСТИЧНО | Пакеты корректны: planner.proto:3, validator.proto:3, common.proto:3; файлов три — airspace.proto нет (согласуется с SR-RPC-06, противоречит букве SR-RPC-01); buf lint не выполнялся (which buf — пусто) | Снять противоречие SR-RPC-01 ↔ SR-RPC-06, запустить buf lint в CI | 02-system-trace.md |
| SR-RPC-02 (критерий round-trip) | GeoJSON-полигон с вырезами → Geometry → GeoJSON с точностью 1e-9 | ЧАСТИЧНО | tests/integration/test_geometry_contract_roundtrip.py:73-98 проходит (в составе uv run pytest -q → 150 passed): Polygon с вырезом, MultiPolygon, LineString, abs=1e-9. Но это round-trip pydantic-моделей geoscan_contracts.geometry, а не proto-сообщения Geometry, которого не существует |
Повторить тест через сгенерированный Geometry после наполнения common.proto | 03-contracts.md |
| SR-RPC-03 | Planner.Plan — server-streaming, progress со стадией/долей/целевой функцией, один result | ЧАСТИЧНО | planner.proto:6 — returns (stream PlanEvent); но PlanEvent (:14-17) = {string kind; bytes payload_json} — ни стадии, ни доли, ни значения целевой функции; сервера нет | Типизировать события, реализовать серверную часть | 02-system-trace.md |
| SR-RPC-06 | Пока airspace внутри gateway — он за портом AirspacePort | ЧАСТИЧНО | airspace.proto корректно отсутствует; но grep -rn AirspacePort src/backend — ноль совпадений | Завести порт AirspacePort в gateway | 02-system-trace.md |
| SR-RPC-07 | Единые правила именования, суффиксы единиц, *_UNSPECIFIED у enum | ЧАСТИЧНО | Поля snake_case (planner.proto:11,20-22), но ни одного enum в контракте нет; buf lint не запускался | Добавить enum'ы с нулевым UNSPECIFIED и линт в CI | 02-system-trace.md |
| SR-RPC-09 (A-03) | keepCase/longs/arrays одной общей функцией + pin-тест по всем файлам с регистрацией клиентов и серверов | ЧАСТИЧНО | Функция полна: shared/src/grpc/loader-options.ts:4-13 — keepCase: true, longs: Number, enums: String, defaults: true, oneofs: true, arrays: true. Единственный вызов загрузчика через неё: shared/src/grpc/client-factory.ts:17-20 (grep -rn "protoLoader\\|loadSync" src/backend даёт только эти два файла). Флаги генератора совпадают: gateway/package.json:6. Но pin-тест tests/integration/test_loader_options.py:28-47 читает ровно два файла и сверяет подстроки — обхода дерева исходников нет, функциональной проверки (snake_case/int64/пустой repeated) нет. Регистраций клиентов сегодня ноль: grep -rn "createGrpcServiceClient\\|loadPackageDefinition" gateway/src → пусто |
Переписать барьер на обход всех исходников; добавить функциональный тест на трёх типах полей после появления сервера | 03-contracts.md |
| SR-RPC-10 | Кодогенерация в CI и в сборке образа, генерат не в git | ЧАСТИЧНО | .gitignore:59-64 игнорирует генерат; Makefile:4-8 и gateway/package.json:6 (флаги --keepCase --longs=Number --enums=String --defaults --oneofs --arrays); но uv run python -c 'import grpc_tools' → ModuleNotFoundError, grpcio-tools нигде не объявлен, proto-gen не входит в members (pyproject.toml:4) | Объявить grpcio-tools, включить proto-gen в uv-workspace | 02-system-trace.md |
| SR-RPC-11 | x-request-id и x-service-api-key в metadata каждого вызова, общей функцией | ЧАСТИЧНО | shared/src/grpc/metadata.ts:3-14 — функция есть; grep -rn buildMetadata src/backend/gateway/src — ноль вызывающих | Подключить при появлении gRPC-клиентов | 02-system-trace.md |
| SR-RPC-15 | GRPC_MAX_MESSAGE_BYTES = 64 МиБ явно на приём и передачу в обоих языках | ЧАСТИЧНО | TS: config/config.schema.ts:14 (67_108_864) и shared/src/grpc/client-factory.ts:10-13 — обе границы; Python-стороны нет | Те же лимиты в Python-серверах + тест размера PlanResult | 02-system-trace.md |
| SR-SVC-02 | gateway — единственный публичный, владелец состояния, без геометрии | ЧАСТИЧНО | gateway/package.json:15-34 — нет @turf/*, jsts, proj4, geolib (deny-список соблюдён); владения состоянием нет — БД не подключена | Хранилище сценариев/планов/очереди; барьерный тест на deny-список | 02-system-trace.md |
| SR-SVC-03 | planner без состояния и доступа к БД, по HTTP — только ops | ЧАСТИЧНО | planner/pyproject.toml:5-10 — только geoscan-contracts/shapely/pyproj/numpy, БД нет; ops-HTTP и .env.example отсутствуют | Ops-HTTP, .env.example, тест повторяемости по seed | 02-system-trace.md |
| SR-SVC-05 | Независимость валидатора обеспечена топологией | ЧАСТИЧНО | validator/pyproject.toml:7-14 — без geoscan-planner и ortools; validator/tests/test_independence.py:15-37 запрещает корни planner/ortools и транспорт (150 тестов зелёные). Оркестрации gateway и трассы по x-request-id нет | Оркестрация Planner.Plan + Validator.Validate из gateway с общей трассой | 02-system-trace.md |
| SR-SVC-07 | Ядро planner: geometry/coverage/routing/export, без циклов, барьер на граф импортов | ЧАСТИЧНО | ls src/backend/planner/planner → baseline cli coverage export geometry pipeline.py — модуля routing/ нет; .importlinter:41-55 описывает слои только для validator | Модуль маршрутизации, контракт слоёв planner в import-linter | 02-system-trace.md |
| SR-SVC-08 | Ядро не знает про HTTP/gRPC, gRPC — тонкий адаптер поверх функций CLI | ЧАСТИЧНО | Для validator закрыто AST-тестом validator/tests/test_independence.py:16,29-37; .importlinter:20-38 источниками указывает только validator и geoscan_contracts — planner не покрыт; gRPC-адаптера нет | Барьер для planner, адаптер, тест эквивалентности CLI ↔ RPC | 02-system-trace.md |
| SR-SVC-14 | Новый процесс — только по критерию соразмерности с обоснованием | ЧАСТИЧНО | Лишних процессов нет — но и манифестов по процессам нет (k3s/geoscan/kustomization.yaml:5-10); барьерного теста из SR-SVC-01 не существует | Барьер на перечень процессов | 02-system-trace.md |
| SR-TST-02 | Pin-тест опций загрузчика: AST-скан всех регистраций клиентов и серверов | ЧАСТИЧНО | tests/integration/test_loader_options.py:28-47 — текстовая сверка строк 'keepCase: true', 'longs: Number' и флагов proto:gen; AST-скана регистраций нет, ts-morph в зависимостях отсутствует | Заменить grep на AST-скан по исходникам | 02-system-trace.md |
| SR-TST-07 | Пакетный барьер: wheel валидатора, Requires-Dist без planner, allow-list | ЧАСТИЧНО | Структура выполнена: src/backend/validator/pyproject.toml — отдельный дистрибутив geoscan-validator, зависимости geoscan-contracts/shapely/pyproj/numpy, planner отсутствует; корневой src/backend/pyproject.toml:1-4 намеренно без [project]. Теста нет: grep -rn "Requires-Dist|wheel|build" по тестам — совпадений нет (также в 02-system-trace.md: ГОТОВО) | Тест test_validator_distribution: собрать wheel, разобрать метаданные, сверить с allow-list | 08-tests.md |
| SR-TST-08 | Статический барьер импортов: import-linter forbidden + AST-скан обходных путей | ЧАСТИЧНО | AST-скан работает: validator/tests/test_independence.py:29-54 + самопроверка :57-68. Контракт .importlinter:7-19 написан, но uv run lint-imports → 'Failed to spawn: lint-imports'; зависимости нет ни в одном pyproject.toml | Добавить import-linter в dev-зависимости и в CI | 02-system-trace.md |
| SR-TST-08 (self-check 6 из 6) | Самопроверка барьера на шести обходных путях импорта | ЧАСТИЧНО | /tmp/bypass2 с exec("import planner as _p"), sys.path.insert и builtins"import" ("planner") в validator/metrics.py → uv run pytest /tmp/bypass2/tests/test_independence.py -q → 56 passed. Ловятся только статические импорты (test_independence.py:29) и вызовы по имени import_module/import (:41) |
Добавить в скан exec/eval/compile, присваивания sys.path, вызовы через Subscript; test_scan_catches_injected_bypass:56 должен вызывать саму функцию скана | 05-validator.md |
| SR-TST-10 | Шесть регрессий модели времени, проекции и детерминизма | ЧАСТИЧНО | Есть: площадь в проекции (validator/tests/test_projection.py:25-33), порядок lon/lat (:43-54), контрольные числа GSD/footprint/шага (validator/tests/test_optics.py:36-120). Проверка T ≥ L/V_g реализована как G6-02 (validator/validator/checks/g6_coverage.py:86-112), но отдельного теста нет (grep -n 'G6-02' validator/tests/test_checks.py — пусто). Монотонности makespan и воспроизводимости по seed нет | Тесты 1, 5 и 6 из шести | 02-system-trace.md |
| SR-TST-10 (1) | T_план > L_total / V_g строго, turn_time_fraction > 0, на s01–s10 по обоим критериям | ЧАСТИЧНО | Проверка реализована как G6-02 — validator/validator/checks/g6_coverage.py:86-112, наивная оценка validator/validator/metrics.py:100. Ассертится косвенно: validator/tests/test_report_and_cli.py:65-74 (G6-02 присутствует в отчёте) и test_checks.py:23 (нет FAIL на чистом плане); turn_time_frac > 0 — test_checks.py:42 | Нет негативного теста (план с T == naive → FAIL) и нет прогона на s01–s10 | 08-tests.md |
| SR-TST-10 (3) | Центроид любой выгрузки внутри bbox входной области; тест падает при перестановке lon/lat | ЧАСТИЧНО | validator/tests/test_projection.py:43-54 — центроид после прямого/обратного проецирования полигона сценария возвращается в bbox кольца | Нет проверки центроида по содержимому экспортированных файлов (GeoJSON/KML) и нет мутационного случая с перестановкой координат | 08-tests.md |
| SR-TST-11 (1) | В каждом LineString KML есть altitudeMode, координаты трёхмерные — по s01–s10 и всем бортам | ЧАСТИЧНО | planner/tests/test_kml_export.py:78 (altitudeMode relativeToGround в каждом блоке LineString, высота в координатах) и tests/integration/test_kml_roundtrip.py:24-34 (для каждого LineString altitudeMode + каждый токен координат — тройка) | Оба теста гоняются на одном синтетическом плане (tests/integration/conftest.py:21) только с survey-этапами; нет прогона по сценариям и нет мутации «удалили altitudeMode» | 08-tests.md |
| SR-TST-11 (2) | Round-trip экспорт → импорт: геометрия, набор и порядок этапов, число галсов; по 4 форматам; RFC 7946 / XSD | ЧАСТИЧНО | tests/integration/test_kml_roundtrip.py:40 — координаты с допуском 1e-7°, высоты 0,1 м, число линий совпадает; tests/integration/test_geojson_roundtrip.py:26 — координаты 1e-7° + altitude_frame == AGL | Обратного конвертера нет (сравнение с in-memory планом); набор и порядок этапов не сверяются — conftest.py:99 отбрасывает всё кроме survey; форматы plan и waypoints — NotImplementedError (planner/planner/export/init.py:19); форматной валидации RFC 7946 и XSD нет | 08-tests.md |
| SR-NFR-12 | gateway остаётся отзывчивым во время расчётов | НЕ ПРОВЕРЕНО | Расчётного пути не существует (plans.controller.ts — заглушка), нагрузочный прогон невозможен | Поднять контур и выполнить нагрузочный тест | 02-system-trace.md |
| SR-NFR-15 | Время старта до /readyz: gateway ≤20 с, planner ≤30 с, validator ≤20 с | НЕ ПРОВЕРЕНО | Образов planner/validator нет, контур не поднимается; Node/npm в среде отсутствует — gateway собрать нечем | Собрать образы и замерить старт | 02-system-trace.md |
| SR-API-15 / SR-API-16 | REST-контракт выгрузки: четыре формата, Content-Type, ?uav=, zip, X-Plan-Admitted | ВНЕ ОБЪЁМА | Контрактная часть покрыта строкой SR-RPC-04. Маршрут /api/plans/{id}/export/{fmt} не зарегистрирован: gateway/src/plans/plans.controller.ts:16,38 — только POST /api/plans и GET /api/plans/:id. Оценка REST-поведения — за ревью гейтвея | 03-contracts.md |
|
| SR-DAT-13 (зоны airspace) | Схема airspace с таблицей зон — при выделении процесса | ВНЕ ОБЪЁМА | Триггер не сработал; D-05 — docs/task5/91-system/11-decisions.md:59 | 02-system-trace.md |
|
| SR-SVC-12 | Выделение airspace в отдельный процесс — по триггеру | ВНЕ ОБЪЁМА | Триггер не сработал; решение зафиксировано docs/task5/91-system/11-decisions.md:59 (D-05) и 04-internal-contract.md:94 (SR-RPC-06) | 02-system-trace.md |
|
| SR-DEP-04 (сертификат) | Certificate geoscan-tls от ClusterIssuer ff-ca-issuer | ГОТОВО | k3s/geoscan/10-certificate.yaml:5-15 — name geoscan-tls, secretName geoscan-tls, dnsNames [geoscan.ff], issuerRef {name: ff-ca-issuer, kind: ClusterIssuer}; kubectl kustomize k3s/geoscan/ → EXIT=0 | 10-build-ops.md |
|
| SR-RPC-09 | Опции загрузчика proto — одной общей функцией, без инлайновых объектов | ГОТОВО | shared/src/grpc/loader-options.ts:4-13 — единственная функция; grep -rn 'protoLoader.load' src/backend → только shared/src/grpc/client-factory.ts:17, вызывает createProtoLoaderOptions() | 02-system-trace.md |
|
| SR-RPC-10 (часть «не коммитить сгенерированное») | git не содержит _pb2.py, .desc и вывода генерации proto | ГОТОВО | git ls-files \| grep -E "_pb2\|\.desc" → пусто; .gitignore:52-56 и gateway/.gitignore:3. Оговорка: в git лежат src/frontend/packages/domain/src/*.d.ts (11 файлов), но это довоенный фронтенд, к каталогам proto-генерации отношения не имеющий |
03-contracts.md |
|
| SR-SVC-04 | validator пересчитывает метрики и нарушения заново по геометрии плана | ГОТОВО | uv run python -m validator check --scenario /tmp/s01.json --plan /tmp/p01.json → проверки G1-01…G6-03 с пересчитанными значениями; реализация validator/validator/checks/g1_airspace.py, g6_coverage.py, metrics.py:100; импортов planner нет (test_independence.py) | 02-system-trace.md |
|
| SR-SVC-10 | CLI валидатора существует раньше gRPC и работает без grpcio | ГОТОВО | uv run python -m validator run --scenarios scenarios/ --criteria makespan,total_time печатает сводную таблицу; check — подробный отчёт; validator/pyproject.toml:7-14 — grpcio отсутствует | 02-system-trace.md |
|
| SR-TST-10 (2) | Площадь в метрической проекции ≈ геодезической, ≤ 0,1 % | ГОТОВО | validator/tests/test_projection.py:25-33 — projected_m2 == approx(geodesic_m2, rel=0.001) на полигоне s01; :57-63 то же для длины; :14-22 выбор UTM/AEQD; :36-40 площадь s01 ≈ 1 км² | 08-tests.md |
|
| SR-TST-10 (4) | Контрольные числа проверяются раздельно для геометрии planner и геометрии validator | ГОТОВО | Две независимые реализации и два независимых теста: planner/planner/geometry/optics.py ↔ planner/tests/test_geometry_optics.py и validator/validator/geo/optics.py ↔ validator/tests/test_optics.py; общего кода формул нет — uv run --with import-linter lint-imports: контракт «Валидатор не видит планировщик» KEPT | 08-tests.md |
|
| SR-TST-11 | Две регрессии выгрузки ПЗ: altitudeMode и 3D-координаты, round-trip геометрии | ГОТОВО | tests/integration/test_kml_roundtrip.py:24-25 (altitudeMode = relativeToGround), :33 (тройки lon,lat,alt), :40-53 (round-trip); tests/integration/test_geojson_roundtrip.py:79-96. Оба зелёные в прогоне uv run pytest -q (150 passed) | 02-system-trace.md |
Архитектурные принципы A-01…A-15 (50-stack/05)¶
| ID | Требование | Статус | Доказательство | Что доделать | Отчёт |
|---|---|---|---|---|---|
| A-09 | Идемпотентность мутаций по Idempotency-Key (реестр {scope,key} → полный ответ) | НЕТ | grep -rni "Idempotency" src/backend/gateway/src src/backend/shared/src → 0 совпадений; каталога shared/src/idempotency/ и canonical-json.ts нет (find shared -type f) | GW-10: реестр ключей в БД, begin/complete, in_flight, 409 IDEMPOTENCY_KEY_CONFLICT, заголовок Idempotency-Replayed: true | 06-gateway.md |
| A-10 | Своя схема данных домена, состояние в Postgres | НЕТ | grep -rn '"pg"|DATABASE_URL|node-pg-migrate' gateway shared → пусто; каталога gateway/src/db/ и файлов *.repository.ts нет | GW-03: пул pg, схема gateway, репозитории scenarios/plans/plan_jobs/idempotency_keys, миграции | 06-gateway.md |
| A-01 | Единственная публичная точка входа: REST /api, OpenAPI /docs, поток прогресса, файлы экспорта | ЧАСТИЧНО | gateway — единственный HTTP-процесс (find src/backend -name 'Dockerfile*' → только gateway/Dockerfile; grep healthz|FastAPI по planner/validator → 0); /docs и /docs-json поднимаются в main.ts:32-40; Ingress ведёт на один backend geoscan:80 (k3s/geoscan/60-ingress.yaml:18-23) | Нет потока прогресса и файлов экспорта; статика фронтенда не раздаётся (STATIC_ROOT объявлен в config.service.ts:32-34, не используется); в k3s/geoscan/40-deployment.yaml:25 всё ещё |
06-gateway.md |
| A-02 | Ops-контракт: /healthz, /readyz, /status, /metrics; бизнес-логики по HTTP в доменах нет | ЧАСТИЧНО | Четыре маршрута — ops.controller.ts:18,24,31,42; /status собирается одной функцией buildStatusBody (shared/src/ops-http-contract.ts:10-25); Content-Type метрик 'text/plain; version=0.0.4; charset=utf-8' (ops.controller.ts:54) | /readyz отдаёт {ready:true} вместо {"status":"ok"} (ops-http-contract.ts:36-38, SR-OBS-01) и всегда 200 (ops.controller.ts:27 — «always ready»); readiness.service.ts нет; барьера «в доменах нет HTTP-контроллеров» нет | 06-gateway.md |
| A-04 | Авторизация в два слоя: сервисный ключ и метаданные в gRPC, домены не парсят JWT | ЧАСТИЧНО | buildMetadata() по форме x-request-id/x-service-api-key/x-user-id — shared/src/grpc/metadata.ts:3-14; SERVICE_API_KEY обязателен (config.schema.ts:5) | Ни одного вызова buildMetadata из гейтвея (grep → только shared/src/index.ts:8); gRPC-клиентов нет, ключ никуда не уходит; guard проверки ключа на стороне доменов отсутствует | 06-gateway.md |
| A-05 | Сквозной x-request-id: приём/генерация → metadata каждого gRPC-вызова → каждая строка лога | ЧАСТИЧНО | request-id.interceptor.ts:20-27 принимает или генерирует id и возвращает его в ответе; интерцептор зарегистрирован глобально в main.ts:30 | Нет AsyncLocalStorage, нет передачи в planner (доменных вызовов нет вовсе), нет контекстного логгера — журнал это console.log/console.error (main.ts:47, domain-exception.filter.ts:59); shared/src/logging/logger.ts отсутствует | 06-gateway.md |
| A-06 | Единый контракт ошибок {errorCode,message,details?,errors?}, таблица gRPC→HTTP в одном месте | ЧАСТИЧНО | Единственная таблица — shared/src/grpc/grpc-to-http.ts:4-33 (шесть кодов A-06 + ALREADY_EXISTS→409); реестр из 18 кодов — shared/src/errors/codes.ts:2-21; единственный фильтр — domain-exception.filter.ts:16-63, подключён в main.ts:29 | Маппинг мёртв: RpcServiceError нигде не бросается (доменных вызовов нет); errors[] не заполняется; 404 неизвестного пути отдаётся как errorCode INTERNAL (domain-exception.filter.ts:39-45); в одном файле два представления таблицы (const + switch) | 06-gateway.md |
| A-07 | Долгий расчёт — задача со статусом: 202 → поллинг → SSE → экспорт | ЧАСТИЧНО | POST /api/plans отвечает 202 (plans.controller.ts:17), GET /api/plans/:id — 200 (:39); тела — {planId, status:'queued', message:'…в GW-07'} (:31-35, :47-51) | Тела не по канону (нет Location, scenarioId, queuePosition, createdAt); planId — случайный UUID, нигде не сохраняется; GET отдаёт queued для любого id, 404 PLAN_NOT_FOUND нет; нет синхронного режима time_limit_s ≤ 5, очереди, диспетчера и SSE — расчёт не запускается никогда | 06-gateway.md |
| A-11 | Конфигурация только из env через ConfigService, секреты — из creds-store, полный .env.example | ЧАСТИЧНО | zod-схема + fail-fast: config.schema.ts:3-31 (safeParse, печать проблем, process.exit(1)); process.env читается только в config/ (grep -rn 'process.env' gateway/src → config.schema.ts:20); паритет ключей кода и .env.example проверен скриптом → equal: True (11 ключей) | Dockerfile:27 — ENV SERVICE_API_KEY=dev-local-key: ключ в git и отключённый fail-fast в образе; теста test_env_example (SR-CFG-02) нет | 06-gateway.md |
| A-12 | Наблюдаемость с первого коммита: geoscan_plan_duration_seconds, geoscan_plan_total, geoscan_validator_violations_total, gRPC-метрики | ЧАСТИЧНО | /metrics есть и отдаёт корректный Content-Type — ops.controller.ts:42-56 | В выдаче только geoscan_process_up 1 и константа geoscan_http_requests_total 0 (ops.controller.ts:50); prom-client не подключён (gateway/package.json:11-28); metrics.interceptor.ts не создан; scrape-target и дашборды в k3s не заведены (ls k3s → geoscan, template) | 06-gateway.md |
| A-13 | Инварианты держатся барьерными тестами, а не договорённостями | ЧАСТИЧНО | cd src/backend && uv run pytest tests -q → 12 passed; pin-тест опций загрузчика — tests/integration/test_loader_options.py:28-47; правило «валидатор не видит планировщик» — .importlinter (контракт validator-is-independent) | Pin-тест сверяет строки в package.json и loader-options.ts, а не обходит AST регистраций клиентов (SR-TST-02); нет барьера «в доменах нет HTTP-контроллеров» (A-02), снимка маршрутов Fastify (SR-API-01), deny-списка зависимостей и dependency-cruiser (SR-SVC-02); в gateway нет ни одного TS-теста и тест-раннера | 06-gateway.md |
| A-14 | Новый сервис — из шаблона, по чеклисту (пп. 1–5) | ЧАСТИЧНО | Каркас по шаблону: main.ts, app.module.ts, config/, ops-модуль, gateway/Dockerfile | П.2 (проверка сервисного ключа) и п.4 (регистрация клиента домена + BFF-контроллер) не сделаны; п.5: docker-compose.yml отсутствует (find . -name 'docker-compose*' → пусто), в k3s/geoscan/ шаблонные |
06-gateway.md |
| A-08 | Транзакционный outbox и единая топология шины — только если заводим шину | ВНЕ ОБЪЁМА | grep -rn 'amqp|rabbit' gateway shared → пусто; принцип условный, 50-stack/05-service-architecture.md:150-152 («RabbitMQ, скорее всего, избыточен») | 06-gateway.md |
|
| A-03 | .proto — источник правды, опции загрузчика зафиксированы одной функцией (keepCase, longs, arrays) | ГОТОВО | shared/src/grpc/loader-options.ts:4-13 (keepCase: true, longs: Number, enums: String, defaults: true, oneofs: true, arrays: true); единственный protoLoader.loadSync — shared/src/grpc/client-factory.ts:17-21; те же флаги в gateway/package.json:6; pin-тест зелёный: uv run pytest tests -q → 12 passed (tests/integration/test_loader_options.py); поля proto в snake_case (proto/geoscan/planner.proto:20-22) | 06-gateway.md |
|
| A-15 | Соразмерность: минимум процессов, целевой контур — четыре | ГОТОВО | find src/backend -name 'Dockerfile*' → только gateway/Dockerfile; planner и validator существуют как библиотеки и CLI; лишних процессов не заведено | 06-gateway.md |
Кодовая база и аспекты реализации (92-code и пр.)¶
| ID | Требование | Статус | Доказательство | Что доделать | Отчёт |
|---|---|---|---|---|---|
| 92-code/06 §6.5 | Типы фронтенда генерируются из OpenAPI (api-types.ts), руками не пишутся | НЕТ | ls src/frontend/packages/domain/src/ — api-types.ts отсутствует; grep -rn "api:types\\|openapi-typescript" src/frontend/*/*/package.json → пусто; packages/api-client/src/endpoints/*.ts типизированы как unknown (plans.ts:12, scenarios.ts:3, validate.ts:3, fleet.ts:3) |
Завести скрипт api:types и генерацию из /docs-json после появления реального OpenAPI | 03-contracts.md |
| FE-01 | Каркас качества: ESLint + dependency-cruiser + Vitest | НЕТ | В src/frontend нет eslint.config./.eslintrc/.dependency-cruiser/vitest.config.; src/frontend/package.json:31-34 — devDependencies только concurrently и typescript, скриптов lint/test/depcruise нет | FE-01 целиком | 07-frontend.md |
| FE-02 | Гигиена воркспейсов: убрать .d.ts, дописать зависимости, включить минификацию | НЕТ | git ls-files 'src/frontend/packages//src//.d.ts' по-прежнему даёт 24 файла (packages/domain/src/index.d.ts и соседние); apps/shell/vite.config.ts:61 — minify:false; packages/ui/package.json не объявляет echarts/zustand/@geoscan/* | Закрыть D-01, D-02, D-05 из 92-code/02 | 07-frontend.md |
| FE-04 | SSE-клиент: подписка, snapshot в кеш, watchdog, закрытие на терминальном событии | НЕТ | Файла sse.ts нет; grep -rn "EventSource" src/frontend — пусто; GET /api/plans/{id}/events не вызывается (endpoints/plans.ts содержит только createPlan/getPlan/replan) | Реализовать по §5.2 архитектуры | 07-frontend.md |
| FE-05 | Слой серверного состояния на TanStack Query | НЕТ | @tanstack/react-query отсутствует в src/frontend/package-lock.json и во всех package.json воркспейсов; кеша и staleTime нет нигде | Ввести Query-слой либо зафиксировать отказ решением | 07-frontend.md |
| FE-07 | packages/domain ужимается до предпросмотра | НЕТ | packages/domain/src/index.ts:6,8,9 реэкспортируют planner, validation, exporters; apps/validator/src/screens/ParetoScreen.tsx:29-30 вызывает solve дважды | Вынести решатель/валидатор/экспорт за флаг dataSource | 07-frontend.md |
| FE-11 | Общий фрагмент shared Module Federation + pin-тест | НЕТ | Блок shared скопирован дословно: apps/shell/vite.config.ts:44-49 и аналогичные блоки в planner/validator/viewer3d; tools/mf-shared.ts не создан | Вынести в общий модуль и закрыть барьерным тестом | 07-frontend.md |
| FE-12 | Контракт RemoteScreen, реестр экранов, проп navigate, deep-link /plan/:planId | НЕТ | packages/state/src/screen.ts не создан (ls packages/state/src → api-compute, create-mission-store, datasource, hooks, index, runtime-config); apps/shell/src/App.tsx:30-40 — восемь Route, перечисленных руками, без параметров | Реестр экранов, единый тип пропов экрана, planId в URL | 07-frontend.md |
| FE-13 | verify с утверждениями, общая фикстура контрольных чисел | НЕТ | tools/verify-numbers.ts:28-34 — только console.log('… (ожидается 1,96)'), ни одного assert; git log --oneline -- src/frontend/tools/verify-numbers.ts → единственный коммит 48df06b | Переделать в падающий тест | 07-frontend.md |
| FE-14 | Импорт геофайлов (KML/KMZ/GeoJSON/SHP) | НЕТ | Ручки импорта в api-client нет; apps/shell/src/screens/StartScreen.tsx по-прежнему с beforeUpload={() => false} | Реализовать после решения ОВ-6 | 07-frontend.md |
| FE-R-06 | Доступ к стору только через useMission/useActions | НЕТ | Хуки есть (packages/state/src/hooks.ts:4,44), но grep -rn "useMission|useActions" apps — ни одного вхождения; все экраны и apps/shell/src/TopBar.tsx:4 импортируют useStore из zustand напрямую; вдобавок дубль в packages/ui/src/store-hooks.ts:10,14 | Перевести экраны, удалить копию в ui, включить правило линтера | 07-frontend.md |
| FE-R-14 | DTO только генерацией из OpenAPI; снимок сверяется с /docs-json | НЕТ | packages/api-client/openapi.snapshot.json:6 — «Минимальный снимок до появления живого /docs-json гейтвея», написан руками, 4 операции из 11 маршрутов раздела 1 (нет /api/scenarios, /api/plans/{id}, /events, /export/{fmt}, /replan); барьера сверки нет | Генерация типов + барьерный тест снимка | 07-frontend.md |
| FE-R-15 | Мутации расчёта уходят только с Idempotency-Key | НЕТ | packages/api-client/src/endpoints/plans.ts:12-16 — заголовок добавляется только если ключ передан; вызов без ключа проходит молча; барьерного теста нет (тестов нет вообще) | Сделать ключ обязательным параметром и закрыть тестом | 07-frontend.md |
| INT-1 | CLI планировщика читает сценарии репозитория | НЕТ | uv run python -m planner.cli run --scenario scenarios/s02-nfz.json --out /tmp/p02.json → EXIT=1, pydantic: solver.timeLimitS Field required, objective.energy_reserve Extra inputs are not permitted, landing_sites/expected Extra inputs are not permitted (CLI использует geoscan_contracts.schema.Scenario, сценарии — под geoscan_contracts.scenario.Scenario) | Свести планировщик и документы к одному контракту сценария | 04-planner-core.md |
| INT-2 | План планировщика читается валидатором (метод «валидатор раньше продукта») | НЕТ | load_plan(Path('/tmp/plan_s01.json')) → InvalidDocument: поле «scenario_id» — Field required; после ручного перевода в snake_case и починки lambda_ — 70 ошибок (phases.0.path Extra inputs are not permitted, phases.0.photos Input should be a valid integer) | Единый контракт плана для planner и validator (SR-RPC-02) | 04-planner-core.md |
| INT-3 | gRPC-сервис Planner (proto/geoscan/planner.proto:5-7, 92-code/05 §2 rpc/) | НЕТ | grep -rn "grpc|serve" src/backend/planner/ → ни одного совпадения; каталога planner/rpc/ нет | Сервер Plan (server-streaming) + ExportPlan + ops-HTTP :3001 | 04-planner-core.md |
| INT-4 | Тесты на сквозной расчёт pipeline.plan() | НЕТ | grep -rn "pipeline" planner/tests tests → пусто; есть только test_geometry_optics.py (5) и test_kml_export.py (1); uv run pytest planner/tests tests/integration -q → 18 passed in 0.04s | Golden-тесты на s01…s10 (70-plan/02) и барьерные тесты на NFZ, бюджет, ветер | 04-planner-core.md |
| LINT | Контракты import-linter для слоёв planner (92-code/05 §2.1) | НЕТ | .importlinter содержит только контракты validator/geoscan_contracts, planner как source_modules отсутствует; uv run lint-imports → error: Failed to spawn: lint-imports | Добавить контракты planner-layers и export-isolation, поставить import-linter в dev-зависимости | 04-planner-core.md |
| Makefile: lint / test / format-check | Цели проверок качества в сборочном файле | НЕТ | src/backend/Makefile прочитан целиком (13 строк): ровно три цели — proto-py, proto-ts, proto. Целей lint, test, format-check, typecheck, build нет | Добавить цели-обёртки над ruff, mypy, pytest, lint-imports | 10-build-ops.md |
| Makefile: proto-py | make proto-py воспроизводимо генерирует Python-стабы | НЕТ | make -n proto-py → 'make: command not found' (make в среде нет); ручной прогон той же команды: uv run python -m grpc_tools.protoc -I proto … → 'ModuleNotFoundError: No module named grpc_tools', EXIT=1; grep -rn 'grpcio-tools' --include=pyproject.toml src/backend → пусто; grep -n 'grpcio' src/backend/uv.lock → пусто; command -v python → exit 1 (есть только python3), а Makefile:5 зовёт python | Объявить grpcio-tools dev-зависимостью workspace'а, включить proto-gen в members, вызывать python3 | 10-build-ops.md |
| SEC-R-03 | Заголовки безопасности одним модулем (@fastify/helmet), CSP | НЕТ | grep -rn 'helmet' src/backend --include='*.ts' → NONE; в src/backend/gateway/package.json:11-28 зависимости нет; CSP-меты нет в src/frontend/apps/shell/index.html | Подключить helmet, задать CSP (SEC-R-56…60) | 09-security-config.md |
| SEC-R-13 | internal: true в compose и NetworkPolicy в k3s | НЕТ | docker-compose.yml отсутствует; ls k3s/geoscan/ → 00-namespace, 10-certificate, 40-deployment, 50-service, 60-ingress — NetworkPolicy нет | Добавить обе декларации | 09-security-config.md |
| SEC-R-15 | Непривилегированный пользователь, readOnlyRootFilesystem, drop ALL | НЕТ | src/backend/gateway/Dockerfile — директивы USER нет (рантайм node:22-bookworm-slim под root); k3s/geoscan/40-deployment.yaml:23-42 — securityContext отсутствует | Добавить USER в образ и securityContext в манифест | 09-security-config.md |
| SEC-R-19 | Лимит частоты @fastify/rate-limit на всех /api/* | НЕТ | grep -rn 'rate-limit' src/backend --include='*.ts' → NONE | Подключить вместе с контрактом ошибок | 09-security-config.md |
| SEC-R-24 | Один глобальный AuthGuard (APP_GUARD) как точка врезки | НЕТ | grep -rn 'APP_GUARD|AuthGuard' src/backend --include='*.ts' → NONE; src/backend/gateway/src/app.module.ts guard не регистрирует | Завести заглушку guard с комментарием D-01 | 09-security-config.md |
| SEC-R-43 | Редакция секретов в журнале по путям (pino redact / structlog) | НЕТ | Логгер не настроен: src/backend/gateway/src/main.ts:24 bufferLogs, далее console.log/console.error (main.ts:47,52; config.schema.ts:28); LOG_LEVEL нигде не потребляется — grep 'logLevel' даёт только config.service.ts:8-10 | Подключить pino с redact, пробросить LOG_LEVEL | 09-security-config.md |
| SEC-R-48 | Новый секрет сразу в creds-store + .env.example + sync-secrets.sh + таблица §3 | НЕТ | list_keys("project/geoscan") → только CLAUDE_CODE_OAUTH_TOKEN_NUBBLE; SERVICE_API_KEY и POSTGRES_PASSWORD отсутствуют; scripts/sync-secrets.sh не существует | Завести оба ключа с комментариями и написать скрипт | 09-security-config.md |
| SMEAR | Ограничения V_g ≤ Bx/Δt_min и смаз V·t_exp ≤ GSD | НЕТ | grep -rn "smear|min_trigger|t_exp" src/backend/planner/ → пусто | В 30-domain/01:95-114 помечено «опционально» — отложить осознанно, записав в известные ограничения | 04-planner-core.md |
| TR-11 | Запрет импорта planner распространён на validator/tests/** | НЕТ | Копия дерева /tmp/blint с import planner в validator/tests/fixtures.py → lint-imports --config .importlinter = Contracts: 4 kept, 0 broken; uv run pytest /tmp/bypass/tests/test_independence.py -q → 56 passed. Причина: validator/tests/test_independence.py:13 сканирует parents[1]/"validator" (только исходники) |
Включить validator.tests в root_packages/контракт либо расширить PACKAGE_ROOT AST-скана | 05-validator.md |
| planner → validator | Сквозной путь: выход планировщика читается валидатором (метод «валидатор раньше продукта») | НЕТ | Ни одного такого теста: validator/tests/fixtures.py:1-6 прямо пишет «Планы для тестов собираются здесь, а не берутся у планировщика». Путь сломан: uv run python -m planner.cli run --scenario scenarios/s01-simple.json → 19 validation errors for Scenario; дамп плана валидатора не читается моделью планировщика → 262 validation errors for Plan | Свести представления сценария и плана, завести сквозной тест pipeline → validate | 08-tests.md |
| §6.1 | Адреса remote'ов по умолчанию относительные (/remotes/ |
НЕТ | apps/shell/vite.config.ts:28,33,38 — дефолты http://localhost:5174/5175/5176; ключа remotes в AppRuntimeConfig нет (packages/state/src/runtime-config.ts:3-6) | Сменить дефолт на относительный, добавить переопределение из config.json | 07-frontend.md |
| §6.2-dev | Dev-прокси /api → http://localhost:3000 в конфиге shell | НЕТ | apps/shell/vite.config.ts:62 — server: { port: 5173, strictPort: true, fs: {...} }, секции proxy нет | Добавить прокси, чтобы dev и прод отличались только config.json | 07-frontend.md |
| В1 | Экраны и потоки переключены на api-client | НЕТ | grep -rn "@geoscan/api-client" src/frontend → только packages/state/src/{api-compute,create-mission-store}.ts и конфиги apps/shell. Ни одного импорта в apps/planner/, apps/validator/, apps/viewer3d/, apps/shell/src/screens/. Экраны на моках: apps/shell/src/screens/StartScreen.tsx:13 (SCENARIO_CARDS), apps/validator/src/screens/ParetoScreen.tsx:6 (solve), apps/planner/src/screens/ExportScreen.tsx:16, apps/planner/src/screens/PlanScreen.tsx:6 (UAV_MODELS) | Перевести стартовый экран на GET /api/scenarios, Парето/бейзлайн — на ответ плана, экспорт — на GET /api/plans/{id}/export/{fmt}, справочники — на GET /api/fleet | 07-frontend.md |
| В2 | Типы api-client согласованы с контрактом гейтвея и src/backend/schema/*.json | НЕТ | Типов нет: packages/api-client/src/endpoints/fleet.ts:3,7, plans.ts:11,19,28, scenarios.ts:3,7,11, validate.ts:3 — везде Promise |
Сгенерировать schema.ts из /docs-json, написать мапперы snake↔camel, закрыть барьером FE-R-14 | 07-frontend.md |
| Воспроизводимость npm-сборки | Лок-файлы для gateway и shared, npm ci вместо npm install | НЕТ | find src/backend -name 'package-lock.json' -o -name 'npm-shrinkwrap.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' → пусто; src/backend/gateway/Dockerfile:6,14,16 используют npm install | Закоммитить package-lock.json для gateway и shared, перейти на npm ci | 10-build-ops.md |
| Покрытие ядра planner | Оптимизатор, нарезка галсов, проекция, бейзлайн покрыты тестами | НЕТ | uv run --with pytest-cov pytest --cov=planner: pipeline.py 215 стейтментов 0%, coverage/transects.py 68 — 0%, geometry/projection.py 79 — 0%, geometry/shapes.py 29 — 0%, baseline/equal_split.py 63 — 0%, coverage/swath.py 17 — 0%. На planner приходится 6 тестов из 150 | Тесты на нарезку галсов, проекцию планировщика, распределение по бортам и наивный бейзлайн | 08-tests.md |
| Сквозной прогон planner→validator | Валидатор проверяет план, произведённый планировщиком | НЕТ | uv run python -m planner.cli run --scenario scenarios/s01-simple.json --objective makespan --out … → 19 validation errors for Scenario (tasks.0.gsdCm Field required, quality/spare_batteries/available_from extra_forbidden); дамп geoscan_contracts.schema.Plan не читается load_plan: «поле «scenario_id» — Field required» (by_alias) и «solver.lambda_ — Extra inputs are not permitted» (snake). Схемы: shared-py/geoscan_contracts/schema.py:176 (Phase.path) против shared-py/geoscan_contracts/plan.py:83 (Phase.geometry) |
Свести планировщик и валидатор на одну схему плана либо написать конвертер с round-trip-тестом | 05-validator.md |
| Схема плана ↔ geoscan_contracts.plan | Канонический вид документа плана | НЕТ | Прогон: Plan(...).model_dump_json(by_alias=True) против schema/plan.schema.json → '/solver -> Additional properties are not allowed (lambda, stop_reason)' и '/metrics/violations/nfz -> 12.5 is not of type integer'. Источник: plan.py:33 (алиас lambda против lambda_frac в plan.schema.json:72), plan.py:39 (stop_reason), plan.py:42-51 (ViolationCounts — float) против plan.schema.json:92-99 (integer) | Согласовать тип violations (величины number против счётчиков integer), внести stop_reason, выровнять lambda_frac | 03-contracts.md |
| Схема сценария ↔ 40-formats/03 и фикстуры | Единый документ входа для схемы, фикстур s01–s03 и pydantic-модели | НЕТ | uv run --with jsonschema …: s01-simple.json errors: 5, s02-nfz.json: 5, s03-multi-sites.json: 7. Конкретно: '/' → 'expected' was unexpected; /launch_sites/0 → 'turnaround_time_s' is a required property + 'turnaround_time_min' was unexpected; /objective → 'energy_reserve_frac' is a required property + 'energy_reserve' was unexpected. Источники: schema/scenario.schema.json:96,211 против scenarios/s01-simple.json:16 и scenario.py:32,139 (те дословно следуют 40-formats/03:28,80) | Выбрать одно имя (Q-CT-3 рекомендует _s/_frac), привести схему, фикстуры, scenario.py и 40-formats/03 одним PR; решить судьбу поля expected | 03-contracts.md |
| Тесты gateway (TS) | Юнит- и барьерные тесты NestJS-шлюза | НЕТ | src/backend/gateway/package.json: scripts = proto:gen, build, start, start:dev — цели test нет; devDependencies без тест-раннера; find -name ".spec.ts" -o -name ".test.ts" по src/ пуст | Добавить тест-раннер и тесты; прогнать в среде с node | 08-tests.md |
| Тесты фронтенда | Тесты packages/api-client, packages/state, packages/domain и приложений | НЕТ | src/frontend/package.json — скрипта test нет, devDependencies = concurrently + typescript; в packages/*/package.json тест-скриптов нет. tools/verify-numbers.ts не тест: grep -n "assert|process.exit|throw" по файлу пуст, он печатает «ожидается 1,96» в console.log и всегда выходит с 0 | Ввести vitest, превратить verify-numbers.ts в тест с ассертами | 08-tests.md |
| FE-03 | packages/api-client: типы из OpenAPI, fetch-обёртка, x-request-id, Idempotency-Key, разбор ошибок | ЧАСТИЧНО | Есть: client.ts:49-99 (обёртка), :55-63 (x-request-id), :11-21 (ApiError), endpoints/plans.ts:12-16 (Idempotency-Key). Нет: типов (unknown), sse.ts/mappers.ts/generated/schema.ts, разбора кодов GSD_UNREACHABLE/QUEUE_FULL/UPSTREAM_UNAVAILABLE (grep по пакету пусто), проверки размера тела, тестов против мок-сервера | Типы, SSE, таблица ошибок, msw-тесты | 07-frontend.md |
| FE-06 | Рассечение стора: packages/state, draft-слайс, compute() → мутация + подписка | ЧАСТИЧНО | Пакет создан, compute подменяется (packages/state/src/create-mission-store.ts:23-41), но слайсов нет: packages/domain/src/store.ts:43-115 — тот же плоский стор, dirty выставляется руками (store.ts:320 и др.), store.ts:17-18 импортирует solve/validate, computing:boolean не заменён статусом задачи | Разделить draft/серверное, ввести status/error/progress | 07-frontend.md |
| FE-08 | Фабрика источника данных mock|api и перевод экранов на серверные данные | ЧАСТИЧНО | Флаг читается ровно в одном месте: packages/state/src/datasource.ts:4, используется в create-mission-store.ts:17. Но экраны не переведены (см. В1), а api-compute.ts:52,54 ставит baseline:null и pareto:[] | Перевод экранов; определить источник бейзлайна и Парето (ОВ-3) | 07-frontend.md |
| FE-10 | Runtime config.json: один бандл для локального контура и geoscan.ff | ЧАСТИЧНО | Загрузка до createRoot реализована: apps/shell/src/main.tsx:10-23, apps/shell/src/runtime-config.ts:11 (fetch('/config.json', {cache:'no-store'}) с фоллбэком). Нет ключей remotes и buildCommit (packages/state/src/runtime-config.ts:3-6); в поставке только apps/shell/public/config.json.example; фронт не контейнеризован (find . -name Dockerfile* → только gateway и api-client), в k3s/geoscan/ статики и ConfigMap нет | Добавить remotes/buildCommit, поставку config.json, образ и манифест | 07-frontend.md |
| FE-R-05 | fetch/EventSource только внутри api-client | ЧАСТИЧНО | Единственный fetch вне пакета — apps/shell/src/runtime-config.ts:11 (загрузка config.json), что предписано §6.2; но правило ничем не удерживается — линтера нет (FE-01) | Включить no-restricted-globals после FE-01 | 07-frontend.md |
| LAYOUT | Раскладка пакета planner по 92-code/05 §2 | ЧАСТИЧНО | Есть geometry/{optics,wind,projection,shapes}, coverage/{transects,swath}, export/{kml,geojson,common}, baseline/equal_split, pipeline, cli. Нет geometry/kinematics.py, coverage/{airspace_clip,decompose,angle,turns}.py, всего routing/, export/{qgc_plan,waypoints}.py, rpc/, tests/{unit,golden,barriers}, .env.example; логика угла и разворотов вшита в pipeline.py (517 строк) | Разнести по спецификации либо обновить спецификацию | 04-planner-core.md |
| PY-01 | ruff, mypy --strict, lint-imports зелёные из чистого клона | ЧАСТИЧНО | uv run ruff check . → error: Failed to spawn: ruff; в src/backend/.venv/bin нет ruff/mypy/lint-imports; dev-групп с ними нет ни в src/backend/pyproject.toml, ни в validator/pyproject.toml:18. Конфиги [tool.ruff]/[tool.mypy] есть (src/backend/pyproject.toml:14,32) |
Завести dev-группу с ruff, mypy, import-linter, hypothesis | 05-validator.md |
| SEC-R-41 | Секрет читается только слоем конфигурации, приватным полем / SecretStr | ЧАСТИЧНО | TS: src/backend/gateway/src/config/config.service.ts:6,12-14 — приватное поле env и геттер serviceApiKey, прямых process.env.SERVICE_API_KEY нет. Python-слоя (Settings/SecretStr) не существует; барьерного теста нет | Слой конфигурации в Python + барьерный тест | 09-security-config.md |
| SEC-R-42 | Секрет не сериализуется, toJSON()/repr печатает *** | ЧАСТИЧНО | В /status секрета нет — src/backend/gateway/src/ops/ops.controller.ts:31-40 отдаёт имя/версию/commit/boot. Но у ConfigService нет toJSON(): JSON.stringify(config) распечатает поле env целиком (config.service.ts:6); теста нет | Добавить toJSON() с маскированием и тест | 09-security-config.md |
| SEC-R-45 | .env.example полон, секреты с префиксом dev-, реальных значений нет | ЧАСТИЧНО | Префикс соблюдён (src/backend/gateway/.env.example:2), реальных значений нет. Полноты нет (см. SR-CFG-02); scripts/check-secrets.sh отсутствует | Дополнить .env.example по реестру, написать check-secrets.sh | 09-security-config.md |
| SEC-R-46 | Dockerfile: секретов через ARG/ENV нет, .dockerignore, порядок слоёв | ЧАСТИЧНО | Multi-stage и порядок для gateway соблюдены (src/backend/gateway/Dockerfile:12-16). Нарушено: Dockerfile:27 ENV SERVICE_API_KEY=dev-local-key в runtime-слое; .dockerignore в репозитории нет (find . -name '.dockerignore' — пусто); Dockerfile:5 COPY src/backend/shared/ . каталогом целиком; src/frontend/packages/api-client/Dockerfile:8-10 — npm install после копирования исходников | Снять ENV, добавить .dockerignore, сузить COPY | 09-security-config.md |
| SEC-R-47 | Секретов в git нет; сканер по дереву и истории в CI; check-secrets.sh | ЧАСТИЧНО | Факт подтверждён прогонами grep по дереву и по git log -p f420004~1..422e43a (0 находок кроме dev-local-key). Автоматики нет: .gitea/, .github/, .gitlab-ci.yml отсутствуют; scripts/check-secrets.sh нет | Завести gitleaks-job и scripts/check-secrets.sh | 09-security-config.md |
| SWATH-LIDAR | swath_spacing отдельной формулой для LiDAR (2·H·tan(FOV/2)) и геофизики | ЧАСТИЧНО | Геофизика обработана (optics.py:94-95, line_spacing_m), LiDAR падает в общую кадровую формулу (optics.py:96-97) вопреки 30-domain/02:70; практического вреда сейчас нет — LiDAR-нагрузок в каталоге нет (payloads.yaml:227-229) | Ветка SurveyType.LIDAR + поле fov_deg в каталоге нагрузок | 04-planner-core.md |
| В3 | packages/state: что хранит и подключён ли к приложению | ЧАСТИЧНО | Подключён: apps/shell/src/App.tsx:3,21 — createMissionStore(runtimeConfig) в useMemo, стор пропом в TopBar и RemoteView. Содержит фабрику с подменой compute/replanWithout/restoreFleet (create-mission-store.ts), runtime-config.ts, datasource.ts, hooks.ts, api-compute.ts:12 («Заглушка расчёта через гейтвей»). Собственного состояния не хранит — стор по-прежнему packages/domain/src/store.ts; tsconfig.json у воркспейса отсутствует (ls packages/state) | Перенести стор и слайсы в пакет по §3.2, добавить tsconfig | 07-frontend.md |
| Детерминизм сравнения планов | normalize_plan с явным списком изменчивых полей | ЧАСТИЧНО | shared-py/geoscan_contracts/normalize.py:13-19 (generated_at, plan_id, request_id, solver.solve_time_s, generator), :47-54 — канонические байты, sort_keys, round(x, 6). Критик: normalize.py:13-19 перечисляет поля в snake_case, планировщик пишет camelCase (by_alias=True) — normalize_plan на реальных планах не вычищает generatedAt, прогон дал equal: False | Синхронизировать список изменчивых полей с фактической сериализацией планировщика | 03-contracts.md |
| Инструменты качества (ruff/mypy/import-linter) | Конфиги линтеров и архитектурных контрактов исполнимы | ЧАСТИЧНО | Конфиги есть: src/backend/pyproject.toml:11-40 ([tool.ruff] 14 наборов правил, [tool.mypy] strict=true), src/backend/.importlinter (4 контракта). Но uv run ruff --version / mypy --version / lint-imports --help → 'error: Failed to spawn: … No such file or directory'; grep по всем pyproject.toml не находит ruff/mypy/import-linter ни в dependencies, ни в dependency-groups; корневой pyproject.toml вовсе не имеет [project]/[dependency-groups] | Добавить dev-группу с ruff, mypy, import-linter; перегенерировать uv.lock; завести make-цели | 10-build-ops.md |
| Сверка declared vs recomputed | План, который врёт о себе, не проходит валидацию | ЧАСТИЧНО | declared_metrics прокинуты в отчёт (validator/validator/validate.py:75), но сверяются только coverage_frac (checks/g6_coverage.py:52) и turn_time_frac (:132); makespan_s, total_flight_time_s, transects_total, photos_total, uavs_used, sorties_total не сверяются | Добавить проверку расхождения объявленных и пересчитанных метрик по всем полям | 05-validator.md |
| Чистота пересчёта покрытия | Метрики считаются только по геометрии плана, без данных решателя | ЧАСТИЧНО | validator/validator/metrics.py:44-57 — при недоступности своей оптики ширина полосы берётся из плана (footprint_across_m/swath_spacing_m); путь помечается в Coverage.fallbacks и выводится в objects G6-01, но статус остаётся pass (checks/g6_coverage.py:74) | Понизить статус до warn при непустом fallbacks либо считать покрытие неизвестным | 05-validator.md |
| BUILD | Сборка и запуск гейтвея (npm run build, docker build, живые ответы ручек) | НЕ ПРОВЕРЕНО | which node npm → пусто; src/backend/gateway/node_modules не существует; по условиям ревью установка Node/npm запрещена | Прогнать npm ci && npm run build и smoke по четырём ops-маршрутам и /docs-json на машине с Node | 06-gateway.md |
| CN | Контрольные числа 30-domain (GSD 1,96 см; 117×78 м; By 47,0 м; H 153 м) | НЕ ПРОВЕРЕНО | Прогон невозможен: Node/npm в среде нет (which node npm npx → пусто, node -v → command not found), npm run verify не запускается. Косвенно: git log --oneline -- src/frontend/tools/verify-numbers.ts src/frontend/packages/domain/src/coverage.ts → единственный коммит 48df06b, в составе f15e917 этих файлов нет | Прогнать после появления Node; данное изменение чисел не трогает | 07-frontend.md |
| E2E | Поведение dataSource: "api" на живом контуре | НЕ ПРОВЕРЕНО | Ни фронт, ни гейтвей поднять нельзя (нет Node); гейтвей в текущем виде — заглушки (src/backend/gateway/src/plans/plans.controller.ts:31-35,47-51) | Прогон e2e после GW-07/GW-12 | 07-frontend.md |
| SEC-R-49…55 | Безопасность выгрузки (Content-Disposition, MIME, zip) | НЕ ПРОВЕРЕНО | Ручек выгрузки в gateway нет; экспортеры лежат в src/backend/planner/planner/export/ и src/frontend/packages/domain/src/exporters.ts — это зона профильного ревью экспорта, не конфигурации | Проверить в ревью экспорта/фронтенда | 09-security-config.md |
| SEC-R-56…60 | CSP фронтенда | НЕ ПРОВЕРЕНО | Статически: CSP нигде не задаётся (helmet нет, в src/frontend/apps/*/index.html нет). Эмпирический прогон невозможен — в среде нет Node.js/npm | Задать CSP и проверить прогоном демо в браузере | 09-security-config.md |
| TC | npm run typecheck на новых пакетах | НЕ ПРОВЕРЕНО | Node/npm отсутствуют. Статически ошибок не видно: реэкспортируемые из @geoscan/domain типы существуют (store.ts:29,31,115; types.ts:271) | Прогнать npm run typecheck | 07-frontend.md |
| buf lint | Линт proto-контрактов | НЕ ПРОВЕРЕНО | command -v buf → не установлен; цели в Makefile нет, только упоминание в src/backend/README.md ('buf lint — при установленном buf') | Добавить цель proto-lint и buf в тулчейн CI | 10-build-ops.md |
| buf lint (критерий SR-RPC-01, SR-RPC-07) | buf lint и protoc проходят по всему каталогу proto | НЕ ПРОВЕРЕНО | buf/protoc не установлены — прогон невозможен. Статически при lint.use: [DEFAULT] (proto/buf.yaml:4-8, исключены только PACKAGE_SAME_GO_PACKAGE и FILE_LOWER_SNAKE_CASE) видны кандидаты на падение: PACKAGE_DIRECTORY_MATCH (geoscan/common.proto при пакете geoscan.common.v1; о проблеме предупреждает src/backend/README.md:29), SERVICE_SUFFIX (сервисы Planner/Validator без суффикса Service — planner.proto:5, validator.proto:5), RPC_RESPONSE_STANDARD_NAME (rpc Plan возвращает PlanEvent — planner.proto:6) | Прогнать buf lint и либо добавить исключения в buf.yaml, либо переразложить каталог/переименовать сервисы | 03-contracts.md |
| make proto-ts | Кодогенерация TS-типов из proto | НЕ ПРОВЕРЕНО | Цель Makefile:9-10 — 'cd gateway && npm run proto:gen'; command -v npm → пусто, установка npm запрещена заданием. Статически: gateway/package.json script proto:gen → 'proto-loader-gen-types … ../proto/geoscan/*.proto', путь из контейнера (/build/gateway/../proto) совпадает с Dockerfile:10 | Проверить на машине с npm или в CI | 10-build-ops.md |
| proto-gen ↔ proto | Сгенерированный код не разъехался с исходными .proto | НЕ ПРОВЕРЕНО | Перегенерация невозможна: нет buf/protoc/grpc_tools. Статически разъезжаться нечему — find proto-gen -type f → README.md, pyproject.toml, src/geoscan/init.py (одна строка docstring); генерация не выполнялась ни разу |
03-contracts.md |
|
| Реализация TS (gateway, shared) | Поведение шлюза и общих gRPC-хелперов | НЕ ПРОВЕРЕНО | В среде нет node/npm (ограничение задания); gateway/package.json без цели test — запускать нечего. Прочитано статически: shared/src/grpc/loader-options.ts (13 строк), metadata.ts (14), grpc-to-http.ts (46), client-factory.ts (45) | Прогнать в среде с node после появления тестов | 08-tests.md |
| Типобезопасность фронтенда | npm run typecheck и npm run verify | НЕ ПРОВЕРЕНО | Нет node/npm. Скрипты объявлены в src/frontend/package.json (typecheck, verify), но verify запускает tools/verify-numbers.ts — печать чисел без ассертов | Прогнать typecheck в среде с node; verify переделать в тест | 08-tests.md |
| G2-04 | Проверка запаса над рельефом | ВНЕ ОБЪЁМА | validator/validator/checks/g2_altitude.py:20 MISSING_CHECKS; в выводе validator check печатается «не реализовано: G2-04 — запас над рельефом: во входной схеме нет модели рельефа» |
05-validator.md |
|
| SEC-R-30…40 | Защита разбора импорта (XXE, zip-бомбы, офлайн-CRS, отравленные числа) | ВНЕ ОБЪЁМА | Импорта файлов в реализации нет: src/backend/gateway/src/plans/plans.controller.ts и validate.controller.ts — заглушки, модулей import/ нет; xml.etree встречается только в тесте tests/integration/test_kml_roundtrip.py:4 | Проверять после появления модуля импорта | 09-security-config.md |
| .importlinter (содержание) | Контракты: независимость валидатора, ядро без транспорта, контракты без геометрии, слои валидатора | ГОТОВО | src/backend/.importlinter:6-60; прогон uv run --with import-linter --python 3.12 lint-imports --config .importlinter → «Analyzed 74 files, 279 dependencies. Contracts: 4 kept, 0 broken» |
05-validator.md |
|
| A-DEP | Границы зависимостей: api-client без React/state/ui, state без ui/tokens/apps | ГОТОВО | grep -rn "react|antd|@geoscan/ui|@geoscan/tokens" packages/api-client/src packages/state/src → только import { useStore } from 'zustand' в packages/state/src/hooks.ts:1, что разрешено таблицей §1.2 архитектуры | 07-frontend.md |
|
| A-IVP | Признак «высота выше порога 150 м» (ФП-138) | ГОТОВО | pipeline.py:469 above_ivp_threshold = altitude_m > 150.0; прогон s01 при 153,19 м → above150 True, совпадает с expected.warning сценария | 04-planner-core.md |
|
| CAT-1 | ТТХ парка и нагрузок как данные (fleet.yaml / payloads.yaml) | ГОТОВО | catalog.py:273-293 (lru_cache, frozen dataclass, перевод единиц в одном месте); нагрузки без оптики отсеиваются (catalog.py:341-350): payloads passing loader: ['geoscan_801_eo_wide','geoscan_801_thermal','geoscan_pf1b','pollux'] | 04-planner-core.md |
|
| CN-1…CN-4 | Контрольные числа 30-domain/01: GSD 1,96 см; захват 117×78 м; шаг 47,0 м; H 153 м | ГОТОВО | uv run python -c "…": GSD@100 cm = 1.9583; footprint@100 = 117.5 78.0; spacing q=60% = 47.0; H for GSD 3cm = 153.19409692079864; trigger@Vg=10,H=150 = 3.51. Те же числа зафиксированы тестом planner/tests/test_geometry_optics.py:47-56. Дополнительно сошлись Pollux (4.31 / 62.1×46.6 / 15.5), тепловизор 801 (13.19 / 84.4×67.5 / 21.1), PF1B на 255 и 500 м | 04-planner-core.md |
|
| Round-trip контрактов | Модели схем переживают сериализацию без потерь | ГОТОВО | tests/integration/test_geometry_contract_roundtrip.py:73/81/90 — Polygon с дыркой, MultiPolygon, 3D LineString, сверка по вершинам abs=1e-9; validator/tests/test_contracts.py:30 (сценарий, again == scenario), :36 (план побайтово), :51 (extra=forbid ловит опечатку) | 08-tests.md |
|
| SEC-R-02 | CORS в gateway не настраивается | ГОТОВО | grep -rn 'enableCors' src/backend --include='*.ts' → NONE; src/backend/gateway/src/main.ts вызова не содержит | 09-security-config.md |
|
| SEC-R-05 | Metadata собирается одной функцией в shared/ | ГОТОВО | src/backend/shared/src/grpc/metadata.ts:3-13 — единственная buildMetadata (x-service-api-key, x-request-id, опционально x-user-id); 'new Metadata(' встречается только в metadata.ts:7 | 09-security-config.md |
|
| SEC-R-12 | В planner/validator нет HTTP-клиентов | ГОТОВО | planner/pyproject.toml:6-11 и validator/pyproject.toml:8-13 — только geoscan-contracts, shapely, pyproj, numpy; барьер validator/tests/test_independence.py:16; прогон: cd src/backend && uv run pytest validator/tests/test_independence.py -q → 56 passed in 0.15s | 09-security-config.md |
|
| SEC-R-18 | Лимит тела запроса на уровне Fastify | ГОТОВО | src/backend/gateway/src/main.ts:17-19 — new FastifyAdapter({ bodyLimit: env.BODY_LIMIT_BYTES }); дефолт 26214400 в config.schema.ts:9 и .env.example:6 | 09-security-config.md |
|
| WS-1 | Воркспейсы api-client и state объявлены и залочены | ГОТОВО | src/frontend/package.json:7-10 — workspaces packages/*; git show 422e43a добавляет node_modules/@geoscan/api-client и @geoscan/state как link:true плюс записи packages/api-client и packages/state; apps/shell/package.json:14-15 объявляет обе зависимости; алиасы и paths — apps/shell/vite.config.ts:57-58, apps/shell/tsconfig.json:9-10 | 07-frontend.md |
|
| uv lock --check | uv.lock консистентен с pyproject'ами workspace'а | ГОТОВО | cd /home/qovalenko/geoscan-wt/review/src/backend && uv lock --check → 'Resolved 25 packages in 1ms', EXIT=0 | 10-build-ops.md |
|
| Аспект: .gitignore | Покрытие env-файлов и ключей | ГОТОВО | .gitignore:1-13 — .creds, .key, .pem, authorized_key.json, kubeconfig, .kube/, .env, .env. с !.env.example, -secret.yaml, sealed*.key; исключение работает — .env.example отслеживается в git, .env нет | 09-security-config.md |
|
| Аспект: git-история f420004..422e43a | Секреты в историю восьми коммитов не попадали | ГОТОВО | git log -p --diff-filter=A f420004~1..422e43a -- '.env' 'key' 'secret' '.pem' '.key' → добавлен только src/backend/gateway/.env.example; git log -p … | grep '^+.*(password|secret|token|api_key)=' → 3 строки, все dev-local-key/объявление zod-поля | 09-security-config.md |
|
| Аспект: правило «секреты только в creds-store» | Файлов с кредами в репозитории нет | ГОТОВО | Совокупно grep/find по дереву и git log -p по 8 коммитам: 0 продовых значений. Обратная сторона (секреты заведены в creds-store) не выполнена — учтено в SR-CFG-05/SEC-R-48 | 09-security-config.md |
|
| Аспект: секреты в рабочем дереве | Реальных кредов в дереве нет | ГОТОВО | grep -rnEi '(BEGIN .PRIVATE KEY|ghp_|glpat-|AKIA[0-9A-Z]{16}|xox[baprs]-|sk-[A-Za-z0-9]{20,}|eyJ…)' src/ k3s/ scripts/ .json .md → 0; find по .key/*.pem/.env/kubeconfig/authorized_key.json → только имя файла docs/task5/91-system/08-config-and-secrets.md; в lock-файлах нет _auth/authToken, resolved только registry.npmjs.org | 09-security-config.md |
|
| В4 | Runtime config shell: адрес API задаётся извне, захардкоженных URL нет | ГОТОВО | grep -rn "localhost|http://|https://|.ff" apps packages tools не даёт ни одного адреса API (остаются дефолты remoteEntry в apps/shell/vite.config.ts:28,33,38, порты в комментариях standalone.tsx и namespace KML). Цепочка: config.json → normalizeRuntimeConfig (packages/state/src/runtime-config.ts:13-19) → GeoscanApiClient({baseUrl}) (create-mission-store.ts:21) → resolveApiBaseUrl (packages/api-client/src/config.ts:20, дефолт '/api') | 07-frontend.md |
|
| Высота 153 м | Высота под GSD 3 см = 153 м и порог ИВП 150 м | ГОТОВО | planner/tests/test_geometry_optics.py:56-58 — altitude_for_gsd_m(PF1B, 3.0) = 153,2±1,0; validator/tests/test_checks.py:32-46 сверяет plan.geometry[0].altitude_m с expected из scenarios/s01-simple.json, test_checks.py:49-56 — 153 м это WARN (ИВП), а не нарушение (30-domain/06-airspace-ru.md:53) | 08-tests.md |
|
| Коды возврата CLI | 0 / 2 / 3 / 4 по 92-code/05 §10.1 | ГОТОВО | validator/validator/cli/exit_codes.py:7-13; прогоны: чистый план EXIT=0; битый сценарий → «невалидный вход: /tmp/gsplans/bad.json: поле «scenario_id» — Field required», EXIT=2; инъекция БПЗ → EXIT=4 | 05-validator.md |
|
| Конвенция ветра | wind_direction_deg — «откуда дует», одно место конверсии на бэкенд | ГОТОВО | shared-py/geoscan_contracts/units.py:92-97 — wind_from_to_wind_to_deg со ссылкой на 40-formats/03:91-93; scenario.py:128-129; schema/scenario.schema.json:196-197 (диапазон 0…360) | 03-contracts.md |
|
| Контрольные числа 30-domain | Оптика валидатора воспроизводит контрольную таблицу 30-domain/01 | ГОТОВО | uv run python … validator.geo.optics для PF1B при GSD 3 см → altitude_m=153.191, across=180.00, spacing@60=72.00; совпадает с scenarios/s01-simple.json (expected 153.2 / 180.0 / 72.0) и с docs/task5/30-domain/01-survey-geometry.md:151-158; uv run pytest validator/tests/test_optics.py -q внутри 132 passed |
05-validator.md |
|
| Контрольные числа 30-domain/01:153 | GSD 1,96 см, кадр 117 × 78 м, шаг 47,0 м при q=60 %, H=100 м | ГОТОВО | validator/tests/test_optics.py:36-56 — GSD 1,96/2,94/3,92/5,00/9,79 см, footprint 117,0 × 78,0 м, шаг 47,0/70,5/94,0/120,0/235,0 м, допуск 0,5 %; planner/tests/test_geometry_optics.py:45-53 — GSD 1,96±0,02, footprint 117,5±1,0 × 78,0±1,0, шаг 47,0±0,5, интервал съёмки 3,5±0,2 с | 08-tests.md |
|
| Контрольные числа Pollux и тепловизора | Таблицы 30-domain/01:167-186 | ГОТОВО | validator/tests/test_optics.py:75-129 — Pollux 4,31/6,47/8,63 см, footprint 62,1 × 46,6, шаг 15,5/23,3/31,1; тепловизор 6,59/13,19/19,78 см, footprint 84,4 × 67,5, шаг 21,1; соотношения 2,2× и 6,6× — :96-112; коэффициенты сверены с payloads.yaml — :135-144 | 08-tests.md |
|
| Метрики 40-formats/03 | Полнота пересчитываемых метрик и счётчиков нарушений | ГОТОВО | validator/validator/metrics.py:147-165 recompute — makespan_s, total_flight_time_s, coverage_frac, transects_total, turns_total, turn_time_frac, photos_total, uavs_used, sorties_total, total_distance_m, violations; ViolationCounts расширены до 7 видов (shared-py/geoscan_contracts/plan.py:45-51); исторические имена coverage_fraction/turn_time_fraction приняты алиасами (plan.py:68-75); solve_time_s берётся из плана осознанно (validate.py:36) | 05-validator.md |
|
| Отказы оптики и ветра | Недостижимые режимы — явная ошибка, а не молчаливое число | ГОТОВО | validator/tests/test_optics.py:147-163 (OpticsUnavailable для riebo_r4 без sensor_width_mm и для LiDAR), :187-189 (|W⊥| ≥ V_a → None); planner/tests/test_geometry_optics.py:61-70 (Infeasible с reason_code ALTITUDE_ABOVE_MAX и CROSSWIND_EXCEEDS_AIRSPEED) | 08-tests.md |
|
| Перечисления контракта (Python) | Семь ViolationKind, четыре ExportFormat, UnassignedReason; токены lower_snake_case | ГОТОВО | shared-py/geoscan_contracts/enums.py:73-82 — семь значений ViolationKind (nfz, airspace, endurance, wind, payload, altitude, turnaround) = рекомендация (а) открытого вопроса 2 из 91-system/04; enums.py:96-100 — четыре ExportFormat; enums.py:85-93 — UnassignedReason; schema/plan.schema.json:79-99 — те же семь ключей violations | 03-contracts.md |
|
| Порядок координат и вырезы | GeoJSON-подобная геометрия WGS84, «долгота, широта», coordinates[0] — внешний контур | ГОТОВО | shared-py/geoscan_contracts/geometry.py:14-17,53-58; тест tests/integration/test_geometry_contract_roundtrip.py:73-87 проходит в составе uv run pytest -q → 150 passed in 1.25s |
03-contracts.md |
|
| Проверка плейсхолдеров в deploy.sh | Раскатка падает на незаполненном шаблоне | ГОТОВО | scripts/deploy.sh:14-18 — grep -rq ' |
||
| Проверки валидатора G1–G6 | Каждая жёсткая проверка ловит своё нарушение | ГОТОВО | validator/tests/test_checks.py — 16 тестов, каждый портит одну вещь: NFZ в метрах (:59), зона слоем выше полёта (:98), выход за разрешённое ВП (:136), ветер выше предела (:165), встречный ветер (:184), потеря разворотов (:196), запас энергии (:204), нехватка 540 с на АКБ (:216), несовместимая нагрузка (:225), unassigned без причины (:237), дубли transect_id (:260), завышенное покрытие (:286), потолок 500 м (:297); покрытие validator/ по --cov 82…100 % | 08-tests.md |
|
| Прогон всех python-тестов | uv run pytest -q зелёный | ГОТОВО | cd /home/qovalenko/geoscan-wt/review/src/backend && uv run pytest -q → «150 passed in 1.96s», падений нет | 08-tests.md |
|
| Разбор сценариев репозитория | scenarios/s01…s03 читаются контрактной моделью | ГОТОВО | tests/integration/test_scenarios_json.py:16-47 — 6 тестов: schema_version, критерий, suitable_for, одна NFZ в s02, две площадки и три борта в s03 | 08-tests.md |
|
| Согласованность workspace'ов фронтенда | package.json ↔ package-lock.json по всем workspace'ам | ГОТОВО | Скрипт сверки по корню и 9 workspace'ам (packages/api-client, state, domain, tokens, ui; apps/shell, planner, validator, viewer3d) → 'mismatches: 0'; 'declared deps not present in lock tree: []'; lockfileVersion 3, 267 пакетов, все 9 слинкованы (node_modules/@geoscan/* link: True). Резолвы: vite 8.3.0, antd 6.6.4, react 19.3.0, typescript 5.9.3, @module-federation/vite 1.21.6, maplibre-gl 5.24.0, @deck.gl/core 9.4.0 | 10-build-ops.md |
|
| Состав проверок (Q-PY-4) | 24 проверки в 6 группах со стабильными идентификаторами | ГОТОВО | uv run python -m validator check … печатает ровно 24 результата: G1×5, G2×4, G3×4, G4×3, G5×4, G6×4; агрегация по объектам — checks/base.py:56; нереализованное видно строкой «не реализовано: G2-04» |
Зафиксировать в 92-code/05 замену предусмотренной G2-04 на не описанную там G2-05 (g2_altitude.py:135) | 05-validator.md |
| Тесты backend | Прогон тестов зелёный | ГОТОВО | cd /home/qovalenko/geoscan-wt/review/src/backend && uv run pytest -q → '150 passed in 1.16s' | 10-build-ops.md |
|
| Тесты валидатора | Прогон тестов пакета и всего бэкенда | ГОТОВО | cd src/backend && uv run pytest validator/tests -q → 132 passed in 1.14s; uv run pytest -q → 150 passed in 1.12s |
05-validator.md |
|
| Формат отчёта 70-plan/01 | Одна команда, одна таблица, бейзлайн в той же таблице, колонка viol | ГОТОВО | validator/validator/report/table.py:139-157 (блок «БЕЙЗЛАЙН (равное деление)») и _improvement_line:114; прогон validator run печатает шапку scenario/crit/makespan/total_fl/cover/turns%/solve/viol; тесты validator/tests/test_report_and_cli.py |
05-validator.md |