Files
LegacyHUB/docs/ADR-shared-core.md
Vadim Malanov d27dd0ffbb feat: align LegacyHUB with TeamHUB platform contract (D2/D4, assets, security)
Close 12 audit-driven platform-compliance gaps on a single branch.

- D4 dispatch: app/integrations/dispatch_client.py participant `legacyhub`,
  emits LegacyhubDocumentIndexed + AssetDerivativeReady after the indexing
  commit (idempotent uuid5), http_inbox route (reindex/tombstone) with
  audit-based dedupe; docs/dispatch-contract.md. Celery+Redis stays intra-module.
- D2 SSO: app/integrations/identity.py validates X-TeamHub-* + role/scope
  mapper; security.py adds trusted-header enforcement (AUTH_REQUIRE_IDENTITY)
  and a scope check on /search; docker-compose.teamhub.yml (external teamhub_net
  + internal db net, api not host-published); RUNBOOK network/firewall section.
- Asset standard: SearchHit/Citation carry asset_id/owner_module; buckets
  renamed teamhub-legacyhub-* (+quarantine/tmp/exports); purge-by-asset_id with
  legal-hold guard (app/indexing/projection.py); OCR-markdown derivative event.
- audit_log model + Alembic 0003 + record_audit on writes (same transaction).
- Secret masking: app/common/json_logger.py recursive mask wired into structlog
  (+ensure_ascii=False); event payloads redacted before persistence.
- Service X-API-Key mandatory on ingest endpoints (defence-in-depth).
- Port: host API 8000->8050 (collision with SalesHUB/MailHUB resolved),
  container still listens on 8000.
- Config: no plaintext secret defaults; fail-loud in non-dev (no value leak).
- Docs drift: README PG 5440, layered-auth note, 5173 removed from CORS;
  ingest/folder gated by ENABLE_FOLDER_INGEST (410 by default).
- ADRs: layers mapping, shared-core extraction, UI locale (RU-first).

Tests: 78 passing (ruff, compileall, pytest, tsc, vite build, compose config).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:44:15 +03:00

72 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR: Extract OCR / Markdown / Search into a shared `teamhub_core` engine
Status: accepted (staged) · 2026-06-15 · scope: `legacy-knowledge-indexer` + `TeamHUB/Engines`
## Контекст
Платформа (08_DECISIONS D10, CONVENTIONS «Shared engines», plans/LegacyHUB.md §4)
требует вынести проверенный пайплайн LegacyHUB (OCR → Markdown → hybrid search)
в переиспользуемое **stateless** ядро, чтобы QMS-Hub и MailHUB потребляли его, а
не дублировали. LegacyHUB остаётся эталоном и первым потребителем.
## Решение
Ядро `teamhub_core` живёт в `C:\Users\manag\TeamHUB\Engines\engines/teamhub_core/`
(НЕ в этом репозитории, НЕ в `TeamHUB-Platform`) и потребляется как pinned
versioned dependency / git submodule (D10). Извлечение делается поэтапно, чтобы
не дестабилизировать работающий эталон; на каждом шаге тесты зелёные.
### Границы ядра (stateless)
| Пакет ядра | Источник в LegacyHUB | Содержимое |
|---|---|---|
| `ingest` | `app/ingestion/{ocr,docling_extractor,chunker,normalizer,quality,table_processor,figure_processor}.py` | OCRmyPDF, Docling, chunker, quality-flags, нормализация (сохранение ГОСТ/ID) |
| `search` | `app/indexing/{embeddings,reranker,hybrid_search,opensearch_client,qdrant_client}.py` | BGE-M3, reranker, `run_search`=RRF(BM25,dense)+rerank, bootstrap индексов |
| `storage` | `app/storage/{minio_client,artifacts}.py` | put/get + retry, `ensure_artifact` |
| `contracts` | search/citation Pydantic-типы | контракт поиска/цитирования |
| `obs` | `app/common/json_logger.py` | JSON-логи с маскированием (уже app-agnostic) |
Персистентность (`app/db/*`, Alembic), эмиссия событий, имена бакетов/индексов/
коллекций и REST-роуты ОСТАЮТСЯ в модуле.
## Что уже сделано для расцепления (этот репозиторий)
- **Имена параметризованы.** `opensearch_index_chunks`, `qdrant_collection_chunks`,
все `minio_bucket_*` читаются из `Settings` (env-aliases). Дефолты приведены к
платформенному стандарту (`teamhub-legacyhub-*`); потребитель (QMS/MailHUB)
переопределяет их через env, не трогая код. Жёстко вшитых `legacyhub-*` имён в
логике больше нет.
- **`run_search` не зависит от БД.** `app/indexing/hybrid_search.py` импортирует
только контракты (`app/api/schemas`), `Settings` и клиентов индексации — слой
поиска уже stateless по отношению к Postgres/ORM.
- **`obs`-ядро готово.** `app/common/json_logger.py` написан без app-specific
импортов и переносится в ядро как есть.
- **Плейтменеджмент секретов вынесен из дефолтов** (см. `_enforce_secret_policy`).
## Остаточные шаги (staged, координируются с QMS/MailHUB)
1. Создать `Engines/engines/teamhub_core/` с README (public API/CLI, inputs/outputs,
profile/config model, команда проверки, release notes) и версионным тегом
(`teamhub-core-v0.1.0`).
2. Перенести `contracts` (типы поиска/цитирования) из `app/api/schemas` в
`teamhub_core.contracts`; `app/api/schemas` ре-экспортирует их для совместимости.
3. Перенести `search`/`ingest`/`storage`/`obs` в ядро; `run_search` принимает
имена индекса/коллекции/бакетов аргументами/конфигом, а не глобальным
`Settings` модуля.
4. Переключить LegacyHUB на ядро как pinned submodule; `app/indexing`,
`app/ingestion` становятся тонкими адаптерами. Временная vendored-копия — только
с задокументированным сроком удаления.
5. Контракт-тесты ядра единым сьютом (chunker/quality/hybrid/hashing/duplicates).
6. Подключить QMS-Hub (свои бакеты `teamhub-qms-*`, индекс `qms_chunks`, своя PG) и
MailHUB (поиск по письмам/вложениям) как вторых потребителей.
## Последствия
- Физический вынос затрагивает три репозитория (LegacyHUB, Engines, потребители) и
выполняется отдельной скоординированной работой; в этом проходе закрыта
**готовность к расцеплению** (параметризация имён, stateless `run_search`,
app-agnostic obs-core) и зафиксирован план.
- Риск регрессий минимизируется поэтапностью и зелёным тест-сьютом на каждом шаге.
См. также `docs/ADR-layers.md` (маппинг слоёв) и `docs/dispatch-contract.md`.