From 4f24f96169c4140bfe8ae1cb6f6fde9db6228c3c Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sun, 7 Jun 2026 16:54:54 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=326 --- docs/work-items/ORCH-022/01-brd.md | 150 +++++++++++++++ docs/work-items/ORCH-022/02-trz.md | 175 ++++++++++++++++++ .../ORCH-022/03-acceptance-criteria.md | 140 ++++++++++++++ docs/work-items/ORCH-022/04-test-plan.yaml | 126 +++++++++++++ 4 files changed, 591 insertions(+) create mode 100644 docs/work-items/ORCH-022/01-brd.md create mode 100644 docs/work-items/ORCH-022/02-trz.md create mode 100644 docs/work-items/ORCH-022/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-022/04-test-plan.yaml diff --git a/docs/work-items/ORCH-022/01-brd.md b/docs/work-items/ORCH-022/01-brd.md new file mode 100644 index 0000000..62b4d54 --- /dev/null +++ b/docs/work-items/ORCH-022/01-brd.md @@ -0,0 +1,150 @@ +# 01 — BRD: Security-гейт (secret-scanning + аудит зависимостей перед мержем) + +Work Item: **ORCH-022** +Приоритет: **★ высокий** +Источник: предложение Стрим, одобрено Славой (2026-06-04). +Стадия: analysis. + +--- + +## 1. Бизнес-проблема + +Оркестратор — автономная мульти-агентная система: агенты (`developer`) пишут код +**без человека-фильтра по умолчанию**. Перед мержем в `main` сейчас нет проверки на: + +- **утёкший секрет** — закоммиченный API-ключ / токен / пароль / приватный ключ; +- **дырявую зависимость** — пакет с известной CVE; +- (опционально) **базовую уязвимость кода** — типовой SAST-паттерн. + +Для автономной системы это критично: ошибку, которую в обычной команде «выловили бы +глазами на ревью», здесь поймать некому. Утёкший в `git`-историю ключ или уязвимая +зависимость может уехать в прод и обслуживать **все** проекты (общий инстанс, +self-hosting). + +### Прецеденты / связки +- **PR #18** (`check_ci_green`: красный CI → возврат на `development`) — задаёт целевой + паттерн поведения красного гейта. Security-гейт должен вести себя так же. +- **Управление секретами** (CLAUDE.md §8): секреты живут только в `.env`/`.env.staging` + на хосте, канон — `.env.example`. Гейт — это автоматический страж этого правила. + +--- + +## 2. Цель + +Ввести **security-гейт перед слиянием ветки задачи в `main`**, который детерминированно +(без LLM) проверяет diff/ветку на секреты и уязвимые зависимости и **блокирует +продвижение** при нарушении порогов: красный security-гейт → **возврат на `development`** +(developer-retry, как красный CI / merge-gate), задача **не уезжает в прод**. + +### Бизнес-ценность +- Структурно невозможно «тихо» влить секрет или известную CVE в прод автономной системы. +- Самоприменение правила CLAUDE.md §8 (секреты не в гит) без участия человека. +- Расширяет уже выстроенную линию автономных страховок (CI-гейт, merge-gate ORCH-043, + staging-провенанс ORCH-058, post-deploy ORCH-021). + +--- + +## 3. Объём (Scope) + +### 3.1 В объёме (v1) — **предположение по умолчанию (A1)** +1. **Secret-scanning** — обязательный минимум гейта. Поиск закоммиченных секретов + в ветке задачи / её diff относительно `main`. +2. **Dependency audit** — аудит зависимостей проекта на известные CVE. +3. **Машиночитаемый артефакт-вердикт** security-гейта (YAML-frontmatter — канон гейтов). +4. **Поведение красного гейта** = откат на `development` + developer-retry (cap + `MAX_DEVELOPER_RETRIES = 3`), наблюдаемость (Telegram + Plane-коммент). +5. **Условный раскат** (kill-switch + scope репозиториев), **never-raise**, + self-hosting (`orchestrator`) — первым. + +### 3.2 Вне объёма (v1) — **предположение (A2), отдельные WI** +- **SAST (semgrep)** — вынесен в follow-up WI: шумнее, требует policy-тюнинга правил; + гейт проектируется с точкой расширения под него, но в v1 не включается. +- **Полноценный мульти-стек** (JS/npm, Android) — см. A3 ниже; в v1 целевой стек — + Python (сам оркестратор). Связь с ORCH-9/15 фиксируется как зависимость на будущее. +- Ретроспективное сканирование уже существующей истории `main` (гейт смотрит вперёд — + ветку перед мержем, не чистит прошлое). +- Управление аллоулистом ложных срабатываний через UI/Plane (в v1 — файл в репозитории). + +### 3.3 Зафиксированные предположения по умолчанию +> ⚠️ Интерактивный опрос Owner на стадии анализа не дал ответа; ниже — +> **дефолты по конвенциям проекта**. Любой из них Owner/архитектор может переопределить +> (для A4 предусмотрены конфиг-флаги порогов). + +- **A1 (объём сканеров v1):** secret-scanning + dependency-audit. SAST отложен. +- **A2 (SAST):** отложен в отдельный WI; гейт оставляет точку расширения. +- **A3 (стек):** **Python-only сначала**, реально только для self-hosting + (`is_self_hosting_repo` / scope-CSV), как ORCH-35/43/58. Прочие репо — no-op pass. + Мульти-стек (детект стека по репо) — отдельный WI. +- **A4 (пороги):** **секреты — всегда блок**; **зависимости — блок на HIGH/CRITICAL, + warning на MEDIUM/LOW**. Пороги вынесены в конфиг (переопределяемы без редеплоя кода). + +--- + +## 4. Заинтересованные стороны +| Роль | Интерес | +|------|---------| +| Owner (Слава) | Прод-безопасность автономного конвейера; контроль порогов и раската. | +| Стрим | Инициатор; снижение риска утечки/уязвимости в автономном режиме. | +| Агент `developer` | Получает понятную причину красного гейта → быстрый фикс. | +| Агент `reviewer` | Гейт снимает с него непосильную задачу «глазами ловить ключи». | +| Все проекты на инстансе | Общий прод не должен получить секрет/CVE через одну задачу. | + +--- + +## 5. Бизнес-требования + +| ID | Требование | Приоритет | +|----|-----------|-----------| +| BR-1 | Перед слиянием ветки задачи в `main` обязателен security-гейт (секреты + аудит зависимостей). | MUST | +| BR-2 | Найден секрет (порог A4) → гейт **красный** → откат на `development`, в прод не уходит. | MUST | +| BR-3 | Уязвимость зависимости уровня блокировки (порог A4) → гейт **красный** → откат на `development`. | MUST | +| BR-4 | Уязвимость ниже порога блокировки → **warning**, продвижение не блокируется, но фиксируется в артефакте. | MUST | +| BR-5 | Красный гейт ведёт себя как красный CI / merge-gate: откат на `development` + developer-retry (cap 3), затем эскалация (Telegram + Plane Blocked). | MUST | +| BR-6 | Вердикт гейта — **машиночитаемый** (YAML-frontmatter артефакта), читается гейтом ТОЛЬКО из frontmatter (канон проекта), не из прозы. | MUST | +| BR-7 | Гейт **детерминированный, без LLM** в критическом пути (как merge-gate / image-freshness). | MUST | +| BR-8 | Гейт **never-raise**: внутренняя ошибка не роняет `advance_stage` и не вешает конвейер всех проектов. | MUST | +| BR-9 | Условный раскат: глобальный kill-switch + scope-CSV репозиториев; пусто → реально только self-hosting (`orchestrator`), прочие репо — no-op pass. | MUST | +| BR-10 | Пороги блокировки конфигурируемы (env-флаги, без редеплоя кода). | SHOULD | +| BR-11 | Наблюдаемость: причина блокировки видна (Telegram + Plane-коммент + артефакт); проход — без шума. | MUST | +| BR-12 | Документация (CLAUDE.md «Артефакты задачи», `docs/architecture/README.md` таблица гейтов, CHANGELOG, ADR) обновлена в том же PR. | MUST | +| BR-13 | Аллоулист ложных срабатываний (заведомо-безопасные совпадения, напр. в `.env.example`, фикстуры тестов) поддерживается версионируемым файлом в репозитории. | SHOULD | +| BR-14 | Точка расширения под SAST и мульти-стек заложена, но в v1 не активна (A2/A3). | SHOULD | + +--- + +## 6. Ограничения и риски (бизнес-уровень) +- **Self-hosting:** гейт исполняется внутри инстанса, который правит сам себя. Запрет на + рестарт/падение прод-контейнера в рамках задачи (CLAUDE.md §self-hosting) сохраняется — + гейт ничего не деплоит и не рестартит, только читает/сканирует. +- **Ложные срабатывания** (false positives) могут зациклить откат `→ development` + (прецедент ORCH-061 со staging-петлёй). Митигировано: cap retry=3 + аллоулист (BR-13) + + конфигурируемые пороги (BR-10) + kill-switch (BR-9). +- **Внешние БД уязвимостей** (CVE-фиды) — сетевая зависимость; недоступность фида не + должна давать ложный красный (см. AC: degrade-поведение при недоступности фида — + решение порога «fail-open vs fail-closed для аудита» закрепляется в acceptance + ADR). +- **Стоимость/время** сканирования добавляется к каждому прогону задачи — должно быть + ограничено таймаутом (как merge-retest). + +--- + +## 7. Критерий успеха (бизнес) +Ветка с подсаженным тестовым секретом и/или зависимостью с известной CRITICAL-CVE +**не может** дойти до `main`/прода: гейт краснеет, задача откатывается на `development` +с понятной причиной. Чистая ветка проходит гейт без задержек и без шума. Для не-self +репозиториев конвейер не меняется (no-op). Прод-контейнер не рестартится гейтом. + +--- + +## 8. Открытые вопросы (для архитектора / Owner) +1. **Размещение гейта** (решение архитектора): (а) на стадии `review`, либо (б) отдельный + под-гейт перед мержем на ребре `deploy-staging → deploy` (где уже живёт merge-gate + ORCH-043 / image-freshness ORCH-058). Требование BRD — «перед слиянием в `main`»; + обе опции его удовлетворяют. См. 02-trz §4. +2. **Где запускается сканер**: новый job в `.gitea/workflows/ci.yml` (тогда вердикт может + течь через существующий `check_ci_green`) **или** отдельный QG-чек/под-гейт в `src/qg`. + Решение — архитектор (02-trz фиксирует требования к обоим путям). +3. **Аудит зависимостей при недоступном CVE-фиде:** fail-open (warning) или fail-closed + (блок)? Дефолт-предложение — **fail-open с громким warning** (не плодить ложные + завороты), закрепить в ADR. +4. **Выбор конкретных инструментов** (gitleaks vs trufflehog; pip-audit vs trivy) — + технологическое решение архитектора; BRD фиксирует только функцию. diff --git a/docs/work-items/ORCH-022/02-trz.md b/docs/work-items/ORCH-022/02-trz.md new file mode 100644 index 0000000..0d95796 --- /dev/null +++ b/docs/work-items/ORCH-022/02-trz.md @@ -0,0 +1,175 @@ +# 02 — ТЗ: Security-гейт (secret-scanning + dependency audit) + +Work Item: **ORCH-022** · Стадия: analysis · См. `01-brd.md`, `03-acceptance-criteria.md`. + +> **Граница ответственности аналитика.** Ниже — *функциональные требования и точки +> касания* кода. Выбор размещения гейта в пайплайне, конкретных инструментов и схемы +> модулей — **решение архитектора** (см. §4 и `01-brd.md` §8). ТЗ фиксирует требования к +> любому из допустимых вариантов и инварианты, которые нельзя нарушать. + +--- + +## 1. Контекст кода (как есть) + +- **Стадии:** `src/stages.py::STAGE_TRANSITIONS` — линейный конвейер + `… review → testing → deploy-staging → deploy → done`. Фактический merge ветки в + `main` делает агент `deployer` **в начале стадии `deploy`** (CLAUDE/README). +- **Quality Gates:** `src/qg/checks.py` — реестр `QG_CHECKS` (имя → функция), сигнатуры + диспетчеризуются в `src/stage_engine.py::_run_qg`. +- **Существующий паттерн «красный гейт → возврат developer»:** + `check_ci_green` (PR #18) и rollback-ветки в + `stage_engine._handle_qg_failure_rollbacks` (откат на `development`, developer-retry, + cap `MAX_DEVELOPER_RETRIES = 3`, затем `set_issue_blocked` + Telegram). +- **Эталонный паттерн детерминированного под-гейта на ребре** (без LLM, never-raise, + условный раскат, откат на `development`): + - merge-gate **ORCH-043** — `src/merge_gate.py` + `check_branch_mergeable` + + `stage_engine._handle_merge_gate` (ребро `deploy-staging → deploy`); + - image-freshness **ORCH-058** — `src/image_freshness.py` + `_check_staging_image_fresh` + + `stage_engine._handle_image_freshness` (то же ребро). + Оба: leaf-модуль с чистой логикой (never-raise) + тонкая обёртка в `QG_CHECKS` + + врезка-обработчик в `advance_stage`, kill-switch `*_enabled` + scope `*_repos`, + реально только для self-hosting при пустом scope. +- **CI:** `.gitea/workflows/ci.yml` — один job `test` (pytest) на `self-hosted` раннере, + push в `feature/**` и PR в `main`. `check_ci_green` читает комбинированный статус + коммита из Gitea API. +- **Артефакты задачи** нумерованы до `16-post-deploy-log.md`. +- **Зависимости Python:** `requirements.txt` (корень репо). + +--- + +## 2. Функциональные требования к реализации + +### FR-1. Secret-scanning ветки перед мержем +- Сканировать ветку задачи / её diff относительно `origin/main` на секреты + (ключи, токены, пароли, приватные ключи). +- **Любой** подтверждённый секрет (не из аллоулиста) → вердикт **FAIL** (порог A4: секреты + всегда блокируют). +- Инструмент (gitleaks / trufflehog) — выбор архитектора. Должен запускаться offline-/ + детерминированно (без LLM) и иметь конфиг правил/аллоулиста в репозитории. + +### FR-2. Dependency audit +- Аудит зависимостей целевого стека на известные CVE. Для Python — манифест + `requirements.txt` (инструмент pip-audit / trivy — выбор архитектора). +- Классификация по severity. **Порог блокировки (A4, конфигурируемо BR-10):** + - `CRITICAL`, `HIGH` → вклад в **FAIL**; + - `MEDIUM`, `LOW` → **warning** (фиксируется в артефакте, не блокирует). +- Недоступность CVE-фида: degrade-поведение по решению ADR (дефолт-предложение — + fail-open + громкий warning, чтобы не плодить ложные завороты). Поведение должно быть + детерминированным и протестированным. + +### FR-3. Машиночитаемый артефакт-вердикт +- Гейт порождает артефакт security-отчёта с **YAML-frontmatter**, напр.: + ``` + --- + security_status: PASS # PASS | FAIL + secrets_found: 0 + deps_blocking: 0 # число уязвимостей уровня блокировки + deps_warning: 2 + --- + ``` + Имя артефакта — предложение: **`17-security-report.md`** (следующий свободный номер; + финализирует архитектор). Тело — человекочитаемый список находок. +- Вердикт читается гейтом **ТОЛЬКО из frontmatter** (канон проекта: «машинные вердикты — + строго YAML-frontmatter, никогда проза»), по образцу `_parse_deploy_status` / + `_parse_staging_status` / `check_reviewer_verdict`. Negative-токен (FAIL) авторитетен. +- Отсутствие/битый frontmatter → `(False, reason)` (fail-closed на чтении вердикта, + как у существующих парсеров). + +### FR-4. Поведение красного гейта (откат) +- `security_status: FAIL` → откат на `development` + enqueue `developer`, по образцу + `_handle_qg_failure_rollbacks` (merge-gate-ветка — точный шаблон): + - cap `MAX_DEVELOPER_RETRIES` (3); при исчерпании — `set_issue_blocked` + Telegram-алерт; + - `task_desc` для developer несёт **дословную причину** (какие секреты/CVE), по образцу + ORCH-046 (встраивание must-fix в `task_desc`), а не только ссылку на артефакт; + - Plane-коммент + `notify_qg_failure` (наблюдаемость BR-11). + +### FR-5. Условный раскат (как ORCH-35/43/58) +- Глобальный kill-switch `security_gate_enabled` (env `ORCH_SECURITY_GATE_ENABLED`, + дефолт по согласованию; рекомендуется `true` с safety-net, как у соседних фич). +- Scope `security_gate_repos` (CSV); пусто → реально только `is_self_hosting_repo(repo)` + (`orchestrator`). Прочие репо → `(True, "security-gate N/A for ")` (мгновенный pass). +- Отдельные пороги-флаги (A4/BR-10): напр. `security_dep_block_severity` + (`HIGH` по умолчанию), при желании `security_secrets_block` (`true`). + +### FR-6. never-raise +- Любая внутренняя ошибка гейта (сбой сканера, отсутствие бинаря, таймаут) → + `(False, "")` **без** проброса исключения в `advance_stage`. Контракт — + как у `check_branch_mergeable` (внешний + внутренний guard). +- Таймаут сканирования ограничен (по образцу `merge_retest_timeout_s`). + +### FR-7. Наблюдаемость +- Блокировка → Telegram + Plane-коммент (BR-11). Проход → лог-строка, без шумных + нотификаций (по образцу merge-gate pass). +- Желательно: краткий снимок в `GET /queue` (опционально, по образцу блоков `reconcile`/ + `reaper`/`post_deploy`) — на усмотрение архитектора. + +--- + +## 3. Задействованные модули `src/` (точки касания) + +| Модуль | Изменение | +|--------|-----------| +| `src/security_gate.py` (**новый leaf-модуль**) | Чистая логика гейта: запуск сканеров, классификация по severity, применение порогов/аллоулиста, формирование вердикта + парсер frontmatter. **never-raise.** По образцу `src/merge_gate.py` / `src/image_freshness.py` / `src/post_deploy.py`. | +| `src/qg/checks.py` | Новый чек `check_security_gate` (тонкая обёртка над `security_gate`, ленивый импорт во избежание циклов) + регистрация в `QG_CHECKS`. Условность (kill-switch/scope/self-hosting) — как `check_branch_mergeable` / `_check_staging_image_fresh`. | +| `src/stage_engine.py` | Врезка-обработчик `_handle_security_gate(...)` по образцу `_handle_merge_gate` / `_handle_image_freshness`: вызов в `advance_stage` на выбранном архитектором ребре; FAIL → откат на `development` (FR-4); never-raise. **`STAGE_TRANSITIONS` НЕ меняется**, если выбран вариант «под-гейт ребра». | +| `src/config.py` | Новые настройки: `security_gate_enabled`, `security_gate_repos`, `security_dep_block_severity`, `security_scan_timeout_s` (+ при необходимости пути к бинарям/конфигам сканеров). С docstring-комментариями по образцу ORCH-043/058. | +| `.gitea/workflows/ci.yml` | **Если** архитектор выберет CI-путь: новый job `security` (secret-scan + dep-audit), влияющий на комбинированный статус коммита (тогда срабатывает `check_ci_green`-паттерн PR #18). Иначе — не трогается. | +| `requirements.txt` / Dockerfile | Установка выбранных сканеров (если они Python-пакеты — в `requirements.txt`; если бинари — в Dockerfile/раннер). | +| Конфиг сканера + аллоулист | Версионируемые файлы в репозитории (напр. `.gitleaks.toml` / аллоулист) — BR-13. | +| `.openclaw/agents/developer.md` | (Если нужно) краткая инструкция developer'у про устранение security-находок при заворотах. | + +> Если выбран вариант «гейт на стадии `review`» — врезка делается в соответствующую +> ветку `advance_stage`/обработчик ревью вместо ребра `deploy-staging → deploy`. + +--- + +## 4. Размещение в пайплайне — варианты для архитектора + +Требование BRD: **«перед слиянием ветки в `main`»**. Допустимы (выбор + обоснование — в ADR): + +- **Вариант R (review):** security-проверка на стадии `review` (раньше отлов, дешевле + откат — задача ещё близко к development). Минус: дальше по конвейеру `main` может уйти + вперёд (но это закрывает merge-gate). +- **Вариант M (merge-edge, рекомендуемый к рассмотрению):** под-гейт на ребре + `deploy-staging → deploy`, рядом с merge-gate (ORCH-043) и image-freshness (ORCH-058) — + непосредственно перед фактическим мержем `deployer`'ом. Плюс: единое место «последней + страховки перед main», переиспользование готового паттерна врезки/отката/lease. +- **Вариант C (CI-job):** добавить job в `ci.yml`; вердикт течёт через `check_ci_green`. + Плюс: меньше нового кода в движке. Минус: пороги/severity-логика и артефакт-вердикт + сложнее выразить только статусом коммита. + +ТЗ не предписывает вариант; реализация обязана сохранить инварианты §6. + +--- + +## 5. Изменения API +- Новых HTTP-endpoint'ов **не требуется**. +- Допустимо (опционально, FR-7): расширить ответ `GET /queue` блоком `security` + (counts/last_run) — по образцу блоков `reconcile`/`reaper`/`post_deploy`. Не обязательно. + +## 6. Изменения схемы БД +- **Не требуется.** Состояние гейта — артефакт-файл + (при необходимости) sentinel-файлы, + по образцу merge-lease / deploy-state / post-deploy-state. Миграций БД нет. +- Если архитектор сочтёт нужным считать security-retry отдельно от developer-retry — + предпочесть подсчёт по `jobs`/`agent_runs` (как `_developer_retry_count` / + `_merge_defer_count`), без новых колонок. + +## 7. Инварианты (НЕ нарушать) +1. `STAGE_TRANSITIONS` и реестр `QG_CHECKS` остаются консистентными; при варианте + «под-гейт ребра» — `STAGE_TRANSITIONS` не меняется (триггер — то же событие стадии). +2. Машинный вердикт — только из YAML-frontmatter, не из прозы. +3. never-raise: гейт никогда не пробрасывает исключение в `advance_stage`. +4. Условность как ORCH-35/43/58: не-self репо при пустом scope не затрагиваются (no-op). +5. Гейт **не деплоит и не рестартит** прод-контейнер (self-hosting safety). +6. Откат и retry-счётчик developer не ломаются (cap=3, затем эскалация). +7. Документация (CLAUDE.md, README, CHANGELOG, ADR) обновлена в том же PR (BR-12). + +## 8. Артефакты pipeline, создаваемые/обновляемые +- **Новый:** `docs/work-items/ORCH-022/17-security-report.md` (имя финализирует архитектор) + с `security_status:`-frontmatter (FR-3) — порождается гейтом per-task. +- **ADR:** `docs/work-items/ORCH-022/06-adr/ADR-001-.md` (решение: размещение, + инструменты, degrade-поведение фида, пороги). При сквозном влиянии — global ADR в + `docs/architecture/adr/`. +- **Обновить:** `CLAUDE.md` (раздел «Артефакты задачи» — добавить 17-…), + `docs/architecture/README.md` (таблица гейтов + реестр `QG_CHECKS` + новый раздел), + `CHANGELOG.md`, `.env.example` (новые `ORCH_SECURITY_*`). diff --git a/docs/work-items/ORCH-022/03-acceptance-criteria.md b/docs/work-items/ORCH-022/03-acceptance-criteria.md new file mode 100644 index 0000000..65695ea --- /dev/null +++ b/docs/work-items/ORCH-022/03-acceptance-criteria.md @@ -0,0 +1,140 @@ +# 03 — Критерии приёмки: Security-гейт (ORCH-022) + +Формат: каждый критерий имеет чёткое условие **PASS/FAIL**. Привязка к +`01-brd.md` (BR-*) и `02-trz.md` (FR-*). + +--- + +## A. Secret-scanning (FR-1, BR-1/BR-2) + +### AC-1 — Подсаженный секрет блокирует гейт +- **PASS:** ветка с тестовым секретом (напр. фиктивный AWS-ключ формата `AKIA…` вне + аллоулиста) → `security_status: FAIL`; гейт возвращает `(False, reason)`, причина + называет секрет/файл. +- **FAIL:** секрет не обнаружен ИЛИ гейт зелёный при наличии секрета. + +### AC-2 — Чистая ветка проходит +- **PASS:** ветка без секретов → `security_status: PASS`; `secrets_found: 0`; + гейт возвращает `(True, …)`. +- **FAIL:** ложное срабатывание (FAIL на чистой ветке). + +### AC-3 — Аллоулист подавляет заведомо-безопасное (BR-13) +- **PASS:** совпадение, явно занесённое в версионируемый аллоулист (напр. плейсхолдер в + `.env.example` / фикстура теста), **не** даёт FAIL. +- **FAIL:** аллоулист игнорируется и даёт ложный FAIL. + +--- + +## B. Dependency audit (FR-2, BR-3/BR-4) + +### AC-4 — CVE уровня блокировки краснит гейт +- **PASS:** зависимость с известной `CRITICAL`/`HIGH` CVE (при пороге по умолчанию + `HIGH`) → вклад в `security_status: FAIL`; `deps_blocking >= 1`. +- **FAIL:** блокирующая уязвимость не приводит к FAIL. + +### AC-5 — Низкая severity = warning, не блок +- **PASS:** только `MEDIUM`/`LOW` уязвимости → `security_status: PASS`, при этом + `deps_warning >= 1` и находки перечислены в теле артефакта. +- **FAIL:** `MEDIUM`/`LOW` блокирует продвижение. + +### AC-6 — Порог блокировки конфигурируем (BR-10) +- **PASS:** при `ORCH_SECURITY_DEP_BLOCK_SEVERITY=CRITICAL` та же `HIGH`-уязвимость + становится warning (не блок); при `=HIGH` — блок. Поведение детерминированно + определяется флагом. +- **FAIL:** флаг не влияет на классификацию. + +### AC-7 — Degrade при недоступном CVE-фиде +- **PASS:** недоступность фида обрабатывается по решению ADR детерминированно и + протестированно (дефолт: fail-open + громкий warning, гейт не краснеет ложно). +- **FAIL:** недоступность фида даёт неконтролируемый красный/исключение. + +--- + +## C. Вердикт и артефакт (FR-3, BR-6) + +### AC-8 — Машинный вердикт только из frontmatter +- **PASS:** вердикт читается ТОЛЬКО из YAML-frontmatter `17-security-report.md`; проза с + «PASS»/«FAIL» в теле не влияет на решение. Negative-токен (FAIL) авторитетен. +- **FAIL:** вердикт извлекается из тела/прозы. + +### AC-9 — Битый/отсутствующий frontmatter → fail-closed на чтении +- **PASS:** нет frontmatter / битый YAML / нет поля `security_status` → `(False, reason)` + (как `_parse_deploy_status`/`check_reviewer_verdict`). +- **FAIL:** битый артефакт трактуется как PASS. + +### AC-10 — Артефакт создаётся с корректными полями +- **PASS:** после прогона существует `17-security-report.md` с валидным frontmatter + (`security_status`, `secrets_found`, `deps_blocking`, `deps_warning`) и телом-списком. +- **FAIL:** артефакт не создан/без машинных полей. + +--- + +## D. Откат и retry (FR-4, BR-5) + +### AC-11 — Красный гейт → откат на development + developer-retry +- **PASS:** `FAIL` → стадия задачи становится `development`, enqueue `developer`, + Plane-коммент + `notify_qg_failure`; счётчик developer-retry растёт. +- **FAIL:** при FAIL задача продвигается дальше / не откатывается. + +### AC-12 — task_desc несёт дословную причину (ORCH-046-паттерн) +- **PASS:** `task_desc` для перезапущенного developer содержит конкретику находок + (какие секреты/CVE), а не только ссылку на артефакт. +- **FAIL:** developer получает только ссылку без сути. + +### AC-13 — Cap retry и эскалация +- **PASS:** после `MAX_DEVELOPER_RETRIES` (3) безуспешных фиксов — `set_issue_blocked` + + Telegram-алерт; бесконечного отскока нет. +- **FAIL:** откат зацикливается без cap/эскалации. + +--- + +## E. Условный раскат и устойчивость (FR-5/FR-6, BR-8/BR-9) + +### AC-14 — Не-self репозиторий = no-op pass +- **PASS:** для repo, не входящего в scope и не self-hosting → гейт возвращает + `(True, "security-gate N/A for ")` мгновенно, конвейер такого репо не меняется. +- **FAIL:** гейт реально запускается/блокирует чужой репо при пустом scope. + +### AC-15 — Kill-switch отключает гейт +- **PASS:** `ORCH_SECURITY_GATE_ENABLED=false` → гейт — no-op pass (`(True, …)`), + поведение конвейера 1:1 как до ORCH-022. +- **FAIL:** при выключенном флаге гейт всё ещё блокирует. + +### AC-16 — never-raise +- **PASS:** искусственный сбой (нет бинаря сканера / таймаут / исключение внутри) → + `(False, reason)` без проброса исключения; `advance_stage` не падает, конвейер других + задач/проектов не встаёт. +- **FAIL:** внутренняя ошибка пробрасывается/вешает движок. + +### AC-17 — Таймаут ограничен +- **PASS:** сканирование, превысившее `ORCH_SECURITY_SCAN_TIMEOUT_S`, корректно + прерывается → детерминированный вердикт (по политике degrade), без зависания. +- **FAIL:** сканер висит без таймаута. + +--- + +## F. Инварианты и интеграция (BR-7/BR-12, TRZ §7) + +### AC-18 — STAGE_TRANSITIONS/QG_CHECKS консистентны +- **PASS:** при варианте «под-гейт ребра» `STAGE_TRANSITIONS` не изменён; новый чек + зарегистрирован в `QG_CHECKS`; `_run_qg` корректно его диспетчеризует. Все + существующие тесты гейтов/стадий зелёные. +- **FAIL:** сломан реестр/переходы/существующие тесты. + +### AC-19 — Гейт не деплоит/не рестартит прод +- **PASS:** код гейта не вызывает деплой-хук/рестарт прод-контейнера; только + чтение/сканирование. +- **FAIL:** гейт инициирует рестарт/деплой. + +### AC-20 — Документация обновлена в том же PR (BR-12) +- **PASS:** обновлены `CLAUDE.md` (артефакт 17-…), `docs/architecture/README.md` + (таблица гейтов + реестр QG + раздел ORCH-022), `CHANGELOG.md`, `.env.example` + (`ORCH_SECURITY_*`); заведён ADR `06-adr/ADR-001-*`. +- **FAIL:** функционал есть, документация/ADR не обновлены → reviewer обязан + REQUEST_CHANGES (CLAUDE.md §6). + +### AC-21 — End-to-end на тестовой задаче +- **PASS:** прогон на self-hosting-репо: грязная ветка (секрет/CVE) → откат на + `development`; после фикса чистая ветка → гейт зелёный → конвейер идёт дальше; прод не + затронут в процессе. +- **FAIL:** любой шаг E2E не воспроизводится. diff --git a/docs/work-items/ORCH-022/04-test-plan.yaml b/docs/work-items/ORCH-022/04-test-plan.yaml new file mode 100644 index 0000000..694ebce --- /dev/null +++ b/docs/work-items/ORCH-022/04-test-plan.yaml @@ -0,0 +1,126 @@ +work_item: ORCH-022 +title: "Security-гейт: secret-scanning + dependency audit перед мержем" +notes: > + План тестов для security-гейта. Чистая логика выносится в leaf-модуль + src/security_gate.py (never-raise) — основной предмет unit-тестов (по образцу + tests для merge_gate / image_freshness / post_deploy / staging_verdict). + Интеграция врезки в advance_stage и условный раскат — integration-тесты. + Имена модулей тестов финализирует разработчик/архитектор по факту реализации. + +tests: + # --- Secret-scanning (FR-1 / AC-1..AC-3) --- + - id: TC-01 + type: unit + description: "Подсаженный тестовый секрет в diff -> вердикт FAIL, secrets_found>=1, причина называет находку." + module: tests/test_security_gate.py + expected: PASS + - id: TC-02 + type: unit + description: "Чистая ветка без секретов -> вердикт PASS, secrets_found=0." + module: tests/test_security_gate.py + expected: PASS + - id: TC-03 + type: unit + description: "Совпадение из аллоулиста (плейсхолдер .env.example / фикстура) НЕ даёт FAIL." + module: tests/test_security_gate.py + expected: PASS + + # --- Dependency audit + пороги (FR-2 / AC-4..AC-7) --- + - id: TC-04 + type: unit + description: "CVE уровня HIGH/CRITICAL при пороге HIGH -> вклад в FAIL, deps_blocking>=1." + module: tests/test_security_gate.py + expected: PASS + - id: TC-05 + type: unit + description: "Только MEDIUM/LOW уязвимости -> PASS, deps_warning>=1, находки в теле артефакта." + module: tests/test_security_gate.py + expected: PASS + - id: TC-06 + type: unit + description: "Конфиг порога: severity=CRITICAL делает HIGH-CVE warning; severity=HIGH делает её блоком." + module: tests/test_security_gate.py + expected: PASS + - id: TC-07 + type: unit + description: "Недоступный CVE-фид -> детерминированный degrade по политике ADR (дефолт fail-open + warning), без исключения и без ложного FAIL." + module: tests/test_security_gate.py + expected: PASS + + # --- Вердикт / парсер frontmatter (FR-3 / AC-8..AC-10) --- + - id: TC-08 + type: unit + description: "Вердикт читается ТОЛЬКО из YAML-frontmatter; проза PASS/FAIL в теле не влияет; negative-токен авторитетен." + module: tests/test_security_gate.py + expected: PASS + - id: TC-09 + type: unit + description: "Нет frontmatter / битый YAML / нет поля security_status -> (False, reason) (fail-closed на чтении)." + module: tests/test_security_gate.py + expected: PASS + - id: TC-10 + type: unit + description: "Артефакт 17-security-report.md создаётся с валидным frontmatter (security_status, secrets_found, deps_blocking, deps_warning) и телом-списком." + module: tests/test_security_gate.py + expected: PASS + + # --- never-raise / таймаут / условность (FR-5/FR-6 / AC-14..AC-17) --- + - id: TC-11 + type: unit + description: "Отсутствие бинаря сканера / внутреннее исключение -> (False, reason), исключение не пробрасывается (never-raise)." + module: tests/test_security_gate.py + expected: PASS + - id: TC-12 + type: unit + description: "Превышение ORCH_SECURITY_SCAN_TIMEOUT_S -> корректное прерывание и детерминированный вердикт, без зависания." + module: tests/test_security_gate.py + expected: PASS + - id: TC-13 + type: unit + description: "check_security_gate: не-self репо при пустом scope -> (True, 'security-gate N/A for ') мгновенно." + module: tests/test_qg_security.py + expected: PASS + - id: TC-14 + type: unit + description: "check_security_gate: ORCH_SECURITY_GATE_ENABLED=false -> no-op pass (True)." + module: tests/test_qg_security.py + expected: PASS + - id: TC-15 + type: unit + description: "Новый чек зарегистрирован в QG_CHECKS и корректно диспетчеризуется _run_qg." + module: tests/test_qg_security.py + expected: PASS + + # --- Откат / retry в stage_engine (FR-4 / AC-11..AC-13) --- + - id: TC-16 + type: integration + description: "security_status FAIL -> advance_stage откатывает на development, enqueue developer, Plane-коммент + notify_qg_failure." + module: tests/test_stage_engine_security_gate.py + expected: PASS + - id: TC-17 + type: integration + description: "task_desc перезапущенного developer содержит дословную причину находок (ORCH-046-паттерн), не только ссылку." + module: tests/test_stage_engine_security_gate.py + expected: PASS + - id: TC-18 + type: integration + description: "После MAX_DEVELOPER_RETRIES (3) -> set_issue_blocked + Telegram-алерт; бесконечного отскока нет." + module: tests/test_stage_engine_security_gate.py + expected: PASS + - id: TC-19 + type: integration + description: "security_status PASS -> advance_stage продвигает конвейер штатно (без отката, без шумных нотификаций)." + module: tests/test_stage_engine_security_gate.py + expected: PASS + + # --- Инварианты / интеграция (BR-7/BR-12 / AC-18..AC-19) --- + - id: TC-20 + type: integration + description: "При варианте 'под-гейт ребра' STAGE_TRANSITIONS не изменён; существующие тесты стадий/гейтов остаются зелёными." + module: tests/test_stages.py + expected: PASS + - id: TC-21 + type: integration + description: "Гейт не вызывает деплой-хук/рестарт прод-контейнера (self-hosting safety)." + module: tests/test_stage_engine_security_gate.py + expected: PASS