Files
enduro-trails/docs/work-items/ET-011/07-infra-requirements.md
claude-bot 6fe2ecf12b
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Successful in 6s
CI / build (push) Has been skipped
architect(ET): auto-commit from architect run_id=64
2026-06-03 20:44:55 +00:00

18 KiB
Raw Blame History

type, work_item_id, title, version, status, created_at, authors
type work_item_id title version status created_at authors
infra-requirements ET-011 Инфраструктурные требования — ET-011: Скачивание трека из popup 1 approved 2026-06-03
agent:architect

Инфраструктурные требования — ET-011

1. Резюме

ET-011 — API-extension only. Добавляется один эндпоинт в существующий router /api/gps-tracks/* + правки UI-модуля gps_tracks.js. Инфраструктура не меняется:

  • 0 новых docker-сервисов;
  • 0 новых файлов БД;
  • 0 новых cron-записей;
  • 0 новых env / секретов / API-ключей;
  • 0 новых исходящих HTTPS-соединений;
  • 0 новых портов и nginx-правил.

Все изменения локализованы в:

  • src/api/gps_tracks/export.py (новый, ~130 строк)
  • src/api/gps_tracks/endpoint.py (+1 route, ~50 строк)
  • src/api/gps_tracks/config.py (+1 optional поле в Pydantic-модели)
  • src/api/main.py (или эквивалент — +1 аргумент при include_router)
  • src/web/gps_tracks.js (+обработчик + правка popup)
  • src/web/app.css (+стиль кнопки)
  • config/gps_sources.yaml (+per-source флаг download_allowed)
  • tests (3 новых файла + расширение существующих)

Эскалация: minor change (см. ADR-014 §«Классификация», ADR-015 §«Классификация»).

2. Контейнеры и сервисы

Аспект Требование
Новый сервис Нет
Изменения Dockerfile Нет
Изменения docker-compose.yml Нет
Перезапуск API после деплоя Нужен — docker compose up -d --no-deps app (≈ 5 сек простоя). Подхватывает новый route + обновлённые src/web/*.js/*.css/gps_tracks.js
Перезапуск gps-collector Не нужен — pipeline не затронут (collector использует тот же gps_sources.yaml, но игнорирует новое optional-поле download_allowed)

2.1 Зависимости между сервисами

Без изменений. Новый эндпоинт GET /api/gps-tracks/{id}/download обслуживается тем же контейнером app, читает ту же БД /app/data/gps_tracks.sqlite.

3. Сеть

Аспект Требование
Новые входящие порты Нет
Изменения nginx Нет (новый route попадает под существующий location /enduro/api/)
Новые исходящие соединения с mva154 Нет
CORS Без изменений; middleware уже отдаёт Access-Control-Allow-Origin: * для всего /api/

3.1 Egress trafик

Скачивание GPX — только в downstream браузер. Один типичный трек ≈ 800 КБ (5000 точек) или ≤ 8 МБ (50000 точек). Cap REQ-NF-02: максимум 200000 точек ⇒ ≤ 20 МБ на запрос.

Пиковая оценка: при 20 одновременных скачиваниях типичных треков — ≈ 16 МБ/сек egress; в норме 12 одновременно. Не блокирует канал test-сервера (uplink ≥ 100 Mbps по DuckDNS).

3.2 Rate-limit на эндпоинт

Не вводим в этой итерации (BRD §5 «out of scope»). Если в проде будет аномальный трафик — добавляем slowapi-middleware в отдельном DevOps-task'е (out of ET-011).

4. Хранилища данных

Аспект Требование
Новые БД Нет
Изменения схемы tracks / pipeline_runs Нет
Миграции Нет
Новые SELECT-запросы Один: SELECT … FROM tracks WHERE id = ? (использует PK-индекс, O(log n))
Новые INSERT/UPDATE Нет (эндпоинт read-only)
Backup Без изменений

4.1 Производительность БД

Запрос по PK — ~ 1 ms на test-сервере. Сборка GPX через xml.etree.ElementTree: 5000 точек ≈ 30 ms, 50000 точек ≈ 150 ms, 200000 точек (cap) ≈ 500 ms. Бюджет REQ-NF-01 = 300 ms p95 для 50k точек — соблюдается с запасом.

_wkb_to_coords (переиспользуется из mvt.py) — уже бенчмаркнут в ET-008: ≈ 1 ms на 1000 точек.

5. Конфигурация и секреты

Аспект Требование
Новые env-переменные Нет
Новые секреты / API-ключи Нет
Новые конфиг-файлы Нет; меняется только содержимое config/gps_sources.yaml (+optional поле)

5.1 Изменения config/gps_sources.yaml

Добавляется одно поле download_allowed: bool per-source. Финальные значения для ET-011 (см. ADR-015 §«Решение D»):

sources:
  - id: osm
    # ... existing fields unchanged
    download_allowed: true

  - id: enduro_russia
    # ... existing fields unchanged
    download_allowed: false

  - id: wikiloc
    # ... existing fields unchanged
    download_allowed: false

  - id: ttrails
    # ... existing fields unchanged
    download_allowed: false

Все остальные поля (enabled, license_adr, base_url, rate_limit_sec, user_agent, attribution, parser_module, source_priority, …) — без изменений.

5.2 Перечитывание конфига

gps_sources.yaml читается при старте контейнера app (один раз) — в момент create_gps_router(db_path, sources_config_path). Для изменения политики download_alloweddocker compose up -d --no-deps app (≈ 5 сек простоя).

6. Зависимости

Аспект Требование
Новые Python-пакеты (runtime) Нет (xml.etree.ElementTree, urllib.parse — stdlib Python 3.12)
Новые Python-пакеты (dev) lxml (для XSD-валидации в UT-03 / IT-07). Возможно уже присутствует через defusedxml; добавить в requirements-dev.txt если отсутствует. ~3 МБ
Новые JS-зависимости Нет (vanilla JS + MapLibre API уже доступен)
Системные библиотеки в Dockerfile Нет
Версия Python 3.12, без изменений

6.1 XSD-фикстура

Файл tests/fixtures/gpx-1.1/gpx.xsd (~30 КБ) — скачивается один раз разработчиком из http://www.topografix.com/GPX/1/1/gpx.xsd, коммитится в репо. Не зависит от runtime, не часть production-образа (на .dockerignore уровне tests/ уже исключён, если нет — проверить).

7. Сборка и деплой

7.1 Pipeline CI

Существующий Gitea Actions:

  • make lint (ruff + eslint) — должен пройти без замечаний по новому коду (export.py, правки endpoint.py, gps_tracks.js).
  • make test — должен включать новые тесты:
    • tests/api/test_gps_tracks_gpx_builder.py (UT-01..05)
    • tests/api/test_gps_tracks_filename.py (UT-04 cases)
    • tests/api/test_gps_tracks_download.py (IT-01..08)
    • tests/web/test_track_download.spec.ts (E2E-01..04)
  • make build — пересобирает образ (никаких изменений в Dockerfile; но новые тестовые фикстуры и gpx.xsd попадают в репо).

7.2 Деплой шаг-за-шагом

  1. git pull origin main на mva154.
  2. docker compose build (опционально; никаких изменений в Dockerfile/requirements не было).
  3. docker compose up -d --no-deps app — рестарт API (≈ 5 сек простоя) для подхвата:
    • нового эндпоинта /api/gps-tracks/{id}/download;
    • обновлённого src/web/gps_tracks.js (popup + handler);
    • обновлённого src/web/app.css (стили кнопки);
    • расширенного config/gps_sources.yaml.
  4. Smoke в UI:
    • Открыть https://openclaw.mva154.duckdns.org/enduro/
    • Включить «Публичные треки», тапнуть OSM-трек → видна кнопка «Скачать» → клик → файл <name>.gpx в загрузках.
    • Тапнуть EnduroRussia-трек → клик «Скачать» → toast «Источник запрещает скачивание…» с ссылкой на сайт источника.
  5. Smoke API:
    curl -I https://openclaw.mva154.duckdns.org/enduro/api/gps-tracks/<osm-track-id>/download
    # ожидаемо: HTTP 200, Content-Type: application/gpx+xml, Content-Disposition: attachment; filename*=UTF-8''…
    
    curl -I https://openclaw.mva154.duckdns.org/enduro/api/gps-tracks/99999999/download
    # ожидаемо: HTTP 404
    
  6. Зафиксировать результат в docs/work-items/ET-011/14-deploy-log.md.

7.3 Время простоя

API: ≤ 5 секунд на шаге 3 (стандартный рестарт контейнера). Pipeline: не задействован.

7.4 Rollback

Сценарий Действие Время
Откат всего ET-011 git revert <merge-commit> + docker compose up -d --no-deps app ≈ 2 мин
«Выключить» новый эндпоинт без отката кода Закомментировать @router.get("/{track_id}/download") или поставить download_allowed: false для всех источников в gps_sources.yaml + рестарт API ≈ 1 мин
Откат БД Не применимо (схема не менялась) n/a

8. Cron / scheduled jobs

Нет новых cron в ET-011. Существующий cron gps-collector (ET-008, Mon+Thu 03:00 UTC) — без изменений; ET-011 не затрагивает collection.

9. Ресурсы (CPU / RAM / диск)

9.1 API-контейнер

Метрика Изменение Комментарий
RAM idle без изменений загрузка gps_sources.yaml — < 10 КБ
RAM на один запрос /download +5 МБ на 50k точек, +20 МБ на cap 200k в пиковом сценарии 10 параллельных скачиваний по 200k = +200 МБ; в реальности 12 параллельно
CPU per запрос 100500 мс worker'а ниже ETC-008 MVT-сборки
Disk write 0 эндпоинт read-only
Disk read размер записи в tracks (geom ≈ 200 КБ для 50k точек) через PK-индекс

Никаких изменений cpus: / mem_limit: в docker-compose.yml.

9.2 gps-collector контейнер

Не задействован.

9.3 Диск

Аспект Изменение
data/gps_tracks.sqlite без изменений (read-only эндпоинт)
tests/fixtures/gpx-1.1/gpx.xsd +30 КБ в репо (не в production-образе)
Production-образ docker без изменений (tests/ исключены)

10. Наблюдаемость

Артефакт Состояние после ET-011
uvicorn access-log Новые строки 200 GET /api/gps-tracks/<id>/download (через стандартный middleware)
Структурный лог (stdout) Новая строка track_download id=<id> sources=<csv> size_bytes=<n> на каждое 200-скачивание (через logging.getLogger("uvicorn.access").info)
4xx/5xx Видны в access-log в обычном формате; 5xx — stderr с traceback
GET /api/gps-tracks/health Без изменений (download — read-only, не влияет на counters)
Метрики (Prometheus / OpenMetrics) Не вводим (REQ-NF-06 явно отказывается от метрик в этой итерации)

10.1 Алерты

Нет новых алертов. При появлении в логах систематических 500 — ручной разбор stack-trace.

10.2 Logrotate

Без изменений (uvicorn пишет в stdout, Docker logger справляется).

11. Безопасность

Vector Митигация
SQL-injection через track_id track_id: int = Path(..., ge=1) — FastAPI/Pydantic валидация, далее parameterized SQL
Path-traversal в имени файла на диске пользователя safe_filename() заменяет `/ \ : * ? " < >
XSS через tracks.name в GPX xml.etree.ElementTree экранирует текст и атрибуты автоматически; integration-тест IT-07 валидирует через XSD
XML-bomb / external entity в сгенерированном GPX N/A — мы только генерируем, не парсим. xml.etree.ElementTree (для сборки) не подвержен XXE
Утечка PII через скачанный GPX tracks.user есть только для OSM (ADR-009 разрешает по ODbL); для остальных — null в БД (ADR-010/012); попадает в <author> только если присутствует
Утечка proprietary metadata через <desc> / <name> Для OSM-источника — публичные данные; для не-OSM — <copyright> опускается (ADR-014 §G); если merged через ANY-rule (ADR-015 §B) — компромисс зафиксирован в ADR-015
Утечка лицензионно-защищённой геометрии License-guard (ADR-015) — 403 для не-разрешённых источников
DoS через скачивание трека 50000+ точек Cap REQ-NF-02 ⇒ 413 для > 200000 точек; rate-limit на API — out of scope
Чтение чужой БД через mounted volume Без изменений (контейнер запускается с user appuser, volume /app/data read-write только для приложения)

11.1 Лицензионные атаки (юридические риски)

Покрыты ADR-015 (default-deny whitelist). Любой источник без явного download_allowed: true — недоступен для скачивания. См. 10-tech-risks.md R-9.

12. Влияние на C4 / архитектурную документацию

12.1 Обновления docs/architecture/README.md

В разделе «GPS Tracks Pipeline (ET-008) → Клиентский слой публичных треков» добавить одну строку после описания GeoJSON-эндпоинта:

- скачивание одного трека через `GET /api/gps-tracks/{track_id}/download`
  (GPX 1.1) — разрешено только для источников с
  `download_allowed: true` в `config/gps_sources.yaml` (ET-011 / ADR-014 / ADR-015).

12.2 Обновления docs/architecture/adr/README.md

Добавить две строки в таблице индекса ADR:

# Решение Статус Дата Источник
ADR-014 GPX-download эндпоинт публичного трека: xml.etree.ElementTree-builder + fetch+Blob на клиенте accepted 2026-06-03 ET-011
ADR-015 Политика реэкспорта публичных треков: per-source download_allowed в gps_sources.yaml, default-deny accepted 2026-06-03 ET-011

12.3 C4 mmd-диаграммы

В проекте отсутствуют (см. ET-008 §12, ET-009 §12). ET-011 не вводит новых компонентов или контейнеров — обновление диаграмм не требуется.

13. Вывод

ET-011 — minimal-change на инфра-уровне:

  • 0 новых сервисов / 0 новых БД / 0 миграций / 0 новых cron / 0 новых env / 0 новых портов / 0 новых runtime-зависимостей;
  • Все изменения локализованы в src-коде, тестах, одной опциональной ячейке gps_sources.yaml;
  • Деплой = git pull + рестарт API;
  • Rollback = git revert или конфиг-флаг.

Эскалация: не требуется (arch:major-change не выставлен; см. ADR-014, ADR-015).