Files
han-app/modules/module-06-bitrix-local-app.md
T

30 KiB
Raw Blame History

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, module-01-api-backend.md, прототип ../../HAN_chat/bitrix-local-app/README.md и ../../HAN_chat/deploy/init-managed-postgres.py.

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_idsession_id;
  • идемпотентный outbound api-backendimconnector.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;
  • изменение 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 и равен App dialog_id;
  • text xor один 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

  1. Аутентифицировать caller и зарезервировать outbound_messages по message_id.
  2. При completed вернуть сохранённый sanitized result.
  3. Собрать MESSAGES Bitrix: user.id, message.id/date/text/files, chat.id.
  4. Вызвать imconnector.send.messages с CONNECTOR=han_mobile_app, LINE=8.
  5. Извлечь CHAT_ID, session ID, Bitrix message id из допускаемых вариантов ответа.
  6. В одной транзакции upsert dialog_sessions, записать result, status delivered.
  7. Вернуть 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:

  1. parse и strict validate auth;
  2. проверить expected portal domain han0107.bitrix24.ru, HTTPS client_endpoint, member_id;
  3. сохранить tokens до внешнего setup;
  4. создать install_runs;
  5. выполнить setup идемпотентно;
  6. вернуть 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, line 8, expected member/domain;
  • проверяет auth.application_token constant-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-After Bitrix/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 1015 с, OAuth 10 с; значения infra env.
  • 4xx domain/schema не открывает circuit; 429/transient/timeout учитываются по policy.
  • Half-open имеет один probe; retry storms предотвращаются jitter/queue concurrency.
  • Один httpx pool; 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 MESSAGES list/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.closed inbox;
  • 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.closed contract-tested;
  • bitrix_local schema, 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-ancestors placement.
  • B-TBD7: antivirus policy operator files остаётся у api-backend.