Восстановить docs/ как зеркало для агентов (ревью МР #11)
ci / build-test (pull_request) Successful in 2m56s
ci / build-test (push) Successful in 2m52s

Ревью rust: перенос в вики не должен удалять из репозитория то, что нужно
агенту для работы (бэклог, статус, планы, код-стайл, спеки). Дублирование
вики и репы разрешено и обязательно: вики — актуальные версии для людей,
docs/ — зеркало для контекста агентов. README разведён по ролям.
This commit is contained in:
Rustam Khalimov
2026-09-12 23:54:28 +03:00
parent 53f8f8214f
commit 195faf1b1f
36 changed files with 10373 additions and 1 deletions
@@ -0,0 +1,228 @@
# Ревью качества кода «Дейл» (2026-09-08)
> Исторический документ этапа 8 (ревью, 2026-09-08). Актуальное состояние — `docs/superpowers/STATUS.md` и `docs/technical/Техническая-документация-Дейл.md`.
Многоосевое ревью (корректность/читаемость/архитектура/безопасность/производительность) бэкенда и
фронтенда. Проводилось 5 ревьюерами по непересекающимся зонам (чтение; правок не вносилось), ключевые
находки перепроверены по коду. Проект НЕ git. Метки: **[Critical]/[Required]/[Nit]/[Optional]**
(Required = исправить до прода; Nit = желательно; Optional = задел).
## Сводка
| Зона | Объём | Critical | Required | Nit | Optional |
|---|---|---|---|---|---|
| Frontend (Vue3, JS) | 25 файлов / 11.3k LOC | 0 | 6 | 6 | 1 |
| Core-каркас (Api/Infrastructure/Contracts) | ~370 файлов | 0 | 9 | 7 | 5 |
| Модули Kanban/Pipeline/Projects | ~140 файлов | 0 | 10 | 5 | 2 |
| Модули Settings/Telegram/Tenants/Discovery | ~130 файлов | 0 | 7 | 8 | 3 |
| gRPC-сервисы (telegram/ai/ml) + proto | ~135 файлов | 0 | 8 | 8 | 4 |
| **Итого** | **~1100 файлов** | **0** | **40** | **34** | **15** |
Общий вердикт: **код высокого качества** — чистая port&adapter-архитектура, 1 тип=1 файл, тенант-
изоляция через схему на тенанта спроектирована сильно, SQL параметризован, XSS/секреты на фронте и в
сервисах чистые. Найдено 0 критических дыр класса «ключ наружу/доступ к чужому тенанту». Ниже — что
требует исправления и что стоит улучшить. Подробности по зонам — в рабочем журнале сессии (5 отчётов
субагентов с file:line); здесь — консолидированный список.
---
## A. Безопасность (приоритет 1)
1. **[Required] SSRF через baseUrl ИИ-провайдера.** `Deal.Infrastructure/Integrations/AiConnectionChecker.cs`
(проверка `ok:false/true`) + PATCH настроек разрешает тенанту задать произвольный `baseUrl` (в т.ч.
`http://127.0.0.1:...` — подтверждено acceptance-логом task-6). На не-local провайдере ключ API уходит
на указанный адрес → аутентифицированный тенант мультитенантного SaaS получает blind-сканер внутренней
сети/метаданных. Исправить: резолв DNS + запрет private/link-local/loopback при проверке и вызове
(или egress-фильтр); не принимать переопределение хоста для каталоговых провайдеров.
2. **[Required] Rate-limit и анти-брутфорс выключены по умолчанию.** `Deal.Api/Program.cs` (регистрация
лимитера), `RateLimitOptions` дефолт `Enabled=false` → без env в проде нет ни лимитов, ни
`LoginAttemptGuard`. compose.prod форсирует `true`, но дефолт кода опасен при запуске вне compose.
Исправить: стартовая проверка «Production ⇒ RateLimit:Enabled задан явно» (fail-closed).
3. **[Required] CORS fail-open при пустом allowlist.** `Program.cs` (AddCors): пустой
`Security:AllowedOrigins` = любой origin + `AllowCredentials` (задумано для dev). Исправить: в Production
пустой список = отказ на старте; «any origin» только в Development.
4. **[Required] Код инвайта пишется в audit_log сырым.** `JoinEndpoint.cs` — capability-токен в вечном
аудите операторов. Исправить: не логировать код (или его SHA-256).
5. **[Required] Пароль: минимум 4 символа.** `AuthEndpoints.cs`, `JoinEndpoint.cs`. Для публичного SaaS —
минимум 8–10 + проверка на границе; единая константа.
6. **[Required] Политика «ключ не перезаписывается маской» не реализована.** `SettingsService.cs`
(aiConfigs и tgKeys): PATCH со значением-маской (например `sk-1…90ab`, ≥8 симв., без `enc:`) зашифрует
маску и безвозвратно потеряет ключ. Комментарий «пустой/маска → не меняется» не подкреплён кодом.
Исправить: не шифровать значение, содержащее `…` (U+2026) либо пустое; тест на roundtrip.
7. **[Required] DDL прикладной ролью на старте и из tenant-ручки.** `TenantProvisioningService.cs`,
`FtsMaintenance.cs``CREATE SCHEMA/Migrate/INDEX` на каждом старте и `/api/admin/fts/rebuild`.
В проде это нарушение least privilege. Исправить: отдельные креды мигратора и runtime; fts-rebuild —
операторской ручкой.
8. **[Required] TenantId без инварианта формата.** `Deal.SharedKernel/Tenants/TenantId.cs` — значение идёт
в Search Path строки подключения и в DDL; `new TenantId(внешняя_строка)` = connection-string-инъекция.
Сейчас все потоки дают Guid, но тип не защищён. Исправить: конструктор от Guid / валидация 32 hex.
9. **[Required] gRPC-сервисы: нет серверных лимитов на входные данные.** AiServiceImpl, MlServiceImpl,
TelegramServiceImpl — контракты фиксируют лимиты («ядро обрежет»), но сервис их не enforcement:
платные LLM-вызовы на мегабайтных промптах, гигантские SQLite-транзакции. Исправить:
INVALID_ARGUMENT на границе + MaxReceiveMessageSize.
10. **[Required] mTLS по умолчанию выключен — тихая деградация до plaintext.** `MtlsOptions.cs`
отсутствие/опечатка env молча даёт plaintext+только service-token. Исправить: fail-closed для
Production (или warn-on-startup) как для session-ключа.
11. **[Required] Инвайт: не проверяется существование/статус тенанта.** `JoinService.cs` — активация по
«битому» инвайту даёт FK-500 или пользователя на несуществующем тенанте.
12. **[Required] AddUsageAsync не атомарно.** `ITenantLimitStore.cs` — read-modify-write теряет списания
при параллельных ИИ-вызовах. Исправить: `UPDATE ... SET Used=Used+@n`.
13. **[Required] Echo-маска: секрет ≤8 символов отдаётся как есть.** `SettingsService.Mask` — маскировать
всегда (кроме пустого).
## B. Корректность / потеря данных (приоритет 2)
14. **[Required] Потеря данных при параллельных мутациях JSON-массивов проектной карточки.**
`ProjectsService.cs` (add_comment/add_link/remove_link), `ProjectFilesService.cs`: комментарии/ссылки/
файлы дописываются «read → PATCH полной заменой массива» без версии/транзакции; double-click теряет
запись. Исправить: append одним SQL (`jsonb ||`/`array_append`) или optimistic concurrency по `updated_at`.
15. **[Required] Коллизия objectKey файла.** `ProjectFilesService.cs` — «проект/карточка/мс_имя»: две
загрузки в одну мс = перезапись объекта. Исправить: случайный суффикс / id записи в ключе.
16. **[Required] Дедуп-pump не атомарен.** `PipelineWorkerService.cs` — Exists→Claim→create без проверки
результата claim — два конкурентных прохода создадут две карточки. Исправить: повторный Exists/
проверка результата Claim перед созданием.
17. **[Required] Move из trash/archive на доску минует снятие спам-сигнала.** `CardsService.cs`
валидируется только цель; «spam +1» не снимается (unlearn только в restore). Исправить: запрет исхода
из archive/trash/taken в MoveLeadAsync (или симметричный unlearn).
18. **[Required] Параллельные пустые `catch { }` в модулях Telegram/Discovery** — сбои зеркала/превью/
backfill невидимы (ILogger в модулях не используется). Исправить: логировать.
19. **[Required] ChangePassword (фронт) шлёт захардкоженный oldPassword='admin'.** `store.js`,
`SettingsView.vue` — после смены пароля повторная смена невозможна, и пароль живёт в реактивном state.
Исправить: поле «текущий пароль», не хранить пароль в store.
20. **[Required] boot() роняет всё приложение одним сбоем** (фронт). `store.js`: параллельные get без
.catch — падение /api/rates (например) = toast «Сервер недоступен» + разлогин. Исправить:
необязательные секции в индивидуальные .catch; разлогин только при 401.
21. **[Required] applySettings затирает несохранённые промпты** (фронт). `store.js` — автосейв тумблера
применяет полный ответ и перезаписывает textarea промптов. Исправить: применять только запатченные ключи.
22. **[Required] Гонки устаревших ответов поиска** (фронт). `store.js` — старый ответ может перетереть
свежий/очищенный. Исправить: seq-токен/AbortController.
23. **[Required] DeleteExpiredSessionsAsync на каждое разрешение сессии.** `AuthService.cs`,
`OperatorAuthService.cs` — глобальный DELETE по public-таблицам в hot-path каждого запроса.
Исправить: фоновый цикл или «с вероятностью N%»/логин.
24. **[Required] ServiceTokenInterceptor проверяет токен только для unary RPC** — первый же
server-streaming RPC пройдёт без проверки; то же в access-логе. Исправить: все 4 handler'а.
25. **[Required] gRPC-логгер не логирует «прочие» исключения** (только OCE/RpcException) — 500-эквивалент
уходит мимо лога. Исправить: catch (Exception) → log + RpcException.
26. **[Required] Heartbeat/reconnect без таймаута** — зависший ConnectAsync последовательно блокирует
все тенанты и shutdown. Исправить: CancelAfter на попытку.
27. **[Required] QR: отмена RPC до первого URL не отменяет фоновую задачу** — «скрытая» авторизация.
Исправить: отменять саму задачу при отмене ожидания.
28. **[Required] TelegramBackfill fire-and-forget Task.Run из tenant-запроса без in-flight guard**
(параллельные полные перечитывания); фоновые задачи не отслеживаются хостом. Исправить: гейт операции
+ токен остановки хоста.
29. **[Required] int.Parse(apiId)** из пользовательской KV-настройки `TelegramEndpoints.cs`
FormatException маскируется под 400 «не подключён». Исправить: TryParse + понятная ошибка.
## C. Архитектура / дублирование (приоритет 3)
30. **[Required]** 9 независимых реализаций чтения настроек (GetAsync+JsonDocument.Parse+дефолт) в
Settings/IncomingRules/RatesService/Discovery*/DialogsService — расхождение семантики уже видно.
**+** ~8 копий KV-хелперов (ReadBool/ReadInt/ReadString/ReadStringList) и 3 копии LoadRatesAsync в
Kanban/Pipeline/Projects. Исправить: один публичный снапшот настроек в Settings или SharedKernel +
общий RatesCacheReader.
31. **[Required]** Обвязка gRPC-сервисов (ServiceTokenInterceptor/RpcCallLogging/MtlsOptions/MtlsCertificates/
Logging + Host) скопирована в 3 независимых sln. Исправить: общий проект `Deal.Grpc.Hosting`.
32. **[Required]** Большие файлы: PipelineWorkerService (914), KanbanStore (726), DiscoveryStore (632),
ProjectsService (576), ProjectsEndpoints (568), CardsService (475), Program.cs (695), LocalFieldsParser
(438), GrpcTelegramClient (447), TelegramIngressService (409); фронт: SettingsView.vue (1779),
DiscoveryView.vue (1243), store.js (2434). Исправить: декомпозиция (см. ниже).
33. **[Required] Фронт: MoveMenu вешает document-слушатель на каждую карточку** (сотни карточек → сотни
слушателей). Исправить: один глобальный обработчик + id открытого меню в store.
34. **[Required] Фронт: квадратичные пересчёты колонок.** `store.js` — filter+sort на каждую колонку/
счётчик при каждом ре-рендере. Исправить: один computed Map<colId, sorted[]>.
35. **[Nit]** Дублирование доменных констант между модулями (EmptyCommentDetail/JustNowLabel/MlSpamLabel/
DefaultChannelHue/PlannedStage-литералы) и расхождение предиката «активные правила» (Kanban vs
AiClassifyContextBuilder) — вынести в единые реестры.
36. **[Nit]** Middleware сессий (Session vs OperatorSession) и токен-генераторы (SessionTokens/
InviteCodeGenerator/TenantAdminService) дублируются — обобщить.
37. **[Nit]** Легаси-ссылки на строки Python-прототипа в XML-doc (L177191 и т.п.) — устаревают;
оставить «зачем/инвариант», убрать номера строк.
38. **[Nit]** Форматтеры времени и «знание» о контактах/типах файлов в 3–4 местах (фронт) — единый
модуль форматов и словари меток.
39. **[Nit]** `window.prompt` в renameBoard на фоне единого ConfirmDialog; дубликаты 86400000; ширины
колонок sm/md/lg в 3 местах — константы/единый RenameDialog.
## D. Мёртвый код (кандидаты на удаление)
- Фронт: `utils.js` fileTypeInfo/EXT_KINDS/KIND_LABELS (не импортируется); `store.js` — curName/fmtMoney
вне store, moveLead-мёртвая ветка, trashLead-пустой if, openDialog (не используется), checkReminders
(нигде не вызывается); опция «mock»-курсов — проверить, жив ли режим на бэкенде.
- Бэкенд: Kanban DemoLeadFactory недостижимый fallback PrimaryContact; DiscoverySearchErrorCounter —
singleton-счётчик без TTL/эвикции и с межтенантным ключом (переделать per-tenant или чистить).
## E. Что соответствует хорошим практикам (подтверждено)
- Тенант-изоляция сильная: схема на тенанта через Search Path, TenantDbContext запрещён вне tenant-запроса
(fail-fast), AsyncLocal сбрасывается в finally, gRPC-ингресс берёт tenant-id только из metadata, SSE
per-tenant.
- SQL параметризован везде (FromSqlInterpolated/ExecuteSqlInterpolated); массовые операции —
ExecuteUpdate/Delete; комментарии-батчи без N+1; AsNoTracking.
- Секреты не покидают систему: ключи шифруются (enc:+nonce‖ct‖tag), наружу маски; токены сессий — SHA-256
хэши; пароли Argon2id; куки httpOnly+SameSite=Lax; fail-closed service-token (с явным гардом
«пусто≠пусто»); path traversal защищён (SessionStore/ModelPool валидируют tenant-id как имя файла).
- Фронт: XSS-аудит чистый (v-html только через экранирующий renderSourceMessage со схемами http/tg),
токенов в localStorage нет (httpOnly-кука), все target=_blank с rel=noreferrer.
- Чистая архитектура port&adapter в модулях (нет EF/HTTP в Application), DTO-рекорды, DI-Registrar'ы,
направленные зависимости без циклов, константы-каталоги вместо магических строк.
## F. Рекомендуемый порядок исправлений
1. **Безопасность (A1–A13)** — до любого прода. Точечные правки + тесты.
2. **Потеря данных/корректность (B14–B29)** — гонки, дедуп, маски, boot/applySettings фронта.
3. **Архитектура (C30–C34)** — вынос общего grpc-hosting, снапшот настроек, декомпозиция больших файлов,
фронт: leadsByCol-компьютед и глобальный слушатель меню.
4. **Чистка мёртвого кода (D)** + реестры констант (C35–C39) — в рамках рефакторингов, не отдельно.
5. **Заделы (Optional)** — пагинация колонок, виртуализация списков, LRU для кэшей сессий WTelegram,
батчинг провижининга схем, MinIO tenant-префикс, per-request size-лимиты загрузок, single-flight
DiscoveryWorker.
---
## Статус исправлений (2026-09-08, после ревью)
Выполнено в ходе rework-захода (детали — `.superpowers/sdd/deal-stage8-quality-rework/progress.md` и
`docs/superpowers/STATUS.md`). Тесты: core **1135/1135**, telegram **118/118**, ai **52/52**, ml **38/38**,
фронт `npm run build` OK.
**A. Безопасность — закрыто (A1–A13):**
- A1 SSRF: `SettingsService` — baseUrl каталоговых облачных провайдеров не переопределяется (только
local/custom); `AiConnectionChecker` — запрет private/loopback/link-local адресов (в т.ч. 169.254.169.254).
- A2/A3: fail-closed в Production (RateLimit:Enabled обязателен, CORS-allowlist непустой, conn-string без
фолбэка) — стартовые проверки `Program.cs`.
- A4: код инвайта в аудите → SHA-256 `codeHash` (3 события, тесты обновлены).
- A5: пароль минимум 8 (единый `AuthService.MinNewPasswordLength`).
- A6: PATCH с маской ключа («…») больше не шифрует маску (терялся бы ключ); A13: короткие секреты
маскируются всегда (`MaskSecret`), apiId остаётся как есть (не секрет).
- A7: DDL (провижининг схем/миграции) — опциональная мигратор-строка `ConnectionStrings:DealMigrator`
(`ConnectionStringProvider.ForSchemaDdl`); dev/тесты — прежнее поведение.
- A8: `TenantId` — инвариант 32 hex (Guid N), фабрика FromGuid.
- A9: gRPC-сервисы — лимиты входных данных (INVALID_ARGUMENT) + MaxReceiveMessageSize=4MiB.
- A10: mTLS fail-closed в Production (сервисы).
- A11: `JoinService` — целевой тенант обязан существовать и быть активным до резервирования кода.
- A12: атомарный инкремент токенов (`UPDATE ... UsedTokens=UsedTokens+@n`) для Npgsql; EF-путь для InMemory.
- Доп.: int.TryParse apiId; ResolveSession учитывает статус пользователя; очистка протухших сессий — вне
hot-path.
**B. Корректность/потеря данных — закрыто (B14–B29):** атомарные append (comment/link/file) в ProjectStore
(1 SQL), objectKey файла с id записи, дедуп-pump атомарен (Claim→bool), move из trash/archive/taken запрещён,
пустые catch логируются (DiscLog/ILogger), фронт: смена пароля (oldPass), boot с .catch, applySettings не
затирает промпты, seq-токены поиска; интерцепторы gRPC на все 4 вида RPC, логгер catch(Exception), reconnect
с таймаутом, QR-cancel; backfill с in-flight guard + lifetime-токеном.
**C. Архитектура — закрыто:** C30 (единый `TenantSettingsSnapshot` вместо ~9 копий чтения настроек и 3 копий
`LoadRatesAsync`; удалён клон `RateTable.cs`), C31 (общий `src/grpc-hosting/Deal.Grpc.Hosting`; 15 файлов
дублей удалены), C32-декомпозиция (KanbanStore→5, PipelineWorkerService→8, DiscoveryStore→5, ProjectsService→4,
CardsService→3, SettingsService→6, DiscoveryWorkerService→6 partial; фронт: store.js→слайсы store/, вынесены
Telegram/Stop/Scope-вкладки SettingsView, DiscoveryCandidateCard), C33/C34 (MoveMenu, leadsByCol), C36
(`UrlSafeToken`). **Закрыто после ревью (2026-09-09):** C35 — общие реестры
`MlLearningLabels`/`SourceDefaults` в Deal.Contracts (метки обучения ML «spam»/«t:hire»/«t:order» и дефолтный
цвет источника «#666») вместо дублей MlSpamLabel/DefaultChannelHue/DefaultDialogHue/SpamLabel в
Pipeline/Discovery/Telegram/Infrastructure; единый предикат «активные правила» — AiClassifyContextBuilder
переведён на `ColumnRules.HasActiveRules` (Kanban; было расхождение Count>0 vs терм после trim); реестр
`ProjectStages` (9 id-констант вместо литералов) и общий `CardsService.JustNowLabel` (Projects/адаптер
KanbanStore); DiscoverySearchErrorCounter — TTL-эвикция (см. D). **Задел:** полный вынос остальных вкладок
SettingsView (риск без e2e).
**D. Мёртвый код:** удалён (фронт: fileTypeInfo/EXT_KINDS/curName/fmtMoney/openDialog/checkReminders и др.;
бэкенд: недостижимый PrimaryContact DemoLeadFactory и др.). DiscoverySearchErrorCounter — добавлена TTL-эвикция
записей (EntryTtlSeconds=1 ч, ленивая при Next/Reset, часы инъекцией; +4 теста) — задел D закрыт.
@@ -0,0 +1,113 @@
# Аудит документации «Дейл»: сверка с кодом/конфигами
> Исторический документ (аудит документации, 2026-09-10; следующий — `2026-09-11-docs-final-sweep.md`). Актуальное состояние — `docs/superpowers/STATUS.md` и `docs/technical/Техническая-документация-Дейл.md`.
> Дата: 2026-09-11
> Проверено: `docs/spec/ТЗ-дейл-новая-архитектура.md`,
> `docs/user-guide/Инструкция-пользователя-Дейл.md`,
> `docs/technical/Техническая-документация-Дейл.md`,
> `docs/api/api-map.md`, плюс `docs/superpowers/STATUS.md`.
> Метод: сверка утверждений с кодом (`src/core/Deal.Api/Endpoints/*`,
> `src/core/Deal.Infrastructure/**`, `src/frontend/src/**`, `src/{ai,ml,telegram}-service`),
> конфигами (`deploy/compose.*.yml`, `appsettings*.json`) и скриптами (`scripts/*.sh`).
> Докер не поднимался, тесты не перезапускались (см. «непроверяемое»).
## Сводка
- Найдено расхождений: **30** (по пунктам таблиц ниже).
- Исправлено прямо в доках: **30**.
- Значимые подтверждённые факты, с которыми доки сходятся: порты (core 5080/5082, telegram 5101,
ai 5102, ml 5103, metrics 9464, postgres 5433, minio 9000/9001, grafana 3001), единые домены
`/api/cards` + `/api/containers`, оператор-консоль `#/operator` и активация `#/join`,
ключи Telegram — у оператора (`global_settings`), команды запуска.
## Расхождения (файл:строка → в доке → реальность → исправлено)
### `docs/technical/Техническая-документация-Дейл.md`
| # | Место | В доке | Реальность (код) | Статус |
|---|---|---|---|---|
| 1 | §2 «Структура» (~L43) | проект `Deal.Modules.Projects/` | каталога нет; есть `Deal.Modules.Telegram/` | ✅ исправлено на `Deal.Modules.Telegram` |
| 2 | §3 «Модули core» (таблица, ~L74) | «Выбранные» владеет `Deal.Modules.Projects`; нет Telegram | сервисы «Выбранных» — в `Deal.Modules.Kanban` (`CardsService.Selected`); модуль `Deal.Modules.Telegram` существует | ✅ исправлено + добавлена строка Telegram |
| 3 | §3 (абзац, ~L80) | «`Projects` — сервисами пространства…» | модуля `Projects` нет (перенесено в Kanban) | ✅ исправлено |
| 4 | §4 «Ключевые таблицы public» (~L109-112) | `tenants(…, limits_json)`, `users(…, email, role)`, `invites(id, tenant_id, email, code, expires_at, used_at)`, `app_settings` | `tenants(Id,Name,Status,CreatedAt)`, `users(…,Login,…)`, `invites(Code PK,Email,TenantId,Status,ExpiresAt,ActivatedAt,CreatedById,CreatedAt)`, `global_settings`; таблицы `app_settings` нет | ✅ исправлено |
| 5 | §4 сноска (~L122) | `Operators`, `OperatorSessions` | таблицы — `operators`, `operator_sessions` (миграция `SystemSaaS`) | ✅ исправлено |
| 6 | §6 «Файлы» (~L202) | ключ объекта = `tenant_<id>/<card_id>/<file_id>` | `CardsService` строит `projects/<card_id>/<file_id>_<unixMs>_<safeName>` | ✅ исправлено |
| 7 | §8 «Развёртывание» (сноска, ~L304) | «корневой `docker-compose.yml` — наследие LeadRadar» | файл перенесён в `archive/leadradar-legacy/`; в корне его нет | ✅ исправлено |
| 8 | §11 этап 5 (~L510) | модуль/таблица `Deal.Modules.Projects`/`ProjectCards` без пометки | упразднены с этапа 9 | ✅ добавлена пометка «историческое состояние» |
| 9 | §11 TODO (~L619-620) | «OpenAPI-карта снимается с LeadRadar», «миграции на 1000 схем — в плане этапа 0» | api-map и контракты есть; пакетная миграция реализована (этап 12) | ✅ исправлено |
| 10 | §13.4a (~L717) | секреты включают `tgKeys.apiHash` в настройках тенанта, маска `apiHashSet` | `tgKeys` у тенанта нет; ключи — у оператора (`global_settings`, `GET/PUT /api/operator/settings/telegram-keys`) | ✅ исправлено + пометка |
| 11 | §13.5 «Проверка схем» (~L888) | схема тенанта содержит `Boards`, `ProjectCards`; public — неполный | `Boards`/`ProjectCards` удалены (этап 9); актуальны `Containers`, `Dialogs`, `Disc*` и т.д. | ✅ исправлено на актуальный список |
| 12 | §13.6 «Тесты» (~L905) | `dotnet test` ожидает **1203 PASS** | актуальный core — **1275** | ✅ исправлено |
| 13 | §13.7 env (~L976) | core в compose задаёт `DEAL_DEMO=1` | в `compose.dev.yml` `DEAL_DEMO` нет; демо-ручки удалены | ✅ исправлено |
| 14 | §13.7 smoke (~L994) | `POST /api/demo/simulate-lead``/api/leads/{id}/trash` | `dev-smoke.sh`: `POST /api/cards``POST /api/cards/{id}/trash` | ✅ исправлено |
| 15 | §13.7 ручные проверки (~L1061) | `PATCH /api/settings tgKeys` | ключи — у оператора (вариант A) | ✅ исправлено |
| 16 | §13.8 (~L1074) | `public.Operators`/`OperatorSessions` | `operators`/`operator_sessions` | ✅ исправлено |
| 17 | §13.8 (~L1111) | «приостановка тенанта (вход **401**…)» | вход приостановленного тенанта — **403** (`AuthEndpoints`) | ✅ исправлено |
| 18 | §13 заголовок (~L636) | «актуально для этапов 0–10» | актуально по этап 12 | ✅ исправлено |
| 19 | §13.4e (~L841) | исторический раздел этапа 5 без пометки | операции переехали в `/api/cards*`, модуль/таблица удалены | ✅ добавлена пометка |
| 20 | §16 «Добивка» (~L1339) | core-тесты **1245/1245** | актуально **1275/1275** | ✅ исправлено |
### `docs/user-guide/Инструкция-пользователя-Дейл.md`
| # | Место | В доке | Реальность | Статус |
|---|---|---|---|---|
| 21 | §1 «Особенности» (~L32-34) | демо-кнопки («демо-карточка», «демо-сообщение») при `DEAL_DEMO=1` | во фронте демо-кнопок нет, ручки `POST /api/demo/*` и флаг удалены | ✅ исправлено |
### `docs/api/api-map.md`
| # | Место | В доке | Реальность | Статус |
|---|---|---|---|---|
| 22 | §3.1 (~L59) | `change-password` — минимум **4** символа | `AuthEndpoints` — минимум **8** | ✅ исправлено |
| 23 | §4.1 (~L248) | `objectKey: "cards/c_…/pf_…"` | формат `projects/<cardId>/<fileId>_<ms>_<name>` | ✅ исправлено |
| 24 | §5 «Прочие домены» (~L400) | Operator + join = **21** | 24 операторских ручки + `/api/join` = **25** | ✅ исправлено |
### `docs/superpowers/STATUS.md`
| # | Место | В доке | Реальность | Статус |
|---|---|---|---|---|
| 25 | (~L30) | core **1203/1203 PASS** | 1275 | ✅ исправлено |
| 26 | (~L52) | «демо `DEAL_DEMO`» | демо удалено | ✅ исправлено |
| 27 | (~L56) | `public.Operators/OperatorSessions` | `operators`/`operator_sessions` | ✅ исправлено |
| 28 | (~L76) | «демо-пространство, `DEAL_DEMO=1`» | dev-seed `admin/admin`, демо удалено | ✅ исправлено |
| 29 | (~L90) | «settings/boards/demo-карточка» (live-приёмка) | актуальные ручки — `/api/settings`, `/api/cards` | ✅ исправлено + историческая пометка |
| 30 | (~L94) | «simulate-lead → карточка inbox» | `dev-smoke.sh`: `POST /api/cards` → карточка `planned` | ✅ исправлено |
> Нумерация строк приблизительная (после правок сместилась).
## Проверено и сходится (выборка)
- **Порты**: core HTTP 5080 / gRPC-ингресс 5082, telegram-service 5101, ai-service 5102,
ml-service 5103, metrics 9464 (`METRICS_PORT`), postgres host-порт 5433, minio 9000/9001,
grafana `127.0.0.1:3001`, prometheus `127.0.0.1:9090` — совпадают с `deploy/compose.*.yml`
и Dockerfile.
- **Команды**: `docker compose -f deploy/compose.dev.yml up -d --build`, `scripts/dev-smoke.sh`,
`scripts/test.sh` (+ `npm run lint:i18n`), фронт `npm run dev` — совпадают.
- **Единый API**: `/api/cards` + `/api/containers`; домены `/api/leads|projects|boards|columns`
удалены — совпадает с `Endpoints/*` и `api-map`.
- **Оператор-консоль**: hash-роутер `#/` / `#/operator` / `#/join?code=…`
`src/frontend/src/router.js`; ключи Telegram — `global_settings` + `OperatorSettingsEndpoints`.
- **БД**: `Containers` вместо `Boards`, `ProjectCards` нет, `Cards` с модульными JSON-полями;
публичные таблицы `audit_log`/`token_usage_events`/`global_settings`/`rate_limit_counters`
и lowercase `operators`/`operator_sessions` — подтверждено EF-конфигами и миграциями.
- **Файлы**: `objectKey = projects/<cardId>/<fileId>_<ms>_<name>``CardsService.Files`.
- **Наблюдаемость**: `/metrics` на отдельном HTTP/1.1-эндпоинте :9464, Serilog, promtail/loki/grafana —
подтверждено `DealMetricsHosting`, `compose.prod.yml`.
## Осталось / непроверяемое
- **Счётчики тестов сервисов** (telegram 125, ai 52, ml 38): перезапуск тестов не выполнялся
(запрет на долгие процессы). В доках трогали только core-счётчик (1203/1245 → 1275) по
ground-truth задания; сами цифры сервисов не подтверждались кодом.
- **Точное число операторских ручек (25)** — подсчёт по `Endpoints/Operator*` + `JoinEndpoint`;
группировка может отличаться от авторской (ранее было 21 — вероятно, до этапа 12).
- **Исторические разделы-журналы** (§11 этапы 17, §13.4c/4d/4e, live-приёмки в STATUS/планах)
намеренно сохраняют легаси-термины (`Boards`, `/api/leads`, `/api/projects`, `DEAL_DEMO`,
`ProjectCards`). Добавлены точечные пометки «историческое состояние»; полный перепис
не выполнялся (вне правил задачи).
- **Планы/архитектурные доки** (`docs/superpowers/plans/*`, `docs/architecture/*`) содержат
легаси-термины (`Boards`, `ProjectCards`, `docker-compose.yml`) — вне периметра аудита.
- **Живые контуры** (Telegram-вход, реальные LLM-вызовы, mTLS-рукопожатие, backup/restore на
docker-стеке) не проверялись — нужны креды/Docker; в доках они помечены ⚠ Manual.
- **Дубли/внутренние противоречия**: техдок §11 этап 5 и §13.4e описывают снятый контур
«Выбранных» как историю; при следующей редакции их, возможно, стоит свернуть в ссылку на §3.
@@ -0,0 +1,329 @@
# Аудит соответствия ТЗ «Дейл (Deal) — новая архитектура»
> Исторический документ (аудит соответствия ТЗ, 2026-09-10). Актуальное состояние — `docs/superpowers/STATUS.md` и `docs/technical/Техническая-документация-Дейл.md`.
> Дата: 2026-09-10
> Проверяется: `docs/spec/ТЗ-дейл-новая-архитектура.md` (§1–§12) + расширенные требования этапов 8–12.
> Метод: **только исходный код и артефакты репозитория** (`C:\telbase`). Док-документам на слово
> не верим — каждое утверждение подкреплено файлом/символом. Единственное запущенное — линтер
> `npm run lint:i18n` (быстрый, read-only); остальное не запускалось.
> Проект не git; правок кода/доков не вносилось, создан только настоящий отчёт.
## Сводка
| Статус | Кол-во |
|---|---|
| ✅ реализовано | 131 |
| ⚠️ частично | 12 |
| ❌ отсутствует | 1 |
| Всего проверено пунктов | 144 |
Топ-находок — в разделе «Найденные пропуски/расхождения».
(Каждая строка таблицы = один проверяемый пункт ТЗ/расширенных требований.)
---
## §1. О продукте
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 1.1 | Приём сообщений из источников в реальном времени | ✅ | `src/telegram-service/Deal.Telegram/Dialogs/RealtimeListener.cs` (PushMessage + mark-read), `Hosting/RealtimeMonitorService.cs` |
| 1.2 | Отсев мусора (реклама/скам/служебное/дубли/устаревшее) | ✅ | `PipelineRejectConstants.cs` (stage labels `stop/spam_ml/spam_ai/filter_ai/dup/stale`), `IncomingRules.cs`, `PipelineWorkerService.Checks.cs` |
| 1.3 | Структурирование в карточки по профилю (сфера/стек/бюджет/локация) | ✅ | `Pipeline/AiCardMapper.cs`, `Parse/LocalFieldsParser.cs`, `PipelineCardWriter.cs` |
| 1.4 | Раскладка по колонкам-фильтрам | ✅ | `Kanban/ColumnRules/ColumnRules.cs`, `CardsService` (ContainerAccepts) |
| 1.5 | Самообучение на действиях (ML) | ✅ | `Kanban/CardsService.Operations.cs` (PushAsync на move/trash/restore), `MlOutboxFlushScheduler` |
| 1.6 | Discovery — поиск/подключение источников | ✅ | `Deal.Modules.Discovery/*`, `DiscoveryWorkerService.Search/Evaluate/Join` |
## §2. Термины
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 2.1 | Тенант владеет схемой БД/настройками/ML | ✅ | `Data/TenantContext.cs`, модель на тенанта (`TenantDb` миграции), модель ML per-tenant (`ml.proto`, `data/ml/<tenantId>.sqlite`) |
| 2.2 | Аккаунт Telegram (1 на тенанта) | ✅ | `Telegram/Sessions/TenantSession.cs` («1 аккаунт на тенанта») |
| 2.3 | Источник (канал/группа/чат) | ✅ | `Deal.Modules.Telegram/Application/ITelegramStore.cs`, `DialogEntity` |
| 2.4 | Сырое сообщение → очередь | ✅ | `Pipeline/Application/Models/QueuedMessage.cs`, `PipelineIngestService.cs` |
| 2.5 | Карточка — ядро + модули | ✅ | `Deal.Modules.Cards/Application/Card.cs`, интерфейсы `IContentCard/IBudgetedCard/IContactCard/IFileCard/ITzCard/IRemindableCard/…` |
| 2.6 | Типы источника (локально/ссылка/файл/Telegram/импорт/API/ИИ/составной) | ✅ | `Deal.Modules.Cards/Application/ILocalSource.cs`, `IWebSource.cs`, `ITelegramSource.cs`, `IApiSource.cs`, `IFileSource.cs`, `IRowSource.cs`, `IAiSource.cs`, `ICompositeSource.cs` |
| 2.7 | Контейнер + политика | ✅ | `Kanban/Application/Models/ContainerPolicyDto.cs`, `ContainersService.cs` |
| 2.8 | Отсев с причиной | ✅ | `Pipeline/Application/Models/RejectedItemDto.cs`, `PipelineProcessingService.Rejected` |
## §3. Роли и доступ
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 3.1 | Оператор: тенанты/инвайты/лимиты/health/impersonation с аудитом | ✅ | `Endpoints/Operator*`, `OperatorTenantsEndpoints.Impersonate`, `AuditEvents.ImpersonationStarted/Stopped` |
| 3.2 | Тенант: вход по инвайту, пароль, TG-аккаунт, обработка, дашборд | ✅ | `Tenants/Application/JoinService.cs`, `AuthService.cs`, `Endpoints/JoinEndpoint.cs` |
| 3.3 | Регистрация только по инвайту | ✅ | `IInviteStore`, `InviteCodeGenerator` (16 симв., 72 ч), публичной регистрации нет |
| 3.4 | Логин email+пароль, email уникален в SaaS | ✅ | `AuthService`, `users` (public), уникальность email |
| 3.5 | `tenantId` — в сессии | ✅ | `Models/SessionDto.cs`, кука `deal_session`; JWT не используется (сессии) — допустимо формулировкой «сессии/JWT» |
| 3.6 | Вход оператора изолирован от тенантов | ✅ | `Configuration/OperatorCookieOptions.cs` (`deal_operator_session`), `OperatorAuthEndpoints` |
## §4. Подключение Telegram-аккаунта
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 4.1 | Оператор **глобально** задаёт `api_id`/`api_hash` | ⚠️ | Ключи хранятся в **настройке тенанта** `tgKeys` (`SettingsKeys.TgKeys`, `Deal.Api/Telegram/TelegramKeysService.cs`) и задаются в UI тенанта (`settings/TelegramTab.vue`). Глобальной (операторской) настройки/ручки нет — расхождение с §4.1/§8 |
| 4.2 | Подключение: QR или телефон+код | ✅ | `TelegramTab.vue` (qr/phone/code/password), `TelegramEndpoints` (start-qr/start-phone/submit-code/password), `TenantSession.StartQrAsync` |
| 4.3 | Сессия сохраняется, статус подключения показан | ✅ | `Sessions/SessionStore.cs`, `SessionFileCipher.cs` (AES-GCM), `TgStatusService`, `GET /api/tg/status` |
| 4.4 | 1 аккаунт на тенанта (схема допускает расширение) | ✅ | `TenantSession` (один на тенанта), `SessionFarm` |
| 4.5 | Список диалогов подтягивается при подключении и обновляется на экране + в фоне | ✅ | `Dialogs/RealtimeSweep.cs` (SyncDialogs каждые 30 с), `TelegramEndpoints` `/dialogs/refresh`, `ChannelsView.vue` |
| 4.6 | Вкл/выкл мониторинга по источнику | ✅ | `DialogsService.SetMonitorAsync`, `TelegramEndpoints` `/dialogs/{id}/monitor` |
| 4.7 | «Новый чат → мониторинг автоматически» (вкл/выкл) | ✅ | `SettingsKeys.AutoMonitorNew`, `DialogsService.SyncFromTelegramAsync`, `TelegramStore.SyncFromTelegramAsync` |
| 4.8 | Удалённые/покинутые источники исчезают | ✅ | `TelegramStore.SyncFromTelegramAsync` (удаление отсутствующих) |
| 4.9 | «Перечитать»: догон ~10 сообщений включённых источников, анти-бан-паузы | ✅ | `Dialogs/BackfillService.cs` (`MessagesLimit=10`, паузы 1.53 с / 36 с), `POST /api/tg/dialogs/backfill-all` |
| 4.10 | Полученные сообщения сразу помечаются прочитанными | ✅ | `RealtimeListener.OnMessageReceivedAsync` (MarkReadAsync после Push), `BackfillService` (read-ack) |
| 4.11 | Discovery: задача поиска → ИИ ключевые слова | ✅ | `DiscoveryEndpoints` (generate-keywords), `IAiTools.GenerateKeywordsAsync` |
| 4.12 | Поиск каналов, где аккаунт не состоит | ✅ | `DiscoveryWorkerService.Search.cs`, `IsDialogMonitoredAsync` |
| 4.13 | Каскад: участники → язык → содержание (порог ≥40%) | ✅ | `DiscoveryWorkerService.Evaluate.cs`, `SettingsDefaults.DiscEvalThreshold = 40` |
| 4.14 | Кандидаты «на рассмотрение» с метаданными/fit/темами/метками (закрытая группа) | ✅ | `DiscoveryWorkerService.Constants.cs` (`MarkClosedGroup`…), `FinishReviewAsync`, `Models/DiscoveryTopicDto` |
| 4.15 | Действия: вручную «Вступить» / авто-вступление с квотами (50/сутки, 50–70 с) | ✅ | `DiscoveryBanGuard.cs` (`DiscJoinLimit=50`), `DiscoveryPacer.cs` (`DiscJoinDelayMin/Max=50/70`) |
| 4.16 | «Отклонить» → чёрный список; список исключает во всех задачах; снимается вручную | ✅ | `DiscoveryBlacklistService.cs`, `DiscoveryBlacklistList.vue`, `RemoveBlacklistAsync` |
## §5. Обработка входящих (пайплайн)
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 5.1 | Путь: источник → очередь → стоп-лист → дедуп → ML → ИИ → карточка | ✅ | `PipelineWorkerService.Pump.cs`, `SignificantPath`, `PipelineIngestService` |
| 5.2 | Этап 1: минимальная длина текста | ✅ | `IncomingRules.Evaluate` (`KindLength`), `SettingsDefaults.MinLen=24` |
| 5.3 | Этап 1: стоп-фразы (настраиваемый список) | ✅ | `SettingsKeys.StopPhrases`, `IncomingRules` (`KindStop`), `settings/StopTab.vue` |
| 5.4 | Этап 1: отсев резюме соискателей (настройка) | ✅ | `SettingsKeys.BlockResumes/ResumeMarkers`, `IncomingRules` (`KindResume`, guard «резюме» при маркере найма) |
| 5.5 | Этап 1: тип заявки (только вакансии / только заказы) | ✅ | `SettingsKeys.WantedType`, `IncomingRules` (`KindType`), `hireMarkers` |
| 5.6 | Этап 1: дедуп (нормализованный хэш) | ✅ | `Parse/DedupHasher.cs`, `DedupEntries` (миграция `TenantPipeline`) |
| 5.7 | Этап 1: устаревшее сообщение → отсев | ✅ | `PipelineWorkerService.Checks.cs` `IsStaleAsync` (`ArchiveAfterDays`) |
| 5.8 | ML: уверена → решает сама (спам/колонка); не уверена → ИИ | ✅ | `PipelineWorkerService.Pump.cs` (`run.MlEnabled && !force`), `ml.proto` (take/label/margin) |
| 5.9 | Возврат из отсева (force) идёт мимо ML к ИИ | ✅ | `Pump.cs` (`force` пропускает ML), `PipelineProcessingService.ReturnAsync` (`Force = true`) |
| 5.10 | ИИ-фильтр: не про заявки → отсев; выключатель `aiFilterEnabled` | ✅ | `Pump.cs`, `SettingsKeys.AiFilterEnabled`, `AiFilterResultDto.Skipped` |
| 5.11 | Классификация: структурированный разбор (компания/формат/задача/требования/плюсы/условия/бюджет/стек/контакты/тип) | ✅ | `Parse/ParsedCardContent.cs`, `AiCardMapper.cs`, `ai.proto` ClassifyReply |
| 5.12 | Назначение колонки с проверкой правил | ✅ | `ContainerAccepts`, `AiCardLearning.cs`, `ColumnRules.cs` |
| 5.13 | Глобальный фильтр «без суммы» отдельно для вакансий и заказов | ✅ | `SettingsKeys.BudgetRequiredHire/Order`, `PipelineWorkerService.Checks.cs` `SkipNoBudgetAsync` |
| 5.14 | Глобальные исключения по ключевым словам/технологиям/бюджету/локации | ⚠️ | Глобальных настроек-исключений нет: в `SettingsKeys` только `StopPhrases` (стоп-фразы) и per-column `exclude` (`ColumnExclusions.cs`). Исключений «ключевые слова/технологии/бюджет/локация» отдельного глобального уровня не найдено |
| 5.15 | Карточка — одна строка одной таблицы `Cards`; `ProjectCards` упразднена | ✅ | Миграция `TenantUnifiedCard.cs` (`DropTable("ProjectCards")` + `AddColumn` `StackJson/LinksJson/FilesJson/HistoryJson/TzText/Reminder…`) |
| 5.16 | Комментарии — общая таблица `LeadComments` | ✅ | Миграция `TenantKanban.cs` (`LeadComments`), `KanbanStore.Comments.cs` |
| 5.17 | Единый реестр контейнеров; пространства не пересекаются; «взять в работу» = смена контейнера | ✅ | `ContainerSpaces.cs`, `ContainersService.cs`, `CardsService.Selected.cs` (`TakeAsync`) |
| 5.18 | Исходное сообщение хранится и доступно (открыть в Telegram / форматированно) | ✅ | `CardDrawer.vue` (`sourceMsg`, `tgSourceUrl`, `renderSourceMessage`), `ProcessingView.vue` |
## §6. Дашборд (канбан)
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 6.1 | Колонки: «Неразобранное», пользовательские, «Архив», «Корзина» | ✅ | `CardIds` (inbox/archive/trash), `ContainerKinds`, `KanbanColumns` |
| 6.2 | Пользователь создаёт колонки; ИИ **предлагает** с обоснованием; принять/отклонить/переименовать | ✅ | `AiSuggestEndpoints`, `SuggestHeuristics.cs`, `ContainerColumn.vue` (`acceptSuggestedBoard`, `suggested` badge) |
| 6.3 | Колонка = сложный набор фильтров (ключевые слова/стек/грейд/уровень/цена/бюджет/локация/тип + отрицательные) | ⚠️ | `ContainerRulesDto` содержит только `Mode/Direction/Keywords/Stack/Grade/Exclude/Budget`. Отдельных групп «уровень/цена/локация/тип» нет (частично покрыты `direction`/`keywords`); отрицательные — `exclude` ✅ |
| 6.4 | При помещении указаны критерии попадания | ✅ | `ColumnRules.ComputeHits`, `MatchHitBuilder`, `MatchHitDto` |
| 6.5 | Свежие сверху; drag&drop между колонками с обучением ML | ✅ | `KanbanStore.Cards.cs` (`OrderByDescending(ReceivedAt)`), `composables/dnd.js`, `PushAsync` on move |
| 6.6 | Быстрые действия: комментарий, корзина, контакт, «открыть исходник» | ⚠️ | Комментарий/корзина/контакт — `Card.vue` (кнопки). «Открыть исходник» на самой карточке нет — только в `CardDrawer.vue` и `ProcessingView.vue` |
| 6.7 | Виджеты-счётчики свёрнутых колонок; двигать/менять размер | ✅ | `Sidebar.vue`, `cards.js` (`cycleWidth`, `colExtra`, `reorder`), `COLUMN_WIDTHS` |
| 6.8 | Архив: старше N дней (1–30), очистка через 90 дней | ✅ | `StorageTickService.cs`, `SettingsDefaults.ArchiveAfterDays=14 (кламп 1..30)`, `ArchiveClearDays=90` |
| 6.9 | Корзина: очистка раз в 7 дней; возврат из архива/корзины | ✅ | `SettingsDefaults.TrashClearDays=7`, `CardsService.Operations.cs` (`RestoreCardAsync`) |
| 6.10 | «Выбранные»: стадии Запланировано→…→Готово/Отложено | ✅ | `CardsDefaultContainers.cs` (planned/reply/agree/work/review/ready/hold) |
| 6.11 | «Взять в работу» — переход в контейнер, не клон | ✅ | `CardsService.Selected.cs` `TakeAsync` |
| 6.12 | Модули работы: комментарии/сумма/стек/контакты, ссылки, ТЗ, файлы (S3/MinIO), значки количества | ✅ | `CardsService.Files.cs`, `CardFileKind.cs`, `FileKindDetector.cs`, `CardDrawer.vue` |
| 6.13 | Отложенные: напоминания (срок+время, календарь); выключатель; выключено → не срабатывают | ✅ | `CardsService.Reminders.cs` (`RemindersDisabledDetail`, snooze +24 ч), `HoldReminderDialog.vue`, `SettingsDefaults.RemindersEnabled` |
| 6.14 | История движения — под спойлером | ✅ | `CardDrawer.vue` (`<details>` «История движения», `historyReversed`) |
| 6.15 | Ручное создание карточки (пометка «создано локально») | ✅ | `CardDetailsEndpoints` `POST /api/cards`, `Local` флаг, `Card.vue`/`CardDrawer.vue` бейдж «Локальная» |
| 6.16 | Терминальные зоны «Отклонено»/«Выполнено»; в архив/корзину дашборда не попадают | ✅ | `CardsDefaultContainers.finished/rejected` (terminal), `ContainerPolicyDto.IsTerminal`, `ClearRejected` |
## §7. Вкладка «Обработка»
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 7.1 | Очередь (этап 1 / ожидают ИИ) с автопрокруткой | ✅ | `ProcessingView.vue` (таймер-опрос ~2.6 с, статусы `etap-1-bez-ii`/`ozhidaet-ii`); «автопрокрутка» реализована как авто-обновление |
| 7.2 | Отсев с причиной и источником решения (правила/ML/ИИ/система) + конкретная фраза | ✅ | `PipelineRejectConstants.cs` (`StageLabels`/`SourceLabels`), `RejectedItemDto` (`kw`, `reason`) |
| 7.3 | Метаданные, «Открыть исходник», «Исходное сообщение (форматированно)» | ✅ | `ProcessingView.vue` (`metaRows`, `sourceUrl`, `srcHtml`) |
| 7.4 | Полнотекстовый поиск по отсеву | ✅ | `PipelineEndpoints` `/rejected?q=` (FTS LIKE), `Store` поиск |
| 7.5 | Возврат из отсева: причины игнорируются, ML/ИИ обучаются, причина возврата | ✅ | `PipelineProcessingService.ReturnAsync` (`Force=true`, `PushAsync(spam,1.0)`, `returnReason`) |
| 7.6 | Автоочистка отсева раз в 3 дня; ручная очистка | ✅ | `PipelineRejectConstants.RetentionDays=3`, `POST /pipeline/rejected/clear`, `DELETE /rejected/{id}` |
| 7.7 | Счётчик обработки в боковой панели; отсев в панели не показывается | ✅ | `Sidebar.vue` (`state.pQueueCounts.total`), отсев — только внутри `ProcessingView.vue` |
## §8. Настройки тенанта
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 8.1 | Telegram: ключи приложения (**оператор**), подключение, авто-мониторинг | ⚠️ | Подключение/авто-мониторинг ✅ (`TelegramTab.vue`, `AutoMonitorNew`). Ключи — настройка **тенанта** `tgKeys`, а не глобальная операторская (см. §4.1) |
| 8.2 | ИИ: провайдер (в т.ч. локальные), модель, ключ зашифрован | ✅ | `AiProviders.cs`, `SettingsService.PatchSecrets.cs` (`enc:`), `ISecretCipher` |
| 8.3 | Промпты: базовый + свой; библиотека по сферам + «мои промпты» | ✅ | `PromptLibraryModal.vue` (`PROMPT_LIBRARY`/`PROMPT_CATEGORIES`, поиск), `SettingsService.PatchMyPrompts.cs`, `AiTab.vue` |
| 8.4 | ИИ вкл/выкл; ИИ-фильтр вкл/выкл | ✅ | `SettingsKeys.AiEnabled/AiFilterEnabled`, `Pump.cs` |
| 8.5 | ML: вкл/выкл, обучение на действиях, **проверка на сообщении/канале**, сброс, самооценка | ⚠️ | `mlEnabled`, обучение (`PushAsync`), predict (сообщение) ✅, сброс ✅ (`/api/ml/reset`), самооценка ✅ (`MlEvalDto`). **Проверка на канале не реализована**: `POST /api/ml/candidates` возвращает пустой список (заглушка), `POST /api/ml/apply` — всегда 404 (`MlEndpoints.cs:130155`) |
| 8.6 | Обработка: стоп-фразы, длина, резюме, тип, домен/ключи, маркеры найма/заказа | ✅ | `SettingsKeys.StopPhrases/MinLen/BlockResumes/WantedType/DomainKeywords/HireMarkers`, `StopTab.vue`/`ScopeTab.vue` |
| 8.7 | Колонки: набор, правила, отрицательные фильтры, исключения | ✅ | `ContainersEndpoints`, `BoardRulesDialog.vue`, `ColumnExclusions.cs` (см. замечание 6.3 по составу групп) |
| 8.8 | Валюта: целевая, источник (4 запроса/сутки), конвертация при приёме + пересчёт старых (кроме архива/корзины), USDT=USD | ✅ | `RatesService.cs` (`RatesFetchInterval` = 6 ч = 4/сутки; USDT→USD), `ConversionRecomputer.cs` (`ConversionExcludedCols` archive/trash) |
| 8.9 | Хранение: срок архивации (1–30), очистка архива/корзины | ✅ | `StorageTickService.cs`, `SettingsDefaults.ArchiveAfterDays/ArchiveClearDays/TrashClearDays`, `StorageTab.vue` |
| 8.10 | Уведомления и напоминания; отложенные — отдельно | ✅ | `NotifyTab.vue`, `SettingsKeys.RemindersEnabled`, `CardsService.Reminders.cs` |
| 8.11 | Звук | ✅ | `NotifyTab.vue` (`soundOn`, `volume`, `testSound`), `utils.js` (Web Audio) — клиентская настройка, без серверного ключа |
| 8.12 | Внешний вид | ❌ | В `SettingsView.vue` вкладок Telegram/AI/Storage/Stop/Scope/ML/Notify/Currency/Profile — раздела «Внешний вид» (тема/оформление) нет; `style.css` содержит единственную тёмную тему |
## §9. Лимиты (бюджет токенов)
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 9.1 | Бюджет токенов на LLM, период настраивается | ✅ | `TenantLimitDto` (`BudgetTokens`, `Period` month/day), `OperatorLimitUpdateRequest` |
| 9.2 | ai-service оценивает вызов в токенах, списывает с бюджета | ✅ | `TokenUsageRecorder.cs`, `BudgetedAiClassifier.cs`, `BudgetedAiTools.cs`, `ai.proto` Usage |
| 9.3 | При исчерпании: fallback + уведомление; приём не блокируется | ✅ | `BudgetedAiClassifier` (Local-фолбэк), `Warned80/NotifiedExhausted`, условия `pipeline` не блокируются |
| 9.4 | Оператор видит расход и меняет бюджет | ✅ | `OperatorLimitsEndpoints` (`/limits`, `/tenants/{id}/limit`), `AnalyticsService` |
## §10. Админка оператора
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 10.1 | Тенанты: создание, инвайты, статус, лимиты, приостановка | ✅ | `OperatorTenantsEndpoints` (create/suspend/unsuspend), `OperatorInvitesEndpoints` |
| 10.2 | Health всех сервисов и очередей | ⚠️ | Сервисы ✅ (`OperatorHealthEndpoints`, ml/ai/telegram по gRPC-пробам). «Очереди» в health нет — глубины очередей публикуются только в метриках (`Observability/DealMetricsCollector.cs``/metrics`) |
| 10.3 | Аудит: входы/выходы, инвайты, impersonation, действия оператора и тенанта | ✅ | `AuditEvents.cs` (login/logout/invite/impersonation/card_*/container_*/settings/channels/telegram), `AuditService` |
| 10.4 | Аналитика: расход токенов (день/тенант/провайдер/модель) + лента действий с фильтрами | ✅ | `AnalyticsService.TokensAsync` (groupBy), `OperatorAnalyticsEndpoints`, `AuditSection.vue`/`AnalyticsSection.vue` |
| 10.5 | Подозрительная активность (по логам безопасности) | ⚠️ | Отдельного разбора/детектора подозрительной активности не найдено; есть счётчики неудачных входов в `AnalyticsService.OverviewAsync` (`failedLogins`) и общие Grafana-дашборды |
| 10.6 | Метрики сервисов (Prometheus/Grafana) | ✅ | `DealMetricsHosting.cs` (`/metrics` :9464), `deploy/observability/prometheus.yml`, `prometheus-rules.yml`, Grafana-дашборды |
| 10.7 | UI: `#/operator` и `#/join` | ✅ | `router.js`, `views/operator/OperatorConsole.vue`, `views/JoinView.vue` |
## §11. Нефункциональные требования
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 11.1 | Безопасность: TLS, mTLS между сервисами | ✅ | `scripts/mtls-certs.sh`, `MtlsCertificates.cs`, `compose.prod.yml` (`DEAL_MTLS_*`), `MtlsOptions.cs` |
| 11.2 | Параметризованный SQL | ✅ | EF Core / Npgsql по всему `Deal.Infrastructure`; ручной SQL — параметризованный (`ExecuteSqlRawAsync` без конкатенации) |
| 11.3 | IDOR/XSS/SSRF/CSRF | ✅ | IDOR — session+tenant-scope middleware; XSS — `renderSourceMessage` (экранирование); SSRF — `AiConnectionChecker.cs` (`IsPrivateEndpoint`, allowlist `AiProviders`), `CbrRateSource` (fixed URL); CSRF — `OriginGuardMiddleware.cs` + SameSite |
| 11.4 | Argon2id | ✅ | `DefaultPasswordHasher.cs` (Isopoh Argon2, Variant Argon2id) |
| 11.5 | Rate limiting (прокси + приложение), счётчики распределённые в БД | ✅ | `StoreBackedFixedWindowRateLimiter.cs`, `IRateLimitCounterStore``RateLimitCounterStore` (public.rate_limit_counters), `LoginAttemptGuard.cs`, `RateLimitPolicies.cs` |
| 11.6 | Cloudflare | ⚠️ | В коде нет интеграции/конфигурации Cloudflare; edge — Caddy (`deploy/caddy/Caddyfile`, TLS `internal`). Требование внешнего периметра, вне репозитория |
| 11.7 | Ежедневные бэкапы (Postgres/файлы/сессии), outbox для событий | ✅ | `scripts/backup.sh`/`restore.sh`/`deal-backup-lib.sh`; outbox — `MlOutboxQueue.cs`, `MlOutboxFlushScheduler.cs` |
| 11.8 | Авто-очистки (retention аудита/лимитов/счётчиков), разлогин suspended | ✅ | `DataRetentionScheduler.cs`, `DataRetentionOptions.cs`; `AuthService.ResolveSessionAsync` (suspended → null) |
| 11.9 | Наблюдаемость: логи → Loki, метрики OTel→Prometheus→Grafana + алерты, `token_usage_events` | ✅ | `Logging/DealLogging.cs`, `deploy/observability/{promtail,loki}.yml`, `prometheus-rules.yml`; миграция `AddTokenUsageEvents` |
| 11.10 | Масштабируемость: модульный монолит + сервисы ml/ai/telegram; k8s позже | ✅ | `Deal.Modules.*`, отдельные проекты `src/{ai,ml,telegram}-service`, `compose.*.yml`; k8s отсутствует (заявлено позже) |
| 11.11 | Производительность: без потерь; анти-бан-паузы не блокируют обработку | ✅ | `PipelineIngestService`/`DedupEntries`, фоновые `PipelineWorkerScheduler`/`BackfillService`, `progressive.js` |
| 11.12 | i18n: строки вынесены, RU по умолчанию, новые языки, переключение на лету с сохранением, форматтеры дат/чисел/валют, фолбэк RU | ⚠️ | Ядро i18n есть (`i18n/index.js`, `ru.js`/`ru.data.js`, `$t`), линтер проходит зелёным (проверено: `npm run lint:i18n` → ✓). Но: **нет UI-переключателя языка, нет второго языка и нет сохранения выбора**`index.js` прямо: «UI-переключателя на этом этапе нет»); даты/числа форматируются жёстко через `toLocale*('ru-RU', …)` (`store/core.js`, `store/settings.js`, `fmtNum` в `store/operator.js`), а не через locale-aware i18n-форматтеры |
## §12. Ограничения и допущения
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| 12.1 | Фронтенд Vue 3 + Vite + Tailwind; единый контракт `/api/cards` + `/api/containers` с этапа 9 | ✅ | `package.json` (vue/vite/tailwind), `api.js`, `CardsEndpoints.cs`, `ContainersEndpoints.cs` |
| 12.2 | Данные LeadRadar тестовые — не мигрируются | ✅ | Отдельные миграции Deal; данных-миграций из LeadRadar нет |
| 12.3 | Kafka, k8s, биллинг-провайдер, саморегистрация — вне рамок | ✅ | В коде отсутствуют |
| 12.4 | 1 Telegram-аккаунт на тенанта; несколько — позже | ✅ | `TenantSession` (1 на тенанта) |
---
## Расширенные требования (этапы 8–12)
| № | Требование | Статус | Доказательство |
|---|---|---|---|
| E1 | Библиотека готовых промптов по специальностям | ✅ | `ru.data.js` `PROMPT_LIBRARY` (IT/дизайн/недвижимость/стройка/услуги/красота/обучение), `PromptLibraryModal.vue` |
| E2 | Категории и поиск в библиотеке | ✅ | `PROMPT_CATEGORIES`, фильтр `query`/`cat` в `PromptLibraryModal.vue` |
| E3 | Раздел «Мои промпты» + свой промпт | ✅ | `SettingsService.PatchMyPrompts.cs`, `AiTab.vue` (`addMyPrompt`/`removeMyPrompt`), лимит ≤100 |
| E4 | Двухэтапный стоп-лист: стоп-фразы без ИИ, затем ИИ-фильтр с возможностью отключить | ✅ | Этап 1 `IncomingRules` (без ИИ); ИИ-фильтр `FilterSafelyAsync` под `AiFilterEnabled` |
| E5 | Исключения внутри колонки | ✅ | `ColumnExclusions.cs` (veto `Exclude`), `BoardRulesDialog.vue` |
| E6 | Discovery: поиск/вступление в каналы и группы | ✅ | `DiscoveryWorkerService.Search/Join`, `DiscoveryOps` (telegram-service) |
| E7 | Discovery: квоты/интервалы, закрытые группы, темы, список на рассмотрение | ✅ | `DiscoveryBanGuard`, `DiscoveryPacer`, `MarkClosedGroup`, `DiscoveryTopicGroup`, статус `review` |
| E8 | «Перечитать каналы»/backfill, пометка прочитанными, мгновенный приём | ✅ | `BackfillService.cs` (10 сообщений, паузы), read-ack; `RealtimeListener.cs` |
| E9 | ML отдельным контейнером | ✅ | `src/ml-service/Deal.Ml/Dockerfile` + `compose.dev.yml`/`compose.prod.yml` (`ml-service`, gRPC :5103) |
| E10 | ML: обучение на действиях пользователя **и** ИИ | ✅ | Пользователь — `CardsService.Operations.cs` (`PushAsync(…,1.0)`); ИИ — `AiCardLearning.cs`, `CardReclassifier.cs` (`AiPushWeight`) |
| E11 | Отдельная настройка проверки ML на сообщении/канале | ⚠️ | Проверка на **сообщении** ✅ (`POST /api/ml/predict`, `MLPanel.vue`); проверка на **канале** ❌ (`/api/ml/candidates` — пустая заглушка, `/api/ml/apply` — 404) |
| E12 | Архив/корзина (сроки, возврат, ручная очистка) | ✅ | `StorageTickService.cs`, `CardsService.Operations.cs`, `clear-col`/`DELETE`, `Restore` |
| E13 | Напоминания «Отложено» (календарь, отключение) | ✅ | `HoldReminderDialog.vue`, `CardsService.Reminders.cs`, `RemindersEnabled` |
| E14 | История карточки под спойлером | ✅ | `CardDrawer.vue` `<details>` «История движения» |
| E15 | Контакты квалифицированные (tg/phone/email/linkedin/site) | ✅ | `Parse/ContactsQualifier.cs` (типы `tg/phone/email/linkedin/whatsapp/site`, дедуп, отбой ботов/сервисных ссылок) |
| E16 | «Открыть исходник» | ✅ | `CardDrawer.vue` (`sourceUrl`), `ProcessingView.vue` |
| E17 | Источник не на карточке (только в деталях) | ✅ | `Card.vue` показывает лишь бейдж «Локальная»/контакты; канал и исходное сообщение — в `CardDrawer.vue` |
| E18 | Бюджет: диапазон/вакансия/валюта + конвертация (4 раза в сутки) | ✅ | `CardBudget.cs`, `BudgetNormalizer.cs`, `RatesService.cs` (6 ч = 4/сутки), `ConversionRecomputer.cs` |
| E19 | Обязательность суммы (опционально для вакансий) | ✅ | `SettingsKeys.BudgetRequiredHire/BudgetRequiredOrder`, `SkipNoBudgetAsync` |
| E20 | Вкладка «Обработка» (очередь + отсев + причины + поиск) | ✅ | `ProcessingView.vue`, `PipelineEndpoints` |
| E21 | Возврат из отсева с обучением | ✅ | `PipelineProcessingService.ReturnAsync` (`PushAsync(spam,1.0)`, `Force`) |
| E22 | Оператор-консоль | ✅ | `views/operator/*` (Tenants/Invites/Limits/Audit/Analytics/Health), `router.js` |
| E23 | Аналитика токенов | ✅ | `AnalyticsService.cs`, `OperatorAnalyticsEndpoints.cs`, `token_usage_events` |
| E24 | Аудит входов/выходов/действий (этап 10) | ✅ | `AuditEvents.cs`, `AuditService.cs`, `AuditSection.vue` |
| E25 | i18n (вынос строк) | ⚠️ | Строки вынесены и линтер зелёный, но нет переключателя языка/второго языка/персистентности и locale-форматтеров (см. 11.12) |
| E26 | Метрики Prometheus | ✅ | `DealMetricsHosting.cs`, `SharedKernel/Observability/DealMetrics.cs`, `prometheus.yml` (таргеты 5/5) |
| E27 | Распределённый rate-limit | ✅ | `RateLimitCounterStore.cs` (Postgres), `StoreBackedFixedWindowRateLimiter.cs`, миграция `RateLimitCounters` |
| E28 | reclassify (реальный, этап 12) | ✅ | `CardsEndpoints` `/reclassify` и `/{id}/reclassify`, `CardReclassifier.cs` (локальный фолбэк), `ReclassifyGate.cs`, audit `card_reclassified` |
---
## Найденные пропуски/расхождения
### ❌ Отсутствует
1. **§8.12 «Внешний вид» (настройки оформления).** В `SettingsView.vue` нет вкладки/раздела внешнего вида;
тема одна (тёмная, `style.css` `@theme`). Отдельной настройки «внешний вид» не найдено.
### ⚠️ Частично
2. **§8.5 / E11 «проверка ML на канале».** `POST /api/ml/candidates` (`MlEndpoints.cs:131141`) возвращает
`{items: []}` с комментарием «До этапа 6 telegram-данных нет» — устаревшая заглушка; `POST /api/ml/apply`
(`MlEndpoints.cs:144155`) всегда отвечает 404 «Исходное сообщение не найдено». Реального разбора
сообщений канала/ручного применения решения ML нет, хотя telegram-данные в системе уже есть
(проверка на сообщении — `POST /api/ml/predict` — работает).
3. **§5.14 «Глобальные исключения по ключевым словам/технологиям/бюджету/локации».** Глобальных настроек
такого исключения в `SettingsKeys` нет: есть только `stopPhrases` (стоп-фразы) и per-column `exclude`
(`ColumnExclusions.cs`). Исключения уровня «технология/бюджет/локация» как общий фильтр не найдены.
4. **§4.1/§8.1 ключи Telegram.** Хранятся как настройка тенанта `tgKeys` (`TelegramKeysService.cs`) и
вводятся в UI тенанта (`TelegramTab.vue`). ТЗ требует, чтобы `api_id`/`api_hash` задавал **оператор
глобально** — глобальной операторской настройки/ручки нет.
5. **§11.12 / E25 i18n.** Строки вынесены в словари (`i18n/locales/ru.js`, `ru.data.js`), `npm run lint:i18n`
проходит. Но отсутствуют: UI-переключатель языка, второй язык, сохранение выбора, «переключение на лету»
`i18n/index.js` явно сказано «UI-переключателя на этом этапе нет»). Форматирование дат/чисел жёстко
`ru-RU` (`store/core.js:232276`, `store/settings.js:351406`, `store/operator.js:356`), не через
locale-aware i18n-форматтеры.
6. **§6.3 состав фильтров колонки.** `ContainerRulesDto` = `Mode/Direction/Keywords/Stack/Grade/Exclude/Budget`.
ТЗ перечисляет также «уровень/цена/локация/тип» отдельными опциями — явных групп нет (частично
покрываются `direction`/`keywords`).
7. **§6.6 «открыть исходник» как быстрое действие карточки.** На `Card.vue` есть комментарий/корзина/контакт,
но ссылки «открыть исходник» нет — она доступна только в `CardDrawer.vue` и `ProcessingView.vue`.
8. **§10.2 health очередей.** `/api/operator/health` проверяет БД и сервисы ml/ai/telegram, но глубины
очередей (пайплайн, MlOutbox) в JSON health не отдаёт — они только в метриках
(`DealMetricsCollector.cs``/metrics`).
9. **§10.5 подозрительная активность.** Специализированного детектора/ленты подозрительной активности по
логам безопасности не найдено; есть лишь счётчик `failedLogins` в обзорной аналитике и общие
Grafana-дашборды.
10. **§11.6 Cloudflare.** В репозитории нет конфигурации/интеграции Cloudflare (edge — Caddy,
`deploy/caddy/Caddyfile`). Требование периметра, вне кода приложения.
11. **§7.1 «автопрокрутка» очереди.** Реализована как периодическое авто-обновление списка (~2.6 с,
`ProcessingView.vue`), а не как буквальная авто-прокрутка. Семантически покрывает требование, но не
дословно.
### Дефекты/легаси, замеченные при проверке (не пункты ТЗ, но влияют на заявленные функции)
12. **`NotifyTab.vue` — сломан список активных напоминаний.** `const holdReminders = computed(() => state.projectCards.filter(...))`
(`settings/NotifyTab.vue:68`), при этом `state.projectCards` больше нигде в `src/` не определяется
(grep даёт ровно одно совпадение — этот файл). После этапа 9 (`projectCards`/`stage` упразднены) обращение
к `state.projectCards.filter` даёт `undefined.filter` → ошибка рендера вкладки «Уведомления».
13. **Легаси-артефакты LeadRadar.** В корне остались `docker-compose.yml` (сервисы `app`/`ml`/`minio`
старого стека), каталог `backend/` (python `app/`) и `mlservice/` (python). Текущая архитектура — `deploy/compose.*.yml`
+ `src/{core,ai,ml,telegram}-service`. Прямого нарушения ТЗ нет, но это риск путаницы (в STATUS.md
«судьба legacy `docker-compose.yml`» помечена как открытый вопрос).
---
## Чего проверка не покрывает
- **Живые внешние интеграции без кредов.** Реальный Telegram-вход (`api_id`/`api_hash`/QR) и реальные
LLM-вызовы не проверялись (нет кредов; см. STATUS.md, п.5 «нужны живые креды»). Проверяется только
наличие кода/контрактов и локальных заглушек.
- **Живой контур Docker/k8s, mTLS-рукопожатие, Grafana/Loki/Prometheus.** Проверены конфиги
(`compose.*.yml`, `deploy/observability/*`) и код обвязки, но не факт поднятия/скрейпа в этой сессии
(сервисы не поднимались).
- **Скрипты бэкапа/восстановления и нагрузочные тесты.** Наличие и читаемость проверены (`scripts/backup.sh`,
`scripts/restore.sh`, `scripts/loadtest/`), но не выполнялись.
- **Корректность чисел в тестах.** Тест-счётчики (STATUS.md: core 1203 и т.п.) не пересчитывались —
тесты не запускались (кроме быстрого `lint:i18n`).
- **UI-поведение в браузере.** Выводы по фронту основаны на чтении `.vue`/`.js`; реальные клики,
drag&drop и рендер не воспроизводились.
- **Внешний периметр (Cloudflare, TLS в проде, DNS, egress-контроль).** Вне репозитория.
- **Соответствие формальным юридическим требованиям/биллингу** — вне рамок ТЗ (заявлено как «позже»).
---
## Обновление (2026-09-10, вечер) — статус после добивки
Часть найденных ⚠️/❌ закрыта в тот же день (детали — `.superpowers/sdd/deal-stage12-observability-hardening/task-tz-*.md`):
| Пункт | Было | Стало |
|---|---|---|
| §8.12 «Внешний вид» | ❌ | ✅ раздел настроек + темы тёмная/светлая/системная (§15 техдока) |
| §8/E11 ML-проверка на канале | ⚠️ заглушка | ✅ `MlReviewService` (`/api/ml/candidates|apply`) |
| §5.14 глобальные исключения | ⚠️ | ✅ `excludeKeywords/Locations/Types/Budget*` на стоп-этапе |
| §6.3 группы фильтров колонки | ⚠️ | ✅ `levels/locations/types/prices` + matchHits |
| §6.6 «открыть исходник» на карточке | ⚠️ | ✅ быстрое действие в `Card.vue` |
| §10.2 health очередей | ⚠️ | ✅ `queues`/`sessions` в `/api/operator/health` |
| §10.5 подозрительная активность | ⚠️ | ✅ `SuspiciousActivityService` + `/api/operator/analytics/suspicious` |
Остаются требующими владельца/кредов (осознанно): глобальные Telegram-ключи оператора (§4.1/§8.1),
переключатель языка (§11.12 — **в бэклоге**, по потребности), живые Telegram/LLM-вызовы, Cloudflare/прод-периметр.
Итог после добивки: core-тесты **1245/1245**; фронт build + `lint:i18n` зелёные.
@@ -0,0 +1,95 @@
# Финальная «подбивка» документации «Дейл» (2026-09-11)
> Дата: 2026-09-11
> Периметр: все `docs/**` (актуальные доки — spec/user-guide/technical/api/STATUS; исторические —
> `plans/*`, `reviews/*`, `specs/*`, старые `architecture/*`).
> Метод: сквозной поиск по проблемным терминам (`Boards`, `ProjectCards`, `Deal.Modules.Projects`,
> `ProjectStages`, корневой `docker-compose.yml`, `DEAL_DEMO`, демо-эндпоинты, `l_`/`pr_`, `app_settings`,
> «лид» как сущность, старые порты/пути/счётчики тестов) + чтение актуальных доков и сверка с кодом
> (`src/**`, `deploy/compose.*.yml`, `scripts/dev-smoke.sh`, `deploy/observability/grafana/dashboards/`).
> Докер не поднимался, тесты не перезапускались. Предшествующий аудит — `2026-09-10-docs-audit.md`
> (30 расхождений, уже помечен как исторический).
## Сводка
- Найдено новых расхождений: **9** (по таблице ниже).
- Исправлено в актуальных доках: **9**.
- Добавлено исторических пометок: **20** файлов.
- Переписывание содержания исторических артефактов не выполнялось (по правилам задачи).
## Расхождения (файл:строка → в доке → реальность → действие)
| # | Файл:строка | В доке | Реальность | Действие |
|---|---|---|---|---|
| 1 | `docs/superpowers/STATUS.md` ~L4 | «Все этапы **010** выполнены (100%)» | таблица этапов — **012**, «Итого 0–12 = 100%» (строка ниже) | ✅ исправлено на 0–12 |
| 2 | `docs/superpowers/STATUS.md` ~L25 | этап 10 — «**ELK**-дашборды» | стек — Loki + promtail + Grafana (`deploy/observability/grafana/dashboards/Deal-*.json`); Elasticsearch/Kibana нет | ✅ «Grafana/Loki-дашборды» |
| 3 | `docs/superpowers/STATUS.md` ~L54 | «Настройки (ключи **AI/Telegram** enc:, промпты, валюты)» у тенанта | ключи Telegram — глобально у оператора (`public.global_settings`); у тенанта только подключение аккаунта | ✅ «ключи AI enc: …; Telegram-ключи — глобально у оператора» |
| 4 | `docs/superpowers/STATUS.md` ~L134 | «судьба legacy `docker-compose.yml`» (открытый вопрос) | файл перенесён в `archive/leadradar-legacy/` (2026-09-10) | ✅ «перенесён в `archive/leadradar-legacy/`» |
| 5 | `docs/superpowers/STATUS.md` ~L141 | «Core-тесты **1245/1245**» | актуально **1275/1275** (в том же разделе ниже уже 1275) | ✅ исправлено на 1275/1275 |
| 6 | `docs/superpowers/STATUS.md` ~L167 | «реестр id-стадий `ProjectStages`» | с этапа 9 каталог — `CardsDefaultContainers` (`Deal.Modules.Cards/Application/CardsDefaultContainers.cs`) | ✅ аннотировано «(с этапа 9 — `CardsDefaultContainers`)» |
| 7 | `docs/superpowers/STATUS.md` ~L7, ~L94 | «dev-smoke **12/12**» (в двух местах) | `scripts/dev-smoke.sh` выполняет **14** проверок (config + 6 контейнеров + login/status/containers/create/list/trash/ML-флашер); таблица этапа 9 уже фиксирует `PASS=14` | ✅ исправлено на 14/14 |
| 8 | `docs/technical/Техническая-документация-Дейл.md` §8 ~L319 | `dev-smoke.sh`: «… → `/api/tg/status`**simulate-lead** → флашер MlOutbox …» | скрипт: `/api/tg/status``POST /api/cards` → trash → флашер MlOutbox | ✅ заменено на `POST /api/cards` → trash |
| 9 | `docs/user-guide/Инструкция-пользователя-Дейл.md` ~L6, L32-34, L41, L262 | «dev/демо-окружение», «демо-пространство с входом `admin`/`admin`» | демо удалено; dev-seed создаёт bootstrap-тенанта `Default` (env `DEAL_BOOTSTRAP_LOGIN/PASSWORD`, дефолт `admin`/`admin`) | ✅ «Dev-окружение», «bootstrap-пространство `Default`» |
## Добавленные исторические пометки
Единая шапка: `> Исторический документ этапа N. Актуальное состояние — docs/superpowers/STATUS.md и docs/technical/Техническая-документация-Дейл.md.`
Планы (`docs/superpowers/plans/`, 15 файлов):
`2026-09-04-channel-discovery.md` (план Discovery прототипа LeadRadar),
`2026-09-05-deal-roadmap.md` (roadmap этапов 07),
`2026-09-05-deal-scaffold.md` (этап 0),
`2026-09-05-deal-stage1-tenancy.md``2026-09-05-deal-stage7-saas.md` (этапы 17),
`2026-09-09-deal-stage9-unified-card.md` (этап 9),
`2026-09-10-deal-stage10-operator-analytics.md` (этап 10),
`2026-09-10-deal-stage11-i18n.md` (этап 11),
`2026-09-10-deal-stage12-observability-hardening.md` (этап 12).
Ревью (`docs/superpowers/reviews/`):
`2026-09-08-code-quality-review.md` (этап 8),
`2026-09-10-tz-compliance-audit.md` (аудит соответствия ТЗ),
`2026-09-10-docs-audit.md` (аудит документации; добавлена ссылка на текущий отчёт).
Специи/архитектура:
`docs/superpowers/specs/2026-09-04-channel-discovery-design.md` (дизайн Discovery прототипа),
`docs/architecture/2026-09-05-deal-architecture-design.md` (архдизайн-черновик),
`docs/architecture/2026-09-09-unified-card.md` (дизайн единой карточки, этап 9).
Не тронуты по существу (актуальны): `docs/architecture/2026-09-10-unified-api-contract.md`,
`docs/architecture/2026-09-10-operator-analytics-contract.md`.
## Проверено и сходится
- **Единый API**: `/api/cards` + `/api/containers`; `api-map` §1/§3.5 корректно фиксирует удаление
`/api/leads|projects|boards|columns` и переименование `new_lead → new_card`; ссылки на «бывшие» домены —
в контексте «удалено», а не как действующие.
- **Ключи Telegram**: spec §4.1/§8, user-guide §3, api-map §6, technical §13.7/§13.10 — везде у оператора
(`/api/operator/settings/telegram-keys`, `public.global_settings`).
- **Оператор-консоль/активация**: `#/operator`, `#/join?code=…` — spec §10, user-guide §11, technical §13.10,
api-map — совпадают.
- **Порты**: core 5080/5082, telegram 5101, ai 5102, ml 5103, metrics 9464, postgres 5433, minio 9000/9001,
grafana 3001, prometheus 9090 — совпадают между spec/user-guide/technical/api и compose-файлами.
- **Core-тесты**: 1275 (technical §13.6/§16, STATUS таблица/итоги) — противоречий в актуальных доках нет.
- **Префиксы id**: `c_` (единый) — api-map §4.1, technical §11/§12/§8; `l_`/`pr_` в актуальных доках отсутствуют
(остались только в помеченных исторических разделах и внешних исторических артефактах).
- **`app_settings`**: в актуальных доках нет; актуальная таблица — `global_settings` (`public`).
- **Исторические артефакты**: `Boards`/`ProjectCards`/`Deal.Modules.Projects`/`ProjectStages`/`DEAL_DEMO`/
демо-ручки/`docker-compose.yml` встречаются только в документах, получивших историческую пометку.
## Осталось спорным / намеренно не тронуто
1. **Ссылка из spec на исторический архдизайн.** `docs/spec/ТЗ-дейл-новая-архитектура.md` (шапка)
указывает среди связанных `docs/architecture/2026-09-05-deal-architecture-design.md` — документ теперь
помечен историческим. Формально не ошибка (файл существует, помечен), но при следующей редакции ссылку,
возможно, стоит заменить на `2026-09-10-unified-api-contract.md`.
2. **`tgKeys` в historical §13.4b технического дока** (`GET /api/settings` перечисляет `tgKeys`): раздел
помечен историческим (§13 шапка + заметка §13.4a о переносе ключей к оператору). По правилам задачи
содержание исторических разделов не переписывалось.
3. **Счётчики тестов сервисов** (telegram 125, ai 52, ml 38): не перепроверялись кодом/прогоном
(запрет на долгие процессы); в актуальных доках они не противоречат друг другу.
4. **«Live SaaS 15/15»** — цифра из исторических приёмок, независимо не подтверждалась.
5. **Число операторских ручек (25)** в api-map §5 — подсчёт по `Endpoints/Operator*` + `/api/join`;
группировка может отличаться от авторской (ранее было 21). Не перепроверялось.
6. **Исторический журнал §11 техдока** (этапы 17) и §13.4c/4d/4e намеренно сохраняют легаси-термины
под пометками; сведение их в ссылки на §3 — задача следующей редакции, а не этой подбивки.
7. **`docs/architecture/2026-09-10-*`** (контракты) по условию задачи не редактировались; они актуальны.