---
work_item: ORCH-099
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---

# 10 — Технические риски: ORCH-099 — FND/F1a: лёгкий `/metrics` (сырьё для sidecar)

Work Item: **ORCH-099** · Repo: **orchestrator** · Стадия: architecture

> Информационный (гейтом не парсится). Перечисляет риски реализации и их митигейшн.

## Реестр рисков

| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| TR-1 | Гонка чтения `/proc/<pid>/stat`: процесс умер между выборкой running-job и чтением proc → `FileNotFoundError`/частичная строка | Сред. | Низ. | `_read_cpu_ticks` never-raise → `cpu_ticks: null` (NFR-2, FR-3, AC-6); прочие поля и эндпоинт целы. Парс proc-stat читает поля **после** `') '` (устойчивость к пробелам в `comm`). |
| TR-2 | PID-namespace mismatch: `jobs.pid` относится не к тому PID-namespace, где орк читает `/proc` | Низ. | Сред. | Агент — дочерний процесс орка (launcher `subprocess` в том же контейнере/ns), `pid` стамплется орком (ORCH-065) → `/proc/<pid>` валиден в том же ns. Несовпадение → `null` (деградация, не падение). |
| TR-3 | Расхождение часов орк↔sidecar искажает расчёт CPU-доли | Низ. | Низ. | Контракт by-design: sidecar считает дельту по `(cpu_ticks, generated_at)` из **двух ответов орка** → всё в домене часов орка, skew-иммунно (ADR D2). |
| TR-4 | Дрейф контракта `/metrics`↔ожидания F1b при будущих расширениях | Сред. | Сред. | `schema_version` (старт 1) + аддитивно-толерантная политика (sidecar игнорирует незнакомые ключи, толерирует отсутствие опциональных); контракт документирован в README в одном репо (BR-7, NFR-6). |
| TR-5 | `cost.running = null` (токены ещё не застамплены) ошибочно прочитан sidecar'ом как «ноль стоимости» | Сред. | Низ. | Документировать: `null` ≠ ноль (= «не завершён, не застамплен»); авторитет по спенду — `cost.aggregate` (ADR D7). |
| TR-6 | Контеншн на `CircuitBreaker._lock` при опросе breaker-снимка | Низ. | Низ. | `snapshot()` держит lock кратко (только чтение полей, `src/queue_worker.py:113`); раздел обёрнут own `try/except` → `breaker: null` при любой проблеме. Частота опроса sidecar — секунды, не микросекунды. |
| TR-7 | Рост стоимости `SUM`-агрегата по `agent_runs` при разрастании таблицы | Низ. | Низ. | `agent_cost_totals()` — один индексируемый full-scan `SUM`, n мал (десятки–сотни строк на текущем горизонте); точка расширения — временное окно/`repo`-срез без бампа версии (ADR D2/D7). |
| TR-8 | Соблазн «протащить» в `/metrics` логику алертинга/порогов | Низ. | Сред. | Scope-граница BRD (вне объёма) + NFR-1 (read-only) + reviewer-контроль; мозг (пороги/алерты) — строго F1b. |
| TR-9 | Незаметная мутация состояния (случайный не-read-only вызов в сборщике) роняет инвариант read-only | Низ. | Выс. | Сборщик использует только SELECT-helper'ы; AC-5/TC-09 — тест «снимок БД до/после идентичен»; reviewer сверяет дифф на отсутствие `INSERT/UPDATE/DELETE/CREATE/ALTER` и запуска процессов. |

## Сводный вывод

Доминирующий класс — **гонки/деградация чтения runtime-данных** (`/proc`, in-memory breaker), все
закрыты конструктивным never-raise по разделам (эталон `serial_gate.snapshot()`) → деградация в
`null`, не отказ. Контрактные риски (TR-4/TR-5) закрыты `schema_version` + документированием.
Наивысшее потенциальное влияние (TR-9, нарушение read-only) митигируется тестом «БД до/после
идентична» (TC-09) и reviewer-сверкой диффа.

Изменение полностью аддитивно, read-only, never-raise, обратимо (kill-switch + удаление модуля).
**Остаточный риск для прод-конвейера (self-hosting, общий с enduro-trails) — near-zero:** эндпоинт
физически не способен мутировать состояние или уронить процесс (NFR-1/NFR-2/NFR-4). Эскалация в
анализ не требуется. Формальный лейбл **`arch:major-change`** проставляется консервативно (новый
компонент наблюдаемости + публичный контракт), хотя по существу изменение низкорисковое; прод-деплой
— строго через staging-гейт (8501), без рестарта прод-контейнера.
