Files
han-app/architectory/arch-07-observability.md
T

540 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# arch-07. Контракт наблюдаемости
> Канонический контракт telemetry для всех application VM и private SigNoz.
> Реализация на конкретной VM — в [`module-09-observability-vm1.md`](../VM1_app/documentation/module-09-observability-vm1.md) и [`module-09-observability-vm2.md`](../VM2_services/documentation/module-09-observability-vm2.md).
> Имена сущностей — [`arch-00-glossary.md`](arch-00-glossary.md). Границы системы — [`arch-01-system-architecture.md`](arch-01-system-architecture.md). HTTP — [`arch-02-api-contracts.md`](arch-02-api-contracts.md). Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md). Env — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). Процесс — [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md). Host security — [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md).
## Назначение
Документ фиксирует то, что **должно совпасть между ВМ1 и ВМ2**: схема сигналов, корреляция, redaction, sampling, Collector pipeline и удаленный backend. Локальные `service.name`, scrape targets, дашборды и алерты конкретной машины в этом файле не детализируются.
Этот контракт **нельзя** независимо кастомизировать в репозитории VM. Изменение JSON-полей, labels, redaction, sampling или `service.namespace` сначала вносится сюда.
## 1. Цели и границы
Наблюдаемость должна позволять:
- найти пользовательский запрос по `request_id`, `trace_id` или `ux_session_id`;
- восстановить путь «frontend → nginx → API → Safety → S3/Open Lines»;
- измерять доступность, задержку, ошибки и насыщение каждого сервиса;
- обнаруживать backlog, DLQ, circuit open, потерю telemetry и истечение TLS;
- расследовать security/audit события без записи PII и секретов;
- проверять SLO по данным, независимым от бизнес-логов.
Telemetry не является источником бизнес-истины и не влияет на auth, safety verdict или доставку сообщений. Недоступность Collector не должна блокировать запросы. Audit в `han_app` — отдельный durable контур.
## 2. Production-like решение MVP
### 2.1. Обязательный минимум в основном Compose
Каждый root Compose (ВМ1 и ВМ2) обязан содержать локальный `otel-collector`. Prometheus/Grafana/Loki/Tempo не обязаны размещаться на application VM.
Операбельный вариант:
1. приложения на каждой VM экспортируют OTLP gRPC только в свой local `otel-collector:4317`;
2. Collector отправляет telemetry в self-hosted SigNoz на отдельной VM `192.168.0.5:4317`;
3. JSON stdout остаётся аварийным локальным журналом Docker с rotation;
4. пока удалённый backend недоступен в конкретном окружении, допустим архитектурный минимум из arch-03: bounded JSON stdout/platform logs и Collector `debug` exporter с sampling в acceptance; такой режим не считается полноценным production-хранением и не закрывает alerting/SLO.
Требуемые возможности backend: OTLP ingest, поиск traces, PromQL-совместимые или эквивалентные metrics, поиск структурированных logs, alerting, RBAC, retention и TLS. Для текущего SigNoz plaintext OTLP разрешён только внутри доверенной приватной сети; UI — через SSH jump host.
ВМ2 никогда не использует Docker hostname collector ВМ1. Каждый collector имеет собственный `otel-queue`.
### 2.2. Самостоятельно размещаемая опция
Опциональный Compose profile `observability-local` может включать Prometheus, Grafana, Loki и Tempo. Он **не включается по умолчанию на малой VM**: стек требует дополнительной RAM/диска и сам становится объектом backup/monitoring.
Для operable local-варианта нужны отдельный volume каждому backend, retention limits, compaction, auth через ops/VPN и отсутствие host ports. Grafana доступна только через отдельный защищённый ops route/VPN, не через публичный `/`.
Рекомендуемый минимум VM при local profile: дополнительно 4 vCPU, 8 ГБ RAM и 100+ ГБ SSD сверх приложения; точный размер — после измерения ingest. Нужен ли profile на ВМ1 — TBD профильной спецификации ВМ1.
## 3. Архитектура Collector
### 3.1. Компоненты
```text
backend services ─OTLP gRPC/HTTP─┐
nginx/Redis/Keycloak exporters ──┼─> otel-collector
Docker JSON stdout ─filelog───────┘ ├─ OTLP remote backend (SigNoz)
├─ Prometheus endpoint (optional)
└─ debug exporter (acceptance only)
```
Collector запускается одним сервисом MVP. При росте разделяется на agent/gateway: локальный agent принимает и буферизует, remote gateway выполняет policy/export.
### 3.2. Receivers
- `otlp` gRPC `0.0.0.0:4317` — основной internal receiver;
- `otlp` HTTP `0.0.0.0:4318` — совместимость SDK;
- `prometheus` — scrape самого Collector, exporters и сервисных `/metrics`, если они не идут OTLP;
- `filelog` — только если Docker logging driver предоставляет read-only каталог/volume; парсит JSON stdout без чтения secret-файлов;
- `hostmetrics` — CPU, memory, filesystem, network VM/container host, если Collector получает только необходимые read-only mounts.
Порты `4317`, `4318`, `8888`, `8889` используют `expose`, не `ports`. Receiver доступен только в сети `observability`.
### 3.3. Processors и порядок
Во всех pipelines первым стоит защита памяти, последним — batch:
1. `memory_limiter`: check interval 1s, soft/hard limit относительно container memory;
2. `resource`: нормализует `service.namespace=han-chat`, `deployment.environment`, `service.version`;
3. `attributes`: удаляет/маскирует sensitive attributes;
4. `transform`: нормализует route/status/error semantic conventions;
5. `filter`: исключает health noise, debug events и запрещённые поля;
6. `probabilistic_sampler` или tail sampling для traces;
7. `batch`: bounded batch/timeout;
8. при remote export — `queued_retry`/sending queue и `file_storage` extension.
`memory_limiter` не заменяется Docker OOM limit. При pressure Collector отбрасывает telemetry контролируемо и увеличивает `otelcol_processor_refused_*`.
### 3.4. Exporters
- `otlp/remote`: endpoint из secret env/mount; для SigNoz в доверенной private network — plaintext `192.168.0.5:4317`; для любого иного remote — TLS verify;
- `prometheus`: optional pull endpoint только internal;
- `debug`: только `APP_ENV=test|acceptance`, verbosity normal; production debug exporter по умолчанию выключен;
- `loki`/`otlphttp` — только если выбран дополнительный backend и его контракт закреплён.
Секрет exporter-а не должен появляться в rendered config, логах или `/debug/configz`. Config монтируется read-only; secret подставляется env.
### 3.5. Extensions
- `health_check` — internal endpoint, используется Compose;
- `pprof`/`zpages` — только при явном ops profile, internal network;
- `file_storage` — persistent sending queue на volume `otel-queue`;
- `basicauth`/`oauth2client` — если требует remote backend.
### 3.6. Принципиальная конфигурация
```yaml
receivers:
otlp:
protocols:
grpc: {endpoint: 0.0.0.0:4317}
http: {endpoint: 0.0.0.0:4318}
prometheus:
config:
scrape_configs:
- job_name: otel-collector
static_configs: [{targets: ["127.0.0.1:8888"]}]
processors:
memory_limiter:
check_interval: 1s
limit_mib: 384
spike_limit_mib: 96
resource/common:
attributes:
- {key: service.namespace, value: han-chat, action: upsert}
- {key: deployment.environment, value: "${env:APP_ENV}", action: upsert}
attributes/redact:
actions:
- {key: http.request.header.authorization, action: delete}
- {key: http.request.header.cookie, action: delete}
- {key: url.query, action: delete}
- {key: db.statement, action: delete}
filter/noise:
error_mode: ignore
traces:
span:
- 'attributes["http.route"] == "/health/live"'
batch:
send_batch_size: 1024
timeout: 5s
exporters:
otlp/remote:
endpoint: "${env:OTEL_REMOTE_ENDPOINT}"
# true только для утверждённого plaintext SigNoz внутри private network;
# для любого другого remote — false и проверка CA.
tls: {insecure: ${env:OTEL_REMOTE_TLS_INSECURE}}
headers: {authorization: "${env:OTEL_REMOTE_AUTH_HEADER}"}
extensions:
health_check: {endpoint: 0.0.0.0:13133}
file_storage: {directory: /var/lib/otelcol/queue}
service:
extensions: [health_check, file_storage]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resource/common, attributes/redact, filter/noise, batch]
exporters: [otlp/remote]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, resource/common, attributes/redact, batch]
exporters: [otlp/remote]
logs:
receivers: [otlp]
processors: [memory_limiter, resource/common, attributes/redact, filter/noise, batch]
exporters: [otlp/remote]
telemetry:
metrics: {address: 0.0.0.0:8888}
```
Конкретная версия schema проверяется командой Collector `validate`; image закрепляется по digest. Значения memory/batch/queue — стартовые, не SLO. Для текущего SigNoz `tls.insecure` и auth header задаются профильной спецификацией VM согласно O1/O-TBD1. Scrape targets кроме самого Collector добавляет спецификация VM.
## 4. Resource attributes и корреляция
Обязательные resource attributes:
- `service.name` — только из реестра ниже;
- `service.namespace=han-chat`;
- `service.version=<release-or-git-sha>`;
- `deployment.environment=production-like|production`;
- `host.name`/`service.instance.id` без публичного IP.
Реестр `service.name`:
| Имя | Владелец |
|---|---|
| `nginx` | nginx каждой VM; различать экземпляры `host.name` / `service.instance.id` |
| `api-backend` | ВМ1 |
| `bitrix-local-app` | ВМ1 |
| `keycloak` | ВМ1 |
| `sms-service` | ВМ1, callback/internal API |
| `sms-worker` | ВМ1, отправка в Direct |
| `message-safety` | ВМ2 |
| `bitrix-sync` | ВМ2 |
| `redis` | Redis каждой VM; различать экземпляры |
| `otel-collector` | collector каждой VM |
Новое имя добавляется только сюда, затем в спецификацию владельца.
Обязательные поля request-события:
- `request_id`;
- `trace_id`, `span_id`;
- `ux_session_id` — nullable, только когда передан;
- `service.name`;
- `environment` либо canonical `deployment.environment`.
`request_id` формирует/валидирует nginx; сервис возвращает его клиенту и передаёт downstream. `trace_id` берётся из active span. `ux_session_id` не является auth и не должен использоваться как metric label.
## 5. W3C propagation
- принимаются только валидные `traceparent` и опциональный `tracestate`;
- nginx передаёт context upstream; при edge instrumentation создаёт server span;
- caller создаёт child spans для своих зависимостей (PostgreSQL, Redis, S3, Safety, Open Lines, JWKS, CRM);
- internal calls передают `traceparent`, `tracestate`, `X-Request-ID`;
- `baggage` по умолчанию не принимается от внешнего клиента; если включён, allow-list исключает PII;
- Bitrix24/S3 могут не вернуть context: внешний client span всё равно закрывается результатом;
- async outbox/inbox связывается span link с исходным trace; новый worker trace не притворяется продолжением спустя долгий срок.
Frontend может отправлять валидный `traceparent`, но backend не доверяет его sampling/security атрибутам.
## 6. JSON stdout contract
Одна JSON-запись на строку UTF-8:
```json
{
"timestamp": "2026-07-10T09:00:00.123Z",
"level": "INFO",
"service.name": "api-backend",
"service.version": "git-abcdef0",
"environment": "production-like",
"module": "message_service",
"event": "message.delivery.completed",
"message": "Message delivery completed",
"request_id": "01J...",
"trace_id": "32hex",
"span_id": "16hex",
"ux_session_id": "uuid-or-null",
"route": "/api/v1/dialogs/{dialog_id}/messages",
"method": "POST",
"status_code": 201,
"duration_ms": 742,
"dependency": "bitrix-local-app",
"outcome": "success",
"error_code": null
}
```
Правила:
- `event` — стабильная mnemonic, `message` — безопасное описание;
- route — template, никогда raw URI с id/query;
- stack trace допускается только в internal error log после redaction;
- message text, callback body, SQL values и file content запрещены;
- Docker driver ограничен `50m × 5`, но это buffer, не retention backend;
- multiline stack trace сериализуется полем JSON, не отдельными строками.
## 7. Redaction и data minimization
Удаляются или маскируются:
- `Authorization`, Cookie, Set-Cookie, JWT, OAuth/code/refresh/access tokens;
- raw OTP/mock code, Keycloak admin/client password;
- phone/email/full name, device id, IP по policy (допустим HMAC/truncated);
- message text, filenames с PII, document/file contents;
- DSN/password, Redis URL, S3 keys;
- presigned URL и любая query string;
- Bitrix raw payload/download URL/application token;
- `db.statement` с literals; предпочтительно operation/table, не SQL.
Redaction выполняется в SDK/logger **до stdout**, затем повторяется Collector processor. Collector не может считаться единственной защитой. Автотесты отправляют canary secrets/PII и требуют отсутствие во всех трёх сигналах.
## 8. Общие правила instrumentation
Детальный список инструментов конкретной VM — в профильной спецификации. Ниже правила, общие для всех сервисов.
### 8.1. FastAPI и workers
- OpenTelemetry ASGI/FastAPI server spans с route template;
- HTTPX client spans с sanitized host/method/status;
- SQLAlchemy/asyncpg spans без параметров и raw statement;
- Redis instrumentation с command name и DB index, без key/value;
- boto/S3 spans: operation/bucket logical name, без object key/query;
- background workers: span на claim/process/finalize, links на origin;
- исключить `/health/live` из traces; readiness оставить в metrics и sampled logs.
### 8.2. PostgreSQL
Собираются pool wait/checked-out, transaction duration, error class, migrations revision, managed PG provider metrics (CPU, storage, connections, locks, replication/PITR state). `user_id`, SQL text и row data не labels.
### 8.3. Redis
`redis_exporter` подключается отдельным ACL user только на `INFO`, `PING`, безопасные latency/keyspace metrics. Нужны memory ratio, evictions, expirations, blocked/rejected clients, command latency, AOF status/rewrite, Pub/Sub buffers, key count/TTL агрегаты. Keys/values не экспортируются.
### 8.4. nginx
JSON access log содержит `request_id`, извлечённый `trace_id`, route class, method, normalized path, status, bytes, request/upstream duration/status, TLS version, cache status. `$request` с query не используется.
Collector `filelog` parser:
- разбирает JSON, timestamp и severity;
- переносит `service.name=nginx`;
- превращает пустые/`-` в null;
- route class нормализует в bounded set;
- отбрасывает ACME/health success noise;
- не парсит raw URI в labels.
Native nginx OTEL module предпочтителен, если image/version закреплены. Без него nginx только передаёт W3C context и коррелирует access log; первый server span создаёт upstream.
### 8.5. Host/Docker
CPU, load, memory/swap, disk usage/inodes/IO, network, container restarts/OOM, Docker daemon health и clock sync. Container name/version — bounded labels; container id не хранится как долгосрочный high-cardinality label.
## 9. Метрики: запрет high-cardinality
Никакие UUID/user/session/dialog/task/message id не labels. Они допустимы только в sampled logs/traces при принятой retention. Allow-list labels; route template вместо raw path; status class/known code; dependency enum.
Карта бизнес-метрик принадлежит спецификации VM-владельца сервиса. Сквозные SLI ниже используют сигналы обеих VM.
## 10. Сквозные dashboards в SigNoz
SigNoz обязан иметь:
1. **Executive/SLO**: availability, error budget burn, p50/p95/p99, message delivery, auth, active incidents.
2. **Business flow**: guest config → OTP → bootstrap → session → dialog → safety → Open Lines → operator reply.
3. **Collector health**: accepted/sent/refused/dropped, queue, retry, exporter errors, memory/CPU — отдельно по `host.name` ВМ1 и ВМ2.
Сервисные дашборды nginx/API/Safety/Keycloak/Redis/sync — в спецификациях VM. Каждая панель содержит release annotation, environment filter и links trace→logs по `trace_id`.
## 11. SLI, SLO и общие alerts
Значения — начальная production-like политика до load/product review:
| SLI | Initial SLO, 30 дней |
|---|---|
| HTTPS edge availability | 99.9% |
| public config/content successful requests | 99.9% |
| protected read API successful requests | 99.5% |
| Keycloak login flow availability | 99.5% |
| text message accepted и доставлен в Open Lines | 99.0% |
| operator inbox applied без permanent loss | 99.5% |
| p95 protected read API | < 750 ms |
| p95 text send без внешнего rate limit | < 5 s |
| telemetry Collector ingest availability | 99.0%, не входит в business availability |
Файловый send измеряется отдельно: p95 не должен превышать configured safety poll budget; user-cancel, safety deny, 4xx validation и edge abuse 429 не считаются server failure. 503/504 и unexpected 5xx считаются.
Paging/ticket alerts, привязанные к конкретному сервису, задаёт спецификация VM. Общие инфраструктурные paging alerts:
- multi-window burn: 14.4× за 5m/1h или 6× за 30m/6h;
- PostgreSQL unavailable/connection saturation >90%;
- Collector exporter queue >80%, dropped/refused telemetry >0 sustained;
- TLS expiry <14 дней warning, <7 дней page;
- disk >85% warning, >92% page; OOM/restart loop;
- managed PG backup/PITR failure.
Alert содержит service, environment, symptom, dashboard, runbook, release и безопасный query; не содержит PII.
## 12. Sampling, cardinality и retention
### Traces
- errors/5xx, circuit, timeout, DLQ, safety final deny и slow requests — 100%;
- обычные успешные requests — 510%;
- health/ACME success — 0%;
- tail sampling предпочтителен в Collector, но head sample SDK должен оставлять достаточно данных;
- sampling decision передаётся W3C.
### Metrics
Cardinality budget: целевой <10 000 active series на MVP environment. CI проверяет запрещённые labels.
### Retention initial
- metrics: 30 дней high resolution, 13 месяцев downsampled при доступности backend;
- traces: 7 дней, errors 14 дней;
- technical logs: 14 дней, security/auth logs 30 дней;
- audit `han_app`: 365 дней **только как временное допущение до legal policy**;
- raw Bitrix callback не хранится в telemetry;
- local Docker logs: не более 250 МБ/container и 5 файлов.
Legal retention/erasure имеет приоритет; изменение требует обновления policy и backup lifecycle.
## 13. Collector health и отказоустойчивость
Контролируются:
- `/health` extension;
- process CPU/RSS/restarts;
- accepted/refused/sent/failed spans, points, records;
- batch send size/latency;
- exporter queue capacity/size, enqueue failures, retry age;
- file storage usage/corruption;
- scrape failures;
- config reload/validation.
При remote outage queue хранится на `otel-queue` с bounded size/age. При заполнении отбрасываются сначала low-priority success traces/logs; приложение продолжает работу. Нельзя позволять queue заполнить системный диск. Telemetry outage/overflow fail-open для business и Safety readiness, но создаёт alert.
## 14. Docker Compose collector
`otel-collector`:
- pinned contrib image;
- networks: только `observability`, а для scrape internal targets — минимально необходимая `backend`;
- `expose`: 4317, 4318, 13133, 8888/8889;
- без host ports;
- config read-only, `otel-queue` volume rw;
- non-root, read-only rootfs, tmpfs `/tmp`, drop capabilities, no-new-privileges;
- перед collector запускается idempotent `otel-queue-init`: one-shot без сети
и secrets, с `user: 0:0`, `cap_drop: ALL` и только `CHOWN/FOWNER`, выставляет
mount root `10001:10001 0700`; collector зависит от
`service_completed_successfully`;
- initial limit: 0.5 CPU/512 MiB, queue disk 510 ГБ; уточнить load test;
- healthcheck extension;
- restart policy с backoff;
- приложения имеют bounded non-blocking OTLP exporter queue.
Новый named volume считается потенциально `root:root`; основной collector не
запускается от root и volume не получает `0777`. Ownership init проверяется
после первого create и повторного recreate.
Доступ к Docker socket запрещён. Для container metrics используется безопасный exporter/hostmetrics, а не unrestricted socket mount.
## 15. Security
- OTLP receiver internal-only; при переходе между hosts — mTLS, кроме явно зафиксированного plaintext SigNoz в private network;
- remote exporter credentials least privilege;
- Grafana/Prometheus/Loki/Tempo не публичны;
- RBAC: viewer/operator/admin; audit доступа к logs/traces;
- dashboards не показывают PII;
- config/secret permissions 0400/0600;
- dependency/image scan и SBOM;
- защита от log injection: JSON encoding, control chars, bounded field lengths;
- telemetry input не исполняет expressions из пользовательских значений;
- регулярная secret-canary проверка и incident deletion procedure.
## 16. Общие runbooks
### Collector not-ready
1. `docker compose ps otel-collector` и bounded logs.
2. Проверить config validation, memory/OOM, queue volume.
3. Проверить DNS/TLS/auth remote exporter.
4. Не рестартовать бесконечно при полной queue; сначала освободить/расширить безопасно.
5. Бизнес-сервисы оставить работающими; подтвердить local JSON logs.
6. После восстановления проверить drain и gap.
### Telemetry отсутствует у одного сервиса
1. Проверить `service.name`, endpoint/protocol и сеть `observability`.
2. Проверить SDK queue/drop counters и clock.
3. Отправить synthetic request с `X-Request-ID`.
4. Найти его в stdout, Collector accepted и backend.
5. Проверить sampling/filter/redaction rules.
### Remote backend outage
1. Подтвердить exporter errors, а не application outage.
2. Оценить queue fill rate/time-to-full.
3. Ограничить debug exporter; не включать verbose.
4. При длительном outage увеличить sampling только через reviewed config.
5. После восстановления подтвердить drain и создать incident note о потере данных.
### Cardinality/ingest spike
1. Найти новое metric/log attribute по release annotation.
2. Отключить offending instrument/filter в Collector.
3. Проверить raw path/id/user/session labels.
4. Rollback instrumentation при риске стоимости/доступности.
5. Добавить CI regression test.
### Логи содержат секрет/PII
1. Ограничить доступ и остановить offending export.
2. Сохранить только incident metadata, не копировать значение.
3. Ротировать скомпрометированный secret.
4. Удалить данные по процедуре backend/provider.
5. Исправить source redaction + Collector defense; добавить canary test.
Разбор высокой latency сообщения — в спецификации VM, которая владеет соответствующим hop.
## 17. Общий Definition of Done
- Collector config проходит validate и запускается в root Compose каждой VM;
- OTLP gRPC и HTTP принимают три сигнала;
- все сервисы имеют правильные resource attributes из реестра §4;
- request проходит nginx/API/Safety/Open Lines с одним `request_id` и связанным trace;
- `ux_session_id` есть только где передан и не является label;
- remote outage, queue full, Collector restart и backend recovery rehearsed;
- local profile, если включён, имеет volumes/retention/auth и не публикует порты;
- secret/PII canary отсутствует в logs/traces/metrics;
- cardinality и sampling tests проходят;
- SLO queries воспроизводимы и исключения документированы;
- runbooks связаны с alerts;
- dashboards и alerts provisioned из versioned files для SigNoz; до полного provisioning это остаётся acceptance/TBD, а не выполненный production DoD.
Профильный DoD VM дополняет этот список своими сервисами и не переопределяет контракт.
## 18. Допущения, TBD и конфликты
### Решения
- O1: обязательный архитектурный минимум — local Collector на каждой application VM; выбран self-hosted SigNoz на отдельной VM `192.168.0.5`, доступный по приватному OTLP gRPC.
- O2: Prometheus/Grafana/Loki/Tempo — отдельный operable profile, не скрытая обязательная нагрузка основной VM.
- O3: JSON stdout — аварийный локальный buffer; audit App DB — durable.
- O4: telemetry fail-open для business path, но потеря telemetry alertится.
- O5: ID/PII не labels; source redaction обязательна до Collector.
Поля JSON, resource attributes, redaction, sampling, `service.namespace` и адрес SigNoz меняются только здесь.
### TBD
- O-TBD1 закрыт: self-hosted SigNoz, `192.168.0.5:4317`, plaintext только внутри доверенной приватной сети; UI через SSH jump host.
- O-TBD2: утвердить SLO/RPS/error-budget с product owner.
- O-TBD3: legal retention/erasure и допустимость IP/user-agent.
- O-TBD4: точные sampling и resource limits после load test.
- O-TBD5: поддерживаемый nginx OTEL module и Keycloak native tracing по pinned versions.
- O-TBD6: нужен ли local observability profile в первой VM — решает спецификация ВМ1.
### Обнаруженные архитектурные конфликты
1. `arch-03` разрешает stdout/platform exporter как минимум, но production-like расследования и alerts без backend ограничены. Закрыто выбором SigNoz (O1 / O-TBD1); stdout остаётся аварийным buffer.
2. Test-only режимы Message Safety не входят в production SLO. Production dashboards используют canonical `403`, sticky verdict и фактический `processing_mode`; значение `stub` считается ошибкой cutover. Детали — спецификация ВМ2.
3. Queue/CRM SLI включаются только после module-07 preflight/cutover и отражают фактическое состояние `bitrix-sync` на ВМ2; синтетический CRM success запрещён. Детали — спецификация ВМ2.
4. Retention, RPO/RTO и production SLO открыты в module-01/04/06/08; значения этого документа являются initial ops policy, не закрывают legal/product TBD.
5. Observability env (`OTEL_REMOTE_*`, service names, sampling/queue limits) добавлены в arch-04 и `.env.example`; sampling/queue limits уточняются после load test.
## 19. Ссылки
- Профильные спецификации: [`module-09-observability-vm1.md`](../VM1_app/documentation/module-09-observability-vm1.md), [`module-09-observability-vm2.md`](../VM2_services/documentation/module-09-observability-vm2.md).
- VM/Docker logging и firewall: [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md) и профильные module-10/runbook.
- Nginx stdout/access log: [`arch-08-nginx.md`](arch-08-nginx.md) и профильные module-03.
- HTTP/OTLP registry: [`arch-02-api-contracts.md`](arch-02-api-contracts.md).
- Compose topology: [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md).