Добавлен батч-резолв логинов владельцев пространств (IAuthStore.FindOwnerLoginsByTenantIdsAsync). Ответы аудита дополнены userName (владелец пространства либо логин попытки), ответы лимитов — ownerLogin; имя пространства остаётся вторичным.
HTTP-тесты аудита и лимитов проверяют changes вместо плоского detailJson; подставки реестра умеют FindNamesByIdsAsync. Добавлены тесты разбора деталей (новый формат, старый плоский, повреждённый JSON) и проверка ответа аудита с именем пользователя и изменениями.
Названия событий, акторов, параметров и значений вынесены в ресурсы ru.js (operator.event/actor/field/value) с заделом под языки. Таблица аудита показывает понятное название действия, сырой ключ вторично, изменения «было → стало» и сырой JSON под спойлером. В колонке пользователя основное — имя, id мелко под ним. Термин «тенант» в UI оператора заменён на «Пользователь».
Детали событий описываются списком изменений «параметр: было → стало» (AuditChangeDto, AuditDetails), а не произвольным JSON. Запись везде идёт через конструктор AuditDetails; разбор старого плоского формата сохранён. Операторские ручки аудита отдают AuditRecordViewDto с changes и именем пользователя (батч-резолв через ITenantRepository.FindNamesByIdsAsync). Детектор подозрительной активности читает логин через новый разбор.
CardsService (get/move/links/reminders) и CardMover бросают NotFoundException вместо null/CardResultDto(null,null)/Exists=false. CardMoveResultDto — только Error; эндпоинты без локальных 404-проверок.
tgStart ждёт старт QR до показа картинки (устранена гонка с 404). Вкладка Telegram опрашивает статус и обновляет QR по таймеру, не останавливаясь до авторизации. Статус: account берётся из живого статуса сервиса, connected = phase ready (авторизация), а не транспорт.
Сервис frontend: сборка SPA (Vite) и отдача через Caddy, /api → core:5080. В профиль observability добавлены Loki/Promtail/Grafana (в dev их не было). Dev-Caddyfile отдаёт SPA с Cache-Control no-cache, чтобы пересборка подхватывалась обновлением страницы.
CardsService (карточка/файл) и ContainersService бросают NotFoundException вместо возврата null; эндпоинты больше не проверяют null — 404 отдаёт общий обработчик. Тесты обновлены под новое поведение.
DealException/NotFoundException/Validation/Conflict/ServiceUnavailable с кодом ошибки, тексты в ErrorMessages.resx. Общий HTTP-обработчик (DealExceptionHandler) и маппинг в gRPC-интерцепторе: доменные ошибки → статус, прочие → обобщённый текст без стектрейса. Правила закреплены в код-стайле §10.
TenantContext, LocalTelegramGateway, DefaultPasswordHasher и
WTelegramSessionClient переведены на явные реализации; типы в тестах
приведены к ITenantContext.
Общая настройка DealTracingHosting (Deal.Grpc.Hosting + Deal.Api):
AspNetCore/Http/GrpcNetClient инструментация, OTLP-экспорт во внешний
коллектор, имя сервиса из env. Логи обогащаются TraceId/SpanId.
По код-стайлу §11 метод Create реализуется явно
(ISecretCipher ISecretCipherFactory.Create()), интерфейс вызывается
только по порту; тест фабрики типизирован интерфейсом.
TelegramIngressTestHost больше не мутирует процессную переменную
DEAL_SERVICE_TOKEN: токен задаётся in-memory конфигурацией хоста.
Три ingress-тест-класса шли параллельно и перетирали env друг другу.
Создание сервисов с порт-интерфейсом перенесено в IXxxFactory:
шифр секретов, хранилище лимитов, ИИ-классификатор, ИИ-инструменты,
файловое хранилище (Local/MinIO). Регистраторы и DiscoveryWorkerService
больше не создают реализации напрямую. Добавлен тест FileStorageFactory.
Хелпер Support/TestSettingsStore: словарь настроек, GetAsync/GetAllAsync/SetAsync/
RemoveAsync читают состояние на момент вызова (GetAllAsync — ленивый, ключ для
снапшот-сервисов). DI получает .Store, тесты сохраняют Preload/GetStoredJson.
Хелпер Support/TestTenantStore: реестр на списке с Create/UpdateStatus,
DI получает .Repository. Потребители (10 файлов), фейк удалён,
тесты 1340 зелёные.
Хелпер Support/TestFileStorage: словари объектов/типов + журнал удалений,
подставка читает состояние на момент вызова (позиция потока при Put — по
контракту порта). Потребители (9 файлов) перетипизированы на .Storage,
фейк удалён, тесты 1340 зелёные.
Хелпер Support/TestDiscoveryStore сгенерирован из фейка: состояние (tasks/
candidates/blacklist/log/monitored) сохранено, 29 методов порта читают состояние
на момент вызова, неожиданных методов нет. Потребители (8 файлов) перетипизированы
на .Store, фейк удалён, тесты 1340 зелёные.
Хелпер Support/TestTelegramStore: словари каталога/превью + NSubstitute-подставка,
все методы порта читают состояние на момент вызова (семантика sync/upsert/
мониторов/бэкфилла сохранена). Потребители (5 файлов) перетипизированы на
.Store, фейк удалён, тесты 1340 зелёные.
Хелпер Support/TestAiClassifier: вся поверхность фейка (FilterResult/ClassifyResult/
Throws-флаги/счётчики) сохранена, сервисы получают NSubstitute-подставку
(.Classifier), поведение читает состояние на момент вызова, счётчики через
When/Do. Потребители (5 файлов), фейк удалён, тесты 1340.
Хелпер Support/TestGlobalSettingsStore: словарь состояния + NSubstitute-подставка
(GetAsync через Returns, SetAsync через When/Do), DI получает .Store.
Потребители (4 файла) перетипизированы, фейк удалён, тесты 1340.