From c3879f2b808082443547e81c7b98c017a05d474b Mon Sep 17 00:00:00 2001 From: claude-bot Date: Fri, 5 Jun 2026 20:44:58 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=125 --- docs/work-items/ORCH-047/01-brd.md | 57 +++++++++++ docs/work-items/ORCH-047/02-trz.md | 68 +++++++++++++ .../ORCH-047/03-acceptance-criteria.md | 68 +++++++++++++ docs/work-items/ORCH-047/04-test-plan.yaml | 97 +++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 docs/work-items/ORCH-047/01-brd.md create mode 100644 docs/work-items/ORCH-047/02-trz.md create mode 100644 docs/work-items/ORCH-047/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-047/04-test-plan.yaml diff --git a/docs/work-items/ORCH-047/01-brd.md b/docs/work-items/ORCH-047/01-brd.md new file mode 100644 index 0000000..080b62d --- /dev/null +++ b/docs/work-items/ORCH-047/01-brd.md @@ -0,0 +1,57 @@ +# BRD — ORCH-047: check_tests_passed должен читать поле `result:` из тест-отчёта + +## 1. Контекст и проблема + +Quality Gate `check_tests_passed` (`src/qg/checks.py`, функция-парсер `_parse_tests_verdict`) гейтит переход `testing → deploy-staging`. Он читает машиночитаемый вердикт из YAML-frontmatter артефакта `13-test-report.md`. + +**Дефект (обнаружен дев-агентом в ходе ORCH-17, подтверждён 05.06.2026):** +парсер читает ТОЛЬКО поля `verdict:` и `status:`. Однако промпт тестер-агента (`.openclaw/agents/tester.md`, строки 51–56 и 78–80) предписывает эмитить машиночитаемое поле **`result: PASS|FAIL`** — и НЕ упоминает ни `verdict:`, ни `status:`. + +В результате тестер, честно следующий своей инструкции (реальный отчёт ORCH-017: `result: PASS`, без `verdict:`/`status:`), упирается в ветку «ни verdict, ни status не заданы» → гейт возвращает `False` с причиной *"No machine-readable verdict/status in test report frontmatter"* → задача откатывается `testing → development`. + +**Последствие:** ЛЮБАЯ задача, где тестер пишет `result: PASS` (то есть строго по своей инструкции), застревает в бесконечной петле `testing ↔ development` до исчерпания `MAX_DEVELOPER_RETRIES`. Именно это крутило ORCH-17. ORCH-016 прошёл раньше лишь потому, что его отчёт избыточно нёс И `verdict:`, И `result:`. + +**Корень:** рассинхрон контракта. Гейт (потребитель) и промпт тестера (производитель) описывают разные имена машиночитаемого поля. + +## 2. Бизнес-цель + +Привести контракт гейта `check_tests_passed` в соответствие с тем, что тестер-агенту реально велено эмитить, чтобы корректные тест-отчёты (`result: PASS`) проходили гейт, а отрицательные (`result: FAIL`) — надёжно откатывали задачу. Устранить ложноотрицательные срабатывания, ломающие конвейер всех проектов. + +## 3. Заинтересованные стороны + +- **Owner / Стрим, Слава** — выявили дефект при разборе ORCH-17 (05.06). +- **Все проекты общего прод-инстанса** (orchestrator self-hosting + enduro-trails) — потребители shared quality-gate. Это SHARED-изменение, влияет на всех. +- **Тестер-агент** — производитель `13-test-report.md`. + +## 4. Объём работ (scope) + +### В объёме +- `_parse_tests_verdict` читает `result:` как первоклассное машиночитаемое поле НАРАВНЕ с `verdict:` и `status:`. +- Семантика приоритетов сохраняется и распространяется на все три поля: + - negative-токен в ЛЮБОМ из трёх (`result`/`verdict`/`status`) → FAIL и авторитетен (перебивает positive в другом поле); + - при отсутствии negative — positive-токен в ЛЮБОМ из трёх → PASS; + - ни одно из трёх полей не задано → FAIL (нет машиночитаемого вердикта); + - заданы, но не распознаны → FAIL. +- Обратная совместимость: отчёты, несущие только `verdict:`/`status:` (стиль enduro-trails ET-001…ET-014, ORCH-016), продолжают работать ровно как раньше. +- **ADR** на изменение семантики shared testing-гейта (правило 2 CLAUDE.md — обязательно для сквозного изменения). +- Обновление документации: `docs/architecture/README.md` (строка про машинные ключи вердикт-парсера), `CHANGELOG.md`. + +### Вне объёма +- Изменение промпта тестера (`.openclaw/agents/tester.md`). Контракт приводится со стороны гейта к тому, что тестеру УЖЕ велено эмитить; промпт не трогаем. +- Изменение других гейтов (`check_reviewer_verdict`, `check_deploy_status`, `check_staging_status`) — у них свои поля (`verdict:`, `deploy_status:`, `staging_status:`), они вне этого дефекта. +- Изменения ORCH-017 (про ссылки) — это отдельный work item. + +## 5. Ограничения и риски + +- **SHARED quality-gate, общий прод-инстанс.** Изменение затрагивает enduro-trails наравне с orchestrator. Регресс недопустим: набор положительных/отрицательных токенов и поведение для старого формата (`verdict:`/`status:`) должны остаться неизменными. +- **Self-hosting.** Орк правит сам себя; деплой проходит через обязательную стадию `deploy-staging` (8501). Прод-контейнер `orchestrator` (8500) не ронять. +- Изменение читает только frontmatter, никогда не прозу (канон гейтов из `docs/architecture/README.md`). +- Парсер не должен бросать исключения ни при каком вводе (битый YAML, пустой файл, frontmatter-не-mapping) → всегда `(False, reason)`. + +## 6. Эталонный код + +Дев-агент уже написал референс-реализацию в ветке `feature/ORCH-017` (`src/qg/checks.py` + `tests/test_qg.py`, 23 теста). Его допустимо использовать как ориентир, но оформить чисто через данный work item с собственным ADR. + +## 7. Критерий успеха + +Тест-отчёт с одним лишь `result: PASS` проходит гейт `check_tests_passed`; с `result: FAIL` — нет. Старый формат (`verdict:`/`status:`) не регрессирует. Все pytest зелёные. ADR заведён. diff --git a/docs/work-items/ORCH-047/02-trz.md b/docs/work-items/ORCH-047/02-trz.md new file mode 100644 index 0000000..5add9be --- /dev/null +++ b/docs/work-items/ORCH-047/02-trz.md @@ -0,0 +1,68 @@ +# ТЗ — ORCH-047: `_parse_tests_verdict` читает `result:` наравне с `verdict:`/`status:` + +## 1. Задействованные модули `src/` + +| Файл | Что меняется | +|------|--------------| +| `src/qg/checks.py` | Функция `_parse_tests_verdict` (стр. ~223–265). Добавить чтение поля `result:` из frontmatter и включить его в проверку токенов наравне с `verdict:`/`status:`. Обновить докстринг функции и `check_tests_passed`. | + +Точка входа `check_tests_passed(repo, work_item_id, branch)` (стр. ~182) и реестр `QG_CHECKS` НЕ меняются (сигнатура и имя гейта те же). + +## 2. Требуемое поведение `_parse_tests_verdict` + +Вход — строковое тело `13-test-report.md`. Выход — `tuple[bool, str]`. + +1. Нет frontmatter (`content` не начинается с `---`) → `(False, "No YAML frontmatter ...")`. +2. Frontmatter некорректен (split по `---` даёт < 3 частей) → `(False, "Malformed YAML frontmatter ...")`. +3. YAML не парсится → `(False, "Invalid YAML frontmatter ...: ")` (никогда не raise). +4. YAML не mapping → `(False, "Malformed YAML frontmatter ... (not a mapping)")`. +5. Прочитать три поля, нормализовать (`str(...).upper().strip()`, защита от `None`): + - `verdict` + - `status` + - **`result` ← НОВОЕ** +6. Если ВСЕ три пусты → `(False, "No machine-readable verdict/status/result in test report frontmatter")`. +7. Собрать объединённую строку полей `fields = f"{verdict} {status} {result}"`. +8. Если в `fields` встречается ЛЮБОЙ negative-токен (`_TESTS_NEGATIVE_TOKENS`) → `(False, "Test verdict: <значение> ()")`. **Negative авторитетен** — проверяется ПЕРВЫМ, перебивает любой positive. +9. Иначе если встречается ЛЮБОЙ positive-токен (`_TESTS_POSITIVE_TOKENS`) → `(True, "Test verdict: <значение> (PASS)")`. +10. Иначе (заданы, но не распознаны) → `(False, "No recognized PASS verdict in frontmatter (...)")`. + +Наборы токенов НЕ изменяются (важно для обратной совместимости с enduro-trails): +```python +_TESTS_NEGATIVE_TOKENS = ("BLOCKED", "FAILED", "FAIL", "REQUEST_CHANGES", "REJECT", "RED") +_TESTS_POSITIVE_TOKENS = ("PASSED", "PASS", "READY-TO-DEPLOY", "READY_TO_DEPLOY", "GREEN", "APPROVED") +``` + +> Примечание для разработчика (порядок токенов): negative-список проверяется раньше positive — это даёт авторитетность отрицания. Внутри positive-набора `"PASSED"` идёт перед `"PASS"` лишь для аккуратного reason-текста; на результат (bool) порядок не влияет, т.к. это подстрочный поиск. + +## 3. Контракт поля (golden source) + +После изменения машиночитаемыми полями testing-гейта считаются **три равноправных**: `result:` (канон промпта тестера), `verdict:`, `status:` (легаси/enduro-trails). Достаточно ЛЮБОГО одного. Это и есть приведение гейта к тому, что тестеру велено эмитить в `.openclaw/agents/tester.md` (`result: PASS|FAIL`). + +## 4. Изменения API + +Нет. HTTP-эндпоинты (`/health`, `/status`, `/queue`, вебхуки) не затрагиваются. Сигнатуры функций гейта не меняются. + +## 5. Изменения схемы БД + +Нет. + +## 6. Требования к новым QG checks + +Новых гейтов нет. Меняется внутренняя логика существующего `check_tests_passed` (через `_parse_tests_verdict`). Реестр `QG_CHECKS` без изменений → снапшот-тест `tests/test_qg_registry_snapshot.py` должен остаться зелёным. + +## 7. Артефакты pipeline (создать/обновить в этом PR) + +- `docs/work-items/ORCH-047/06-adr/ADR-001-*.md` — **обязательно** (правило 2 CLAUDE.md): ADR на изменение семантики SHARED testing-гейта (влияет на все проекты общего инстанса). Заводит архитектор. +- `docs/architecture/README.md` — обновить строку о вердикт-парсере (раздел «Plane Sync», п. про машинные ключи): для testing-гейта перечислить `result:`/`verdict:`/`status:`. +- `CHANGELOG.md` — запись `fix:` про ORCH-047. +- `tests/test_qg.py` — добавить кейсы на `result:` (см. `04-test-plan.yaml`). + +## 8. Нефункциональные требования + +- Парсер не бросает исключений ни на каком вводе. +- Изменение читает только frontmatter, не прозу (канон гейтов). +- Полная обратная совместимость: существующие тесты `TestCheckTestsPassed` остаются зелёными без правок (кроме, возможно, переименования reason-строки в п.6 BRD — текст причины «No machine-readable verdict/status...» обновляется на «...verdict/status/result...», соответствующий ассерт при наличии обновить). + +## 9. Деплой + +Self-hosting: стандартный путь через `deploy-staging` (8501) перед прод-деплоем. Прод-контейнер `orchestrator` (8500) не перезапускать в рамках разработки/тестинга. diff --git a/docs/work-items/ORCH-047/03-acceptance-criteria.md b/docs/work-items/ORCH-047/03-acceptance-criteria.md new file mode 100644 index 0000000..672cb90 --- /dev/null +++ b/docs/work-items/ORCH-047/03-acceptance-criteria.md @@ -0,0 +1,68 @@ +# Критерии приёмки — ORCH-047 + +Каждый критерий имеет однозначное условие PASS/FAIL. + +## AC-01 — `result: PASS` проходит гейт (главный кейс ORCH-17) +- **Дано:** `13-test-report.md` с frontmatter, содержащим только `result: PASS` (без `verdict:`/`status:`). +- **Ожидается:** `check_tests_passed(...)` → `(True, ...)`, в reason присутствует «PASS». +- **PASS:** возвращается True. **FAIL:** возвращается False. + +## AC-02 — `result: FAIL` откатывает задачу +- **Дано:** frontmatter с `result: FAIL` (без `verdict:`/`status:`). +- **Ожидается:** `(False, ...)`, reason содержит токен отрицания (`FAIL`). +- **PASS:** False. **FAIL:** True. + +## AC-03 — Negative авторитетен поверх positive (в т.ч. между полями) +- **Дано:** `result: PASS`, но `verdict: BLOCKED` (или `status: failed`). +- **Ожидается:** `(False, ...)`, reason упоминает negative-токен (`BLOCKED`/`FAILED`). +- **PASS:** False. **FAIL:** True. + +## AC-04 — Positive в любом из трёх полей даёт PASS +- **Дано (каждый подкейс отдельно):** + - только `verdict: PASS`; + - только `status: PASSED`; + - только `result: ready-to-deploy`. +- **Ожидается:** все три → `(True, ...)`. +- **PASS:** все True. **FAIL:** хоть один False. + +## AC-05 — Обратная совместимость (enduro-trails / ORCH-016) +- **Дано:** существующие реальные формы из `TestCheckTestsPassed`: + - `verdict: PASS` + `status: pass`; + - `verdict: PASS — ready-to-deploy`; + - `verdict: ready-to-deploy` + `status: PASSED`; + - `verdict: stage:ready-to-deploy` + `status: pass`; + - `verdict: BLOCKED` + проза «23 passed». +- **Ожидается:** результаты идентичны прежним (PASS-кейсы → True, BLOCKED → False). Старые тесты `TestCheckTestsPassed` зелёные. +- **PASS:** поведение не изменилось. **FAIL:** любой регресс. + +## AC-06 — Ни одно из трёх полей не задано → FAIL +- **Дано:** frontmatter без `result`/`verdict`/`status` (например, только `type:`/`version:`); тело может содержать «Result: PASS» прозой. +- **Ожидается:** `(False, ...)`, причина про отсутствие машиночитаемого вердикта. +- **PASS:** False. **FAIL:** True. + +## AC-07 — Только проза, без frontmatter → FAIL +- **Дано:** отчёт без YAML-frontmatter, в теле «Result: PASS / All tests passed». +- **Ожидается:** `(False, ...)`, причина про отсутствие frontmatter. Прозу не читаем. +- **PASS:** False. **FAIL:** True. + +## AC-08 — Битый YAML → FAIL без исключения +- **Дано:** некорректный YAML во frontmatter. +- **Ожидается:** `(False, ...)` c упоминанием YAML/frontmatter, функция НЕ бросает исключение. +- **PASS:** False и нет raise. **FAIL:** raise или True. + +## AC-09 — Отчёт отсутствует → FAIL +- **Дано:** файла `13-test-report.md` нет. +- **Ожидается:** `(False, "...not found...")`. +- **PASS:** False. **FAIL:** True. + +## AC-10 — Реестр гейтов неизменен +- **Ожидается:** `QG_CHECKS` содержит ровно те же ключи, что и до изменения; `tests/test_qg_registry_snapshot.py` зелёный. +- **PASS:** снапшот совпал. **FAIL:** снапшот изменился. + +## AC-11 — Документация и ADR обновлены (правило 2/6 CLAUDE.md) +- **Ожидается:** заведён `docs/work-items/ORCH-047/06-adr/ADR-001-*.md`; обновлены `docs/architecture/README.md` (вердикт-парсер testing-гейта) и `CHANGELOG.md`. +- **PASS:** все три присутствуют и описывают изменение. **FAIL:** что-либо отсутствует → REQUEST_CHANGES на review. + +## AC-12 — Полный регресс зелёный +- **Ожидается:** `pytest tests/ -q` — все тесты PASS. +- **PASS:** exit code 0. **FAIL:** любой упавший тест. diff --git a/docs/work-items/ORCH-047/04-test-plan.yaml b/docs/work-items/ORCH-047/04-test-plan.yaml new file mode 100644 index 0000000..e442856 --- /dev/null +++ b/docs/work-items/ORCH-047/04-test-plan.yaml @@ -0,0 +1,97 @@ +work_item: ORCH-047 +module_under_test: src/qg/checks.py::_parse_tests_verdict (via check_tests_passed) +test_file: tests/test_qg.py +notes: > + Добавить в класс TestCheckTestsPassed. Шаблон записи отчёта — существующий + хелпер self._write(dir, content). Наборы токенов не меняются; проверяем, что + поле result: теперь равноправно с verdict:/status:, а старые кейсы не регрессируют. + +tests: + - id: TC-01 + type: unit + description: "result: PASS без verdict/status -> гейт PASS (главный кейс ORCH-17, AC-01)" + module: tests/test_qg.py + fixture_frontmatter: "---\ntype: test-report\nresult: PASS\n---\n\n# Test Report\n" + expected: PASS + + - id: TC-02 + type: unit + description: "result: FAIL без verdict/status -> гейт FAIL, reason содержит FAIL (AC-02)" + module: tests/test_qg.py + fixture_frontmatter: "---\nresult: FAIL\n---\n\nbody\n" + expected: FAIL + + - id: TC-03 + type: unit + description: "result: PASS, но verdict: BLOCKED -> negative авторитетен -> FAIL (AC-03)" + module: tests/test_qg.py + fixture_frontmatter: "---\nresult: PASS\nverdict: BLOCKED\n---\n\n23 passed\n" + expected: FAIL + + - id: TC-04 + type: unit + description: "result: PASS, но status: failed -> negative авторитетен -> FAIL (AC-03)" + module: tests/test_qg.py + fixture_frontmatter: "---\nresult: PASS\nstatus: failed\n---\n\nbody\n" + expected: FAIL + + - id: TC-05 + type: unit + description: "result: ready-to-deploy (positive-токен, без слова PASS) -> PASS (AC-04)" + module: tests/test_qg.py + fixture_frontmatter: "---\nresult: ready-to-deploy\n---\n\nbody\n" + expected: PASS + + - id: TC-06 + type: unit + description: "Только verdict: PASS (легаси) -> PASS, без регресса (AC-05)" + module: tests/test_qg.py + fixture_frontmatter: "---\nverdict: PASS\nstatus: pass\n---\n\nbody\n" + expected: PASS + + - id: TC-07 + type: unit + description: "verdict: BLOCKED + проза '23 passed' (ET-013 баг) -> FAIL, без регресса (AC-05)" + module: tests/test_qg.py + fixture_frontmatter: "---\nverdict: BLOCKED\n---\n\nTests: 23 passed, 0 failed.\n" + expected: FAIL + + - id: TC-08 + type: unit + description: "Ни result, ни verdict, ни status; тело с прозой 'Result: PASS' -> FAIL (AC-06)" + module: tests/test_qg.py + fixture_frontmatter: "---\ntype: test-report\nversion: 1\n---\n\nResult: PASS\n" + expected: FAIL + + - id: TC-09 + type: unit + description: "Нет frontmatter, проза 'Result: PASS' -> FAIL (AC-07)" + module: tests/test_qg.py + fixture_frontmatter: "# Test Report\n\nResult: PASS\nAll tests passed.\n" + expected: FAIL + + - id: TC-10 + type: unit + description: "Битый YAML во frontmatter -> FAIL без исключения, reason про YAML/frontmatter (AC-08)" + module: tests/test_qg.py + fixture_frontmatter: "---\nresult: [unclosed\n : : :\n---\n\nbody PASS\n" + expected: FAIL + + - id: TC-11 + type: unit + description: "Файл 13-test-report.md отсутствует -> FAIL, reason 'not found' (AC-09)" + module: tests/test_qg.py + fixture_frontmatter: null + expected: FAIL + + - id: TC-12 + type: unit + description: "Реестр QG_CHECKS не изменился -> снапшот зелёный (AC-10)" + module: tests/test_qg_registry_snapshot.py + expected: PASS + + - id: TC-13 + type: integration + description: "Полный регресс pytest tests/ -q зелёный, существующий TestCheckTestsPassed без правок логики (AC-05, AC-12)" + module: tests/ + expected: PASS