From 59603f6e92c71c44850db578b0501296fc5ce8f4 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 8 Jun 2026 05:12:49 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=350 --- docs/work-items/ORCH-069/01-brd.md | 76 ++++++++++++ docs/work-items/ORCH-069/02-trz.md | 95 +++++++++++++++ .../ORCH-069/03-acceptance-criteria.md | 56 +++++++++ docs/work-items/ORCH-069/04-test-plan.yaml | 112 ++++++++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 docs/work-items/ORCH-069/01-brd.md create mode 100644 docs/work-items/ORCH-069/02-trz.md create mode 100644 docs/work-items/ORCH-069/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-069/04-test-plan.yaml diff --git a/docs/work-items/ORCH-069/01-brd.md b/docs/work-items/ORCH-069/01-brd.md new file mode 100644 index 0000000..99e1a38 --- /dev/null +++ b/docs/work-items/ORCH-069/01-brd.md @@ -0,0 +1,76 @@ +# BRD — ORCH-069: QG-0 title-лимит → параметр ORCH_QG0_TITLE_MAX (дефолт 200) + +Work Item ID: ORCH-069 +Тип: Enhancement (QoL / конфигурируемость) +Источник: Слава, 2026-06-08 +Связано с: QG-0 (gate входа конвейера, `_qg0_errors`) + +## 1. Проблема (As-Is) +QG-0 — первый quality gate конвейера. Он валидирует заголовок и описание задачи +до старта pipeline (`start_pipeline`) и в soft-режиме на `work_item.created`. + +Верхний лимит длины заголовка задачи **захардкожен** в +`src/webhooks/plane.py:362`: + +```python +if len(name) > 80: + errors.append("Title слишком длинный (максимум 80 символов)") +``` + +Лимит 80 — «гигиенический», а не структурный. Проверено, что **ниже по течению +ничего от значения 80 не зависит**: +- slug ветки режется независимо: `re.sub(...)[:30]` (`src/webhooks/plane.py:478`); +- БД `tasks.title TEXT` — без ограничения длины; +- Telegram-карточка использует `html.escape(title)` без обрезки; +- Plane хранит `name` самостоятельно. + +Следствие: вполне валидные осмысленные заголовки длиной 81–200 символов +отклоняются на входе конвейера без бизнес-причины. + +## 2. Цель (To-Be) +Вынести верхний лимит длины заголовка QG-0 в конфигурируемый параметр со +значением по умолчанию **200** (вместо текущего хардкода 80). Расширить лимит +безопасно, сохранив возможность регулировать его через окружение, как и +остальные `ORCH_*` настройки. + +## 3. Бизнес-ценность +- Меньше ложных отклонений валидных задач на входе конвейера (QoL для постановщика). +- Лимит становится операционно настраиваемым без правки кода и редеплоя + (изменение env-переменной). +- Изменение чисто аддитивное и обратносовместимое: дефолт 200 > прежних 80, поэтому + все заголовки, проходившие раньше, проходят и теперь. + +## 4. Объём (Scope) +### В объёме +- Новый параметр Settings `qg0_title_max` (env `ORCH_QG0_TITLE_MAX`, дефолт 200). +- Замена хардкода `> 80` на `> settings.qg0_title_max` в `_qg0_errors`. +- Динамический текст ошибки с подстановкой актуального лимита. +- Graceful-поведение при невалидном/пустом значении env → дефолт 200, без падения процесса. +- Документация: `.env.example`, `.env.staging.example`, `CHANGELOG.md`, + при необходимости README-таблица конфигов / `CLAUDE.md`. +- Юнит-тесты на `_qg0_errors` с разными лимитами. + +### Вне объёма (Out of scope) +- Slug-логика `[:30]` (`src/webhooks/plane.py:478`) — самодостаточна, не трогать. +- Нижний лимит заголовка (`< 5`) и лимит description (`< 20`) — оставить как есть. +- Схема БД, реестры `STAGE_TRANSITIONS` / `QG_CHECKS`, контракты `handle_*`. +- Soft-QG-0 на `work_item.created` (там только warning) — логика валидации общая + (`_qg0_errors`), отдельных изменений не требует и не вносит. + +## 5. Заинтересованные стороны +- Owner / постановщик задач (Слава) — снижение ложных отклонений. +- Агенты конвейера — поведение QG-0 при старте pipeline. + +## 6. Ограничения и риски (self-hosting) +- Правка касается работающего в проде инструмента (self-hosting). Прод-контейнер + `orchestrator` в рамках задачи **не рестартить**; обязательна страховка + `deploy-staging` (8501). +- Риск минимален: изменение обратносовместимо, изолировано в одной функции и одном + новом параметре config. + +## 7. Допущения +- Механизм чтения env — стандартный `pydantic_settings.BaseSettings` с + `env_prefix = "ORCH_"`, как у остальных параметров. +- «Невалидное/пустое значение → дефолт 200» — требование graceful-деградации: + процесс не должен падать на старте из-за мусора в `ORCH_QG0_TITLE_MAX` + (нюанс реализации pydantic-валидации передаётся архитектору, см. 02-trz §5). diff --git a/docs/work-items/ORCH-069/02-trz.md b/docs/work-items/ORCH-069/02-trz.md new file mode 100644 index 0000000..7ffb071 --- /dev/null +++ b/docs/work-items/ORCH-069/02-trz.md @@ -0,0 +1,95 @@ +# ТЗ — ORCH-069: QG-0 title-лимит → параметр ORCH_QG0_TITLE_MAX (дефолт 200) + +Work Item ID: ORCH-069 + +## 1. Задействованные модули `src/` +| Файл | Текущее состояние | Требуемое изменение | +|------|-------------------|---------------------| +| `src/config.py` | `Settings(BaseSettings)`, `env_prefix = "ORCH_"` (строки 4, 347-349) | Добавить поле `qg0_title_max: int = 200` с комментарием-описанием. | +| `src/webhooks/plane.py` | `_qg0_errors` (строки 357-367), хардкод `if len(name) > 80:` (строка 362); `from ..config import settings` уже импортирован (строка 11) | Заменить хардкод `> 80` на `> settings.qg0_title_max`; текст ошибки — динамический с подстановкой лимита. | + +Других модулей изменение не затрагивает. + +## 2. Изменение config.py +Добавить в класс `Settings` новое поле (рядом с другими `ORCH_*` группами, +рекомендуется отдельный блок с комментарием): + +```python +# ORCH-069: QG-0 upper title-length limit (entry gate _qg0_errors). The 80-char +# cap was a hygiene limit, not structural (slug is cut to [:30] independently, +# DB title TEXT is unbounded). Configurable via env ORCH_QG0_TITLE_MAX; default +# 200 (was hardcoded 80). Invalid/empty value -> default (graceful, no crash). +qg0_title_max: int = 200 +``` + +- Env-переменная: `ORCH_QG0_TITLE_MAX` (автоматически из `env_prefix = "ORCH_"`). +- Тип `int`, дефолт `200`. + +## 3. Изменение `_qg0_errors` (src/webhooks/plane.py) +Текущий блок (строки 362-363): +```python +if len(name) > 80: + errors.append("Title слишком длинный (максимум 80 символов)") +``` + +Требуемое: +```python +if len(name) > settings.qg0_title_max: + errors.append( + f"Title слишком длинный (максимум {settings.qg0_title_max} символов)" + ) +``` + +Требования: +- Лимит берётся из `settings.qg0_title_max` (динамически, на каждый вызов — чтобы + тесты могли подменять значение через мок/патч settings). +- Текст ошибки содержит актуальное число лимита (для AC-1/AC-2: текст упоминает + 200 / 120 соответственно). +- Нижний лимит заголовка `< 5` (строка 360-361) и проверка description `< 20` + (строка 364-365) — **не трогать**. +- Сигнатура `_qg0_errors(name, description) -> list` не меняется. + +## 4. Поведение границы (точная семантика) +- Условие fail — строго `len(name) > limit`. То есть `len == limit` → PASS, + `len == limit + 1` → FAIL. +- При дефолте: 200 символов → PASS, 201 → FAIL. +- При `ORCH_QG0_TITLE_MAX=120`: 120 → PASS, 121 → FAIL. + +## 5. Graceful-обработка невалидного значения (требование AC-3) +Требование: невалидное/отсутствующее `ORCH_QG0_TITLE_MAX` → используется дефолт 200, +процесс не падает. + +Нюанс для архитектора/разработчика: `pydantic_settings` по умолчанию при +непарсящемся в `int` значении env (например `ORCH_QG0_TITLE_MAX=abc` или пустая +строка) выбрасывает `ValidationError` на инстанцировании `Settings()` — +т.е. падение на старте процесса. Это противоречит требованию graceful. +Реализация должна обеспечить, что: +- отсутствие переменной → дефолт 200 (это стандартное поведение, ОК «из коробки»); +- пустая строка / нечисловое значение → дефолт 200 без исключения. + +Способ (на усмотрение архитектора, без предписания со стороны аналитика) — +например field-validator с `mode="before"`, который при невалидном входе +возвращает дефолт. Конкретный механизм фиксируется в ADR на стадии architecture. + +## 6. Изменения API +Нет. Эндпоинты не меняются. + +## 7. Изменения схемы БД +Нет. `tasks.title TEXT` остаётся без ограничения длины. + +## 8. Новые QG checks +Нет. Реестр `QG_CHECKS` и `STAGE_TRANSITIONS` не меняются. QG-0 — не зарегистрированный +stage-gate, а inline-валидация входа (`_qg0_errors`), её контракт сохраняется. + +## 9. Артефакты pipeline, которые должны быть созданы/обновлены +- `.env.example` — добавить `ORCH_QG0_TITLE_MAX=200` с комментарием. +- `.env.staging.example` — добавить `ORCH_QG0_TITLE_MAX` (дефолт/комментарий). +- `CHANGELOG.md` — запись об ORCH-069. +- README-таблица конфигов / `CLAUDE.md` — обновить при наличии релевантной таблицы + параметров (по требованию reviewer; документация = golden source). +- Юнит-тесты (`tests/`) — см. `04-test-plan.yaml`. + +## 10. Обратная совместимость +- Дефолт 200 > прежних 80 → все ранее проходившие заголовки проходят и теперь. +- Поведение при не заданном env идентично «как было», но с порогом 200 вместо 80. +- Изменение чисто аддитивное; откатов/миграций не требует. diff --git a/docs/work-items/ORCH-069/03-acceptance-criteria.md b/docs/work-items/ORCH-069/03-acceptance-criteria.md new file mode 100644 index 0000000..a76e91f --- /dev/null +++ b/docs/work-items/ORCH-069/03-acceptance-criteria.md @@ -0,0 +1,56 @@ +# Критерии приёмки — ORCH-069 + +Work Item ID: ORCH-069 + +Формат: каждый критерий имеет чёткое условие PASS/FAIL. + +## AC-1 — Дефолтный лимит 200, граница на 201 +**Дано:** env `ORCH_QG0_TITLE_MAX` не задан (используется дефолт 200), description валиден (≥ 20 символов). +**Тогда:** +- заголовок длиной 200 символов → `_qg0_errors` НЕ содержит ошибки про длину title (PASS); +- заголовок длиной 201 символ → `_qg0_errors` содержит ошибку про длину title, и текст ошибки упоминает «200». +**FAIL если:** на 200 появляется ошибка длины, либо на 201 ошибки нет, либо текст не упоминает 200. + +## AC-2 — Настраиваемый лимит 120, граница на 121 +**Дано:** `ORCH_QG0_TITLE_MAX=120` (через мок/патч settings в тесте), description валиден. +**Тогда:** +- заголовок 120 символов → нет ошибки длины title (PASS); +- заголовок 121 символ → есть ошибка длины title, текст упоминает «120». +**FAIL если:** граница срабатывает не на 121, либо текст ошибки упоминает не 120. + +## AC-3 — Graceful при невалидном/пустом значении +**Дано:** `ORCH_QG0_TITLE_MAX` пустой (`""`) или нечисловой (`"abc"`). +**Тогда:** +- инстанцирование `Settings()` / импорт приложения НЕ выбрасывает исключение (процесс не падает); +- эффективное значение лимита = дефолт 200 (поведение AC-1 сохраняется). +**FAIL если:** старт процесса падает с `ValidationError`, либо лимит != 200. + +## AC-4 — Нижние лимиты не сломаны +**Дано:** любое валидное значение `ORCH_QG0_TITLE_MAX`. +**Тогда:** +- заголовок длиной < 5 символов → `_qg0_errors` содержит ошибку «Title слишком короткий»; +- description длиной < 20 символов → `_qg0_errors` содержит ошибку «Description слишком короткий». +**FAIL если:** нижний лимит title или лимит description перестал срабатывать. + +## AC-5 — Юнит-тесты зелёные +**Дано:** реализованные юнит-тесты на `_qg0_errors` с разными значениями лимита (мок settings). +**Тогда:** `pytest tests/ -q` проходит полностью (зелёный), включая новые тесты ORCH-069 и существующий набор. +**FAIL если:** хотя бы один тест падает. + +## AC-6 — Документация обновлена в том же PR +**Дано:** PR с изменениями кода. +**Тогда в том же PR:** +- `.env.example` содержит `ORCH_QG0_TITLE_MAX` с дефолтом и комментарием; +- `.env.staging.example` содержит `ORCH_QG0_TITLE_MAX`; +- `CHANGELOG.md` содержит запись об ORCH-069; +- при наличии релевантной таблицы конфигов в README / `CLAUDE.md` — она обновлена. +**FAIL если:** какой-либо из обязательных файлов документации не обновлён (reviewer → REQUEST_CHANGES). + +## AC-7 — Обратная совместимость +**Дано:** env не задан. +**Тогда:** любой заголовок, который проходил QG-0 при прежнем лимите 80 (len ≤ 80), проходит и теперь (len ≤ 200). +**FAIL если:** ранее валидный заголовок отклоняется. + +## AC-8 — Изоляция изменений +**Тогда:** не изменены slug-логика (`[:30]`), схема БД, реестры `STAGE_TRANSITIONS` / `QG_CHECKS`, контракты `handle_*`, soft-QG-0 поведение (warning на `work_item.created`). +**FAIL если:** затронут любой из перечисленных вне-объёмных элементов. diff --git a/docs/work-items/ORCH-069/04-test-plan.yaml b/docs/work-items/ORCH-069/04-test-plan.yaml new file mode 100644 index 0000000..b99bcb0 --- /dev/null +++ b/docs/work-items/ORCH-069/04-test-plan.yaml @@ -0,0 +1,112 @@ +work_item: ORCH-069 +description: > + Юнит-тесты для конфигурируемого верхнего лимита длины заголовка QG-0 + (_qg0_errors) через параметр settings.qg0_title_max (env ORCH_QG0_TITLE_MAX, + дефолт 200). Тесты патчат settings.qg0_title_max (monkeypatch на объекте + src.config.settings, который импортирован в src.webhooks.plane) и проверяют + границы и тексты ошибок. Файл тестов: tests/test_qg0_title_limit.py. + +tests: + - id: TC-01 + type: unit + description: "Дефолтный лимит 200: заголовок ровно 200 символов -> нет ошибки длины title (PASS на границе)." + module: tests/test_qg0_title_limit.py + setup: "settings.qg0_title_max=200 (дефолт); name='x'*200; description валиден (>=20 символов)." + assert: "В списке _qg0_errors нет элемента про длину title." + covers: [AC-1] + expected: PASS + + - id: TC-02 + type: unit + description: "Дефолтный лимит 200: заголовок 201 символ -> ошибка длины title, текст упоминает '200'." + module: tests/test_qg0_title_limit.py + setup: "settings.qg0_title_max=200; name='x'*201; description валиден." + assert: "В _qg0_errors есть ошибка длины title и её текст содержит подстроку '200'." + covers: [AC-1] + expected: PASS + + - id: TC-03 + type: unit + description: "Настраиваемый лимит 120: заголовок 120 символов -> нет ошибки длины title." + module: tests/test_qg0_title_limit.py + setup: "monkeypatch settings.qg0_title_max=120; name='x'*120; description валиден." + assert: "Нет ошибки длины title." + covers: [AC-2] + expected: PASS + + - id: TC-04 + type: unit + description: "Настраиваемый лимит 120: заголовок 121 символ -> ошибка длины title, текст упоминает '120'." + module: tests/test_qg0_title_limit.py + setup: "monkeypatch settings.qg0_title_max=120; name='x'*121; description валиден." + assert: "Есть ошибка длины title и её текст содержит подстроку '120' (и НЕ '80')." + covers: [AC-2] + expected: PASS + + - id: TC-05 + type: unit + description: "Graceful: невалидное (нечисловое) значение env ORCH_QG0_TITLE_MAX не роняет инстанцирование Settings и даёт дефолт 200." + module: tests/test_qg0_title_limit.py + setup: "monkeypatch.setenv('ORCH_QG0_TITLE_MAX','abc'); создать новый экземпляр Settings()." + assert: "Settings() не выбрасывает исключение; settings.qg0_title_max == 200." + covers: [AC-3] + expected: PASS + + - id: TC-06 + type: unit + description: "Graceful: пустая строка env ORCH_QG0_TITLE_MAX -> дефолт 200, без исключения." + module: tests/test_qg0_title_limit.py + setup: "monkeypatch.setenv('ORCH_QG0_TITLE_MAX',''); создать новый экземпляр Settings()." + assert: "Settings() не падает; settings.qg0_title_max == 200." + covers: [AC-3] + expected: PASS + + - id: TC-07 + type: unit + description: "Корректное числовое env -> применяется заданное значение (sanity положительного пути)." + module: tests/test_qg0_title_limit.py + setup: "monkeypatch.setenv('ORCH_QG0_TITLE_MAX','150'); создать новый экземпляр Settings()." + assert: "settings.qg0_title_max == 150." + covers: [AC-2, AC-3] + expected: PASS + + - id: TC-08 + type: unit + description: "Нижний лимит title не сломан: заголовок < 5 символов -> ошибка 'Title слишком короткий' при любом верхнем лимите." + module: tests/test_qg0_title_limit.py + setup: "settings.qg0_title_max=200; name='abc' (3 символа); description валиден." + assert: "В _qg0_errors есть ошибка короткого title." + covers: [AC-4] + expected: PASS + + - id: TC-09 + type: unit + description: "Лимит description не сломан: description < 20 символов -> ошибка 'Description слишком короткий'." + module: tests/test_qg0_title_limit.py + setup: "settings.qg0_title_max=200; name валиден (>=5, <=200); description='short'." + assert: "В _qg0_errors есть ошибка короткого description." + covers: [AC-4] + expected: PASS + + - id: TC-10 + type: unit + description: "Обратная совместимость: заголовок длиной 81-200 (ранее отклонялся лимитом 80) теперь проходит при дефолте." + module: tests/test_qg0_title_limit.py + setup: "settings.qg0_title_max=200; name='x'*100; description валиден." + assert: "Нет ошибки длины title (раньше при лимите 80 была бы)." + covers: [AC-7] + expected: PASS + + - id: TC-11 + type: unit + description: "Полный набор тестов зелёный (регрессия не внесена)." + module: tests/ + command: "pytest tests/ -q" + assert: "Все тесты проходят." + covers: [AC-5] + expected: PASS + +notes: + - "settings импортирован в src.webhooks.plane как 'from ..config import settings', _qg0_errors читает settings.qg0_title_max динамически -> monkeypatch на src.config.settings.qg0_title_max (или импортируемом объекте) меняет поведение в рамках теста." + - "Для TC-05/06/07 нужен СВЕЖИЙ экземпляр Settings(): глобальный src.config.settings создаётся один раз на импорт, поэтому env-тесты инстанцируют Settings() локально, а не полагаются на готовый синглтон." + - "Тесты не требуют сети, БД, агентов или FastAPI TestClient — чистая проверка leaf-функции _qg0_errors и парсинга Settings."