Histórico de fases, parte 3: Programa 28 e auditorias

Registro de 11/09/2026: descreve o Brabo daquela data

O Programa 28 (onda a onda), a auditoria fluxo.yml × código, os agentes novos do time e o grafo de conhecimento. Texto movido sem reescrita do Histórico de fases. O que vale hoje mora nas RNs e nos ADRs.

PROGRAMA 28 — Onda 1, frente A: o tema deixa de ser inalcançável (RN-182..185)

O tema claro existia em design/tokens.css, tinha teste de paridade e estava descrito no design/README.md como se fosse uma tela — e nada em apps/web escrevia data-theme. O único jeito de vê-lo era digitar o atributo no DevTools. Os dois testes de contraste sabiam e diziam: um afirmava por expect que TRÊS pares do claro reprovam, "registro do que se herda, não garantia do que se renderiza". Enquanto durou, o argumento era honesto — medir uma tela que ninguém pode abrir é medir uma intenção. Com o botão de tema chegando, ele inverte de sinal.

O boot é um ARQUIVO (apps/web/public/theme-boot.js), síncrono no <head>, antes do bundle. O handoff manda usar script INLINE; a imagem serve sob script-src 'self' (ADR 0058), então inline funcionaria em pnpm dev:web e seria bloqueado em produção — a falha do ADR 0036 com outro sujeito. tema.ts é a API que o shell consome; o botão não mora nela. localStorage que lança degrada para "pinta e não persiste", e valor desconhecido cai no default.

Seis tokens do tema claro mudaram de valor (ADR 0074), porque o fundo mais exigente do claro é o --code-bg de papel: accent 3,56 → 4,81, warning 3,15 → 4,98, success 3,89 → 5,12, violet 4,16 → 4,95, text-muted 2,76 → 5,17, mais o accent-hover um degrau abaixo. O ESCURO não mudou nenhum, e a dívida dele segue travada nos mesmos cinco números. Dois efeitos colaterais registrados: os cinco pares que no escuro são dívida PASSAM no claro, e a "exceção conhecida do design system" (--on-accent sobre --accent, 3,20:1) some no claro — o conserto que o comentário do teste descrevia, escurecer até --terracota-500, é o que o claro passou a fazer.

Cinco dos oito valores --syn-* do handoff foram RECUSADOS por medição (o pior, --syn-cm, dá 2,32:1 no claro). Onde o handoff reprova, vale o número medido: é a mesma régua das fontes. Os oito papéis passam a existir com prefixo --syntax-*; os cinco semânticos que ainda pintam vão medidos ao lado deles.

PROGRAMA 28 — Onda 1, frente D0: gasto por provider (RN-186..188)

A contenção mudou de natureza, e é isso que importa — não o eixo novo. provider voltou a ser dimensão de sumGroupedBy, revisando o ADR 0063 por decisão do dono do produto. Antes, o membro não alcançava a quebra por CREDENCIAL porque ela não existia — o ADR 0063 escreveu "não há argumento a passar". Agora existe, e quem contém é o TIPO: duas sobrecargas, e o escopo que carrega actor (o único da visão do membro) só aceita Exclude<SpendDimension, 'provider'>. A garantia passou de "impossível de expressar" para "impossível de compilar", que é mais fraca — e é por isso que as barreiras são DUAS e independentes: a rota do membro também não tem parâmetro de dimensão nenhum. Nenhum if sobre a combinação, de propósito (mesma lição da RN-153/154), e a prova foi por INVERSÃO: sem o @ts-expect-error o tsc acusa TS2769 na linha exata.

porOwner/porAgente são partição de porAtor por actor_kind, sem consulta a mais; system fica fora dos dois e continua no total. O índice em token_usage(created_at) que o ADR 0063 mediu e adiou entrou na migração 0044. A TELA não mudou — é onda posterior.

PROGRAMA 28 — Onda 2, frente H2: ranking de modelos sem nota inventada (RN-210, ADR 0077)

O handoff pedia duas coisas que dependiam do MESMO dado que o produto não tem: uma nota de qualidade por capacidade (mock: "código → claude-sonnet-4 / qwen2.5-coder:14b (9.4)") e um badge "ideal" no ModelPicker quando o modelo cobre "todas as capacidades exigidas pelo agente". A investigação confirmou duas vezes que esse segundo dado não existe: o próprio ModelsSection já tinha renomeado a coluna "Agente · capacidades" para só "Agente" por essa razão, e o ModelPicker de binding de agente lê GET /projects/:id/models (papel viewer) — a curadoria (uses, ADR 0051) só existe em ModelComCuradoria, de uma rota maintainer-only. O badge NÃO foi construído; a tabela "Melhores modelos por capacidade" FOI, com dois sinais reais: quantos agentes do projeto já resolvem, pela cascata, para cada modelo curado, e o custo do catálogo como desempate — nunca nota calculada.

PROGRAMA 28 — Onda 2, frente B: sidebar recolhe (RN-195..201)

264px/62px com trilha de ícones no colapso; projetos expansíveis revelando as abas (lidas de project-tabs.ts, nunca hardcoded); seção Atividades agrupada por agente-base/instância REAL — sufixo -2 de extraDevAgentId, nunca um contador -01/-02 inventado (achado da Onda 1/frente B0) —, escopada ao projeto da rota atual, não a todos, para não abrir N+1 de eventos. Botão de tema no rodapé; os dois itens globais inertes saem, só Projetos e Atividades são globais; auto-collapse da aba Código via AutoCollapseContext (sidebar-state.ts), sem gravar preferência. Persistência centralizada em sidebar-state.ts, as seis chaves brabo.* do handoff.

Duas divergências do handoff documentadas e NÃO resolvidas (RN-197): o badge do projeto continua pendingApprovalsCount (RN-151 vence, é mais recente) e a cor de identidade do handoff só aparece na trilha recolhida — a linha expandida mantém só o dot de status existente (RN-039), dois dots seria ruído.

PROGRAMA 28 — Onda 2, frente C: moldura de tela (RN-202/203, ADR 0078)

As quatro correções literais do checklist "moldura de tela" do handoff: .headerTop ganhou min-height: var(--header-h) — PISO, não teto, porque o cabeçalho do projeto (identidade + TokenMeter compacto) é mais rico que o cabeçalho genérico das 6 telas internas do handoff, e forçar 60px cortaria o alerta de orçamento, a mesma classe de erro que a RN-088 proíbe; aba ativa com box-shadow: inset 0 -2px 0 var(--accent) em vez de border-bottom; rolagem horizontal na régua; container de conteúdo com max-width: 1040px nas abas em forma de documento (não nas semRespiro). Os valores de espaçamento da régua que viviam como override de CSS de descendente em ProjectPage.module.css desde a FASE 16 migraram para Tabs.module.css, agora com o mesmo dono.

A parte que exigia decisão, não só CSS: o handoff lista 7 abas de projeto e o registro (project-tabs.ts) tem 10. executores (RN-121), backlog (RN-048) e insights (hipóteses do Psicólogo) nasceram DEPOIS do handoff, com dado real e RN própria, e FICAM — o handoff é referência de fidelidade VISUAL, não teto de quantas abas o produto tem (RN-203, ADR 0078). E a chave sessions continua rotulada "Chat", nunca "Chat RAG" como o handoff mais recente pede (RN-202): "Chat RAG" descreveria uma funcionalidade que não existe — o contrato de embeddings está pronto (ADR 0075) mas nada ainda o consome, sem pipeline de indexação e sem UI de citação — e renomear a aba hoje seria o mesmo erro que o ADR 0042 já recusa para modelo de catálogo: anunciar uma capacidade antes dela existir. Nenhuma FORMA de export mudou (AbaDoProjeto, ABAS_DO_PROJETO); só o rótulo de code foi de "Code" para "Código".

PROGRAMA 28 — Onda 2, frente H1: foco visível (ADR 0036)

Select, Modal (botão de fechar) e ProjectCard ganharam :focus-visible no mesmo tratamento calibrado de Input.module.css, incluindo o bloco de forced-colors: nenhum dos três tinha indicação de foco alcançável só por teclado. O botão de fechar do Modal subiu de 30px para 32px, o piso de alvo de toque em desktop. Table e Badge foram auditados e NÃO precisaram de mudança — nenhum dos dois expõe afordância interativa própria: linha de Table é apresentação pura (quem precisa de linha clicável já usa <button>/<a> dentro da célula, via render) e Badge não é usado com onClick em lugar nenhum do produto.

PROGRAMA 28 — Onda 3, frente H3: a aba Criativo ganha KPIs sem fingir dado que não existe (RN-227..230)

O handoff pede 4 selos de status para os 5 estados reais da sessão (created/active/closing/closed/closed_abnormally). closed_abnormally→ abortada e created→aguardando são diretos; closing NÃO foi fundido com "fechada" — ganha selo próprio ("encerrando", tom accent, pulsante), porque em closing o desfecho ainda não é conhecido e chamá-la de "fechada" mentiria sobre isso. Os filtros pill (só 4, pelo mesmo motivo) agrupam created e closing por TRAJETÓRIA, não aparência: created cai em "Ativas" (ainda em jogo), closing cai em "Fechadas" (a caminho de fechar sem erro) — o selo da linha nunca é reescrito pelo filtro, só o agrupamento muda. Dos 4 KPIs, dois são honestos por dado ausente: "custo do mês" REAPROVEITA getMySpend (mesma queryKey da aba Gastos, visão do membro/RN-101) e nunca mostra o total do projeto — que é dado do owner (porProjeto), e a aba Criativo é vista por qualquer membro; "taxa ideação → commit" é DECLARADA ausente (mostra "—"), porque não existe vínculo entre sessão e commit no produto hoje — inventar o cálculo seria a mesma classe de erro que o ADR 0042 já recusa para nota de modelo.

PROGRAMA 28 — Onda 4, frente G2: o Chat RAG ganha pipeline de indexação e busca híbrida (RN-231..238, ADR 0080)

Sobre a tabela chunks da Onda 3 (ADR 0079): indexação MANUAL (sem watcher, POST .../rag/reindex, full rebuild idempotente — apaga o escopo/sessão antes de recriar) dos três escopos honestos — docs/adr via ReadProjectCodeUseCase (mesma credencial/portão/checagem de caminho da aba Code) e session só de chat.message/agent.response. Chunking por heading/parágrafo, 1200 caracteres com 150 de sobreposição — documentado como ponto de partida, não calibração contra dado real (não existe, ainda, corpo de perguntas rodado contra o índice). Busca híbrida é DUAS consultas independentes (vetor via HNSW, léxico via GIN), fundidas por soma ponderada (0.6/0.4) e cortadas num limiar (0.2) — números também ponto de partida. Quando o provider de embedding (fixo: ollama) não responde, o pipeline grava os chunks SEM vetor e declara a lacuna no relatório — nunca finge indexação completa, e a busca degrada para léxico-only avisando. A tela (Chat RAG como aba própria) é da Onda 5; esta frente é só o backend.

PROGRAMA 28 — Onda 4, frente F1: ciclo de vida do container vira tabela, sem orquestrador (RN-243..248, ADR 0081)

Ver item 31 da FASE 25 (PARCIALMENTE REVOGADO): project_containers (migração 0046) grava o ESTADO mutável — provisioning → running ⇄ stopped, failed alcançável dos três, removed só sai reprovisionando — distinto de artifact.project_image (a DECISÃO imutável do Arquiteto). Confirmado por investigação (grep, zero ocorrências): NENHUM serviço do produto monta /var/run/docker.sock nem roda privileged — os dois casos de uso só GRAVAM e LEEM, nenhuma chamada real a Docker. A primeira transição exige a imagem já decidida (RN-105) e CONGELA versão/recursos na linha; projeto local (ADR 0072) é recusado, porque não sobe container próprio. Teto de recursos (cpus/memory_mb/pids_limit) é DECLARADO, não aplicado — nenhuma tela afirma "o container está limitado a X", só "a intenção registrada era X". Nenhuma rota HTTP nova à época: sem consumidor real ainda, expor uma seria adivinhar contrato — fechado pela Onda 5/frente F2 logo abaixo.

PROGRAMA 28 — Onda 5, frente G3: o Chat RAG ganha aba própria (RN-252..254, ADR 0082)

A tela que RN-202 (Onda 2/C) tinha adiado — "Chat RAG" descreveria uma capacidade que ainda não existia — chega junto com o backend da Onda 4/G2: key: 'rag', aba NOVA (ordem: 28, entre Código e Backlog), NUNCA renomeando sessions. A distinção continua a mesma, só a razão mudou: sessions é conversa com agente ATIVADO (gasta a chave do owner por turno, RN-058); rag é busca sobre um índice já construído, sem agente nenhum no meio, read-only por natureza. A tela consome os três contratos do ADR 0080 sem adivinhar forma e mostra as duas degradações honestas que o backend já declarava e nenhuma tela lia — vectorAvailable: false vira aviso visível, e a cobertura nunca inventa "reindexado há Xmin". Citação de origem session navega até o EVENTO exato, reusando o mecanismo que os chips de evidência do Psicólogo já usam; origem file mostra caminho/heading como texto, sem link — a aba Código não tem deep-link por caminho hoje, fora de escopo aqui.

PROGRAMA 28 — Onda 5, frente F2: o consumidor real do ciclo de vida do container (não o terminal) (RN-267/268, ADR 0083)

O plano original desta frente era o terminal interativo completo. A investigação confirmou que a FASE 25b continua cortada: nenhum serviço monta /var/run/docker.sock, e mesmo depois do ADR 0081 nada em produção transiciona project_containers — não existe container real para abrir um terminal DENTRO dele. Implementar um terminal fingido, ou rodando no mesmo container do monorepo do Brabo (a dívida que o ADR 0055 já descreve como política, não isolamento), inventaria capacidade — o mesmo erro que os ADRs 0041/0042 recusam para provider de LLM e modelo de catálogo. A entrega real: GET /projects/:projectId/container/lifecycle (role:viewer), primeira exposição HTTP do ciclo de vida (revisa o ADR 0081, que tinha adiado a rota por falta de consumidor), e a aba Terminal mostrando esse estado — badge traduzido, motivo de falha — SOB o texto explicativo que já existia, nunca no lugar dele, buscado só enquanto a aba está aberta. null (nunca provisionado) é o resultado honesto e mais comum hoje.

PROGRAMA 28 — Onda 5, frente I: login social revoga a proibição do backlog do ADR 0031 (RN-272..283, ADR 0084)

Pedido explícito do dono do produto, ciente das consequências de segurança — a proibição sai só para GitHub/GitLab; MFA, OIDC provider e federação genérica continuam fora de escopo (ver "O que NÃO fazer"). SocialLoginCallbackUseCase termina no MESMO EmitirSessaoUseCase do login por senha — nenhum segundo formato de sessão. Reusa o cliente OAuth existente, mas o state tem propósito PRÓPRIO (purpose: 'social_login', checado ANTES de qualquer outro campo): nunca aceita o state do fluxo de CONEXÃO de git a um projeto, o que seria escalação de privilégio. Tabela nova social_identities (migração 0047), chave (provider, provider_user_id) — nunca e-mail/login. Decisão em três passos: identidade conhecida → login; e-mail bate com conta existente E verificado pelo provider → vincula (e verifica o e-mail da conta se ainda não estava); e-mail bate mas NÃO verificado → recusa — um e-mail digitado não é prova de posse, aceitar abriria account takeover; sem conta correspondente → provisiona conta nova sem senha, mesmo estado "pendente" da migração do Keycloak. Reusa o MESMO app OAuth da conexão de git — zero variável de ambiente nova, só redirect_uri/scope diferentes por fluxo; a ação do OPERADOR de cadastrar o segundo callback no provider é o que justifica o branch breaking/.

Auditoria fluxo.yml × código — o plano do Dev Lead vira aprovação de verdade (RN-284, ADR 0086)

Não é fase planejada: fecha a divergência que uma auditoria só-leitura de docs/fluxo.yml × código encontrou (achado A2, docs/explanation/auditoria-fluxo-vs-codigo.md) — o fluxo já declarava a saída plano-de-paralelismo do dev-lead como via: proposed_action desde o ADR 0085, e o código nunca foi ajustado para bater. O dono do produto decidiu que o código errava.

propose_execution_plan entrou em decide.ts (papel mínimo maintainer), DELIBERADAMENTE fora do bloco de tetos absolutos — pode ser configurado para auto-aprovar, ao contrário de parallelize/raise_max_parallel. O Dev Lead é o PRIMEIRO agente conversacional (todos rodam turno síncrono via GenServer.call de até 180s) a suspender esperando uma decisão humana — o padrão já existia para o dev agent (ADR 0052) e os gates de QA/Infra (ADR 0057), mas os dois são disparados por cast e nunca precisaram lidar com um from síncrono pendente. TurnoAssincrono.tratar_resultado/2 ganhou um ramo: presente a chave :aguardando_aprovacao (checada pelo VALOR, não pela presença — o Dev Lead a carrega nil desde o init/1), responde ao from do mesmo jeito e na mesma hora de sempre, mas emite só agent.status: awaiting_approval em vez de agent.done — o turno não terminou. LiveBroadcast.agent_status/4 ganhou o status novo na guarda. A retomada usa o MESMO Engine.Dev.Wake que QaLeadServer já reusa para os subagentes de QA (a entrega é por AGENTE, não por tipo).

Lacuna aceita, declarada: sem tabela de estado própria, um restart do engine enquanto o Dev Lead está suspenso perde a inscrição — a decisão continua registrada e visível em Aprovações, mas ele não narra o desfecho sozinho. Fechar isto exigiria o mesmo mecanismo de persistência do ADR 0052; fora do escopo desta correção, que só alinha o comportamento ao que docs/fluxo.yml já declarava.

UX Designer — o quinto agente conversacional, antecipado (RN-285..287, ADR 0087)

Não é gatilho de separação disparado — docs/fluxo.yml sempre declarou "quando o projeto GERENCIADO tiver interface própria a desenhar" como critério, e ele não disparou: o design system continua insumo estático. Decisão CONSCIENTE do dono do produto de antecipar o papel mesmo assim.

Engine.Agents.UxDesignerServer espelha o DevLeadServer — GenServer por sessão, teto de 14 iterações, ativado por handoff accepted endereçado a "ux-designer" pelo mecanismo GENÉRICO já existente (nenhuma linha mudou em ActivateAgentUseCase). SOLO: sem área, sem subagentes. O kickoff lê a artifact.product_brief mais recente — a MESMA "necessidade de negócio" que o Criativo produz, sem artefato novo — e o sistema de design (design/tokens.css, design/COMPONENTS.md) é DESCRITO na identidade do agente (Engine.Harness.Agents), texto estático: os agentes conversacionais não têm ferramenta de leitura de arquivo do repositório.

propose_prototype é a ÚNICA ferramenta (personas, jornadas, prototipo com telas/anotacoes, resumo). Grava artifact.prototipo_navegavel sem caso de uso dedicado na api — diferente de choose_project_image/create_c4_diagram, que precisam de um porque têm conteúdo DERIVADO de outro artefato ou recusa de domínio compartilhada; nenhum dos dois motivos vale aqui, então a validação de forma mora no engine (ArtifactSchemas) e a gravação usa o append_event_returning genérico que a api já expõe, mesmo caminho do artifact.product_brief. Um artefato só, dois handoffs sobre ele — para "po" e para "dev-lead" — nunca um segundo artefato para "spec-visual": o protótipo (telas + anotações) É a spec visual, e duplicá-lo arriscaria as duas cópias divergirem depois. Reusa a metade do desenho do ADR 0086 que sobrevive sem a suspensão dele: propose_prototype bem-sucedido encerra o turno, para o modelo não propor de novo e duplicar o artefato — sem o "aguardando_aprovacao", porque propor um protótipo não tem efeito externo.

apps/web/src/lib/agents.ts ganhou a entrada (color: var(--accent), o token semântico menos reusado do roster; icon: PencilIcon). uxDesignerActive entrou no roster nas DUAS fontes que a RN-090 exige em sincronia — o painel do time (agent-status.ts) e o card do dashboard (projects-summary.repository.ts, mesma consulta de infraActive ampliada, sem query nova).

Fora de alcance, declarado: teste-de-usabilidade exige usuário humano real — nenhum agente substitui isso. metricas-de-uso segue lacuna mesmo com analytics active (ADR 0089): o relatório de funil mede sessão→commit→PR→merge, não adoção de feature pelos usuários FINAIS do projeto que o Brabo constrói — "evidência de adoção por feature" está DECLARADA como métrica sem caminho para existir hoje, não pendência a fechar na próxima rodada.

Staff — código pronto, dormente para disparo automático (RN-305/306, ADR 0088)

Não é fase planejada: docs/fluxo.yml declara o Staff/Principal Engineer como status: planned desde o ADR 0085 ("contrato pronto, ativação decidida, aguarda gatilho"). Decisão CONSCIENTE do dono do produto: antecipar o CÓDIGO mesmo sabendo que o gatilho AUTOMÁTICO (a Anamnese notando um problema sistêmico RECORRENTE) não vai disparar — a Anamnese está pausada (ANAMNESE_ENABLED=false, decisão de 2026-08-10). Dormente para disparo automático, não para acionamento MANUAL.

Sexto agente conversacional solo (Engine.Agents.StaffServer), ao lado de Criativo/PO/Arquiteto/Dev Lead/UX Designer, espelhando o Arquiteto (laço bounded teto 14) com duas diferenças: SEM kickoff/1 (não há artefato de sessão para resumir — sobe e fica ocioso até a primeira user_message, que é como quem endereçou o handoff explica o problema) e ativado pelo caminho GENÉRICO de canActivateAgent (handoff accepted endereçado a "staff"), sem entrar em USER_STARTED_AGENTS — investigação confirmou que nenhuma mudança de domínio na api era necessária, porque assertHandoffTargetAllowed só recusa subagente de área e o Staff não tem área. A única ferramenta, propose_rfc (problema, opções com trade-offs, recomendação, PoC descartável), grava artifact.rfc_staff DIRETO via append_event_returning — mesmo padrão sem tabela de emit_insight, e não o de artifact.c4_diagram (que deriva o Container level do module_map na api) — e devolve o handoff ao Arquiteto no MESMO tool call, sem proposed_action (registrar um documento não é efeito externo).

staffActive entrou na roster do painel do time e no card do dashboard (ProjectCardSummary.roster), mesmo critério de infraActive, para não abrir a divergência que o comentário de RosterFacts já alertava. Declarado, não escondido: SessionPage.tsx/AGENTES_DE_CHAT não foram tocados — mesmo padrão já aceito para infra (um lead REAL e ATIVO também fora dessa lista). O caminho ponta a ponta de uso hoje é a rota interna (POST .../agent/message, agent: "staff"), não a tela de Sessão; a UI genérica de "handoff manual a agente à escolha" segue no backlog.

O gate implementavel ativo — QA-estratégia como segundo momento do qa-lead (ADR 0090)

Decisão consciente do dono do produto de antecipar o gatilho que docs/fluxo.yml já previa ("quando o gate implementavel ativar") — o gatilho AQUI é o próprio trabalho de construir o mecanismo, não um sintoma de uso esperando para acontecer. docs/gates.yml, implementavel: status: planned → active (dono dev-lead, severidade: warn intocada — o registro já dizia "nasce warn mesmo quando ativar").

QA-estratégia deixa de ser papel proposto: é o PRÓPRIO qa-lead, num segundo MOMENTO — mesmo processo, entregável separado do veredito de PR, exatamente como docs/fluxo.yml já declarava ("pode ser o próprio qa-lead em segundo MOMENTO, não necessariamente agente novo: a separação é de entregável"). Engine.Gates.QaLeadServer.run_design/3 é um ponto de entrada NOVO e ADITIVO (sem tocar run/2, o caminho de sempre, amarrado a DevAgentState.find_by_task_id), acionado por Engine.Gates.Dispatcher.run_qa_estrategia/3 (mesma indireção trocável em teste que run_qa/2/run_secops/2 já usam).

Engine.Gates.QaEstrategiaAgent é módulo SEM ESTADO (não GenServer), mesma forma de QaPerformanceSegurancaAgent — registro sem Terminal (ReadFile, SearchWorkspace, EmitPlanoDeTeste) —, mas o CONTEXTO é outro: Engine.Gates.QaEstrategiaContext.fetch/3 busca SÓ story (de list_backlog) e module_map vigente (de get_infra_context, reusado só pelo campo moduleMap — zero rota nova), sem dev_state nem worktree_path, porque o gate roda PRE-DEV: não há dev agent, worktree nem task_id ainda. Nenhuma das três ferramentas passa pelo ActionPipeline (só terminal/write_file passam), então este agente NUNCA suspende — run_design/3 roda síncrono dentro do próprio handle_cast. O teto de iterações fica em 8 (conversacional), não 60 (gate) — DE PROPÓSITO: sem token_budget_micros por baixo (não há task, PRE-DEV), a mesma razão pela qual infra-workflows fica em 8 mesmo usando ferramenta (RN-085). O entregável (emit_plano_de_teste: síntese, critérios executáveis, estratégia de automação GENÉRICA e sem framework) vira artifact.plano_de_teste no event log da sessão que chamou.

assess_implementability, ferramenta nova do Dev Lead, lê o artifact.plano_de_teste mais recente da story no histórico da PRÓPRIA sessão: sem plano ainda, dispara a avaliação e devolve erro pedindo retentativa (erro de ferramenta é ENTRADA do laço, não fim de linha — RN-163); com plano, propõe o parecer (implementavel/inviavel + justificativa, plano embutido no payload) como proposed_action, MESMO padrão de três desfechos de propose_execution_plan (ADR 0086) — maintainer, DELIBERADAMENTE fora do bloco de tetos absolutos de decide.ts.

appsec deixa de ser proposto — o segundo momento do SecOps entra na seção seguinte.

O appsec ganha o segundo momento do secops (RN-360/361, ADR 0090)

docs/fluxo.yml (id: appsec, camada_seguranca) declarava por antecipação "mesmo padrão do QA: dois MOMENTOS, não dois agentes por ora" — decisão consciente do dono do produto de antecipar a ativação, sem esperar o gate implementavel (frente qa-estrategia, mesmo ADR conceitual 0090) que o próprio registro citava como gatilho.

Engine.Gates.SecOpsAgentServer.run_design/2 roda no MESMO processo do secops de PR (mesma chave de Registry), sem Diff/Scanner/ DevAgentState nenhum: busca a story no backlog + o module_map vigente (Engine.Gates.AppSecContextBuilder, sem dev_state/worktree_path) e chama Engine.Gates.AppSecAgent.run/3 — módulo SEM ESTADO (não é GenServer, mesma forma de QaPerformanceSegurancaAgent), registro de ferramentas SEM Terminal, rodando um checklist STRIDE-lite via ToolLoop.run/1 sobre o DESENHO da story, nunca sobre código. Termina emitindo artifact.threat_model e criando handoff para os três leads declarados (arquiteto, dev-lead, infra — o AGENTE endereçável do id area-infra, RN-361).

Lacuna declarada, não bug: run_design/2 é ACIONÁVEL, mas nada aciona sozinho ainda — o gatilho natural é assess_implementability do Dev Lead (frente qa-estrategia), fora do escopo desta entrega, que foi mantida autocontida (decide.ts/docs/gates.yml/dev_lead_tools.ex intocados).

analytics/delivery-metricas viram relatório (RN-320..322, ADR 0089)

Decisão consciente do dono do produto de ANTECIPAR dois papéis do modelo-alvo (docs/fluxo.yml, status: proposto) sem esperar o gatilho orgânico que cada um já declarava. A forma é a que o próprio fluxo prescrevia: analytics "absorvido por medicao" até métrica de PRODUTO virar entrada obrigatória do PO, delivery-metricas "nunca vira agente — vira RELATÓRIO do medicao". Os dois viram um SCRIPT só — apps/api/scripts/analise-funil.ts (pnpm --filter api analise:funil -- --projeto <uuid> [--json]), no MESMO formato de medir-execucao.ts (Fase 13b): leitura pura via Drizzle, zero escrita, sem GenServer, sem agente de LLM.

Mede DE VERDADE, sobre proposed_actions.execution_result das três ações git do dev agent (git_commit/pr_open/git_merge, só status: 'executed'): funil real (quantas sessões produziram commit / PR aberta / PR mergeada, e a conversão entre etapas — conta SESSÃO, não ação), lead time real (primeiro commit ao primeiro merge da sessão, por updated_at da EXECUÇÃO, não da proposta) e deployment frequency real (merge em branch PROTECTED_BRANCHES, por dia, cruzando por referência com o gate backmerge de docs/gates.yml — cuja evidência é CI, fora do alcance de um script que só lê o banco).

Três métricas ficam numa seção "Não medido, de propósito" — declaração PERMANENTE, não lacuna a fechar na próxima rodada: funil de produto completo ideação → commit (sessions sem storyId, RN-230, exigiria schema novo — nenhuma migration nesta frente); evidência de adoção por feature (não é dado que falta coletar — o Brabo não instrumenta os projetos que ele CONSTRÓI, sem caminho nenhum para essa telemetria existir hoje); MTTR e change failure rate (exigem sinal de INCIDENTE de produção real, a mesma dependência de secops-runtime/platform — outra frente).

secops-runtime como script de relatório (RN-375..377, ADR 0091)

Antecipação decidida do dono do produto do papel secops-runtime (docs/fluxo.yml, camada_seguranca, antes proposto), sem esperar o gatilho declarado ("produção com tráfego real, pós DEPLOY_ENABLED + platform ativo"). Só o que o gatilho NÃO exige entra: pnpm --filter api relatorio:seguranca-runtime lê rate_limit_hits — o dado que o RateLimitGuard do ADR 0027 já grava, inclusive sob tráfego de dev/CI — e produz ranking de baldes (user:<uuid>/ip:<endereço>, os dois únicos formatos gravados; sem rota nem motivo, que a tabela nunca guardou) e distribuição temporal em fatias fixas. É SCRIPT, não agente LLM nem GenServer: não há decisão a tomar sobre o dado, só agregação.

Detecção automática de incidente, resposta a incidente e postmortem de segurança continuam FORA — dependem do mesmo gatilho que não disparou — e o relatório os lista numa seção "não medido" PERMANENTE, sem simular incidente de exemplo nem inventar número (mesmo princípio dos ADRs 0041/0042/0077). A janela retida é curta por desenho (DomainGaugesCollector.pruneRateLimit apaga hits com mais de 2 × RATE_LIMIT_WINDOW_MS, 240s por padrão), e o relatório declara as duas janelas — CONFIGURADA e OBSERVADA — nunca deixando a segunda passar por um histórico maior do que é.

platform ganha uma primeira entrega: relatório de telemetria sob demanda (RN-385/386, ADR 0092)

Decisão consciente do dono do produto de antecipar parte do papel platform (docs/fluxo.yml, camada_plataforma) — cujo status continua planned e cuja ativacao continua sincronizada com DEPLOY_ENABLED, que não existe. pnpm --filter api relatorio:telemetria [--projeto <uuid>] [--json] é um SCRIPT (não agente LLM, não GenServer) que lê, sob demanda, as MESMAS perguntas que o DomainGaugesCollector já coleta para o scrape do Prometheus — sessões ativas/closing por projeto, tasks bloqueadas por projeto, estado do último backup (sempre GLOBAL, nunca por projeto, porque o produto faz backup do banco inteiro) — e termina depois de imprimir. As consultas SQL são REPLICADAS, não importadas do coletor: os métodos dele são privados e terminam escrevendo num gauge Prometheus, sem metade pura de "só a query" para reusar sem acoplar um script de CLI ao ciclo de vida de um @Injectable.

A saída sempre traz "onde ver mais" (os dashboards versionados em deploy/k8s/observability/dashboards/*.json, os alertas em deploy/k8s/observability/alerts/brabo-alerts.yaml, docs/runbook.md #observabilidade, pnpm dev:obs para observabilidade local) — link, nunca duplicação — e uma seção "não medido" que DECLARA, sem inventar: nenhum SLO numérico está definido no produto, postmortem depende de incidente real que não aconteceu, e telemetria de volta ao produto em loop fechado é o que tornaria platform active — o gatilho (DEPLOY_ENABLED) segue ausente. O gate_saida: { id: operavel, status: planned } do papel não muda; docs/fluxo.yml só ganha uma nota na saída telemetria-consolidada dizendo que a versão manual/sob-demanda já é real.

dbre vira dois scripts mecânicos (RN-400..403, ADR 0093)

Do papel dbre do fluxo.yml, só dois dos quatro entregáveis dependem de volume real de dados (plano-de-capacidade, tuning — seguem lacuna, sem prazo). Os outros dois não precisam de carga real: parecer-de- migracao é reconhecimento de padrão em texto SQL (apps/api/scripts/lint-migracao.ts, varre apps/api/src/db/migrations/ *.sql, sinaliza DROP TABLE/TRUNCATE/DROP COLUMN/ALTER COLUMN ... TYPE/ADD COLUMN ... NOT NULL sem DEFAULT, != 0 se achar algo — hoje acha 3 ocorrências em migrations já mergeadas e aceitas, informativo, não corrigidas de passagem); backup-restore-testado já era real desde a Fase 5 (CronJob + backup_runs + make test-restore executado, RTO ~40s) — faltava só um relatório sob demanda (relatorio:backup, mesma leitura do DomainGaugesCollector.collectBackup()). Nenhum dos dois entra em CI ainda: o linter varre o repositório inteiro, não o diff de uma PR, e viraria gate que reprova PR por achado que não é dela — ver o ADR 0093 para a técnica de escopo (pr-police.ts) quando isso mudar. A regra de "uma migration por onda" (meta/_journal.json) deixou de ser chamada de "versão mecanizada" deste papel: ela evita conflito de snapshot entre agentes em paralelo, preocupação ortogonal a "este SQL tem um padrão arriscado".

Auditoria fluxo.yml × código — Onda 1: fluxo.yml/gates.yml em dia

Primeira das seis ondas do plano da auditoria (seção D, docs/explanation/auditoria-fluxo-vs-codigo.md) a fechar — as Ondas 3, 4 e 5 já tinham sido antecipadas fora de ordem (analytics/DORA, gate implementavel, QA-estratégia/appsec); esta é só metadado, sem ADR, sem linha de código de produção.

docs/gates.yml (paralelismo-autorizado): status: planned → active, com evidencia no MESMO formato do gate implementavel vizinho — event_types: [proposed_action.created/approved/denied], filtro: { actionType: parallelize }, onde: request-parallelization.use-case.ts (achado A1/B5 — o fluxo.yml já dizia ativo desde o ADR 0053/FASE 14d, só o gates.yml ficou para trás). docs/fluxo.yml ganhou mais três correções da auditoria, todas de citação ou rótulo, nenhuma de comportamento: a máquina de estados do dev passou de 4 para 5 estados (awaiting_approval entrou, Fase 12e/ADR 0052 — A3); a citação de RN-160 saiu da entrada backlog-promovido do Arquiteto e foi para onde ela realmente se aplica, a saída handoff-duplo (o botão "Confirmar arquitetura pronta" que ela trava — A4); a citação de RN-161 saiu de handoff-duplo e virou nota na entrada do Dev Lead (é o passo de ACEITAR o handoff que encadeia ativação de execução, não o duplo handoff em si — A5); e a origem de worktree-por-agente do dev deixou de dizer harness (Engine.Dev.WorktreeManager não é um dos 4 componentes do papel harness — A8). docs/explanation/backlog.md também foi atualizado: a linha do gates.yml desatualizado saiu da tabela de pendências (fechada aqui), junto com o gate implementavel (B3, já fechado pelo ADR 0090 antes desta onda, sem ninguém ter tirado da tabela) e a parte de B7 (deployment frequency/lead time) que o analise:funil (ADR 0089) já entrega de verdade — MTTR/change failure rate continuam de fora, mas como lacuna PERMANENTE já declarada em fluxo.yml, não pendência de engenharia.

Verificação: pnpm --filter api validacao:gates -- --sem-banco (registro válido, sem ativo-sem-evidencia) e pnpm docs:check verdes. A fase 3 do script (evidência real no event log) exige --projeto <uuid> contra um banco com dados — fora do alcance desta correção, que é só de metadado.

Pendentes do mesmo plano, não desta onda: Onda 2 (delegação Dev Lead → dev, RN-160 revalidada no backend) e Onda 6 (gate necessidade-validada, que exige ADR e decisão de produto antes de codar).

Auditoria fluxo.yml × código — Onda 2: RN-160 no backend e delegação Dev Lead → dev (RN-404/405, ADR 0094)

Segunda das seis ondas do plano da auditoria (seção D), os DOIS itens de maior escopo depois da Onda 1 — os outros quatro (3, 4, 5 antecipadas fora de ordem; 6 ainda pendente, exige ADR e decisão de produto). Os dois vivem exclusivamente em apps/api; nenhuma mudança em engine/web foi necessária.

B6 — RN-160 revalidada no backend. A regra ("Confirmar arquitetura pronta" exige ao menos 1 história promovida) só era garantida no CLIENTE (SessionPage.tsx, hasPromotedStory) — uma chamada HTTP direta a POST /agents/arquiteto/handoff-infra ignorava a regra por completo. OfferInfraHandoffUseCase.execute agora consulta StoryRepository.findByProject e recusa com BadRequestException ANTES de gravar o evento ou chamar o engine, quando nenhuma história tem status !== 'draft'.

B1 — a delegação Dev Lead → dev vira dado. O ADR 0053 (item 5) já previa isto e declarou fora de escopo à época — CLAUDE.md listava como corte reversível. Fechado aqui: AcceptParallelizationUseCase.execute (lado API, cobre os caminhos direto E aprovado de graça, porque os dois já convergem nele) grava delegations com area: 'dev', leadAgent: 'dev-lead', subagent = o id do dev-<modulo> recém-ativado. Diferente de QA/Infra (que gravam do lado ENGINE, porque é lá que o subagente produz um parecer), status: 'completed' foi REDEFINIDO para esta área: significa "a delegação foi EFETIVADA" (o agente subiu), não "parecer emitido" — e parecerArtifactId aponta para o artifact.module_map mais recente do projeto (via SessionEventRepository.listByTypeForProject, método já existente, sem consulta nova). Sem module_map no projeto — não deveria acontecer —, a delegação não é gravada com id falso: só loga o estado inesperado e segue, porque a ativação do dev agent já é sucesso quando essa gravação é tentada; falha em RecordDelegationUseCase também não derruba a ativação, pela mesma razão. Decisão registrada no ADR 0094, que também revoga o corte do ADR 0053 item 5.

docs/fluxo.yml (dev-lead, saída delegacao): status: lacuna → status: ativo, regra: RN-405.

Auditoria fluxo.yml × código — Onda 6 (última): o gate necessidade-validada (RN-406, ADR 0095)

Fecha a ÚLTIMA das seis ondas do plano da auditoria (seção D, docs/explanation/auditoria-fluxo-vs-codigo.md) — as seis fecharam: a 3, 4 e 5 tinham sido antecipadas fora de ordem (analytics/DORA, gate implementavel, QA-estratégia/appsec, ADRs 0089/0090); a 1 e a 2 fecharam nos PRs anteriores (fluxo.yml/gates.yml em dia; RN-160 no backend + delegação Dev Lead → dev). Esta é a 6ª: o gate necessidade-validada (Criativo → PO), declarado proposto desde o ADR 0085 sem mecanismo nenhum atrás.

modelo-de-time.md já registrava por que ele ficou parado: o Criativo (o modelo) decidir sozinho que a necessidade que ele mesmo produziu está validada seria autovalidação, não gate de verdade. A decisão do dono do produto foi confirmação humana SEPARADA — um terceiro botão em SessionPage.tsx, "Confirmar necessidade validada", no MESMO padrão de "Confirmar arquitetura pronta" (RN-160): rota dedicada (POST .../agents/criativo/validate-necessity), caso de uso dedicado (ValidateNecessityUseCase), evento próprio (necessity.validated). Nunca reaproveitou confirm_readiness/RN-142 (que continua sendo só o piso estrutural, "≥1 regra capturada") nem o aceite do handoff pelo PO (AcceptHandoffUseCase, estrutural, sem julgamento de conteúdo).

O encadeamento escolhido: o botão só habilita DEPOIS que confirm_readiness já consolidou o artifact.product_brief (hasProductBrief) — não faz sentido "validar" um resumo que ainda não existe, e é a leitura mais consistente com docs/fluxo.yml (o gate é de SAÍDA do Criativo, o momento em que o trabalho dele já entregou um artefato concreto). Diferente de OfferInfraHandoffUseCase, esta confirmação NÃO sinaliza o engine — o handoff Criativo→PO já aconteceu dentro do próprio confirm_readiness, então o evento novo é só o registro do MÉRITO, sem gatilho nenhum esperando por ele.

docs/gates.yml ganha o gate necessidade-validada: active, aprovacao_humana: true, mas severidade: warn — nada no produto hoje CONSULTA a passagem deste gate antes de deixar o PO seguir, diferente dos block de story-promovida/plano-de-adocao, que têm trava real de código por trás. docs/fluxo.yml (criativo, gate_saida): status: proposto → status: ativo, regra: RN-406.

Auditoria fluxo.yml × código — B4 (a ÚLTIMA pendência): o PO lê métricas de produto (RN-407)

Fecha o item B4 — a última linha da tabela "Backlog do modelo de time (ADR 0085)" em docs/explanation/backlog.md; a tabela fica VAZIA depois desta correção, e a auditoria fluxo.yml × código termina aqui. Sem ADR novo: segue o mesmo padrão já estabelecido pela RN-164 (leitura de agente escopada ao projeto, sem efeito externo, sem proposed_action), só com uma RN nova. docs/fluxo.yml (papel po, entrada metricas-de-produto): status: lacuna → regra: RN-407.

O DADO já existia desde o ADR 0089 (analise:funil — funil real sessão → commit → PR → merge, lead time real, deployment frequency real); faltava só o PO conseguir LER esse relatório dentro do turno. Terceira ferramenta de leitura do PO, listar_metricas_de_produto (GET .../product-metrics), mesmo desenho exato das duas irmãs da RN-164 — :direct, sem parâmetro, escopo fechado no projeto. As funções de CÁLCULO puras e a query (calcularFunil/calcularLeadTimes/ leadTimeMedioMs/deploymentFrequencyPorDia/buscarAcoesGitDoFunil) migraram de apps/api/scripts/analise-funil.ts para apps/api/src/application/services/funil-metrics.ts — refatoração MECÂNICA e necessária (scripts/ não pode viver em src/, e src/ não importa de scripts/): o script passou a REEXPORTAR dali, sem mudar assinatura nem comportamento, e o teste de regressão do script continua verde sem ser tocado. O corpo JSON não tem campo para as três ausências permanentes que o script já declarava só em texto (funil de produto completo ideação → commit, evidência de adoção por feature, MTTR/change failure rate) — a ferramenta do PO as cita pelo nome no TEXTO que devolve ao modelo, sempre, para ele nunca concluir por omissão dos números que não há lacuna.

GOVERNANCE.md — o critério do modo community, não o mecanismo dele

Item do backlog anterior ("Modo community do approval-ladder") virou correção de registro, não implementação: investigação confirmou que o modo community já existe em código desde a FASE 6 (scripts/ci/approval-ladder.ts), testado nos dois lados, só desligado por APPROVAL_MODE=solo (default). A frase do backlog ("vira mudança de aprovacao_humana no registro de gates") vinha de uma linha ESPECULATIVA do ADR 0054 — não editada, por regra, mas a imprecisão está registrada em docs/explanation/backlog.md: aprovacao_humana do gate aprovacoes-da-escada já é true fixo nos dois modos, sem campo nenhum no schema do Gate que reaja a APPROVAL_MODE.

O que faltava de verdade era outro TODO(humano), em docs/explanation/branching-policy.md: o critério de quem entra em cada lista de aprovadores (devs/po/gestão) — quem entra, quem sai, com base em quê. GOVERNANCE.md (raiz do repo, novo) fecha isso: MECANISMO continua em branching-policy.md (a escada, pessoas distintas, o passo a passo da troca de variável), CRITÉRIO passa a morar em GOVERNANCE.md. É proposta, não comitê em funcionamento — o projeto continua com um mantenedor só, e o documento é explícito sobre isso: a maior parte dele descreve o que passa a valer QUANDO houver mais gente, não uma estrutura já ativa. Regra warn nova no docmap (governanca) cobra revisão quando approval-ladder.ts/ branching-policy.md mudam — separada da regra block existente (politica-de-branches) pelo mesmo motivo que checks-e-rulesets é separada dela: MECANISMO decorre do código, CRITÉRIO é decisão humana.

SMTP real no MailSender (RN-408, ADR 0096)

Fecha o item de backlog aberto desde o corte do Keycloak (ADR 0032, "SMTP real continua sendo config futura"). nodemailer sobre transporte SMTP puro (protocolo de LINHA com estado/MIME/STARTTLS/AUTH, diferente das APIs JSON HTTP que o resto do produto integra), atrás de MAIL_TRANSPORT — log (default, INCLUSIVE em produção: enviar e-mail de verdade é opt-in do operador) ou smtp, selecionado por useFactory em AuthUseCasesModule sem tocar nenhum caso de uso (RegisterUseCase, RequestPasswordResetUseCase, LoginUseCase, o script migrate-keycloak-users.ts continuam só injetando MailSender). SMTP_HOST/SMTP_USER/SMTP_PASSWORD/SMTP_FROM seguem o padrão RN-114 em produção quando o modo é smtp — mas SEM o default público que as quatro RN-114 originais têm, então a régua só entra quando o operador optou por smtp; a validação roda no construtor de SmtpMailSender, exercitado pelo useFactory na montagem do grafo de providers (mesmo desenho de CREDENTIALS_MASTER_KEY), não por chamada eager em main.ts. Corpo em TEXTO PURO, nunca HTML — a porta não carrega estrutura para corpo rico, e um template engine seria superfície de injeção sem ganho nenhum.

Investigação achou uma lacuna real antes de codar: email_verification não tinha rota web nenhuma — verificarEmail existia em apps/web/src/lib/auth.ts desde a Fase 7a sem NENHUM chamador, então o link do e-mail de verificação levaria a lugar nenhum assim que SMTP passasse a mandar e-mail de verdade. /verificar-email (VerifyEmailPage.tsx) fecha isso, espelhando SetPasswordPage.tsx — mesmo padrão de validateSearch no router.tsx, mesma resposta única para link inexistente/expirado/já usado — com uma diferença: sem formulário, a confirmação dispara sozinha ao montar, então a tela precisa dos três estados da RN-088 (carregando/erro/sucesso), não só dois.

"N agentes online" no dashboard — status AO VIVO, nunca presença histórica (RN-409, ADR 0097)

Item do backlog anterior fechado. Investigação prévia confirmou que não existia agregado de liveness nenhum, nem por projeto nem por workspace — só presença HISTÓRICA (RosterFacts, "já apareceu alguma vez") e status ao vivo de verdade só no cliente, derivado do event log, só quando um projeto está ABERTO. ProjectCardSummary.onlineAgentCount soma dois mecanismos pela MESMA régua ("não ocioso, não travado"): dev agents (engine.dev_agent_states.status NOT IN ('idle', 'idle_tripped'), agregado em lote no engine) e agentes conversacionais (último agent.status da sessão mais recente com status !== 'idle', payload que só aceita working/idle/awaiting_approval). QA/SecOps nunca contam — não emitem agent.status, veredito único por invocação, sem noção de "ocioso" entre chamadas.

A decisão de arquitetura que vale registrar: a consulta lê engine.dev_agent_states DIRETO, via SQL cru batelado por workspace, dentro do MESMO Promise.all que já soma onze consultas do read model de RN-090 (DOZE → CATORZE, ainda CONSTANTE) — em vez de abrir uma rota HTTP interna nova no engine. api e engine compartilham o MESMO banco físico, separados só por schema Postgres, e já havia precedente (apps/api/scripts/medir-execucao.ts já lê engine.oban_peers); esta decisão eleva o padrão de script manual para código de produção TESTADO (fixture mínima e declarada da tabela do engine em test/support/global-setup.ts). Consequência aceita e declarada no ADR 0097: a consulta pressupõe que quem opera o produto migra os dois lados juntos (db:migrate E engine:migrate), e falha alto/visível se só a api migrou — sem try/catch escondendo o erro.

Sessão de execução não fecha mais com dev agent pendente (RN-411)

Correção de defeito achado numa sessão de execução REAL, não item de roteiro: cinco dev agents subiram, ficaram idle_tripped (RN-047, o circuit breaker) travados esperando o usuário desbloquear tarefa a tarefa, e o heartbeat de 30s da sessão (Engine.Sessions.SessionServer) fechou a sessão por baixo enquanto o desbloqueio manual continuava. O terceiro sinal de GetSessionPendingWorkUseCase (RN-064) só lê agent.status, vocabulário dos agentes conversacionais — dev agents falam dev.* (Engine.Dev.AgentIo) e nunca emitem agent.status, então nenhum dev agent jamais segurava a sessão por esse sinal. O quarto sinal lê o último evento dev.* de cada dev-<modulo>/dev-<modulo>-2 na sessão: dev.working/dev.blocked/dev.idle_tripped seguram a sessão (travado esperando decisão humana É trabalho pendente); só dev.idle (drenado de verdade) não segura. dev.awaiting_gate/dev.awaiting_approval ENTRARAM na régua pela RN-412 — ver abaixo; a lacuna residual que este parágrafo descrevia foi fechada.

Workspace pessoal automático no cadastro (RN-410)

Achado navegando: o botão "Novo projeto" do dashboard não fazia NADA, sem erro. Causa raiz: RegisterUseCase e SocialLoginCallbackUseCase (no ramo que provisiona conta nova) criavam usuário e credencial mas NUNCA um workspace — TODO cadastro novo caía nessa parede, e só não aparecia antes porque seed.ts sempre cria um workspace junto dos dados de demonstração. Os dois pontos agora criam o workspace e adicionam o usuário como owner na MESMA transação que já cria a conta. Nome/slug saem de uma função pura única, nomeESlugDoWorkspacePessoal (domain/auth/personal-workspace.ts), para a regra não divergir em dois arquivos; o slug é sempre sufixado com userId.slice(0, 8) (mesmo padrão de extraDevAgentId) para ser único sem round-trip ao banco (RN-410).

413 nas PRs e Executores "sem execução" — as duas pontas e o encadeamento (RN-412, ADR 0098)

Não é fase planejada: correção pedida por USO real ("nas PRs sempre está estourando entity too large") mais o defeito irmão da aba Executores aparecer vazia com trabalho real rolando. Investigação achou os dois encadeados: o gate morre com 413 → devs ficam presos em dev.awaiting_gate → esse estado não segurava a sessão (lacuna da RN-411) → o heartbeat de 30s fecha a sessão por baixo → a aba exige sessão ativa e some com o roster inteiro.

A causa do 413 era da API do Brabo, não do provider de LLM — os comentários que atribuíam o erro ao provider (runtime.exs, terminal_executor.ex, docker-compose.yml) estavam errados; FalhaDeTurno.origem({413, _}) já classificava como "codigo", e estava certo. apps/api/src/main.ts nunca configurou limite de body do Express — valia o default de 100 KB, muito abaixo dos 8 MB que o Phoenix aceita, no sentido mais pesado do transporte (engine → api, POST /internal/sessions/:sessionId/llm-turn, que reenvia o histórico INTEIRO a cada iteração do ToolLoop). API_JSON_BODY_LIMIT (default 10 MB) fecha a ponta do transporte.

A segunda ponta, sem a qual subir o limite só adiaria o estouro: o ContextManager do engine (ADR 0098) tinha DOIS defeitos que mantinham a compactação inalcançável antes do corpo estourar — estimate/1 contava só content, então mensagens de assistant cheias de toolCalls custavam ~zero tokens na estimativa; e a janela de compactação usava só context_window (128.000 tokens nos agentes de gate), o que dava ~350 KB antes de compactar — bem depois de qualquer teto de transporte razoável. A janela EFETIVA agora é min(context_window, teto_de_transporte), com o corte sempre em FRONTEIRA DE ITERAÇÃO do ToolLoop (nunca separando um assistant com toolCalls dos role: "tool" que o respondem — quebraria o protocolo de tool-use do provider).

RN-412 estende a RN-411: DEV_PENDING_TYPES ganhou dev.awaiting_gate (o argumento original — gate pode morrer e travar o dev agent nesse estado indefinidamente) e também dev.awaiting_approval, por um argumento novo achado na investigação: a decisão de aprovação grava proposed_actions.status de forma SÍNCRONA, mas a retomada do dev agent via outbox/Oban é ASSÍNCRONA — na janela entre as duas, nada segurava a sessão, o mesmo defeito da RN-411 um nível mais fundo.

A aba Executores/Visão Geral tinham o defeito irmão do lado web: executionActivated era derivado de events.some(...) sobre a janela de só 200 eventos de useSessionEvents — execution.activated é dos PRIMEIROS eventos de uma sessão e saía da janela em qualquer execução real, apagando o roster inteiro. O valor correto já existia, agregado sobre TODOS os eventos, no resumo do workspace (RN-090, projects-summary.repository.ts) — as duas telas passaram a consumir esse valor. Achado de bônus no caminho: ExecutionSection (Visão Geral) recalculava activated de novo, independentemente, sobre a mesma janela — uma execução longa voltava a oferecer "Ativar execução" como se nunca tivesse começado; corrigido junto. gatesEverOpened sofre da MESMA classe de defeito e ficou DECLARADO como limitação conhecida, não corrigido aqui — corrigi-lo exigiria mudar a assinatura de deriveAgentRoster/rosterFactsFromEvents, fora do escopo desta correção pontual.

Neo4j como grafo de conhecimento — a fundação (RN-413/414/415, ADR 0099/0100)

Pedido do dono do produto: Neo4j + ollama-model-loader (gemma:1b, yi-coder:1.5b, nomic-embed-text), inspirado explicitamente no repositório ErickWendel/neo4j-ai-experiments — agradecimento registrado no ADR 0099 e em prompts/README.md, pela inspiração concreta do padrão "prompt como arquivo versionado, não string presa no código" e do grafo como memória de agentes.

Duas responsabilidades, uma fundação: templates de prompt versionados (PromptTemplate/PromptVersion, idempotente por hash — RN-413) e memória relacional (interações, hipóteses do Psicólogo com evidência, perfis da Anamnese, handoffs — consumo real é onda futura, esta entrega só constrói o mecanismo). O grafo é memória DERIVADA, nunca fonte de verdade; pgvector continua sendo o índice vetorial dos chunks, sem duplicar embedding em dois bancos. Driver neo4j-driver na api (não no engine) — a api já é dona de toda persistência e do RAG, o engine consome por HTTP interno com service token, como sempre.

rag_search (RN-414) fecha o maior vão do RAG existente: nenhum agente o consultava, só a aba web "Chat RAG". Tool nova no engine, tetos próprios (RN-150), degradação sempre visível no início do texto ao modelo. ollama-model-loader (RN-415) fecha um bug real separado: nomic-embed-text nunca era puxado automaticamente e o RAG degradava pra léxico-only em silêncio em qualquer ambiente limpo.

Primeira leva de templates extraída para prompts/*.md (ux-designer, Psicólogo, Anamnese, sumarização do ContextManager).

Consumo do grafo — templates sem tocar GenServers, relevância sem substituir recência (RN-416/417, ADR 0101)

InstructionFiles ganhou fonte :graph (db > graph > dir > root — o instruction_patch do usuário continua vencendo tudo), e TRÊS agentes passaram a TENTAR resolver template do grafo antes do texto inline, que virou fallback obrigatório: ux-designer (graph_instruction_templates_enabled?), Psicólogo e Anamnese (graph_templates_enabled?, mesma chave pros dois). Duas flags, não uma — colidiriam com defaults CONTRÁRIOS entre frentes paralelas se dividissem a chave; os dois defaults são false. Só context-manager-summarize (dos quatro templates da leva) ficou sem consumidor à época — lacuna que a ADR 0101 declarou aberta e que fechou depois, pelo QUARTO consumidor da MESMA graph_templates_enabled?: o prompt de sumarização do ContextManager (prompt/1, ver a RN-417).

Psychologist.ContextBuilder/Anamnese.ContextBuilder ganharam uma SEGUNDA fonte de contexto — rag_search, com query derivada do gatilho da análise — em COMPOSIÇÃO com a leitura de recentes/janela temporal de sempre, nunca em substituição: sem hit, o comportamento é idêntico ao de antes. Os trechos entram no orçamento EXISTENTE de Triage, e degraded: true do RAG é sempre visível no contexto, nunca escondido.

O grafo se escreve sozinho: GraphProjector (api) drena uma SEGUNDA linha de outbox (aggregateType: 'graph_projection', mesma transação, valor que o filtro do engine — IN ('session', 'task') — nunca casa, evitando a corrida com o Engine.Outbox.Drain que já drena 'session' em ~2s) e chama os casos de uso de gravação já existentes. O engine NUNCA escreve no grafo direto — preserva o event log como única fonte de verdade; o grafo é reconstruível por replay.

Achado real durante a verificação, registrado porque ensina: o teste de body limit da RN-412 (apps/api/test/main.spec.ts) tinha um bug LATENTE — AppModule importado ESTATICAMENTE no topo do arquivo avalia DrizzleModule (que cria seu pool de conexão em escopo de MÓDULO, não numa factory do Nest) ANTES do beforeAll conseguir apontar DATABASE_URL pra base de teste. Ficou mascarado enquanto a base de dev local também estava migrada (mesma tabela, então a query "achava" mesmo apontando pro lugar errado); destravou assim que a base de dev foi recriada vazia (pelo teste de fumaça real desta própria onda contra Neo4j+Postgres). Corrigido com await import(...) DINÂMICO dentro do beforeAll, depois do DATABASE_URL já setado — é o padrão a seguir sempre que um teste precisa redirecionar DATABASE_URL para um AppModule real.