ADR 0200 — Контракт между модулями становится версионируемым артефактом Arquiteto, и dev agent читает его вместо чужого worktree
Статус
Accepted. 2026-10-01 (AT-276, история HS-071, эпик EP-030, раунд 36;
решение владельца от 01.10: «контракт между модулями становится версионируемым артефактом
Arquiteto (по образцу module_map/c4_diagram), читаемым devs через
инструмент чтения; изменение контракта — новая версия от Arquiteto»). Расширяет
дизайн артефакта без таблицы из ADR 0065,
ADR 0068 и
ADR 0131; применяет правило
«агент, который пишет, должен иметь возможность читать» (RN-164/165) к dev agents, под
сдержанным чтением из ADR 0060.
Контекст
Симптом
Реальное использование 2026-09-29 (анализ analise-uso-real.md, пункт A30, 6(e)):
dev-board-engine, seq 1157/1230/1268/1344/1486 — пять ходов, читающих
интерфейсы в worktrees других модулей. По замеру AT-237
(medicao-do-jev.md) у этого агента было 24
шага с инструментом за весь раунд: 5 из 24, ~21% его шагов,
ушли на выяснение чужого интерфейса. В проекте было семь dev-модулей
(piece-catalog, retro-renderer, input-keyboard, game-session,
scoring, persistence, board-engine).
Что здесь НЕ удалось измерить: стоимость этих пяти шагов в токенах.
Event log от 29.09 живёт в локальном экземпляре сопровождающего (compose
brabo-dev) и в этом окружении отсутствует; число выше взято из анализа и
таблицы агентов замера Jev, а не из повторённого запроса. Выигрыш в
стоимости измерим позже, той же линейкой, что у AT-239 (token_usage по актору).
Что продукт давал dev (измерено в dev, 2026-10-01)
module_map(domain/architecture/module-graph.ts) — это{name, stack, responsibility, dependsOn[]}. Он говорит, КТО от кого зависит, и ничего о том, ЧТО модуль предлагает. Ни один из артефактов Arquiteto (module_map,project_image,module_routing,c4_diagram) не несёт интерфейса.- Контекст dev (
Engine.Dev.ContextBuilder) приносит story, task, бизнес-правила и ADR, отфильтрованные по модулю, — никакого интерфейса соседа. - Инструменты dev (
Engine.Dev.Tools) былиread_file,search_workspace,write_file,terminal,rag_search/rag_feedbackи завершающие: никакого чтения артефакта проекта. Единственным способом увидеть интерфейс соседа былterminal, читающий worktree другого dev, — код В РАБОТЕ в ветке, которая ещё не дошла доdevи может измениться после чтения.
Решение
- Контракт — это артефакт
artifact.module_contractsот Arquiteto. Без таблицы: событие является записью, версионируемой поversionв payload, а действующий — тот, у которого наибольшийversionс разрешением ничьей поseq, — то же сведение, что уGetC4DiagramUseCase/GetModuleRoutingUseCase. Каждое объявление несёт ВЕСЬ список и ЗАМЕНЯЕТ предыдущий; изменение контракта — новая версия, испущенная Arquiteto, а предыдущие остаются в log. - Формат — минимальный, который решает задачу: по модулю, только то, что он ПРЕДОСТАВЛЯЕТ.
{modulo, expoe: [{tipo, assinatura, descricao}]}, сtipoизfuncao | rota | evento | dado, обязательнойassinatura(до 300 символов), опциональнымdescricaoи от 1 до 40 элементов на модуль.- То, что он ПОТРЕБЛЯЕТ, в контракт не записывается. Это выводится при чтении
из
dependsOnдействующегоmodule_map— тот же аргумент, что для уровня Container C4 (ADR 0068): карта уже проверила эти рёбра на отсутствие циклов, и второй список, написанный вручную, разошёлся бы с ней при первой пересмотренной карте. Детализация «какие элементы X использует модуль Y» осталась вне: Arquiteto не знает её до реализации, а тот, кто потребляет X, читает весь список того, что X предоставляет. tipo— enum, аassinatura— свободный текст. Тип — то, что меняет способ ИСПОЛЬЗОВАНИЯ элемента; сигнатура не умещается в единую схему, потому что стек каждого модуля свободен (функция Elixir и маршрут Go).- Потолки существуют потому, что контракт идёт в контекст каждого dev. Контракт, ставший всей документацией API, стоил бы на каждом шаге того, чего стоило чтение worktree за один.
- То, что он ПОТРЕБЛЯЕТ, в контракт не записывается. Это выводится при чтении
из
- Артефакт ОТДЕЛЬНЫЙ от
module_map, никогда не лишнее поле в нём. Карта — это таблица, её читает всё выполнение (claim задачи, маршрутизация, C4), и её пересмотр заново открывает проверку циклов. Контракт меняется в другом ритме — интерфейс обнаруживается, пока devs работают, — и пересмотр одного не может переиспускать другое или оспаривать его схему с другим направлением (lane ресурсов раунда 36 добавляет поле вmodule_map). - Arquiteto получает
declare_module_contracts(:direct, какroute_modules_to_infra: архитектурное объявление, без внешнего эффекта, никогда неproposed_action). api проверяет и отказывает с полной причиной, которая возвращается модели (RN-061): модуль вне действующей карты (с перечислением допустимых), безmodule_map, повторяющийся модуль, пустойexpoeили выше потолка, неизвестныйtipo, пустая или слишком длиннаяassinatura. Его kickoff получает шаг 5; инструмент входит вEngine.Harness.IdiomaDaResposta.ferramentas_de_artefato/0(RN-623). - Dev agent получает
listar_contratos_de_modulos(:direct, в реестреEngine.Dev.Tools, вне глобального). СДЕРЖАННОЕ чтение: область, замкнутая на проекте через путь маршрута (GET internal/projects/:projectId/module-contracts), НИ ОДНОГО параметра, а модуль dev берётся из контекста цикла (ctx.module, устанавливаетсяDevAgentServer), никогда от модели. При известном модуле он показывает целиком только свой и те, которые он потребляет; те, кто его потребляет, и остальные появляются по имени. Потолок 120 элементов в тексте, с указанием того, что осталось вне. Контракт модуля, вышедшего из карты, ОБЪЯВЛЯЕТСЯ, никогда не приписывается. - Kickoff dev говорит, что контракт существует и где его читать, и что
интерфейс другого модуля не читается в его worktree. Без контракта для
нужного модуля инструмент говорит, что делать: реализовывать по тому, что
говорят story и task, и, если task зависит от несуществующего интерфейса,
report_blocked, называя модуль и то, чего не хватает, — чтобы Arquiteto объявил. Не отправляет перечитывать worktree.
Последствия
- Шаг, который dev тратил на выяснение интерфейса, становится чтением постоянной стоимости, и то, что он читает, — версия, которую зафиксировал Arquiteto, а не код в работе другого dev.
- У интерфейса появляются владелец и версия. Меняет его Arquiteto, в новой версии; dev, которому нужно изменение, не делает его в коде соседа, а блокируется, называя то, чего не хватает.
- Это не блокировка. Ничто не мешает
terminalчитать другой worktree: инструмент и kickoff меняют путь, который знает dev, а не запрещают другой. Закрыть чтение чужого worktree было бы сдерживанием пути (ADR 0055), другим направлением. - Arquiteto получает шаг в kickoff, под ТЕМ ЖЕ потолком в 14
итераций. Не измерено, умещается ли он вместе с остальными семью в большой архитектуре;
исчерпание потолка по-прежнему описывается (
toolloop.limit_reached, RN-166). - Проекты без контракта продолжают работать: чтение возвращает карту с
expoe: nullв каждом модуле и предупреждение. Никакой миграции, никакой таблицы. - Папка
docs/артефактов (ADR 0148) получает тип как версионируемый: действующий перезаписывает тот же файл, как и остальные четыре. - Вне этого, объявлено: собственный экран (контракт появляется в хронологии и в
папке
docs/, а не на вкладке Архитектура); эффект на выигрыш Jev (AT-239 измеряет инструмент на шаг; этот меняет меню dev, аscripts/jev/catalogo.json— снимок от 29.09, здесь не перегенерированный); тесты ExUnit запускались внеmix(repo.hex.pmдаёт 403 в этом окружении) — их доказывает CI.