20 KiB
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. Назначение и жёсткая граница
На текущем этапе сервис доказывает только:
- контейнер и FastAPI process стабильно запускаются в общем Compose;
- сервис подключается к managed PostgreSQL по private network/TLS;
- при старте и затем примерно раз в 60 секунд выполняется
SELECT 1; - состояние доступно через health и защищённый status endpoint;
- 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, GUChan.sync_suppress; - DLQ бизнес-задач и field mapping.
Упоминания полноценного sync в arch-01/02/03 описывают будущую целевую границу, а не функциональность этого stub. Расширение требует новой версии спецификации, migrations/GRANT, OpenAPI и contract tests.
Notification Center добавляет в han_app.sync_queue task type document.client_uploaded, но stub его не claim-ит и не подтверждает: задача остаётся накопленной для будущей реализации. Producer — DB trigger на client_documents, dedup key — client_document_id; payload содержит client_document_id, user_id, context_type, context_id, submission_id, bucket/object key и безопасные metadata файла, без presigned URL.
2. Технологический профиль
- Python 3.12+, FastAPI, Pydantic v2, Uvicorn.
- SQLAlchemy 2 async/
asyncpgлибо прямойasyncpgpool; выбран 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
Каждая проверка:
- получает connection из pool с bounded acquire timeout;
- выполняет параметризованный/constant
SELECT 1; - проверяет результат
1; - фиксирует monotonic duration и wall-clock UTC completion;
- возвращает connection;
- атомарно обновляет 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 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. Не обращается к БД.
{"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; - для
document.client_uploadedдобавляется read толькоclient_documentsи обработчик с идемпотентностью поclient_document_id; - запрещён 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/observabilitynetworks, без 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_appgrants; - 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:
- FastAPI перестаёт принимать новые запросы по server grace;
- выставляется stop event;
- interruptible sleep завершается немедленно;
- новый probe не стартует;
- текущий probe ждётся максимум shutdown budget, затем отменяется;
- connection корректно возвращается/invalidate;
- pool и telemetry flush закрываются;
- 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_appaccess; - 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 документирована и не реализована скрыто.
document.client_uploadedдокументирован как накопляемая stub-задача и не выдаётся за обработанный status.
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_appGRANT отсутствует до реальной 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 — отдельная будущая спецификация.