Композитная risk-модель закрытия задачи (v15-risk-model)
Статус: принято (v1.5, эпик v15-evidence-attestation) Реализация: scripts/risk_model.py · Потребители: v15-risk-compute-on-done (расчёт при task_done), v15-l3-risk-trigger (high → обязательный L3 adversarial review), v15-risk-surface-metrics (metrics/status).
Зачем
QG-2 отвечает бинарно: гейты прошли или нет. Но «прошли» бывает разным: scoped-прогон с одним реальным гейтом и тысячестрочным диффом по auth-коду — не то же самое, что полный прогон на пятистрочном фиксе документации. Risk-score сводит эту разницу в одно число 0.0–1.0, чтобы downstream-механика (L3-ревью, метрики, аудит) могла реагировать ступенчато, а не вкл/выкл.
Формула
score = Σ wᵢ · fᵢ , fᵢ ∈ [0, 1], Σ wᵢ = 1Взвешенная сумма, не max(): риск закрытия накапливается из независимых слабостей. Один посредственный фактор не должен доминировать (за это отвечает вес), но три посредственных вместе обязаны поднимать уровень — max() это компаундирование игнорирует.
Каждый фактор нормализован так, что 0.0 = безопасно, 1.0 = максимально рискованно.
Факторы и веса
| Фактор | Вес | Что измеряет | Нормализация |
|---|---|---|---|
gate_coverage | 0.25 | Доля сконфигурированных verify-гейтов, которые НЕ выполнились реально (skipped) | 1 − ran/total; пустой список = 1.0 |
test_delta | 0.20 | Изменение исходников без пропорционального изменения тестов | source=0 → 0.0; tests/source ≥ 0.5 уже даёт ≤0.2 |
ac_evidence | 0.20 | Доля AC-критериев без явного evidence | 1 − covered/total; нет AC = 1.0 |
security_hits | 0.20 | Затронуты ли security-чувствительные файлы (security_pattern) | 0 хитов = 0.0; иначе пол 0.5 + 0.5·доля |
code_churn | 0.15 | Размер диффа (log-шкала) | log10(lines+1)/3, кэп 1.0 (≈1000 строк) |
Обоснование весов
- gate_coverage 0.25 — максимальный. Вся цепочка evidence-attestation (подписанные receipts) заверяет только гейты, которые РЕАЛЬНО выполнились; skipped-гейты исключаются из receipt by design. Непокрытый гейт — самый сильный одиночный сигнал, что «зелёное» закрытие ничего не доказывает.
- test_delta 0.20. Изменённый код без изменённых тестов — классический предвестник тихой регрессии. Чуть ниже гейтов, потому что scoped-прогоны легитимно пропускают несвязанные suites, а доли тестов в диффе зависят от жанра задачи.
- ac_evidence 0.20. AC без evidence = «сделано» со слов агента. QG-2 уже требует
--ac-verified, но парсер evidence показывает, какая доля критериев подтверждена ссылками на тесты/прогоны. - security_hits 0.20. Стоимость дефекта на security-поверхности кратно выше. Пол 0.5 («любое касание ≥ medium») синхронизирован с политикой verify-кэша: security-файлы всегда re-verify, кэшу не доверяем.
- code_churn 0.15 — минимальный. Большой дифф прячет дефекты, но сам по себе слабейший предиктор: рефакторинг-переименование на 800 строк безопаснее 20 строк в парсере денег. Лог-шкала, чтобы 30 и 100 строк отличались заметно, а 2000 и 5000 — уже нет.
Уровни
| Уровень | Диапазон | Реакция downstream |
|---|---|---|
low | score < 0.33 | нет |
medium | 0.33 ≤ score < 0.66 | подсветка в metrics/status |
high | score ≥ 0.66 | обязательный L3 adversarial review (v15-l3-risk-trigger) |
Границы попадают В уровень выше (0.33 → medium, 0.66 → high): спорный балл эскалируется, а не амнистируется.
Контракт ошибок
- Неизвестное имя фактора, значение вне [0,1], NaN/Inf →
ValueError. Тихий clamp маскировал бы баг интегратора и портил исторические ряды. - Отсутствующий канонический фактор → консервативный дефолт 1.0 + имя в списке
defaultedв выводе. Интеграция, не умеющая измерить фактор, не должна выглядеть безопаснее той, что измерила его как плохой (fail-visible, не fail-open).
Известные ограничения
- Веса априорные, не обученные. Калибровка на исторических
verification_runs/usage_eventsвозможна после накопления риск-строк (v15-risk-compute-on-done пишет их в БД). - test_delta считает файлы, не assert'ы. Переименование тестового файла выглядит как «тесты менялись». Сознательный компромисс: дёшево, детерминированно, без парсинга AST.
- code_churn не различает жанры диффа (генерированный код, lock-файлы). Частично купируется малым весом.
- Формула линейна — нет взаимодействий факторов (security × churn). Усложнение без данных для калибровки = ложная точность.
- Score описывает закрытие задачи, не проект. Агрегация по эпику/сессии — отдельная задача (
v15-risk-surface-metrics).