fix(staging): tolerate sandbox-infra-only FAILs (C9a/C9b) in deploy-staging verdict
Some checks failed
CI / test (push) Failing after 39s
CI / test (pull_request) Failing after 35s

The self-hosting orchestrator looped on deploy-staging -> development because
scripts/staging_check.py exited 1 on ANY failed check, so two infra-only checks
(C9a sandbox branch / C9b analyst-job — caused by SANDBOX bot accounts not being
members of the sandbox Plane project, NOT a pipeline regress) forced
staging_status: FAILED -> rollback -> loop, burning developer retries and tokens.

Direction (б) per ADR-001: classify staging checks as REAL (all pipeline checks,
fail-closed) vs SANDBOX_INFRA (narrow allowlist {C9a, C9b}, waivable). New leaf
module src/staging_verdict.py (stdlib-only, never-raise): classify_check +
compute_staging_verdict fold per-check results into a tolerant-but-fail-closed
verdict — any REAL failure -> FAILED/exit1 (safety net holds under any flag);
only C9a/C9b failed & tolerant -> SUCCESS/exit0 with waived list; only infra &
strict -> FAILED/exit1; any internal error -> FAILED/exit1 (never a false green).

staging_check.py now auto-classifies each check (public 3-tuple _items shape kept
as an ORCH-048 b6 regression guard), exposes categorized_items(), prints
INFRA-WAIVED/VERDICT lines, and exits via the verdict; new --strict flag forces
legacy strictness per-run. Kill-switch ORCH_STAGING_INFRA_TOLERANCE_ENABLED
(default true) restores legacy strict mode globally. launcher gains
action_stage_no_changes_note so "no changes to commit" on action stages is logged
as expected, not treated as under-delivery.

Contracts unchanged: STAGE_TRANSITIONS, QG_CHECKS registry, staging_status:/
deploy_status: frontmatter, hook exit-code (0/1/2), check_staging_status; no DB
migration. Docs: README, STAGING_CHECK.md, deployer.md, .env.example, CHANGELOG.

Refs: ORCH-061

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 12:39:00 +00:00
parent 1d1208c136
commit 9070489968
15 changed files with 831 additions and 7 deletions

View File

@@ -211,4 +211,4 @@ never-raise на единицу работы; тишина при синхрон
Схема БД, потоки данных, resilience-слой, детали Dockerfile — [internals.md](internals.md).
---
*Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой `deploy`, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, `docs/work-items/ORCH-060/06-adr/ADR-001`) — реализовано в ветке feature/ORCH-060 (Guard 1 `developer_retry_count>=MAX_DEVELOPER_RETRIES` + Guard 2 `plane_sync.fetch_issue_state` Blocked/Needs-Input, флаг `ORCH_RECONCILE_SKIP_BLOCKED_ENABLED`); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, `docs/work-items/ORCH-061/06-adr/ADR-001`) — design, ветка feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled).*
*Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой `deploy`, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, `docs/work-items/ORCH-060/06-adr/ADR-001`) — реализовано в ветке feature/ORCH-060 (Guard 1 `developer_retry_count>=MAX_DEVELOPER_RETRIES` + Guard 2 `plane_sync.fetch_issue_state` Blocked/Needs-Input, флаг `ORCH_RECONCILE_SKIP_BLOCKED_ENABLED`); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, `docs/work-items/ORCH-061/06-adr/ADR-001`) — реализовано в ветке feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled).*

View File

@@ -12,7 +12,9 @@
| B | ACCESS | Plane sandbox (R), Gitea sandbox (R+push), реестр проектов |
| C | E2E | Создать задачу → триггер конвейера → ветка + коммент → cleanup |
Exit code: **0** = все PASS, **non-zero** = есть FAIL.
Exit code: **0** = advance (все REAL-проверки PASS), **1** = rollback (есть REAL-FAIL).
С ORCH-061 exit 0 может включать *waived* sandbox-infra FAIL (C9a/C9b) — см.
[«Толерантность к sandbox-infra (ORCH-061)»](#толерантность-к-sandbox-infra-orch-061).
---
@@ -85,6 +87,56 @@ B6 «Registry: sandbox present, prod ET/ORCH absent» подтверждает
---
## Толерантность к sandbox-infra (ORCH-061)
**Проблема.** Self-hosting `orchestrator` зацикливался на `deploy-staging → development`:
прежде скрипт давал exit 1 при **любом** FAIL, поэтому две чисто инфраструктурные
проверки — **C9a** (ветка не появилась в `orchestrator-sandbox`) и **C9b** (job
аналитика не встал в очередь staging) — приводили к `staging_status: FAILED`
откат → цикл. Корень: SANDBOX-бот-аккаунты не состоят в sandbox-проекте Plane,
поэтому шаги 6+ конвейера в песочнице недостижимы. Это **не** регресс конвейера.
**Решение.** Проверки классифицируются на две категории (`src/staging_verdict.py`):
| Категория | Что входит | Поведение |
|-----------|-----------|-----------|
| `REAL` | все проверки конвейера (A*, B*, C7, C8) | **fail-closed** — любой FAIL = rollback |
| `SANDBOX_INFRA` | строго allowlist `{C9a, C9b}` | **waivable** — FAIL терпится, если все REAL зелёные |
Вердикт сворачивается в `compute_staging_verdict(items, infra_tolerant)`:
- любой REAL-FAIL → `FAILED` / exit 1 (страховка сохраняется при ЛЮБОМ значении флага);
- упали **только** C9a/C9b и толерантность включена → `SUCCESS` / exit 0,
упавшие метки попадают в `waived` (наблюдаемость, печатается строкой `INFRA-WAIVED:`);
- упали только C9a/C9b, толерантность выключена → `FAILED` / exit 1 (legacy-строгий);
- любая внутренняя ошибка вердикта → `FAILED` / exit 1 (никогда не ложный green).
Blast-radius waiver-а ровно две allowlist-метки; всё неизвестное классифицируется
как `REAL` (fail-closed).
### Kill-switch и `--strict`
| Управление | Эффект |
|-----------|--------|
| env `ORCH_STAGING_INFRA_TOLERANCE_ENABLED` (default `true`) | глобальный флаг; `false` → строгий режим (1:1 до ORCH-061) |
| CLI `--strict` | форсит строгий режим для одного запуска, игнорируя env |
Флаг живёт в `.env.staging` (staging-инстанс). `--strict` имеет приоритет над env.
### Что печатает скрипт
В конце прогона `summary()` показывает разбивку REAL/SANDBOX_INFRA, затем:
```
INFRA-WAIVED: C9a Branch appears in orchestrator-sandbox; C9b Analyst job enqueued ...
VERDICT: SUCCESS (infra-waived): ['C9a …', 'C9b …'] are known sandbox-infra checks; all real checks green
```
Контракт `staging_status: SUCCESS|FAILED` во frontmatter **не меняется**
толерантность применяется в скрипте ДО записи артефакта деплоером.
---
## Режимы (`--mode`)
| Режим | Описание | Скорость |