ADR 0199 — Минимальный ресурс контейнера выводится из module_map: каждый модуль объявляет свой, а Infra поднимает с суммой
Статус
Accepted. 2026-10-01 (AT-261, история HS-065, эпик EP-030, раунд 36;
решение владельца от 01.10: "o recurso mínimo é derivado do module_map — o que
o Arquiteto declara por módulo —, não um preset fixo por stack"). Меняет
схему артефакта artifact.module_map (Фаза 3b) и то, что выполнение
container_start делает с опущенным resources, ссылаясь, не редактируя их, на
ADR 0065
(ресурсы с пределом в артефакте образа),
ADR 0130 (pidsLimit в spec broker),
ADR 0131 (маршрутизация
Arquiteto), ADR 0133 (Infra
выбирает) и ADR 0190 (подъём
на сервере при принятии, который использовал "recursos padrão").
Контекст
Измерено в dev 2026-10-01, до изменения:
RECURSOS_PADRAO = {cpus 2, memoryMb 4096, pidsLimit 512}и пределRECURSOS_MAXIMOS = {8, 16384, 4096}вapps/api/src/domain/containers/project-container.ts. Опущенное полеresourcesстановилось значением по умолчанию вvalidarDecisaoDeImagem; выше предела — отказ. Понятия МИНИМУМА не было.- Подъём на сервере при принятии (
subir_no_aceite/2, ADR 0190) всегда отправляетresources: %{}, так что каждый поднятый им контейнер получал значение по умолчанию. Реальный контейнер использования 29.09 имелMem=4 GiB,NanoCpus=2e9,Pids=512— значение по умолчанию. - У
module_mapбыло{name, stack, responsibility, dependsOn}на модуль (ModuleNodeвdomain/architecture/module-graph.ts, JSONB-колонкаmodule_maps.modules, инструментcreate_module_mapв engine) и ни одного поля ресурса. У маршрутизации (artifact.module_routing) и образа тоже нет. "Совместимый минимум" нельзя было вывести из существующих данных. - У проекта ОДИН контейнер:
project_id UNIQUEвproject_containers.
Решение
module_mapполучаетresourcesна модуль. Необязательно в артефакте (у старых карт его нет; никакой миграции — это поле JSON), с ТРЕМЯ числами spec broker:cpus,memoryMb,pidsLimit. Означает то, что нужно ЭТОМУ модулю самому по себе внутри контейнера. Инструментcreate_module_mapначинает его запрашивать (requiredв схеме элемента, с тремя обязательными полями внутри). api валидирует при создании карты: все три или ни одного (объявление наполовину — 400; дополнение недостающего поля значением по умолчанию было бы выдумыванием числа), каждое положительное и в пределе контейнера.- Минимум — это СУММА по модулям, а не максимум. Все модули живут в одном контейнере одновременно: dev agent каждого работает там, параллельно, когда область параллелизует, и dev-сервер одного не выключается ради работы другого. Максимум был бы правдой, только если бы никогда не стояли два одновременно, и его ошибка — OOM-kill посреди команды, а не понятный отказ. Сумма, превышающая предел, отклоняется УЖЕ при создании карты (400 для Arquiteto, который может исправить), и снова, в качестве защиты, при подъёме.
- Модуль без объявления учитывается с сегодняшним значением по умолчанию как ПОРОГОМ.
Число для него никогда не выдумывается: единственное существующее — то, что он
получает сегодня, значение по умолчанию контейнера. Поэтому при наличии модуля без объявления минимум —
это НАИБОЛЬШЕЕ из суммы объявленных и
RECURSOS_PADRAO, поле за полем; только когда все объявлены, минимум оказывается ниже значения по умолчанию. Карта совсем без объявлений — каждая карта до этого ADR — даёт ровно значение по умолчанию, и её поведение не меняется. Порог на КОНТЕЙНЕР, а не "значение по умолчанию на модуль в сумме", — намеренно: суммирование 2 cpus на каждый необъявленный модуль заставило бы старую карту из пяти модулей превысить предел и перестать подниматься. - Выводит api, при выполнении
container_start.ExecuteContainerStartUseCaseчитает действующийmodule_map(тот же, что выдвинула маршрутизация), выводит минимум и разрешает ресурсы поле за полем: ОПУЩЕННОЕ становится минимумом; НИЖЕ минимума — названныйfailed(без записи решения и без подъёма); выше действует до предела, проверяемого как всегдаvalidarDecisaoDeImagem. Без действующегоmodule_mapминимум — значение по умолчанию. Engine НЕ выводит: подъём на сервере продолжает отправлять пустойresources, а его rationale говорит, что ресурсы — выведенный минимум, — одно мерило, на TypeScript, никогда вторая копия суммы на Elixir. rationaleартефакта говорит, откуда взялось каждое число. Решение, которое записываетDecidirImagemDoProjetoUseCase(artifact.project_image,decidedBy: 'infra-lead'), получает фразу "Recursos mínimos (RN-683): …", с суммированными модулями, НЕ объявившими (названными) и версией карты. Тот, кто проверяет контейнер, читает, почему у него эти числа.
Последствия
- Подъём на сервере (ADR 0190) начинает поднимать с НАИМЕНЬШИМ допустимым ресурсом: полностью объявленный проект может оказаться ниже значения по умолчанию в 2 cpus/4 GiB/512 pids, что и было расточительством, измеренным AT-261.
- Arquiteto начинает объявлять ресурс на модуль, и модель может ошибиться с
числом. Ошибка В БОЛЬШУЮ сторону стоит машинных ресурсов и проверяема в
rationale; ошибка выше предела — 400 с причиной; ошибка в МЕНЬШУЮ сторону — оставшийся риск, и он того же калибра, что Arquiteto уже решал при выборе образа. Число ОБЪЯВЛЕНО, а не измерено — измерение реального потребления контейнера — другое направление, здесь не взятое. - Infra Lead запрашивает ВЫШЕ минимума, когда хочет (до предела), никогда ниже.
Отказ ниже минимума происходит при ВЫПОЛНЕНИИ: предложение
pendingс ресурсом ниже минимума падает только при одобрении, с причиной вexecutionResult. Отказ уже при предложении потребовал бы читать карту вProposeActionUseCase, и здесь это не сделано. choose_project_image(Arquiteto, решающий образ с нуля, RN-105) НЕ меняется: там Arquiteto сам выбирает свои ресурсы, а просьба владельца была о выборе Infra. Подъём через runner (container_start_via_runner) тоже не меняется: он поднимает уже решённый образ с записанными в нём ресурсами.- Предел остаётся тем же, и никакой другой не сдвигается.
- Сумма долей CPU округляется до трёх знаков, чтобы артефакт не
записывал
0.30000000000000004.