Engine — parte 5 (desde RN-724)
A Anamnese não paga duas vezes pela mesma janela, e o resgate de gate não reinicia ciclo que espera alguém (RN-722)
RN-724 — O ciclo de gate estacionado volta por um botão no fio, não só pelo terminal do operador
- Regra: o ciclo de gate ESTACIONADO pelo resgate
(RN-722, ADR 0207)
é retomado por
POST /projects/:projectId/tasks/:taskId/gates/:gate/resume, com o MESMO papel mínimo de quem decide a PR (developer). A api só autoriza e pede ao engine (POST /internal/projects/:id/tasks/:taskId/gates/:gate/resume), que delega aGateRescuer.retomar_estacionado/3: ciclo que não existe, não está estacionado ou gate fora deqa/secopsé 409gate_nao_estacionadoe nada é despachado. No fio da sessão, o eventogate.rescue_parkedvira um aviso com o botão "Retomar gate", inerte com o motivo em texto quando o papel não alcançadeveloper; a recusa mostra a frase da api. Nenhum poll novo: o andamento do gate retomado chega pelo log da sessão. - Onde:
apps/engine/lib/engine_web/controllers/gate_command_controller.ex:15(resume),apps/api/src/application/use-cases/gates/resume-parked-gate.use-case.ts:18(execute),apps/api/src/infrastructure/http-clients/api-to-engine-client.ts:379(resumeParkedGate),apps/api/src/interfaces/http/gates/gate-resume.controller.ts:40(resume),apps/web/src/routes/RetomarGateEstacionado.tsx:31(RetomarGateEstacionado) - Teste:
apps/engine/test/engine_web/controllers/gate_command_controller_test.exs("ciclo estacionado: 202 e despacha o gate de novo"; falha: "ciclo NÃO estacionado: 409 nomeado e nada despachado"),apps/api/test/interfaces/http/gates/gate-resume.controller.spec.ts(feliz, 409gate_nao_estacionado, papel abaixo dedeveloper),apps/web/src/routes/RetomarGateEstacionado.test.tsx(o clique chama a rota; a recusa vira toast com a frase da api; papel insuficiente deixa o botão inerte) - Origem: AT-397, fechando a lacuna declarada na RN-722
RN-725 — Texto com escape literal é decodificado, e a recusa de argumento diz o que chegou
Contexto: no TP-01 (03/10, Claude Haiku 4.5) o PO gravou títulos de
história com o escape unicode LITERAL (código): a normalização da
RN-719 só decodificava campo array/object. Recriadas com o título certo,
as histórias passaram pela recusa de duplicata (RN-720/RN-081), que não casou
o escapado com o normal. E as recusas "exige X" não diziam o que tinha
chegado: o PO gastou 28+ passos (48% do custo do teste) até o teto, e
complete_story respondia "ainda faltam RF, DoD, DoR ou business_rule_ids"
sem dizer qual.
A regra:
- No mesmo ponto da RN-719, string com
\uXXXXLITERAL (par surrogate incluído) é decodificada, e com ela\n,\t,\"e\\da MESMA string. String sem\uXXXXvolta byte a byte — código numcontentcom"\n"literal não muda. Isso revisa o "campostringnunca é tocado" da RN-719 só nesse caso. - A duplicata EXATA de história (api) e de regra (engine) compara o título com o escape decodificado, antes da caixa e do NFD de sempre.
- Recusa de ferramenta que diz "exige
X" ganha, no mesmo ponto, as CHAVES que chegaram (nomes, nunca valores), ou que chegou string/lista em vez de objeto.emit_artifactcom chave faltando nopayloaddiz as chaves faltantes e as recebidas, em vez de{:missing_keys, …}. complete_storynomeia exatamente o que falta (falta: business_rule_ids), pela mesma régua demissingForReadyda api, lida da história devolvida.
- Onde:
apps/engine/lib/engine/harness/argumentos_de_ferramenta.ex:102(decodificar_escapes),:152(chaves_recebidas),:166(com_chaves_recebidas);apps/engine/lib/engine/harness/tools/complete_story.ex:80(faltam);apps/api/src/domain/backlog/story-overlap.ts:39(decodificarEscapesLiterais),:25(normalizarTitulo);apps/engine/lib/engine/harness/artifact_dedupe.ex(normalizar) - Teste:
apps/engine/test/engine/harness/argumentos_de_ferramenta_test.exs(describe "RN-725": escape decodificado, texto sem\ue lista intactos; recusa com as chaves e sem os valores;complete_storynomeia o campo);apps/api/test/domain/backlog/story-overlap.spec.ts(título escapado casa com o normal; título diferente segue diferente) - Origem: AT-405, AT-408
RN-726 — Crédito do provedor esgotado pausa o dev agent, com origem infra, sem queimar as tarefas seguintes
- Regra: quando o provedor do modelo recusa por falta de CRÉDITO (HTTP 402,
ou o texto "available credits"/"add credits" do OpenRouter), o turno do dev
agent não é "parou sem concluir": a tarefa é bloqueada com o motivo
"crédito do provider esgotado" e origem
infra(nuncacodigo), o contador da parada automática (RN-047) NÃO avança, e o agente NÃO reivindica a próxima tarefa — fica emidle_trippede emitedev.credit_exhausted(motivo: credito_esgotado), nuncadev.idle_tripped. A retomada é o Rearmar que já existia no painel do time, depois de recarregar o crédito; a tarefa bloqueada volta pelo desbloqueio de sempre. A tela de Executores mostra o agentetravadocom "crédito do provedor do modelo esgotado — recarregue e rearme para retomar", e o sino diz o mesmo. O mesmo reconhecimento fazFalhaDeTurno.origem/1devolverinfrapara o frame final de crédito (vale para os agentes de gate, como o QA). A sessão contadev.credit_exhaustedcomo trabalho pendente, comodev.idle_tripped. Desde a RN-730 o 402 chega comcodepróprio (insufficient_credit) e é ele que decide; o reconhecimento pelo texto ficou só como rede. - Onde:
apps/engine/lib/engine/agents/falha_de_turno.ex:142(credito_esgotado?),apps/engine/lib/engine/dev/dev_agent_server.ex:931(handle_outcome),apps/engine/lib/engine/dev/agent_io.ex:279(pausar_por_credito),apps/web/src/lib/agent-status.ts:178(breakerReasonFor),apps/api/src/application/use-cases/sessions/get-session-pending-work.use-case.ts - Teste:
apps/engine/test/engine/dev/dev_agent_server_test.exs("402 do provider: origem infra, pausa sem idle_tripped e sem queimar a próxima task"; falha: "erro que não é de crédito segue como antes: parou sem concluir, conta no disjuntor"),apps/engine/test/engine/agents/falha_de_turno_test.exs("402 do provider é infra e é reconhecido"),apps/web/src/lib/agent-status.test.ts("RN-726: dev.credit_exhausted é travado com motivo de crédito…") - Origem: AT-407 (TP-01 de 03/10: 402 do OpenRouter virou três tarefas
bloqueadas com origem
codigoe a parada automática)
RN-731 — O fecho do turno diz o que foi gravado com o número do servidor
- Regra: o número do que um agente conversacional gravou num turno é FATO
do servidor, nunca texto do modelo (RN-163). O Criativo, o PO e o Arquiteto
contam, no processo do turno, cada ferramenta de escrita que voltou
ok(emit_artifactpor tipo — regra de negócio, decisão, nota, outro —,create_epic,create_story,update_story,archive_story,create_task), e o fecho daagent.responseganha, DEPOIS do texto do modelo e sem reescrevê-lo, a linha "Gravado neste turno: N …". Ferramenta recusada e leitura não contam; turno sem escrita não ganha linha; a contagem morre com o turno. A linha sai no idioma do turno (RN-622): o do autor, senão o do projeto;pt*em português ("Gravado neste turno: 1 regra de negócio"), qualquer outro idioma — ou nenhum resolvido — em inglês ("Recorded this turn: 2 business rules"), com singular e plural nos dois. - Onde:
apps/engine/lib/engine/agents/gravado_no_turno.ex:66(anotar),:99(descarregar),apps/engine/lib/engine/harness/idioma_da_resposta.ex:272(idioma_do_turno),apps/engine/lib/engine/agents/texto_do_turno.ex:65(payload_do_turno) - Teste:
apps/engine/test/engine/agents/gravado_no_turno_test.exs(feliz: seis regras e uma decisão gravadas, uma recusada — o fecho "8 regras" do modelo fica e ganha "6 regras de negócio, 1 decisão" ao lado; sem escrita: nada acrescentado; pt-BR e en, singular e plural; idioma fora dos dois cai em en),apps/engine/test/engine/agents/criativo_server_test.exs - Origem: AT-414 (TP-01 de 03/10: o Criativo fechou com "Registrei as 8 regras de negócio" e o painel tinha 6)
RN-732 — O texto do turno passa pela mesma régua de escape dos argumentos
- Regra: o texto que o modelo escreve no turno de um agente conversacional
— o
content(fecho) e cadapassosdaagent.response— passa pela MESMAdecodificar_escapes/1da RN-725 antes de ser gravado:\u00e3literal viraã. Uma régua só, sem cópia, e com o mesmo limite: só age quando há\uXXXXliteral, então texto sem escape — inclusive código com"\n"literal num bloco cercado — volta byte a byte. Oagent.deltado streaming NÃO é decodificado: um escape pode chegar partido entre dois deltas, e a bolha gravada substitui o texto em curso no fim do turno. Regra própria, e não cláusula da RN-725, porque a superfície é outra (resposta do modelo, não argumento de ferramenta) e a decisão sobre o delta é desta. - Onde:
apps/engine/lib/engine/agents/texto_do_turno.ex:65(payload_do_turno),apps/engine/lib/engine/harness/argumentos_de_ferramenta.ex:102(decodificar_escapes) - Teste:
apps/engine/test/engine/agents/texto_do_turno_test.exs(feliz: os cinco escapes medidos, no fecho e num passo; intacto: texto sem escape e bloco de código com\nliteral) - Origem: AT-417 (TP-01 de 03/10, 2ª rodada: a resposta final do Criativo gravada com "colis\u00e3o", "usu\u00e1rio" e mais três escapes na bolha)
RN-733 — A falha de crédito diz o que fazer, e o reason nunca é a tupla
- Regra: o
reasongravado noagent.errordos conversacionais, do Infra Lead e doToolLoop(dev agent) é o TEXTO do frame final quando há um — nunca a tupla{:final, …}inspecionada; outras formas seguem cominspect/1. Com crédito do provider esgotado (errorCode: insufficient_credit, RN-730, ou o reconhecimento por texto da RN-726), amensagemda bolha é curta e acionável no idioma do turno (RN-622): pt "Crédito do provedor do modelo esgotado — recarregue a chave e tente de novo.", qualquer outro idioma "The model provider's credit is exhausted — top up the key and try again."; sem idioma resolvido, pt-BR. O JSON do provider fica só noreason(diagnóstico). Os demais erros mantêm a frase de sempre. - Onde:
apps/engine/lib/engine/agents/falha_de_turno.ex:166(mensagem),:189(diagnostico) - Teste:
apps/engine/test/engine/agents/dev_lead_server_test.exs(402 em pt-BR e en: frase curta,reasonsem{:final; erroupstream: frase de sempre),apps/engine/test/engine/agents/falha_de_turno_test.exs - Origem: AT-421 (TP-01 de 03/10, 2ª rodada: o Dev Lead gravou
reason: "{:final, …"e a bolha despejou o JSON do 402)
RN-735 — A rede none da imagem do Arquiteto diz que não instala dependências
- Regra: a descrição de
choose_project_imagee o RESULTADO dela dizem que, com redenone, nenhum gerenciador de pacotes (npm, pnpm, pip, mix, cargo, go mod...) instala dependência no container, e que a Infra herda essa rede (RN-723). Comnone, o resultado pede ao modelo que chame a ferramenta de novo comegressse a stack instala dependências. É AVISO: a api não adivinha a stack, e a contenção do broker (ADR 0130) não muda. (Desde a RN-906 a api RECUSAnonequando a stack declarada de um módulo baixa dependências; fora desse caso, segue só o aviso.) - Código:
apps/engine/lib/engine/harness/tools/choose_project_image.ex:96(mensagem_de_fixada),apps/engine/lib/engine/harness/tools/choose_project_image.ex:23(spec) - Teste:
apps/engine/test/engine/harness/tools/choose_project_image_test.exs - Origem: AT-420 (TP-01 de 03/10:
node:22-bookworm-slimcom redenone, e o dev agent não conseguiunpm install)
RN-742 — O dev agent não morre calado, e a resposta HTTP tardia não fica na caixa dele
- Regra: toda chamada HTTP não-streamada do engine à api
(
post_returning/3e os GETs deEngineApiClient.Live) roda numaTaskdescartável; a resposta que chega depois doreceive_timeoutcai num processo que já acabou, nunca na caixa de mensagens de quem chamou, e exceção dentro do pedido volta como{:error, _}. E o dev agent que CAI (saída que não é:normalnem:shutdown) não é tratado como terminado: oEngine.Dev.Monitorgravaagent.errorDURÁVEL com origeminfra(reason: processo_do_dev_caiu,religado,taskId) e o RELIGA a partir da própria linha dedev_agent_states, até 3 vezes por agente; oresumepassa pelohandle_continue({:restart_recovery, _})que já existia, que bloqueia a task interrompida com diagnóstico (origeminfra) e tenta o próximo claim. Sem linha, ou no teto, a linha sai e o erro diz que não houve religamento. Ollm_turn_stream/6fica fora do isolamento (ointo:grava no dicionário do chamador); os conversacionais já o chamam de dentro da Task do turno (TurnoAssincrono), então o padrão "HTTP dentro do GenServer" não os alcança — declarado. A web não mudou: a aba Executores passa a ver oagent.errore odev.blockedda recuperação. - Onde:
apps/engine/lib/engine/sessions/engine_api_client.ex:2243(isolado),apps/engine/lib/engine/dev/monitor.ex:69(ao_terminar),:107(registrar_e_religar) - Teste:
apps/engine/test/engine/sessions/engine_api_client_isolado_test.exs(mensagem tardia não chega ao chamador; exceção vira{:error, _}),apps/engine/test/engine/dev/monitor_test.exs(crash gravaagent.errorinfra e religa;:normalapaga a linha; sem linha não religa) - Origem: AT-428 (08/10:
CaseClauseErroremFinch.HTTP1.Conn.receive_response/8depois do timeout donpm install)
Revisada pela RN-922 (ADR 0226). O dev que cai com working recente e sem retomada anterior na tarefa deixa de ser bloqueado: preserva o worktree como commit e retoma a mesma tarefa uma vez. Fora disso o bloqueio descrito acima segue.
RN-743 — Bloquear a task preserva o trabalho do worktree, e a próxima parte dele
- Regra: antes de bloquear uma task (qualquer origem, inclusive o teto de
orçamento
taskBudgetMicros, cujo VALOR não muda aqui), o que o dev agent deixou no worktree vira commit na branch da própria task, com a identidade<agente>[bot](git add -A+commit --no-verify). Odev.blockedlevatrabalhoPreservado({branch, commit}ounull) e o diagnóstico DIZ onde o trabalho ficou — ou que não foi possível preservá-lo, com o motivo; a falha ao preservar nunca impede o bloqueio. A próxima task do MESMO agente (logo, do mesmo módulo) nasce dessa branch (create_from/4) em vez dedev, uma vez só. Worktree limpo não gera commit. Declarado: o ponteiro mora só em memória (restart do engine perde o ponteiro, não o commit), e no modorunnero worktree está na máquina do usuário e o engine não preserva nada. - Onde:
apps/engine/lib/engine/dev/worktree_manager.ex:79(preservar_em),:66(create_from),apps/engine/lib/engine/dev/agent_io.ex:542(preservar_trabalho),apps/engine/lib/engine/dev/agent_io.ex:330(base_preservada) - Teste:
apps/engine/test/engine/dev/worktree_manager_test.exs(preservar_em/3 e create_from/4: commit comdev-api[bot]e a próxima task enxerga o arquivo; limpo é:nada; pasta sem repositório é erro),apps/engine/test/engine/dev/bloqueio_preserva_trabalho_test.exs(odev.blockeddiz a branch; falha ao preservar não impede o bloqueio) - Origem: AT-429 (08/10:
feature/task-86dade03sem commit,package.jsonesrcperdidos depois do bloqueio por orçamento)
RN-744 — O dev sabe que o shell é sh e começa vendo o próprio worktree
- Regra: a descrição da ferramenta
terminaldiz que o shell do container ésh(dash), não bash — sem brace expansion, sem[[ ]], sem arrays, semsource— e dá a forma que funciona. O kickoff de cada task do dev agent ganha uma segunda mensagem fixada com o RETRATO do worktree: a branch e os arquivos que já existem (rastreados + não rastreados não ignorados), com teto de 80 linhas e o total real quando corta (ADR 0060); é o servidor que monta, sem parâmetro do modelo. Pasta que o engine não alcança (modorunner) diz que o retrato está indisponível e manda listar peloterminal. Declarado e NÃO corrigido: o worktree do dev pode nascer antes do merge da PR de infra, então o que a Infra entregou por PR pode não estar nadevde onde ele parte. - Onde:
apps/engine/lib/engine/harness/tools/terminal.ex:12(spec),apps/engine/lib/engine/dev/worktree_manager.ex:117(retrato),apps/engine/lib/engine/dev/dev_agent_server.ex:657(retrato_do_worktree) - Teste:
apps/engine/test/engine/dev/worktree_manager_test.exs(retrato/2: branch e arquivos; pasta inalcançável diz indisponível; a descrição do terminal dizsh) - Origem: AT-444 (TP-01 de 08/10:
mkdir -p src/{a,b}no dash e o dev recriando o que já existia)
RN-745 — Na resposta cortada, só a chamada incompleta é recusada
- Regra: quando a resposta do modelo vem cortada pelo teto de saída
(
truncated: true, RN-737), só a ÚLTIMA chamada de ferramenta da resposta é tratada como incompleta e recusada com o erro nomeado (que agora diz "a última da resposta", que as anteriores foram executadas e pede "uma por chamada"). As chamadas anteriores da mesma resposta executam normalmente — medido no código: o stream OpenAI-compatível monta as chamadas em ordem, o corte cai na última, e oparseArgumentosda api devolve{}para o JSON partido. A chamada é identificada por nome e argumentos. Resposta cortada sem chamada conhecida continua recusando toda ferramenta (régua da RN-737). A descrição depropose_adrorienta um ADR por resposta. - Onde:
apps/engine/lib/engine/harness/resposta_cortada.ex:44(registrar),:64(incompleta?),apps/engine/lib/engine/harness/argumentos_de_ferramenta.ex:26(executar),apps/engine/lib/engine/harness/tools/propose_adr.ex:27(spec) - Teste:
apps/engine/test/engine/harness/resposta_cortada_test.exs("só a última chamada da resposta cortada é recusada; as completas executam"; "ferramenta de resposta cortada NÃO executa", sem chamada conhecida) - Origem: AT-430 (08/10: o Arquiteto mandou 5
propose_adrnuma resposta cortada e as cinco foram recusadas; 0 ADRs)
RN-746 — O fecho do Arquiteto diz o que gravou, e as escritas recusadas são contadas
- Regra: a linha factual "Gravado neste turno: …" da
RN-731 passa a contar as escritas do Arquiteto (ADR proposto,
mapa de módulos, atribuição de módulos, imagem decidida, diagrama C4,
roteamento para a infra, contrato de módulos, insight), a RFC do Staff e o
protótipo do UX Designer, e conta à parte as chamadas de ESCRITA recusadas
(
{:error, _}) do turno: "…; N chamadas de escrita recusadas". Turno que tentou escrever e não gravou nada fecha com "Gravado neste turno: nada; N chamadas de escrita recusadas." (en: "Recorded this turn: nothing; N write calls refused."). O Criativo passa a anotar a recusa também; Staff e UX Designer passam a anotar. Leitura nunca conta. O texto do modelo não é reescrito (RN-163: o agente não anuncia como feito o que o servidor recusou — quem lê vê o fato ao lado). Fora, declarado: Dev Lead (o plano suspende o turno, RN-284) e Infra Lead. - Onde:
apps/engine/lib/engine/agents/gravado_no_turno.ex:81(anotar),:96(descarregar),:141(classe),apps/engine/lib/engine/agents/arquiteto_server.ex:330(dispatch_tool) - Teste:
apps/engine/test/engine/agents/gravado_no_turno_test.exs("Arquiteto com as cinco propose_adr recusadas fecha com nada gravado"; "escritas do Arquiteto contadas, leitura e ferramenta desconhecida não") - Origem: AT-431 (08/10: com os 5
propose_adrok: false, o Arquiteto fechou com "✅ ADRs Propostas (5 decisões críticas)" e nenhuma linha factual)
RN-756 — A Anamnese só roda com interação de conteúdo técnico do sujeito; aprovar não conta
- Regra: revisa a contagem de "interação própria" da RN-680/RN-722. Só
contam para fazer alguém sujeito da rodada (a) evento
actor_kind: "user"do próprio membro cujo tipo está emtipos_de_evento_com_conteudo/0—chat.message(mensagem escrita por ele) echat.structured_question_answered— e (b) decisão de ação com motivo de recusa escrito (rejectionReasonnão vazio, uma correção). Aprovação, aceite de handoff, clique e qualquer outro evento de usuário NÃO contam: não são evidência de competência. O piso de 5 (RN-722), o teto diário e o desfecho sem sujeito (motivo no log;anamnese.run_skippedsó na rodada pedida à mão) não mudam, e a triagem segue LOCAL, antes do LLM e do RAG. - Código:
apps/engine/lib/engine/anamnese/elegibilidade.ex:61(avaliar),:102(recusa_com_motivo?),:108(tipos_de_evento_com_conteudo) - Teste:
apps/engine/test/engine/anamnese/elegibilidade_test.exs(só interação com conteúdo técnico conta (RN-756, AT-439): mensagem e resposta estruturada fazem sujeito; só aprovações, handoff e recusa sem motivo não fazem) - Origem: AT-439 (TP-01, 08/10, projeto
c1dc053d: duas rodadas durante a execução, US$ 0,20, ambasskip_proficiencycom "o usuário apenas aprovou propostas")
RN-757 — O terminal auto-aprovado espera o comando, e o teto do comando é 2 min
- Regra: a ação
terminalque a api auto-aprova é EXECUTADA dentro da mesma requisição dopropose_action(api → engine/internal/actions/execute→ apicontainer-exec→ broker), e o engine espera essa requisição porTERMINAL_ACTION_TIMEOUT_MS+ 120 s — não pelo default de 15 s doReq. O teto do comando (TERMINAL_ACTION_TIMEOUT_MS) passa de 15 s para 120 s nos três composes e noruntime.exs. A cadeia fica broker (T) < api → broker (T + até 45 s, RN-604) < engine →container-exec(T + 90 s) < engine →propose_action(T + 120 s), e com T = 120 s o salto de fora (240 s) segue abaixo dos 300 s de cabeçalhos dofetchdo Node. Se a espera ainda estourar, o modelo recebe texto NOMEADO — o teto em segundos, que o processo pode seguir rodando e a sugestão denohup … &com log —, nunca%Req.TransportError{reason: :timeout}cru. Exec assíncrono/streaming foi considerado e não feito: a menor mudança que faznpm installcomum caber é o teto. - Onde:
apps/engine/lib/engine/sessions/engine_api_client.ex:2006(opcoes_do_propose_action) eapps/engine/lib/engine/harness/hooks/action_pipeline.ex:85(resultado_de_teto_do_terminal) - Teste:
apps/engine/test/engine/sessions/engine_api_client_propose_terminal_test.exs(api falsa que demora 16 s ainda é esperada; ordem dos tetos abaixo de 300 s; o texto de estouro nomeia o teto) - Origem: AT-440 (TP-01 de 08/10:
npm install 2>&1 | tail -50num containernode:22-bookworm-slimvoltou "falha no pipeline: %Req.TransportError{reason: :timeout}"; medido nesta máquina, o mesmonpm installdebcrypt+sqlite3+expresslevou 5,3 s com a imagem em cache e prebuilds disponíveis — a cadeia, não o comando, cortava em 15 s)
RN-760 — A próxima task do dev agent parte da última branch dele ainda não mergeada
- Regra: generaliza a RN-743. Ao terminar uma task — aprovada
nos gates (
nextAction: "done") ou bloqueada por QUALQUER motivo, inclusive o "ciclo de correção esgotado (gate)" que a api decide e chega ao engine só comogate_resolved—, o dev agent guarda a branch dela e a próxima task do MESMO agente (logo, do mesmo módulo) nasce dessa branch, e não dedev. O que sobrou sem commit no worktree é preservado antes, como na RN-743; o ponteiro gravado peloblock_task/4não é sobrescrito. Quando a branch anterior já é ancestral dadevlocal (mergeada), a próxima parte dadev, que já a contém. QUANDO a fila anda não muda: a próxima task continua sendo reivindicada ao fim dos gates, antes do merge do usuário (decisão do dono). Declarado: merge por squash num remoto não deixa a branch ancestral dadev, então a próxima segue partindo dela; o ponteiro continua só em memória, e no modorunnera checagem de merge não roda. - Onde:
apps/engine/lib/engine/dev/agent_io.ex:352(levar_branch_para_a_proxima),:333(criar_worktree),apps/engine/lib/engine/dev/worktree_manager.ex:553(ja_mergeada_volta_ao_trabalho) - Teste:
apps/engine/test/engine/dev/bloqueio_preserva_trabalho_test.exs(levar_branch_para_a_proxima/1 e criar_worktree/2: aprovada leva a branch para a próxima; o ponteiro do bloqueio não é sobrescrito),apps/engine/test/engine/dev/worktree_manager_test.exs(add_worktree/4 com a branch da task anterior: não mergeada parte dela, mergeada parte dadev) - Origem: AT-447 (TP-01 de 09/10: tasks
762833bde5ebad64b— a segunda nasceu dedevsem a rota de login da primeira, e o QA reprovou)
RN-763 — Encerrar a sessão de execução para os dev agents e os gates, e "Parar execução" na aba Executores
- Regra: o fechamento da sessão (
session.closed/closed_abnormally, peloSessionLifecycleWorker, o ponto por onde todo fechamento passa) PARA os dev agents dela em todos os nós e os gates (QA lead e SecOps) do projeto — a régua que já valia para os conversacionais (RN-581). O processo é derrubado na hora, sem gravar o turno em curso, e oEngine.Dev.Monitoré avisado antes (esquecer/1): a linha durável sai e o agente NÃO é religado (RN-742). Para cada dev com task em curso (working,awaiting_approvalouawaiting_gate), o trabalho do worktree vira commit na branch da task (RN-743) e a task é BLOQUEADA com motivo "sessão de execução encerrada", origempolitica, e o diagnóstico dizendo onde ficou o trabalho — volta à fila quando um humano a libera, nunca perdida. As linhas degate_statesda sessão saem, para oGateRescuernão reabrir ciclo da sessão encerrada. A aba Executores ganha "Parar execução" (papeldeveloper, o de encerrar sessão), cujo mecanismo é o MESMO fechamento (closing→closed), sem estado novo; a confirmação diz antes do clique o que fica: as tarefas em curso (com a contagem), as PRs abertas, as aprovações pendentes e que a sessão não reabre. Declarado: ação pendente de dev agent continua pendente; PR aberta continua aberta; sessão comexecution.activatednão reabre (RN-650), então retomar é ativar a execução numa sessão nova; a contagem de tarefas da confirmação vem da janela de eventos da aba. A confirmação no "Encerrar" da barra da sessão é de outra atividade (AT-452). - Onde:
apps/engine/lib/engine/dev/encerramento_da_sessao.ex:40(parar_da_sessao),:74(parar_processos),:104(devolver_task),apps/engine/lib/engine/dev/monitor.ex:34(esquecer),apps/engine/lib/engine/workers/session_lifecycle_worker.ex:94(parar_execucao),apps/web/src/components/PararExecucao.tsx - Teste:
apps/engine/test/engine/dev/encerramento_da_sessao_test.exs(o dev morre sem ser religado, a linha sai, a task é bloqueada com origempolitica; sessão sem dev é no-op e o dev de outra sessão fica de pé),apps/web/src/components/PararExecucao.test.tsx(a confirmação diz o que fica e fechaclosing→closed; a recusa da api fica na confirmação; sem papel o botão fica inerte com o motivo) - Origem: AT-456 (TP-01 de 09/10: com a sessão de execução encerrada, o
dev-encurtador-apibloqueou a tarefa 3, pegou a 4 e abriu PR; o único freio foi parar o container)
RN-765 — O gate de QA julga a TAREFA, e RF de tarefa irmã é observação
-
Regra: a QA-estratégia (plano de teste, RN-674) e a QA-automação recebem, além da história, o RECORTE da tarefa: título e descrição, as regras de negócio da história e as OUTRAS tarefas da mesma história com o status (
siblingTasksemGET .../dev-context). O veredito é sobre o que é DESTA tarefa: requisito que pertence a uma tarefa irmã (feita ou pendente) não reprova — vai aoresumocomo observação, fora deitens, e não impedeapproved. A régua é de PROMPT, não de código: oemit_qa_verdictcontinua exigindo só a suite verde para aprovar. Desde a AT-479, quando a descrição da tarefa ou uma regra quantifica POR item ("por X", "cada", "de todos os" — "por exemplo"/"por padrão" não contam), o recorte ganha a seção "Forma da resposta": conferir que a resposta traz o valor de CADA item, não só o total (o painel aprovado com só{totalCliques}sobre "a contagem… de todos os links"), e que a recusa de entrada nomeia o campo — esta última é OBSERVAÇÃO noresumo, fora deitens, salvo regra que a exija. Continua régua de prompt: nenhuma reprovação nova no código. -
Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:18(texto),apps/engine/lib/engine/gates/recorte_da_tarefa.ex:82(secao_da_forma),apps/api/src/application/use-cases/execution/get-dev-task-context.use-case.ts:220(tarefasIrmas) eapps/engine/lib/engine/gates/qa_automacao_agent.ex:170apps/engine/lib/engine/gates/recorte_da_tarefa.ex:82(secao_da_forma),apps/api/src/application/use-cases/execution/get-dev-task-context.use-case.ts:220(tarefasIrmas) eapps/engine/lib/engine/gates/qa_automacao_agent.ex:225(com_o_recorte) -
Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs(desde a AT-479 também "regra que quantifica por item pede a conferência da forma da resposta" e o caso sem quantificador) eapps/api/test/application/use-cases/execution/get-dev-task-context.use-case.spec.ts(as irmãs sem a própria tarefa). O golden-set do QA (ADR 0168) NÃO ganhou caso: ele roda o modelo de verdade, e provar o veredito sem chamada paga não é possível — declarado. -
Origem: AT-448 (TP-01 de 09/10: o gate de QA da tarefa "Modelar tabela de usuários e hash bcrypt" reprovou três vezes por "RF01: login HTTP com JWT não existe nesta entrega", que é de outra tarefa, e a tarefa bloqueou;
artifact.qa_verdictdas tasks42bea901e762833bd)
RN-772 — O Arquiteto lê o status ATUAL das ADRs que propôs
- Regra: o Arquiteto tem a leitura
listar_adrs_propostas: as açõesopen_adr_prdo PROJETO, lidas agora deproposed_actions(mesmo Postgres, sem HTTP no laço), com o status de cada uma — pendente, aprovada, aprovada automaticamente, executada (com a URL da PR), recusada (com o motivo) ou falhada. Contida (ADR 0060, RN-164): sem parâmetro, escopo fechado no projeto do contexto, uma contagem e uma consulta por chamada, as 20 mais recentes, e o total real dito quando corta. Falha de leitura étool.resultok: falsecom o motivo, nunca texto vazio. Desde a RN-784 ela ESTÁ no piso do Jev. - Onde:
apps/engine/lib/engine/harness/tools/listar_adrs_propostas.ex:37(run),:40(ler),apps/engine/lib/engine/agents/arquiteto_server.ex:629(ListarAdrsPropostas) - Teste:
apps/engine/test/engine/harness/tools/listar_adrs_propostas_test.exs(aprovada aparece aprovada, executada com a PR, recusada com o motivo, só as do projeto; teto com o total real; sem proposta; leitura que falha vira erro nomeado) - Origem: AT-454 (TP-01 de 09/10, sessão
771131f4: com as 3 ADRs aprovadas, o fecho seguinte disse "As 3 ADRs seguem pendentes de aprovação")
RN-773 — O Arquiteto lê o backlog, e consulta antes de afirmar estado
- Regra: o Arquiteto ganha a mesma
listar_backlogdo PO (RN-164), com a contagem de tarefas por história. O kickoff dele e o resultado dopropose_adrdizem que o status das ADRs e as tarefas mudam fora da conversa e mandam consultar as duas leituras antes de afirmá-los, em vez do retrato de um resultado anterior. Nada de fluxo do time é narrado aqui (RN-711). - Onde:
apps/engine/lib/engine/agents/arquiteto_server.ex:630(ListarBacklog),apps/engine/lib/engine/harness/tools/propose_adr.ex:108(propor) - Teste:
apps/engine/test/engine/harness/tools/listar_backlog_test.exs(a contagem de tarefas por história),apps/engine/test/engine/agents/arquiteto_server_test.exs(as duas leituras estão no catálogo do Arquiteto) - Origem: AT-454 (TP-01 de 09/10: com 17 tarefas no backlog, o Arquiteto disse "faltam as tarefas" e "não consigo verificá-las daqui")
RN-774 — A primeira tarefa de cada módulo tem o dobro do teto por tarefa
A primeira tarefa de um módulo monta o esqueleto inteiro (manifesto, app,
banco, testes) e custa mais que as seguintes: no TP-01 de 08/10 ela gastou
US$ 0,53 com o Haiku 4.5 e foi bloqueada pelo teto fixo de US$ 0,50
(taskBudgetMicros: 500000). Por decisão do dono (09/10), ela ganha
2× o teto por tarefa (US$ 1,00 com o padrão); as demais seguem o teto
configurado. O multiplicador é constante nomeada no engine, não configuração.
"Primeira" é medida pelo log, nunca pelo estado da tarefa: é a tarefa cujo
backlog.task_claimed é o MAIS ANTIGO do projeto para aquele módulo. Bloquear
zera assigned_to e reiniciar o engine perde a memória do processo; o log não
muda, então a mesma tarefa reivindicada de novo depois de liberada continua a
primeira, e uma correção pedida por gate também recebe o 2×. A api responde
task.primeiraDoModulo no contexto do dev (false sem module, e false sem
claim nenhum do módulo — o teto normal é o conservador), e o teto EFETIVO é
resolvido num ponto só do engine. O bloqueio por orçamento diz o teto que valeu
para aquela tarefa ("teto da primeira tarefa do módulo: 1000000 micro-USD,
US$ 1.00"). Os gates de QA seguem com o task_budget_micros base — fora do
recorte do card.
- Onde:
apps/engine/lib/engine/dev/teto_da_tarefa.ex:32(efetivo);apps/engine/lib/engine/dev/dev_agent_server.ex:733(diagnostico_de_orcamento);apps/api/src/application/use-cases/execution/get-dev-task-context.use-case.ts:207(ehPrimeiraTarefaDoModulo) - Teste:
apps/engine/test/engine/dev/dev_agent_server_test.exs(describe "RN-774": a primeira com US$ 0,53 não bloqueia, a segunda do mesmo módulo bloqueia — caso de falha —, a primeira de outro módulo também ganha 2×);apps/engine/test/engine/dev/teto_da_tarefa_test.exs;apps/api/test/application/use-cases/execution/get-dev-task-context.use-case.spec.ts(describe "RN-774") - Origem: AT-433
RN-778 — O dev agent órfão fecha por evento novo no boot
- Regra: no boot, para cada sessão não terminal, o dev agent cujo último
dev.*na sessão é de espera (dev.working,dev.blocked,dev.idle_trippede os demais que oGetSessionPendingWorkUseCaseconta) e que NÃO tem linha emdev_agent_statesNESTA sessão ganhadev.errorcom origeminfra(reason: dev_agent_sem_processo) seguido dedev.idle, que o tira da régua do trabalho pendente (RN-064). É o molde da RN-586 para o vocabuláriodev.*: nunca reexecuta, nunca reescreve evento, e a linha durável é a prova de dono — com ela oDevRehydratorreliga o agente, e fechá-lo seria matar um agente saudável. Não toca a tarefa: tarefa bloqueada ou com PR esperando o merge continua segurando a sessão de execução vigente pela RN-776 (AT-457), que tem dono humano. - Onde:
apps/engine/lib/engine/dev/dev_orfao.ex:49(varrer),apps/engine/lib/engine/sessions/rehydrator.ex:81(varrer) - Teste:
apps/engine/test/engine/dev/dev_orfao_test.exs(o órfão fecha comdev.errorinfra +dev.idle; o que tem linha durável e o que já estáidlenão; leitura que falha não fecha nada) - Origem: AT-465 (TP-01 de 09/10: a sessão
63a4e1aareagendada pelo heartbeat a cada 30 s por umdev.workingsem processo)
Citada pela RN-922 (ADR 0226), sem mudar: o dev sem linha durável em dev_agent_states continua só fechado, porque não há tarefa a retomar.
RN-779 — O worktree da task (re)pegada parte da dev ATUAL e a integra
- Regra: antes de criar o worktree de uma task (primeira vez ou retomada),
o engine faz
git fetch originno working tree do projeto (serializado por projeto) — é no remoto que as PRs são mergeadas, e adevlocal só andava no checkout da inicialização. A ponta dadevéorigin/devquando a local ficou para trás. A task nasce: (1) da branch da task anterior não mergeada (RN-760), (2) da PRÓPRIA branch quando ela tem trabalho que adevnão contém — a task retomada num processo novo, que perdeu o ponteiro em memória (RN-743) —, ou (3) da ponta atual dadev. Nos casos (1) e (2) o worktree INTEGRA a ponta dadev(merge --no-ff, identidade<agente>[bot]) antes do primeiro passo. Conflito nessa integração vai para o agente (RN-1010); só o que o agente não resolve é recusa NOMEADA ("conflito ao integrar adevatual", com os arquivos): o merge é abortado, a branch de origem fica intacta e a task bloqueia com motivo próprio e origemcodigo.fetchque falha bloqueia a task com o motivo do git, nunca cai nadevvelha. A readoção depois de conflito de merge (RN-715) INTEGRA amainatual pelo sistema, como o claim (AT-798, RN-1010). Medido no TP-01: a AT-447 DISPAROU no 1º claim (reflog:Created from feature/task-b3cc92b8); quem perdeu a base foi o reclaim, que redefiniu a branch para adevLOCAL parada no merge da infra (Reset to dev). Declarado: no modorunnernada disso roda (o git é da máquina do usuário). - Onde:
apps/engine/lib/engine/dev/worktree_manager.ex:156(atualizar_remoto),:321(add_worktree),:399(ponta_do_trabalho),:462(retomada),:472(integrar_trabalho) - Teste:
apps/engine/test/engine/dev/worktree_manager_test.exs(integração da dev atual (RN-779): retomada com branch antiga parte da dev nova do remoto; trabalho preservado integra a dev nova; anterior emawaiting_useré a base com a dev integrada; conflito é recusa nomeada) - Origem: AT-458 (TP-01 de 09/10:
feature/task-c80daf6cnasceu de1fa2241sem as 12 tarefas mergeadas depois, e o merge conflitou)
RN-780 — Resposta só de raciocínio cortada pelo teto é corte, não desistência
- Regra: no
ToolLoop(caminho de todo agente de execução e de gate), a resposta sem texto e sem chamada de ferramenta que a api marcatruncated: true(ofinish_reason: lengthda RN-737) é o raciocínio que comeu o teto de saída. A volta é retentada UMA vez, sem a resposta vazia no histórico e contando iteração — o teto de iterações e o orçamento da tarefa continuam valendo. Se repetir, o laço para marcandocorte_pelo_teto, e o dev agent bloqueia a task com o motivo "resposta só de raciocínio, cortada pelo teto" e origemmodelo, em vez de "parou sem concluir nem reportar bloqueio". Uma chamada de ferramenta no meio zera a retentativa. Declarado: o engine recebe da apitruncatedeusage.outputTokens, mas NÃO osreasoningTokens(ficam só emtoken_usage), então a resposta vazia que bate no teto semfinish_reason: lengthnão é reconhecida. - Onde:
apps/engine/lib/engine/harness/tool_loop.ex:256(parada_sem_chamada),apps/engine/lib/engine/dev/dev_agent_server.ex:1218(motivo_da_parada),:1237(stop_diagnosis) - Teste:
apps/engine/test/engine/harness/tool_loop_test.exs(corte só de raciocínio: retentada uma vez e segue; repetido para comcorte_pelo_teto),apps/engine/test/engine/dev/dev_agent_server_test.exs(resposta só de raciocínio cortada pelo teto bloqueia com motivo nomeado) - Origem: AT-459 (TP-01 de 09/10: a tarefa
c80daf6cbloqueou em 40 s com 8.192 tokens de saída, todos de raciocínio)
RN-781 — A mensagem de commit do dev não grava pendência que é do sistema
- Regra: o resumo do
report_doneé a mensagem de commit do dev agent, e commit, push e PR são feitos pelo sistema depois dele. Antes de propor ogit_commit, o engine corta do marcador de pendência ("Pendente", "Pendência", "Pending") ao fim quando o resto fala de commit, push ou PR, e tira o parêntese "(ainda sem commit…)"; pendência que não fala disso fica. A descrição do camposummarydiz que ele vira a mensagem de commit e que não se narra commit, push nem PR. Resumo que fica vazio cai na mensagem padrão<agente>: <task>. - Onde:
apps/engine/lib/engine/dev/agent_io.ex:407(mensagem_de_commit),:382(propose_commit),apps/engine/lib/engine/dev/tools/report_done.ex:27(summary) - Teste:
apps/engine/test/engine/dev/mensagem_de_commit_test.exs(corta a narração; tira o parêntese; pendência alheia ao sistema fica) - Origem: AT-464 item 2 (TP-01 de 09/10:
git log feature/task-9d6dcd53com "Pendente: commit… as ações git_commit, git_push e pr_open não estavam disponíveis neste passo")
RN-787 — O gate conta o diff da PR contra a dev da origem, e o parecer de QA grava lista
- Regra: (1) o diff que o QA e o SecOps julgam (
dev...HEAD, RN-664) usa adevMAIS NOVA que o working tree enxerga: com origem local (caminho no disco) o gate faz antesgit fetch origin dev, melhor esforço, e entreorigin/deve adevlocal vale a que contém a outra. Adevlocal do clone não anda quando a PR é mergeada na origem, e o parecer do SecOps contava o acumulado das tarefas já mergeadas. Com origem remota não há fetch no gate (a credencial é doWorkspace) — declarado. O semgrep e o gitleaks seguem varrendo o worktree inteiro, não só o diff: estreitar o que o scanner vê muda o que o gate reprova, e isso fica para o dono. (2)itensecoverageMatrixdo parecer de QA chegam aoartifact.qa_verdictsempre como LISTA: string JSON de lista é decodificada (o molde da RN-719), e string que não é lista vira[]. - Onde:
apps/engine/lib/engine/gates/diff.ex:44(base_mais_nova),apps/engine/lib/engine/gates/hooks/termination.ex:54(lista) - Teste:
apps/engine/test/engine/gates/diff_test.exs(a tarefa B nascida da A, com a A mergeada na origem: o diff é só B) eapps/engine/test/engine/gates/hooks/termination_test.exs(string JSON vira lista; lista intacta; string inválida vira[]) - Origem: AT-464 itens 1 e 3 (TP-01 de 09/10, sessão
80e3ce09: "17 arquivo(s) alterado(s) nesta PR" numa PR de 3 arquivos contra adev, ecoverageMatrix/itensgravados como"[]"nos últimos pareceres)
RN-790 — O README de como subir o entregável é critério do DoD que o PO escreve
- Regra: a instrução de kickoff do PO manda incluir, no DoD da história que
cria a entrada da aplicação (servidor, CLI, configuração ou persistência) — ou
numa história própria —, o critério "README diz como instalar, configurar
(variáveis de ambiente obrigatórias e opcionais, com o padrão) e subir a
aplicação, e como criar o primeiro acesso, quando houver". O dev agent cumpre
o DoD da história, e o gate o julga por ele. Régua de PROMPT, como a
RN-765: nenhuma ferramenta recusa história sem esse critério. A
outra variante — o dev agent atualizar o README em toda tarefa que mexe em
configuração/entrada — NÃO foi feita: exigiria classificar a tarefa no
AmbienteDoAgenteou noreport_done, regra nova de código — declarado. - Onde:
apps/engine/lib/engine/agents/po_server.ex:605(kickoff_instruction) - Teste:
apps/engine/test/engine/agents/po_server_test.exs:438(a instrução de kickoff traz o critério do README) - Origem: AT-466 (TP-01 de 09/10: o
encurtador-apisaiu com um README só comnpm test, semJWT_SECRETobrigatório,DB_PATH/PORT,npm startnem como criar o primeiro membro)
RN-792 — Ao declarar o contrato, o Arquiteto recebe as rotas das histórias que o contrato não declara
- Regra: depois de
declare_module_contractsgravar a versão (RN-684), a ferramenta lê o backlog do projeto e extrai, de forma determinística e sem LLM, todoMÉTODO /caminho(GET, POST, PUT, PATCH, DELETE) do título, descrição, RF e DoD das histórias não arquivadas, e o mesmo padrão dasassinaturas do contrato. Rota citada por história e ausente da UNIÃO das rotas do contrato (a história do front cita a rota do back) volta no resultado como DIVERGÊNCIA, com o que o contrato declara no mesmo método — entrada do laço (RN-163), para o Arquiteto corrigir o contrato ou registrar a decisão. Parâmetro (:id,{id}), barra final e query string são normalizados; teto de 10 linhas, com o total. A leitura do backlog falhar NÃO recusa a declaração (já gravada): o resultado diz que não conferiu. Fica de fora, declarado: rota que só o contrato declara (não é divergência), história editada DEPOIS da declaração (só é conferida na próxima), e o contrato acompanhar a rota que o DEV renomeou (o QA nomeia a divergência no parecer, RN-786, mas nada a leva de volta ao Arquiteto). - Onde:
apps/engine/lib/engine/harness/rotas_do_contrato.ex:37(divergencias),apps/engine/lib/engine/harness/tools/declare_module_contracts.ex:125(conferencia_com_historias) - Teste:
apps/engine/test/engine/harness/tools/declare_module_contracts_test.exs(GET /painelna história ×GET /panelno contrato acusa; rota igual com parâmetro e barra normalizados não acusa; backlog ilegível diz que não conferiu) - Origem: AT-468 (TP-01 de 09/10: a história pedia
GET /painel, o contrato declarouGET /panel, e ninguém acusou na arquitetura)
RN-796 — O item do gitleaks diz a regra e o trecho com o segredo mascarado
- Regra: o item que o
gitleakspõe no parecer do SecOps leva, além do caminho e da linha, a regra que acusou (RuleID) e o trecho acusado (Match) com o segredo (Secret) trocado por***— nunca o segredo em claro. Sem oSecretpara mascarar, o trecho não entra. E o ambiente do agente de execução (RN-706) diz em uma linha que senha, token ou chave de teste são gerados em tempo de execução, nunca literais. Sem allowlist nova. - Onde:
apps/engine/lib/engine/actions/gitleaks_detector.ex:121(mensagem),apps/engine/lib/engine/harness/ambiente_do_agente.ex:102(montar) - Teste:
apps/engine/test/engine/actions/gitleaks_relatorio_test.exs,apps/engine/test/engine/harness/ambiente_do_agente_test.exs - Origem: AT-470
RN-797 — A task cuja branch não tem diff contra a dev fecha como "já na dev"
- Regra: no
report_done, antes de propor commit, push e PR, o dev agent pergunta se a branch do worktree traz algo que adevnão tem: árvore limpa egit diff --quiet <ponta da dev>...HEADsem diferença. É o caso da task reintegrada (RN-779) cujo código já entrou por outra PR. Sem diff, a task fecha comodonecom o eventodev.task_already_in_dev(agente, task, branch, motivo) — sem commit, sem PR, sem gates e sem clique. Qualquer dúvida (pasta que o engine não alcança, como no modorunner; git que falha; ref dadevausente) é "tem diff": a task segue para os gates, nunca fecha sozinha. Mede DIFF e não ancestralidade, então cobre também o merge por squash que a RN-760 declara não ver. Declarado: a PR antiga da branch, se existir, não é fechada no provider. - Onde:
apps/engine/lib/engine/dev/worktree_manager.ex:418(sem_diff_contra_a_dev?),apps/engine/lib/engine/dev/dev_agent_server.ex:1150(ja_na_dev) - Teste:
apps/engine/test/engine/dev/tarefa_ja_na_dev_test.exs - Origem: AT-474
RN-798 — O QA confere os comandos do README tocado pela entrega contra os scripts
- Regra: quando o diff da entrega contra a
devtoca um README, o QA confere, sem LLM, que todonpm run <s>,npm test,npm start,pnpm [run] <s>eyarn [run] <s>citado nele existe emscriptsdopackage.jsonda mesma pasta (senão o da raiz do worktree). A conferência entra na consolidação como mais um parecer ("Conferência do README"): divergência échanges_requestedcom um item por comando, nomeando o script que falta; sem divergência, aprova. Sem README no diff, diff que falhou oupackage.jsoninalcançável, nada é afirmado. De FORA, declarado: subcomando embutido do gerenciador (pnpm install,pnpm dlx…), comando com flag antes do script (pnpm --filter x test), flags de CLI do próprio projeto e comando que não é de Node. - Onde:
apps/engine/lib/engine/gates/conferencia_do_readme.ex:28(conferir),apps/engine/lib/engine/gates/qa_lead_server.ex:818(conferencia_do_readme) - Teste:
apps/engine/test/engine/gates/conferencia_do_readme_test.exs - Origem: AT-475
RN-805 — O plano de teste gravado é o que a ferramenta aceitou
- Regra:
emit_plano_de_testevalida os argumentos JÁ normalizados (RN-719:criteriosExecutaveisem string JSON vira lista), e o hook que encerra o laço da QA-estratégia lêcriteriosExecutaveispela MESMA normalização (Termination.lista/1, a da RN-787). Antes, a tool dizia "plano registrado: 9 critérios", o hook levava a string crua, o artefato era recusado (:criterios_vazios), o agente devolvia{:ok, plano}mesmo assim e a Automação caía noEnumda string — o QA Lead morria e o gate ficava parado até o resgate (18 min no uso real). Artefato recusado agora é falha NOMEADA da QA-estratégia (agent.error, origemcodigo) e a revisão segue sem plano, como já seguia quando o plano falha; a Automação também normaliza os critérios que recebe. - Onde:
apps/engine/lib/engine/gates/hooks/termination_plano_de_teste.ex:23(criterios_executaveis),apps/engine/lib/engine/gates/qa_estrategia_agent.ex:231(emit_returning) - Teste:
apps/engine/test/engine/gates/qa_estrategia_agent_test.exs("critérios em string JSON viram lista: o plano é gravado e devolvido como lista") - Origem: AT-478 (TP-01 de 10/10)
RN-806 — Um ciclo de QA por tarefa: pedido anterior ao veredito é descartado
- Regra: o QA Lead roda UM ciclo por task. Pedido
{:run, task}para a task cujo ciclo está suspenso esperando decisão é ignorado, e ao fim de um ciclo (veredito ou bloqueio) todo{:run, task}que esperava na caixa do processo é descartado — foi pedido antes de o veredito existir, e a correção legítima do dev só nasce depois dele. No uso real o resgate (RN-722) disparou o ciclo duas vezes no mesmo instante, e o segundo, rodando depois do aprovado, gravou um parecer contrário sobre a tarefa já mergeada. De FORA, declarado: duas réplicas do engine (a caixa é por processo, a mesma ressalva doGateRescuer) e a causa da dupla varredura do resgate, que não foi corrigida aqui. - Onde:
apps/engine/lib/engine/gates/qa_lead_server.ex:106(handle_cast),apps/engine/lib/engine/gates/qa_lead_server.ex:157(descartar_pedidos_obsoletos) - Teste:
apps/engine/test/engine/gates/qa_lead_server_test.exs("pedido repetido que esperava na caixa é descartado ao fim do ciclo"; "pedido para a task com ciclo suspenso não abre um segundo") - Origem: AT-478 (TP-01 de 10/10)
RN-810 — Falha de rede passageira do provider é retentada, e o bloqueio dela é infra sem contar no disjuntor
- Regra: quando a chamada ao modelo volta com erro de REDE do provider —
o
codeconnection/timeoutque a api normaliza (ADR 0041) ou, sem código, o texto do transporte (EAI_AGAIN,ECONNRESET,ETIMEDOUT,ECONNREFUSED,ENOTFOUND) —, oToolLoopa retenta até duas vezes, com espera de 1 s e 3 s, gravandotoolloop.network_retrya cada uma. Vale para todo consumidor doToolLoop(dev agents e subagentes de gate). Esgotado o teto, a origem do desfecho éinfra, nuncacodigo, e o bloqueio da task do dev agent por essa causa NÃO incrementa o contador de bloqueios seguidos (RN-047): o agente segue reivindicando. No uso real umgetaddrinfo EAI_AGAIN openrouter.aide 17 s bloqueou duas tarefas comocodigoe desarmou o dev. A correção pós-gate segue a mesma régua (AT-621, RN-864). - Onde:
apps/engine/lib/engine/harness/tool_loop.ex:285(llm_turn_com_retentativa),apps/engine/lib/engine/agents/falha_de_turno.ex:119(falha_de_rede?),apps/engine/lib/engine/dev/dev_agent_server.ex:1047(bloquear_parada),apps/engine/lib/engine/dev/agent_io.ex:228(finish_task) - Teste:
apps/engine/test/engine/harness/tool_loop_test.exs("falha de rede do provider: retenta e segue quando a rede volta"; "falha de rede que persiste esgota o teto"),apps/engine/test/engine/dev/dev_agent_server_test.exs("erro de rede do provider: origem infra e o disjuntor não conta"),apps/engine/test/engine/agents/falha_de_turno_test.exs - Origem: AT-484 (TP-01 de 10/10)
RN-811 — O "já na dev" compara com a dev do remoto atualizada
- Regra: antes da checagem "já na dev" (RN-797) no
report_done, o dev agent atualiza adevdo remoto pelo MESMOfetchque a RN-779 faz no claim, serializado por projeto. Sem ele a comparação usava adevdo dia em que o worktree nasceu: a branch da task cujos commits já tinham entrado nadevremota pela PR de outra task passou por QA e SecOps com 0 arquivos.fetchque falha não decide nada — compara com o que há, e na dúvida a task segue para os gates. - Onde:
apps/engine/lib/engine/dev/worktree_manager.ex:418(sem_diff_contra_a_dev?),apps/engine/lib/engine/dev/worktree_manager.ex:438(sem_diff_atualizado?),apps/engine/lib/engine/dev/dev_agent_server.ex:931(handle_outcome) - Teste:
apps/engine/test/engine/dev/tarefa_ja_na_dev_test.exs("caminho feliz: com a dev atualizada, a branch não tem diff"; "falha: remoto inalcançável não fecha a task sozinha") - Origem: AT-486 (TP-01 de 10/10)
RN-813 — Tarefa que toca o ponto de entrada exige evidência de que ele sobe
- Regra: extensão do recorte da RN-765. Quando o título ou a
descrição da tarefa toca o ponto de entrada, a fiação (rotas, migração,
registro) ou o README de execução, o recorte do gate de QA ganha a seção
"Ponto de entrada": exigir evidência de que o entrypoint REAL sobe (o
start/maindo projeto rodado, ou um teste que importe o entrypoint real, não um app montado no próprio teste) e responde numa rota ou comando. Sem essa evidência, a lacuna vai paraitens. É régua de PROMPT: o contrato externo dos gates (apps/api/config/gates.yml) e o código doemit_qa_verdictnão mudam, e nenhum gate novo nasce. Tarefa que não toca nada disso não ganha a seção. - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:121(secao_do_ponto_de_entrada) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("tarefa que toca a fiação exige evidência do ponto de entrada real" e "tarefa que não toca entrypoint nem fiação não ganha a seção") - Origem: AT-483 (TP-01 de 10/10: o
server.tsnunca chamava a migração da tabelalinkse/:codesombreavaGET /links; os 53 testes passavam porque cada um montava o próprio banco e app)
RN-814 — O parecer do QA lista todas as lacunas numa volta
- Regra: extensão do recorte da RN-765: o gate de QA lista em
itensTODAS as lacunas da tarefa que achar na volta, de uma vez, nunca uma por volta — cada volta é uma correção paga e um passo do teto do ciclo. O prompt anterior só mandava listar "quais regras faltam", e lacuna que não era regra descoberta ficava para a volta seguinte. Régua de prompt. - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("o recorte pede todas as lacunas numa volta só") - Origem: AT-487 (TP-01 de 10/10, tarefa
c7ced5a2: duas reprovações por duas lacunas que já existiam na entrega original)
RN-817 — O QA roda a suíte em estado limpo, e teste pulado não é verde
- Regra: extensão do recorte da RN-765, para toda tarefa. Antes
de rodar a suíte, o gate de QA encerra o que o PRÓPRIO projeto deixou de pé
(servidor do
startescutando numa porta do projeto), só processos daquela pasta, nunca de fora. Teste que sobe processo deve encerrar o GRUPO (ou subirnodedireto, não vianpm); o que não encerra trava a suíte num clone limpo e é lacuna emitens. Teste que se PULOU por ambiente (porta ocupada, ferramenta ausente) não conta como verde: vai aoresumocomo observação. É instrução do recorte, não passo do harness: quem roda a suíte é o próprio QA peloterminal, que já passa pela política e pelo roteamento de container dos três modos — umpkillno harness precisaria de um segundo caminho de execução por modo. Régua de prompt; o contrato dos gates não muda. - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:178(secao_do_ambiente_limpo) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-817: pede ambiente limpo, encerrar o grupo e pulado como observação") - Origem: AT-496 (TP-01 de 10/10:
npm testtravava num clone limpo — o teste matava onpme onodeneto ficava na 3000 —, e no container o QA viu exit 0 porque o teste se pulava com a porta ocupada por um servidor órfão)
RN-818 — A evidência do ponto de entrada é a que o ambiente do dev produz
- Regra: extensão da RN-813. A seção "Ponto de entrada" diz o
que CONTA como evidência no ambiente do dev — subir o processo do
entrypoint na pasta do projeto, rodar o script no container do projeto,
bater numa rota do processo de pé — e o que NÃO cabe ali: construir imagem
Docker,
docker compose, rede externa. Exigência desse segundo tipo nunca vai paraitens; vai aoresumocomo observação. Régua de prompt. - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:121(secao_do_ponto_de_entrada) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-818: com ponto de entrada diz o que conta e o que não cabe" e "RN-818: sem ponto de entrada não traz a régua de evidência") - Origem: AT-492 (TP-01 de 10/10, task
c708f8b2: a 4ª volta exigiu construir a imagem a partir do Dockerfile, o container do projeto não tem Docker, e o ciclo esgotou em bloqueiopolitica)
RN-819 — O QA varre regra por regra antes de emitir
- Regra: extensão da RN-814. Além de listar todas as lacunas, o recorte pede uma VARREDURA POR REGRA antes de emitir: percorrer cada regra de negócio e cada critério da tarefa, um por um, apontando onde está a prova ou que ela falta, e só emitir depois de passar por todas. "Liste todas" sozinho não segurou lacuna pré-existente aparecendo na volta seguinte. Régua de prompt.
- Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-819: pede varredura por regra antes de emitir") - Origem: AT-490 (TP-01 de 10/10: tarefas 2 e 11 trouxeram na 2ª volta lacuna que já estava na entrega)
RN-823 — O ciclo de gate que o reinício do engine matou é retomado no boot, e o fio diz isso
- Regra: no BOOT do engine, a linha
in_progressdegate_statesRECENTE (abaixo degate_rescue_stale_after_seconds, 15 min, e por isso invisível para o tick) sem processo vivo NESTE nó nem nos OUTROS nós do cluster é órfã do reinício, não um ciclo lento: o boot a retoma na hora (Dispatcher.run_qa/run_secops), sem esperar o limiar. "Vivo" é a régua da RN-722 (a task em voo no lead), perguntada também aos outros nós por:erpc.multicall— nó que não responde conta como VIVO, a régua doTurnoOrfao(RN-586). Valem as guardas de sempre: ação pendente de ator do gate não reinicia, e dev dono ausente só apaga a linha. Antes de redespachar, o boot gravagate.interruptedno log da sessão (taskId,gate,ultimaAtividadeEm,retomadoEm), e o fio o mostra como aviso, nos dois idiomas. O tick periódico NÃO muda: fora do boot, linha recente é ciclo em voo. Resíduo declarado: nó que ainda não entrou no cluster no instante do boot não é perguntado, e o pior desfecho é o de sempre (trabalho duplicado e barato; o segundo veredito é recusado pela api). - Onde:
apps/engine/lib/engine/gates/gate_rescuer.ex:231(run_boot),:240(interrompido_no_boot),apps/web/src/routes/RetomarGateEstacionado.helpers.ts:21(cicloInterrompido) - Teste:
apps/engine/test/engine/gates/gate_rescuer_test.exs("boot com ciclo RECENTE órfão do reinício: grava gate.interrupted e retoma já (RN-823)"; falhas: "boot com ciclo recente esperando ação PENDENTE do gate: não reinicia nem grava (RN-823)" e o processo local vivo intocado),apps/web/src/routes/RetomarGateEstacionado.test.tsx("cicloInterrompido (RN-823)") - Origem: AT-495 (TP-01 de 10/10: o reinício das 18:51 matou o ciclo de QA
da tarefa 10, e o dev ficou
awaiting_gate15 min sem tela nenhuma dizer)
Revisada pela RN-923 (ADR 0226). A retomada do boot conta como a automática do ciclo (auto_resumes): se o ciclo já usou a sua, o boot o estaciona em vez de redespachá-lo, e o subagente retomado leva o plano do último agent.turn_resumed.
RN-832 — O dev agent sabe que report_done faz commit, push e PR, e que teste novo entra no script test
- Regra: o dev agent não tem nem precisa de
git_commit,git_pushoupr_open: commit, push e PR acontecem quando ele chamareport_done. A descrição dereport_done, a dereport_blocked("falta ferramenta de git" não é motivo de bloqueio), o kickoff do dev agent e a mensagem de ambiente (RN-706) dizem isso — a de ambiente mandava, até aqui, usar as ações tipadas de git, que o dev não tem. O kickoff e o ambiente dizem também que, se o scripttestdopackage.jsonlista arquivos um a um, todo arquivo de teste novo entra nele. Só texto: nenhuma regra nova de código, e o Jev segue sem piso para dev agents (RN-758) — até a RN-839, que pôsreport_done/report_blockedno piso deles. - Onde:
apps/engine/lib/engine/dev/tools/report_done.ex:16(spec),apps/engine/lib/engine/dev/tools/report_blocked.ex:12(spec),apps/engine/lib/engine/dev/dev_agent_server.ex:666(initial_message),apps/engine/lib/engine/harness/ambiente_do_agente.ex:102(montar) - Teste:
apps/engine/test/engine/dev/report_done_instrucao_test.exs("caminho feliz: report_done, kickoff e ambiente carregam as duas instruções"; falha: "o ambiente não manda mais usar as ações tipadas de git") - Origem: AT-499 (TP-01, 11ª: tarefa bloqueada com a suíte verde por
"git_commit não existe"; quatro voltas de QA por teste fora do script
test)
RN-845 — A leitura das ADRs propostas diz se a PR já foi mergeada
- Regra: na linha de uma ADR
executed,listar_adrs_propostas(RN-772) não afirma mais "PR aberta" pelo status da AÇÃO: cruza opullRequestIddo resultado com ogit_mergeEXECUTADO do mesmo projeto (a ação pela qual todo merge passa, RN-418) e diz "PR MERGEADA em <data da decisão>" ou "PR aberta (nenhum merge dela registrado no Brabo)". Resultado sem id de PR, ou consulta de merge que falha, vira "estado da PR: NÃO CONSEGUI LER" — nunca "aberta". Contida (ADR 0060): uma consulta a mais, só sobre os ids listados, no mesmo Postgres. Limite declarado: PR fechada sem merge FORA do Brabo (no provider remoto) não é vista, e segue lida como aberta — perguntar ao provider seria HTTP no laço do agente. - Onde:
apps/engine/lib/engine/harness/tools/listar_adrs_propostas.ex:88(cruzar_com_merges),:102(merges_por_pr),:155(estado_da_pr). - Teste:
apps/engine/test/engine/harness/tools/listar_adrs_propostas_test.exs("PR mergeada aparece como mergeada…", "estado da PR ilegível diz que não leu, nunca aberta"). - Origem: AT-514 (TP-01, 11/10: o Arquiteto disse "aberta como PR" 30 s depois do merge).
RN-846 — A rota HTTP vai ao README e o erro de entrada é do projeto
- Regra: extensão da RN-819. O kickoff do dev agent de
execução pede que rota HTTP criada ou mudada entre no README (método,
caminho, corpo, respostas e códigos) e que erro de entrada (JSON malformado,
campo inválido) responda com mensagem própria no formato do projeto, nunca a
mensagem crua da biblioteca. O recorte do QA confere as duas coisas na
varredura por regra, e o que faltar vai a
itens. Régua de prompt. - Onde:
apps/engine/lib/engine/dev/dev_agent_server.ex:666(initial_message) eapps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/dev/report_done_instrucao_test.exs("RN-846: kickoff pede a rota no README e erro de entrada próprio") eapps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-846: confere rota no README e erro de entrada próprio") - Origem: AT-515 (TP-01, 12ª: o README não documentava a API, e JSON malformado respondia 400 com "Expected property name…" do parser)
RN-849 — O turno de prontidão da arquitetura abre com o estado atual das ADRs
- Regra: extensão da RN-845. Quando o usuário confirma que a
arquitetura está pronta, o servidor do Arquiteto LÊ o estado das ADRs pelo
mesmo código de
listar_adrs_propostas(Postgres local, sem HTTP, mesmo teto de 20 linhas) e o põe na mensagem de abertura desse turno, com a instrução de não afirmar estado de ADR diferente. Leitura que falha diz "NÃO CONSEGUI LER" e manda dizer que não sabe — nunca "pendente". - Onde:
apps/engine/lib/engine/harness/tools/listar_adrs_propostas.ex:50(contexto_de_prontidao),apps/engine/lib/engine/agents/arquiteto_server.ex:223(contexto_de_prontidao). - Teste:
apps/engine/test/engine/harness/tools/listar_adrs_propostas_test.exs("contexto_de_prontidao/2 (RN-849)"). - Origem: AT-519 (TP-01: no turno de prontidão o Arquiteto disse "pendente de aprovação" sobre ADR já mergeada, repetindo a leitura do turno anterior).
RN-850 — A rota entra na seção "Rotas HTTP" e o README não leva nota de processo
- Regra: extensão da RN-846. O kickoff do dev agent pede que a rota entre DENTRO da seção "Rotas HTTP" do README (criada antes de "Testes" se faltar), nunca no fim do arquivo, e que o README, que é do usuário, não ganhe nota de processo nem instrução para agentes. O recorte do QA confere as duas coisas. Régua de prompt.
- Onde:
apps/engine/lib/engine/dev/dev_agent_server.ex:666(initial_message) eapps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/dev/report_done_instrucao_test.exs("RN-850: a rota vai dentro de Rotas HTTP e o README não leva nota de processo") eapps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-850: confere a seção Rotas HTTP e a ausência de nota de processo") - Origem: AT-520 (TP-01: cada tarefa acrescentou rotas no fim do README, depois de "Testes", e sobrou um parágrafo de processo para agentes)
RN-851 — O compose da Infra sobe o APP na máquina do usuário, e a PR declara como rodá-lo
- Regra: o kickoff do Infra Lead pede o compose que SOBE O APP (não só
ambiente de dev):
builddo Dockerfile de cada módulo comFROM= a imagem decidida (projectImagedo contexto da Infra; sem ela, a candidata do roteamento, dito no README), porta${APP_PORT:-8080}:<porta interna>, volume para dados persistentes,.env.examplecommitado (nunca.env) e a seção "Rodar na sua máquina" no README. O container de TRABALHO dos agentes segue subindo pelo broker, e não há deploy.propose_infra_prganha o campo OBRIGATÓRIOrun(commandaté 300 caracteres;urlsde 1 a 5,labelaté 60,kindemapi_docs | web,urlcomeçando comhttp://localhost:ouhttp://127.0.0.1:;envExamplebooleano), validado no engine ANTES do HALT com recusa nomeada que entra no laço (RN-163) e repassado no payload deopen_infra_pr. Decisão no ADR 0213. - Onde:
apps/engine/lib/engine/infra/instrucoes_de_execucao.ex:27(validar),apps/engine/lib/engine/infra/infra_lead_server.ex:1414(build_kickoff) e:1327(abrir_pr), eapps/engine/lib/engine/infra/tools/propose_infra_pr.ex:24(spec) - Teste:
apps/engine/test/engine/infra/infra_lead_server_test.exs(describe "o compose sobe o app e a PR levarun(RN-851)") - Origem: AT-517 (o entregável não tinha como ser rodado pelo usuário)
RN-853 — O dev expõe OpenAPI e Swagger quando a tarefa toca rotas
- Regra: parte do ADR 0213. Quando a
tarefa toca ponto de entrada ou rotas (a MESMA régua do recorte do QA,
toca_ponto_de_entrada?, nunca uma cópia), o kickoff do dev ganha a cláusula: módulo que expõe HTTP serve/openapi.json(OpenAPI 3) e/docs(UI Swagger); sem rede ou sem pacote,/docsé página HTML que carrega o swagger-ui de CDN; módulo web serve a página inicial; a URL entra na seção "Rodar na sua máquina" do README. Tarefa que não toca rotas não paga a cláusula (a mensagem é fixada e volta a cada iteração). - Onde:
apps/engine/lib/engine/dev/dev_agent_server.ex:698(clausula_de_openapi),apps/engine/lib/engine/gates/recorte_da_tarefa.ex:113(toca_ponto_de_entrada?) - Teste:
apps/engine/test/engine/dev/report_done_instrucao_test.exs("RN-853: tarefa que toca rotas pede /openapi.json e /docs"; falha: "RN-853 falha: tarefa sem rota nem entrypoint não paga a cláusula") - Origem: AT-517
RN-854 — O QA prova /openapi.json e /docs dentro do container
- Regra: parte do ADR 0213. Na seção
de ponto de entrada do recorte (RN-813), o QA sobe o app pelo
comando do README em background DENTRO do container, bate em
localhost:<porta interna>/openapi.jsone/docs(curl,wgetounode -e fetch) e encerra o grupo de processos (RN-817). Falha vai aitens; odocker compose upreal fica noresumocomo "provado só na máquina do usuário". - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:121(secao_do_ponto_de_entrada) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("tarefa que toca rotas pede /openapi.json e /docs, compose em resumo"; falha: "falha: tarefa sem rota não recebe a seção") - Origem: AT-517
RN-862 — A pergunta em aberto do lado do engine: ferramenta, veredito e decisão no contexto
- Regra: o Criativo, o PO, o Arquiteto e o Dev Lead declaram toda dúvida
deixada para o usuário com
declare_open_question(perguntaaté 500,sugestaoaté 300 opcional,afeta: {regras, historias, tarefas}), que gravaartifact.open_questioncomquestionId(uuid),autoresessionId; payload inválido é recusa nomeada e não grava. A instrução é UMA frase central, no texto do fluxo dos quatro (RN-711), nunca por servidor. No gate de QA, requisito "não confirmado pelo usuário" (ou contradito por decisão vigente) éneeds_user_decision, nuncachanges_requested:emit_qa_verdicto aceita só comperguntaouopenQuestionId, o QA Lead o consolida acima deapproved, o veredito vai à api com a pergunta e o ciclo fica ESTACIONADO (ADR 0207,gate.rescue_parked, sem resgate automático) até a api pedir a retomada pela rota da RN-724. Asdecisoesdo dev-context entram no kickoff do dev e no recorte do QA como "Decisões registradas para esta tarefa/módulo — vencem RF/DoR quando conflitam" (pergunta → decisao); vazio não escreve nada. A pergunta gravada conta no fecho do turno (RN-731), e o PO passaquestion_idemupdate_story/complete_storyquando corrige a história a partir de uma resposta (ADR 0214). - Onde:
apps/engine/lib/engine/harness/tools/declare_open_question.ex:54(run),apps/engine/lib/engine/harness/fluxo_do_time.ex:116(frase_da_pergunta),apps/engine/lib/engine/gates/tools/emit_qa_verdict.ex:81(run),apps/engine/lib/engine/gates/qa_lead.ex:89(consolidar_decisao),apps/engine/lib/engine/gates/qa_lead_server.ex:595(estacionar_por_decisao),apps/engine/lib/engine/dev/context_builder.ex:121(secao_de_decisoes) - Teste:
apps/engine/test/engine/harness/perguntas_em_aberto_test.exs("válida grava artifact.open_question…"; "inválida é recusa nomeada…"; "consolidação: needs_user_decision vence approved"; "o recorte diz a régua…"; "ContextBuilder: a história leva as decisões…"),apps/engine/test/engine/gates/qa_lead_server_test.exs("grava a pergunta na api, estaciona o ciclo e só o gesto humano o retoma") - Origem: AT-521, ADR 0214
RN-863 — api_docs é sempre /docs, toda rota entra no OpenAPI, e o QA reprova a que falta
- Regra: módulo HTTP declara em
run.urlsSEMPRE/docs(a UI Swagger,kind: "api_docs") mesmo antes de existir, e módulo web a página inicial — nunca uma rota de negócio; o kickoff da Infra diz isso no passo do README (estende a RN-851). A validação dorunrecusa, nomeada e com a MESMA régua nos dois lados,api_docscuja URL não termina em/docs(estende a RN-852). A cláusula do dev diz que TODA rota HTTP criada ou mudada na tarefa entra no/openapi.json— path com método, corpo e respostas (estende a RN-853). No recorte do QA, cada rota da seção "Rotas HTTP" do README precisa estar empathsdo/openapi.jsonservido; rota faltando é item que reprova, nunca observação (estende a RN-854). - Onde:
apps/engine/lib/engine/infra/instrucoes_de_execucao.ex:60(url),apps/api/src/domain/infra/run-instructions.ts:77(recusaDoRun),apps/engine/lib/engine/infra/infra_lead_server.ex:1414(build_kickoff),apps/engine/lib/engine/dev/dev_agent_server.ex:698(clausula_de_openapi),apps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/infra/infra_lead_server_test.exs("run api_docs em rota de negócio é recusado com motivo, sem propor"),apps/api/test/application/use-cases/execution/get-run-instructions.use-case.spec.ts("recusaDoRun (RN-852)"),apps/engine/test/engine/dev/report_done_instrucao_test.exs("RN-853: …"),apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-854: …") - Origem: AT-524, ADR 0213
RN-864 — Falha passageira de rede volta sozinha em até 3 tentativas, e o dev pausa
- Regra: a task bloqueada pelo dev agent com origem
infrapor falha PASSAGEIRA de rede/provedor — lista de permitidos explícita: DNS (EAI_AGAIN,ENOTFOUND,getaddrinfo),ECONNRESET,ETIMEDOUT,aborted, HTTP 5xx/429 do provedor (códigosrate_limit/upstream,connection/timeout) — é RETOMADA sozinha até 3 vezes, depois de 1, 3 e 10 min (decisão do dono), por um job Oban que chama o MESMO desbloqueio do humano (UnblockTaskUseCase, pela rota interna.../tasks/:taskId/unblock, atorsystem, no-op se a task já não está bloqueada). O motivo do bloqueio diz qual retomada foi agendada; depois da 3ª retomada a falha seguinte fica bloqueada como antes, e o motivo diz que as 3 já foram tentadas. E o agente PAUSA 1 min em vez de pegar a próxima task (o wake é ignorado durante a pausa). Crédito esgotado (402, RN-726/730) e erro de código NÃO entram: bloqueiam na hora. O contador de retomadas é só em memória: reiniciar o engine o zera. Vale também para a correção pós-gate (AT-621): o 503 do provider numa correção segue o mesmo caminho do laço principal (crédito RN-726 + retomada), com o motivo marcado "(correção)". - Onde:
apps/engine/lib/engine/dev/dev_agent_server.ex:843(handle_correction_outcome),apps/engine/lib/engine/agents/falha_de_turno.ex:140(passageira?),apps/engine/lib/engine/dev/dev_agent_server.ex:1075(bloquear_passageira),apps/engine/lib/engine/dev/agent_io.ex:302(pausar_por_rede),apps/engine/lib/engine/workers/retomada_de_tarefa_worker.ex:33(agendar),apps/api/src/application/use-cases/execution/unblock-task.use-case.ts:33(ator) - Teste:
apps/engine/test/engine/dev/dev_agent_server_test.exs("falha passageira: agenda a retomada, pausa e não queima a fila", "fim da pausa volta a reivindicar"; falha: "falha passageira depois de 3 retomadas fica bloqueada pedindo o clique", "402 não agenda retomada"; correção: "correção com 503: infra, retomada agendada, pausa e disjuntor intacto", "correção sem crédito: pausa por crédito, sem retomada", "correção com erro não passageiro: bloqueia na hora, sem retomada"),apps/engine/test/engine/workers/retomada_de_tarefa_worker_test.exs - Origem: AT-525
RN-865 — Endpoint e verbo HTTP com caminho também ligam a conferência do OpenAPI, e ela cobre as rotas do código
- Regra: a régua única de "a tarefa toca ponto de entrada/rotas"
(
toca_ponto_de_entrada?, usada pelo recorte do QA e pela cláusula do dev da RN-853) casa também "endpoint" e verbo HTTP seguido de caminho (POST /links). Sem isso, tarefa descrita como "Endpoint protegido…" não recebia nem a cláusula do OpenAPI (dev) nem a conferência do/openapi.json(QA) — foi assim quePOST /linkseGET /panelficaram fora do OpenAPI com as duas tarefas aprovadas no TP-01. A conferência do QA passa a exigir empathsdo/openapi.jsonservido, além das rotas da seção "Rotas HTTP" do README, cada rota que o CÓDIGO registra; a que falta é item que reprova (estende a RN-863). - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:103(@ponto_de_entrada),apps/engine/lib/engine/gates/recorte_da_tarefa.ex:114(toca_ponto_de_entrada?) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-865: tarefa que diz só endpoint ou verbo com caminho recebe a seção"; falha: "falha: tarefa sem rota não recebe a seção") - Origem: AT-527
RN-868 — O tick do resgate de gates é uma corrente só
- Regra: o
GateRescueSchedulerWorkerreagenda o próprio sucessor com a MESMA unicidade dokickoff/0(estadosavailable/scheduled/retryable, semexecuting, janela de dois intervalos). Antes só okickoff/0era único: o job em execução não conta, então o kickoff do boot com o tick anterior em voo, ou um retry que re-roda operform/1, abria uma SEGUNDA corrente, e elas nunca se fundiam. Medido no uso real (14/10): 11 jobs agendados ao mesmo tempo e a linha "espera decisão humana" da RN-722 11 vezes por tick para UMA linha degate_states. Não era defeito de dado. Com a unicidade no reagendamento as correntes duplicadas colapsam numa só no tick seguinte. - Onde:
apps/engine/lib/engine/workers/gate_rescue_scheduler_worker.ex:42(unicidade) - Teste:
apps/engine/test/engine/workers/gate_rescue_scheduler_worker_test.exs("o tick se reagenda e mantém a corrente viva"; falha: "com um sucessor já agendado, o tick não abre uma segunda corrente") - Origem: AT-529
RN-874 — O PO recebe no turno as decisões do usuário e as perguntas abertas do projeto
- Regra: todo turno do PO leva, como mensagem
systemEFÊMERA acrescentada pela fachadaEngineApiClientlogo depois do fluxo do time (RN-711) — nunca emstate.messagesnem no prompt do servidor —, as decisões do usuário (artifact.decision_record,choiceecontext) e as perguntas AINDA abertas do projeto (artifact.open_questionsemopen_question.answeredcom o mesmoquestionId), de TODAS as sessões. O texto manda escrever a RESPOSTA decidida na história, regra, DoR, DoD e tarefa, nunca "pergunta em aberto N"; pergunta ainda aberta é citada peloquestionId, sem inventar resposta. A leitura é LOCAL (Postgres direto, sem HTTP no laço), dos 200 eventos mais recentes desses tipos, com teto de 10 decisões, 10 perguntas, 240 caracteres por item e 3 000 no texto. As perguntas abertas vêm ANTES das decisões e o corte é por linha inteira (revisado pela RN-1058). Sem decisão nem pergunta aberta, ou com a leitura falhando, o turno segue sem a mensagem, nunca cai. Só oporecebe. Cada decisão leva o seuquestionIde as histórias deafeta.historias, para o PO citar o id emcomplete_story/update_storye corrigir uma históriaready(RN-859, AT-752). - Onde:
apps/engine/lib/engine/harness/decisoes_do_projeto.ex:34(anexar),:49(texto),apps/engine/lib/engine/session_events/event.ex:224(list_types_for_project),apps/engine/lib/engine/sessions/engine_api_client.ex:753(DecisoesDoProjeto) - Teste:
apps/engine/test/engine/harness/decisoes_do_projeto_test.exs("o turno do PO leva a decisão e só a pergunta AINDA aberta", "a decisão sai com o questionId e as histórias de afeta", "custo de entrada tem teto"; falha: "sem decisões nem perguntas não acrescenta nada", "leitura que falha deixa o turno sem a mensagem, sem queda")
RN-876 — O needs_user_decision recusado pela api volta ao QA, e só o aceite estaciona o gate
- Regra: o gate de QA só ESTACIONA (
gate.rescue_parked) quando a api aceita o vereditoneeds_user_decisioncomnextAction: "await_user". Se a api RECUSA (400, 404, ou o 409pergunta_ja_respondida), a recusa volta ao laço da QA de Automação como mensagem nova (RN-163: erro é entrada do laço), retomando o MESMO contexto em que ela emitiu — no 409, com a decisão já tomada para o QA aplicar em vez de perguntar de novo. As outras delegações não rodam de novo. A volta conta no teto de iterações do próprio laço; esgotado, a tarefa é bloqueada com o motivo e a origemmodelo(RN-059), nunca estacionada em silêncio. Antes, o retorno da api era descartado e o gate estacionava sem pergunta no fio (medido:openQuestionId: "1", 400 do@IsUUID). A ferramentaemit_qa_verdictrecusa, DENTRO do laço e antes da api,openQuestionIdque não é uuid. - Onde:
apps/engine/lib/engine/gates/qa_lead_server.ex:601(devolver_ao_qa),apps/engine/lib/engine/gates/qa_lead_server.ex:312(devolver_ao_qa),apps/engine/lib/engine/gates/qa_lead_server.ex:697(texto_da_recusa),apps/engine/lib/engine/gates/qa_automacao_agent.ex:186(devolver),apps/engine/lib/engine/gates/tools/emit_qa_verdict.ex:75(uuid) - Teste:
apps/engine/test/engine/gates/qa_lead_server_test.exs("400: nada estaciona, o QA recebe o motivo e uma nova volta acontece"; "409 no laço: a volta seguinte do QA lê a decisão"; falha: "teto esgotado depois da recusa: bloqueio narrado, nunca estacionamento"),apps/engine/test/engine/harness/perguntas_em_aberto_test.exs("AT-526 (RN-876): openQuestionId que não é uuid é recusa nomeada; uuid passa") - Origem: AT-526
RN-877 — O recorte do QA manda conferir as decisões antes de pedir decisão
- Regra: a instrução do recorte do QA diz que, antes de
needs_user_decision, ele confere as decisões listadas; requisito que uma delas já responde não é pergunta e a decisão se aplica.openQuestionIdsó com o uuid de uma pergunta ABERTA; sem ele, a pergunta nova vai empergunta/sugestao. É lembrete de leitura: não muda a régua do veredito. - Onde:
apps/engine/lib/engine/gates/recorte_da_tarefa.ex:20(texto) - Teste:
apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("AT-526 (RN-877): manda conferir as decisões antes de needs_user_decision"; a falha da instrução é coberta pelo teste da RN-876 da ferramenta) - Origem: AT-526
RN-878 — O idioma do agente de execução vale desde a primeira frase
- Regra: nos turnos SEM autor humano dos agentes de execução (
dev-*,qa-*), a orientação de idioma da RN-622 diz que vale desde a PRIMEIRA frase, inclusive a narração antes de cada ferramenta, e perde a ressalva "salvo pedido do usuário" (não há usuário no turno). Medido na sessão6c68fbc0(projeto pt-BR,anthropic/claude-haiku-5.5): a orientação chegava, e mesmo assim a iteração 0 doqa-automacaoe dodev-encurtador-apiabria em inglês ("I'll start by running…"). O caminho continua UM (a fachada, RN-622): nada em prompt de servidor,agent_instructionsou descrição de ferramenta (RN-667). O teto de 50 tokens (AT-169) segue: medido comgpt-tokenizer,pt-BR32/26,en19/19 e o genérico com código na forma curta no pior caso 30/30 (cl100k/o200k); código fora da forma curta cai na orientação de sempre, que já cabia. Turno com autor e os conversacionais não mudam. Sem idioma resolvido, nada é acrescentado. A eficácia se mede no próximo TP-01. - Onde:
apps/engine/lib/engine/harness/idioma_da_resposta.ex:164(orientacao_de_execucao),:154(agente_de_execucao?) - Teste:
apps/engine/test/engine/harness/idioma_da_resposta_test.exs("agente de execução: vale desde a primeira frase, dentro do teto"; falha: "agente de execução sem idioma resolvido: nada é acrescentado") - Origem: AT-530
RN-879 — URL absoluta do entregável com a porta, e o mesmo code para o mesmo erro
- Regra: quando a tarefa toca ponto de entrada ou rotas, a cláusula do dev
manda que URL absoluta devolvida pela API do entregável (ex.: o link curto)
venha de
PUBLIC_BASE_URLou doHostda requisição COM a porta — nuncahttp://localhostfixo nem o hostname sem porta —, e que o mesmo caso de erro responda o mesmocodeem todas as rotas. O recorte do QA confere as duas coisas e trata a divergência (ex.:not_found×link_not_found) como ITEM. Decisão do dono de 14/10: regra geral. - Onde:
apps/engine/lib/engine/dev/dev_agent_server.ex:698(clausula_de_openapi);apps/engine/lib/engine/gates/recorte_da_tarefa.ex:122(secao_do_ponto_de_entrada) - Teste:
apps/engine/test/engine/dev/report_done_instrucao_test.exs("RN-879/880: tarefa que toca rotas leva URL com porta, code único e CORS" e a falha "RN-879/880 falha: tarefa sem rota não recebe CORS nem PUBLIC_BASE_URL");apps/engine/test/engine/gates/recorte_da_tarefa_test.exs("RN-879/880: URL, code de erro e CORS") - Origem: AT-531