store.Create классифицирует 23505 по имени constraint: (namespace,device_id) →
ErrDeviceIDExists, иначе ErrAlreadyExists. Handler отдаёт разные 409-сообщения.
Помогает Terraform понять, какое поле конфликтует.
- handler CreateIoTDevice: пароль устройства хранится bcrypt-хэшем (hashPassword),
открытый пароль отдаётся один раз в 201.
- store.EnsureBridgeDevice: пароль бриджа тоже bcrypt-хэшем.
- emqx/emqx.conf: password_hash_algorithm name=bcrypt, salt_rounds=10 (подтверждено
по emqx/emqx: HOCON-ключ salt_rounds; Go bcrypt.DefaultCost=10).
- Добавлена зависимость golang.org/x/crypto v0.42.0.
iotDeviceUpdateRequest получил поле metadata; UpdateIoTDevice обновляет переданные
поля (enabled и/или metadata). Добавлен store.UpdateMetadata. Хотя бы одно поле
обязательно. Раньше metadata нельзя было изменить (только enabled).
- QueryTelemetry: ORDER BY ts DESC, id DESC (был без tiebreaker → нестабильная
пагинация при одинаковых ts).
- ListIoTDevices: limit дефолт 100, потолок 1000 (ошибка парсинга больше не даёт
безлимитный запрос).
- Логи 'batch dropped' → 'валидные строки — построчно' (после fallback батч НЕ
теряется целиком).
- iotDeviceResponse: добавлено telemetry_topic = «ns/telemetry/device» (явный
топик для Terraform; topic_prefix = «ns/» был неполным).
- metadata теперь без omitempty и всегда «{}» при пустом — убран drift
«{}» против null (ревью 2026-10-10, «Что стабилизировать для Terraform»).
- deviceToResponse: created_at теперь RFC3339 (был «2006-01-02 15:04:05 UTC»)
— единый формат с last_connected, без drift для Terraform.
- CreateIoTDevice: после INSERT перечитываем устройство из БД (GetByName),
чтобы в 201 отдавался реальный created_at (in-memory структура его не имеет).
- handler/devices.go: CreateIoTDevice отдаёт mqtt_password один раз в 201
(было deviceToResponse(device, "") — пароль терялся, устройство нельзя
подключить; блокер для Terraform). Заголовок поправлен (GET — без пароля).
- store/devices.go: Create пишет phase явно через phaseOf(enabled) — иначе
enabled=false лежал в БД как 'Active' (DEFAULT), расходясь с POST/GET.
GetIoTDevice больше не возвращает mqtt_password (deviceToResponse получает ).
Пароль остаётся доступен только один раз — при создании устройства.
Хэширование паролей в БД + перевод EMQX на алгоритм хэша (часть 1) требует
изменения emqx.conf и миграции — отдельным шагом.