Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)
This commit is contained in:
@@ -318,7 +318,7 @@ COMMIT
|
||||
return stable user_id
|
||||
```
|
||||
|
||||
Повторный bootstrap безопасен: уникальный ключ согласия не создаёт дубль; `last_login_at` обновляется. Триггеры на `UserIdentity`/`ClientProfile` создают `contact.map_or_create`/`contact.update` в `sync_queue`; application code задач не вставляет.
|
||||
Повторный bootstrap безопасен: уникальный ключ согласия не создаёт дубль; `last_login_at` обновляется. Триггеры на `UserIdentity`/`ClientProfile` создают `contact.map_or_create`/`contact.update`/`contact.deactivate` в `sync_queue`; application code задач не вставляет. Полный trigger/dedup/lease contract задан в [`module-07-bitrix-sync.md`](module-07-bitrix-sync.md), §6.
|
||||
|
||||
#### `POST /api/v1/consents`
|
||||
|
||||
@@ -430,7 +430,7 @@ Success `201` возвращает финальный `MessageResponse`:
|
||||
}
|
||||
```
|
||||
|
||||
На safety deny — `422 message_blocked`; blocked message допустимо сохранять для аудита, но его текст должен храниться по политике минимизации данных (см. решение M8). В той же транзакции backend создаёт отдельную локальную `company`-реплику с безопасным бизнес-текстом для сообщения или документа; эта реплика публикуется в realtime, но не отправляется в Open Lines. На dependency failure — `503/504`; если Message уже создан, его `delivery_status=failed`.
|
||||
На safety deny — `422 message_blocked`; исходный blocked text не сохраняется: применяется M8. В той же транзакции backend создаёт отдельную локальную `company`-реплику с текстом из `text_resources.mnemonic=safety.chat.blocked`; internal `rule_id` клиенту не передаётся. Реплика публикуется как `message.new`, но не отправляется в Open Lines. На dependency failure — `503/504`; если Message уже создан, его `delivery_status=failed`.
|
||||
|
||||
### 6.7. Attachments
|
||||
|
||||
@@ -615,9 +615,9 @@ CHECK consent type: `personal_data | user_agreement | marketing`. Unique `(user_
|
||||
|
||||
### 9.5. `client_profiles`
|
||||
|
||||
`id`, `user_id` UNIQUE FK, `bitrix_contact_id NULL`, `full_name`, `citizenship`, `russian_phone`, `foreign_phone`, `email`, `source_updated_at`, common fields.
|
||||
`id`, `user_id` UNIQUE FK, `full_name`, `citizenship`, `russian_phone`, `foreign_phone`, `email`, `source_updated_at`, common fields. CRM Contact ID в App DB не хранится; canonical mapping принадлежит schema `bitrix_sync`.
|
||||
|
||||
Индексы: unique active `user_id`; partial unique `bitrix_contact_id WHERE bitrix_contact_id IS NOT NULL AND record_status='A'`; `updated_at`. PII поля не включаются в логи и generic audit payload.
|
||||
Индексы: unique active `user_id`; `updated_at`. PII поля не включаются в логи и generic audit payload.
|
||||
|
||||
### 9.6. `dialogs`
|
||||
|
||||
@@ -647,7 +647,7 @@ WHERE record_status='A';
|
||||
|
||||
### 9.7. `messages`
|
||||
|
||||
Поля: `id`, `dialog_id`, `sender_type`, `content_kind`, `text`, `safety_status`, `delivery_status`, `external_message_id NULL`, `client_idempotency_key NULL`, `occurred_at`, common fields.
|
||||
Поля: `id`, `dialog_id`, `sender_type`, `content_kind`, `text`, `safety_status`, `safety_processing_mode` (`standard | mock`), `safety_config_version` bigint, `delivery_status`, `related_message_id NULL` (self-FK), `external_message_id NULL`, `client_idempotency_key NULL`, `occurred_at`, common fields. Safety mode/config version — internal audit fields и не входят в public DTO.
|
||||
|
||||
CHECK:
|
||||
|
||||
@@ -655,24 +655,25 @@ CHECK:
|
||||
- content: `text | file`;
|
||||
- safety: `pending | allowed | blocked` (`needs_review` зарезервирован, не создаётся);
|
||||
- delivery: `accepted | processing | delivered | rejected | failed`;
|
||||
- text message: `text <> ''`;
|
||||
- text message: `text <> ''`, кроме blocked client message после M8 redaction (`text=''` допустим только при `sender_type=client AND safety_status=blocked`);
|
||||
- file message: `text = ''`;
|
||||
- company message: `safety_status='allowed'`;
|
||||
- synthetic safety company-replica: `related_message_id` указывает на blocked client message, `content_kind=text`, `delivery_status=delivered`, `external_message_id=NULL`;
|
||||
- rejected → blocked; delivered → allowed.
|
||||
|
||||
Индексы: `(dialog_id, created_at, id) WHERE record_status='A'`; `(delivery_status, updated_at)` для recovery; unique `(dialog_id, external_message_id)` where external id not null; unique `(dialog_id, client_idempotency_key)` where not null.
|
||||
Индексы: `(dialog_id, created_at, id) WHERE record_status='A'`; `(delivery_status, updated_at)` для recovery; unique `(dialog_id, external_message_id)` where external id not null; unique `(dialog_id, client_idempotency_key)` where not null; unique `(related_message_id) WHERE sender_type='company' AND related_message_id IS NOT NULL`.
|
||||
|
||||
**Решение M3:** исходящее сообщение создаётся до safety со статусами `pending/accepted`, чтобы `safety_tasks` всегда имел FK и crash checkpoint. При начале poll delivery может стать `processing`; клиенту этот промежуточный ответ не отдаётся.
|
||||
|
||||
### 9.8. `message_attachments`
|
||||
|
||||
Поля: `id`, `dialog_id`, `message_id NULL`, `owner_user_id`, `direction` (`client_upload | company_inbound`), `original_file_name`, `safe_file_name`, `mime_type`, `size_bytes`, `checksum_sha256`, `scan_status`, `storage_bucket`, `object_key`, `quarantine_object_key NULL`, `upload_expires_at`, `completed_at`, common fields.
|
||||
Поля: `id`, `dialog_id`, `message_id NULL`, `owner_user_id`, `direction` (`client_upload | company_inbound`), `original_file_name`, `safe_file_name`, `mime_type`, `size_bytes`, `checksum_sha256`, `scan_status`, `storage_bucket`, `object_key`, `quarantine_object_key NULL`, `quarantine_version_id NULL`, `quarantine_etag NULL`, `upload_expires_at`, `completed_at`, common fields.
|
||||
|
||||
Ограничения:
|
||||
|
||||
- `size_bytes > 0`;
|
||||
- SHA-256 — 64 lowercase hex;
|
||||
- scan: `pending | clean | infected | failed`;
|
||||
- scan: `pending | clean | bypassed | infected | failed`; `bypassed` допустим только для file allow с `safety_processing_mode=mock`;
|
||||
- до allow client file находится только в quarantine;
|
||||
- attachment связывается максимум с одним message;
|
||||
- для MVP у message максимум одно active attachment: unique partial `message_id`.
|
||||
@@ -691,12 +692,15 @@ Reserved MVP table: `id`, `user_id`, `name`, `mime_type`, `size_bytes`, `checksu
|
||||
- `message_id` UNIQUE;
|
||||
- `attachment_id NULL`;
|
||||
- `quarantine_object_key NULL`;
|
||||
- `quarantine_version_id NULL`, `quarantine_etag NULL`;
|
||||
- `task_location`;
|
||||
- `processing_mode`, `config_version`, `rules_version NULL`, `last_poll_http_status NULL`;
|
||||
- `status`: `polling | finalizing | completed | failed`;
|
||||
- `deadline_at`, `next_poll_at`, `attempt_count`, `last_error_code`;
|
||||
- `locked_at`, `locked_by`;
|
||||
- timestamps.
|
||||
|
||||
Индексы: `(status, next_poll_at)`, `(deadline_at)`. Worker забирает `FOR UPDATE SKIP LOCKED`. Это не очередь анализа и не заменяет `message-safety`.
|
||||
Индексы: `(status, next_poll_at)`, `(deadline_at)`. Worker забирает `FOR UPDATE SKIP LOCKED`. Это не очередь анализа и не заменяет `message-safety`. Checkpoints `completed|failed` удаляются через 7 дней; active checkpoints — только после terminal reconciliation.
|
||||
|
||||
### 9.11. `delivery_outbox`
|
||||
|
||||
@@ -747,7 +751,9 @@ Append-only: `id`, `event_type`, `actor_type`, `user_id NULL`, `ux_session_id NU
|
||||
- `app_settings` — поля и правила из arch-04;
|
||||
- `text_resources`: `id`, `mnemonic`, `locale`, `text_value`, `sort_order`, common fields; unique active `(mnemonic, locale)`;
|
||||
- `popular_questions`: `id`, `mnemonic`, `locale`, `question_text`, `sort_order`, common fields; unique active `(mnemonic, locale)`;
|
||||
- `sync_queue`, `entity_external_mapping` — shared contract с `bitrix-sync`.
|
||||
- `sync_queue` — shared contract с `bitrix-sync`: migrations и trigger-функция принадлежат App DB/module-01, runtime claim выполняет `bitrix-sync` через минимальные GRANT.
|
||||
- `bitrix_sync.entity_external_mapping` и rebind workflow принадлежат исключительно `bitrix-sync`; `api-backend` их не читает и не изменяет.
|
||||
- существующие `han_app.entity_external_mapping`, `ClientProfile.bitrix_contact_id` и partial index удаляются expand/contract migration после переноса mapping и проверки отсутствия readers.
|
||||
|
||||
## 10. Alembic и транзакции
|
||||
|
||||
@@ -842,30 +848,36 @@ validate current consents and content union
|
||||
enforce rate limits and idempotency
|
||||
for file: lock attachment, require completed/pending and checksum equality
|
||||
create Message(pending, accepted)
|
||||
call POST /internal/safety/v1/messages/check
|
||||
call POST /internal/safety/v2/messages/check
|
||||
|
||||
if 200 allow:
|
||||
finalize_allow()
|
||||
persist response.processing_mode, response.config_version
|
||||
finalize_allow(processing_mode, config_version)
|
||||
elif 403 deny:
|
||||
finalize_deny()
|
||||
elif 203 pending:
|
||||
persist safety_tasks(task_id, deadline)
|
||||
persist response.processing_mode, response.config_version
|
||||
finalize_deny(processing_mode, config_version)
|
||||
elif 202 pending:
|
||||
persist safety_tasks(task_id, Location, deadline, processing_mode=standard, config_version)
|
||||
while monotonic_now < request_deadline:
|
||||
sleep(backoff_with_jitter)
|
||||
poll GET /internal/safety/v1/messages/tasks/{task_id}
|
||||
if 200 allow: finalize_allow() and return
|
||||
if 403 deny: finalize_deny() and return
|
||||
if 400 and code=stub_final_error and verdict=deny and details.terminal=true:
|
||||
finalize_deny() and return
|
||||
poll GET Location
|
||||
if 200 allow: persist processing_mode/config_version; finalize_allow(...) and return
|
||||
if 403 deny: persist processing_mode/config_version; finalize_deny(...) and return
|
||||
if 503 and terminal=true and retryable=false:
|
||||
mark failed and return 503
|
||||
if other 4xx/5xx: return mapped dependency error
|
||||
mark failed, retain checkpoint/quarantine
|
||||
return 504
|
||||
elif 409 and code=safety_request_conflict:
|
||||
alert invariant violation, mark failed, return 500, do not repeat POST
|
||||
else:
|
||||
mark failed
|
||||
return mapped dependency error
|
||||
```
|
||||
|
||||
Polling interval начинается с `MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC`, допускает capped exponential backoff и jitter, но не превышает общий `MESSAGE_SAFETY_TASK_POLL_MAX_SEC`. Клиенту не возвращается `203`. Terminal `400 stub_final_error` — намеренное test-only расширение `module-05`; оно преобразуется в публичный `422 message_blocked`, не считается infrastructure failure и не смешивается с malformed `400`.
|
||||
Polling interval начинается с server `Retry-After`, допускает capped exponential backoff и jitter, но не превышает caller env `MESSAGE_SAFETY_TASK_POLL_MAX_SEC`. Каждый v2 verdict/pending содержит `processing_mode` и `config_version`; MOCK возвращает только sync `200/403`. Клиенту internal mode/config/`202` не возвращаются: public POST сохраняет синхронную семантику. Legacy stub `/v1` с `203`/`stub_final_error` поддерживается только временным adapter-ом до cutover и не является target production path.
|
||||
|
||||
Capability snapshot `/health/ready` допускается кэшировать не дольше 5 с для fast-fail: file требует `files`, text с URL — `links`, text без URL — `text`. При `processing_mode=mock` normal capabilities имеют состояние `bypassed` и не применяются как fast-fail gate. Snapshot не является correctness gate: definitive capability повторно проверяет `POST /check`. При unavailable в standard mode api-backend возвращает public `503 dependency_unavailable`, не создаёт delivery outbox и не меняет status на blocked.
|
||||
|
||||
### 13.2. Final allow
|
||||
|
||||
@@ -874,13 +886,20 @@ Polling interval начинается с `MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC`
|
||||
1. проверить checkpoint;
|
||||
2. copy quarantine object в attachments bucket с conditional/idempotent key;
|
||||
3. HeadObject destination, сверить checksum/size;
|
||||
4. в транзакции изменить attachment на `clean`, storage location на S3-data; message на `allowed/accepted`; создать delivery outbox;
|
||||
4. в транзакции изменить attachment на `clean` для standard mode или `bypassed` для MOCK, storage location на S3-data; message на `allowed/accepted` с фактическим `safety_processing_mode`; создать delivery outbox;
|
||||
5. удалить quarantine object best-effort; при сбое cleanup повторит;
|
||||
6. попытаться синхронно доставить outbox, чтобы исходный POST вернул финальный `delivered`.
|
||||
|
||||
### 13.3. Final deny
|
||||
|
||||
В транзакции: `blocked/rejected`, attachment `infected`; затем delete quarantine best-effort. В Open Lines ничего не отправляется. Realtime `message.status` публикуется, если message уже мог быть виден этому клиенту.
|
||||
В одной транзакции:
|
||||
|
||||
1. client message → `blocked/rejected`, `text` заменяется пустой строкой/безопасным marker по M8;
|
||||
2. attachment при наличии → `infected`;
|
||||
3. создаётся ровно одна synthetic company-replica, связанная `related_message_id`, с `text` из active `text_resources(safety.chat.blocked, locale)`, `allowed/delivered`;
|
||||
4. сохраняются только hash, internal rule/verdict/version в audit.
|
||||
|
||||
После commit quarantine удаляется best-effort. В Open Lines ничего не отправляется. Realtime публикует `message.status` исходного сообщения и `message.new` company-реплики. Public `422` содержит generic error envelope без `rule_id`.
|
||||
|
||||
### 13.4. Timeout/crash recovery
|
||||
|
||||
@@ -892,13 +911,13 @@ claim with lease
|
||||
poll safety by task_id
|
||||
if pending before recovery deadline: schedule next_poll_at
|
||||
if allow: idempotent promote + delivery checkpoint
|
||||
if deny (canonical 403 or test-only terminal 400): idempotent delete + reject
|
||||
if deny (canonical 403): idempotent delete + reject + company replica
|
||||
if budget exhausted: mark task failed, message failed, preserve audit
|
||||
```
|
||||
|
||||
HTTP disconnect не отменяет durable recovery. Клиентский retry с тем же idempotency key получает восстановленный результат либо текущую dependency error. Recovery не принимает решение о типе анализа.
|
||||
|
||||
**Решение M5:** после client-facing timeout recovery budget продолжается ещё 15 минут как техническая константа модуля; до production-load test значение должно быть вынесено в infra env и добавлено в arch-04. Пока это **TBD-2**, код обязан иметь безопасный default и метрику.
|
||||
**Решение M5:** `deadline_at = min(message_safety.expires_at, checkpoint.created_at + HAN_APP_SAFETY_RECOVERY_MAX_SEC)`, initial env = 1200 с. Client-facing wait остаётся 300 с; recovery продолжает без открытого клиентского соединения. Terminal Safety `503 retryable=false` немедленно завершает checkpoint как failed.
|
||||
|
||||
## 14. S3 attachment lifecycle
|
||||
|
||||
@@ -914,23 +933,23 @@ Lifecycle:
|
||||
|
||||
1. `init`: allow-list extension + declared MIME + size; create metadata; presign exact key, MIME, max size, TTL;
|
||||
2. direct PUT client → S3-quarantine;
|
||||
3. `complete`: HeadObject, size/MIME/checksum metadata; checksum при отсутствии trustworthy S3 checksum вычисляется safety service при scan;
|
||||
3. `complete`: HeadObject конкретной version, size/MIME/server checksum; атомарно фиксирует `version_id + ETag + authoritative checksum`;
|
||||
4. message send: attachment ownership/state/checksum;
|
||||
5. allow: copy + verify + DB finalize + quarantine delete;
|
||||
5. allow: conditional copy сохранённой source version с ETag/checksum match + verify + DB finalize + quarantine delete;
|
||||
6. deny: quarantine delete + infected metadata;
|
||||
7. abandoned/failed: cleanup only if expired and no active safety task;
|
||||
7. abandoned/failed: cleanup через 48 ч, только если нет active safety task;
|
||||
8. download: owner check → audit commit → short presigned GET.
|
||||
|
||||
Extension и MIME оба должны быть разрешены; server normalizes filename and sets safe `Content-Disposition`. S3 credentials never reach frontend.
|
||||
|
||||
**Допущение A3:** Selectel S3 может не предоставлять SHA-256 в `HeadObject`; `complete` сверяет клиентский checksum с signed metadata, а authoritative checksum подтверждает Message Safety. Если storage поддерживает checksum header, он обязателен.
|
||||
Presigned PUT обязательно подписывает `If-None-Match: *`, checksum header и `Content-Type`; versioning quarantine включён. Повторный PUT того же key получает `412`. При отсутствии подтверждённой поддержки этих условий выбранным S3 adapter production upload блокируется, а не деградирует до overwrite.
|
||||
|
||||
Inbound operator file:
|
||||
|
||||
- validate count/size/MIME and URL scheme/host policy;
|
||||
- protect against SSRF: no redirects to private/link-local ranges, DNS rebinding checks, max bytes streaming;
|
||||
- download with timeout to temporary stream, never local persistent disk;
|
||||
- optional antivirus policy; Message Safety outbound pipeline не вызывается;
|
||||
- Message Safety/ClamAV не вызываются; остаточный malware-риск доверенного Bitrix24-channel принят для MVP;
|
||||
- upload directly to S3-data attachments;
|
||||
- only then atomically save attachment/message and ack inbox.
|
||||
|
||||
@@ -973,13 +992,19 @@ Outbox worker и synchronous first attempt используют один dispatc
|
||||
|
||||
Миграции создают triggers:
|
||||
|
||||
- insert active `UserIdentity`/`ClientProfile` без mapping → `contact.map_or_create`;
|
||||
- изменение tracked profile/auth-phone fields → `contact.update`;
|
||||
- trigger строит deterministic dedup key;
|
||||
- insert active `UserIdentity`/`ClientProfile` → coalesced `contact.map_or_create`; trigger не читает schema `bitrix_sync`, наличие mapping проверяет worker;
|
||||
- фактическое изменение App-master `UserIdentity.phone_number` → `contact.update`;
|
||||
- переход `UserIdentity` или `ClientProfile` из active в inactive/deleted → `contact.deactivate`;
|
||||
- возврат active записи → coalesced `contact.map_or_create`;
|
||||
- изменения CRM-master `full_name`, `citizenship`, `email` не создают App→CRM задачу;
|
||||
- trigger проверяет значения через `IS DISTINCT FROM`, а не только факт присутствия колонки в `UPDATE OF`;
|
||||
- trigger строит deterministic dedup key, уникальный только среди активных queue rows; завершённая/cancelled/dead-letter запись не блокирует новое событие;
|
||||
- при `current_setting('han.sync_suppress', true)='true'` задача не создаётся;
|
||||
- trigger и business update находятся в одной транзакции.
|
||||
|
||||
`bitrix-sync` получает ограниченные GRANT. Ошибка CRM не откатывает bootstrap и chat. Open Lines не зависит от CRM mapping.
|
||||
`entity_id` всех contact-задач — `UserIdentity.id`; payload содержит только `schema_version`, `user_id`, безопасную причину и source timestamp, но не PII snapshot. Worker перечитывает актуальные identity/profile.
|
||||
|
||||
`bitrix-sync` получает ограниченные column/table GRANT, заданные module-07. Ошибка CRM не откатывает bootstrap и chat. Open Lines не зависит от CRM mapping.
|
||||
|
||||
## 17. Realtime
|
||||
|
||||
@@ -1197,6 +1222,8 @@ Open Lines недоступность отображается как component
|
||||
|
||||
**Решение M8:** blocked message text не нужен продукту после deny. В `messages.text` хранится пустая строка/безопасный redacted marker, а audit хранит только rule/verdict id и hash содержимого. Если регуляторно требуется исходный текст, это отдельное согласованное изменение retention/security.
|
||||
|
||||
Оценка monitor-only semantic rules выполняется контролируемой, аудируемой выборкой из App DB: доступ только у утверждённой роли, выборка ограничена по времени/объёму, purpose фиксируется в audit. Текст не копируется в schema/логи Message Safety; там остаются hash, `rule_id` и version.
|
||||
|
||||
## 24. Docker/runtime
|
||||
|
||||
Service compose:
|
||||
@@ -1205,7 +1232,7 @@ Service compose:
|
||||
- networks: `backend`, `observability`;
|
||||
- env только через `${VAR}` из root `.env`;
|
||||
- healthcheck `/health/live` для процесса; root orchestration учитывает readiness;
|
||||
- depends_on health для Redis/Keycloak/message-safety, но приложение само retry startup dependencies;
|
||||
- depends_on health только для локальных Redis/Keycloak; remote Message Safety не является Compose dependency и проверяется capability-aware на send path;
|
||||
- managed PostgreSQL вне compose, TLS обязателен;
|
||||
- stateless container, без persistent volume;
|
||||
- init process для signal forwarding;
|
||||
@@ -1251,7 +1278,7 @@ Frontend выполняет refresh token grant. API не обновляет tok
|
||||
|
||||
### 25.3. Файловое сообщение
|
||||
|
||||
create/reuse dialog → init → direct PUT quarantine → complete → send file message → safety `203` poll → allow promote → delivery outbox → Open Lines → delivered. При deny quarantine удаляется, Bitrix не вызывается.
|
||||
create/reuse dialog → init → direct immutable PUT quarantine → complete with version/ETag/checksum → send file message → safety `202` poll → allow conditional promote → delivery outbox → Open Lines → delivered. При deny quarantine удаляется, Bitrix не вызывается.
|
||||
|
||||
### 25.4. Ответ оператора
|
||||
|
||||
@@ -1289,7 +1316,7 @@ Bitrix event → local app durable inbox → `POST /internal/openlines/v1/inbox`
|
||||
### 26.3. Contract
|
||||
|
||||
- generated FastAPI OpenAPI matches committed `api-backend/openapi.yaml`;
|
||||
- Message Safety POST `200/203/403`, task poll `203/200/403` и test-only terminal `400 stub_final_error`; проверены различение malformed `400` и mapping terminal `400` → public `422`;
|
||||
- Message Safety v2 POST `200/202/403`, task poll `202/200/403`, terminal failed `503` и conflict `409`; legacy stub adapter тестируется отдельно до cutover;
|
||||
- Open Lines message idempotency and inbox schemas;
|
||||
- settings bridge DTO/token;
|
||||
- common request-id/trace propagation;
|
||||
@@ -1368,15 +1395,15 @@ Bitrix event → local app durable inbox → `POST /internal/openlines/v1/inbox`
|
||||
|
||||
- **A1:** locale API зарезервирован, MVP фактически `ru`.
|
||||
- **A2:** inbox получит стабильный `event_id`; временно возможен deterministic fingerprint.
|
||||
- **A3:** authoritative SHA-256 может подтверждаться Message Safety, если S3 HeadObject его не отдаёт.
|
||||
- **A3:** production S3 adapter подтверждает signed checksum headers, versioning и conditional requests; иначе immutable upload не включается.
|
||||
|
||||
### Требуют согласования
|
||||
|
||||
- **TBD-1:** единый код для protected endpoint до bootstrap (`409` предложен).
|
||||
- **TBD-2:** extended recovery budget после `MESSAGE_SAFETY_TASK_POLL_MAX_SEC`.
|
||||
- **Решение M5:** extended recovery ограничен `HAN_APP_SAFETY_RECOVERY_MAX_SEC=1200` и Safety `expires_at`.
|
||||
- **TBD-3:** добавить `event_id`/`occurred_at` в WS events и `event_id` в inbox OpenAPI.
|
||||
- **TBD-4:** production SLO, RPS, concurrency, RPO/RTO и retention.
|
||||
- **TBD-5:** antivirus policy для файлов оператора, которые не проходят outbound Message Safety.
|
||||
- **Решение M9:** файлы оператора не проходят Message Safety/AV в MVP; только MIME/size/audit, residual malware risk принят.
|
||||
- **TBD-6:** legal retention/erasure для PII, audit, blocked messages и S3-data.
|
||||
- **TBD-7:** точный max WS connections/subscriptions/frame и queue size.
|
||||
- **TBD-8:** G10 — окончательный DTO/mapping public app-config при оформлении OpenAPI.
|
||||
|
||||
Reference in New Issue
Block a user