Спросите своего кодового агента, зачем существует обёртка с ретраями, и посмотрите, что будет. Он читает код, выводит правдоподобную причину и излагает её с полной уверенностью. Правдоподобная причина неверна: обёртка существует потому, что конкретный провайдер отдаёт 200 с телом ошибки, — а из самой обёртки этого не видно вообще.
Настоящая причина записана. Она лежит в записи архитектурного решения, в папке, которую никто не открывал с момента мержа. Агент её не прочитал, потому что ничто не сообщило ему, что запись существует и что она относится к правимому файлу.
Об этом режиме отказа и пост, и чинится он не словами «пишите больше ADR». Чинится превращением записей, которые у вас уже есть, в то, по чему агент может пройти.
Почему прозаические ADR не работают для машин
Стандартный ADR — markdown-файл с контекстом, решением и последствиями. Для человека формат хороший, для программы плохой, по трём конкретным причинам.
Нет рёбер. Решение 0084 заменяет 0061 и опирается на 0012. В прозе это отношение — предложение: иногда ссылка, часто просто номер в абзаце, порой лишь подразумевается. Проходить не по чему, поэтому «от чего зависит это решение» превращается в задачу на понимание текста, а не в запрос.
Нет стабильной идентичности. Записи переименовывают, когда улучшают заголовок; перенумеровывают, когда кто-то переупорядочивает папку; переносят, когда реорганизуют директорию. Каждая ссылка на них молча гниёт. Через полгода половина перекрёстных ссылок указывает в пустоту, и никто этого не замечает, потому что ничто не проверяет.
Нет валидации. Сломанная ссылка между двумя документами невидима. Сравните с кодом: обращение к несуществующему символу роняет сборку немедленно — именно поэтому код остаётся внутренне согласованнее документации.
Почините эти три вещи — и папка с ADR перестаёт быть полкой и становится индексом.
Как сделать из этого граф
Идентификатор чеканится один раз и не двигается
Дайте каждой записи идентификатор, присвоенный при создании и больше не меняющийся, — не заголовок, не позицию в папке, не порядковый номер в списке, который кто-то может переупорядочить. Всё остальное в записи может меняться свободно: заголовок, директория, формулировки, статус. Ссылки указывают на идентификатор и переживают всё это.
Это изменение с наибольшим рычагом, и его стоит сделать, даже если больше вы отсюда не возьмёте ничего. Цена — одна строка во frontmatter. Выгода — ссылка, написанная сегодня, остаётся валидной после двух реорганизаций.
Один тип рёбер, а не семь
Соблазн — богатый словарь: заменяет, уточняет, конфликтует с, реализует, связано с. Сопротивляйтесь. Богатые таксономии рёбер начинают применяться непоследовательно с третьего контрибьютора, а непоследовательно типизированное ребро хуже нетипизированного: запросы теперь молча промахиваются.
Один тип рёбер несёт почти всю ценность: опирается на. Решение B опирается на решение A, когда неправота A ставит B под вопрос. Одно это отношение даёт обход зависимостей, анализ влияния и топологический порядок чтения. Статус (accepted, superseded, rejected) живёт на узле, а не на ребре, — и замещению не нужно собственное отношение.
Висячее ребро роняет сборку
Это правило, благодаря которому остальное переживает контакт с живой командой. Ссылка на несуществующий идентификатор — не предупреждение в лог, который никто не читает, а провал валидации, и валидация гоняется в CI рядом с тестами.
Та же проверка обеспечивает ацикличность. Цикл в «опирается на» означает, что рассуждение круговое, и поймать это в момент внесения куда проще, чем через полгода, когда кто-то пытается разобрать цепочку.
Как только валидация становится воротами, а не отчётом, граф остаётся истинным без того, чтобы кто-то поддерживал его как повинность. В этом весь фокус: дисциплину обеспечивает машина, и она не зависит от того, помнит ли кто-нибудь.
Соедините граф с кодом
Валидируемый граф документов — всё ещё отдельный мир от исходников. Мост — маркер: короткий аннотированный комментарий у определения, к которому относится, с именем идентификатора решения.
// @decision ADR-0084 — обёртка с ретраями: этот провайдер отдаёт 200 с телом ошибки
export async function callProvider(req: Request): Promise<Result> {
Дёшево пишется и меняет возможное с обеих сторон. Из кода: агент, правящий эту функцию, может спросить, что здесь было решено. Из графа: решение может перечислить код, который его реализует. А маркер, указывающий на несуществующее решение, роняет ту же валидацию, что и висячее ребро, — мост тоже не может молча сгнить.
Что агент получает право спросить
С графом три вопроса становятся одиночными вызовами вместо археологии:
| Вопрос | Что возвращает |
|---|---|
| Почему это устроено так? | Решения, привязанные к этому файлу или символу, плюс то, на что они опираются, — цепочка рассуждения, а не результат поиска. |
| Что эта правка ставит под удар? | Всё, что опирается на затрагиваемое решение, транзитивно. Чек-лист ревью, выведенный, а не вспомненный. |
| В каком порядке это читать? | Топологический обход снизу вверх, чтобы агент, входящий в область, читал причины раньше следствий. |
Второй вопрос окупает всё упражнение. «Что ещё это затронет» — вопрос, из которого делаются регрессии, и ровно тот вопрос, на который человек отвечает по памяти, а агент не отвечает вовсе — если зависимости не записаны рёбрами.
Происхождение: вторая половина
Граф решений объясняет почему. Он не объясняет, как этот символ стал таким, какой он есть, — это другой вопрос с другим ответом.
Выводите это из того, что реально произошло, а не храните руками. Символ переименовали, перенесли в другой модуль, разбили надвое, потом слили обратно. Каждое из этих событий восстановимо из истории, а сцепленные вместе они отвечают «откуда это взялось» задолго после того, как исходное имя исчезло.
Важное проектное решение здесь — что делать, когда цепочка рвётся: когда переименование неоднозначно или файл переписали целиком. Неправильный ответ — угадать и подать догадку как историю. Правильный — сообщить о разрыве: «цепочка непрерывна до этого коммита, раньше установить нельзя». С ограниченной историей агент работать может. С выдуманной — нет.
Стоит ли это обычной команде
Честный ответ: правило идентификатора и валидационные ворота стоят почти при любом размере, потому что стоят почти нуля и предотвращают то медленное гниение, из-за которого документации перестают доверять. У меня так живёт 191 запись решений, и накладные расходы близки к нулю — именно потому, что это обеспечивается, а не вспоминается.
Маркеры и слой происхождения начинают окупаться, когда агенты правят код, который писали не вы лично, или когда команда достаточно велика, чтобы рассуждение вышло из комнаты. Ниже этого порога граф без моста в код всё равно сильно лучше папки.
Единственное, чего делать не стоит, — брать церемонию без обеспечения. Папка ADR с богатым словарём рёбер и без валидации дороже, чем отсутствие папки: она выглядит авторитетно и дрейфует.
Выводы
- Прозаические ADR проваливаются для машин по трём пунктам: нет проходимых рёбер, нет стабильной идентичности, нет валидации.
- Чеканьте идентификатор при создании и не меняйте его. Максимальная отдача при минимальной цене.
- Один тип рёбер — опирается на. Статус живёт на узле. Богатые таксономии применяются непоследовательно.
- Висячая ссылка роняет сборку. Обеспечение — то, что держит граф истинным без ручной поддержки.
- Маркеры в коде соединяют граф с исходниками в обе стороны и валидируются так же.
- Происхождение выводите из произошедшего; когда цепочка рвётся — сообщайте о разрыве, а не угадывайте.
- Берите обеспечение или не берите церемонию: невалидируемый граф дрейфует, выглядя авторитетно.
Граф решений отвечает на почему; долговременная память отвечает на что мы уже выучили тяжёлым способом; а общее правило за обоими — что ответ обязан заявлять собственную надёжность — это нормативная модель для сервисов, которых читает машина. Если хотите это в своём репозитории — направление AI-разработка.
