14 KiB
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/postvs обёртка транспорта 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.pyBlock C (E2E) должен остаться зелёным. - Существующий тест-сьют: autouse fail-closed-фикстура не должна ломать существующие тесты —
большинство тестов мокируют
plane_*/add_comment(напримерtests/test_auto_labels_integration.py:58monkeypatch.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.