Фронт (pcp-tgu-ops-ui): - тест-страж utcTime.guard.test.ts: запрещает Date.parse / new Date(string) / toLocale* без timeZone вне utils/utcTime; тестовые фикстуры переведены на parseUtc. Бэкенд: - UTC-now: LocalDateTime.now() -> LocalDateTime.now(ZoneOffset.UTC) по всем сервисам (entity-дефолты, воркеры, use case'ы) и в тестовых фикстурах. - Конвертации к UTC (баги на не-UTC JVM, каждая с регресс-тестом в не-UTC поясе): - tgu: VisibilityPayloadParser, SyncSpacecraftFromNsiUseCase (Instant -> UTC LocalDateTime); - mission-planing: MissionPlaningService (OffsetDateTime -> UTC через toUtcLocalDateTime); - dynamic-plan: RequestServiceClient (OffsetDateTime -> UTC); - slots: SlotRepositoryImpl (getObject(LocalDateTime) вместо getTimestamp().toLocalDateTime()). - Контрактные сериализационные тесты формата на проводе (naive без зоны / Z): PlanResponse (tgu), RequestResponseDto (request, эталон), TguPlanning DTO (ui). - KDoc-инвариант «=UTC» на UI-facing DTO (tgu PlanResponse, ui-service DTO вкладки ТГУ). Документация: docs/standards/DATETIME_UTC.md (соглашение, таблица сервисов, под-задачи на миграцию к Z), docs/standards/CHECKLIST.md (пункт про UTC на API).
pcp-route-processing-service
Назначение сервиса
pcp-route-processing-service принимает route passport из Kafka, парсит входной JSON, нормализует GeoJSON geometry в WKT, сохраняет паспорт маршрута в БД и создаёт downstream events в outbox.
Публикация downstream events выполняется не из Kafka listener, а через scheduled outbox worker:
RoutePassportMessageListenerчитает input Kafka record.RoutePassportJsonParserServiceвалидирует и парсит payload.RoutePassportIngestionServiceв одной DB transaction сохраняетroute_passportsи создаётroute_passport_outbox.- Listener подтверждает offset только после успешного ingestion.
RoutePassportOutboxWorkerclaim'ит due outbox rows и публикует их черезRoutePassportKafkaPublisherService.
Kafka topics
Input topic:
pcp.request.survey-georeference.v1
DLQ topic:
pcp.request.survey-georeference-dlq.v1
Output topics:
pcp.route.in.v1- событие для mission-planning flow.pcp.route.georeference.v1- событие для request-service matching flow.
DB tables
route_passports- сохранённые паспорта маршрутов и source metadata входного Kafka record.route_passport_outbox- pending/published/failed downstream events для Kafka publication.
Horizontal scaling
Несколько pod'ов сервиса могут безопасно запускать outbox worker одновременно.
Outbox claim использует DB-level concurrency:
SELECT ... FOR UPDATE SKIP LOCKEDвыбирает duePENDINGrows.lockedUntilвыставляется в той же transaction, что и claim.- После commit другие instances не claim'ят row, пока
lockedUntilв будущем. - Если instance умер после claim, abandoned row снова станет доступна после истечения
lockedUntil. - In-memory locks, leader election и single-instance assumptions не используются.
Delivery semantics
Delivery semantics: at-least-once.
Exactly-once не обещается. Kafka transactions не используются. Downstream consumers должны быть идемпотентны к повторной доставке одного и того же события.
Error handling
Input Kafka flow:
- contract/parsing errors не подтверждают offset и обрабатываются configured retry/DLQ policy;
- после исчерпания retry record публикуется в
pcp.request.survey-georeference-dlq.v1; - временные ошибки ingestion retry'ятся bounded policy.
Output Kafka flow:
- downstream events сначала сохраняются в
route_passport_outbox; - output Kafka errors не теряются, а переводят row обратно в
PENDINGс bounded exponential backoff; - после исчерпания
maxAttemptsиз outbox row событие получает статусFAILED.
Observability
Логи содержат operational identifiers без полного payload:
- source topic, partition, offset, key и payload size для input ingestion;
routeId,routeNameFull,traceId,correlationId;- outbox id, event type, target topic, message key, attempts, status,
nextAttemptAt,lockedUntil,publishedAt; - exception class/message для failure path.
Доступные Micrometer counters:
route_passport_ingest_success_totalroute_passport_ingest_failure_totalroute_passport_outbox_published_totalroute_passport_outbox_retry_totalroute_passport_outbox_failed_total
Operational notes
Test controller is disabled by default and must not be exposed in production unless explicitly enabled with:
app:
route-processing:
test-controller:
enabled: true
Current business limitations
routeStatusсейчас остаётсяPROCESSED.- Сервис не делает request matching.
- Сервис не считает coverage.
- Сервис не вызывает request-service или mission-service по HTTP.