# DEV TASK: orchestrator — check_tests_passed пропускает BLOCKED/FAILED отчёты (substring "PASS") Репо: `slin@82.22.50.71:/home/slin/repos/orchestrator` (пароль `motoZ@yaz2010`). Push в main запрещён → **только PR в Gitea**. Gitea токен: `docker exec orchestrator printenv ORCH_GITEA_TOKEN`. Одна ветка `fix/tests-machine-verdict` от актуального main, **один PR**. НЕ мержить сам. Baseline pytest на проде: **277 passed + 9 failed** (9 = off-limits HMAC/401, НЕ чинить). --- ## КОНТЕКСТ / БАГ (подтверждён на ET-013, 2026-06-04) Стадия `testing → deploy` гейтится функцией `check_tests_passed` (`src/qg/checks.py:139`). Сейчас она делает **наивный substring-поиск**: ```python if "PASS" in content or "All tests passed" in content: return True, "Test report indicates PASS" ``` `content` — это весь `docs/work-items//13-test-report.md`. ### Что пошло не так на ET-013 Тестер ЯВНО выставил машинный вердикт в YAML-frontmatter: ```yaml status: blocked verdict: BLOCKED ``` (причина: провален P1-критерий AC-19 — на проде нет hillshade z9-тайлов). НО в теле отчёта полно слова `PASS` («23 passed», «✅ PASS (часть AC-18)», «All checks passed»). Substring `"PASS"` нашёлся → гейт вернул True → задача с вердиктом **BLOCKED** уехала в deploy и доползла до **Done**. Недоделанная фича попала на прод. ### Корень `check_tests_passed` не читает машинный вердикт тестера, а ищет подстроку. Это зеркальная проблема той, что уже ПОЧИНЕНА в `check_reviewer_verdict` (S-5, читает `verdict:` из frontmatter) и `check_deploy_status` (читает `deploy_status:`). `check_tests_passed` забыли привести к тому же контракту. --- ## ШАГ 0 — ПОДТВЕРДИТЬ ПО КОДУ 1. Открой `check_tests_passed` (checks.py:139) и `check_reviewer_verdict` (checks.py:211) — последняя уже читает frontmatter `verdict:` правильно, используй её как ЭТАЛОН структуры. 2. Посмотри, какие значения вердикта реально пишет тестер. На ET-013: `verdict: BLOCKED`, `status: blocked`. Проверь ещё 1-2 прошлых отчёта (`git show origin/main:docs/work-items/ ET-012/13-test-report.md`, ET-011) — какой вердикт у УСПЕШНЫХ (вероятно `PASS`/`PASSED`/ `GREEN`/`APPROVED` — зафиксируй ТОЧНЫЕ значения, чтобы не сломать прошедшие задачи). 3. Зафиксируй verbatim в отчёте: какие значения = успех, какие = блок. ## ЧТО СДЕЛАТЬ Привести `check_tests_passed` к машинному контракту, как `check_reviewer_verdict`: - Читать ТОЛЬКО `verdict:` (и при необходимости `status:`) из YAML-frontmatter `13-test-report.md`. НЕ искать подстроку в теле. - Успех (return True) ТОЛЬКО при явном положительном вердикте. По образцам определи множество положительных значений — как минимум `PASS`/`PASSED` (нормализуй `.upper().strip()`). Если у успешных прошлых отчётов вердикт иной (напр. `GREEN`, `APPROVED`) — включи и его, но ОБОСНУЙ по реальным образцам, не выдумывай. - Любой иной вердикт (`BLOCKED`, `FAILED`, `REQUEST_CHANGES`, пустой, нет frontmatter) → return False с понятной причиной (напр. `Test verdict: BLOCKED`). - Файла нет → False «Test report not found» (как сейчас). - Невалидный YAML → False с причиной (как в check_reviewer_verdict). - Никогда не падать. ### ВАЖНО — обратная совместимость - Если у УЖЕ ПРОШЕДШИХ успешных задач (ET-011, ET-012, ET-014) в отчётах НЕТ frontmatter- вердикта (старый формат) — реши аккуратно: либо считать отсутствие вердикта блоком (строго, но может сломать тесты), либо оставить узкий fallback. ПРЕДПОЧТИТЕЛЬНО строгий машинный контракт; если это ломает существующие pytest-фикстуры — почини фикстуры под новый контракт (добавь им `verdict: PASS`), а не ослабляй проверку. Опиши решение в отчёте. ### НЕ ТРОГАТЬ - `check_reviewer_verdict`, `check_deploy_status` (эталоны, уже верные), merge-gate gitea.py, PLANE_STATES, set_issue_done, launcher deployer-guard, HMAC, queue, usage_comment/Plane- комменты, cost_usd, формат трекера/Итого, миграции, stages.py mapping. Только `check_tests_passed` + его тесты (+ при необходимости фикстуры тест-отчётов). ## ТЕСТЫ (обязательно) - `verdict: PASS` (или подтверждённое успешное значение) в frontmatter → True. - `verdict: BLOCKED` + тело со словом «PASS» (кейс ET-013!) → **False** (главный кейс). - `verdict: FAILED` → False. - Тело содержит «23 passed», но frontmatter `verdict: BLOCKED` → False (substring больше не обманывает). - Нет frontmatter / нет вердикта → False. - Невалидный YAML → False, не исключение. - Файла нет → False «not found». - Полный pytest зелёный кроме тех же 9 off-limits. ## СДАЧА - Ветка `fix/tests-machine-verdict`, один PR. НЕ мержить — на ревью Стрим. - Отчёт: `tasks/orchestrator/reports/dev-2026-06-04-tests-verdict-fix.md` — commit, PR-номер, вывод pytest, какие значения вердикта = успех (по реальным образцам ET-011/012/014), до/после поведения на кейсе ET-013, как решена обратная совместимость. - Перенос на прод: scp нет → `base64 | ssh 'base64 -d'` или docker cp. - Сообщить: PR-номер, pytest, краткое описание.