analyst(ET): auto-commit from analyst run_id=350

This commit is contained in:
2026-06-08 05:12:49 +00:00
parent d5f11e5caa
commit 59603f6e92
4 changed files with 339 additions and 0 deletions

View File

@@ -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` самостоятельно.
Следствие: вполне валидные осмысленные заголовки длиной 81200 символов
отклоняются на входе конвейера без бизнес-причины.
## 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).

View File

@@ -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.
- Изменение чисто аддитивное; откатов/миграций не требует.

View File

@@ -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 если:** затронут любой из перечисленных вне-объёмных элементов.

View File

@@ -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."