SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/ Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue, контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер), Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог). Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0, тесты 1340/130/52/38/9 зелёные.
45 KiB
Аудит соответствия ТЗ «Дейл (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.5–3 с / 3–6 с), 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:130–155) |
| 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 |
Найденные пропуски/расхождения
❌ Отсутствует
- §8.12 «Внешний вид» (настройки оформления). В
SettingsView.vueнет вкладки/раздела внешнего вида; тема одна (тёмная,style.css@theme). Отдельной настройки «внешний вид» не найдено.
⚠️ Частично
- §8.5 / E11 «проверка ML на канале».
POST /api/ml/candidates(MlEndpoints.cs:131–141) возвращает{items: []}с комментарием «До этапа 6 telegram-данных нет» — устаревшая заглушка;POST /api/ml/apply(MlEndpoints.cs:144–155) всегда отвечает 404 «Исходное сообщение не найдено». Реального разбора сообщений канала/ручного применения решения ML нет, хотя telegram-данные в системе уже есть (проверка на сообщении —POST /api/ml/predict— работает). - §5.14 «Глобальные исключения по ключевым словам/технологиям/бюджету/локации». Глобальных настроек
такого исключения в
SettingsKeysнет: есть толькоstopPhrases(стоп-фразы) и per-columnexclude(ColumnExclusions.cs). Исключения уровня «технология/бюджет/локация» как общий фильтр не найдены. - §4.1/§8.1 ключи Telegram. Хранятся как настройка тенанта
tgKeys(TelegramKeysService.cs) и вводятся в UI тенанта (TelegramTab.vue). ТЗ требует, чтобыapi_id/api_hashзадавал оператор глобально — глобальной операторской настройки/ручки нет. - §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:232–276,store/settings.js:351–406,store/operator.js:356), не через locale-aware i18n-форматтеры. - §6.3 состав фильтров колонки.
ContainerRulesDto=Mode/Direction/Keywords/Stack/Grade/Exclude/Budget. ТЗ перечисляет также «уровень/цена/локация/тип» отдельными опциями — явных групп нет (частично покрываютсяdirection/keywords). - §6.6 «открыть исходник» как быстрое действие карточки. На
Card.vueесть комментарий/корзина/контакт, но ссылки «открыть исходник» нет — она доступна только вCardDrawer.vueиProcessingView.vue. - §10.2 health очередей.
/api/operator/healthпроверяет БД и сервисы ml/ai/telegram, но глубины очередей (пайплайн, MlOutbox) в JSON health не отдаёт — они только в метриках (DealMetricsCollector.cs→/metrics). - §10.5 подозрительная активность. Специализированного детектора/ленты подозрительной активности по
логам безопасности не найдено; есть лишь счётчик
failedLoginsв обзорной аналитике и общие Grafana-дашборды. - §11.6 Cloudflare. В репозитории нет конфигурации/интеграции Cloudflare (edge — Caddy,
deploy/caddy/Caddyfile). Требование периметра, вне кода приложения. - §7.1 «автопрокрутка» очереди. Реализована как периодическое авто-обновление списка (~2.6 с,
ProcessingView.vue), а не как буквальная авто-прокрутка. Семантически покрывает требование, но не дословно.
Дефекты/легаси, замеченные при проверке (не пункты ТЗ, но влияют на заявленные функции)
NotifyTab.vue— сломан список активных напоминаний.const holdReminders = computed(() => state.projectCards.filter(...))(settings/NotifyTab.vue:6–8), при этомstate.projectCardsбольше нигде вsrc/не определяется (grep даёт ровно одно совпадение — этот файл). После этапа 9 (projectCards/stageупразднены) обращение кstate.projectCards.filterдаётundefined.filter→ ошибка рендера вкладки «Уведомления».- Легаси-артефакты LeadRadar. В корне остались
docker-compose.yml(сервисыapp/ml/minioстарого стека), каталогbackend/(pythonapp/) иmlservice/(python). Текущая архитектура —deploy/compose.*.ymlsrc/{core,ai,ml,telegram}-service. Прямого нарушения ТЗ нет, но это риск путаницы (в STATUS.md «судьба legacydocker-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 |
| §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 зелёные.