Pular para o conteúdo principal

ADR 0135 — O portão da imagem do Arquiteto (RN-105) passa a valer nos três modos de execução

Context​

Desde a FASE 25 (ADR 0065/RN-105), a aba Code de um projeto responde 409 enquanto ninguém decidiu qual imagem de container ele executa — artifact.project_image, emitido pelo Arquiteto (choose_project_image) e, desde o ADR 0133 (RN-491), também pela Infra elegendo entre candidatas do próprio roteamento do Arquiteto. A ordem é do usuário: o container é o que dá sentido a ler o código ali, e liberar a leitura antes de existir onde executar ensinaria que o portão é decorativo.

O ADR 0072 (RN-169), revisado pelo ADR 0104 (RN-421), dispensou esse portão para projeto mounted/runner: como esses dois modos não sobem container PRÓPRIO — o código mora numa pasta do usuário, montada nos containers que já existem (mounted) ou confirmada por um CLI rodando fora deles (runner) — a regra original respondia 409 para sempre num projeto onde a decisão do Arquiteto nunca ia acontecer. A dispensa evitava fechar a aba por efeito colateral.

O plano original desta entrega ("Execução em container real", Parte 1) declarou essa dispensa como decisão #5 a reabrir, já aceita antes de este PR existir: "Portão RN-105 passa a valer nos TRÊS modos, container e aba Code. Custo aceito: exp001/exp002 perdem a aba Code até o Arquiteto decidir." Este ADR executa essa decisão e registra o que ela custa e o que ela NÃO muda.

A dispensa original confundia duas perguntas que o ADR 0130 (broker de container) e o ADR 0133 (eleição da Infra) separaram, sem que a api tivesse acompanhado: "este projeto sobe container no SERVIDOR?" (não, em mounted/runner — pergunta sobre MECANISMO) e "faz sentido exigir que alguém tenha decidido a imagem do projeto antes de abrir a leitura de código?" (sim, nos três modos — pergunta sobre POLÍTICA). A dispensa respondia a segunda pergunta usando a resposta da primeira.

Decision​

Os TRÊS modos de execução (container/mounted/runner) passam a exigir artifact.project_image decidido antes de a aba Code abrir. ReadProjectCodeUseCase.portaoDoContainer deixa de checar executionMode — o portão vira incondicional, do mesmo jeito que já era incondicional dentro do modo container. ProjectCodeTab (web) para de tratar mounted/runner como caso à parte: os três modos seguem o mesmo useQuery carregando/erro/sem_decisao/decidido, o que SIMPLIFICA o componente — o ramo modoLocal e o enabled condicional por modo somem.

Custo aceito, com todas as letras: todo projeto mounted/runner EXISTENTE sem artifact.project_image decidido — o que inclui projetos reais de dogfooding deste repositório, exp001 e exp002 — PERDE acesso à aba Code no instante em que este PR é deployado, até que o Arquiteto (ou a Infra) decida uma imagem para ele. Isso é uma AÇÃO DO OPERADOR exigida DEPOIS do deploy, não uma correção transparente que os usuários nem notam — por isso a branch nasce breaking/, não bugfix/, mesmo o conteúdo sendo uma correção de lacuna: a convenção do repositório (CLAUDE.md) é explícita que o conteúdo não decide o prefixo quando a mudança exige ação do operador. A versão sobe MAJOR como consequência mecânica de scripts/ci/version.ts, não por decisão manual — não se corrige o número depois.

O ciclo de vida do container (project_containers) também para de recusar por modo, mas por um motivo mais estreito. RegistrarTransicaoDeContainerUseCase respondia 400 para qualquer projeto fora do modo container — uma checagem que pertencia, na verdade, ao broker (que já recusa por um motivo PRÓPRIO e mais específico), não a este caso de uso. O 400 sai; a tabela passa a poder registrar linha para os três modos, pelo MESMO funil de provisioning que já lia a decisão de imagem (agora também exigida para mounted/runner).

O que continua IMPOSSÍVEL, e por decisão deliberada, é mounted/runner chegarem em running DE VERDADE. Isso é aplicado num lugar só — ContainerBrokerPort.start() recusa com ModoDeExecucaoNaoSuportadoError (apps/broker/src/operacoes.ts): o broker roda no SERVIDOR e não enxerga a pasta do usuário onde o código de mounted/runner mora, então compor um container ali seria compor a partir de uma pasta vazia. Essa política NÃO muda neste PR — o comentário da classe de erro já apontava para este ADR ("o PR do portão nos três modos (1.7) revisita as duas ao mesmo tempo") e a revisão confirma que ela está certa como está.

Investigado e decidido, sem mudança de código: ExecuteContainerStartUseCase chama ContainerBrokerPort.start() ANTES de qualquer RegistrarTransicaoDeContainerUseCase.execute(..., 'provisioning') — se o broker recusa (o caso normal para mounted/runner), a falha vira container.start_failed com o motivo nomeado (BrokerRecusouError, NUNCA um crash ou um fallback silencioso) e a transição de ciclo de vida nunca chega a ser chamada. Ou seja: pelo caminho real de aprovação (container_start), nenhuma linha de project_containers nasce para mounted/runner mesmo com o 400 por modo removido — o broker já é o portão efetivo, mais preciso que o antigo 400 (ele sabe SE PODE compor o container; o caso de uso só sabia o MODO). Por isso este PR NÃO adiciona uma segunda checagem de modo em propose-action.use-case.ts nem em ExecuteContainerStartUseCase: duplicar criaria um segundo lugar para divergir do primeiro — a mesma lição que o próprio comentário do broker já registrava.

Também investigado: nem propose_container_start (a ferramenta do Infra Lead, apps/engine/lib/engine/infra/tools/propose_container_start.ex) nem GetInfraContextUseCase restringem por executionMode hoje — o Infra Lead PODE propor container_start para um projeto mounted/runner, e a proposta só falha quando alguém aprova e o broker recusa. Isso é uma lacuna PRÉ-EXISTENTE (não introduzida por este PR) que gasta um ciclo de aprovação humana numa ação destinada a falhar — mas falha de forma clara, nomeada, nunca silenciosa. Corrigi-la exigiria tocar o prompt/instrução do Infra Lead no engine, fora do escopo desta fatia (API/domínio); fica declarada abaixo, não corrigida de passagem.

Consequences​

exp001/exp002 e qualquer outro projeto mounted/runner sem imagem decidida perdem a aba Code até alguém rodar o Arquiteto (ou a eleição da Infra) para eles. Este é o custo aceito da decisão #5 do plano original, não um efeito colateral descoberto depois.

A contenção real contra mounted/runner subirem container no servidor não mudou. Continua sendo o broker (ModoDeExecucaoNaoSuportadoError), não um 400 da api — na verdade FICOU mais precisa, porque agora há um lugar só decidindo isso em vez de dois.

project_containers pode, em teoria, registrar linha provisioning para mounted/runner — mas nenhum caminho de produção hoje chama RegistrarTransicaoDeContainerUseCase.execute(..., 'provisioning') para esses modos sem passar primeiro pelo broker, que recusa. Se um caminho novo algum dia contornar ExecuteContainerStartUseCase (chamando o caso de uso direto), ele herda a mesma proteção: obterImagem.execute continua exigindo decisão, e nada além disso muda aqui.

O Infra Lead pode propor container_start para um projeto mounted/ runner sabendo que vai falhar — lacuna pré-existente, não introduzida nem fechada por este PR, declarada no CLAUDE.md ("Estado atual e aberto") para não ser perdida.

Alternativa considerada e descartada: adicionar a checagem de modo em propose-action.use-case.ts ou em ExecuteContainerStartUseCase, para recusar container_start de mounted/runner ANTES de chamar o broker. Evitaria o ciclo de aprovação desperdiçado citado acima — mas moveria conhecimento sobre QUEM pode compor um container (hoje só o broker sabe, via PROJECT_WORKSPACES_HOST_ROOT) para dentro da api, que não tem esse contexto e replicaria uma regra que já existe em outro lugar. Descartada por ora: o custo é um ciclo de aprovação evitável, não incorreção — e a mensagem de falha já nomeia a causa. Fica registrada como candidata a UX, não como correção de bug, se o volume de propostas fúteis justificar.