Разработана первая версия приложений

This commit is contained in:
mi
2026-07-10 18:06:14 +03:00
parent aa8761d1b3
commit 8c7b4074c4
162 changed files with 12178 additions and 16 deletions
+479
View File
@@ -0,0 +1,479 @@
# 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 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/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?options=-csearch_path%3Dbitrix_sync
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 — отдельная будущая спецификация.