Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)

This commit is contained in:
mi
2026-08-13 18:52:42 +03:00
parent 5100ba9fc3
commit 99605b1c77
144 changed files with 15295 additions and 1120 deletions
+112 -64
View File
@@ -1,6 +1,6 @@
# arch-04. Настройки и изменяемые параметры
> **`.env`** — инфраструктура и секреты. **`app_settings`** (App DB) — единственный источник бизнес-настроек. Имена полей и enum — [`arch-00-glossary.md`](arch-00-glossary.md). Docker Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
> **`.env`** — только несекретная инфраструктурная конфигурация. Production-секреты доставляются отдельно по [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md). **`app_settings`** (App DB) — единственный источник бизнес-настроек. Имена полей и enum — [`arch-00-glossary.md`](arch-00-glossary.md). Docker Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).
## Цель
@@ -8,7 +8,8 @@
| Слой | Где | Что |
|---|---|---|
| **Инфраструктура** | `.env` | подключения, URL, секреты, nginx/TLS, service tokens |
| **Инфраструктура** | `.env` | несекретные host/port/URL, nginx/TLS, режимы и технические параметры |
| **Production-секреты** | Selectel Secrets Manager → `/run/han-chat/secrets`; для no-egress VM — root-owned files | credential-bearing DSN, пароли, private keys, service/webhook tokens, provider credentials |
| **Бизнес-логика** | таблица **`app_settings`** | лимиты, флаги, телефоны, типы файлов, CORS, consent URLs |
| **Настройки SMS runtime** | таблица **`sms.sms_setting`** | sender default, provider timeouts, callback flag, worker intervals |
| **Контент** | `text_resources`, `popular_questions` | тексты UI |
@@ -17,22 +18,31 @@ Managed PostgreSQL **поднимается до** развёртывания п
## Источники настроек
### `.env` — только инфраструктура
### `.env` — только несекретная инфраструктура
Корневой `backend/.env` читается сервисами compose. В репозитории — `.env.example`, не `.env`.
**Допустимо в `.env`:**
- URL сервисов, публичные endpoint, порты;
- строки подключения PostgreSQL, Redis, Keycloak DB;
- секреты: S3, Bitrix OAuth, service tokens, webhook-тokens;
- host/port/database/schema без паролей и токенов;
- параметры **nginx/TLS** и edge rate limits (`NGINX_RATE_LIMIT_*`);
- идентификация Keycloak: realm, audience, public/internal URL;
- переключатель и секрет временного OTP mock (`KEYCLOAK_OTP_MOCK_*`); mock обязателен до прохождения real-SMS rollout gates и запрещён как незаявленный fallback;
- переключатель и client/server keys Yandex SmartCaptcha (`KEYCLOAK_YANDEX_CAPTCHA_*`); сложность остаётся в Yandex Cloud, а server key не попадает в тему/логи;
- переключатели OTP mock и Yandex SmartCaptcha, а также публичный CAPTCHA client key; mock code и CAPTCHA server key являются секретами;
- `SECRETS_SOURCE=selectel|file`, который выбирает утверждённый механизм доставки, но не содержит secret value;
- технические параметры сервисов, пока профильная спецификация не определила service-owned settings; для `sms-service` runtime-параметры уже вынесены в `sms.sms_setting`.
**Запрещено в `.env` (→ только `app_settings`):**
**Запрещено в `.env`:**
- credential-bearing DSN/URL;
- пароли БД, Keycloak bootstrap password и OTP mock/HMAC secrets;
- S3 access/secret keys;
- Bitrix OAuth secrets и application/webhook tokens;
- service tokens внутренних API;
- SMS API key/callback credentials и OTLP auth header;
- TLS private keys и любые иные credentials.
Они доставляются как Compose/systemd secret files по arch-06. Бизнес-параметры ниже хранятся только в `app_settings`:
- включение/отключение OTP, OTP-лимиты для UI/продукта;
- телефон оператора, consent URLs/versions;
@@ -52,7 +62,13 @@ Managed PostgreSQL **поднимается до** развёртывания п
### `text_resources` / `popular_questions`
Контент UI — отдельные таблицы (не `app_settings`). Ключи MVP — TBD (спецификация frontend).
Контент UI — отдельные таблицы (не `app_settings`). Обязательная safety-мнемоника MVP:
| mnemonic | Назначение |
|---|---|
| `safety.chat.blocked` | Generic company-реплика при любом Message Safety deny; текст locale-aware, без раскрытия `rule_id` |
Миграция/seed обязаны создать активную запись минимум для `ru`. Изменение `text_value` не требует redeploy Safety и не меняет API/error code.
---
@@ -131,6 +147,7 @@ chat.attachments.storage=selectel_s3
chat.attachments.upload_mode=presigned_put
chat.attachments.safety_scan_required=true
chat.attachments.presigned_upload_ttl_seconds=600
messages.max_text_length=4000
rate_limit.message_send.per_user=30/minute
rate_limit.message_send.per_dialog=20/minute
@@ -174,21 +191,32 @@ worker.poll_interval_ms=500
worker.lease_seconds=90
```
В `.env` остаются только `SMS_DATABASE_URL`, URL внутренних/внешних сервисов, service tokens, Direct API key и callback credentials. Детальный контракт — `module-11-idgtl-sms.md`.
В `.env` остаются только несекретные URL внутренних/внешних сервисов и технические параметры. `SMS_DATABASE_URL`, service tokens, Direct API key и callback credentials входят в runtime secret catalog. Детальный контракт — `module-11-idgtl-sms.md`.
`<approved>` — обязательный deployment placeholder, а не допустимое production-значение. Перед real mode должны существовать active approved template `auth_otp` с точными placeholders `code`/`ttl_min` и согласованный `senderName`. Отсутствие template/sender делает readiness false.
---
## Service-owned настройки `message-safety`
Runtime policy хранится в версионированной `message_safety.config_versions`, а не в `.env` и не в `han_app.app_settings`. Сюда входят task lease/deadline/attempts, internal rate/pending limits, retention/cache TTL, URL/DNS pipeline limits, ClamAV policy timeout/signature age и enabled file MIME/size policy. Полный schema/seed/activation contract — module-05 §10.1 и §15.
`han_app.app_settings:chat.attachments.*` остаётся бизнес-настройкой api-backend. Message Safety не получает cross-schema read к `han_app`; файл допускается только при пересечении business allow-list, active safety policy и immutable detector manifest. Active policy может сузить manifest, но не добавить parser и не увеличить hard limit.
В env Message Safety остаются только bootstrap/topology/capacity (`APP_ENV`, worker concurrency, DNS resolver, ClamAV/S3/OTLP endpoints); credentials доставляются secret files. Rules/detector versions вычисляются/проверяются по immutable artifacts. Emergency MOCK остаётся в отдельном root-owned mode file и намеренно не переносится в БД.
---
## Пример `.env.example`
Только инфраструктура. Бизнес-параметры — в seed `app_settings`.
Только несекретная инфраструктура и выбор secret source. Бизнес-параметры — в seed `app_settings`, а перечисленные ниже runtime secrets — в конфигурации `han-secrets`, не в этом файле.
```text
# =============================================================================
# Общие
# =============================================================================
APP_ENV=production-like
SECRETS_SOURCE=selectel
API_PORT=8000
LOG_LEVEL=INFO
@@ -199,14 +227,10 @@ HAN_PG_HOST=<managed-pg-private-host>
HAN_PG_PORT=5433
HAN_PG_DATABASE=han_chat
DATABASE_URL=postgresql+asyncpg://han_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
BITRIX_DATABASE_URL=postgresql://bitrix_local_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
BITRIX_SYNC_APP_DATABASE_URL=postgresql://bitrix_sync_user:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
BITRIX_SYNC_DATABASE_URL=postgresql://bitrix_sync_user:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
MESSAGE_SAFETY_DATABASE_URL=postgresql://message_safety_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
SMS_DATABASE_URL=postgresql://sms_user:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>
KEYCLOAK_DB_URL=jdbc:postgresql://<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?user=keycloak_user&password=change-me&currentSchema=keycloak
KC_DB_URL_PROPERTIES=currentSchema=keycloak
# Runtime secrets: DATABASE_URL, BITRIX_DATABASE_URL,
# BITRIX_SYNC_APP_DATABASE_URL, BITRIX_SYNC_DATABASE_URL,
# MESSAGE_SAFETY_DATABASE_URL, SMS_DATABASE_URL, KEYCLOAK_DB_URL.
# Selectel PgBouncer 5433: pool_mode=session; search_path задаётся на уровне ролей.
# Не добавлять options=-csearch_path: pooler отклоняет этот startup parameter.
@@ -245,51 +269,45 @@ KEYCLOAK_INTERNAL_URL=http://keycloak:8080
KEYCLOAK_REALM=han-chat
KEYCLOAK_AUDIENCE=han-chat-api
KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_CODE=1234
KEYCLOAK_YANDEX_CAPTCHA_ENABLED=false
KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY=
KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY=
KEYCLOAK_SMS_SERVICE_URL=http://sms-service:8080
# Runtime secrets: KEYCLOAK_OTP_MOCK_CODE,
# KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY and OTP HMAC/bootstrap credentials.
# =============================================================================
# Redis (I4: раздельные DB index)
# Redis ВМ1 (I4: раздельные DB index)
# =============================================================================
# /0 — api-backend: rate limits, idempotency
# /1 — api-backend realtime/coordination (опционально; можно совместить с /0)
# /2 — message-safety: verdict cache / workers
REDIS_URL=redis://redis:6379/0
REDIS_REALTIME_URL=redis://redis:6379/1
MESSAGE_SAFETY_REDIS_URL=redis://redis:6379/2
# =============================================================================
# Service tokens (internal API) — все переменные только в backend/.env
# Service tokens (internal API) — runtime secret catalog, не .env
# =============================================================================
MESSAGE_SAFETY_SERVICE_TOKEN=change-me
BITRIX_LOCAL_APP_INTERNAL_TOKEN=change-me
BITRIX_API_INBOX_TOKEN=change-me
BITRIX_INTERNAL_API_TOKEN=change-me
BITRIX_API_FORWARD_TOKEN=change-me
BITRIX_SYNC_SERVICE_TOKEN=change-me
KEYCLOAK_SETTINGS_BRIDGE_TOKEN=change-me
SMS_SERVICE_TOKEN=change-me
KEYCLOAK_SMS_SERVICE_TOKEN=change-me
NOTIFICATIONS_TOKEN_PRODUCER_TEST=change-me
# MESSAGE_SAFETY_SERVICE_TOKEN, BITRIX_LOCAL_APP_INTERNAL_TOKEN,
# BITRIX_API_INBOX_TOKEN, BITRIX_INTERNAL_API_TOKEN,
# BITRIX_API_FORWARD_TOKEN, BITRIX_SYNC_SERVICE_TOKEN,
# KEYCLOAK_SETTINGS_BRIDGE_TOKEN, SMS_SERVICE_TOKEN,
# KEYCLOAK_SMS_SERVICE_TOKEN, NOTIFICATIONS_TOKEN_PRODUCER_TEST.
# =============================================================================
# SMS provider (URL и секреты; runtime-параметры — sms.sms_setting)
# SMS provider (секреты runtime secret catalog; параметры — sms.sms_setting)
# =============================================================================
IDGTL_SMS_BASE_URL=https://direct.i-dgtl.ru
IDGTL_SMS_API_KEY=change-me
IDGTL_SMS_CALLBACK_PUBLIC_URL=https://tohin.ru/callbacks/idgtl/sms
IDGTL_SMS_CALLBACK_USERNAME=change-me
IDGTL_SMS_CALLBACK_PASSWORD=change-me
# Runtime secrets: IDGTL_SMS_API_KEY, IDGTL_SMS_CALLBACK_USERNAME,
# IDGTL_SMS_CALLBACK_PASSWORD.
# =============================================================================
# api-backend (интеграции + resilience I2)
# =============================================================================
BITRIX_LOCAL_APP_BASE_URL=http://bitrix-local-app:8080
BITRIX_API_INBOX_PATH=/internal/openlines/v1/inbox
MESSAGE_SAFETY_URL=http://message-safety:8080
MESSAGE_SAFETY_URL=https://processing.internal:8443
MESSAGE_SAFETY_CA_FILE=/run/han-chat/secrets/processing-internal-ca.crt
MESSAGE_SAFETY_API_PREFIX=/internal/safety/v2
MESSAGE_SAFETY_CIRCUIT_FAILURE_THRESHOLD=5
MESSAGE_SAFETY_CIRCUIT_OPEN_SEC=30
BITRIX_LOCAL_APP_CIRCUIT_FAILURE_THRESHOLD=5
@@ -300,35 +318,58 @@ BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC=10
# bitrix-sync
# =============================================================================
BITRIX_SYNC_ENABLED=true
BITRIX_SYNC_CRM_BASE_URL=https://han0107.bitrix24.ru
BITRIX_SYNC_CRM_WEBHOOK_URL=change-me
BITRIX_SYNC_CONTACT_MAP_INTERVAL_SEC=60
BITRIX_SYNC_CONTACT_UPDATE_INTERVAL_SEC=30
BITRIX_SYNC_CRM_MAX_CONCURRENCY=2
BITRIX_SYNC_CONTACT_LIST_BATCH_SIZE=50
BITRIX_SYNC_WEBHOOK_TOKEN=change-me
BITRIX_SYNC_MODE=full
BITRIX_SYNC_PORTAL_HOST=han0107.bitrix24.ru
BITRIX_SYNC_PORTAL_MEMBER_ID=<approved-member-id>
BITRIX_SYNC_PUBLIC_BASE_URL=https://processing.example.ru
BITRIX_WEBHOOK_ALLOWED_CIDRS=<comma-separated-cidrs>
BITRIX_SYNC_CONTACT_USER_ID_FIELD=UF_CRM_...
BITRIX_SYNC_CONTACT_REGISTERED_FIELD=UF_CRM_1778692456
BITRIX_SYNC_CONTACT_CITIZENSHIP_FIELD=UF_CRM_1768493029
BITRIX_SYNC_HTTP_TIMEOUT_SEC=10
BITRIX_SYNC_DB_POOL_SIZE=5
# Hot worker/rate/retry/reconciliation/alert parameters:
# versioned bitrix_sync.settings, не env.
# Runtime secrets: BITRIX_SYNC_DATABASE_URL,
# BITRIX_SYNC_CRM_REST_WEBHOOK_URL,
# BITRIX_SYNC_CONTACT_RECEIVER_TOKEN,
# BITRIX_SYNC_ALERT_RECEIVER_TOKEN,
# BITRIX_SYNC_SERVICE_TOKEN.
# =============================================================================
# bitrix-local-app
# =============================================================================
BITRIX_CLIENT_ID=change-me
BITRIX_CLIENT_SECRET=change-me
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_API_FORWARD_URL=http://api-backend:8000/internal/openlines/v1/inbox
BITRIX_APPLICATION_TOKEN=change-me
# Runtime secrets: BITRIX_CLIENT_ID, BITRIX_CLIENT_SECRET,
# BITRIX_APPLICATION_TOKEN and token encryption key.
# =============================================================================
# message-safety (technical)
# api-backend → Message Safety (ВМ1 caller)
# =============================================================================
# POST check timeout; poll interval/max — бюджет sync-wait внутри POST .../messages (G4)
MESSAGE_SAFETY_POST_TIMEOUT_SEC=5
MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC=2
MESSAGE_SAFETY_TASK_POLL_MAX_SEC=300
MESSAGE_SAFETY_FILE_SCAN_TIMEOUT_SEC=60
MESSAGE_SAFETY_RULES_VERSION=2026-01-01
QUARANTINE_ORPHAN_RETENTION_HOURS=48
HAN_APP_SAFETY_CHECKPOINT_RETENTION_DAYS=7
HAN_APP_SAFETY_RECOVERY_MAX_SEC=1200
# =============================================================================
# message-safety (ВМ2 bootstrap/topology/capacity)
# =============================================================================
# Service runtime policy находится в message_safety.config_versions.
# Runtime secrets: MESSAGE_SAFETY_DATABASE_URL,
# MESSAGE_SAFETY_REDIS_URL=redis://message_safety:<secret>@redis:6379/0,
# MESSAGE_SAFETY_SERVICE_TOKEN, S3 quarantine read credentials.
# Emergency mode находится только в root-owned
# /etc/han-chat/message-safety-mode.env и меняется approved helper-ом.
MESSAGE_SAFETY_WORKER_CONCURRENCY=5
MESSAGE_SAFETY_DNS_RESOLVERS=<VPC-resolver-IP>
MESSAGE_SAFETY_CLAMAV_HOST=clamd
MESSAGE_SAFETY_CLAMAV_PORT=3310
# =============================================================================
# Frontend (nginx)
@@ -344,14 +385,14 @@ SELECTEL_S3_ENDPOINT_URL=https://s3.storage.selcloud.ru
SELECTEL_S3_BUCKET_DOCUMENTS=han-chat-documents
SELECTEL_S3_BUCKET_ATTACHMENTS=han-chat-attachments
SELECTEL_S3_BUCKET_QUARANTINE=han-chat-quarantine
SELECTEL_S3_ACCESS_KEY=change-me
SELECTEL_S3_SECRET_KEY=change-me
SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY=change-me
SELECTEL_S3_QUARANTINE_READ_SECRET_KEY=change-me
# Runtime secrets: SELECTEL_S3_ACCESS_KEY, SELECTEL_S3_SECRET_KEY,
# SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY,
# SELECTEL_S3_QUARANTINE_READ_SECRET_KEY.
# =============================================================================
# Observability
# =============================================================================
# На каждой VM это local Docker DNS; ВМ2 не указывает collector ВМ1.
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
OTEL_SERVICE_NAME_API=api-backend
OTEL_SERVICE_NAME_SMS_API=sms-service
@@ -359,28 +400,28 @@ OTEL_SERVICE_NAME_SMS_WORKER=sms-worker
OTEL_TRACES_SAMPLER=always_on
SMS_METRICS_PORT=9464
OTEL_REMOTE_ENDPOINT=192.168.0.5:4317
OTEL_REMOTE_AUTH_HEADER=
OTEL_REMOTE_TLS_INSECURE=true
OTEL_QUEUE_SIZE=10000
# Runtime secret when configured: OTEL_REMOTE_AUTH_HEADER.
```
S3-клиенты используют только virtual-hosted addressing
(`https://<bucket>.s3.storage.selcloud.ru/<object-key>`). Это часть контракта
presigned URL и CORS Selectel; path-style адресация не поддерживается приложением.
Все переменные — **только** в `backend/.env`. Отдельного хранилища нет.
Production использует отдельные allow-listed env manifests ВМ1 и ВМ2; не все переменные примера копируются на оба хоста. На ВМ2 `han-secrets` под отдельным IAM principal материализует отдельный root-owned файл каждому сервису в `/run/han-chat/secrets`; общий bundle ВМ1/ВМ2 запрещён.
Для production `change-me`, `<...>`, примерные sender/template/API key/callback credentials отклоняются `validate-env`. `IDGTL_SMS_API_KEY` — выданный Direct готовый `TOKEN_1` для Basic, без повторного Base64. Реальный статический egress IP хранится в deployment inventory, а не env; если он не обеспечен NAT/сетевой конфигурацией, `KEYCLOAK_OTP_MOCK_ENABLED=false` запрещён.
Для production placeholders и примерные sender/template/credentials отклоняются `validate-env` и runtime manifest validation. `IDGTL_SMS_API_KEY` в secret catalog — выданный Direct готовый `TOKEN_1` для Basic, без повторного Base64. Реальный статический egress IP хранится в deployment inventory, а не env; если он не обеспечен NAT/сетевой конфигурацией, `KEYCLOAK_OTP_MOCK_ENABLED=false` запрещён.
**Webhook-токены** (публичные callback, не service API): `BITRIX_APPLICATION_TOKEN`, `BITRIX_SYNC_WEBHOOK_TOKEN`.
**Webhook-токены:** `BITRIX_APPLICATION_TOKEN` относится только к `bitrix-local-app`. CRM sync не использует local app/event handler; штатные HTTP-webhook роботы передают отдельные `BITRIX_SYNC_CONTACT_RECEIVER_TOKEN` и `BITRIX_SYNC_ALERT_RECEIVER_TOKEN` в query, поскольку custom Bearer header недоступен. Токены остаются secret-manager values, query исключается из logs/traces, а nginx дополнительно применяет `BITRIX_WEBHOOK_ALLOWED_CIDRS`. `BITRIX_SYNC_CRM_REST_WEBHOOK_URL` — отдельный секрет исходящего CRM REST-доступа sync-service.
`NOTIFICATIONS_TOKEN_<SOURCE>` — индивидуальный секрет продюсера Internal Notifications API. Для seed/smoke используется `NOTIFICATIONS_TOKEN_PRODUCER_TEST`; secret хранится только в deployment env/secret, а `notification_sources.token_hash` — только hash. Инструкция не имеет `notification.instruction.allowed_hosts`: она всегда открывается в новой вкладке, iframe-режима нет.
`NOTIFICATIONS_TOKEN_<SOURCE>` — индивидуальный секрет продюсера Internal Notifications API. Для seed/smoke используется `NOTIFICATIONS_TOKEN_PRODUCER_TEST`; secret хранится только в secret store/runtime secret file, а `notification_sources.token_hash` — только hash. Инструкция не имеет `notification.instruction.allowed_hosts`: она всегда открывается в новой вкладке, iframe-режима нет.
## Namespace переменных Bitrix
- `bitrix-local-app`: `BITRIX_CLIENT_*`, `BITRIX_CONNECTOR_*`, `BITRIX_PUBLIC_BASE_URL`, `BITRIX_DATABASE_URL`, `BITRIX_API_FORWARD_URL`, `BITRIX_APPLICATION_TOKEN` + service tokens.
- `api-backend`: `BITRIX_LOCAL_APP_BASE_URL`, `MESSAGE_SAFETY_URL`, circuit/timeout vars, Redis `/0`/`/1` + service tokens; **бизнес-настройки** — из `app_settings`. OTP counters **не** ведёт.
- `bitrix-sync`: `BITRIX_SYNC_ENABLED`, `BITRIX_SYNC_*`, `BITRIX_SYNC_WEBHOOK_TOKEN`, `BITRIX_SYNC_SERVICE_TOKEN`.
- `bitrix-sync`: non-secret `BITRIX_SYNC_ENABLED/MODE/PORTAL_HOST/PORTAL_MEMBER_ID`, `BITRIX_WEBHOOK_ALLOWED_CIDRS`, custom field names и capacity bootstrap; runtime secrets — database/inbound CRM REST webhook URL, Contact/alert receiver tokens и service token; hot policy — в `bitrix_sync.settings`.
- `bitrix-sync` не читает `BITRIX_CLIENT_ID` / `BITRIX_CLIENT_SECRET`.
### `BITRIX_SYNC_ENABLED`
@@ -390,7 +431,13 @@ presigned URL и CORS Selectel; path-style адресация не поддер
| `true` (default) | `bitrix-sync` обрабатывает `sync_queue` и принимает CRM webhook |
| `false` | синхронизация с Bitrix24 CRM не выполняется; сервис стартует в no-op/degraded режиме; чат Open Lines через `bitrix-local-app` **не** затрагивается |
В MVP выбран режим **no-op service**: контейнер `bitrix-sync` стартует, `/health/live` отвечает успешно, `/health/ready` возвращает degraded/not-ready с явной причиной `sync_disabled`, worker не обрабатывает `sync_queue`, webhook CRM возвращает безопасный `503` или `202 ignored` по контракту модуля. Это сохраняет единый compose-контур и не влияет на чат Open Lines.
При `false` контейнер остаётся live, `/health/ready` возвращает `503 sync_disabled`, worker/reconciliation не claim-ят работу. Public webhook не должен безусловно подтверждать событие как обработанное: до cutover endpoint закрывается на edge либо возвращает retryable `503`. Это не влияет на чат Open Lines.
### `bitrix_sync.settings`
Versioned hot settings содержат batch size/wait, claim size, lease TTL, portal limiter refill/burst, max in-flight, retry base/max/horizon, Contact/alert reconciliation intervals, пороги всплеска Contact, восстановленных без webhook, и IDs/стадии/поля/SLA smart process. Новая версия активируется только после полной type/range/cross-field validation; невалидная версия не заменяет последнюю рабочую.
Secrets, DSN, portal host/member ID, inbound source IP CIDR allow-list, custom Contact field names и cutover watermark не являются hot settings. Полный каталог и defaults — [`../modules/module-07-bitrix-sync.md`](../modules/module-07-bitrix-sync.md), §12.
## Keycloak settings bridge для OTP
@@ -414,6 +461,7 @@ Challenge сохраняет snapshot TTL, длины кода и `settings_vers
| `chat.attachments.allowed_extensions` | `jpg`, `jpeg`, `png`, `webp`, `heic`, `heif`, `pdf` |
| `chat.attachments.allowed_mime_types` | `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf` |
| `chat.attachments.max_size_mb` | `5` |
| `messages.max_text_length` | `4000` (public/business limit; Safety hard ceiling остаётся `10000`) |
Правило: файл принимается только если **и** расширение, **и** MIME в allow-list. Детальная проверка — модуль `message-safety`.