# Журнал сессии — 2026-10-10 (IoT: чистка легаси → ветка master → ревью → фиксы) **Рабочая папка:** /home/naeel/nubes/SQS_und_IoT/IoT **Ветка:** `master` (переименована из `feature/replace-kafka-with-sqs`) **Состояние на конец сессии:** 28 коммитов сверх `origin/master`, **не запушено** (пуш — только по команде). --- ## 1. Чистка легаси Цель владельца: «старое ГАВНО не всплывало». - 🗑 Удалён код-кластер старого IoT (не импортировался монолитом, не собирался): `cmd/iot-operator/`, `cmd/mqtt-bridge/`, `controllers/`, `api/v1alpha1/`, `internal/api/` (старый REST + UI-embeds admin/console). Коммит `f152f54`. - `go mod tidy` убрал k8s-зависимости (`k8s.io/*`, `controller-runtime`) — −57 строк `go.mod`, −614 `go.sum`. - 🗑 `bin/` со старыми бинарниками (operator/kafka-consumer/mqtt-bridge). - `iot-admin.html` (живой `/iot-admin`): секции Kafka/K8s Pods → «Очередь shared-SQS» + «Рантайм»; удалён CSS `.pod-*/.badge-*/.dot-*/.lag-bar*`. Коммит `42f0d6a`. - `.gitignore`: убраны сталые пути бинарников. - `LEGACY.md` обновлён (помечено `УДАЛЁН 2026-10-10`). Осталось на диске вне git: `iot-console.html` со **старыми доменами** `sless.kube5s.ru` / `iot.kube5s.ru` (см. §6, требует решения владельца — какой домен). ## 2. Ветка → master, коммит/пуш - SQS-service: `main` → `master`; IoT: `feature/replace-kafka-with-sqs` → `master` (старая `master` перезаписана, её 3 коммита влиты merge'ом, конфликт только в `.gitignore`). - Пуш в Gitea (`gitea.services.ngcloud.ru/Nail/{SQS-service,IoT}`): `origin/master` создан/обновлён. Force не использовался. ## 3. Правки кода по ревью Opus ### Блокеры Terraform (Этап 2) - `632e551` MQTT-пароль устройства отдаётся один раз в 201 + `phase` пишется в БД (Create). - `a6ef9f9` `created_at` → RFC3339 + перечитывание после Create. - `7e3828f` `telemetry_topic` + нормализация `metadata` (без drift `{}`/null). - `7ea4923` валидация `namespace/name/device_id` (`^[a-zA-Z0-9][a-zA-Z0-9-]*$`, запрет `_ + # /`). ### Потери данных (Этап 1) - `046db68` 🔴1 сериализация flush (`flushMu`) — Flush дожидается in-flight записи. - `e0cd1ca` 🔴2 построчный fallback (ядовитая строка не роняет весь батч). - `006dbfa` 🔴2 семантическая валидация envelope (пустые поля → DLQ). - `01d21f6` 🔴3 слив буфера bridge при shutdown + безопасное закрытие канала. - `241c98a` 🔴4 ретраи SQS до shutdown + таймаут на попытку. - `69ae2ff` 🔴6 не рвать автореконнект (ретрай подписки, без `Disconnect`). - `530ee7b` 🔴7 ретрай резолва DLQ + не терять битые при недоступной DLQ. - `47d9a04` 🔴5 ретрай GetQueueUrl на старте + выход ≠0 при сбое подсистемы. ## 4. PRO-ревью (моё) — находки и фиксы Разобрал работу предыдущей модели, нашёл 3 🔴 + 🟡/🟢: - `98e6def` **#1** `os.Exit` обходил `defer iotStore.Close()` → финальный flush терялся. Вынес логику в `run() int`, `main = os.Exit(run())`. - `6e4a844` **#2** «не подтверждать битое при недоступной DLQ» = ядовитое зацикливается. Добавлено чтение `ApproximateReceiveCount` (после `maxReceiveCount=5` — дроп). - `c1733c5` **#3** `/ready` = «горутина запущена», а не «подписан». Bridge сам управляет флагом через `onSubscribed`. ### Этап 4 (изоляция/JWT) - `ea569da` JWT: обязательный `exp`, 401 вместо 403, guard тест-режима, JSON-ошибки. - `2bea1de` `replaceDSNDatabase` (устранено смешение тенантов: ошибка для key=value DSN, корректный разбор `sslrootcert=/path`) + guard namespace (`safeNamespaceRe`, `ErrInvalidNamespace`). ### Этап 3 (жизненный цикл) - `75c30e5` readiness consumer по SQS (`onReady`), ограничение `wg.Wait` (`waitWithTimeout`, 15с), идемпотентный `Close` (`sync.Once`). ### Этап 5 (мелочи) - `f37f216` удалён мёртвый `mqtt_auth.go` + constant-time админ-токен. - `fa98020` `decodeJSON` (MaxBytesReader 64КиБ + DisallowUnknownFields) + таймауты сервера. - `d7cb850` `DeleteMessageBatch` (до 10 за вызов). - `1510efe` пагинация: tiebreaker `ts DESC, id DESC` + потолок `limit` + точный лог. - `11bb38e` `MaxReconnectInterval` 30с + Debug для health-проб/телеметрии. ### Terraform / безопасность - `eacc740` PATCH обновляет `metadata`. - `0a51d70` 409 различает конфликт `name` vs `device_id` (`ErrDeviceIDExists`). - `c1adb26` **P0-3** bcrypt-хэш паролей устройств и бриджа + EMQX `name=bcrypt, salt_rounds=10` (ключ `salt_rounds` подтверждён по исходникам emqx/emqx; Go `DefaultCost=10`). ### Тесты / доки - `46744ac` юнит-тесты: `validateIdentifier` (handler), `replaceDSNDatabase` + `safeNamespaceRe` (iotpg). - `1600339` деплой-дока: пароль отдаётся один раз, в БД bcrypt. ## 5. Повторное PRO-ревью — открытые находки - 🟠 **ReceivedAt — точность до секунды.** `bridge/handler.go` пишет `time.RFC3339`, `consumer/processor.go` парсит `time.RFC3339`; субсекунды теряются. Очередь standard (не FIFO) → два показания в одну секунду могут встать не в том порядке; tiebreaker `id DESC` не чинит (id = порядок SQS-доставки). **Фикс: RFC3339Nano на обеих сторонах.** - 🟡 Пустое тело (`msg.Body == ""`) подтверждается без DLQ — для консистентности тоже в DLQ. - 🟡 `processBackoff`-sleep внутри цикла батча задерживает подтверждение соседей → redelivery (спасают дедуп + фоновый flusher). Пре-существующий дизайн. - ⚠️ Валидация `namespace`/`exp` зависят от формата `sub`/токена платформы — не проверено. - ⚠️ `flushMu` — глобальная сериализация (осознанный компромисс 🔴1 vs throughput). - ⚠️ Гонки не прогнаны под `go test -race`. ## 6. Осталось (не сделано) **Хвосты доделаны 2026-10-11:** `18cdea7` RFC3339Nano; `250bbdf` пустое тело → DLQ; `49422ef` домены консоли (apiBase → origin, mqttBase → exqx…); `d195629` race-тест dispatcher. Осталось: - Проверить формат `sub` платформы (валидация namespace + JWT `exp`) — **проверка**, не код. - Тесты на `flushAll`/`Close` (требуют мока PG/SQS; `go test -race` прогнан, dispatcher покрыт). - Terraform-провайдер (следующая большая задача после IoT). - SQS-хвосты (`CreateQueue`→`QueueNameExists`, `ListQueues`, JWT по `sub`, лимит по байтам). ## Правила (напомню себе) - Коммитить после каждой правки; пуш — только по команде. - Документировать всё, включая ошибки (HISTORY/ + doc/thinking/). - Не задавать вопросов, ответ на которые есть в коде/доках. - «Рекомендация без замера — не рекомендация».