# bitrix-sync Изолированный Python 3.12 сервис durable-синхронизации Contact между `han_app` и Битрикс24. Сервис не участвует в Open Lines и не имеет HTTP-зависимости от `api-backend`. ## Entrypoints - `han-bitrix-sync-api` — health, internal status и два bounded robot receiver; - `han-bitrix-sync-worker` — queue/webhook/rebind workflows с lease fencing; - `han-bitrix-sync-reconciliation` — один advisory-lock incremental run; - `alembic upgrade head` — отдельная контролируемая миграция, не startup DDL. Disabled mode требует только `BITRIX_SYNC_ENABLED=false` и `BITRIX_SYNC_MODE=disabled`, не читает БД и не принимает webhook. Full mode валидирует весь каталог secret files, portal identity, custom fields, HTTPS host lock и непустой CIDR allow-list до startup. ## Наблюдаемость JSON stdout включён всегда. Если задан стандартный `OTEL_EXPORTER_OTLP_ENDPOINT`, traces, metrics и логи дополнительно отправляются напрямую по OTLP/gRPC через bounded batch queues. Ошибка инициализации, экспорта или остановки телеметрии не меняет readiness и не останавливает API, worker или reconciliation. Процессы различаются как `bitrix-sync-api`, `bitrix-sync-worker` и `bitrix-sync-reconciliation`; namespace остаётся `han-chat`. FastAPI (кроме `/health/live`), SQLAlchemy и HTTPX инструментированы. Перед stdout и OTLP выполняется application redaction: query/token/credential URL, headers, payload, PII, object keys и SQL не экспортируются. Бизнес-метрики используют только закрытые множества labels; UUID и внешние идентификаторы в labels/spans не записываются. ## Границы безопасности - CRM credential URL используется как единый секрет; redirect выключен, TLS проверяется, REST method выбирается только из закрытого allow-list. - Receiver принимает только `application/x-www-form-urlencoded` с bounded content length, числом и длиной полей. Query token сравнивается constant-time. - nginx должен перезаписывать `X-Real-IP` из TCP peer, проверять CIDR до proxy и не логировать `$request_uri`, args или body. Контейнер receiver недоступен напрямую. - DB хранит только hash CRM-master значений в snapshot; safe command projection не содержит PII. URL credential, form body и token не логируются. - Запись CRM-master полей выполняется в одной транзакции после `SET LOCAL han.sync_suppress='true'`. ## Локальные проверки Лёгкие проверки, не требующие Docker, сервиса или реального PostgreSQL: ```text python -m pytest python -m ruff check app tests ``` PostgreSQL integration и Bitrix contract suites намеренно являются внешними gates: локальный managed PostgreSQL не поднимается Compose-файлом. ## External gates до `BITRIX_SYNC_ENABLED=true` 1. Применить migrations migration-role и проверить grants runtime-role. 2. На disposable managed PostgreSQL проверить concurrent `SKIP LOCKED`, lease expiry/fencing, active mapping uniqueness, rebind partial failure, transaction-local GUC без утечки и crash после CRM success. 3. Подтвердить на целевом портале wire-контракты `duplicate.findbycomm`, Contact add/get/update, mixed `batch`, custom fields, enum dictionary и `crm.item.list` с `opened=1`, registration REST field `=1`. 4. Заполнить и активировать валидную `business_alerts` settings version: entity/category/stage/field IDs и responsible party. Placeholder `null` запрещает alert receiver. `value_json` настройки `business_alerts` использует ключи `entity_type_id`, `category_id`, `stage_new`, опциональный `responsible_id` и объект `field_ids`. В `field_ids` REST-имена пользовательских полей сопоставляются ключам `alert_number`, `fingerprint`, `alert_type`, `severity`, `app_user_id`, `selected_external_id`, `occurrence_count`, `first_occurred_at`, `last_occurred_at`, `workflow_id`. Конфликтующие Contact передаются в стандартное поле `contactIds`, а значение `BITRIX_SYNC_CONTACT_SOURCE` — в стандартное поле `sourceId`. 5. Проверить least-privilege credential negative tests; credential администратора запрещён. 6. Валидировать nginx exact routes, no-redirect HTTP policy, body/rate limits, version-controlled CIDR и отсутствие query/body в access/error/traces. 7. Запустить synthetic webhook с реальным robot form contract, затем убедиться, что durable inbox commit предшествует `202`. 8. Выполнить 10k incremental reconciliation/load gate, webhook-loss recovery, 429/5xx/TLS/DNS/timeout/uncertain-create и restart-at-each-step tests. 9. Проверить container image digest, dependency/SBOM/vulnerability scan и compose hardening; root Compose ВМ2 подключает этот fragment отдельно. 10. Зафиксировать cutover watermark, отменить только pre-cutover active tasks, выполнить disabled preflight и затем controlled enablement. `compose.fragment.yaml` — сервисный фрагмент, не root Compose и не команда развёртывания. Reconciliation entrypoint выполняет один run; расписание задаёт root-owned scheduler/deployment layer.