From 0c240198e45f625971c5fcdc757c7bf038e849f5 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 8 Jun 2026 21:46:03 +0300 Subject: [PATCH] architect(ET): auto-commit from architect run_id=394 --- .../06-adr/ADR-001-model-name-validation.md | 145 ++++++++++++++++++ docs/work-items/ORCH-074/10-tech-risks.md | 23 +++ 2 files changed, 168 insertions(+) create mode 100644 docs/work-items/ORCH-074/06-adr/ADR-001-model-name-validation.md create mode 100644 docs/work-items/ORCH-074/10-tech-risks.md diff --git a/docs/work-items/ORCH-074/06-adr/ADR-001-model-name-validation.md b/docs/work-items/ORCH-074/06-adr/ADR-001-model-name-validation.md new file mode 100644 index 0000000..c22df3c --- /dev/null +++ b/docs/work-items/ORCH-074/06-adr/ADR-001-model-name-validation.md @@ -0,0 +1,145 @@ +# ADR-001: Убрать мёртвый frontmatter `model:` + валидация имени модели через формат-чек `claude-*` + +Work Item ID: ORCH-074 +Эпик: ORCH-052 (слой 3), под-задача ORCH-52a +Связан с: ORCH-041 (каркас `resolve_agent_model`/`resolve_agent_effort`), `src/config.py`, `src/agents/launcher.py` + +## Статус +Accepted + +## Контекст + +Каркас выбора модели агентов (ORCH-041) работает корректно: `launcher.resolve_agent_model(agent, project_id)` +резолвит модель по приоритету project-override → `ORCH_AGENT_MODEL_` → `agent_model_default` +→ CLI-дефолт. Все 6 агентов резолвятся в `claude-opus-4-8` (через `agent_model_default`). + +Аудит кода (08.06) выявил два дефекта **данных/валидации** (не дефект механизма): + +- **P1 — лживый/мёртвый `model:` во frontmatter.** Все 6 промптов `.openclaw/agents/*.md` + содержат `model:` (`claude-sonnet-4-6` у analyst/developer/tester/deployer, `claude-opus-4-7` + у architect/reviewer). launcher **не читает** frontmatter `model:` — это мёртвая декларация, + которая лжёт о реально используемой модели и нарушает принцип «документация = golden source». + Мина: если кто-то «починит» launcher читать frontmatter → все агенты молча уедут на устаревшие + модели. +- **P2 — нет валидации имени модели.** В отличие от effort (`VALID_EFFORTS`-гард в + `resolve_agent_effort`), имя модели не валидируется. Опечатка в `agent_model_*` / project-override + → `--model <мусор>` → CLI падает или тихо деградирует. Нарушение принципа never-break. + +Скоуп зафиксирован стейкхолдером (Слава, 08.06): **G1 + G2 + опц. G4. G3 routing НЕ включаем +(все 6 агентов остаются `claude-opus-4-8`). Эффорт не трогаем.** rev.2 BRD подтвердила скоуп +без изменений. Код-факт (TRZ §4): `agent_fallback_model` читается напрямую на `launcher.py:374`, +минуя `resolve_agent_model`. + +Архитектор должен зафиксировать три решения: (а) форма G1, (б) предикат валидации G2, +(в) судьба G4 (fallback) и G3 (routing). + +## Решение + +### Решение 1 (G1): убрать `model:` из frontmatter, НЕ учить launcher его читать + +Из YAML-frontmatter всех 6 файлов `.openclaw/agents/*.md` удаляется **только** строка `model: …`. +Ключи `name`/`description`/`tools` сохраняются; frontmatter остаётся валидным YAML и **описательным**. +config (`agent_model_*` / `agent_model_default`) остаётся **единственным источником правды** о модели. + +Отвергнутая альтернатива — научить launcher читать frontmatter `model:` — отвергнута: она вводит +второй источник правды (frontmatter ⊕ config), усложняет резолв, и моментально активировала бы +устаревшие значения (sonnet-4-6 / opus-4-7) для всех агентов. «Убрать» проще, безопаснее и +устраняет мину раз и навсегда. + +### Решение 2 (G2): предикат валидации — формат-чек `claude-*`, оформленный отдельным helper + +Добавляется **чистый helper** `is_valid_model(name: str) -> bool` рядом с `VALID_EFFORTS` в +`src/agents/launcher.py`. Предикат — **формат-чек**, а не allowlist имён: + +``` +strip → непустая строка → соответствует ^claude-[a-z0-9.-]+$ +``` + +То есть: имя после `strip()` непусто, начинается с `claude-` и состоит только из строчных +букв/цифр/точек/дефисов. Регэксп оформляется модульной константой (напр. `_MODEL_NAME_RE`). + +**Почему формат-чек, а не allowlist `VALID_MODELS`:** +allowlist (по образцу `VALID_EFFORTS`) воссоздаёт ровно ту мину, которую мы убиваем в G1 — статичный +список имён, который **врёт при устаревании**. Когда Anthropic выпустит `claude-opus-4-9`, оператор, +корректно прописавший новую модель, получит её молчаливый дроп на устаревший default (never-break +сработает против пользователя). Это хуже, чем пропустить структурно-корректное, но опечатанное имя: +финальный авторитет о существовании модели — сам Claude CLI, а не наш код. Формат-чек +**forward-compatible** (новые версии проходят без правки кода) и ловит реальные классы отказов: +чужой провайдер (`gpt-4`), пустая строка/пробелы, мусор с недопустимыми символами, неверный префикс +(`claud-opus-typo`). Признанное ограничение: формат-чек НЕ ловит опечатку, которая всё ещё выглядит +как валидное claude-имя (`claude-opus-typo`) — такие отсекает CLI на запуске (контракт never-break ++ exit-code обработка в `_monitor_agent` это покрывают). Задача валидатора — не быть реестром моделей, +а не дать **структурному мусору** уехать в `--model`. + +**Применение (контракт never-break):** +- В `resolve_agent_model`: резолвенное имя валидируется **перед возвратом**. Невалидное → + `logger.warning(...)` + откат на следующий валидный уровень. Реализация: helper применяется внутри + каскада приоритетов так, что невалидный уровень пропускается (project-override невалиден → пробуем + env → default), а если итог всё равно невалиден → возврат `""` (без флага `--model`, CLI-дефолт). + **Никогда** не возвращается мусор и **никогда** не бросается исключение. +- Контракт уровней резолва ORCH-041 сохраняется: валидация добавляется **поверх**, порядок приоритетов + и сигнатуры не меняются. Все ныне используемые валидные имена (`claude-opus-4-8`, валидный enduro + per-project override) проходят без изменения поведения. +- Поведенческая аналогия с `resolve_agent_effort` (`VALID_EFFORTS`): валидный → как есть, невалидный → + лог + дроп. Разница только в форме предиката (формат-чек vs множество) по причинам выше. + +### Решение 3 (G4): fallback НЕ включаем; но валидатор применяем к точке чтения fallback + +`agent_fallback_model` остаётся `""` (флаг `--fallback-model` не прокидывается). **AC-5 помечается +N/A.** Обоснование отказа: +- G3 выключен ради **детерминизма**: все агенты на `claude-opus-4-8`. Fallback вернул бы скрытую + вариативность модели под нагрузкой (агент молча отработал бы на другой модели) — это противоречит + духу зафиксированного скоупа. +- Нет наблюдаемой проблемы доступности, мотивирующей fallback. Принцип минимального изменения. +- Self-hosting: новое рантайм-поведение под нагрузкой трудно наблюдать; не вводим без нужды. + +**При этом** helper `is_valid_model` применяется ТАКЖЕ на месте чтения fallback (`launcher.py:374`, +`fb = settings.agent_fallback_model`) — **независимо** от того, что значение сейчас пустое. Причина — +код-факт TRZ §4: fallback читается напрямую, мимо `resolve_agent_model`, поэтому валидация только +внутри резолва его НЕ покрывает. Защитный гард на месте чтения навсегда закрывает дыру never-break: +если кто-то позже задаст `ORCH_AGENT_FALLBACK_MODEL` с опечаткой, мусор будет залогирован и +сброшен (`fb_flag = ""`), а не уедет в `--fallback-model`. Для текущего пустого значения регрессии нет: +`is_valid_model("") == False` → `fb_flag = ""` — то же поведение, что и сейчас (`if fb`). Это делает +**TC-11** проверяемым (мусорный fallback дропается) при выключенном G4. + +### Решение 4 (G3): routing НЕ включаем + +Подтверждается отказ от model-routing как осознанное решение стейкхолдера (Слава, 08.06). Все 6 +агентов резолвятся в `claude-opus-4-8`. **AC-4 = N/A.** + +## Размещение и форма (для разработчика) + +- `is_valid_model(name)` + `_MODEL_NAME_RE` — в `src/agents/launcher.py` рядом с `VALID_EFFORTS` + (один валидатор, два места вызова: резолв модели и чтение fallback — оба в этом модуле, без + кросс-модульного импорта). +- Префикс `claude-` хардкодится в launcher: оркестратор привязан к Claude CLI (`CLAUDE_BIN`), + конфигурировать предикат не нужно (не over-engineering). Каноничная версия модели по-прежнему + живёт ТОЛЬКО в `config.py::agent_model_default` — в launcher версия не хардкодится. +- frontmatter: удалить только `model:`-строку; не вносить генератор, возвращающий её обратно. + +## Последствия + +**Плюсы:** +- frontmatter перестаёт лгать; config — единственный источник правды о модели (golden source цел). +- Опечатка/чужой провайдер/мусор в имени модели больше не роняет и не деградирует запуск агента + (never-break соблюдён в обеих точках: резолв и fallback). +- Forward-compatible: будущие модели Claude не требуют правки кода (в отличие от allowlist). +- Минимальное изменение: механизм ORCH-041, API, схема БД, структура CLI-команды не меняются. + +**Минусы / ограничения:** +- Формат-чек пропускает структурно-валидную опечатку вида `claude-opus-typo` (отсекается CLI на + запуске + never-break обработкой exit-code). Принятый компромисс ради forward-compat. +- Префикс `claude-` зашит — при гипотетической смене CLI-провайдера потребуется правка (приемлемо: + оркестратор Claude-специфичен по дизайну). + +**Не затрагивается:** +- API (HTTP) — нет. Схема БД — нет миграций. Стадии/QG — без изменений (это runtime-гард в launcher, + не quality-gate). Топология/инфра — без изменений (07/08 артефакты не требуются). +- Эффорт (`agent_effort_*`) и `VALID_EFFORTS`-гард — не трогаются (регрессия покрыта TC-10). +- enduro per-project override — валидные имена проходят без изменения поведения (AC-8 / TC-08). + +## Соответствие принципам + +Всё в Docker / один сервер — да. Минимум зависимостей — новых нет. Без ORM/очередей/облака — да. +Self-hosting: изменение применяется к БУДУЩИМ запускам агентов, прод-контейнер не перезапускается +в рамках задачи; прод-деплой орка — только через staging-гейт (8501) и Plane-статус «Confirm Deploy». diff --git a/docs/work-items/ORCH-074/10-tech-risks.md b/docs/work-items/ORCH-074/10-tech-risks.md new file mode 100644 index 0000000..584d27f --- /dev/null +++ b/docs/work-items/ORCH-074/10-tech-risks.md @@ -0,0 +1,23 @@ +# Технические риски — ORCH-074 + +Work Item ID: ORCH-074 +Связан с: ADR-001 (`06-adr/ADR-001-model-name-validation.md`). + +| ID | Риск | Вероятность | Влияние | Митигация | +|----|------|-------------|---------|-----------| +| R-1 | **Валидация роняет запуск агента** (исключение вместо graceful-деградации) — нарушение never-break, встал бы конвейер всех проектов. | Низкая | Высокое | Helper `is_valid_model` — чистый предикат без исключений; невалидное → `logger.warning` + откат на default/`""`. Покрыто TC-03..TC-05, TC-10. | +| R-2 | **Fallback обходит валидацию** (код-факт: `launcher.py:374` читает `agent_fallback_model` напрямую, мимо `resolve_agent_model`). | Средняя (если позже зададут fallback) | Среднее | ADR-001 решение 3: один helper применяется ТАКЖЕ на месте чтения fallback. Мусорный fallback дропается с warning. Покрыто TC-11. | +| R-3 | **Регрессия enduro per-project override** — валидация ломает корректный не-self override (общий инстанс/БД/очередь). | Низкая | Высокое | Валидные claude-имена проходят формат-чек без изменения поведения; механизм приоритетов ORCH-041 не меняется. Покрыто TC-08. | +| R-4 | **Формат-чек пропускает структурную опечатку** вида `claude-opus-typo` (валидный префикс, несуществующая модель). | Средняя | Низкое | Принятый компромисс (ADR-001): финальный авторитет — CLI; never-break + обработка exit-code в `_monitor_agent` покрывают отказ запуска. Allowlist отвергнут как воссоздающий мину устаревания (G1). | +| R-5 | **frontmatter-генератор возвращает `model:` обратно** → мина P1 оживает. | Низкая | Среднее | Проверить отсутствие автогенератора, возвращающего `model:`; frontmatter остаётся описательным. Покрыто TC-01/TC-02 (CI-гард на отсутствие `^model:`). | +| R-6 | **Хардкод версии модели в launcher** при добавлении валидации. | Низкая | Среднее | Префикс `claude-` зашит осознанно (CLI-специфика); каноничная ВЕРСИЯ остаётся только в `config.py::agent_model_default`. Регэксп версию не фиксирует. | +| R-7 | **Self-hosting деплой** — рестарт прод-контейнера встанет конвейер всех проектов (enduro). | — | Высокое | Изменение применяется к будущим запускам; прод-деплой только через staging-гейт (8501) и Plane-статус «Confirm Deploy». Без немедленного рестарта прода. | + +## Инварианты (должны держаться после изменения) + +1. **never-break**: невалидная модель/эффорт/fallback НЕ роняет запуск агента — деградация на + default/CLI-дефолт + лог. +2. **Один источник правды о модели**: config (`agent_model_*`); frontmatter — описательный. +3. **Обратная совместимость ORCH-041**: все валидные имена (`claude-opus-4-8`, enduro override) + резолвятся без изменения поведения; порядок приоритетов и сигнатуры не меняются. +4. **Детерминизм**: все 6 агентов = `claude-opus-4-8` (G3/routing выключен, G4/fallback выключен).