99 — Сводный список дефектов реализации¶
Источник — десять отчётов ревью 01-business-trace.md … 10-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.py→41 passed. Валидатор берёт числа прямо из справочника (validator/tests/test_optics.py:24—load_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 -q→150 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.json
→ pydantic_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 --help →
No module named planner.__main__; 'planner' is a package and cannot be directly executed.
uv run python -m planner.cli run … --seed 42 → unrecognized 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:44 —
return 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=server →
Error 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.yml → No 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-23 —
message 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-15 —
ValidateRequest { 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-imports → error: Failed to spawn: lint-imports;
uv run ruff --version и uv run mypy --version → то же. При ручной установке конфиг рабочий:
uv run --with import-linter lint-imports --config .importlinter → Contracts: 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-missing
→ planner/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,497 — coverage_fraction = transects_total / planned, где
planned считается из тех же транзектов (pipeline.py:434-436), то есть доля числа пройденных
галсов, а не покрытая площадь; pipeline.py:499-500 — turns_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:255 — clock += 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:492 — status='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-30 —
void 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.yml → No 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:27 — ENV 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,14 — npm 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-40 — fetch('/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:13 — PACKAGE_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 .importlinter → Contracts: 4 kept, 0 broken
и uv run pytest tests/test_independence.py -q → 56 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.py → uv run pytest /tmp/bypass2/tests/test_independence.py -q →
56 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:43 —
const 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:326 —
turnaround_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:14 — payload: 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:6 — rpc 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:248 — energy_used_frac = used / (endurance_max_min*60).
Как воспроизвести. Прогон s01 даёт 0.713 при бюджете 0,8 · endurance.
Чем грозит. Читающий отчёт видит «запас 29 %», хотя до отсечки по резерву остаётся 9 % — оператор примет решение по завышенному запасу.
Источник. Найдено в 04-planner-core.md.
D-76 [minor] Критерий оптимизации из сценария никогда не применяется¶
Что сломано. pipeline.py:350 — criterion = 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/v1 —
https://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, ни сервера в нём нет; :7 —
RUN 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 -c →
validator/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:24 — bufferLogs: 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:8 —
z.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:11 —
HARDWARE_CATALOG_DIR; src/backend/shared-py/geoscan_contracts/catalog.py:33 —
HARDWARE_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,38 —
process.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.