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