fix(plane): sandbox-only fail-closed guard for Plane writes from test process (ORCH-117)

Close the root class of incident ORCH-114: a pytest/worktree process performed a
REAL write (PATCH issues state=<Done> + comment) against the PRODUCTION Plane
project, because test/staging processes inherit the live Plane token
(PLANE_HEADERS/PROJECT_ID are captured at import — a post-hoc env/token swap is a
no-op) and nothing forced them to write only to the sandbox. Symmetric to the
existing _no_telegram autouse floor.

- New pure never-raise leaf src/plane_write_guard.py (decide/audit_block/
  audit_allow), wired into the 3 plane_sync write primitives (update_issue_state /
  add_comment / _set_issue_state_direct) via _guard_allows_write, AT CALL TIME,
  before any network step. Active ONLY in a test process (pytest in sys.modules /
  PYTEST_CURRENT_TEST); live + staging runtimes (uvicorn) are a strict no-op.
- In a test process: default-deny. A write is allowed iff opt-in
  (plane_test_write_enabled) AND target project in the sandbox allowlist
  (plane_test_sandbox_projects, default = the one SANDBOX id). Prod is blocked even
  with opt-in (allowlist sandbox-only); unresolved project -> block (fail-closed).
- Independent second layer: tests/conftest.py::_plane_sandbox_only autouse floor.
  Intentionally NO prod-block kill-switch (anti back-door, NFR-6).
- Audit: block -> loud ERROR; sandbox-allow -> INFO.
- Bypass fixtures for the 3 (+1) pre-existing tests that assert on the mocked
  write primitive's httpx call (header/URL/state logic), the guard is no Quality
  Gate: STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict / DB schema
  untouched.
- Tests: tests/test_orch117_plane_write_isolation.py (TC-01 mandatory ORCH-114
  regression + TC-02..TC-14). Docs: CLAUDE.md, architecture/README.md,
  operations/INFRA.md, .env.example, CHANGELOG.md.

Refs: ORCH-117
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 21:16:28 +03:00
committed by deployer
parent 77d3a66356
commit 861b5ee984
14 changed files with 679 additions and 1 deletions

View File

@@ -29,6 +29,25 @@ class Settings(BaseSettings):
plane_bot_deployer: str = ""
plane_bot_stream: str = ""
# ORCH-117 (ADR-001 D6): sandbox-only fail-closed guard for Plane WRITE
# primitives from a test/worktree process (regression of incident ORCH-114,
# where a pytest run mutated a live prod board issue). The guard (leaf
# src/plane_write_guard.py) is a no-op in the live runtime (no pytest in the
# uvicorn process); in a test process it blocks every Plane write UNLESS both
# the opt-in flag is ON and the target project is in the sandbox allowlist.
# plane_test_write_enabled -> opt-in for REAL Plane writes from a test process
# (env ORCH_PLANE_TEST_WRITE_ENABLED). Default False
# = safe (default-deny). NOT a kill-switch for the
# prod-block: even ON, only sandbox projects are
# writable (allowlist below); a prod write from
# pytest stays physically impossible (NFR-6/FR-7).
# plane_test_sandbox_projects -> CSV allowlist of sandbox project ids the opt-in
# may write to (env ORCH_PLANE_TEST_SANDBOX_PROJECTS).
# Default = the single SANDBOX project. Empty -> no
# project is writable from a test process at all.
plane_test_write_enabled: bool = False
plane_test_sandbox_projects: str = "8c5a3025-4f9d-4190-b79f-fa06276bb27e"
# Gitea
gitea_url: str = "http://localhost:3000"
gitea_public_url: str = "" # external URL for clickable links in comments; falls back to gitea_url