- consumer.Run получил onReady: /ready теперь false при ошибке ReceiveMessage
(раньше флаг consumer'а выставлялся до старта и не отражал SQS).
- main.go: wg.Wait заменён на waitWithTimeout (shutdownGrace=15с) — зависшую
подсистему не ждём бесконечно.
- iotpg.Close идемпотентен (sync.Once) — повторный вызов не паникует.
Было: main выставлял ready.SetBridge(true) ПЕРЕД bridge.Run, поэтому «соединён,
но подписка не прошла» давал /ready=200 и молча терял телеметрию до реконнекта.
Теперь bridge сам управляет флагом (колбэк onSubscribed): true только после
успешного SUBSCRIBE, false при потере соединения/сбое подписки.
os.Exit обходит defer-ы: на fatal-пути и при ошибке HTTP-сервера iotStore.Close()
(финальный flushAll последнего батча телеметрии) не выполнялся. Логика вынесена в
run() int, main = os.Exit(run()); все выходы через return — defer-ы отрабатывают.
- Добавлен ResolveQueueURLWithRetry (10 попыток × 2с backoff): временная ошибка
GetQueueUrl больше не роняет bridge/consumer без ретрая.
- main.go: при ПОСТОЯННОЙ ошибке bridge/consumer процесс завершается с кодом ≠ 0
(fatal-флаг + cancel), чтобы платформа перезапустила контейнер — раньше процесс
оставался жив, а /health отдавал 200.
Не собирался Dockerfile/Makefile (только ./cmd/iot-service/) → в образ/деплой
не попадал; логика поглощена internal/service/consumer/. Был источником пустого
message_id (обход дедупа). message_id оставлен nullable — единственный пишущий
путь (bridge) всегда ставит UUID. LEGACY.md обновлён. Плюс исправлена неточность
в HISTORY про поведение без DLQ.
Раньше ts проставлялся now() в БД при INSERT → при батчинге и параллельных
записях порядок телеметрии (ORDER BY ts DESC) ломался. Теперь в ts идёт
ReceivedAt из envelope (серверное время приёма, ставит bridge); битый/пустой
received_at → zero → стор подставляет now(). Схема не меняется (используется
существующая колонка ts). Обновлены callers: consumer/processor.go и legacy
cmd/sqs-consumer.
Envelope получил MessageID (UUID); таблица iot_telemetry — столбец message_id +
UNIQUE(device_id,message_id); батч-INSERT ON CONFLICT DO NOTHING. Дубли при
at-least-once повторе из SQS больше не вставляются. Пустой message_id → NULL
(без ложного дедупа).
/health остаётся liveness (процесс жив); /ready возвращает 503, если bridge или
consumer не в строю (горутина упала/не поднялась). Добавлен api.Readiness с
atomic-флагами, NewRouter принимает *Readiness, main поднимает/опускает флаги
вокруг фоновых горутин.