480 lines
20 KiB
Markdown
480 lines
20 KiB
Markdown
# 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=<secret>
|
||
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 — отдельная будущая спецификация.
|