# Бриф проекта — контракт Конвейер общий, а находки — проектные. Проход, не знающий, что в этом проекте нельзя нарушать, чем краснеет гейт и сколько данных реально проходит через узел, выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать. Поэтому проектная специфика живёт **в одном файле проекта**, а не в charter'ах агентов. Charter описывает **метод** прохода (что он делает и почему именно так), бриф — **предмет** (что здесь дорого, чем это меряется, где лежит). Шаблон для заполнения — [brief-template.md](brief-template.md). ## Где лежит и как находится Порядок разрешения пути, одинаковый для скилла и для каждого агента: 1. путь, названный в задании конвейера (`бриф: <путь>`) — конвейер обязан его передавать каждому проходу; 2. `docs/review-brief.md`; 3. `.claude/review-brief.md`; 4. брифа нет ни по одному пути — **он заводится**, скиллом `av-dev-pipeline:project-brief`, и прогон продолжается по заведённому. Разрешает путь конвейер, один раз, и дальше передаёт готовым. Агент, получивший путь в задании, сам ничего не ищет. ## Деградированный режим — исход, а не умолчание Он включается ровно тогда, когда бриф **завести не удалось**: репозиторий доступен только на чтение, человек прямо запретил, инварианты вывести неоткуда. Во всех остальных случаях брифа быть обязано. Каждый проход в этом режиме: - не присваивает `critical` по основанию «нарушен инвариант проекта» — инвариантов он не знает; - не оперирует числами объёма и потока — формулирует условиями; - пишет в границы покрытия строку: «брифа проекта нет (<причина>): инварианты, модель угроз и профиль нагрузки неизвестны; находки этих классов не искались». Причина обязательна: без неё строка неотличима от «мы просто не стали», и одинаковая строка в каждом отчёте перестаёт читаться на третьей задаче. Триаж сводит эти строки в одну и выносит в финальный отчёт. Отсутствие брифа — дыра покрытия, а не нейтральное умолчание. ## Форма Markdown. Разделы — заголовки второго уровня с **точными именами** из списка ниже: по ним агенты находят свой кусок. Порядок разделов свободен, лишние разделы допустимы и игнорируются, отсутствующий раздел работает как деградированный режим для тех проходов, которые его читают. ## Разделы ### `## Проект` — обязателен Абзац: что система делает — и, что важнее, **чего она не делает**. Граница домена нужна архитектурному проходу как критерий: «хранилище, а не аналитика», «единое ядро, тонкие транспорты», «связующий сервис, а не медиатека». Без неё перенос понятия через границу выглядит просто новым кодом. Читают: `architecture`, `rubric`, `reimpl`, `specs`. ### `## Инварианты` — обязателен Список того, что нарушать нельзя. Каждый пункт — три вещи: - формулировка **как проверяемое свойство**, а не как лозунг: «точка сохраняется дословно: незнакомое поле не отбрасывается», а не «бережно относимся к данным»; - **последствие нарушения** и его обратимость; - **severity по умолчанию** — если это не `critical`, скажи прямо. Это единственный раздел, который **цитируется формулировкой**, а не пересказывается ссылкой: по нему присваивается severity, и пересказ здесь стоит неверной оценки. **Оговорка про severity, потому что она единственная не цитируется.** Проекты почти никогда не пишут severity рядом с инвариантом — её приходится выводить, и правило вывода одно: **по обратимости последствия**. Необратимо и молча — `critical`; лечится повтором, видно сразу — ниже. Выведенная severity помечается словом «выведена по обратимости», а не выдаётся за решение проекта: проход ставит по ней `critical`, и он вправе знать, чьё это суждение. Лучший исход — дописать severity туда, откуда цитируется формулировка, и тогда пометка снимается. Читают: `specs` (режим 1 — отражены ли задетые инварианты в спеке), `code`, `adversary`, `architecture`, `triage` (ранжирование и разметка «развилка»). ### `## Гейт` — обязателен - **Команда** целиком, включая передачу базы диффа (`task gate BASE=<база>`), и как база определяется по умолчанию. - **Где логи** отдельных шагов. - **Что означает каждый исход**: чем гейт краснеет, что предупреждает, что пропускается по составу диффа. - **Шаги, которые красят безусловно, и почему.** Это самая ценная часть раздела: «данные под контролем версий», «структура конфига изменилась, а образец нет», «миграции не накатываются с нуля» — проход обязан знать, что здесь не бывает «ну это мелочь». - **Чего в гейте намеренно нет** и почему — прогон на живом корпусе, длинный интеграционный тест. У проверки, которую гейт не гоняет, краснота никому не видна; это уезжает в границы покрытия. Читает: `gate`. ### `## Команды` — обязателен Что проход имеет право выполнить и чем: - **карта проекта для архитектуры** — команда, отдающая пакеты, граф зависимостей и инвентарь концепций (`task review:context`); - **запуск изменения вживую** — чем поднять и как проверить поведение (нужно пайплайну задачи на шаге поведенческой верификации); - **тесты, линт, дополнительные проверки** — и какие из них дорогие; - **дорогие проверки вне гейта — с адресатом.** Мало сказать «`verify:archive` идёт минуту»: назови, **кто и когда обязан** её гонять — какой класс изменения её требует, кто её запускает (проход, пайплайн, человек) и что делать, если она не прогонялась. Без адресата дорогая проверка не гоняется никогда, а её краснота не видна никому. **Адресат в проекте не записан нигде — тогда бриф его назначает**, и назначение помечается: «адресат назначен брифом, владельцем не подтверждён». Это тот же класс, что выведенная severity у инварианта: слот честнее заполнить назначением с пометкой, чем оставить пустым; - **что запускать запрещено**: рабочая БД, боевой каталог данных, внешние сервисы. Формулируй запретом с путями, а не «будь осторожен». Читают: `architecture`, `gate`, `ops`, `triage`, пайплайн задачи. ### `## Прод и поток` — обязателен Материал для эксплуатационного прохода, и он же — половина ранжирования триажа. **Первой строкой — главный вопрос эксплуатации этого проекта.** Один заголовок покрывает противоположные постановки: «поток идёт непрерывно и молча, отправитель об отказе не узнает» и «мы опрашиваем чужие сервисы, и главный вопрос — что делать, когда сосед отвечает медленно». От того, какая из них здесь главная, зависит порядок находок в отчёте, а вывести её проход не может — он видит одинаковый код. Дальше: - где это работает: машина, окружение, что рядом, кто перезапускает; - **внешние зависимости поимённо** и чем каждая отказывает: не только «падает», но и «отвечает медленно», «молчит», «отдаёт мусор». Эксплуатационный проход спрашивает про каждую отдельно, и список зависимостей он взять больше неоткуда. **Зависимостей почти нет — так и напиши**: «внешних зависимостей нет, смотри на диск и на СУБД». Пустой пункт, не названный пустым, проход тратит впустую или заполняет выдумкой; - **кто заметит отказ и когда** — есть ли вообще наблюдатель; - **характер потока**: непрерывный и молчаливый, по запросу, по расписанию; есть ли обратная связь у отправителя; - **представление данных и настройки хранилища.** Чем физически лежит запись (сжатый BLOB, JSON-строка, колонки), что происходит при чтении и записи (распаковка целиком, read-modify-write), и **настройки, у которых есть числовое значение**: таймаут занятости СУБД, режим журналирования, лимит тела, размер пула, ретеншен. Это не украшение раздела: ровно эти два факта превращают **замер** в находку. Замеренный пик памяти — аномалия только если известно, что запись лежит сжатой и распаковывается целиком; замеренная длительность удержания блокировки — гарантированный отказ соседа только если известно, чему равен таймаут занятости. Без этих фактов проход снимет верное число и честно понизит находку до гипотезы, потому что сравнить его будет не с чем. Цена пропущенного пункта здесь не «не найдём», а **«найдём и не починим»**. Числа с провенансом — в следующем пункте, воспроизведённые случаи — в разделе `## Прецеденты`; - **измеренные числа с провенансом**: объёмы, размеры тел, темп, размеры таблиц. Число без источника проход обязан превратить в условие — так и напиши, откуда оно. **Замер и настройка — разные пункты, и путать их нельзя:** настройка (`busy_timeout`, лимит тела, размер пула) живёт пунктом выше и говорит, чему равен порог; замер говорит, что происходит на самом деле. Проекту без наблюдаемой нагрузки нечего писать во втором пункте — **так и напиши**: «измеренных чисел нагрузки нет, всё, что ниже, — настройки». Тогда проход формулирует условиями осознанно, а не потому, что не нашёл; - **что обратимо, а что нет.** Падение, которое лечится повтором, и тихая потеря, которую нечем восстановить, — разные классы, и порядок находок в отчёте зависит от того, какой из них здесь главный. Читают: `ops`, `adversary`, `triage`, `reimpl`. ### `## Модель угроз` — обязателен **Первой строкой — периметр.** «Сервис открыт наружу; злоумышленник в локальной сети неинтересен» и «контур доверенный, публичного интернета здесь нет, не выдумывай его» — это один и тот же заголовок при противоположной постановке, и враждебный проход не может выбрать между ними сам. Периметр, объявленный первой строкой, задаёт смысл всему остальному разделу. **Периметров может быть два — целевой и сегодняшний**, если контур ещё не развёрнут: «целевой — открыт наружу за прокси с TLS; сегодняшний — только локальная машина, токены пусты осознанно». Тогда назови оба и скажи прямо, **против какого строятся находки**. Иначе враждебный проход либо завалит отчёт находками «нет TLS» по сегодняшнему состоянию, либо не станет искать дефекты, спящие до выкладки, — оба исхода стоят прохода целиком. Дальше: - **что недоверенное** и каким каналом приходит: тело запроса, файл, аргумент команды, ответ внешней системы, содержимое архива; - **из чего строятся пути и ключи** — раскладка файлов на диске, состав координатного ключа записи, имя каталога. Враждебный проход выводит запись за пределы песочницы именно отсюда, и без этого пункта он ищет вслепую; - **что разграничивает доступ** — токены, контуры, права файлов; - **что чувствительнее чего**: если данные дороже секретов, скажи это прямо; - **что вне модели** — перечислить явно. Пустой пункт «вне модели» означает, что враждебный проход выдумает угрозу сам, и находка никогда не будет исправлена. Читает: `adversary`. ### `## Карта` — обязателен Где что лежит, путями: - **основная ветка** — её имя. Отсюда берутся ветки задач, в неё вливается батч, от неё считается база диффа по умолчанию (`git merge-base HEAD <основная>`). Батч подставляет это имя в каждую команду git; взять его больше неоткуда, а угадывание между `master` и `main` ломает интеграцию целиком; - актуальные спеки и дельта-спеки предлагаемого изменения; - **нарезка capability и миграционное состояние спек** — по какому признаку проект режет capability (по домену, по транспорту, по подсистеме), какие из них уже перенесены в актуальные спеки, а какие ещё живут только в документации или в коде. Проход по спекам иначе примет непереехавшую тему за пробел в спеке, а архитектурный — за отсутствие понятия; - конвенции прозой — **файл или каталог файлов**, путями; и **какая их часть уже механизирована** правилом. Механизация бывает **в нескольких местах сразу**: конфиг линтера, собственный анализатор и — чаще всего незамеченное — **тест-сканер исходников** (правило про направление зависимостей, форму миграций, логику в транспорте), который внешне неотличим от обычного теста. Перечисли все места: непойманное место механизации означает, что проход по конвенциям будет добросовестно проверять уже проверенное; - **наблюдения на живых данных** — где записано, как внешний мир ведёт себя на самом деле (что реально шлёт источник, чем документация формата расходится с практикой, какие числа сняты с живого потока). Их спрашивают `specs`, `reimpl` и `ops`, и все трое — «из раздела `## Карта`». Отдельного файла нет — **так и напиши**, и перечисли суррогаты: спеки, где наблюдения рассыпаны, комментарии в адаптерах, `testdata`. Отдельный файл — лучшая форма, потому что при нескольких внешних источниках наблюдения иначе не сойдутся в одном месте; но честный перечень суррогатов лучше молчания, от которого три прохода ищут несуществующий путь; - архитектура и решения; журнал проскочивших дефектов; - **единые точки проекта** — где генерируются идентификаторы и время, где единственный парсер входного формата, где маппинг доменной ошибки в код ответа, где общий путь приёма. Это материал для вопроса «не появился ли второй способ»; если команда карты проекта их выгружает, здесь хватит ссылки на неё; - **нумерованные артефакты** — путь миграций и правило нумерации: батч раздаёт номера заранее, чтобы параллельные задачи не столкнулись файлами; - где ведутся задачи (пайплайн только читает и сообщает исход); - `testdata` и что в них лежит; куда можно писать временное; - **каталоги, которые не трогают вовсе**. Читают: все проходы. ### `## Типовые узлы` — необязателен, но без него рубрика беднеет Роды узлов, из которых состоит проект (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего API, CLI-команда, файловое хранилище), и по 3–5 **специфичных для рода** проверяемых свойств к каждому. **Рода, а не инвентарь того, что сейчас лежит в пакетах.** Список пишется по природе проекта: род, который проект уже задумал, но ещё не написал, включать полезно (рубрика на него понадобится ровно на той задаче, где его заводят); а род, случайно оказавшийся в коде в одном экземпляре, — нет. Иначе раздел протухает на каждой задаче и требует пересмотра, которого никто не делает. Читает: `rubric`. Без раздела рубрика выродится в общие слова и повторит конвенции — то есть станет applicative-проходом, ради отсутствия которого она и существует. ### `## Прецеденты` — обязателен, хотя бы строкой «пусто» **Воспроизведённые случаи этого проекта, с оракулом.** Не «здесь бывают гонки», а «такой дефект здесь уже был, вот чем он воспроизведён»: что оказалось не так, каким экспериментом или тестом это показано, какими числами, где это записано. Каждый пункт — четыре вещи: - **класс дефекта** — так, чтобы проход узнал его в другом месте; - **как проявился** — симптом, который увидел человек; - **чем воспроизведён** — команда, тест, стенд, замер. Без этого пункт превращается в байку. **Регрессионный тест, написанный вместе с починкой, годится** наравне с независимым экспериментом: он исполняемый и падает на старом коде, а это всё, что требуется от оракула. Слабее он ровно в одном — сформулирован уже зная ответ; это отмечается словом, а не служит поводом выбросить пункт; - **чем закончилось** — починка, правило линтера, пункт брифа, «ничего». Зачем раздел существует. Прецедент — самая сильная опора, какая у прохода вообще бывает: он проектный, воспроизводимый и уже однажды оказался правдой. Пока слота не было, прецеденты вмерзали в charter'ы проходов — то есть каждый проект читал про чужую контрольную точку в чужой СУБД и искал её у себя. Charter описывает **форму класса**, бриф — **случай**. Источники: журнал проскочивших дефектов, архивные отчёты триажа, `git log` по починкам. Прецедентов нет — так и напиши: «прецедентов не накоплено», и это честнее пустого раздела. Читают: все проходы — свой класс; `triage` — как готовый оракул. ### `## Типовые ложноположительные` — необязателен, но без него отсев слепой Находки, которые в **этом** проекте выглядят убедительно и всегда неверны. Это единственный проектный вход в шаг триажа «отсев вкусовщины»: общие критерии («не меняет поведения, не влияет на стоимость следующего изменения, не нарушает записанного») ловят вкусовщину, но не ловят находку, которая нарушает общее правило **осознанно**. Каждый пункт — формулировка находки, какой её выдаёт проход, плюс одна строка «почему здесь это не дефект». Типичные обитатели: «дословное хранение надо нормализовать» там, где дословность — инвариант; «повтор надо сделать идемпотентным» там, где повтор невозможен по построению; «это надо вынести в конфиг» там, где значение задано внешним протоколом. Читает: `triage`. ### `## Вопросы к проходам` — необязателен Проектные вопросы, адресованные **поимённо** конкретному проходу. Главный их источник — журнал проскочивших дефектов: запись «проход не мог этого знать» чаще всего лечится фактом в другом разделе, но иногда лечится не фактом, а **вопросом**: «`ops`, спроси про поведение при откате бинаря поверх новой схемы», «`adversary`, проверь имена внутри архива». Такие вопросы живут здесь, а не в charter'е: charter общий для всех проектов, а вопрос выведен из промаха в этом. **Журнал — не единственный источник, а лучший.** У молодого проекта журнал пуст, и слот тогда заполняется из того, что есть: незакрытые находки аудита, известное расхождение кода с документацией, место, где решение принято «пока так». Правило одно и не смягчается — **у каждого вопроса указан провенанс**, и по нему видно, насколько он выстрадан: «журнал, запись такая-то» весит больше, чем «открытая находка аудита». Форма: `<имя прохода>: <вопрос> (<провенанс>)`. Проход, увидев свой блок, задаёт эти вопросы **дополнительно** к обязательным — и отвечает на них в выводе явно. Читают: проходы, названные поимённо. ### `## Триггеры` — необязателен Проектная конкретизация правила выбора профиля: какие пути и контракты означают `deep`; что считается «поведением, видимым снаружи»; при каком изменении запускается `reimpl`. Умолчания записаны в самом скилле и работают без этого раздела — но общее правило говорит «изменение публичного контракта», а какой контракт публичный, знает только проект. Читают: скилл конвейера, пайплайн задачи. ### `## Недоступно проверке` — обязателен, и делится на два подраздела Раздел целиком уезжает в границы покрытия финального отчёта — он существует ровно затем, чтобы «критичных проблем не обнаружено» никогда не читалось как «проверено всё». Но внутри лежат **два разных класса**, и смешивать их нельзя: при следующем промахе один пересматривается, другой нет. #### `### Не проверит ни один проход` Принципиально недоступное: поведение внешних систем и их будущих версий, реальный профиль нагрузки, соответствие сохранённого действительности, завязка внешних потребителей на текущую форму, суждение «а нужна ли эта функциональность». Этот список не пересматривается по факту промаха: дефект отсюда — не ошибка конвейера, а его честная граница. Он меняется только когда меняется сам проект (появился стенд, появился второй потребитель, появилась телеметрия). #### `### Перестали проверять сознательно` Решения о сужении: перестали звать проход, понизили профиль правилом, сузили класс проверяемого, сняли правило линтера как шумное. Каждый пункт — **что перестали, когда и почему**, со ссылкой на запись журнала ревью. Этот список **пересматривается первым**, как только что-то проскочило: первый вопрос по любому пропущенному дефекту — «не тот ли это класс, который мы перестали проверять». Пункт, из-за которого дефект проскочил, либо возвращается, либо получает строку «оставляем, цена поимки выше цены дефекта» с датой. Оба подраздела обязательны; пустой называется пустым. Читает: `triage`; каждый проход — свою часть. ## Правила ведения - **Бриф не пересказывает документацию проекта.** Факт, записанный в `CLAUDE.md` или в архитектуре, попадает сюда ссылкой и одной строкой сути. Два дома для одного факта разъезжаются, и разошедшийся бриф хуже отсутствующего: он выглядит актуальным. Исключение одно — раздел инвариантов, он цитируется. - **Числа — с провенансом, и провенанс проверяется переходом по ссылке.** «Тела доходили до 42 МБ (замер, ссылка)». Число без источника проход не имеет права использовать как утверждение. Отдельный и более коварный случай — **число, чей источник по ссылке не подтверждается**: в документе по ссылке другое число, или его там нет вовсе. Такое число не выбрасывается и не переписывается по догадке: оно остаётся с пометкой «расходится с источником: там <что нашли>», а проход обязан читать его как условие, а не как замер. Молча подставить «правильное» число хуже всего — расхождение перестанет быть видно, а причина его останется. - **Пустой пункт называется пустым.** «Внешних зависимостей нет — смотри на диск и на СУБД», «прецедентов не накоплено», «наблюдений на живых данных не ведём», «измеренных чисел нагрузки нет», «сознательно ничего не отключали». Отсутствие строки читается проходом как «здесь не написали», и он тратит обязательный вопрос впустую либо заполняет пробел выдумкой. Прямое «пусто» стоит одной строки и экономит проход целиком. - **Назначенное помечается назначенным.** Бриф отражает решения проекта, но местами оказывается **первым** местом, где решение вообще записано: severity у инварианта, адресат дорогой проверки, периметр, восстановленный из конфига. Так можно — молчать хуже, — но пометка обязательна («выведена по обратимости», «назначен брифом, владельцем не подтверждён»). Проход имеет право знать, чьё это суждение, а владелец — увидеть, что за него что-то решили. - **Что вне модели — называется явно.** Это относится и к угрозам, и к нагрузке, и к классам находок, которые проект сознательно перестал проверять (последние — в свой подраздел `## Недоступно проверке`, а не вперемешку с принципиальным). - **Бриф подчиняется промоуту.** Свойство, ставшее правилом линтера, из брифа вычёркивается — как и из конвенций, и из charter'ов (см. [promote.md](promote.md), шаг 3). - **Когда обновлять:** сменился гейт; появился новый контур, зависимость или источник входа; журнал ревью получил запись вида «проход не мог этого знать»; воспроизвели дефект — он идёт в `## Прецеденты`. Планового пересмотра нет. - **Заводится и обновляется шагом, а не руками** — скиллом `av-dev-pipeline:project-brief`. Он же вызывается автоматически, когда конвейер или пайплайн задачи не нашли брифа ни по одному пути. - **Бриф ведёт проект**, а не плагин. Файл живёт в репозитории проекта; плагин его читает и заводит по шаблону, но не хранит у себя и не подменяет.