# module-07. Проектная спецификация заглушки `bitrix-sync` > Статус: целевая спецификация инфраструктурной заглушки MVP. CRM-синхронизация не реализуется. > Источники: [`README.md`](README.md), [`arch-00-glossary.md`](arch-00-glossary.md), [`arch-01-system-architecture.md`](arch-01-system-architecture.md), [`arch-02-api-contracts.md`](arch-02-api-contracts.md), [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md), [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md), [`module-01-api-backend.md`](module-01-api-backend.md), [`../../HAN_chat/deploy/init-managed-postgres.py`](../../HAN_chat/deploy/init-managed-postgres.py). ## 1. Назначение и жёсткая граница На текущем этапе сервис доказывает только: 1. контейнер и FastAPI process стабильно запускаются в общем Compose; 2. сервис подключается к managed PostgreSQL по private network/TLS; 3. при старте и затем примерно раз в 60 секунд выполняется `SELECT 1`; 4. состояние доступно через health и защищённый status endpoint; 5. shutdown корректно останавливает loop и закрывает pool. В этой версии **нет**: - чтения/обработки `han_app.sync_queue`; - CRM Contact map/create/update; - вызовов Bitrix24 REST; - CRM webhook `/bitrix/sync/webhook/contact`; - доступа к OAuth `bitrix-local-app`; - write-back в `han_app`, GUC `han.sync_suppress`; - DLQ бизнес-задач и field mapping. Упоминания полноценного sync в arch-01/02/03 описывают будущую целевую границу, а не функциональность этого stub. Расширение требует новой версии спецификации, migrations/GRANT, OpenAPI и contract tests. ## 2. Технологический профиль - Python 3.12+, FastAPI, Pydantic v2, Uvicorn. - SQLAlchemy 2 async/`asyncpg` либо прямой `asyncpg` pool; выбран SQLAlchemy async для единообразия с backend. - Managed PostgreSQL; схема/role `bitrix_sync`. - Один in-process periodic loop на replica. - OpenTelemetry, JSON logging, pytest/anyio. ```text bitrix-sync/ app/ main.py settings.py api/{health,status,auth,errors,schemas}.py application/{db_probe,periodic_loop,state}.py infrastructure/{database,observability}.py tests/{unit,integration,contract}/ openapi.yaml Dockerfile docker-compose.yml ``` ## 3. Runtime model FastAPI lifespan: ```text validate env configure logs/OTEL create bounded DB engine/pool run initial probe with startup timeout publish initial state start exactly one periodic task serve HTTP on shutdown: signal stop → await/cancel sleep → finish bounded probe → close pool/OTEL → exit ``` HTTP process и loop разделяют thread-safe/async-safe immutable state snapshot. Router не выполняет probe для каждого status request. ## 4. Periodic loop ### 4.1. Период Целевой интервал — примерно 60 секунд: ```text BITRIX_SYNC_DB_CHECK_INTERVAL_SEC=60 ``` Следующий запуск планируется от завершения предыдущего (`fixed-delay`), а не запускается параллельно. Добавляется jitter, например ±10%, чтобы несколько replicas не синхронизировались. ### 4.2. Initial check Первый `SELECT 1` выполняется при startup до перехода в ready. Ошибка initial check не обязана завершать process: сервис остаётся live/not-ready и продолжает reconnect loop. Это позволяет восстановиться после временной недоступности managed PG без restart storm. Невалидный env/DSN/TLS policy, напротив, является configuration error: process fail-fast. ### 4.3. Probe Каждая проверка: 1. получает connection из pool с bounded acquire timeout; 2. выполняет параметризованный/constant `SELECT 1`; 3. проверяет результат `1`; 4. фиксирует monotonic duration и wall-clock UTC completion; 5. возвращает connection; 6. атомарно обновляет state. Никаких table scans, DDL, schema writes и создания business rows. ### 4.4. Timeout Общий probe timeout включает pool acquire + query: ```text BITRIX_SYNC_DB_CHECK_TIMEOUT_SEC=5 ``` На PostgreSQL задаются `connect_timeout`, `command_timeout`/`statement_timeout`. Timeout помечает check failed, отменяет query и гарантированно освобождает/инвалидирует connection. ### 4.5. Backoff При успехе — обычный interval+jitter. При последовательных ошибках: ```text delay = min(base * 2^(failures-1), max_backoff) + full_jitter ``` Ориентиры: base 5 с, max 60 с. Успех сбрасывает failure counter. Backoff не создаёт tight loop и не превышает readiness stale policy без явного статуса. ### 4.6. Prevention overlap Одна task выполняет `await probe(); await sleep()`, поэтому overlap конструктивно невозможен. Дополнительно `asyncio.Lock`/single-flight защищает ручной internal trigger, если он когда-либо появится. В MVP trigger endpoint отсутствует. При нескольких replicas каждая проверяет БД независимо; distributed lock не нужен, потому что `SELECT 1` безопасен и не является worker job. ## 5. Connection pool Начальная конфигурация минимальна: - pool size 1–2; - max overflow 0; - `pool_pre_ping=true` допустим, но не заменяет explicit probe; - pool recycle меньше сетевого idle timeout провайдера; - short acquire/connect/query timeout; - TLS verify (`sslmode=verify-full` или эквивалент) с CA; - `application_name=han-bitrix-sync`; - search path только `bitrix_sync`. Pool создаётся один раз и закрывается shutdown. Connection после network/protocol error invalidated. Пароль/DSN не логируются. ## 6. Enabled/disabled semantics Сохраняется канонический `BITRIX_SYNC_ENABLED`. ### `true` Для stub это означает: process запускает DB connectivity loop. Это **не** означает включённую CRM-синхронизацию. Status явно возвращает `mode=db_connectivity_stub`. ### `false` - process и HTTP endpoint запускаются; - DB pool можно не создавать, periodic loop не запускается; - `/health/live` → `200`; - `/health/ready` → `503` с `reason=sync_disabled`, как зафиксировано arch-04; - internal status → `200`, `enabled=false`, `state=disabled`; - CRM-функций всё равно нет. Таким образом, disabled — явный no-op, а не скрытый success readiness. ## 7. HTTP API ### 7.1. `GET /health/live` Без auth внутри Docker network. Не обращается к БД. ```json {"status":"live"} ``` `200`, пока process/event loop обслуживает запросы. ### 7.2. `GET /health/ready` Не выполняет новый DB query; читает snapshot. `200`: ```json { "status": "ready", "mode": "db_connectivity_stub", "database": { "status": "ok", "last_success_at": "2026-07-10T09:00:00Z", "age_seconds": 12 } } ``` `503`: ```json { "status": "not_ready", "reason": "database_unavailable", "database": { "status": "down", "last_success_at": null, "consecutive_failures": 3 } } ``` Ready только если enabled, initial success был и последний success не старше: ```text max(2 * interval + jitter budget, BITRIX_SYNC_READY_MAX_STALENESS_SEC) ``` Рекомендуемый default staleness 150 с. Error detail не содержит host/DSN. ### 7.3. `GET /internal/sync/v1/status` Защита: ```text Authorization: Bearer ${BITRIX_SYNC_SERVICE_TOKEN} ``` `X-Service-Token` можно поддержать только как migration compatibility; канонический вариант этого модуля — Bearer. Endpoint internal-only, edge не публикует. ```json { "service": "bitrix-sync", "enabled": true, "mode": "db_connectivity_stub", "crm_sync_implemented": false, "state": "healthy", "started_at": "2026-07-10T08:00:00Z", "last_check": { "started_at": "2026-07-10T09:00:00Z", "finished_at": "2026-07-10T09:00:00Z", "success": true, "duration_ms": 7, "error_code": null }, "last_success_at": "2026-07-10T09:00:00Z", "consecutive_failures": 0, "next_check_in_seconds": 48 } ``` Не возвращаются queue depth/dead letters, поскольку сервис их не читает. Поля, обещающие CRM run, не симулируются. Invalid token → `401 service_unauthorized`, constant-time compare. Status endpoint не запускает probe. ## 8. State machine ```text starting ├─ disabled → disabled ├─ initial success → healthy └─ initial failure → degraded healthy ├─ one/more failures → degraded └─ shutdown → stopping degraded ├─ success → healthy └─ shutdown → stopping ``` Snapshot содержит start/check timestamps, last success/failure, consecutive failures, duration и safe error code: `db_connect_timeout`, `db_query_timeout`, `db_auth_failed`, `db_tls_failed`, `db_unavailable`, `unexpected_result`. Auth/TLS/config ошибки могут быть classified non-transient и alertятся немедленно, но loop продолжает с max backoff, если env был syntactically valid. ## 9. PostgreSQL и права Managed init уже создаёт: - schema `bitrix_sync`; - role `bitrix_sync_user`; - search path `bitrix_sync`; - отсутствие доступа к чужим схемам. Для stub достаточно `CONNECT` к database и возможности `SELECT 1`; `USAGE` на `bitrix_sync` допустим для будущих migration/version checks. Таблицы не нужны. Alembic может иметь пустую baseline revision, чтобы зафиксировать ownership/version, но runtime не выполняет DDL. К `han_app` **не выдаются GRANT** до реализации полноценной CRM sync. Это сознательно строже общего будущего требования. Когда появится sync: - GRANT выдаётся точечно на `sync_queue`, mapping и необходимые columns; - запрещён broad schema write; - GUC/write-back и trigger contract проходят integration tests; - обновляются deploy scripts и module spec. `BITRIX_SYNC_APP_DATABASE_URL` из arch-04 в stub не требуется. Канонический runtime DSN stub — `BITRIX_SYNC_DATABASE_URL` с search path `bitrix_sync`. ## 10. Env Уже канонические: ```text APP_ENV=production-like LOG_LEVEL=INFO BITRIX_SYNC_ENABLED=true BITRIX_SYNC_DATABASE_URL=postgresql://bitrix_sync_user:.../han_chat BITRIX_SYNC_SERVICE_TOKEN= OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 ``` Предлагаемые technical env, которые нужно синхронизировать с arch-04 до реализации: ```text BITRIX_SYNC_DB_CHECK_INTERVAL_SEC=60 BITRIX_SYNC_DB_CHECK_TIMEOUT_SEC=5 BITRIX_SYNC_DB_CHECK_JITTER_RATIO=0.10 BITRIX_SYNC_DB_RETRY_BASE_SEC=5 BITRIX_SYNC_DB_RETRY_MAX_SEC=60 BITRIX_SYNC_READY_MAX_STALENESS_SEC=150 BITRIX_SYNC_DB_POOL_SIZE=2 BITRIX_SYNC_DB_POOL_RECYCLE_SEC=300 ``` Старые `BITRIX_SYNC_CONTACT_*`, CRM URL/webhook/concurrency в stub не читаются и не должны создавать иллюзию sync. Их можно оставить в root env зарезервированными, но status явно сообщает `crm_sync_implemented=false`. ## 11. Observability ### Logs JSON fields: - timestamp, level, `service.name=bitrix-sync`; - module, event, request_id, trace/span id; - enabled/mode/state; - check sequence, success, duration_ms, consecutive failures; - error_code; shutdown reason. Не логируются DSN, DB password, service token, SQL exception с credentials, host при принятой security policy. Сам `SELECT 1` можно не логировать каждый раз на INFO: success — DEBUG/metric, state transition — INFO, failure — WARN/ERROR с throttling. ### Metrics - `bitrix_sync_db_probe_total{outcome}`; - duration histogram; - consecutive failures gauge; - seconds since last success; - state info/enabled; - pool checked-out/wait duration/errors; - HTTP requests/latency/status; - loop lag; - readiness. Labels low-cardinality; DB host/error text не labels. ### Traces Initial/periodic probe создаёт span `bitrix_sync.db_probe`; SQL statement sanitised/semantic convention. OTEL outage не влияет на readiness. ## 12. Security - сервис только в `backend`/`observability` networks, без published port; - `/internal/sync/v1/status` не edge-routed; - Bearer token constant-time, secret только env/secret mount; - managed PG private network + TLS verify; - runtime DB role least privilege; никаких `han_app` grants; - strict env validation; OpenAPI docs off production; - non-root/read-only rootfs/tmpfs/drop capabilities; - pinned dependencies/image, vulnerability scan; - responses/logs не раскрывают DSN/credentials/internal stack. ## 13. Docker и healthcheck Service: - `expose: 8080`, без `ports`; - networks `backend`,`observability`; - env из root `.env`; - managed PostgreSQL вне Compose; - restart policy `unless-stopped`/platform policy; - init/signal forwarding; - graceful stop timeout больше probe timeout. Container healthcheck использует `/health/live`, чтобы временная DB outage не создавала restart storm. Orchestrator/monitoring отдельно проверяет `/health/ready`. Startup dependency не задаётся через fake PostgreSQL container. Application самостоятельно reconnect с backoff. ## 14. Graceful shutdown На SIGTERM: 1. FastAPI перестаёт принимать новые запросы по server grace; 2. выставляется stop event; 3. interruptible sleep завершается немедленно; 4. новый probe не стартует; 5. текущий probe ждётся максимум shutdown budget, затем отменяется; 6. connection корректно возвращается/invalidate; 7. pool и telemetry flush закрываются; 8. task awaited — никаких `Task was destroyed`. Shutdown не пишет бизнес-данные и не требует БД. ## 15. Ошибки и degraded behavior | Ситуация | Process | Live | Ready | Loop | |---|---|---|---|---| | disabled | работает | 200 | 503 `sync_disabled` | не запущен | | PG startup down | работает | 200 | 503 | retry/backoff | | PG кратко down после success | работает | 200 | 503 после policy/stale | retry | | wrong password | работает или fail-fast по policy | 200 если работает | 503 | max backoff + alert | | malformed DSN/env | fail-fast | — | — | — | | OTEL down | работает | 200 | по DB | продолжает | | loop task unexpectedly died | работает кратко | 200 | 503 `worker_not_running` | supervisor/exit | Необработанное исключение loop не должно молча оставить stale ready. Lifespan supervisor помечает not-ready и завершает process либо перезапускает task bounded; предпочтительно fail process после alert, чтобы orchestrator восстановил clean state. ## 16. Тестовая матрица ### Unit - interval+jitter boundaries; - exponential backoff/reset; - state transitions/staleness; - no-overlap single-flight; - enabled/disabled; - safe error classification/redaction; - shutdown during sleep/probe. ### Integration - initial and periodic `SELECT 1` на PostgreSQL; - pool size/acquire timeout/recycle; - DB unavailable then recovery without restart; - query timeout/cancel and connection return; - wrong credentials/TLS; - no tables/writes and no `han_app` access; - exact approximate 60-second scheduling with fake clock. ### Contract - OpenAPI 3.1 parity; - health/status schemas and HTTP codes; - missing/wrong/correct `BITRIX_SYNC_SERVICE_TOKEN`; - request-id/trace; - internal endpoint absent through nginx. ### Runtime/failure - SIGTERM at each loop phase; - loop crash detection; - long DB outage without log/reconnect storm; - multiple replicas independently probe without overlap within replica; - no secret/DSN in logs; - Compose health does not restart solely on PG outage. ## 17. Definition of Done - FastAPI process и один periodic loop реализованы; - initial check и `SELECT 1` примерно каждые 60 с работают; - timeout, jitter, backoff, overlap prevention и recovery проверены; - pool bounded и graceful shutdown доказан; - live/ready/status соответствуют contract и service token; - enabled/disabled semantics явны; - status всегда сообщает `mode=db_connectivity_stub`, `crm_sync_implemented=false`; - runtime не читает `han_app`, queue или Bitrix CRM; - least-privilege DB/network/container security соблюдены; - structured logs/metrics/traces без secrets; - OpenAPI, Docker healthcheck и tests готовы; - future CRM boundary документирована и не реализована скрыто. ## 18. Решения, допущения и TBD **Решения:** - S1: stub выполняет только DB connectivity probe. - S2: fixed-delay loop + jitter; overlap невозможен. - S3: PG outage даёт live/not-ready, а не restart storm. - S4: disabled даёт live 200, ready 503 `sync_disabled`. - S5: `han_app` GRANT отсутствует до реальной sync. - S6: status internal защищён Bearer `BITRIX_SYNC_SERVICE_TOKEN`. **Допущения:** - A1: одна replica MVP; несколько replicas безопасны, поскольку probe read-only. - A2: interval 60 с и timeout 5 с достаточны для connectivity smoke. - A3: managed PG CA/TLS параметры предоставляет ops. **TBD:** - S-TBD1: добавить proposed DB probe env в arch-04. - S-TBD2: ready staleness threshold и alert thresholds после ops review. - S-TBD3: fail-fast или persistent degraded при non-transient auth/TLS error. - S-TBD4: baseline Alembic revision без таблиц — решение владельца deploy. - S-TBD5: полноценная CRM sync, webhook, queue, grants, retries и mapping — отдельная будущая спецификация.