Перевести ядро на единый контракт источника

Карточка, очередь и отсев работают с SourceItem (SourceRef + SourceContent); Telegram-поля убраны из домена, персистентности, конвейера и wire, остались только в адаптере приёма. Tenant-миграции пересозданы с нуля. Фронт переведён на generic source/content с просмотрщиком вложений.
This commit is contained in:
Rustam Khalimov
2026-09-11 14:58:54 +03:00
parent 770dba7257
commit ebc761d40e
94 changed files with 4150 additions and 9398 deletions
+14 -2
View File
@@ -1,7 +1,19 @@
# Дейл (Deal) — Статус разработки и прогресс
> Обновляется в конце каждого захода. Проект НЕ git — это главный борд состояния.
> Дата последнего обновления: 2026-09-10. **Все этапы 0–12 выполнены (100%)** — см. roadmap
> Обновляется в конце каждого захода. Проект в git (ветка `main`, коммиты на русском) — борд состояния + git-история.
> Дата последнего обновления: 2026-09-11.
>
> **2026-09-11 — единый контракт источника (generic source).** Ядро (домен Cards, Storage-сервис,
> персистентность, конвейер, wire, фронт) переведено с Telegram-полей карточки на generic-тип
> `SourceItem` (`SourceRef` + `SourceContent`, вложения — `DataRef` → общий Storage). Дашборды/канбан/конвейер
> больше не знают о Telegram; Telegram-специфика — только в тонком адаптере приёма. Tenant-миграции
> пересозданы с нуля (init). Ядро: build 4 sln 0/0, `Deal.Tests.Unit` **1277/1277 PASS**, фронт `build` +
> `lint:i18n` зелёные. Детали — `docs/superpowers/specs/2026-09-11-source-contract-design.md`.
> Осталось (в backlog): `GET /api/cards/{id}/source` + `ISourceContentProvider`, выгрузка вложений
> telegram-адаптером в Storage, `TelegramSourceContentProvider`, перенос оставшейся Telegram-специфики
> (`TelegramStore`, `Dialogs`/`TgMessages`, Discovery) в telegram-сервис.
**Все этапы 0–12 выполнены (100%)** — см. roadmap
> `docs/superpowers/plans/2026-09-05-deal-roadmap.md`, план этапа 10
> `docs/superpowers/plans/2026-09-10-deal-stage10-operator-analytics.md` и ledgers в `.superpowers/sdd/`.
> Live-приёмки на Docker Desktop выполнены: dev-smoke 14/14, SaaS-контур 15/15, prod-контур+mTLS+
@@ -1,6 +1,6 @@
# Дизайн: единый контракт источника + общий Storage-сервис данных
Дата: 2026-09-11. Статус: на согласовании (контракт не плодит типы вложений; файлы — в общем Storage).
Дата: 2026-09-11. Статус: реализовано в ядре (домен, Storage-сервис, персистентность, конвейер, wire, фронт); адаптер/провайдер telegram-сервиса и Storage-выгрузка — следующие шаги. Контракт не плодит типы вложений; файлы — в общем Storage.
## 1. Принцип
@@ -127,3 +127,45 @@ API ядра: `GET /api/cards/{id}/source` → generic контент.
6. Frontend: generic источник + универсальный просмотрщик.
7. Telegram: адаптер + провайдер исходника (только в telegram-сервисе) + выгрузка в Storage.
8. Комментарии: убрать упоминания Telegram из ядра и задачи/этапы — везде.
## 9. Реализация: зафиксированные сигнатуры
### Ядро: домен
- `SourceRefs` (Deal.Modules.Cards/Application/Sources): `Empty`, `DefaultHue = "#666"`,
`HueKey = "hue"`, расширения `DedupeKey()` (вид|оригинал|внешний id), `ResolveHue()`.
- `CardSnapshot`: вместо `ChannelName/ChannelHandle/ChannelHue/SourceMsg/SourceDialogId/SourceMsgId`
`SourceRef Source` + `SourceContent Content`; `ReceivedAt` остаётся.
- `CardDto`: вместо `Channel`/`SourceMsg`/`SourceDialogId`/`SourceMsgId`/прежнего `Source`
`SourceRef Source` + `SourceContent Content`; `ReceivedAtMs` остаётся. `CardChannelDto`/`CardSourceDto` удалены.
- `ICardStore.GetCardBySourceAsync(SourceRef source, CancellationToken ct)`.
### Ядро: конвейер
- `QueuedMessage { required SourceItem Item; bool Force; }`.
- `QueueItemDto { string Id; SourceRef Source; SourceContent Content; string Text; string Status;
long MsgAtMs; long QueuedAtMs; bool Force; }` (JsonIgnore на Force).
- `RejectRecord { SourceRef Source; SourceContent Content; string Text; long MsgAtMs;
string DecidedBy; string Stage; string Reason; string Kw; string? DeterministicId; }`
(`DeterministicId = r_{Kind}_{OriginRef}_{ExternalId}`).
- `RejectedItemDto`: `Source`/`Content`, `DecidedBy`/`DecidedByLabel` (решение), остальное как было.
- `IPipelineStore.ExistsDuplicateAsync(SourceRef source, CancellationToken ct)`.
- `PipelineChannelDto` удалён.
### Схема БД (схема тенанта)
- Cards: удалить `ChannelName/ChannelHandle/ChannelHue/SourceMsg/SourceDialogId/SourceMsgId`;
добавить `SourceKind`, `SourceExternalId`, `SourceOriginRef` (text, для запросов), `SourceJson` (text),
`ContentJson` (text), `SourceText` (text). FTS: Title+Summary+SourceText+Contact.
- QueueItems: удалить `DialogId/ChannelName/ChannelHandle/ChannelHue/MsgId`; добавить
`SourceKey` (text, уникальный ключ дедупа), `SourceJson`, `ContentJson`. `Text/MsgAt/Status/Force/CreatedAt/UpdatedAt` остаются.
- RejectedItems: удалить `DialogId/MsgId/ChannelName/ChannelHandle/ChannelHue`; добавить
`SourceKey`, `SourceJson`, `ContentJson`. FTS — по `Text`.
- Миграции tenant: старые удалить, сгенерировать новый init с нуля (данных нет).
### Маппинг источника
- Адаптер Telegram (в ядре — тонкий край приёма): `Kind="telegram"`, `ExternalId=MsgId`,
`OriginRef=DialogId`, `DisplayName=ChannelName`, `Extra["hue"]=ChannelHue` (иначе дефолт),
`ReceivedAt=msgAt`, `Content.Text=Text`, `Content.Author=ChannelName`.
- Дашборды/карточки/конвейер работают только с `SourceRef`/`SourceContent`; Telegram-поля не проходят дальше адаптера.