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>
61 lines
2.4 KiB
Markdown
61 lines
2.4 KiB
Markdown
# LegacyHUB — dispatch participant contract
|
|
|
|
Status: draft · 2026-06-15 · D4 / 08_DECISIONS, INTER_MODULE_CONTRACT §2
|
|
|
|
LegacyHUB joins the shared platform RabbitMQ via `dispatch-api` (not directly).
|
|
Internal background work stays on Celery+Redis; this contract covers only the
|
|
inter-module bus.
|
|
|
|
## Participant
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| `participant_code` | `legacyhub` |
|
|
| `participant_type` | `service` |
|
|
| Auth to dispatch-api | `X-API-Key` (env `DISPATCH_API_KEY`) |
|
|
| Delivery mode (inbound) | `http_inbox` → `POST /api/v1/dispatch/inbox` + business-confirm |
|
|
| Inbox auth | service `X-API-Key` (`require_service_api_key`) |
|
|
|
|
Configuration: `DISPATCH_ENABLED`, `DISPATCH_API_URL`, `DISPATCH_API_KEY`,
|
|
`DISPATCH_PARTICIPANT_CODE`, `DISPATCH_TIMEOUT_SECONDS`.
|
|
|
|
## Envelope (INTER_MODULE_CONTRACT §2.4)
|
|
|
|
```json
|
|
{
|
|
"message_id": "<uuid4, unique per send>",
|
|
"event_id": "<uuid5 over the business key, idempotent>",
|
|
"card_uid": "<asset_id or document_id>",
|
|
"message_type": "<Module><Event>",
|
|
"participant_code": "legacyhub",
|
|
"created_at": "<ISO-8601 UTC>",
|
|
"body": { "...": "JSON only — no binary, no presigned URLs, no local paths" }
|
|
}
|
|
```
|
|
|
|
## Published events
|
|
|
|
| `message_type` | When | Body (JSON) |
|
|
|---|---|---|
|
|
| `LegacyhubDocumentIndexed` | After a document reaches `INDEXING_COMPLETED` (post-commit) | `document_id`, and for asset ingests `asset_id`, `owner_module`, `owner_record_type`, `owner_record_id`, `manifest_version` |
|
|
| `AssetDerivativeReady` | After OCR/markdown derivative is produced for an asset ingest (respects `ingest_options.emit_events`) | `event_type=AssetDerivativeReady`, `asset_id`, `owner_*`, `derivative_type=ocr_markdown`, `content_type=text/markdown`, `source_asset_sha256`, `generator{pipeline,version}` |
|
|
|
|
Idempotency: `event_id = uuid5(namespace, business-key)`, so re-emits of the same
|
|
indexing/derivative are de-dupable by consumers.
|
|
|
|
## Subscriptions (inbound)
|
|
|
|
| `message_type` | Action |
|
|
|---|---|
|
|
| `ReindexRequested` / `LegacyhubReindexRequested` | Resolve `document_id` (directly or via `asset_id`) and enqueue the Celery reindex task |
|
|
|
|
Inbound delivery is at-least-once; the inbox is idempotent by `event_id`
|
|
(recorded in `audit_log` as `dispatch.inbox.processed`).
|
|
|
|
## Boundaries
|
|
|
|
- No direct RabbitMQ from this module — only `dispatch-api`.
|
|
- No binary payloads / presigned URLs / local filesystem paths in any body
|
|
(10 §7). Originals stay owner-module-owned; LegacyHUB references object storage
|
|
+ `asset_id` only.
|