Перейти к содержанию

99 — Сводный список дефектов реализации

Источник — десять отчётов ревью 01-business-trace.md10-build-ops.md (153 записи). После слияния дублей (одна проблема = одна запись, источники объединены, severity — максимальная из вариантов, доказательство — самое конкретное из имеющихся) осталось 103 уникальных дефекта.

Сводка

Severity Уникальных дефектов Было записей в отчётах
critical 18 26
major 41 65
minor 44 62
Итого 103 153

Нумерация сквозная: D-01…D-18 — critical, D-19…D-59 — major, D-60…D-103 — minor. Внутри каждой severity сначала идут дефекты, ломающие сквозной сценарий и демонстрацию.


Расхождения с контрольными числами

Расхождений с контрольными числами из docs/task5/30-domain не найдено. Ни один из 153 дефектов десяти отчётов не помечен как расходящийся с эталонными таблицами предметной области.

Положительный результат подтверждён прямо:

  • Контрольные числа оптики и геометрии съёмки (30-domain/01-survey-geometry.md:153 — GSD 1,96 см, отпечаток 117 × 78 м, Bx 47,0 м, By 23,5 м на 100 м) воспроизводятся кодом: cd src/backend && uv run pytest -q validator/tests/test_optics.py planner/tests/test_geometry_optics.py41 passed. Валидатор берёт числа прямо из справочника (validator/tests/test_optics.py:24load_catalog()), и результаты совпадают (см. D-65 — там речь только о способе получения чисел в тестах планировщика, сами значения сходятся).
  • Найденные ошибки планировщика не противоречат 30-domain, а наоборот — численно совпадают с его предсказаниями: проигрыш при выборе галсов вдоль ветра лёг в предсказанные 30-domain/03:95 «+67 % при W/V_a = 0,8» (D-06), а игнорирование оборота на площадке даёт занижение makespan того же порядка, что описано в 30-domain/05:143 (≈ ×2,3, D-21).
  • Весь набор тестов бэкенда зелёный: cd src/backend && uv run pytest -q150 passed in 1.11s. Проблема не в числах предметной области, а в том, что эти числа нигде не встречаются со сквозным путём «сценарий → план → валидатор» (D-01).

Critical

D-01 [critical] Два несовместимых диалекта контрактов: сквозной путь «сценарий → план → валидатор» не работает

Что сломано. В одном пакете shared-py/geoscan_contracts/ сосуществуют два независимых представления документов: schema.py (camelCase-алиасы gsdCm/timeLimitS/windSpeedMs, Phase.path, coverage_fraction, extra='forbid') — им пользуется планировщик (planner/planner/pipeline.py:12, planner/planner/cli/__main__.py:10), и scenario.py + plan.py (snake_case, Phase.geometry, coverage_frac) — им пользуются documents.py:37-48, фикстуры scenarios/*.json и валидатор. Планировщик и не читает вход репозитория, и пишет выход, который не читают ни валидатор, ни schema/plan.schema.json.

Как воспроизвести. 1. cd src/backend && uv run python -m planner.cli run --scenario scenarios/s01-simple.json --objective makespan --time-limit 30 --out /tmp/plan.jsonpydantic_core.ValidationError: 19 validation errors for Scenario (tasks.0.gsdCm Field required, tasks.0.quality extra_forbidden, weather.windSpeedMs Field required, solver.timeLimitS Field required, airspace.no_fly extra_forbidden, landing_sites extra_forbidden, …), exit 1. 2. Обратно: дамп плана из validator/tests/fixtures.py:build_s01_plan через geoscan_contracts.schema.Plan → 262 ошибки (metrics.coverageFraction missing / metrics.coverage_frac extra); load_plan() над выгрузкой планировщика → InvalidDocument: поле «scenario_id» — Field required, а после ручного перевода в snake_case — 70 ошибок (phases.0.path Extra inputs are not permitted). 3. Выгрузка: planner/planner/cli/__main__.py:41 пишет model_dump_json(by_alias=True)TOP KEYS ['schemaVersion','scenarioId','generatedAt',…], METRICS KEYS ['makespanS','totalFlightTimeS','coverageFraction',…] и 37 ошибок против schema/plan.schema.json. Модели Weather (schema.py:107-113) и SolverConfig (schema.py:126-130) объявлены без populate_by_name, поэтому snake_case на входе тоже ошибка.

Чем грозит. Ни одно приёмочное число (violations = 0, coverage, makespan) получить нельзя: uv run python -m validator run --scenarios scenarios/ печатает «нет плана» по всем трём сценариям. Метод проекта «валидатор раньше продукта» замкнут сам на себя — и план, и его проверка порождаются validator/tests/fixtures.py. По SR-API-01 гейтвей обязан отдавать план побайтово равным ответу планировщика, то есть наружу уйдёт документ, не соответствующий ни plan.schema.json, ни 40-formats/03. Недостижимы SR-SVC-09, SR-TST-12, SR-TST-14, SR-NFR-16. 150 зелёных тестов проходят мимо разрыва, потому что фикстуры строятся конструкторами в диалекте schema.py.

Источник. Найдено в 01-business-trace.md, 02-system-trace.md, 03-contracts.md, 04-planner-core.md, 05-validator.md, 08-tests.md.

D-02 [critical] POST /api/plans фабрикует planId, GET /api/plans/{id} всегда отдаёт queued — расчёт не запускается никогда

Что сломано. src/backend/gateway/src/plans/plans.controller.ts:26-52: POST генерирует planId через randomUUID() и ничего не сохраняет; GET возвращает {status:'queued'} для любого идентификатора, включая заведомо несуществующий. Нет Location, scenarioId, queuePosition, нет 404 PLAN_NOT_FOUND, нет терминального статуса, планировщик не вызывается вовсе (grep -rn 'createGrpcServiceClient' gateway/src → пусто).

Как воспроизвести. Любой POST /api/plans → 202 с новым UUID; затем GET /api/plans/любая-строка → 200 queued. Код: gateway/src/plans/plans.controller.ts:31-36,42-52.

Чем грозит. Снаружи заглушка неотличима от работающего сервиса: клиент (src/frontend/packages/api-client/src/endpoints/plans.ts:20) будет поллить вечно. Нарушены A-07, SR-API-06, SR-API-07. Демонстрация «сервис считает план» невозможна через HTTP.

Источник. Найдено в 06-gateway.md, 01-business-trace.md.

D-03 [critical] Асинхронная модель расчёта (A-07 / SR-API-06,07) во фронтенде не реализована: один GET сразу после POST

Что сломано. packages/state/src/api-compute.ts:38-48 — сразу после createPlan вызывается getPlan, поле status ответа не читается, опроса и SSE нет. Ответ-конверт приводится к Plan (api-compute.ts:40) и пишется в store.plan.

Как воспроизвести. Положить config.json с dataSource:"api", нажать «Рассчитать». Текущий гейтвей (gateway/src/plans/plans.controller.ts:47-51) всегда возвращает {planId, status:'queued', message} без поля plan, поэтому в store.plan попадёт объект без metrics/missions/solver.

Чем грозит. apps/planner/src/screens/PlanScreen.tsx читает plan.metrics.* и plan.solver.status — экран упадёт либо покажет пустоту. Режим dataSource=api нерабочий целиком.

Источник. Найдено в 07-frontend.md.

D-04 [critical] В POST /api/plans уходит camelCase-сценарий при snake_case-схеме с additionalProperties:false

Что сломано. packages/state/src/api-compute.ts:24-30 отправляет доменный объект scenario как есть. Домен camelCase (packages/domain/src/types.ts:124-139: schemaVersion, scenarioId, launchSites, objective.safetyBufferM, SurveyTask.gsdCm, forwardOverlapPct), а src/backend/schema/scenario.schema.json требует schema_version, scenario_id, launch_sites, objective.safety_buffer_m, quality.gsd_cm, overlap_forward_frac, имеет additionalProperties:false на всех уровнях и требует landing_sites, которого в домене нет. Мапперов нет (mappers.ts не существует). Расходятся и единицы: turnaround_time_s (с) против turnaroundTimeMin (мин), battery_left_frac (0…1) против batteryPct (%), overlap_*_frac против *OverlapPct.

Как воспроизвести. packages/state/src/api-compute.ts:24-30 против src/backend/schema/scenario.schema.json; лишние поля utmZone и dropped_uav_ids схемой и SR-API-06 не предусмотрены.

Чем грозит. При включении валидации входа (SR-API-05) — гарантированный 400 SCHEMA_VALIDATION_FAILED на каждом расчёте; режим dataSource=api нерабочий даже после починки гейтвея.

Источник. Найдено в 07-frontend.md.

D-05 [critical] Бесполётные зоны и граница разрешённого ВП игнорируются, а violations захардкожены нулями

Что сломано. scenario.airspace не читается в pipeline.plan() ни разу (grep "scenario\." src/backend/planner/planner/pipeline.py), а pipeline.py:505-511 возвращает словарь violations из пяти зашитых нулей.

Как воспроизвести. cd src/backend && uv run python /tmp/run_s02.py (сценарий scenarios/s02-nfz.json) → transects crossing NFZ: 3 of 8, approx length inside 810 m, при этом metrics.violations = {'nfz': 0, 'airspace': 0, 'endurance': 0, 'wind': 0, 'payload': 0} и coverage_fraction 1.0.

Чем грозит. План, который валидатор обязан отклонить, выдаётся как допустимый (BR-01): пользователь, доверившийся плану без отдельного прогона валидатора, получает «зелёный» план с любыми нарушениями. Нарушены FR-PLN-03, FR-PLN-05, жёсткие проверки FR-VAL-04/05.

Источник. Найдено в 04-planner-core.md, 01-business-trace.md.

D-06 [critical] Автоподбор угла галсов и расчёт времени используют угол семейства вместо курса галса — выбирается направление вдоль ветра

Что сломано. pipeline.py:85-91 (_optimal_angle_deg) и pipeline.py:159-165 (_pack_mission) передают в transect_time_s параметр angle, тогда как фактический курс галса возвращает сам генератор (coverage/transects.py:90) и он отличается на 90°.

Как воспроизвести. cd src/backend && uv run python /tmp/wind.py (квадрат 1 × 1 км, V_a = 10 м/с, ветер 8 м/с с запада) → chosen angle: 3.0 / angle=3.0: real_total=3935.5s / angle=93.0: real_total=2419.6s.

Чем грозит. Выбирается вариант на 63 % медленнее — ровно предсказанный 30-domain/03:95 проигрыш «+67 % при W/V_a = 0,8». Вторая половина ошибки: план отчитывается временем поперечных галсов, а летит продольными — время съёмки 2301,7 с против честных 3823,4 с (занижение в 1,66 раза). Сценарий s06 из 70-plan/02 на этом коде провалится.

Источник. Найдено в 04-planner-core.md.

D-07 [critical] Галс, не влезающий в бюджет вылета, попадает в план как выполнимый

Что сломано. pipeline.py:174: проверка used + leg_s + back_s + LANDING_S > budget_s and taken применяется только начиная со второго галса — при пустом taken она не срабатывает и первый галс берётся всегда, а вылет выходит за endurance · (1 − reserve). Требуемой FR-PLN-10 нарезки на подгалсы нет.

Как воспроизвести. cd src/backend && uv run python /tmp/more.py (полоса 40 км, один Gemini) → transects: 2 sorties: 2 energy_frac: [3.33, 3.33] unassigned: 0 status: feasible.

Чем грозит. План требует 333 % заряда АКБ и объявлен допустимым; валидатор зафиксирует violations.endurance > 0, то есть публично выданный план будет отклонён собственной проверкой.

Источник. Найдено в 04-planner-core.md, 01-business-trace.md.

D-08 [critical] Ветка «нет совместимого борта» гарантированно роняет расчёт (ValidationError на Unassigned)

Что сломано. pipeline.py:305-311 конструирует Unassigned(transect_id=..., task_id=...), а модель schema.py:236-242 объявлена без populate_by_name и с extra='forbid'.

Как воспроизвести. cd src/backend && uv run python /tmp/edge.py, случай «201 + riebo_r4, задание rgb» → RAISED ValidationError: 4 validation errors for Unassigned / transectId Field required / transect_id Extra inputs are not permitted.

Чем грозит. Любой сценарий с недостижимым типом съёмки или нагрузкой без нужной оптики падает с трейсбеком вместо штатного отчёта с unassigned и причиной (FR-PLN-24, FR-VAL-02).

Источник. Найдено в 04-planner-core.md.

D-09 [critical] python -m planner не запускается, флага --seed нет

Что сломано. В src/backend/planner/planner/ нет __main__.py — точка входа лежит в planner/cli/__main__.py и вызывается как python -m planner.cli.

Как воспроизвести. cd src/backend && uv run python -m planner --helpNo module named planner.__main__; 'planner' is a package and cannot be directly executed. uv run python -m planner.cli run … --seed 42unrecognized arguments: --seed 42.

Чем грозит. Документированная команда запуска (SR-SVC-09 — docs/task5/91-system/02-services.md:92, src/backend/README.md:18, R-DOC-3, NFR-36) нерабочая: жюри, выполнив README дословно, получит ошибку.

Источник. Найдено в 01-business-trace.md, 02-system-trace.md.

D-10 [critical] Образ gateway не собирается: ошибка TypeScript в пакете shared

Что сломано. src/backend/shared/src/grpc/client-factory.ts:44return new ServiceCtor(address, credentials, defaultChannelOptions(maxBytes)) as T; даёт error TS2352: Conversion of type ServiceClient to type T may be a mistake…. shared/tsconfig.json включает strict и noUncheckedIndexedAccess, поэтому ошибка неизбежна.

Как воспроизвести. Из корня репозитория: docker build -f src/backend/gateway/Dockerfile -t geoscan-gateway:review .EXIT=1, падение на первой стадии shared-build.

Чем грозит. Нет ни одного артефакта поставки: SR-DEP-01/02/11/12/13 и NFR-09 недостижимы, офлайн-прогон NFR-11/NFR-14 физически не на чем провести.

Источник. Найдено в 10-build-ops.md.

D-11 [critical] Образ api-client не собирается: TS2352 в config.ts

Что сломано. src/frontend/packages/api-client/src/config.ts:16 — приведение globalThis для чтения VITE_GEOSCAN_API_BASE.

Как воспроизвести. docker build -f src/frontend/packages/api-client/Dockerfile -t geoscan-apiclient:review .EXIT=1: src/config.ts(16,10): error TS2352: Conversion of type typeof globalThis to type { process: { env: Record<string, string>; }; } may be a mistake … Property process is missing.

Чем грозит. Сломан и штатный npm run build пакета, а от api-client зависят @geoscan/state и apps/shell — фронтенд не собирается ни в образ, ни локально.

Источник. Найдено в 10-build-ops.md.

D-12 [critical] k3s/geoscan/ не раскатывается: плейсхолдеры <IMAGE> и <PORT> не подставлены

Что сломано. Каталог остался нетронутым шаблоном: diff -r k3s/template k3s/geoscan показывает единственный вид правки — подстановку <NAME>geoscan.

Как воспроизвести. grep -rn '<IMAGE>\|<PORT>' k3s/geoscan/ → 6 вхождений (40-deployment.yaml:16,25,28,33,37, 50-service.yaml:12); kubectl --context n2 apply -k k3s/geoscan/ --server-side --dry-run=serverError from server: failed to create typed patch object … .containerPort: expected numeric (int or float), got string.

Чем грозит. scripts/deploy.sh:14-18 штатно прервёт выкат — https://geoscan.ff поднять нечем. Критерий приёмки SR-DEP-04 не выполнен.

Источник. Найдено в 10-build-ops.md.

D-13 [critical] Нет docker-compose.yml — офлайн-пакета для жюри не существует

Что сломано. ls docker-compose.ymlNo such file or directory; find . -name 'docker-compose*' → пусто. SR-DEP-13 требует контур того же состава, что k3s, поднимаемый без интернета и без tailnet.

Как воспроизвести. См. команды выше; дополнительно оба существующих Dockerfile делают npm install из registry.npmjs.org без лок-файла (D-43), поэтому путь «собираются из исходников теми же Dockerfile» без сети нерабочий, а docker save невозможен, потому что образы не собираются (D-10, D-11).

Чем грозит. NFR-09 и R-DOC-3 не закрыты: на защите без интернета сервис не поднять.

Источник. Найдено в 10-build-ops.md.

D-14 [critical] Planner.ExportPlan принимает plan_id вместо плана — нарушение решения D-04 и SR-RPC-04

Что сломано. src/backend/proto/geoscan/planner.proto:19-23message ExportPlanRequest { string plan_id = 1; string format = 2; string uav_id = 3; }. Формат передаётся строкой вместо enum ExportFormat.

Как воспроизвести. src/backend/proto/geoscan/planner.proto:19-23 против SR-RPC-04 (docs/task5/91-system/04-internal-contract.md:92) и решение D-04 (11-decisions.md:58).

Чем грозит. Критерий приёмки «экспорт плана, который сервис не считал (взят из файла)» идентификатором невыразим; экспорт потребует у planner доступа к состоянию, что ломает SR-SVC-03 (planner без состояния и без БД). Неподдерживаемый формат ловится только рантаймом.

Источник. Найдено в 02-system-trace.md, 03-contracts.md.

D-15 [critical] Validator.Validate теряет сценарий на входе и отчёт на выходе

Что сломано. src/backend/proto/geoscan/validator.proto:9-15ValidateRequest { bytes plan_json = 1; } (сценария нет) и ValidateResponse { repeated string violations = 1; } (нет метрик, warnings, unassigned, признака допуска).

Как воспроизвести. src/backend/proto/geoscan/validator.proto:9-15 против SR-RPC-05; реализованный отчёт валидатора — validator/validator/validate.py:29-31, validator/validator/report/json_report.py:36-37.

Чем грозит. Валидатор по контракту не получает пороги (objective.energy_reserve, safety_buffer_m) и не может вернуть числа, которые SR-RPC-05 требует сверять с CLI-валидатором до последнего знака. Публичное обещание «проверьте наш план сами» через gRPC невыполнимо.

Источник. Найдено в 02-system-trace.md, 03-contracts.md.

D-16 [critical] JSON-схемы schema/*.json не подключены ни к одному потребителю, SR-API-05 не реализован

Что сломано. grep -rn "scenario.schema.json\|plan.schema.json" src/ даёт совпадения только внутри самих файлов (schema/scenario.schema.json:3, schema/plan.schema.json:3). ajv объявлен в gateway/package.json:21-22, но grep -rn "ajv\|Ajv" src/backend/gateway/src → пусто.

Как воспроизвести. Команды выше.

Чем грозит. SR-API-05 (проверка по схеме до постановки задачи в очередь, 400 SCHEMA_VALIDATION_FAILED с errors[]) не реализован; схемы никем не исполняются и поэтому уже разошлись и с фикстурами, и с моделями (D-51, D-52). Мусорный сценарий дойдёт до планировщика.

Источник. Найдено в 03-contracts.md.

D-17 [critical] Инструменты качества (import-linter, ruff, mypy) настроены, но не установлены и не запускаются — барьер SR-TST-08 мёртв

Что сломано. src/backend/.importlinter описывает 4 корректных контракта (validator-is-independent, core-has-no-transport, contracts-have-no-geometry, validator-layers), src/backend/pyproject.toml:11-40 содержит [tool.ruff] с 14 наборами правил и [tool.mypy] strict = true. Ни один из инструментов не объявлен зависимостью: grep по всем pyproject.toml не находит ruff/mypy/import-linter ни в dependencies, ни в optional-dependencies, ни в dependency-groups; grep -c "import-linter" src/backend/uv.lock → 0.

Как воспроизвести. cd src/backend && uv run lint-importserror: Failed to spawn: lint-imports; uv run ruff --version и uv run mypy --version → то же. При ручной установке конфиг рабочий: uv run --with import-linter lint-imports --config .importlinterContracts: 4 kept, 0 broken. В src/backend/Makefile только proto-цели, CI-джоба нет (.github/.gitea/.gitlab-ci.yml отсутствуют).

Чем грозит. Ключевое методологическое требование проекта — независимость валидатора (SR-TST-07/08, PY-01 из 92-code/05-python-core.md:746) — держится только на ручном чтении диффа и одном AST-тесте validator/tests/test_independence.py; регрессия въедет незамеченной.

Источник. Найдено в 05-validator.md, 02-system-trace.md, 08-tests.md, 10-build-ops.md, 01-business-trace.md.

D-18 [critical] Ядро планировщика имеет 0 % покрытия тестами

Что сломано. Тестов у планировщика 6 из 150 (planner/tests/test_geometry_optics.py — 5, test_kml_export.py — 1); распределение по бортам, нарезка галсов, метрическая проекция и наивный бейзлайн не покрыты вовсе.

Как воспроизвести. cd src/backend && uv run --with pytest-cov pytest -q --cov=planner --cov-report=term-missingplanner/planner/pipeline.py 215 стейтментов — 0 % (1-481), 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 %. Итого 471 непокрытый стейтмент.

Чем грозит. Любая правка оптимизатора или геометрии пройдёт в main незамеченной; все дефекты D-05…D-08 существуют именно потому, что этот код не проверяется ничем.

Источник. Найдено в 08-tests.md.


Major

D-19 [major] Решателя нет: критерии оптимизации сведены к весовому делению площади, бейзлайн совпадает с планом

Что сломано. ortools не объявлен ни в одном pyproject.toml (grep -rn ortools --include=*.toml → только комментарий validator/pyproject.toml:7), модуля routing/ нет (ls src/backend/planner/planner). Распределение — деление полигона на полосы (planner/baseline/equal_split.py:51), а «критерий» сводится к показателю exponent в pipeline.py:352-358 (1.0 для makespan, 2.0 для total_flight_time); веса полос = (spacing · V_cruise)^exponent (pipeline.py:350-369) и у одинаковых бортов равны при любом exponent. time_limit_s и seed только переписываются в отчёт (pipeline.py:489-490).

Как воспроизвести. cd src/backend && uv run python /tmp/run_s03.py (сценарий s03, три Gemini) → makespan / total_flight_time / naive_equal_split дают одно и то же: makespan_min 17.09 flight_min 47.08, распределение галсов 4/4/4.

Чем грозит. FR-PLN-17/18/21 фактически не реализованы, «выигрыш против наивного» в презентации равен 0 %, SR-NFR-01…SR-NFR-09 закрыть нечем.

Источник. Найдено в 02-system-trace.md, 04-planner-core.md.

D-20 [major] Метрики покрытия и разворотов декоративны: coverage_fraction тождественно 1.0, turns_total и turn_time_fraction — нули

Что сломано. pipeline.py:476,497coverage_fraction = transects_total / planned, где planned считается из тех же транзектов (pipeline.py:434-436), то есть доля числа пройденных галсов, а не покрытая площадь; pipeline.py:499-500turns_total=0, turn_time_fraction=0.0 при том, что этапы turn в плане есть.

Как воспроизвести. cd src/backend && uv run python /tmp/cov.py на s02 → reported coverage_fraction 1.0, честная цель «площадь минус NFZ» = 0.8636.

Чем грозит. BR-21/BR-24 и FR-PLN-08 не выполнены; валидатор считает правильно (validator/metrics.py:38-71, 30-domain/03:210-217) и завалит такой план проверками G6-01 и G6-03 (validator/checks/g6_coverage.py:116-144) — то есть собственный план сервиса не проходит собственную приёмку.

Источник. Найдено в 01-business-trace.md, 04-planner-core.md.

D-21 [major] Между вылетами одного борта не закладывается оборот на площадке / зарядка АКБ

Что сломано. pipeline.py:255clock += used: следующий вылет стартует ровно в момент посадки предыдущего. turnaround_min грузится в каталоге (catalog.py:327) и нигде не используется.

Как воспроизвести. cd src/backend && uv run python /tmp/run_big.py (полигон 25 км², один Gemini) → 41 вылет подряд за 17,9 ч, sortie 2 start_min 18.57 при dur_min 18.57 первого, без единой зарядки при 105 мин заряда по спецификации.

Чем грозит. По 30-domain/05:143 такая ошибка занижает makespan примерно в 2,3 раза — главная заявляемая метрика сервиса систематически завышает качество плана.

Источник. Найдено в 04-planner-core.md.

D-22 [major] Исключения Infeasible при отборе бортов не перехватываются: один непригодный борт роняет весь расчёт

Что сломано. pipeline.py:321 вызывает altitude_for_gsd_m внутри цикла по бортам без обработки исключения; geometry/wind.py:20-21 бросает Infeasible('CROSSWIND_EXCEEDS_AIRSPEED') из _optimal_angle_deg (pipeline.py:85) и _pack_mission (pipeline.py:159). Фильтр pipeline.py:304 пропускает борт при wind_max_ms >= wind_speed_ms, а у Gemini и 801 airspeed_cruise = wind_max = 10 м/с (fleet.yaml:122-125), то есть допущенный фильтром борт гарантированно падает.

Как воспроизвести. cd src/backend && uv run python /tmp/edge.py: парк «Gemini + 801», GSD 10 см → RAISED Infeasible: ALTITUDE_ABOVE_MAX: altitude 510.6 m > max AGL 500.0 m (хотя Геоскан 801 выполняет это задание на 282 м); случай «gemini only, wind=10 m/s» → RAISED Infeasible: CROSSWIND_EXCEEDS_AIRSPEED.

Чем грозит. Деградация парка (FR-PLN-16, FR-VAL-01, BR-04, FR-PLN-24) не работает: вместо отчёта «доступно N из M бортов» и unassigned с причиной сервис падает целиком. Ожидаемый сценарий s09 («ветер 11 м/с») роняет расчёт.

Источник. Найдено в 04-planner-core.md, 01-business-trace.md.

D-23 [major] plan.solver.status принимает значение вне контракта — 'feasible_with_unassigned'

Что сломано. planner/planner/pipeline.py:492status='feasible' if not unassigned else 'feasible_with_unassigned'. SR-NFR-02 и перечисление SolverStatus (shared-py/geoscan_contracts/enums.py) допускают только optimal/feasible/infeasible/timeout.

Как воспроизвести. planner/planner/pipeline.py:492 против shared-py/geoscan_contracts/enums.py.

Чем грозит. enum SolverStatus в gRPC-контракте и лейбл метрики geoscan_plan_total{status} (SR-OBS-04) сломаются; в validator/validator/cli/run.py:132 сравнение с SolverStatus.INFEASIBLE молча не сработает — недопустимый план попадёт в таблицу приёмки как нормальный.

Источник. Найдено в 02-system-trace.md.

D-24 [major] POST /api/validate — заглушка, отдаёт 200 без единой проверки; gRPC-сервера валидатора нет

Что сломано. src/backend/gateway/src/validate/validate.controller.ts:19-30 возвращает {status:'stub', message:'Проксирование Validator.Validate будет подключено в GW-12', received:true}; grep buildMetadata\|createGrpcServiceClient по gateway/src → 0 совпадений. В validator/validator/ нет модуля rpc/ (ls validator/validator → admission, checks, cli, geo, metrics, report, validate).

Как воспроизвести. src/backend/gateway/src/validate/validate.controller.ts:19-30; ls src/backend/validator/validator.

Чем грозит. Публичная ручка «проверьте наш план сами» (A-15, SR-API-18, FR-VAL-17) выдаёт успех, не вызвав валидатор: демонстрация жюри «наш план проверен» строится на пустом ответе. Обещание защиты работает только через CLI.

Источник. Найдено в 05-validator.md, 06-gateway.md.

D-25 [major] @geoscan/api-client обращается к несуществующим маршрутам гейтвея

Что сломано. src/frontend/packages/api-client/src/endpoints/scenarios.ts:130-140 вызывает /api/scenarios, а в gateway/src/app.module.ts:9-15 такого модуля нет.

Как воспроизвести. src/frontend/packages/api-client/src/endpoints/scenarios.ts:130-140 против src/backend/gateway/src/app.module.ts:9-15.

Чем грозит. Любая операция сохранения/загрузки сценария через API даст 404 в форме, которую клиент не разбирает (см. D-31); в связке с D-02 режим dataSource='api' либо падает с «Ответ POST /api/plans не содержит идентификатор плана», либо отдаёт заглушку вместо плана.

Источник. Найдено в 01-business-trace.md.

D-26 [major] Фронтенд показывает заведомо зелёную проверку температуры

Что сломано. src/frontend/packages/domain/src/validation.ts:311-318 — статус 'pass' и текст «в пределах −20…+40 °C для всех бортов парка» зашиты, scenario.weather.temperatureC только подставляется в строку.

Как воспроизвести. src/frontend/packages/domain/src/validation.ts:311-318.

Чем грозит. При −18 °C Gemini (диапазон −15…+40) будет показан допущенным, тогда как BR-06 требует пометить его недоступным — пользователь отправит борт за пределы эксплуатационного диапазона по «зелёной» проверке сервиса.

Источник. Найдено в 01-business-trace.md.

D-27 [major] Интерфейс обещает пересчёт CRS и генерализацию, которых нет; импорт файла не разбирается

Что сломано. src/frontend/apps/planner/src/screens/ExportScreen.tsx:179 — «При импорте выполняется пересчёт CRS в WGS 84 и генерализация по Дугласу-Пекеру». Ни пересчёта CRS, ни упрощения геометрии в коде нет; импорт файла вообще не выполняется — apps/shell/src/screens/StartScreen.tsx:151 содержит beforeUpload={() => false}.

Как воспроизвести. Файлы и строки выше.

Чем грозит. FR-SCN-04 и FR-EXP-18 не выполнены, интерфейс вводит пользователя в заблуждение: загруженный KML/GeoJSON области съёмки молча игнорируется.

Источник. Найдено в 01-business-trace.md.

D-28 [major] Типизации границы нет: все ручки Promise, снимок OpenAPI рукописный и неполный

Что сломано. packages/api-client/src/endpoints/fleet.ts:3,7; plans.ts:11,19,28; scenarios.ts:3,7,11; validate.ts:3 — все тела unknown; src/generated/schema.ts отсутствует. packages/api-client/openapi.snapshot.json:6 помечен как «минимальный снимок» и описывает 4 операции против 11 маршрутов раздела 1 контракта (нет /api/scenarios, /api/scenarios/{id}, /api/plans/{id}, /events, /export/{fmt}, /replan).

Как воспроизвести. Файлы и строки выше.

Чем грозит. Барьер FE-R-14 («снимок совпадает с /docs-json») невыполним; расхождение клиента с гейтвеем (D-04, D-25) не поймает ни компилятор, ни тест.

Источник. Найдено в 07-frontend.md.

D-29 [major] useActions возвращает новый объект на каждый вызов — в zustand 5 бесконечный ререндер

Что сломано. packages/state/src/hooks.ts:8-45: actionsSelector собирает новый объект из 30 действий, useActions передаёт его в useStore без useShallow. Версия zustand в src/frontend/package-lock.json — 5.0.15, где useStore построен на useSyncExternalStore.

Как воспроизвести. packages/state/src/hooks.ts:8-45 + версия в src/frontend/package-lock.json.

Чем грозит. React-ошибка «The result of getSnapshot should be cached to avoid an infinite loop» и цикл перерисовок при первом же использовании хука. 92-code/02 §3.3 прямо требует «useActions, возвращающий стабильные ссылки». Дефект латентный только потому, что хук нигде не вызывается (grep -rn "useActions" apps — пусто).

Источник. Найдено в 07-frontend.md.

D-30 [major] Состояние ошибки отсутствует: ApiError гасится пустым catch

Что сломано. packages/state/src/create-mission-store.ts:28-30void runApiCompute(...).catch(() => store.setState({computing:false})). Ни errorCode, ни message, ни x-request-id никуда не попадают; поля ошибки в сторе нет (packages/domain/src/store.ts:43-69).

Как воспроизвести. Файлы и строки выше.

Чем грозит. 422 GSD_UNREACHABLE (сценарий s08), 429 QUEUE_FULL и 503 UPSTREAM_UNAVAILABLE пользователь увидит как молчаливый возврат в «Черновик» (apps/shell/src/TopBar.tsx:25-31) — против SR-API-10/11/13; диагностика инцидента на защите невозможна.

Источник. Найдено в 07-frontend.md.

D-31 [major] JSON.parse выполняется до проверки res.ok; заголовки ответа не читаются

Что сломано. packages/api-client/src/client.ts:81-93: const text = await res.text(); const json = JSON.parse(text) — до if (!res.ok).

Как воспроизвести. packages/api-client/src/client.ts:81-93.

Чем грозит. Не-JSON ответ (HTML ingress при 502/503, text/plain, будущий zip экспорта) даст SyntaxError вместо ApiError с errorCode, и обработчик ошибок контракта не сработает. Дополнительно не читаются Retry-After (SR-API-11), Location (SR-API-06), Idempotency-Replayed (SR-API-14), x-request-id (SR-API-02).

Источник. Найдено в 07-frontend.md.

D-32 [major] Смешанный контур в режиме api: план с сервера, Парето и бейзлайн — локальным решателем

Что сломано. packages/state/src/api-compute.ts:52-54 при успехе ставит baseline:null и pareto:[], а apps/validator/src/screens/ParetoScreen.tsx:29-30 независимо от источника данных вызывает solve(scenario,…) дважды на каждое изменение сценария.

Как воспроизвести. Файлы и строки выше.

Чем грозит. Числа с сервера и числа клиентского решателя не обязаны совпадать; «выигрыш против наивного» на экране плана при dataSource=api не покажется вовсе — ключевой аргумент презентации исчезает именно в боевом режиме.

Источник. Найдено в 07-frontend.md.

D-33 [major] В TypeScript-части нет ни одного теста, тест-раннера и каркаса качества

Что сломано. find src -name "*.spec.ts" -o -name "*.test.ts" -o -name "*.spec.tsx" -o -name "*.test.tsx" | grep -v node_modules → пусто. src/backend/gateway/package.json: scripts = proto:gen, build, start, start:dev; devDependencies = @nestjs/cli, @types/node, @types/uuid, typescript — раннера нет. src/frontend/package.json:31-34 — devDependencies только concurrently и typescript, скриптов lint/test/depcruise нет, конфигураций eslint/dependency-cruiser/vitest нет.

Как воспроизвести. Команда и файлы выше (проверено статическим чтением: node/npm в среде нет).

Чем грозит. Барьеры SR-TST-02 (полная AST-форма), 03, 04, 05, 12, 13, 16 реализовать нечем; критерий готовности FE-03 («вызывается из тестов против мок-сервера; 400/422/429/503 разбираются в ApiError») не проверяется ни одним автоматическим шагом. Дефекты D-28…D-32 не видны ни одному тесту.

Источник. Найдено в 07-frontend.md, 08-tests.md, 06-gateway.md.

D-34 [major] Приёмочный прогон валидатора возвращает 0 при полном отсутствии планов

Что сломано. validator/validator/cli/run.py:148-154 при пустом списке отчётов возвращает EXIT_OK. Поведение закреплено как ожидаемое: validator/tests/test_report_and_cli.py:98-114 (test_cli_run_without_plan_says_so ассертит code == EXIT_OK).

Как воспроизвести. cd src/backend && uv run python -m validator run --scenarios scenarios/ --criteria makespan,total_time → три строки «нет плана» и EXIT=0.

Чем грозит. SR-TST-14 предполагает, что этот прогон — приёмочный гейт сборки: в CI он будет зелёным на пустоте, то есть полное отсутствие планировщика неотличимо от успешной приёмки. Сейчас именно так и происходит (D-01).

Источник. Найдено в 02-system-trace.md, 08-tests.md.

D-35 [major] В репозитории 3 сценария из 10 и ноль эталонных планов

Что сломано. ls src/backend/scenarios/s01-simple.json, s02-nfz.json, s03-multi-sites.json, тогда как docs/task5/70-plan/02-test-scenarios.md:11-98 описывает s01–s10. Планов-фикстур нет: validator/tests/fixtures.py:105 (build_s01_plan) строит план арифметикой на лету и только для s01.

Как воспроизвести. ls src/backend/scenarios; uv run python -m validator run --scenarios scenarios/ → «нет плана» для s02 и s03.

Чем грозит. Все критерии приёмки «на s01–s10» (SR-TST-09, SR-TST-10 (1), SR-TST-11 (1) и (2), SR-TST-14, TS-04, FR-VAL-13/FR-VAL-18) физически невыполнимы, и ни один тест этого не фиксирует.

Источник. Найдено в 08-tests.md, 05-validator.md.

D-36 [major] Набор барьеров и CI не заведён: нет tests/barriers/, invariants.yaml, scripts/barriers.sh, pipeline-файлов

Что сломано. find . -path ./src/backend/.venv -prune -o -name "*barrier*" -print -o -name invariants.yaml -print → только docs/task5/91-system/10-test-barriers.md. ls scripts/bootstrap-graphify.sh, deploy.sh. ls .gitea .github .gitlab-ci.ymlNo such file or directory для всех трёх.

Как воспроизвести. Команды выше.

Чем грозит. Из 16 требований SR-TST-01…16 не начаты 10 (01, 03, 04, 05, 06, 09, 12, 13, 15, 16), остальные закрыты частично; нет ни единой точки входа, ни мета-теста карты инвариантов, ни блокировки merge. Требования SR-RPC-01/07/08/10 сформулированы через «в CI проходит buf lint / buf breaking / make proto» — ни один шаг нигде не запускается, как и проверки grep ':latest' и grep плейсхолдеров из критериев деплоя (D-12).

Источник. Найдено в 08-tests.md, 03-contracts.md, 10-build-ops.md.

D-37 [major] Нет Dockerfile для planner и validator

Что сломано. find . -name 'Dockerfile*' (без node_modules и .venv) → ровно два файла: src/backend/gateway/Dockerfile и src/frontend/packages/api-client/Dockerfile.

Как воспроизвести. Команда выше.

Чем грозит. Образов geoscan-planner и geoscan-validator из SR-DEP-01 собрать нечем: контур в k3s и офлайн-пакет (D-13) неполны по составу процессов.

Источник. Найдено в 10-build-ops.md.

D-38 [major] Ни мигратора, ни Job миграций, ни манифеста Postgres

Что сломано. ls k3s/geoscan/30-postgres.yaml и 70-migrate-job.yaml отсутствуют; find src -iname '*migrat*' (без node_modules и .venv) → пусто: кода мигратора и каталога миграций нет.

Как воспроизвести. Команды выше.

Чем грозит. SR-DEP-07, SR-DEP-09, SR-DEP-10 не закрыты; хранение сценариев и планов (а с ним асинхронная модель D-02) реализовать не на чем. Решения об осознанной отсрочке в docs/task5/91-system/11-decisions.md нет, поэтому это несделанное, а не вынесенное из объёма.

Источник. Найдено в 10-build-ops.md.

D-39 [major] Секретный ключ запечён в образ gateway: ENV SERVICE_API_KEY=dev-local-key

Что сломано. src/backend/gateway/Dockerfile:27ENV SERVICE_API_KEY=dev-local-key в рантайм-слое.

Как воспроизвести. cat src/backend/gateway/Dockerfile → строка 27; Buildkit сам выдаёт предупреждение в выводе сборки: SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SERVICE_API_KEY") (line 27).

Чем грозит. Нарушены A-11, SR-CTX-09, SR-CFG-05, SR-DEP-02, SR-DEP-08, SEC-R-46: ключ остаётся в слое и в docker history, уедет в реестр вместе с образом и будет действовать в проде, если env не переопределят. Побочно отключён fail-fast SR-CFG-01/GW-01: обязательная переменная всегда задана дефолтом из образа, и строгая zod-схема (config.schema.ts:5,22-30) никогда не сработает.

Источник. Найдено в 02-system-trace.md, 06-gateway.md, 09-security-config.md, 10-build-ops.md.

D-40 [major] Рантайм-образ работает от root, тащит сборочный тулчейн, в поде нет securityContext

Что сломано. src/backend/gateway/Dockerfile (29 строк) — инструкции USER нет ни в одной стадии; Dockerfile:16 ставит typescript, @nestjs/cli, @types/node, а Dockerfile:22 копирует /build/gateway/node_modules в рантайм целиком без npm prune --omit=dev. В k3s/geoscan/40-deployment.yaml:23-42 нет securityContext.

Как воспроизвести. src/backend/gateway/Dockerfile целиком; критерий SR-DEP-02 — docker run --entrypoint id <образ> -u ≠ 0.

Чем грозит. SR-DEP-02 не выполнен, поверхность атаки расширена сборочным тулчейном в рантайме, образ избыточен по размеру для офлайн-поставки.

Источник. Найдено в 10-build-ops.md, 02-system-trace.md, 09-security-config.md.

D-41 [major] Нет заголовков безопасности, CSP и лимита частоты запросов

Что сломано. grep -rn 'helmet|rate-limit|APP_GUARD|AuthGuard|enableCors' src/backend --include='*.ts' → ничего; в src/backend/gateway/package.json:11-28 нет ни @fastify/helmet, ни @fastify/rate-limit. CSP (SEC-R-56…60) не задана ни helmet-ом, ни мета-тегом в src/frontend/apps/*/index.html.

Как воспроизвести. Команда и файлы выше.

Чем грозит. Не выполнены SEC-R-03, SEC-R-15, SEC-R-19, SEC-R-24: публичная ручка расчёта (тяжёлая по CPU) не защищена от лавины запросов, ответы уходят без базовых заголовков защиты.

Источник. Найдено в 09-security-config.md.

D-42 [major] Нет .dockerignore — COPY копирует каталог целиком

Что сломано. find . -name '.dockerignore' (без node_modules) → пусто, при этом src/backend/gateway/Dockerfile:5 выполняет COPY src/backend/shared/ ..

Как воспроизвести. Команда и строка выше.

Чем грозит. Любые локальные .env, node_modules, dist и артефакты из shared попадут в слой образа. SEC-R-46 требует .dockerignore с .env, .env.*, .git, node_modules, **/dist.

Источник. Найдено в 09-security-config.md.

D-43 [major] Нет npm-лок-файлов, сборка образов использует npm install вместо npm ci

Что сломано. 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,14npm install, :16 — доустановка typescript @nestjs/cli @types/node --no-save. Зависимости объявлены плавающими мажорами (@nestjs/* ^11, fastify ^5, typescript ^5.8.3, zod ^3.25.76).

Как воспроизвести. Команда и строки выше.

Чем грозит. Две сборки одного коммита дают разные деревья зависимостей при правиле «тег образа = 8-символьный sha коммита» (SR-CFG-08): тег перестаёт соответствовать содержимому, ломаются воспроизводимость и NFR-16, офлайн-сборка из SR-DEP-13 (D-13) невозможна, растёт поверхность supply-chain.

Источник. Найдено в 10-build-ops.md, 09-security-config.md, 06-gateway.md.

D-44 [major] Статика фронтенда не попадает в образ gateway и не раздаётся

Что сломано. src/backend/gateway/Dockerfile:18-29 — рантайм-стадия копирует только package.json, node_modules, dist, /shared и docs/task5/10-hardware; собранного фронтенда нет. Пакет @fastify/static объявлен (gateway/package.json:13), STATIC_ROOT разобран (config/config.schema.ts:10, геттер config/config.service.ts:32), но grep -rn 'useStaticAssets|fastify/static' gateway/src → 0: в gateway/src/main.ts (50 строк) только Swagger и app.listen.

Как воспроизвести. Файлы и команды выше.

Чем грозит. SR-DEP-03 не выполнен: единственная публичная точка входа отдаёт только API, SPA-fallback отсутствует — на https://geoscan.ff интерфейса не будет. По составу зависимостей требование выглядит закрытым, что маскирует дефект.

Источник. Найдено в 10-build-ops.md, 06-gateway.md.

D-45 [major] readinessProbe указывает на /healthz, а /readyz всегда отвечает 200 и не той формы

Что сломано. k3s/geoscan/40-deployment.yaml:32-39 — и readinessProbe, и livenessProbe бьют в /healthz (SR-DEP-06 требует /readyz). gateway/src/ops/ops.controller.ts:24-29 безусловно возвращает readyzBody(true) с комментарием «Skeleton: always ready until DB and planner checks land». Сама форма тела тоже не по контракту: shared/src/ops-http-contract.ts:36-38 отдаёт {ready:true}, а SR-OBS-01 и таблица A-02 требуют {"status":"ok"} / 503 {"status":"error","message":…}.

Как воспроизвести. Файлы и строки выше.

Чем грозит. Под попадёт в Endpoints Service до готовности зависимостей, rollout status позеленеет на неработающем контуре, трафик пойдёт на неготовый процесс — нарушены SR-OBS-01, SR-OBS-02, SR-DEP-06; внешние проверки, написанные по спецификации, не найдут поля status.

Источник. Найдено в 02-system-trace.md, 06-gateway.md, 10-build-ops.md.

D-46 [major] GET /api/fleet отдаёт пять моделей вместо трёх; ?include_reference, конверсия единиц и Cache-Control не реализованы

Что сломано. gateway/src/fleet/fleet.controller.ts:33 — параметр помечен как неиспользуемый (_includeReference), фильтрации reference_only нет; fleet.catalog.ts:22-38 отдаёт YAML как есть, без перевода минут и процентов в секунды и доли; Cache-Control не выставляется (только ETag, :43).

Как воспроизвести. python3 -c "yaml.safe_load(open('docs/task5/10-hardware/fleet.yaml'))" → 5 записей uav, из них geoscan_401_geodesy и geoscan_701 с reference_only: true (fleet.yaml:150,169).

Чем грозит. SR-API-19 требует ровно три модели по умолчанию и reference_only только при ?include_reference=true, а конверсию единиц — в загрузчике гейтвея (92-code/06 §6.4 п.3). В выборе парка на UI появятся борта, которых по ТЗ в парке нет (401 и 701 — по ним TODO.md уже фиксировал ошибку макетов), а потребитель получит минуты и проценты там, где ждёт секунды и доли.

Источник. Найдено в 02-system-trace.md, 06-gateway.md, 03-contracts.md.

D-47 [major] ETag/304 в /api/fleet не работает: @HttpCode(200) перезатирает reply.status(304)

Что сломано. src/backend/gateway/src/fleet/fleet.controller.ts:30-41 и :50-60 — обработчик помечен @HttpCode(HttpStatus.OK) и использует @Res({passthrough:true}); Nest в режиме passthrough после возврата из обработчика сам вызывает reply.status(<код из метаданных>).send(result).

Как воспроизвести. curl -H 'If-None-Match: <etag>' /api/fleet → ожидается 304, вернётся 200 с пустым телом (прогоном не проверено — Node в среде отсутствует; вывод из чтения кода и семантики passthrough).

Чем грозит. Критерий SR-API-19 «повторный запрос с If-None-Match → 304» не закрыт, а клиент упадёт на разборе пустого тела как JSON (см. D-31).

Источник. Найдено в 06-gateway.md.

D-48 [major] x-request-id отсутствует на ответах вне пайплайна маршрута и не валидируется по формату

Что сломано. gateway/src/common/interceptors/request-id.interceptor.ts:15-29 — глобальный интерцептор Nest выполняется только для сматченных маршрутов, поэтому 404 на неизвестный /api/... и ошибки парсера тела Fastify (413 через FST_ERR_CTP_BODY_TOO_LARGE, domain-exception.filter.ts:46-51) уходят без заголовка. Дополнительно :20-24 принимает любую непустую строку вместо проверки по ^[A-Za-z0-9_.:-]{8,128}$.

Как воспроизвести. Файлы и строки выше; правильное место — onRequest-хук Fastify, а не NestInterceptor.

Чем грозит. SR-API-02 требует x-request-id в каждом ответе, включая ошибочный — трассировка инцидента рвётся именно на ошибках; SR-OBS-08 нарушен: клиент может навязать произвольный идентификатор трассы.

Источник. Найдено в 06-gateway.md, 02-system-trace.md.

D-49 [major] make proto / make proto-py невыполнимы на чистом клоне — grpcio-tools нигде не объявлен

Что сломано. Makefile:4-8 (src/backend) зовёт python -m grpc_tools.protoc, но grpcio-tools отсутствует во всех pyproject.toml, а каталог proto-gen не входит в [tool.uv.workspace] members (src/backend/pyproject.toml:4["shared-py", "planner", "validator"]), поэтому его зависимости grpcio/protobuf (proto-gen/pyproject.toml:11-14) не попадают в лок (grep -n 'grpcio' src/backend/uv.lock → пусто). Плюс command -v python → exit 1 (есть только python3), а constraints.txt с пинами grpcio/grpcio-tools/protobuf, обязательный по 92-code/06 §5.2 п.2, отсутствует.

Как воспроизвести. cd src/backend && uv run python -c 'import grpc_tools'ModuleNotFoundError: No module named 'grpc_tools'. Каталог proto-gen/src/geoscan/ содержит только __init__.py — генерация не выполнялась ни разу.

Чем грозит. SR-RPC-10 («чистый клон + make proto даёт собираемый проект без ручных шагов») не выполнен; gRPC-стабы Python невоспроизводимы, uv lock --check проходит только потому, что gRPC-обвязки в локе нет вовсе. Без стабов planner и validator к гейтвею не подключить.

Источник. Найдено в 02-system-trace.md, 03-contracts.md, 10-build-ops.md.

D-50 [major] common.proto пуст; planner.proto и validator.proto его не импортируют

Что сломано. src/backend/proto/geoscan/common.proto:1-5 — весь файл: syntax, package geoscan.common.v1, message Empty {}. grep -n import proto/geoscan/*.proto → пусто; grep -c "enum|oneof" proto/geoscan/*.proto → 0.

Как воспроизвести. Команды выше.

Чем грозит. Нет общих Geometry/Ring/Position/Surface, SolverStatus, ExportFormat, ViolationKind, UnassignedReason, Violation, Warning, Unassigned. Критерий SR-RPC-02 («геометрические типы используются и planner, и validator, своих копий домены не заводят») невыполним, а фактические копии уже заведены в Python и уже разошлись (D-01).

Источник. Найдено в 03-contracts.md.

D-51 [major] Схема сценария расходится с фикстурами, scenario.py и 40-formats/03 по трём полям

Что сломано. schema/scenario.schema.json требует expected отсутствующим, turnaround_time_s и energy_reserve_frac, тогда как фикстуры и модель используют expected, turnaround_time_min, energy_reserve.

Как воспроизвести. cd src/backend && uv run --with jsonschema python -c "Draft202012Validator(schema/scenario.schema.json) …"s01-simple.json errors: 5, s02-nfz.json: 5, s03-multi-sites.json: 7; ошибки: '/' → Additional properties are not allowed ('expected'); /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, shared-py/geoscan_contracts/scenario.py:32,139, docs/task5/40-formats/03-our-mission-schema.md:28,80.

Чем грозит. Ни один сценарий репозитория не проходит собственную схему: включение SR-API-05 (D-16) отвергнет все три файла, а внешний потребитель, написавший сценарий по схеме, получит отказ от pydantic-модели.

Источник. Найдено в 03-contracts.md.

D-52 [major] Схема плана расходится с канонической pydantic-моделью плана

Что сломано. plan.schema.json:92-99 требует integer для metrics.violations.*, а shared-py/geoscan_contracts/plan.py:42-51 намеренно хранит величины (float — «длина в метрах, нехватка в секундах»); plan.py:33 сериализует алиас lambda, схема ждёт lambda_frac (plan.schema.json:72); plan.py:39 добавляет stop_reason, которого схема при additionalProperties:false не допускает.

Как воспроизвести. Plan(...).model_dump_json(by_alias=True) против schema/plan.schema.json'/metrics/violations/nfz -> 12.5 is not of type integer' и '/solver -> Additional properties are not allowed (lambda, stop_reason were unexpected)'.

Чем грозит. Даже канонический (валидаторский) план не проходит собственную схему — контракт выдачи плана наружу (SR-API-01, 40-formats/03) не определён однозначно ни одним из двух артефактов.

Источник. Найдено в 03-contracts.md.

D-53 [major] Pin-тест опций загрузчика не соответствует критерию приёмки SR-RPC-09

Что сломано. tests/integration/test_loader_options.py:28-47 читает ровно два файла (gateway/package.json и shared/src/grpc/loader-options.ts) и сверяет подстроки.

Как воспроизвести. tests/integration/test_loader_options.py:28-47 против SR-RPC-09.

Чем грозит. Новый protoLoader.loadSync({...}) с инлайновыми опциями в произвольном файле тест не заметит, функциональной проверки (snake_case-поле, int64, пустой repeated) нет. Барьер обязан обходить все файлы репозитория «чтением исходников, не по списку»; сейчас риск замаскирован тем, что регистраций клиентов ноль (grep -rn createGrpcServiceClient gateway/src → пусто).

Источник. Найдено в 03-contracts.md.

D-54 [major] Таблица соответствия gRPC→HTTP продублирована внутри одного файла

Что сломано. src/backend/shared/src/grpc/grpc-to-http.ts: константа GRPC_TO_HTTP (строки 4-12) и независимый switch в функции grpcToHttp (строки 14-33) — две копии одного соответствия, обе экспортируются через shared/src/index.ts:3-7; у константы нет ни одного потребителя (grep -rn GRPC_TO_HTTP src/backend → только определение и реэкспорт).

Как воспроизвести. Команда и строки выше.

Чем грозит. SR-API-09 требует перевода кодов «таблицей A-06 в одном месте кода», SR-TST-05 — барьера «поиск по репозиторию не находит второй таблицы»; при правке одной копии вторая разъедется беззвучно и HTTP-код ответа станет зависеть от точки вызова. Тестов на shared нет.

Источник. Найдено в 02-system-trace.md, 03-contracts.md, 06-gateway.md.

D-55 [major] Введена рантайм-конфигурация базового адреса API вопреки SR-CFG-12

Что сломано. src/frontend/apps/shell/src/runtime-config.ts:11,29-40fetch('/config.json', {cache:'no-store'}) с подстановкой apiBaseUrl, вызывается из apps/shell/src/main.tsx:11; форма конфига — packages/state/src/runtime-config.ts:3-20, пример — apps/shell/public/config.json.example; packages/api-client/src/config.ts:51-58 читает переменную окружения в рантайме, а client.ts:32 пропускает абсолютные http(s)://-адреса.

Как воспроизвести. Файлы и строки выше; SR-CFG-12 — docs/task5/91-system/08-config-and-secrets.md:169.

Чем грозит. Прямое нарушение SR-CFG-12: открываются обращения на чужой origin, CORS становится обязательным вопреки SR-CTX-03. Дополнительно конфликт двух уровней требований не разрешён — docs/task5/92-code/02-frontend-architecture.md:515-530 (FE-10) рантайм-конфиг, наоборот, предписывает; решения в 11-decisions.md нет.

Источник. Найдено в 02-system-trace.md, 09-security-config.md.

D-56 [major] .env.example разошёлся с нормативным реестром переменных, теста синхронизации нет

Что сломано. Сверка src/backend/gateway/.env.example с реестром docs/task5/91-system/08-config-and-secrets.md §1: отсутствуют DATABASE_URL, AIRSPACE_GRPC_ADDR, PLAN_CONCURRENCY, PLAN_QUEUE_LIMIT, PLAN_DEFAULT_TIME_LIMIT_S, PLAN_SYNC_TIME_LIMIT_S, IDEMPOTENCY_TTL_H; лишние SERVICE_VERSION, BODY_LIMIT_BYTES, STATIC_ROOT, HARDWARE_CATALOG_DIR, GRPC_MAX_MESSAGE_BYTES. У planner и validator .env.example нет вовсе.

Как воспроизвести. Сверка скриптом (uv run python) файла src/backend/gateway/.env.example с §1 реестра; теста test_env_example (SR-CFG-02) не существует.

Чем грозит. Расхождение будет расти незаметно, а выкат по .env.example даст процесс без DATABASE_URL и лимитов очереди.

Источник. Найдено в 09-security-config.md.

D-57 [major] Секреты не заведены в creds-store, нет sync-secrets.sh и check-secrets.sh

Что сломано. list_keys("project/geoscan") возвращает единственный ключ CLAUDE_CODE_OAUTH_TOKEN_NUBBLE — ни SERVICE_API_KEY, ни POSTGRES_PASSWORD там нет. ls scripts/ → только bootstrap-graphify.sh и deploy.sh. В k3s/geoscan/40-deployment.yaml:29-31 блок env закомментирован.

Как воспроизвести. Команды выше.

Чем грозит. Раздел SR-CFG-05…07 существует только на бумаге: при первом выкате секрет окажется «в чьей-то оболочке» (SEC-R-48), а Secret geoscan-secrets из SR-DEP-08 взять неоткуда.

Источник. Найдено в 09-security-config.md.

D-58 [major] Тесты валидатора не покрыты барьером независимости (TR-11)

Что сломано. Ни import-linter (root package validator резолвится в validator/validator), ни AST-скан (validator/tests/test_independence.py:13PACKAGE_ROOT = parents[1]/"validator") не смотрят на validator/tests/**.

Как воспроизвести. Копия дерева в /tmp/blint, printf 'import planner\n' >> /tmp/blint/validator/tests/fixtures.py, затем uv run --with import-linter lint-imports --config .importlinterContracts: 4 kept, 0 broken и uv run pytest tests/test_independence.py -q56 passed.

Чем грозит. Фикстуры планов — самое вероятное место заимствования у планировщика — не защищены; независимость валидатора (требование docs/task5/92-code/08-testing.md:564) может быть нарушена незаметно, а вместе с ней обесценится весь метод «валидатор раньше продукта».

Источник. Найдено в 05-validator.md.

D-59 [major] AST-скан ловит 3 обхода из 6, а его самопроверка тавтологична

Что сломано. validator/tests/test_independence.py ловит статические импорты (:29) и вызовы по имени import_module/__import__ (:41), но не ловит exec("import planner"), манипуляции sys.path и вызов через __builtins__"__import__" ("planner") (узел Subscript). Тест test_scan_catches_injected_bypass (:56-68) проверяет один путь и повторяет логику детекции inline вместо вызова рабочей функции скана.

Как воспроизвести. Копия /tmp/bypass2 с тремя указанными строками, добавленными в validator/validator/metrics.pyuv run pytest /tmp/bypass2/tests/test_independence.py -q56 passed.

Чем грозит. SR-TST-08 требует внедрения шести обходов (importlib.import_module, __import__, exec, sys.path, импорт внутри функции, try/except ImportError) и падения барьера на каждом — реализован один. Тест останется зелёным, даже если скан удалить целиком; с учётом D-17 это единственный живой слой барьера.

Источник. Найдено в 05-validator.md, 08-tests.md.


Minor

D-60 [minor] Экспорт принимает сценарий и не использует его — вспомогательные слои не выгружаются

Что сломано. planner/planner/export/geojson.py:8 и export/kml.py:8 принимают параметр scenario и ни разу к нему не обращаются; в выгрузке только LineString этапов полёта.

Как воспроизвести. Файлы и строки выше.

Чем грозит. FR-EXP-10 (область съёмки, БПЗ, граница ВП, площадки, полосы захвата) и FR-EXP-07 (точки старта и посадки) не выполнены — выгрузку нельзя показать как самодостаточный документ.

Источник. Найдено в 01-business-trace.md, 04-planner-core.md.

D-61 [minor] GeoJSON-выгрузка не соответствует RFC 7946

Что сломано. export/geojson.py:31-35 кладёт properties на уровень FeatureCollection, чего RFC 7946 не предусматривает; высота выброшена из координат (geojson.py:27 — только [lon, lat]); точность координат не ограничена семью знаками, в отличие от KML (export/common.py:13-14).

Как воспроизвести. Файлы и строки выше.

Чем грозит. Нарушены FR-EXP-03, FR-EXP-05, FR-EXP-11: сторонние ГИС проигнорируют properties, а высота полёта в GeoJSON теряется.

Источник. Найдено в 01-business-trace.md.

D-62 [minor] rng и seed в планировщике — мёртвые параметры

Что сломано. pipeline.py:276 принимает rng: object | None и нигде его не использует; scenario.solver.seed только копируется в вывод (pipeline.py:490).

Как воспроизвести. Строки выше.

Чем грозит. Детерминизм сейчас фактический (источников случайности нет), но незащищённый: регрессии «два прогона дают идентичные метрики» среди 150 тестов нет (NFR-15, FR-PLN-22).

Источник. Найдено в 01-business-trace.md.

D-63 [minor] 404 на неизвестный путь /api/... получает errorCode INTERNAL и теряет сообщение

Что сломано. gateway/src/common/filters/domain-exception.filter.ts:33-45: для любого HttpException без поля errorCode (в частности штатного NotFoundException Nest) подставляется ErrorCodes.INTERNAL с сообщением «Внутренняя ошибка сервиса».

Как воспроизвести. Строки выше.

Чем грозит. SR-API-01 требует 404 в форме контракта ошибок с осмысленным кодом; сейчас код вводит в заблуждение, а исходное сообщение теряется — диагностика опечатки в URL превращается в разбор «внутренней ошибки».

Источник. Найдено в 02-system-trace.md, 06-gateway.md.

D-64 [minor] Вторая независимая реализация экспорта KML живёт на фронтенде

Что сломано. src/frontend/packages/domain/src/exporters.ts:130 формирует KML-документ в браузере.

Как воспроизвести. Строка выше.

Чем грозит. Решение D-04 (11-decisions.md:58) и SR-CTX-05 делают экспорт исключительной ответственностью planner/export, а единственным каналом выдачи — GET /api/plans/{id}/export/{fmt}. Две реализации разойдутся, а SR-TST-11 (altitudeMode, round-trip) проверяет только бэкендовую.

Источник. Найдено в 02-system-trace.md.

D-65 [minor] Тесты оптики планировщика не сверяются со справочником ТТХ

Что сломано. planner/tests/test_geometry_optics.py:21-29 создаёт PayloadOptics литералами (sensor_width_mm=23.5, gsd_coefficient=1.9583e-4) вместо загрузки из docs/task5/10-hardware/payloads.yaml. У валидатора сделано правильно — validator/tests/test_optics.py:24 использует load_catalog().payload(...).

Как воспроизвести. Файлы и строки выше.

Чем грозит. SR-TST-10 (проверка 4) требует сверки именно с таблицами 30-domain; расхождение справочника и реализации planner тестом не поймается. Сами контрольные числа сейчас совпадают (см. раздел «Расхождения с контрольными числами»), то есть дефект — в способе проверки, а не в числах.

Источник. Найдено в 02-system-trace.md.

D-66 [minor] Дефект нумерации в самой спецификации: SR-DAT-13 задан дважды, SR-DAT-14 отсутствует

Что сломано. grep -nE '^\| SR-' docs/task5/91-system/05-data-model.md показывает SR-DAT-13 в строке 346 (схема airspace и таблица зон) и снова SR-DAT-13 в строке 360 (миграции схемы БД); идентификатора SR-DAT-14 в разделе нет.

Как воспроизвести. Команда выше.

Чем грозит. Трассировка по идентификатору неоднозначна: автоматический сбор требований даёт 136 уникальных ID при 137 строках требований.

Источник. Найдено в 02-system-trace.md.

D-67 [minor] GrpcChannelOptions.maxSendMessageLength игнорируется

Что сломано. shared/src/grpc/client-factory.ts:43const maxBytes = channelOpts?.maxReceiveMessageLength ?? 67_108_864; подставляется в обе опции канала (:10-13); объявленное в интерфейсе (:7) поле maxSendMessageLength не читается нигде.

Как воспроизвести. Строки выше.

Чем грозит. Вызывающий, передавший разные лимиты приёма и передачи, молча получит один — ровно тот класс «работает, но не то» ошибок, ради которого написан SR-RPC-15.

Источник. Найдено в 03-contracts.md.

D-68 [minor] schema/units.json без потребителя и в расхождении с units.py

Что сломано. grep -rn "units.json" src/ scripts/ → единственное совпадение: комментарий shared-py/geoscan_contracts/units.py:20. Списки разошлись: units.py:21-41 содержит _um, _a, _w, _mah, _v, которых нет в schema/units.json:4-19; исключения (seq, seed, photos, nfz, airspace, …) объявлены только в JSON (:20-45).

Как воспроизвести. Команда выше.

Чем грозит. Проверка SR-RPC-07 («числовое поле без разрешённого суффикса единицы роняет сборку», scripts/check-units.py из 92-code/06 §5.4) не существует, и соглашение об именах единиц уже поехало в двух местах.

Источник. Найдено в 03-contracts.md.

D-69 [minor] turnaround_min заполняется временем зарядки батареи

Что сломано. shared-py/geoscan_contracts/catalog.py:326turnaround_min=0.0 if charge_s is None else seconds_to_minutes(charge_s).

Как воспроизвести. uv run python -c "from geoscan_contracts.catalog import load_uav_models; …"geoscan_gemini turnaround_min = 105.0 — это battery.charge_time_min из fleet.yaml, а не оборот на площадке.

Чем грозит. По схеме входа оборот задаётся площадкой (launch_sites[].turnaround_time_min, 40-formats/03:28) и смягчается spare_batteries; подмена смысла под тем же именем даст завышенный оборот, как только turnaround_min начнут использовать (сейчас он не используется вовсе — D-21). Само число 105 мин справочнику соответствует.

Источник. Найдено в 03-contracts.md.

D-70 [minor] PayloadsCatalogDocument объявляет ключ payload, а в payloads.yaml ключ payloads

Что сломано. gateway/src/fleet/fleet.catalog.ts:14payload: Record<string, unknown> против docs/task5/10-hardware/payloads.yaml:36 (payloads:). Ср. shared-py/geoscan_contracts/catalog.py:285, где читается правильный ключ.

Как воспроизвести. Строки выше.

Чем грозит. Рантайм-эффекта сейчас нет (loadPayloadsCatalog возвращает документ целиком через as T, :31-38), но тип лжёт: первый же доступ к body.payload даст undefined.

Источник. Найдено в 03-contracts.md.

D-71 [minor] buf lint статически непроходим на текущих .proto

Что сломано. buf.yaml:4-8 включает lint.use: [DEFAULT], а файлы нарушают минимум три правила: PACKAGE_DIRECTORY_MATCH (geoscan/common.proto при пакете geoscan.common.v1; предупреждение уже записано в src/backend/README.md:29), SERVICE_SUFFIX (planner.proto:5, validator.proto:5), RPC_RESPONSE_STANDARD_NAME (planner.proto:6rpc Plan возвращает PlanEvent).

Как воспроизвести. Чтение buf.yaml и proto/geoscan/*.proto; прогнать нельзя — buf и protoc в окружении отсутствуют.

Чем грозит. Критерий SR-RPC-01 «в CI проходит buf lint» не будет выполнен в момент, когда CI появится (D-36).

Источник. Найдено в 03-contracts.md.

D-72 [minor] «Равное деление площади» делит диапазон по оси, а не площадь

Что сломано. baseline/equal_split.py:51-85 нарезает полосы по равным долям диапазона y в повёрнутой системе.

Как воспроизвести. cd src/backend && uv run python /tmp/more.py (L-образный полигон s02, веса 1:1) → band 0 area m2 540154 / band 1 area m2 373952 (перекос 1,44×).

Чем грозит. Бейзлайн и балансировка нагрузки перекошены на любых невыпуклых и непрямоугольных областях — сравнение «план против наивного» считается на неверном наивном.

Источник. Найдено в 04-planner-core.md.

D-73 [minor] Нумерация этапов полёта дублируется: phase_seq не инкрементируется после transit/turn

Что сломано. pipeline.py:179-190 добавляет этап transit/turn с seq=phase_seq, но увеличивает phase_seq только после этапа survey.

Как воспроизвести. В выгруженном KML s01 видно takeoff 0, transit 1, survey 1.

Чем грозит. Нарушены FR-EXP-04/08 («порядковый номер этапа»): по номеру нельзя однозначно сослаться на этап в выгрузке.

Источник. Найдено в 04-planner-core.md.

D-74 [minor] Длительность разворота — константа, не связанная с расстоянием, при этом расстояние идёт в total_distance_m

Что сломано. pipeline.py:179-189: duration_s = turn_s (8 с для мультиротора в режиме ЛЗП), а в total_distance_m добавляется реальная длина перехода transit_in.

Как воспроизвести. Строки выше.

Чем грозит. На переходах между полосами и заданиями время и дистанция плана рассогласованы; подразумеваемая скорость может быть любой, и валидатор увидит несогласованный профиль.

Источник. Найдено в 04-planner-core.md.

D-75 [minor] energy_used_frac считается от полного endurance, а не от бюджета с резервом

Что сломано. pipeline.py:248energy_used_frac = used / (endurance_max_min*60).

Как воспроизвести. Прогон s01 даёт 0.713 при бюджете 0,8 · endurance.

Чем грозит. Читающий отчёт видит «запас 29 %», хотя до отсечки по резерву остаётся 9 % — оператор примет решение по завышенному запасу.

Источник. Найдено в 04-planner-core.md.

D-76 [minor] Критерий оптимизации из сценария никогда не применяется

Что сломано. pipeline.py:350criterion = options.criterion or scenario.objective.criterion, при этом PlanOptions.criterion имеет непустой дефолт MAKESPAN, то есть левая часть всегда истинна.

Как воспроизвести. Строка выше и определение PlanOptions в schema.py.

Чем грозит. objective.criterion сценария молча игнорируется: запрос «минимум суммарного налёта» из файла сценария не действует (эффекта, впрочем, пока нет и по D-19).

Источник. Найдено в 04-planner-core.md.

D-77 [minor] Внутренние кольца (дыры) полигона не участвуют в генерации галсов и делении на полосы

Что сломано. coverage/transects.py:53 и baseline/equal_split.py:68 берут только poly.coordinates[0]; polygon_from_geom с дырами (geometry/shapes.py:20) в pipeline не используется.

Как воспроизвести. Строки выше.

Чем грозит. Полигон съёмки с вырезом будет покрыт галсами прямо по вырезу — лишний налёт и потенциальный заход в исключённую область.

Источник. Найдено в 04-planner-core.md.

D-78 [minor] Объявленные планом метрики почти не сверяются с пересчитанными

Что сломано. validator/validator/validate.py:75 прокидывает declared_metrics в отчёт, но сверка есть только для 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, пройдёт проверку — ослаблен заявленный в admission.py:4 принцип «плану, который врёт о себе, верить нельзя».

Источник. Найдено в 05-validator.md.

D-79 [minor] Покрытие может молча опереться на ширину полосы из плана

Что сломано. validator/validator/metrics.py:44-57: если своя оптика недоступна, width_m берётся из plan.geometry (footprint_across_m или swath_spacing_m), то есть из чисел планировщика. Путь регистрируется в Coverage.fallbacks и печатается в objects G6-01, но статус проверки остаётся pass (checks/g6_coverage.py:74).

Как воспроизвести. Строки выше.

Чем грозит. Независимость пересчёта в этом сценарии теряется без явного сигнала ревьюеру — валидатор подтвердит покрытие числами того, кого он проверяет.

Источник. Найдено в 05-validator.md.

D-80 [minor] Заход в зону и нарушение буфера складываются в один счётчик violations.nfz

Что сломано. checks/g1_airspace.py:56 (G1-01, ViolationKind.NFZ) и :171 (G1-04, тоже ViolationKind.NFZ) суммируются в admission.aggregate_violations.

Как воспроизвести. Прогон с инъекцией зоны: G1-01 даёт 3174.262 м, итоговая строка показывает nfz=4043.95 — разницу (кольцо буфера) по итоговой метрике не восстановить.

Чем грозит. В таблице 70-plan/01 колонка viol — первое, куда смотрит ревьюер; смешение захода в зону и нарушения буфера скрывает разницу в тяжести нарушения.

Источник. Найдено в 05-validator.md.

D-81 [minor] Состав группы G2 расходится со спецификацией: G2-04 отсутствует, число 24 добрано не описанной G2-05

Что сломано. docs/task5/92-code/05-python-core.md:218 требует в группе G2 четырёх проверок, включая запас над рельефом. В коде G2-04 вынесена в MISSING_CHECKS (validator/validator/checks/g2_altitude.py:20), а вместо неё добавлена G2-05 «Высота галса соответствует требуемому GSD» (:135), помеченная в докстринге как «проверка не из списка 24».

Как воспроизвести. Файлы и строки выше.

Чем грозит. Формальные 24 результата в выводе CLI сохраняются, но состав отличается от спецификации, а расхождение не зафиксировано решением в docs — приёмка по числу проверок обманчива.

Источник. Найдено в 05-validator.md.

D-82 [minor] Метрика geoscan_http_requests_total захардкожена в 0; ни одной метрики A-12

Что сломано. src/backend/gateway/src/ops/ops.controller.ts:44-52/metrics отдаёт две константные строки, включая geoscan_http_requests_total 0. geoscan_plan_duration_seconds, geoscan_plan_total{status}, geoscan_validator_violations_total{kind} и gRPC-метрики отсутствуют; prom-client не подключён (gateway/package.json:11-28).

Как воспроизвести. Строки выше.

Чем грозит. Постоянный ноль хуже отсутствия метрики: правило GeoscanGatewayErrors и дашборды будут построены на заведомо ложных данных (требование мониторинга из CLAUDE.md и SR-OBS-04).

Источник. Найдено в 06-gateway.md.

D-83 [minor] Ветка 413 в фильтре, вероятно, мертва — тело 30 МБ вернётся не в контракте ошибок

Что сломано. gateway/src/common/filters/domain-exception.filter.ts:46-51 и :66-72 ловят FST_ERR_CTP_BODY_TOO_LARGE, но эта ошибка возникает на стадии парсинга тела Fastify, до обработчика Nest, и уходит в error-handler Fastify, не доходя до глобального фильтра.

Как воспроизвести. Строки выше; прогоном не проверено — Node в среде отсутствует.

Чем грозит. Ответ будет {statusCode,code,error,message} вместо {errorCode:'PAYLOAD_TOO_LARGE', message:…} (SR-API-05), и клиент разберёт его как чужой формат.

Источник. Найдено в 06-gateway.md.

D-84 [minor] tsconfig.build.json теряет исключение src/generated

Что сломано. src/backend/gateway/tsconfig.build.json:3 переопределяет массив exclude целиком (["node_modules","dist","**/*.spec.ts"]), из-за чего исключение "src/generated" из tsconfig.json:32 не действует.

Как воспроизвести. Файлы и строки выше; проверить прогоном нельзя — Node отсутствует.

Чем грозит. Сгенерированные proto-типы попадут в продакшн-сборку и будут проверяться строгим компилятором — лишний источник падений сборки (ср. D-10).

Источник. Найдено в 06-gateway.md.

D-85 [minor] Таймаут запроса не действует при переданном options.signal; отмены наружу нет

Что сломано. packages/api-client/src/client.ts:50-53: создаётся controller, но signal = options.signal ?? controller.signal, поэтому setTimeout(() => controller.abort()) прерывает неиспользуемый контроллер.

Как воспроизвести. Строки выше.

Чем грозит. Запрос вызывающего со своим signal висит до сетевого таймаута; контроллер наружу не отдаётся, отмена при размонтировании компонента (§5.1) не поддержана.

Источник. Найдено в 07-frontend.md.

D-86 [minor] Дефолт dataSource=mock против api в §6.2; config.json в поставке отсутствует

Что сломано. packages/state/src/runtime-config.ts:10,18 и apps/shell/public/config.json.example:3 задают mock, тогда как таблица §6.2 документа 92-code/02-frontend-architecture.md задаёт дефолт api. Самого config.json нет, фронт не контейнеризован, в k3s/geoscan/ нет ни статики, ни ConfigMap.

Как воспроизвести. Файлы и строки выше.

Чем грозит. Поставка из коробки работает на моках, а включить режим api на развёрнутом контуре нечем — демонстрация рискует пройти целиком на мок-данных незаметно для зрителя.

Источник. Найдено в 07-frontend.md, 09-security-config.md.

D-87 [minor] resolveApiBaseUrl читает process.env.VITE_GEOSCAN_API_BASE — мёртвая ветка в браузере и незарегистрированная переменная

Что сломано. packages/api-client/src/config.ts:10-20 обращается к globalThis.process.env.VITE_GEOSCAN_API_BASE. Vite подставляет import.meta.env.VITE_*, а process в браузерном бандле отсутствует. Переменной нет ни в реестре SR-CFG §1, ни в .env.example, ни в src/frontend/README.md.

Как воспроизвести. Строки выше.

Чем грозит. Ветка никогда не срабатывает, а как источник конфигурации это третий способ задать базу API (помимо config.json и дефолта /api) и притом build-time — нарушение A-11, SR-CFG-02 и духа SR-CFG-12. Та же строка ломает сборку образа (D-11).

Источник. Найдено в 07-frontend.md, 09-security-config.md.

D-88 [minor] Idempotency-Key генерируется на каждый вызов compute, а не на нажатие кнопки

Что сломано. packages/state/src/api-compute.ts:22 вызывает newIdempotencyKey() внутри каждого запуска расчёта; Idempotency-Replayed не читается.

Как воспроизвести. Строка выше.

Чем грозит. Повтор после сетевой ошибки пойдёт с новым ключом и заведёт второй расчёт — смысл SR-API-14 теряется; двойной клик по «Рассчитать» защищён только флагом loading (apps/shell/src/TopBar.tsx:85).

Источник. Найдено в 07-frontend.md.

D-89 [minor] Префикс /api зашит в пути ручек, склейка с базой держится на срезании суффикса

Что сломано. packages/api-client/src/client.ts:31-40: если база заканчивается на /api, а путь начинается с /api/, у базы срезаются 4 символа; ручки всегда зовут '/api/...' (endpoints/fleet.ts:4,8 и др.).

Как воспроизвести. Строки выше.

Чем грозит. При apiBaseUrl вида /gw получится /gw/api/fleet, при https://host/v1https://host/v1/api/fleet: конфигурируемость базы работает только при совпадении суффикса /api.

Источник. Найдено в 07-frontend.md.

D-90 [minor] Образ api-client без CMD/ENTRYPOINT и с правкой tsconfig через sed

Что сломано. packages/api-client/Dockerfile:12-15 копирует dist/ и openapi.snapshot.json в финальный образ, но ни CMD, ни ENTRYPOINT, ни сервера в нём нет; :7RUN sed -i 's|"../../tsconfig.base.json"|"/frontend-base/tsconfig.base.json"|' tsconfig.json.

Как воспроизвести. Файл и строки выше; у самих приложений (shell/planner/validator/viewer3d) Dockerfile отсутствует.

Чем грозит. Назначение артефакта не определено, в составе контура SR-DEP-01 ему места не назначено; сборка правит конфиг регуляркой и молча сломается при любом изменении относительного пути в tsconfig.json.

Источник. Найдено в 07-frontend.md, 10-build-ops.md.

D-91 [minor] Хуки доступа к стору заведены в двух пакетах с разной реализацией и без единого потребителя

Что сломано. packages/state/src/hooks.ts:4,44 (через useStore/селектор) и packages/ui/src/store-hooks.ts:10,14 (useActions возвращает store.getState()). Ни один экран не использует ни ту, ни другую версию (grep -rn "useMission|useActions" apps — пусто).

Как воспроизвести. Файлы и строки выше.

Чем грозит. packages/ui продолжает зависеть от @geoscan/domain вопреки границе «ui → tokens»: дефект D-08 из 92-code/02 не закрыт, а продублирован; две реализации разойдутся по семантике (см. также D-29).

Источник. Найдено в 07-frontend.md.

D-92 [minor] tools/verify-numbers.ts выдаёт себя за сверку контрольных чисел, но не содержит ассертов

Что сломано. src/frontend/tools/verify-numbers.ts:28-34 печатает «GSD @100 м : … (ожидается 1,96)», «footprint … (ожидается 117 × 78)», «By @60 % … (ожидается 47,0)», «H для GSD 3 см … (ожидается 153)» через console.log.

Как воспроизвести. grep -n "assert|process.exit|throw" src/frontend/tools/verify-numbers.ts → пусто.

Чем грозит. Скрипт завершается нулём при любых числах: npm run verify в CI будет зелёным даже при полностью сломанной геометрии фронтенда — единственная видимая сверка с 30-domain на фронте ничего не проверяет.

Источник. Найдено в 08-tests.md.

D-93 [minor] Round-trip выгрузки не сравнивает набор и порядок этапов полёта

Что сломано. Фикстура tests/integration/conftest.py:21 (sample_plan) содержит только два survey-этапа; хелпер survey_paths_from_plan (conftest.py:99) явно отбрасывает всё, что не survey, поэтому test_kml_roundtrip.py:40 и test_geojson_roundtrip.py:26 сравнивают только съёмочные линии.

Как воспроизвести. Файлы и строки выше.

Чем грозит. Требование SR-TST-11 (2) «множество этапов (takeoff, transit, survey, turn, return, landing) и их порядок совпадают» не проверяется: потеря взлёта, перелёта, разворотов или возврата при экспорте тестами не ловится.

Источник. Найдено в 08-tests.md.

D-94 [minor] Форматы выгрузки plan и waypoints — заглушки, не покрыты тестами

Что сломано. shared-py/geoscan_contracts/enums.py:96-100 объявляет ExportFormat = geojson | kml | plan | waypoints, но planner/planner/export/__init__.py:19 на plan и waypoints бросает NotImplementedError.

Как воспроизвести. Файлы и строки выше.

Чем грозит. SR-TST-11 требует round-trip «по всем четырём форматам в части, применимой к формату»; тестов на два оставшихся формата нет, в том числе нет теста, фиксирующего, что это осознанная заглушка — выбор формата в UI приведёт к 500.

Источник. Найдено в 08-tests.md.

D-95 [minor] Счётчик «150 passed» завышен параметризацией одного AST-скана

Что сломано. uv run pytest -q --collect-only | grep :: | sed 's/::.*//' | sort | uniq -cvalidator/tests/test_independence.py даёт 56 из 150 тестов: это два скана, параметризованных по 28 файлам валидатора.

Как воспроизвести. Команда выше.

Чем грозит. Содержательных проверок около 94; оценка готовности по числу тестов даёт полуторакратное завышение (при 0 % покрытия ядра планировщика — D-18).

Источник. Найдено в 08-tests.md.

D-96 [minor] Логгер не настроен, редакции секретов нет, LOG_LEVEL не используется

Что сломано. src/backend/gateway/src/main.ts:24bufferLogs: true, дальше вывод идёт через console.log (main.ts:47) и console.error (main.ts:52, config.schema.ts:28). LOG_LEVEL разобран (config.schema.ts:4) и отдаётся геттером (config.service.ts:8-10), но не потребляется нигде.

Как воспроизвести. Файлы и строки выше.

Чем грозит. SEC-R-43 (pino redact по путям + фильтр по значению) не выполнен: первый же console.log(config) выведет SERVICE_API_KEY (который к тому же зашит в образ — D-39).

Источник. Найдено в 09-security-config.md.

D-97 [minor] parseEnvConfig вызывается дважды, process.exit внутри схемы

Что сломано. src/backend/gateway/src/main.ts:15 и src/backend/gateway/src/config/config.module.ts:10 независимо вызывают parseEnvConfig() — два разных объекта конфигурации в одном процессе; process.exit(1) внутри config.schema.ts:29.

Как воспроизвести. Файлы и строки выше.

Чем грозит. Конфигурация может разойтись между модулями, а process.exit в схеме делает функцию непригодной для юнит-теста (тест убьёт процесс) — при том что SR-CFG-01 требует теста на fail-fast.

Источник. Найдено в 09-security-config.md.

D-98 [minor] HTTP_PORT не ограничен сверху 65535

Что сломано. src/backend/gateway/src/config/config.schema.ts:8z.coerce.number().int().positive().

Как воспроизвести. Строка выше.

Чем грозит. Критерий SR-CFG-01 требует диапазона 1…65535; HTTP_PORT=999999 пройдёт валидацию и упадёт позже, в listen, без внятного сообщения о том, какая переменная виновата.

Источник. Найдено в 09-security-config.md.

D-99 [minor] Имя переменной каталога справочников разное в TS и в Python

Что сломано. src/backend/gateway/.env.example:8 и config.schema.ts:11HARDWARE_CATALOG_DIR; src/backend/shared-py/geoscan_contracts/catalog.py:33HARDWARE_DIR_ENV = "GEOSCAN_HARDWARE_DIR" (читается в catalog.py:144).

Как воспроизвести. Файлы и строки выше.

Чем грозит. SR-CFG-02 прямо запрещает расхождение имени между составами: при выкате один и тот же каталог придётся задавать двумя переменными, и рассинхрон приведёт к разным справочникам у гейтвея и планировщика.

Источник. Найдено в 09-security-config.md.

D-100 [minor] Абсолютные localhost-дефолты REMOTE_* запекаются в бандл

Что сломано. src/frontend/apps/shell/vite.config.ts:28,33,38process.env.REMOTE_PLANNER ?? 'http://localhost:5174/remoteEntry.js' (и аналогично 5175, 5176). .env.example у фронтенда нет, выставить REMOTE_* негде: CI-конфигов и docker-compose.yml в репозитории нет (D-36, D-13), сборки фронта в gateway/Dockerfile тоже нет (D-44).

Как воспроизвести. Строки выше.

Чем грозит. Прод-бандл, собранный без этих env, будет тянуть remoteEntry.js с localhost и не загрузится на https://geoscan.ff; нарушены SR-DEP-03 (относительные пути от origin), SR-CFG-11, SEC-R-56 ('self') и формально NFR-11 (офлайн).

Источник. Найдено в 09-security-config.md, 10-build-ops.md.

D-101 [minor] Ресурсы пода не соответствуют профилям SR-NFR-13

Что сломано. k3s/geoscan/40-deployment.yaml:40-42 — единственный контейнер geoscan с requests {cpu: 50m, memory: 128Mi} и limits {cpu: "1", memory: 512Mi}.

Как воспроизвести. Строки выше против SR-NFR-13 (planner: requests 500m/1Gi, limits 2 CPU/2Gi; gateway: requests 100m/256Mi, limits 1 CPU/1Gi; validator — свой профиль).

Чем грозит. planner получит вчетверо меньше памяти, чем заложено, и числа NFR-01…NFR-06 на geoscan.ff не воспроизведутся.

Источник. Найдено в 10-build-ops.md.

D-102 [minor] src/backend/.coverage не покрыт .gitignore

Что сломано. В .gitignore есть .pytest_cache/ и .ruff_cache/, но нет .coverage.

Как воспроизвести. git status --porcelain?? src/backend/.coverage.

Чем грозит. Артефакт прогона легко попадёт в коммит и будет шуметь в диффах.

Источник. Найдено в 09-security-config.md.

D-103 [minor] Шрифты Inter и JetBrains Mono не завендорены (NFR-13)

Что сломано. src/frontend/packages/tokens/src/global.css:29-30 задаёт --font-ui: 'Inter', 'Inter var', system-ui… и --font-mono: 'JetBrains Mono', ui-monospace… при отсутствии @font-face и @import.

Как воспроизвести. find src/frontend -name '*.woff2' -o -name '*.woff' -o -name '*.ttf' (без node_modules) → ничего.

Чем грозит. Внешних запросов это не порождает (что хорошо для NFR-11), но NFR-13 требует именно вендоренные woff2: в офлайн-контуре начертания будут системным фолбэком и разойдутся с прогоном с сетью. Ограничение честно записано в src/frontend/README.md:160.

Источник. Найдено в 10-build-ops.md.