Files
IoT/HISTORY/2026-10-07-session-log.md
T
cairnr01probe 38602b7560 P0-8: идемпотентность телеметрии (dedup по message_id)
Envelope получил MessageID (UUID); таблица iot_telemetry — столбец message_id +
UNIQUE(device_id,message_id); батч-INSERT ON CONFLICT DO NOTHING. Дубли при
at-least-once повторе из SQS больше не вставляются. Пустой message_id → NULL
(без ложного дедупа).
2026-10-07 07:26:50 +03:00

5.0 KiB
Raw Blame History

Журнал сессии — 2026-10-07 (IoT: P0-доработки до production-ready)

Рабочая папка: /home/naeel/nubes/SQS_und_IoT/IoT Ветка: feature/replace-kafka-with-sqs

Основание: doc/thinking/remediation-plan-2026-10-05.md (на ветке master) — план по двум анализам готовности. Работа только по явной команде; после каждой правки — коммит.


Сделано

P0-1/P0-2 — tenant-граница + обязательная подпись JWT (5e1cd28)

  • internal/service/api/middleware/auth.go:
    • validateJWT теперь возвращает sub;
    • middleware сверяет sub с {namespace} из URL (mux.Vars) → 403 при несовпадении (закрыт горизонтальный доступ к чужим namespace);
    • вне AUTH_TEST_MODE подпись HS256 ОБЯЗАТЕЛЬНА: без JWT_HMAC_SECRET middleware отказывает (fail-closed, 500), а не проверяет «структурно».
  • internal/service/config/config.go: JWT_HMAC_SECRET добавлен в список обязательных (когда AUTH_TEST_MODE=false) — отказ на старте.

P1-4 — убраны неаутентифицированные /internal/mqtt/* (cd47258)

  • internal/service/api/router.go: удалены маршруты /internal/mqtt/auth|acl. EMQX ходит в PostgreSQL напрямую (backend=postgresql), HTTP-fallback был не нужен и висел на публичном порту без auth (перебор паролей/ACL).
  • Примечание: те же эндпоинты остаются в ЛЕГАСИ internal/api/ (не исполняется).

P0-3 (часть 2) — пароль устройства не отдаётся повторно (f480be3)

  • internal/service/api/handler/devices.go: GetIoTDevice больше не возвращает mqtt_password (передаём "" в deviceToResponse). Пароль — только при создании.
  • Часть 1 (хэш паролей в БД + перевод EMQX на password_hash_algorithm) НЕ сделана: требует правки emqx/emqx.conf и миграции существующих строк.

P0-7 — подтверждение из SQS только после записи в БД (c84cbae)

  • Проблема: InsertTelemetry лишь буферизует и возвращает nil до фактической записи; consumer удалял сообщение сразу после processTelemetry == nil → краш/flush-ошибка в окне = безвозвратная потеря телеметрии.
  • internal/storage/iotpg/iot_telemetry_store.go: добавлен Flush(ctx).
  • internal/service/consumer/consumer.go: копим receipt handles, вызываем store.Flush(ctx), и только при успехе — DeleteMessage. При ошибке flush сообщения НЕ удаляются (вернутся после visibility timeout).

P1-9 — /ready отражает состояние подсистем

  • internal/service/api/readiness.go — тип Readiness (atomic-флаги bridge/consumer).
  • internal/service/api/router.go — NewRouter принимает *Readiness; добавлен /ready (200 если обе подсистемы в строю, иначе 503). /health остался liveness-пробой.
  • cmd/iot-service/main.go — bridge/consumer поднимают/опускают свои флаги; если горутина завершилась (ошибка/падение), /ready вернёт 503.

P0-8 — идемпотентность телеметрии (dedup по message_id)

  • internal/service/bridge/handler.go: в TelemetryEnvelope добавлен MessageID (UUID на каждое MQTT-сообщение).
  • internal/service/consumer/processor.go: MessageID передаётся в InsertTelemetry.
  • internal/storage/iotpg/iot_telemetry_store.go: в таблицу добавлен столбец message_id + UNIQUE INDEX (device_id, message_id); батч-INSERT теперь ON CONFLICT DO NOTHING. Пустой message_id → NULL (NULL в unique-индексе не конфликтует → без ложного дедупа). При at-least-once повторе из SQS дубликат не вставляется.
  • Легаси cmd/sqs-consumer: вызов обновлён (""), файл помечен к удалению (P2-14).

Осталось (не сделано)

  • P0-3 часть 1 (хэш паролей + EMQX), P1-11/P1-12 (тесты/CI), P2-14 (вычистить легаси internal/api/, cmd/sqs-consumer).
  • Платформенные проблемы (wss ~150с, MSS/MTU) — вне кода.

Проверки

  • go build ./... и go vet ./internal/... — зелёные на каждом шаге.