# 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 <repo>")` (мгновенный pass).
- Отдельные пороги-флаги (A4/BR-10): напр. `security_dep_block_severity`
  (`HIGH` по умолчанию), при желании `security_secrets_block` (`true`).

### FR-6. never-raise
- Любая внутренняя ошибка гейта (сбой сканера, отсутствие бинаря, таймаут) →
  `(False, "<reason>")` **без** проброса исключения в `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-<slug>.md` (решение: размещение,
  инструменты, degrade-поведение фида, пороги). При сквозном влиянии — global ADR в
  `docs/architecture/adr/`.
- **Обновить:** `CLAUDE.md` (раздел «Артефакты задачи» — добавить 17-…),
  `docs/architecture/README.md` (таблица гейтов + реестр `QG_CHECKS` + новый раздел),
  `CHANGELOG.md`, `.env.example` (новые `ORCH_SECURITY_*`).
