Files
orchestrator/docs/work-items/ORCH-117/02-trz.md

14 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used, escalate
work_item stage author_agent status created_at model_used escalate
ORCH-117 analysis analyst ready-for-review 2026-06-15 claude-opus-4-8 full-cycle

02 — ТЗ (TRZ): ORCH-117 — sandbox-only fail-closed изоляция записи в Plane

Work Item: ORCH-117 · Repo: orchestrator · Стадия: analysis

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование/решения (точка перехвата, признак тест-процесса, протокол opt-in) — задача архитектора (06-adr/). Ниже — требования и ограничения, привязанные к реальным модулям.

1. Сводка изменения

Ввести fail-closed гард записи в Plane: любая мутирующая запись (state-PATCH / comment-POST), исходящая из тест-процесса (pytest/worktree), блокируется по умолчанию и допускается только при явном аудируемом opt-in и целевом проекте из sandbox-allowlist. Боевой рантайм-процесс оркестратора и staging-рантайм (8501) не затронуты (гард для них — no-op). Перехват выполняется на момент вызова примитивов записи src/plane_sync.py (а не на импорте, где токен уже захвачен). Дефолтная тестовая поза — блокировка, через autouse-страховку в tests/conftest.py (по образцу _no_telegram). Изменение — bugfix-изоляция: не Quality Gate, не стадия; STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схема БД — не трогаются.

2. Задействованные модули / пути

Путь Действие Зачем
src/plane_sync.py изменить Врезать гард в 3 примитива записи: update_issue_state (httpx.patch, стр. 861), add_comment (httpx.post, стр. 885), _set_issue_state_direct (httpx.patch, стр. 1047). Учесть захват PLANE_HEADERS/PROJECT_ID на импорте (стр. 17/57).
src/plane_write_guard.py (кандидат, имя — на усмотрение архитектора) создать (вероятно) Чистый leaf-гард (never-raise в боевом пути, по образцу serial_gate/cancel/deploy_status_guard): decide(project_id, op, work_item) -> ALLOW | BLOCK + детект тест-процесса + sandbox-allowlist. Альтернатива/комбинация — обёртка над httpx/autouse-фикстура (решает ADR).
src/config.py изменить Новые ключи: интеграционный opt-in флаг записи из тестов, sandbox-allowlist проектов, (опц.) kill-switch гарда. Дефолты = безопасные (fail-closed в тестах).
tests/conftest.py изменить Новая autouse-фикстура fail-closed (блок Plane-записи во всех тестах по умолчанию), по образцу _no_telegram. Тесты sandbox-e2e переопределяют её своим opt-in (как test_* переопределяют _disable_*).
.env.example изменить Канон новых ORCH_* ключей (opt-in/allowlist/kill-switch) с безопасными дефолтами.
scripts/staging_check.py проверить/при необходимости адаптировать Block C (E2E в SANDBOX) должен остаться рабочим: реальная запись в sandbox под opt-in. SANDBOX_PROJECT_ID (стр. 283) — источник идентификатора sandbox.
CLAUDE.md, docs/architecture/README.md, docs/operations/INFRA.md изменить Документировать инвариант изоляции тест/staging-записи (golden source наравне с кодом).
tests/test_orch117_plane_write_isolation.py (имя — кандидат) создать Покрытие FR-1…FR-6, включая обязательный регресс ORCH-114 (TC-01).

⚠️ Список модулей не предписывает архитектуру. Точку перехвата (низкий чокпоинт в plane_sync у httpx.patch/post vs обёртка транспорта vs autouse-фикстура) и признак тест-процесса выбирает архитектор в ADR. ТЗ фиксирует требования к поведению, а не способ реализации.

3. Функциональные требования

FR-1 — Fail-closed блок записи в боевой проект из тест-процесса (BR-1, BR-2)

В тест-процессе (pytest/worktree) любой вызов update_issue_state / add_comment / _set_issue_state_direct с целевым project_id вне sandbox-allowlist (в частности боевой ORCH 7a79f0a9-5278-49cd-9007-9a338f238f9c и любой боевой enduro-проект) НЕ должен выполнять httpx.patch/httpx.post — запись блокируется. Свойство fail-closed: при невозможности достоверно определить целевой проект → блокировать (NFR-1). Гард читает контекст в момент вызова (NFR-4), не полагается на токен/os.environ.setdefault.

FR-2 — Разрешение только в sandbox при явном аудируемом opt-in (BR-2, BR-3, BR-5)

Запись из тест-процесса допускается ⇔ одновременно: (а) включён выделенный интеграционный opt-in-флаг и (б) целевой project_id ∈ sandbox-allowlist (по умолчанию — единственный 8c5a3025-4f9d-4190-b79f-fa06276bb27e). При выполнении обоих условий примитив выполняет реальный httpx-вызов в SANDBOX. Отсутствие любого условия → блок (default-deny). Запись в боевой проект запрещена даже при включённом opt-in (allowlist sandbox-only).

FR-3 — Дефолтная тестовая поза fail-closed (BR-4)

При обычном pytest tests/ (без явного opt-in) autouse-страховка conftest.py гарантирует, что ни один тест не пишет в Plane (все 3 примитива заблокированы/застаблены). Тесты sandbox-e2e, которым нужна реальная запись, явно включают opt-in в собственной фикстуре/монкипатче (поверх autouse), ограничивая реальную запись своим scope — паттерн уже применён для _disable_merge_verify/_disable_transition_lease/_no_telegram в conftest.py.

FR-4 — Детект тест-процесса vs боевой/staging рантайм (NFR-2, NFR-3)

Гард активен только в тест-процессе. Признак тест-процесса (например PYTEST_CURRENT_TEST в env / pytest в sys.modules / явный конфиг-флаг тест-режима — выбор за ADR) обязан:

  • не срабатывать в боевом рантайм-процессе оркестратора → боевая запись в Plane = байт-в-байт как прежде (no-op гарда);
  • не срабатывать в staging-рантайме (8501) → staging пишет в SANDBOX как прежде (staging — реальный процесс, не pytest).

FR-5 — Аудит/наблюдаемость (BR-6)

  • Каждая заблокированная запись → структурный лог уровня WARNING/ERROR с полями: целевой project_id, work_item, операция (state/comment), причина (prod-project-in-test / opt-in-disabled / ambiguous-target). Сообщение должно делать инцидент класса ORCH-114 очевидным.
  • Каждая разрешённая sandbox-запись из тест-процесса → audit-строка (INFO) с project_id и операцией.
  • (Опц., на усмотрение архитектора) read-only-видимость состояния гарда (флаг/allowlist) — без обязательного нового эндпоинта.

FR-6 — Поведение блокировки (NFR-5)

  • В боевом пути гард never-raise: его внутренний сбой/недоступность не роняет конвейер и не блокирует легитимную боевую запись (для боевого процесса гард в принципе no-op — FR-4).
  • В тест-процессе срабатывание гарда — громкое: запись подавляется и аудируется; допустимо жёсткое исключение/ассерт-фрэндли поведение, чтобы регресс-тест (TC-01) был детерминированно красным до фикса и зелёным после. Конкретная семантика (no-op-стаб vs raise) — решение ADR, но наблюдаемый контракт: «0 реальных PATCH/POST в боевой проект из pytest».

FR-7 — Kill-switch без чёрного хода (NFR-6)

Если вводится kill-switch гарда — он не должен при выключении переоткрывать запись в боевой проект из тест-процесса. Допустимое поведение «выключено» = деградация к прежнему (до-ORCH-117), но без молчаливого разрешения прод-записи из pytest сверх того, что было; запись в SANDBOX из тестов управляется только opt-in-флагом + allowlist (FR-2), а не общим kill-switch.

4. Изменения API

Нет обязательных. Никаких новых публичных эндпоинтов изоляция не требует. (Опционально архитектор может добавить read-only-видимость состояния гарда, например блок в GET /queue — не обязательно.)

5. Изменения схемы БД

Нет. Изоляция — рантайм-гард по конфигу/окружению; персистентного состояния не требует. Схема БД не трогается (NFR-2).

6. Требования к новым/изменённым QG checks

Нет. Это не Quality Gate и не под-гейт. QG_CHECKS / check_* / STAGE_TRANSITIONS / machine-verdict ключи (verdict:/result:/deploy_status:/staging_status:/security_status:/ coverage_status:) — байт-в-байт не тронуты (инвариант ORCH-019 NFR-1: срезается/меняется только не-гейтовое поведение; здесь — изоляция записи). Гард — свойство клиента Plane, не гейт конвейера.

7. Совместимость / регресс

  • Боевой рантайм: гард — no-op (FR-4) → запись в Plane байт-в-байт как до ORCH-117 (NFR-2).
  • Staging-рантайм (8501): реальный процесс, sandbox-проект по конфигу → пишет в SANDBOX как прежде (NFR-3). scripts/staging_check.py Block C (E2E) должен остаться зелёным.
  • Существующий тест-сьют: autouse fail-closed-фикстура не должна ломать существующие тесты — большинство тестов мокируют plane_*/add_comment (например tests/test_auto_labels_integration.py:58 monkeypatch.setattr(stage_engine, "plane_add_comment", MagicMock())), поэтому реальная запись и так не происходит; гард лишь делает это гарантией по умолчанию. Прежние неработающие os.environ.setdefault("ORCH_PLANE_API_TOKEN","test-token") строки можно не трогать — гард не зависит от них (NFR-4).
  • Sandbox-e2e: под явным opt-in + allowlist реальная запись в SANDBOX сохраняется (BR-5).
  • Kill-switch: при выключении гарда (если введён) — деградация к прежнему поведению, без переоткрытия прод-записи из тестов (FR-7/NFR-6).
  • Обратимость: дефолты безопасные (fail-closed в тестах); включение реальной записи — только явным opt-in.
  • Артефакты pipeline: создаёт/обновляет docs/work-items/ORCH-117/06-adr/ADR-001-*.md (architect, после эскалации), 10-tech-risks.md; в этом PR — обновление .env.example, CLAUDE.md, docs/architecture/README.md, docs/operations/INFRA.md.