# arch-04. Настройки и изменяемые параметры > **`.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). ## Цель Параметры разделены по слоям: | Слой | Где | Что | |---|---|---| | **Инфраструктура** | `.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 | Managed PostgreSQL **поднимается до** развёртывания приложения. Бизнес-настройки **не дублируются** в `.env`: seed в `app_settings` выполняется миграцией/скриптом модуля `database` **до** первого запуска `api-backend`. ## Источники настроек ### `.env` — только несекретная инфраструктура Корневой `backend/.env` читается сервисами compose. В репозитории — `.env.example`, не `.env`. **Допустимо в `.env`:** - URL сервисов, публичные endpoint, порты; - host/port/database/schema без паролей и токенов; - параметры **nginx/TLS** и edge rate limits (`NGINX_RATE_LIMIT_*`); - идентификация Keycloak: realm, audience, public/internal URL; - переключатели 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`:** - 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; - лимиты приложения (сообщения, download URL, login); - типы/размер файлов чата, UX idle timeout; - CORS origins, feature flags frontend. ### `app_settings` — бизнес-настройки (App DB) **Единственный источник правды** для параметров, которые: - меняет продукт/оператор без redeploy; - отдаются в `GET /api/v1/public/app-config` (публичные ключи); - используются `api-backend` (и при необходимости другими сервисами) в runtime. Позже — редактирование через админку; на MVP — seed-миграция. ### `text_resources` / `popular_questions` Контент 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. --- ## Требования к таблице `app_settings` Схема: **`han_app`**. Детальная DDL — модуль `database`; arch фиксирует контракт. ### Колонки (минимум) | Колонка | Тип | Назначение | |---|---|---| | `setting_key` | `varchar`, PK | Канонический ключ (`auth.phone.enabled`, см. ниже) | | `setting_value` | `text`, NOT NULL | Значение (строка; парсинг по типу) | | `value_type` | `enum` | `boolean` \| `integer` \| `string` \| `duration` \| `string_list` | | `is_public` | `boolean` | Разрешён в `GET /api/v1/public/app-config` | | `description` | `text`, nullable | Комментарий для админки/ops | | `updated_at` | `timestamptz` | Последнее изменение | | `record_status` | `char(1)` | Soft delete: `'A'` active | ### Правила 1. **Seed обязателен** до первого запуска `api-backend` в новой среде (миграция или idempotent seed-скрипт). 2. **`api-backend`** загружает настройки при старте; допускается in-memory cache с инвалидацией по `updated_at` (реализация — модуль). 3. Отсутствие **обязательного** ключа при старте → сервис **не** переходит в `ready` (fail-fast). 4. Публичные ключи (`is_public=true`) отдаются только через **строгий DTO** `app-config`, не raw dump таблицы. 5. Секреты и infra **не** хранятся в `app_settings`. ### Ключи MVP (seed) Полный пример значений — раздел «Seed MVP» ниже. Группы: | Группа | Ключи | |---|---| | Auth | `auth.phone.enabled`, `auth.password.enabled` | | OTP (продукт; потребитель — Keycloak SPI через settings bridge api-backend) | `otp.phone.max_send_attempts_per_24h`, `otp.phone.min_seconds_between_attempts`, `otp.phone.max_verify_attempts`, `otp.phone.code_length`, `otp.phone.ttl_seconds`, `otp.phone.sms_order_timeout_ms` | | Оператор | `operator.call.phone` | | Consent | `consent.personal_data.*`, `consent.privacy_policy.document_url`, `consent.user_agreement.*`, `consent.marketing.*` | | Файлы чата | `chat.attachments.*` | | Rate limits (app) | `rate_limit.message_send.*`, `rate_limit.download_url.*`, `rate_limit.public_endpoints.*`, `rate_limit.login.*` | | Notification Center | `notification.home.max_items`, `notification.center.max_items`, `notification.carousel.*`, `notification.hidden.default_ttl_days`, `notification.documents.max_files`, `notification.expire_job.run_at`, `notification.upload_draft.ttl_days` | | Rate limits (notifications) | `rate_limit.notifications_read.per_user`, `rate_limit.notifications_action.per_user`, `rate_limit.notification_upload.per_user`, `rate_limit.notifications_public.per_ip` | | UX | `ux.session.idle_timeout_minutes` | | Security | `security.cors.allowed_origins`, `security.public_cache.max_age_seconds` | ### Seed MVP ```text auth.phone.enabled=true auth.password.enabled=false otp.phone.max_send_attempts_per_24h=3 otp.phone.min_seconds_between_attempts=30 otp.phone.max_verify_attempts=5 otp.phone.code_length=6 otp.phone.ttl_seconds=60 otp.phone.sms_order_timeout_ms=3000 operator.call.phone=+74999591007 consent.personal_data.required=true consent.personal_data.document_url=https://www.han0107.ru/privacy/persdata-agree-mobile consent.personal_data.version=2026-06-10 consent.privacy_policy.document_url=https://www.han0107.ru/privacy consent.user_agreement.required=true consent.user_agreement.document_url=https://www.han0107.ru/user-agreement consent.user_agreement.version=2026-06-10 consent.marketing.required=false consent.marketing.document_url=https://www.han0107.ru/privacy/ads-agree consent.marketing.version=2026-06-10 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.disallowed_extensions=svg,doc,docx,xls,xlsx,csv chat.attachments.max_size_mb=5 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 rate_limit.download_url.per_user=60/hour rate_limit.public_endpoints.per_ip=60/minute rate_limit.login.per_ip=10/minute rate_limit.notifications_read.per_user=120/minute rate_limit.notifications_action.per_user=60/minute rate_limit.notification_upload.per_user=20/minute rate_limit.notifications_public.per_ip=60/minute notification.home.max_items=7 notification.center.max_items=15 notification.carousel.autoplay_enabled=false notification.carousel.autoplay_interval_ms=5000 notification.hidden.default_ttl_days=3 notification.documents.max_files=10 notification.expire_job.run_at=00:01 notification.upload_draft.ttl_days=7 ux.session.idle_timeout_minutes=30 security.cors.allowed_origins=https://tohin.ru security.public_cache.max_age_seconds=3600 ``` --- ## Service-owned настройки `sms-service` Параметры, изменение которых не меняет Compose, секреты, URL или сетевую топологию, хранятся в `sms.sms_setting`, а не в `.env`. Ключи и seed: ```text provider.idgtl.default_sender_name= provider.idgtl.connect_timeout_ms=3000 provider.idgtl.request_timeout_ms=70000 provider.idgtl.callback_enabled=true worker.poll_interval_ms=500 worker.lease_seconds=90 ``` В `.env` остаются только несекретные URL внутренних/внешних сервисов и технические параметры. `SMS_DATABASE_URL`, service tokens, Direct API key и callback credentials входят в runtime secret catalog. Детальный контракт — `module-11-idgtl-sms.md`. `` — обязательный 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. Для ClamAV schema допускает возраст сигнатур не более `720` часов (30 дней), seed `max_signature_age_hours` равен `240` часам (10 дней). Полный 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` Только несекретная инфраструктура и выбор secret source. Бизнес-параметры — в seed `app_settings`, а перечисленные ниже runtime secrets — в конфигурации `han-secrets`, не в этом файле. ```text # ============================================================================= # Общие # ============================================================================= APP_ENV=production-like SECRETS_SOURCE=selectel API_PORT=8000 LOG_LEVEL=INFO # ============================================================================= # Managed PostgreSQL # ============================================================================= HAN_PG_HOST= HAN_PG_PORT=5433 HAN_PG_DATABASE=han_chat 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. # ============================================================================= # Публичные URL (HTTPS) # ============================================================================= PUBLIC_WEB_URL=https://tohin.ru PUBLIC_API_URL=https://tohin.ru/api PUBLIC_AUTH_URL=https://tohin.ru/auth # ============================================================================= # nginx (edge, TLS, rate limits) # ============================================================================= NGINX_HTTP_PORT=80 NGINX_HTTPS_PORT=443 TLS_CERT_PATH=/etc/nginx/certs/fullchain.pem TLS_KEY_PATH=/etc/nginx/certs/privkey.pem NGINX_TLS_PROTOCOLS=TLSv1.2 TLSv1.3 NGINX_HSTS_MAX_AGE=31536000 NGINX_CLIENT_MAX_BODY_SIZE=8m NGINX_RATE_LIMIT_API=60r/m NGINX_RATE_LIMIT_AUTH=10r/m NGINX_RATE_LIMIT_DOWNLOADS=30r/m NGINX_RATE_LIMIT_PUBLIC=60r/m NGINX_RATE_LIMIT_POLLING=60r/m NGINX_RATE_LIMIT_NOTIFICATIONS_READ=120r/m NGINX_RATE_LIMIT_NOTIFICATIONS_ACTION=60r/m NGINX_RATE_LIMIT_NOTIFICATION_UPLOAD=20r/m NGINX_RATE_LIMIT_NOTIFICATIONS_PUBLIC=60r/m # ============================================================================= # Keycloak (mock остаётся true до controlled SMS cutover) # ============================================================================= KEYCLOAK_PUBLIC_URL=https://tohin.ru/auth KEYCLOAK_INTERNAL_URL=http://keycloak:8080 KEYCLOAK_REALM=han-chat KEYCLOAK_AUDIENCE=han-chat-api KEYCLOAK_OTP_MOCK_ENABLED=true KEYCLOAK_YANDEX_CAPTCHA_ENABLED=false KEYCLOAK_YANDEX_CAPTCHA_CLIENT_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 ВМ1 (I4: раздельные DB index) # ============================================================================= # /0 — api-backend: rate limits, idempotency # /1 — api-backend realtime/coordination (опционально; можно совместить с /0) REDIS_URL=redis://redis:6379/0 REDIS_REALTIME_URL=redis://redis:6379/1 # ============================================================================= # Service tokens (internal API) — runtime secret catalog, не .env # ============================================================================= # 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 (секреты — runtime secret catalog; параметры — sms.sms_setting) # ============================================================================= IDGTL_SMS_BASE_URL=https://direct.i-dgtl.ru IDGTL_SMS_CALLBACK_PUBLIC_URL=https://tohin.ru/callbacks/idgtl/sms # 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=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 BITRIX_LOCAL_APP_CIRCUIT_OPEN_SEC=30 BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC=10 # ============================================================================= # bitrix-sync # ============================================================================= BITRIX_SYNC_ENABLED=true BITRIX_SYNC_MODE=full BITRIX_SYNC_PORTAL_HOST=han0107.bitrix24.ru BITRIX_SYNC_PORTAL_MEMBER_ID= BITRIX_SYNC_PUBLIC_BASE_URL=https://processing.example.ru BITRIX_WEBHOOK_ALLOWED_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_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 # Runtime secrets: BITRIX_CLIENT_ID, BITRIX_CLIENT_SECRET, # BITRIX_APPLICATION_TOKEN and token encryption key. # ============================================================================= # api-backend → Message Safety (ВМ1 caller) # ============================================================================= MESSAGE_SAFETY_POST_TIMEOUT_SEC=5 MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC=2 MESSAGE_SAFETY_TASK_POLL_MAX_SEC=300 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:@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= MESSAGE_SAFETY_CLAMAV_HOST=clamd MESSAGE_SAFETY_CLAMAV_PORT=3310 # ============================================================================= # Frontend (nginx) # ============================================================================= FRONTEND_STATIC_PATH=/usr/share/nginx/html FRONTEND_DEV_PROXY_ENABLED=false EXPO_DEV_SERVER_URL=http://host.docker.internal:8081 # ============================================================================= # Selectel S3 # ============================================================================= 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 # 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 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_TLS_INSECURE=true OTEL_QUEUE_SIZE=10000 # Runtime secret when configured: OTEL_REMOTE_AUTH_HEADER. ``` S3-клиенты используют только virtual-hosted addressing (`https://.s3.storage.selcloud.ru/`). Это часть контракта presigned URL и CORS Selectel; path-style адресация не поддерживается приложением. Production использует отдельные allow-listed env manifests ВМ1 и ВМ2; не все переменные примера копируются на оба хоста. На ВМ2 `han-secrets` под отдельным IAM principal материализует отдельный root-owned файл каждому сервису в `/run/han-chat/secrets`; общий bundle ВМ1/ВМ2 запрещён. Для 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-токены:** `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_` — индивидуальный секрет продюсера 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`: 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` | Значение | Поведение | |---|---| | `true` (default) | `bitrix-sync` обрабатывает `sync_queue` и принимает CRM webhook | | `false` | синхронизация с Bitrix24 CRM не выполняется; сервис стартует в no-op/degraded режиме; чат Open Lines через `bitrix-local-app` **не** затрагивается | При `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 OTP settings (`otp.phone.*`) хранятся в `app_settings`, но Keycloak не получает прямой доступ к схеме `han_app`. MVP-механизм: 1. `api-backend` читает публичные/служебные настройки из `app_settings` и кэширует их. 2. Для Keycloak SPI доступен internal endpoint `GET /internal/settings/v1/otp` в Docker/VPC-сети, защищённый service token. 3. Keycloak SPI читает limits, `otp.phone.code_length`, `otp.phone.ttl_seconds` и `otp.phone.sms_order_timeout_ms` через этот endpoint с локальным cache. 4. При недоступности settings bridge SPI использует последнее валидное cache-значение; если cache пустой — fail-closed и не выдаёт OTP. Challenge сохраняет snapshot TTL, длины кода и `settings_version`; изменение settings влияет только на новые challenges. Счётчики попыток OTP остаются в зоне Keycloak/SPI, не в `api-backend`. ## Разрешённые типы файлов чата (MVP) Источник значений — ключи **`app_settings`** (раздел «Seed MVP»). Остальные arch-* **ссылаются сюда**. | Ключ | MVP-значение | |---|---| | `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`. Публичный UI: `GET /api/v1/public/app-config` (строгий DTO, без секретов). ## Публичный config endpoint `GET /api/v1/public/app-config` — только ключи с `is_public=true` из `app_settings`: - OTP по телефону (`auth.phone.enabled`); - номер оператора; - типы файлов и max size; - `ux.session.idle_timeout_minutes`; - feature flags; - публичные лимиты для подсказок UI. Секреты, service tokens, внутренние URL **не** возвращаются. DTO явный, не сериализация всей таблицы. Rate limit: 60/min per IP. `Cache-Control: public, max-age` из `security.public_cache.max_age_seconds`. ## Публичный content endpoint `GET /api/v1/public/content` — `text_resources` для текущего языка. Те же требования безопасности, что у config. ## Nginx и HTTPS Infra-переменные — `.env.example` (`NGINX_*`, `TLS_*`). Edge rate limits (`NGINX_RATE_LIMIT_*`) **не** дублируют `rate_limit.*` из `app_settings`: nginx — защита периметра, app — бизнес-лимиты в backend. Реализация — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).