Files
orchestrator/docs/operations/INFRA.md
claude-bot 861b5ee984 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>
2026-06-15 21:32:20 +03:00

30 KiB
Raw Blame History

INFRA.md — инфраструктура и эксплуатация оркестратора

RUNBOOK. Топология, контейнеры, порты, переменные окружения, границы. Секреты тут НЕ хранятся — только дескрипторы. Реальные значения — в .env на хосте.

Топология

                 host: mva154 (slin@82.22.50.71), network_mode: host
 ┌──────────────────────────────────────────────────────────────────────┐
 │  orchestrator        (PROD)     :8500   env_file .env                  │
 │    БД: ./data/orchestrator.db          (обслуживает ВСЕ прод-проекты)  │
 │                                                                        │
 │  orchestrator-staging (STAGING) :8501   env_file .env.staging          │
 │    БД: ./data/staging/orchestrator.db  (изолирована, только sandbox)   │
 │    profile: staging — НЕ стартует обычным `docker compose up`          │
 └──────────────────────────────────────────────────────────────────────┘
        │ webhooks                                  │ git
        ▼                                           ▼
   Plane (ag_proj)                            Gitea (localhost:3000)
   /repos/<project>  ← общий каталог репозиториев (host: /home/slin/repos)

Инвариант deploy-базы (ORCH-112, нормативно). Shared main checkout <host_repos_dir>/<repo> (= /home/slin/repos/orchestrator == /repos/orchestrator в контейнере через bind-mount == settings.deploy_host_repo_path) — это deploy/worktree-management база, НЕ редактируемый workspace. Рабочие изменения туда не пишутся конвейером/агентами: агенты — worktree /repos/_wt/<repo>/<branch> (git_worktree), docker build — worktree-контекст, fallback'и гейтов — read-only git show origin/main. Self-deploy git pull устойчив к грязной базе (resilient-pull, см. self-hosting-страховки ниже).

Контейнеры

Контейнер Роль Порт env_file БД (хост) Старт
orchestrator прод 8500 .env ./data/orchestrator.db docker compose up -d
orchestrator-staging staging / песочница 8501 .env.staging ./data/staging/orchestrator.db docker compose --profile staging up -d orchestrator-staging

Оба: network_mode: host, init: true (tini как PID 1 — reaping зомби, B-2), restart: unless-stopped.

Рантайм-uid (ORCH-040)

Оба сервиса бегут под user: "1000:1000" (slin), не root. Артефакты конвейера (git worktree /repos/_wt/..., коммиты в docs/work-items/...) создаются как slin:slin, поэтому git pull / git reset на хосте под slin работают без ручного chown. Доступ к docker.sock сохранён через group_add: ["999"] (gid docker, не через root — НЕ удалять). При переносе на другой хост uid пересматривается. См. ADR docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md и глобальный docs/architecture/adr/adr-0005-container-runs-as-host-uid.md.

Host-prerequisites (обязательная процедура Owner, в git не коммитятся):

  • P-1 (блокер): uid 1000 читает claude creds — chown -R 1000:1000 /home/slin/.claude; проверка sudo -u '#1000' test -r /home/slin/.claude/.credentials.json. Без этого preflight (ORCH-044) заворачивает весь конвейер.
  • P-2: ssh-ключи в /home/slin/.orchestrator-ssh читаемы uid 1000 (маунт ведёт в /home/slin/.ssh).
  • P-3: id slin1000:1000; /repos, /app/data уже 1000:1000.
  • P-4: прод-рестарт self — только в окно тишины (GET /status без активных задач): общий инстанс с enduro-trails.
  • P-5 (блокер миграции uid, ORCH-057): нормализация всех legacy root:root файлов в /repos — см. подраздел «Миграция uid: обязательная нормализация legacy root-файлов» ниже. Без неё первый job падает на launch при создании worktree (инцидент 06.06, ORCH-043).

Миграция uid: обязательная нормализация legacy root-файлов (ORCH-057)

ORCH-040 сменил user: контейнера, но не владельца уже существующих файлов в bind-mount /repos, созданных прежним root-контейнером. Под uid 1000 src/git_worktree.py::ensure_worktree не может создать worktree рядом с root:root каталогом /repos/_wt/fatal: could not create leading directories … Permission denied (агент даже не стартует). С ORCH-057 эта ошибка распознаётся и выдаётся внятно (с лечащей командой) + детектится на старте сервиса (WARNING/Telegram, блок fs_ownership в GET /queue), но фактический chown обязан выполнить оператор под root на хосте (контейнер бежит без root и chown'ить чужие файлы не может).

Обязательный разовый шаг при миграции uid / на новой среде (под root на mva154, ПЕРЕД стартом app):

# 1) worktree-корень (все ветки всех проектов режутся здесь)
sudo chown -R 1000:1000 /home/slin/repos/_wt
# 2) .git обоих репо (objects / worktrees-административные записи)
sudo chown -R 1000:1000 /home/slin/repos/orchestrator/.git \
                        /home/slin/repos/enduro-trails/.git
# 3) корень orchestrator целиком (включая data/runs/*.log — 37 root-логов в инциденте)
sudo chown -R 1000:1000 /home/slin/repos/orchestrator
# Проверка (пусто = ок):
find /home/slin/repos/_wt ! -uid 1000 -print -quit

Процедура идемпотентна (повтор на корректной среде — no-op) и входит в чеклист деплоя/миграции self. Область охвата: _wt, оба .git (objects+worktrees), data/runs. См. docs/work-items/ORCH-057/06-adr/ADR-001-legacy-ownership-normalization.md и сквозной docs/architecture/adr/adr-0031-legacy-ownership-normalization.md.

Тома (volumes)

  • ./data/app/data (БД; у staging — ./data/staging)
  • /home/slin/repos/repos (рабочие репозитории проектов)
  • /var/run/docker.sock (для docker-операций деплоя)
  • claude-code, node, ~/.claude* (CLI агентов, ro)
  • ~/.orchestrator-ssh/home/slin/.ssh (ro, деплой по ssh; target в HOME агента, согласован с HOME=/home/slin из launcher — ORCH-040, ранее /root/.ssh)

Disk-watchdog: мониторинг заполнения диска mva154 (ORCH-063)

07.06.2026 диск хоста mva154 тихо дорос до 100% и положил весь конвейер всех проектов (один прод-инстанс orchestrator на общей БД/очереди). Чтобы такой инцидент сигнализировался заранее, работает фоновый daemon-поток src/disk_watchdog.py (каркас reconciler/job_reaper):

  • Что мониторится: заполнение хост-разделов по смонтированным bind-путям (/repos → host /home/slin/repos, /app/data → host ./data) через stdlib shutil.disk_usageНЕ overlay / контейнера (иначе замер ложно-низкий). Пути с одним физическим устройством (st_dev) дедуплицируются → один алерт, не два.
  • Порог и период: при заполнении ≥ 85% (ORCH_DISK_MONITOR_THRESHOLD_PCT) шлётся Telegram-алерт оператору; замер — раз в 300с (ORCH_DISK_MONITOR_INTERVAL_S). Пока диск выше порога, повтор — не чаще раза в ~6ч (ORCH_DISK_MONITOR_REALERT_S, анти-спам). При возврате ниже порога — однократное recovery-сообщение.
  • Как отключить: ORCH_DISK_MONITOR_ENABLED=false (демон не стартует; GET /queuedisk_monitor.enabled=false; поведение 1:1 как сейчас). Наблюдаемость — блок disk_monitor в GET /queue (последний замер: used_pct/free_gb/alerting/last_alert_at по каждому пути).
  • Что делать при алерте: watchdog только сигнализирует — он не трогает диск/контейнер и не рестартит прод (self-hosting безопасность). Освобождение docker build cache автоматизировано отдельным демоном (ORCH-062, см. ниже); прочие «пожиратели» — старые worktree-каталоги /home/slin/repos/_wt/* завершённых задач, логи, dangling-образы (docker image prune) — по-прежнему ручная операция оператора (авто-уборка этих категорий — вне объёма ORCH-062/063).

Build-cache-pruner: авто-prune docker build cache на mva154 (ORCH-062)

Доминирующий «пожиратель» в инциденте 07.06.2026 — docker build cache (≈11 ГБ от частых пересборок прод/staging-образов). Чтобы он не мог снова заполнить диск без оператора, работает фоновый daemon-поток src/build_cache_pruner.py (каркас disk_watchdog) — «вторая половина» watchdog'а: watchdog сигналит, pruner убирает.

  • Что делает: каждые ORCH_BUILD_CACHE_PRUNE_INTERVAL_S (дефолт 21600с = 6ч) выполняет строго docker builder prune -f --filter until=<until> (BuildKit GC; дефолт until=24h — удаляется build cache старше суток, тёплый свежий кэш сохраняется). Команда затрагивает только build cacheНЕ образы/контейнеры запущенных сервисов; рестарт docker daemon/прода НЕ выполняется (self-hosting безопасность).
  • Как исполняется: в контейнере нет docker CLI (образ несёт только openssh-client git), поэтому уборка идёт на хосте через ssh тем же каналом ORCH_DEPLOY_SSH_USER@_HOST, что деплой/image_freshness. Пустой ORCH_DEPLOY_SSH_HOST → тик no-op (фича активна только на self-host, где ssh настроен).
  • Как отключить: ORCH_BUILD_CACHE_PRUNE_ENABLED=false (демон не стартует; поведение 1:1 как до ORCH-062). Наблюдаемость — блок build_cache_prune в GET /queue (enabled/interval_s/ until/last_run_ts/last_reclaimed/last_error); never-raise; in-memory учёт (без миграции).
  • Ручной fallback (если ssh-канал недоступен) — host-cron на mva154: 0 */6 * * * docker builder prune -f --filter until=24h (off-git, процедура Owner).

Переменные окружения (карта; значения — в .env)

Переменная Назначение
ORCH_PLANE_API_URL / _TOKEN / _WORKSPACE_SLUG доступ к Plane API
ORCH_PLANE_WEB_URL внешний (браузерный) web-URL Plane для кликабельных ссылок на issue в уведомлениях (ORCH-017); пусто → фолбэк на ORCH_PLANE_API_URL, loopback-фолбэк → ссылка опускается
ORCH_PLANE_WEBHOOK_SECRET HMAC-проверка вебхуков Plane
ORCH_PLANE_TEST_WRITE_ENABLED ORCH-117: opt-in реальной записи в Plane из тест-процесса (дефолт false = default-deny). НЕ kill-switch прод-блока: даже true пишет только в sandbox-allowlist (прод-запись из pytest невозможна). В боевом/staging рантайме гард — no-op
ORCH_PLANE_TEST_SANDBOX_PROJECTS ORCH-117: CSV-allowlist sandbox-проектов, куда opt-in разрешает запись из тестов (дефолт = единственный SANDBOX 8c5a3025-…; пусто → ни один проект из тестов не пишется)
ORCH_GITEA_URL / _TOKEN / _WEBHOOK_SECRET доступ к Gitea + HMAC
ORCH_CLAUDE_BIN путь к claude CLI
ORCH_REPOS_DIR / ORCH_HOST_REPOS_DIR каталог репозиториев (в контейнере / на хосте)
ORCH_DB_PATH путь к SQLite БД
ORCH_PROJECTS_JSON реестр проектов (Plane id → repo + prefix); пусто → дефолт из src/projects.py
ORCH_AGENT_MODEL_DEFAULT LLM-модель агентов по умолчанию (ORCH-41); дефолт claude-opus-4-8
ORCH_AGENT_MODEL_<AGENT> per-agent модель (ANALYST/ARCHITECT/DEVELOPER/REVIEWER/TESTER/DEPLOYER); пусто → default
ORCH_AGENT_EFFORT_DEFAULT режим работы --effort по умолчанию (ORCH-41): low|medium|high|xhigh|max; дефолт high
ORCH_AGENT_EFFORT_<AGENT> per-agent effort; дефолт: думающие → high, tester/deployer → medium
ORCH_AGENT_FALLBACK_MODEL опц. фолбэк-модель при overloaded (--fallback-model); пусто → без флага
ORCH_SELF_DEPLOY_ENABLED ORCH-036 kill-switch исполняемого самодеплоя (true); false → legacy-путь для всех
ORCH_SELF_DEPLOY_REPOS CSV репозиториев с реальным самодеплоем; пусто → только self-hosting orchestrator
ORCH_DEPLOY_REQUIRE_MANUAL_APPROVE требовать человеческий Plane «Approved» для прод-деплоя (true, безопасно)
ORCH_DEPLOY_FINALIZE_DELAY_S / _MAX_ATTEMPTS задержка и бюджет defer'ов finalizer'а (Фаза C; 90 / 10)
ORCH_DEPLOY_SSH_USER / _SSH_HOST куда запускается detached хост-деплой (Фаза B, ssh user@host)
ORCH_DEPLOY_HOOK_SCRIPT / _HOST_REPO_PATH путь к хук-скрипту (отн. репо) и чекаут orchestrator на хосте
ORCH_DEPLOY_PROD_SOURCE_IMAGE staging-образ для build-once retag на прод-тег (без rebuild)
ORCH_DEPLOY_PROD_TARGET_SERVICE / _TARGET_PORT / _TARGET_IMAGE / _COMPOSE_PROFILE / _PREV_IMAGE_FILE прод-цель хука + снапшот для авто-rollback
ORCH_IMAGE_FRESHNESS_ENABLED ORCH-058 единый kill-switch провенанса staging-образа (A+B как целое); дефолт true, false → legacy build-once без проверки свежести
ORCH_IMAGE_FRESHNESS_REPOS CSV репозиториев с реальным гейтом свежести; пусто → только self-hosting orchestrator
ORCH_RECONCILE_ENABLED kill-switch sweeper потерянных webhook (ORCH-053); дефолт true. При инциденте/раскаткеfalse глушит весь фоновый reconciler
ORCH_RECONCILE_PLANE_ENABLED отдельный флаг F-2 (опрос Plane API); false гасит только plane-ветку, F-1 продолжает работать; дефолт true
ORCH_RECONCILE_INTERVAL_S период фонового прохода reconciler, сек; дефолт 120
ORCH_RECONCILE_GRACE_DEFAULT_S порог «застряла» по tasks.updated_at, сек; дефолт 600
ORCH_RECONCILE_GRACE_OVERRIDES_JSON per-stage пороги, напр. {"development":300}; невалидный JSON → дефолт
ORCH_RECONCILE_NOTIFY_UNBLOCK слать Telegram при разблокировке застрявшей задачи; дефолт true
ORCH_DISK_MONITOR_ENABLED kill-switch disk-watchdog (ORCH-063); дефолт true. false → демон не стартует, поведение 1:1 как сейчас
ORCH_DISK_MONITOR_INTERVAL_S период heartbeat-замера заполнения диска, сек; дефолт 300
ORCH_DISK_MONITOR_THRESHOLD_PCT порог заполнения для алерта, %; дефолт 85 (валидация 1..100, иначе → дефолт)
ORCH_DISK_MONITOR_REALERT_S cooldown повторного алерта, пока выше порога, сек; дефолт 21600 (~6 ч)
ORCH_DISK_MONITOR_PATHS CSV отслеживаемых хост-bind-путей; пусто → /repos,/app/data
ORCH_BUILD_CACHE_PRUNE_ENABLED kill-switch build-cache-pruner (ORCH-062); дефолт true. false → демон не стартует, поведение 1:1 как до задачи
ORCH_BUILD_CACHE_PRUNE_INTERVAL_S период тика авто-prune, сек; дефолт 21600 (~6 ч); валидация >0, иначе → дефолт
ORCH_BUILD_CACHE_PRUNE_UNTIL возраст удержания тёплого кэша (docker builder prune --filter until=); дефолт 24h; валидация ^\d+[smhdw]?$, иначе → 24h
ORCH_BUILD_CACHE_PRUNE_ALL добавить -a к prune (только в паре с until); дефолт false
ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S таймаут ssh-команды prune, сек; дефолт 120
ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB Telegram при освобождении ≥ N ГБ; дефолт 0 (тихо)
DEPLOY_SSH_USER / _HOST / DEPLOY_HOOK_SCRIPT параметры деплой-хука
ORCH_AGENT_HOME_DIR ORCH-101: HOME всех акторских subprocess-env (агенты/finalizer/monitor) и таргет маунтов .claude/.claude.json/.ssh и ARG APP_HOME Dockerfile (группа ORCH-040 двигается согласованно); дефолт /home/slin
ORCH_AGENT_GIT_NAME / ORCH_GIT_EMAIL_DOMAIN ORCH-101: git-идентичность коммитов агентов (claude-bot @ mva154.local); системные акторы держат платформенные имена deploy-finalizer/post-deploy-monitor под тем же доменом
ORCH_STAGING_PORT ORCH-101: порт staging-инстанса (дефолт 8501); читается и image_freshness, и compose command: staging; guard fail-closed при совпадении с прод-портом (ORCH-058 AC-9)
ORCH_HOST_CLAUDE_DIR / _CLAUDE_JSON / _SSH_DIR ORCH-101: host-источники bind-маунтов ~/.claude, ~/.claude.json, ssh-ключей (/home/slin/.{claude,claude.json,orchestrator-ssh})
ORCH_HOST_CLAUDE_CODE_DIR / _NODE_BIN ORCH-101: host-пути дистрибутива claude-code и бинаря node (/usr/lib/node_modules/@anthropic-ai/claude-code, /usr/bin/node)
ORCH_RUN_UID / ORCH_RUN_GID ORCH-101: uid:gid контейнера (user:) + ARG APP_UID/APP_GID (дефолт 1000:1000, ORCH-040)
ORCH_DOCKER_GID ORCH-101: gid docker-группы хоста для group_add (дефолт 999; «МИНА 1» ORCH-040 — не удалять)

Секреты — только в .env / .env.staging / .env.watchdog на хосте, в гит НЕ коммитятся. Канон — .env.example, .env.staging.example, .env.watchdog.example (ORCH-102: sidecar-watchdog читает ТОЛЬКО .env.watchdog; WATCHDOG_* в .env для него инертен). Выпуск нового комплекта секретов для нового хоста — scripts/gen_secrets.py (боевые секреты не копируются). Тираж платформы на новую инфру (карта переменных, секреты, smoke-процедура, границы Lite/Bundled) — docs/operations/REPLICATION.md (ORCH-101); сквозная инструкция Lite-тиража для внешнего оператора («голый хост → конвейер», орк+watchdog) — docs/deployment/LITE_SETUP.md (ORCH-102). Когерентность портов при смене прод-порта: ORCH_DEPLOY_PROD_TARGET_PORTWATCHDOG_METRICS_URLORCH_POST_DEPLOY_BASE_URL.

Реестр проектов (src/projects.py, ORCH-6)

Связывает Plane project id → gitea repo + work-item prefix. Источник: ORCH_PROJECTS_JSON, fallback — встроенный дефолт. Прод видит: enduro-trails (ET), orchestrator (ORCH). Staging видит ТОЛЬКО orchestrator-sandbox (SANDBOX) — изоляция.

Модель и effort агентов (src/config.py + src/agents/launcher.py, ORCH-41)

Модель LLM и режим работы (--effort) каждого агента конфигурируемы — глобально per-agent (env) и per-project (через ORCH_PROJECTS_JSON).

Приоритет резолвинга (resolve_agent_model / resolve_agent_effort):

  1. per-project override — agent_models / agent_efforts в записи ORCH_PROJECTS_JSON;
  2. per-agent env — ORCH_AGENT_MODEL_<AGENT> / ORCH_AGENT_EFFORT_<AGENT> (если непусто);
  3. глобальный дефолт — ORCH_AGENT_MODEL_DEFAULT (claude-opus-4-8) / ORCH_AGENT_EFFORT_DEFAULT (high);
  4. пусто → флаг не передаётся, действует дефолт CLI.

Значения effort: low < medium < high < xhigh < max — рычаг «качество vs стоимость/время». Дефолтная раскладка: думающие агенты (analyst/architect/developer/reviewer) → high, механические (tester/deployer) → medium. Невалидное значение → лог-warning, флаг опускается.

Per-project override в ORCH_PROJECTS_JSON (поля agent_models / agent_efforts опциональны, старые записи работают):

{"plane_project_id":"...","repo":"orchestrator","work_item_prefix":"ORCH",
 "agent_models":{"developer":"claude-opus-4-8","reviewer":"claude-sonnet-4-6"},
 "agent_efforts":{"developer":"xhigh","tester":"low"}}

⚠️ Бюджет (ORCH-38): claude-opus-4-8 дефолт в коде; реальное переключение прод-env делается отдельно после согласования.

⚠️ Self-hosting — оркестратор дорабатывает САМ СЕБЯ

Факт: прод-инстанс orchestrator (8500) — ОДИН на ВСЕ прод-проекты (enduro-trails + orchestrator), с ОБЩЕЙ БД ./data/orchestrator.db и общей очередью задач (ORCH-1).

Следствие — групповой риск: когда орк выполняет задачу из проекта ORCH (дорабатывает себя), он бежит в том же инстансе, что обслуживает enduro-trails.

  • Рестарт / падение прод-контейнера орк-задачей → конвейер ВСЕХ проектов встаёт.
  • Кривой self-деплой (ORCH-36, Вариант B) → лежат все проекты сразу.
  • Общая очередь → орк-задача занимает concurrency-слоты других проектов.

Что изолировано (безопасно):

  • Staging (8501) — отдельная БД (./data/staging), отдельный реестр (ORCH_PROJECTS_JSON = только sandbox). Прод-проекты не видит.
  • Репозитории разделены, изоляция веток через git worktree (ORCH-2).
  • Запись в Plane из тест-процесса — sandbox-only fail-closed (ORCH-117). Тест/worktree-процесс наследует живой боевой Plane-токен (PLANE_HEADERS/PROJECT_ID захвачены на импорте plane_sync); раньше ничто не мешало pytest смутировать боевую доску (инцидент ORCH-114 — «ложный Done»). Теперь leaf src/plane_write_guard.py врезан в 3 примитива записи plane_sync (update_issue_state/add_comment/_set_issue_state_direct) и в тест-процессе (детект pytest-в-процессе) блокирует запись по умолчанию; разрешена только при opt-in ORCH_PLANE_TEST_WRITE_ENABLED=true И целевом проекте ∈ ORCH_PLANE_TEST_SANDBOX_PROJECTS (sandbox-only — боевой проект запрещён даже при opt-in). Боевой и staging рантаймы (uvicorn src.main:app, без pytest в процессе) — гард no-op, запись как прежде. Прод-блок без kill-switch (выключателя-чёрного-хода нет); второй слой — autouse-floor tests/conftest.py::_plane_sandbox_only (по образцу _no_telegram). Детали — CLAUDE.md «Sandbox-only fail-closed изоляция записи в Plane (ORCH-117)», adr-0046.

Страховки:

  • Стадия deploy-staging (порт 8501) — обязательный гейт перед прод-деплоем орка. Прод-деплой недостижим, пока staging-гейт не зелёный (см. STAGING.md, ORCH-35). Гейт условный: реален только для self-hosting (repo=orchestrator), для остальных проектов — no-op.
  • Свежесть staging-образа (ORCH-058): на ребре deploy-staging → deploy (ПОСЛЕ merge-gate, ДО Phase A) QG-под-чек check_staging_image_fresh пересобирает staging-образ из валидированного коммита и пересоздаёт 8501 (Strategy A), а хук перед build-once retag fail-closed сверяет OCI-лейбл revision с EXPECTED_REVISION (Strategy B). Гарантирует: в прод промоутится РОВНО провалидированный артефакт (инцидент LESSONS_ORCH-036 п.4 — тихий промоут устаревшего образа). Сборки/recreate — ТОЛЬКО staging (8501); FAIL → откат на development. Условный: реален только для self-hosting.
  • Гигиена shared deploy-базы (ORCH-112): self-deploy git pull origin main устойчив к грязному рабочему дереву deploy-базы (модифицированные tracked + untracked-остатки failed/cancelled/брошенных задач). Хук --deploy перед pull приводит базу к чистому origin/main (resilient-pull: git fetch + git reset --hard origin/main + git clean -fd), строго сохраняя rollback-снимки .deploy-prev-image-*, deploy-hook.log, gitignored .env/data//*.db (НИКОГДА -x!), sibling .deploy-state-*/.merge-lease-*.json, .git/worktrees/*. Гейт — kill-switch ORCH_CHECKOUT_HYGIENE_ENABLED (дефолт True; off → голый pull 1:1); скоуп ORCH_CHECKOUT_HYGIENE_REPOS (пусто → self-hosting only). Грязь базы детектируется → лог + Telegram-алерт (Phase-C finalizer). Решает инцидент ORCH-111 (грязь ORCH-104 заблокировала git pull). Детально — docs/work-items/ORCH-112/06-adr/ADR-001, сквозной adr-0044.

Правила для агентов при задачах ORCH:

  1. НЕ перезапускать / не ронять прод-контейнер orchestrator в рамках задачи.
  2. Все проверки деплоя — на staging (8501), боевой 8500 не трогать.
  3. Деплой self — только через хук с health-check + авто-rollback (DEPLOY_HOOK.md).

Эксплуатация (быстрые команды)

# статус
docker ps --filter name=orchestrator
curl -s http://localhost:8500/health
curl -s http://localhost:8500/status   # активные задачи
curl -s http://localhost:8500/queue    # очередь

# поднять staging-песочницу
docker compose --profile staging up -d orchestrator-staging
curl -s http://localhost:8501/health

# логи
docker logs --tail 100 orchestrator

RUNBOOK 2026-06-05. Обновлять при изменении топологии/портов/переменных. См. CONTRIBUTING.md §8.