30 KiB
module-06. Проектная спецификация bitrix-local-app
Статус: целевая production-спецификация MVP.
Портал:han0107.bitrix24.ru; connector:han_mobile_app; Open Line:8.
Источники:README.md,arch-00-glossary.md,arch-01-system-architecture.md,arch-02-api-contracts.md,arch-03-docker-compose-blueprint.md,arch-04-settings-and-content.md,arch-05-agent-development-process.md,arch-06-service-hosting-security.md,module-01-api-backend.md.
1. Назначение и приоритет
Сервис является локальным серверным приложением Bitrix24 и адаптером Open Lines. Он изолирует OAuth и протокол imconnector от api-backend, надёжно доставляет разрешённые сообщения клиента оператору и события оператора обратно в HAN.
При конфликте действуют приоритеты README.md. Настоящий документ детализирует существующие контракты, но не меняет их. Любой новый внешний/internal endpoint сначала фиксируется в arch-02.
Канонический URL канала:
https://han0107.bitrix24.ru/contact_center/connector/?ID=han_mobile_app&LINE=8
2. Ответственность и границы
Сервис отвечает за:
- install/lifecycle локального приложения и OAuth Bitrix24;
- шифрованное хранение и безопасное обновление portal tokens;
imconnector.register,imconnector.activate,event.bind, status/retry setup;- публичный приём
ONAPP*иONIMCONNECTOR*; - tolerant parsing JSON/form/multipart и PHP-style массивов;
- проверку callback, нормализацию, durable inbox, retry и DLQ;
dialog_sessions:external_chat_id(=dialog_id) ↔bitrix_chat_id↔session_id;- идемпотентный outbound
api-backend→imconnector.send.messages; - forward входящих сообщений/файлов и
dialog.closedвapi-backend; imconnector.send.status.deliveryтолько после durable ack API;- health, telemetry, audit технических переходов.
Сервис не отвечает за:
- JWT/пользовательскую авторизацию, Message Safety и App DB;
- хранение истории HAN, realtime и S3;
- CRM Contact/profile sync — зона
bitrix-syncна ВМ2, не этого модуля; - изменение
Dialog.statusвhan_app; - публикацию internal API на edge.
3. Технологический профиль и структура
- Python 3.12+, FastAPI, Pydantic v2, Uvicorn.
- SQLAlchemy 2 async +
asyncpg; Alembic. - Один долгоживущий
httpx.AsyncClientс bounded pool. - PostgreSQL managed, только схема
bitrix_local. - OpenTelemetry и JSON logging.
bitrix-local-app/
app/
main.py
settings.py
api/{public_bitrix,internal_openlines,health,schemas,errors,auth}.py
application/{install,setup,outbound,inbound,forward,delivery_ack}.py
domain/{entities,enums,policies}.py
infrastructure/
bitrix/{client,oauth,connector,parser,normalizer}.py
db/{models,repositories,uow}.py
crypto/{token_cipher,keyring}.py
resilience/{retry,circuit,rate_limit}.py
observability/{logging,metrics,tracing}.py
workers/{inbox_forward,outbox_delivery,setup_reconcile}.py
alembic/
tests/{unit,integration,contract,e2e}/
openapi.yaml
Dockerfile
docker-compose.yml
Router только валидирует/аутентифицирует; use case задаёт транзакцию; Bitrix adapter скрывает внешний payload.
4. Публичные endpoint
Сервис предоставляет следующие endpoint. Корневой nginx публикует первые три; health остаются internal по умолчанию и открываются exact-route только при явно выбранной ops/monitoring policy:
| Method | Path | Назначение |
|---|---|---|
| GET/POST | /bitrix/handler |
probe и callbacks ONAPP*/ONIMCONNECTOR* |
| GET/POST | /bitrix/install |
install callback/probe |
| GET | /bitrix/placement |
минимальный HTML placement |
| GET | /health/live |
liveness; internal по умолчанию |
| GET | /health/ready |
readiness; internal по умолчанию |
GET handler/install возвращает безопасный 200, не раскрывая OAuth/setup. POST принимает только bounded body и разрешённые content types. Placement имеет отдельный CSP frame-ancestors с точным allow-list Bitrix24.
Internal /internal/openlines/v1/* доступны только по Docker/VPC network и не маршрутизируются nginx наружу.
5. Internal Open Lines API
Все вызовы требуют:
Authorization: Bearer ${BITRIX_INTERNAL_API_TOKEN}
X-Request-ID: UUID/ULID
traceparent: optional W3C
Caller api-backend передаёт BITRIX_LOCAL_APP_INTERNAL_TOKEN, значение которого равно BITRIX_INTERNAL_API_TOKEN. Сравнение constant-time.
5.1. POST /internal/openlines/v1/messages
Одна операция — одно сообщение MVP. Idempotency-Key обязателен и равен message_id.
{
"message_id": "uuid",
"external_chat_id": "uuid",
"occurred_at": "2026-07-10T09:00:00Z",
"user": {
"id": "uuid",
"display_name": "Клиент HAN"
},
"message": {
"content_kind": "text",
"text": "Здравствуйте",
"files": []
}
}
Файловый вариант:
{
"message_id": "uuid",
"external_chat_id": "uuid",
"occurred_at": "2026-07-10T09:00:00Z",
"user": {"id": "uuid", "display_name": "Клиент HAN"},
"message": {
"content_kind": "file",
"text": "",
"files": [{
"attachment_id": "uuid",
"name": "document.pdf",
"mime_type": "application/pdf",
"size_bytes": 12345,
"download_url": "https://short-lived-signed-url"
}]
}
}
Правила:
external_chat_idстрого UUID и равен Appdialog_id;textxor один file; unknown fields запрещены;- signed URL не сохраняется в обычные логи и редактируется в durable payload по истечении необходимости;
- PII профиля не требуется; телефон/email не передаются;
- fingerprint строится по стабильным полям без signed query;
- тот же key/fingerprint возвращает прежний результат;
- тот же key с иным fingerprint →
409 idempotency_key_reused.
Успех 200/201:
{
"status": "delivered",
"message_id": "uuid",
"external_chat_id": "uuid",
"bitrix_message_id": "string-or-null",
"dialog_session": {
"bitrix_chat_id": 1807,
"session_id": "sess-42"
}
}
api-backend выставляет delivery_status=delivered только после этого ответа/duplicate result. 202 не считается финальной доставкой в основном синхронном flow.
5.2. GET /internal/openlines/v1/dialogs/{external_chat_id}
Возвращает active mapping:
{
"external_chat_id": "uuid",
"bitrix_chat_id": 1807,
"session_id": "sess-42",
"status": "open",
"updated_at": "2026-07-10T09:00:00Z"
}
404 — mapping отсутствует/soft-deleted. Пользовательская PII не возвращается.
5.3. GET /internal/openlines/v1/status
Возвращает безопасный статус portal OAuth, connector registration/activation, event bindings, worker backlog и circuit state; токены и raw Bitrix response исключены. 200 может иметь status=degraded; 503 — нет usable OAuth/БД.
5.4. POST /internal/openlines/v1/setup/retry
Идемпотентно запускает reconcile register → activate line 8 → event.bind. Одновременно разрешён один run по portal advisory lock/DB lease. Ответ содержит per-step status. Endpoint ops-only с тем же Bearer и дополнительным service rate limit.
6. Outbound: HAN → Open Lines
- Аутентифицировать caller и зарезервировать
outbound_messagesпоmessage_id. - При completed вернуть сохранённый sanitized result.
- Собрать
MESSAGESBitrix:user.id,message.id/date/text/files,chat.id. - Вызвать
imconnector.send.messagesсCONNECTOR=han_mobile_app,LINE=8. - Извлечь
CHAT_ID, sessionID, Bitrix message id из допускаемых вариантов ответа. - В одной транзакции upsert
dialog_sessions, записать result, statusdelivered. - Вернуть ack API.
Ambiguous timeout не разрешает слепой повтор без idempotency/reconciliation. Worker сверяет локальный state/session и повторяет только если метод/Bitrix semantics не создадут дубль; иначе manual_review/DLQ. Automatic retry допустим для connect failure до отправки, explicit rate-limit и известных transient ошибок.
7. Install, OAuth и setup
7.1. Install
POST install/handler с ONAPPINSTALL:
- parse и strict validate
auth; - проверить expected portal domain
han0107.bitrix24.ru, HTTPSclient_endpoint,member_id; - сохранить tokens до внешнего setup;
- создать
install_runs; - выполнить setup идемпотентно;
- вернуть
installedлибоinstalled_with_errors; partial setup не теряет OAuth.
ONAPPUNINSTALL помечает portal installation uninstalled, запрещает outbound и планирует revocation/retention. В отличие от прототипа, usable tokens не остаются active.
7.2. Token storage и encryption
access_token,refresh_token,application_tokenшифруются application-level envelope encryption (AES-256-GCM или эквивалент AEAD).- Master key только secret env/mount:
BITRIX_TOKEN_ENCRYPTION_KEY; в БД —ciphertext,nonce,key_version. - AAD связывает ciphertext с
member_id, portal domain и token type. - Поддерживается keyring current+previous для rolling rotation и re-encryption job.
- Токены никогда не логируются, не экспортируются в metrics/traces и не возвращаются API.
- DB/TLS и backup encryption остаются дополнительными слоями.
7.3. Refresh
- refresh заранее, когда
expires_at - now <= skew(ориентир 60 с); - single-flight на portal через DB advisory lock/lease;
- POST только на allow-listed
https://oauth.bitrix.info/oauth/token/; - refresh token rotation сохраняется атомарно;
- при
expired_token— максимум один refresh+replay; invalid_grantпереводит installation вreauth_required, readiness degraded, outbound fail-closed;- timeout/retry bounded; secret/client credentials не попадают в exception text.
7.4. Connector setup
Используемые методы:
imconnector.register:ID=han_mobile_app, name/icon,{BITRIX_PUBLIC_BASE_URL}/placement;imconnector.activate: connector,LINE=8,ACTIVE=1;event.bind:OnImConnectorMessageAdd,OnImConnectorDialogStart,OnImConnectorDialogFinish;imconnector.statusдля reconcile/readiness;imconnector.send.messages;imconnector.send.status.delivery.
Каждый setup step хранит desired/observed state, attempts и safe error. Повтор не создаёт duplicate binding; если API Bitrix не гарантирует это, сначала проверяется status/list binding.
8. Webhook parsing и безопасность
Поддерживаются JSON, form-urlencoded, multipart и PHP-style keys/числовые dict. Parser:
- ограничивает body/header/field count, nesting, array/message count и строковые длины;
- NFKC не применяется к opaque ids/tokens;
- не сохраняет неизвестный raw body без redaction;
- принимает только известные events; прочие безопасно
ignoredс metric; - проверяет connector
han_mobile_app, line8, expected member/domain; - проверяет
auth.application_tokenconstant-time против расшифрованного portal token и/илиBITRIX_APPLICATION_TOKEN; - не доверяет IP как единственной аутентификации, но nginx edge limit/allow policy дополняет token;
- всегда отвечает достаточно быстро после durable insert, чтобы Bitrix retry не создал storm.
Невалидный security token не маскируется как успешная обработка в telemetry: внешний ответ может быть нейтральным, но audit/metric фиксируют reject. Callback secret и payload не логируются.
9. Нормализация и inbox-контракт API
Owned receiver находится в api-backend:
POST http://api-backend:8000/internal/openlines/v1/inbox
Authorization: Bearer ${BITRIX_API_FORWARD_TOKEN}
Значение равно BITRIX_API_INBOX_TOKEN на API.
message.new:
{
"event_id": "stable-opaque",
"event_type": "message.new",
"external_chat_id": "uuid",
"bitrix_message_id": "string",
"occurred_at": "2026-07-10T09:00:00Z",
"message": {
"text": "Ответ оператора или пустая строка",
"files": [{
"name": "scan.pdf",
"mime_type": "application/pdf",
"size_bytes": 12345,
"download_url": "https://..."
}]
}
}
dialog.closed:
{
"event_id": "stable-opaque",
"event_type": "dialog.closed",
"external_chat_id": "uuid",
"bitrix_message_id": null,
"occurred_at": "2026-07-10T09:00:00Z",
"message": null
}
event_id обязателен согласно допущению module-01 A2; предпочтительно используется Bitrix event/message/session id, иначе versioned SHA-256 стабильных полей. message.new дополнительно unique по (external_chat_id, bitrix_message_id).
Пустые text+files отклоняются. URL файла передаётся только API; API защищается от SSRF, скачивает с лимитами и сохраняет в S3-data. Local app не скачивает/не хранит файл.
10. Delivery ack входящего события
Критический инвариант:
Bitrix webhook → durable inbox → API 201/duplicate 200/204
→ только затем imconnector.send.status.delivery
Ack запрещён при timeout/5xx/неприменённом 404 API. Если API commit успешен, но HTTP response потерян, повтор forward получает duplicate ack, после чего delivery status безопасно отправляется. Ack имеет собственный outbox/retry. Ошибка ack не повторяет application события в API.
11. Inbox, outbox, DLQ и backoff
Inbox
Webhook transaction сохраняет event, normalized payload/fingerprint и initial status. Worker использует FOR UPDATE SKIP LOCKED, lease и heartbeat.
States:
received → forwarding → api_acked → ack_pending → completed
↘ retry
received/forwarding/retry → dead_letter
Outbound messages
States: received | sending | delivered | retry | ambiguous | dead_letter. Unique message_id; payload versioned; signed URLs не должны переживать TTL — при retry API обязан дать актуальный URL по согласованному recovery контракту либо операция уходит в reconciliation.
Backoff
- exponential full jitter, ориентир 1, 2, 4, 8… max 300 с;
- учитывать
Retry-AfterBitrix/API; - max attempts и max age — infra env;
- permanent 4xx/schema/auth не повторяются автоматически;
- DLQ содержит safe error code, не token/raw PII;
- replay — ops runbook/CLI с audit, не публичный endpoint MVP.
12. PostgreSQL bitrix_local
Общие правила: UUID/timestamptz, schema-qualified DDL, soft delete для прикладных records, технические queue rows архивируются/удаляются по retention. Runtime role bitrix_local_app; отдельная migration role. Прямого доступа к han_app нет.
12.1. portal_installations
id, member_id unique, domain, client_endpoint, encrypted token columns, expires_at, scope, key_version, install_status, setup_status, last_refresh_at, last_error_code, common fields.
Indexes: unique active member_id; unique active normalized domain. MVP разрешает только один active expected portal.
12.2. connector_setup
id, portal_id, connector_id, line_id, registered, activated, bindings_json, desired_version, observed_at, next_retry_at, attempt_count, lease/error fields. Unique (portal_id, connector_id, line_id).
12.3. dialog_sessions
id, external_chat_id uuid, bitrix_chat_id bigint NULL, session_id varchar NULL, portal_id, status open|closed, common fields.
Indexes:
- unique active
external_chat_id; - index
(bitrix_chat_id) WHERE record_status='A'; - index
(session_id); (status, updated_at).
Связь с user_id не нужна: идентичность принадлежит App DB.
12.4. inbox_events
id, event_id, event_type, external_chat_id, bitrix_message_id, payload_fingerprint, normalized_json, status, attempts/next/lease, api_ack_status, delivery_ack_status, safe error, timestamps.
Unique event_id; unique partial (external_chat_id, bitrix_message_id); worker index (status,next_attempt_at).
Raw payload хранится только если необходим для forensic, зашифрован/редактирован и с коротким retention; preferred — минимальный normalized payload.
12.5. outbound_messages
id, message_id uuid unique, external_chat_id uuid, request_fingerprint, payload_json, status, bitrix_message_id, response_json, attempts/lease/error/timestamps. Index worker (status,next_attempt_at).
12.6. delivery_ack_outbox
Unique inbox event; status/attempt/next/lease, minimal Bitrix delivery DTO. Не содержит API token.
12.7. install_runs и audit_events
Append-only setup step/results и security/ops actions без tokens/raw payload. BRIN/date indexes при росте.
13. Alembic и транзакции
- Никакого
CREATE TABLE IF NOT EXISTSпри startup. alembic upgrade head— отдельный deploy step.- Expand/migrate/contract, forward-fix; destructive migration только после backup/согласования.
- Smoke upgrade пустой и предыдущей версии.
- Внешний HTTP не выполняется внутри DB transaction.
- Claim → commit lease → external call → finalize under row lock.
- Setup/refresh используют portal-scoped lock.
14. Bitrix rate limits и resilience
- Ограничить concurrency (начально 2 на portal) и локальный token bucket.
- Разделить quotas setup, outbound, ack/status.
- На Bitrix rate-limit учитывать headers/body code и
Retry-After. - Circuit breakers отдельно: OAuth endpoint, portal REST, API forward.
- Timeout: connect 3 с, обычный REST/read 10–15 с, OAuth 10 с; значения infra env.
- 4xx domain/schema не открывает circuit; 429/transient/timeout учитываются по policy.
- Half-open имеет один probe; retry storms предотвращаются jitter/queue concurrency.
- Один
httpxpool; TLS verify обязателен; redirects для token/REST запрещены либо allow-listed.
15. Health
GET /health/live: только процесс/event loop, 200.
GET /health/ready с коротким timeout проверяет:
- PostgreSQL
SELECT 1, expected Alembic revision; - usable active portal OAuth либо сообщает
portal_not_installed; - connector desired state register+line 8+bindings;
- workers heartbeat/lease;
- backlog age/DLQ thresholds;
- forward URL/token configured;
- circuit state.
DB/schema failure → 503. До install сервис может быть 200 degraded или 503 portal_not_installed согласно ops policy; для production traffic выбран 503, liveness остаётся 200. Ответ не делает внешних Bitrix calls на каждый probe — использует свежий cached observed state.
16. Observability
JSON fields: timestamp, level, service.name=bitrix-local-app, module, event, request_id, trace/span id, route, event_type, portal hash/member hash, message/event id hash, attempt, queue age, dependency, status/error code, duration.
Не логируются OAuth/application/service tokens, Authorization, raw callback, message text, phone/email/name, filenames с PII, file/download URL, response body Bitrix.
Metrics:
- HTTP latency/status;
- callback accepted/rejected/duplicate;
- parser variants/errors;
- OAuth refresh success/failure/time-to-expiry;
- connector setup desired/observed;
- outbound success/retry/ambiguous/DLQ;
- inbox depth/oldest age/retry/DLQ;
- API forward and delivery ack;
- Bitrix REST latency/rate-limit/circuit;
- DB pool/lease/readiness.
IDs не metric labels. Trace context передаётся в API; внешний Bitrix call — child span без token/query.
17. Security
- TLS boundary — root nginx; internal HTTP только backend network.
- Internal endpoints не edge-routed, Bearer token обязателен.
- Exact host/domain/connector/line allow-list.
client_endpointиз callback валидируется против portal allow-list для защиты SSRF.- Strict DTO/body limits; parameterized SQL.
- OAuth encryption+key rotation; secrets только env/secret mount.
- Non-root, read-only root fs, tmpfs, dropped capabilities.
- OpenAPI UI off production; committed OpenAPI 3.1 обязателен.
- CORS не нужен; placement не получает secrets.
- Error envelope не раскрывает host/stack/raw dependency response.
- Dependency/image scanning и pinned lock/image.
18. Env
Канонические из arch-04:
BITRIX_DATABASE_URL
BITRIX_CLIENT_ID
BITRIX_CLIENT_SECRET
BITRIX_CONNECTOR_ID=han_mobile_app
BITRIX_CONNECTOR_NAME=HAN Mobile App
BITRIX_OPEN_LINE_ID=8
BITRIX_PUBLIC_BASE_URL=https://tohin.ru/bitrix
BITRIX_APPLICATION_TOKEN
BITRIX_INTERNAL_API_TOKEN
BITRIX_API_FORWARD_URL=http://api-backend:8000/internal/openlines/v1/inbox
BITRIX_API_FORWARD_TOKEN
OTEL_EXPORTER_OTLP_ENDPOINT
APP_ENV
LOG_LEVEL
Предлагаемые infra env, которые до реализации нужно добавить в arch-04:
BITRIX_TOKEN_ENCRYPTION_KEY
BITRIX_TOKEN_ENCRYPTION_KEY_VERSION
BITRIX_HTTP_TIMEOUT_SEC=15
BITRIX_HTTP_MAX_CONCURRENCY=2
BITRIX_RETRY_MAX_ATTEMPTS=10
BITRIX_RETRY_MAX_DELAY_SEC=300
BITRIX_INBOX_RETENTION_DAYS
BITRIX_DLQ_ALERT_AGE_SEC
Business settings здесь не хранятся. Legacy BITRIX_SYNC_FORWARD_* удаляются после migration window и не являются каноническими.
19. Docker и deployment
- service
bitrix-local-app,expose: 8080, безports; - networks
backend,observability; root nginx отдельно; - managed PostgreSQL вне compose, TLS обязательно;
- нет SQLite volume production;
- healthcheck
/health/live; readiness — orchestration/monitoring; - migration one-shot job до rollout;
- graceful shutdown: stop claims, finish in-flight до grace, release leases, close pools;
- stateless filesystem.
Прототипные deploy/nginx/*, certbot/SSL scripts и отдельный compose-stack не переносятся: сертификат и routing принадлежат корневому nginx/compose.
20. Что переиспользуется из прототипа
Концептуально переиспользуются и покрываются новыми тестами:
- tolerant parser JSON/form/multipart и
auth[...]; - преобразование PHP-style
MESSAGESlist/dict; - разделение client/connector/handler/normalizer/session store;
- setup
register → activate → bind; - refresh до expiry и один replay
expired_token; - extraction session
CHAT_ID/ID; application_tokenи Bearer constant-time compare;- deterministic idempotency event key как основа fingerprint;
dialog_sessionsи enrichment;- отключение docs production;
- различение Open Lines и CRM sync.
Обязательно меняется:
/internal/v1/*→ только/internal/openlines/v1/*;- forward envelope → канонический
POST /internal/openlines/v1/inbox; - immediate delivery ack до API запрещён;
- single-attempt forward → durable worker/backoff/DLQ;
- plaintext tokens → AEAD encryption/key rotation;
- sync psycopg2/thread lock → async pool/transactions/leases;
- SQLite и DDL-on-start не используются production;
- отдельный nginx/certbot/compose удаляются из production topology;
/bitrix-internal/edge alias не нужен: internal API не публикуется;- raw payload/error storage/logging минимизируется;
- uninstall деактивирует installation;
- Alembic и OpenAPI 3.1 обязательны.
21. Тестовая матрица
Unit
- parser variants/nesting/limits;
- normalizer message/file/start/finish;
- token encryption/decryption/AAD/rotation;
- fingerprint/idempotency;
- session extraction variants;
- retry classification/backoff/jitter;
- URL/portal validation and redaction.
Integration
- Alembic empty/upgrade;
- concurrent duplicate webhook;
- outbound same/different fingerprint;
SKIP LOCKED, lease expiry, crash recovery;- refresh single-flight;
- setup reconcile;
- DB constraints/soft delete;
- no DDL at startup.
Contract
- all public/internal schemas in committed OpenAPI;
- tokens and paired names with module-01;
message.new/dialog.closedinbox;- API 201/duplicate before delivery ack;
- request-id/trace propagation;
- Bitrix fixture payloads and response variants.
E2E/failure
- install portal → connector visible on line 8;
- text/file send and mapping;
- operator text/file → API → ack;
- duplicate/reordered callbacks;
- API outage, Bitrix 429/5xx/timeout, OAuth expiry/invalid_grant;
- crash at every checkpoint;
- DLQ/replay;
- circuit half-open;
- logs contain no secrets/PII/URLs.
22. Definition of Done
- portal/connector/line fixed and validated;
- public and internal paths exactly match arch-02;
- internal API is unreachable from public edge;
- install/OAuth encryption/refresh/setup reconciliation complete;
- outbound idempotency survives crash/ambiguous response;
- inbox retry/DLQ and ack-after-API invariant proven;
- operator text/files and
dialog.closedcontract-tested; bitrix_localschema, indexes and Alembic migrations tested;- rate-limit/circuit/timeout/graceful shutdown implemented;
- health/metrics/traces/JSON logs secure;
- OpenAPI 3.1 committed and parity checked;
- root Compose starts non-root container without published port;
- runbooks: reinstall, key rotation, OAuth failure, setup retry, backlog/DLQ, migration/rollback;
- no SQLite, startup DDL or separate production nginx.
23. Решения, допущения и TBD
Решения:
- B1: canonical internal prefix только
/internal/openlines/v1. - B2: delivery ack только после API commit/duplicate ack.
- B3: OAuth tokens шифруются application-level AEAD.
- B4: durable PostgreSQL inbox/outbox/DLQ; Redis не требуется.
- B5:
external_chat_id=dialog_id; local app не хранит user profile. - B6: production только managed PostgreSQL + Alembic.
Допущения:
- A1: один active portal
han0107.bitrix24.ruв MVP. - A2: Bitrix fixtures позволят стабильно извлечь event/message/session ids; иначе versioned fingerprint.
- A3: API может повторно выдать актуальный signed file URL при delayed outbound recovery; exact handshake требуется в contract test.
TBD:
- B-TBD1: точные Bitrix REST quotas/headers и safe retry матрица по официальной документации/portal tests.
- B-TBD2: окончательный outbound DTO user display name и file fields в OpenAPI.
- B-TBD3: exact stable
event_idдля dialog events (согласовать с module-01 TBD-3). - B-TBD4: retention/RPO/RTO и DLQ replay authorization.
- B-TBD5: encryption key source/rotation runbook до production.
- B-TBD6: точный CSP
frame-ancestorsplacement. - B7: operator files считаются trusted-channel данными MVP; api-backend применяет MIME/size/audit без Message Safety/AV, residual malware risk принят.