Files
han-app/modules/module-07-bitrix-sync.md
T

20 KiB
Raw Blame History

module-07. Проектная спецификация заглушки bitrix-sync

Статус: целевая спецификация инфраструктурной заглушки MVP. CRM-синхронизация не реализуется.
Источники: 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/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.
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:

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 секунд:

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:

BITRIX_SYNC_DB_CHECK_TIMEOUT_SEC=5

На PostgreSQL задаются connect_timeout, command_timeout/statement_timeout. Timeout помечает check failed, отменяет query и гарантированно освобождает/инвалидирует connection.

4.5. Backoff

При успехе — обычный interval+jitter. При последовательных ошибках:

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 12;
  • 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/live200;
  • /health/ready503 с 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. Не обращается к БД.

{"status":"live"}

200, пока process/event loop обслуживает запросы.

7.2. GET /health/ready

Не выполняет новый DB query; читает snapshot.

200:

{
  "status": "ready",
  "mode": "db_connectivity_stub",
  "database": {
    "status": "ok",
    "last_success_at": "2026-07-10T09:00:00Z",
    "age_seconds": 12
  }
}

503:

{
  "status": "not_ready",
  "reason": "database_unavailable",
  "database": {
    "status": "down",
    "last_success_at": null,
    "consecutive_failures": 3
  }
}

Ready только если enabled, initial success был и последний success не старше:

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

Защита:

Authorization: Bearer ${BITRIX_SYNC_SERVICE_TOKEN}

X-Service-Token можно поддержать только как migration compatibility; канонический вариант этого модуля — Bearer. Endpoint internal-only, edge не публикует.

{
  "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

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

Уже канонические:

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=<secret>
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317

Предлагаемые technical env, которые нужно синхронизировать с arch-04 до реализации:

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 — отдельная будущая спецификация.