remendo de sintoma
Esconde o sintoma envolvendo o crash. O 500 pode parar de aparecer, mas o null deref continua ali esperando.
Veredito do contrato: falha — sem causa raiz.
LEARN olhou. ANALYZE escolheu. Agora você constrói — mas exatamente uma coisa, a única unidade delimitada mais valiosa, e nada mais. EXECUTE não é "digitar código até parecer pronto." É um contrato: leia por inteiro, encontre a causa raiz, faça a coisa mais simples que atende ao escopo, adicione a verificação que prova isso, rode essa prova e registre-a. A disciplina está na delimitação.
Cada volta do loop tem cinco passos — LEARN → ANALYZE → EXECUTE de uma unidade delimitada → VERIFY no boundary real → DECIDE. Esta lição é o terceiro passo, aquele em que o trabalho de fato acontece. Tudo antes dele era preparação; tudo depois dele é julgamento. EXECUTE é onde você faz uma mudança.
A armadilha é óbvia uma vez nomeada: quando você finalmente vai construir, é tentador consertar cinco coisas já que está ali dentro. Você veio reparar uma torneira pingando e acaba refazendo o encanamento do banheiro inteiro. Isso parece produtivo. É a forma mais comum de uma volta do loop dar errado — porque agora nada é pequeno o bastante para provar, e se algo quebrar você não sabe qual das cinco mudanças causou.
Então o EXECUTE tem uma regra acima de todas as outras: faça a única unidade delimitada mais valiosa e pare. "Delimitada" significa que a mudança tem uma borda que você consegue apontar — estas linhas, este arquivo, este único comportamento. "Mais valiosa" significa que o ANALYZE já a classificou; você não rediscute isso aqui. Você lê tudo primeiro, encontra a causa real (não o sintoma), faz a mudança mais simples que satisfaz o escopo, adiciona uma verificação que a prova, roda essa prova e registra o que fez. Essa sequência é o Contrato de Unidade, e o resto desta lição o ensina por seis ângulos.
Pense nisso como… um cirurgião com um único item na lista. Ele não abre você para um apêndice e decide também "dar uma arrumada" num joelho de quebra. Ele delimita o corte exatamente ao que foi combinado, confere a contagem de instrumentos antes de fechar e escreve a nota operatória. Delimitado, provado, registrado. A habilidade não é cortar mais — é cortar apenas o que foi delimitado, e provar que deixou o resto intacto.
Numa execução autônoma, AFK, o loop roda sem um humano aprovando cada diff. A única coisa que mantém isso seguro é que todo EXECUTE produz uma mudança pequena o bastante para o passo VERIFY provar ou rejeitar num boundary real. Um diff que toca um comportamento mapeia limpo para um Proof Gate; um diff que toca cinco comportamentos precisa de cinco provas e de um raio de impacto muito maior se tiver de ser revertido. Delimitar é o que torna o próximo passo — VERIFY — tratável.
O EXECUTE não tem o direito de dizer "pronto". Ele produz a mudança e a verificação que vai julgar a mudança, e então roda essa verificação. O veredito pertence ao VERIFY (a próxima lição), e num time pertence a um Validador independente — nunca a quem construiu. Então o trabalho do passo EXECUTE é deixar para trás algo que seja barato e honesto de verificar: um teste que falhava e agora passa, um comando cujo código de saída vira, uma observação de boundary que muda. "Adicionei a verificação que prova e a rodei" é o entregável; "funciona" não é algo que o EXECUTE tenha permissão de afirmar.
Se, no meio da construção, você descobre uma segunda mudança valiosa, a jogada correta é registrá-la como uma nova unidade e devolvê-la ao ANALYZE para classificação — não embuti-la no diff atual. O escopo foi definido na lição 2; o ANALYZE escolheu uma unidade dele na lição 4. O EXECUTE honra essa escolha. Alargar o escopo silenciosamente no meio da volta quebra o contrato que deixa o loop rodar sem supervisão.
O EXECUTE não é livre. É uma sequência fixa de seis jogadas, e pular uma é como as voltas dão errado. Leia como um checklist que você roda toda vez, por menor que a mudança pareça.
Leia toda a superfície relevante antes de editar um caractere — a função e quem a chama, o teste que a cobre, a issue/escopo, as fontes confiáveis do LEARN. A maioria das correções ruins é ruim porque quem construiu editou a primeira linha plausível sem ler a segunda, que explicava por que ela foi escrita daquele jeito.
Nomeie a causa de fato, não o sintoma. Um erro 500 é um sintoma; "o handler desreferencia user antes da checagem de null" é uma causa. Depois escreva o menor plano que ataca essa causa e desenhe sua borda: quais linhas, qual arquivo, qual único comportamento muda. A borda é o limite.
Entre os planos que satisfazem o escopo, pegue o de menos partes móveis. Não o mais esperto, não o mais geral, não aquele que "também nos prepara para" um recurso futuro. Simplicidade aqui é o que mantém o diff dentro da borda e mantém a prova barata.
Escreva a verificação antes de acreditar na mudança. Um teste de regressão que falha no código antigo e passa no novo; uma asserção; um comando cujo código de saída vira. A verificação é a espinha do contrato — é o que transforma "acho que funciona" em algo que o VERIFY pode confirmar num boundary real.
Rode de verdade, no boundary real — não na sua cabeça, não como mock, não como alegação. Se passar, entregue ao VERIFY. Se falhar, você volta à jogada 2 e replaneja a mesma unidade; você não absorve uma correção nova para fazer a falha sumir.
Uma linha em LOOP-LOG.md: qual unidade, qual causa raiz, qual prova, passou/falhou. É isso que torna a execução observável a um humano que nunca toca na construção — o registro durável de que o contrato foi honrado.
O EXECUTE é o meio do ciclo — alimentado pela unidade escolhida pelo ANALYZE, entregando uma mudança provada ao VERIFY. É o único passo que escreve no artefato, e é exatamente por isso que ele precisa ser o mais delimitado.
Aqui está o Contrato de Unidade para uma mudança real, disposto como um plano em etapas. A faixa é o contrato; cada cartão amplia uma jogada com suas tarefas concretas, a barra de saída que permite avançar e os riscos de pulá-la. Clique numa etapa — ou foque a faixa e use as setas — para abrir seu cartão.
O exemplo recorrente desta lição: um endpoint de login que lança um 500 quando o e-mail é desconhecido. O ANALYZE já o classificou como a unidade mais valiosa. Agora estamos executando-o.
Objetivo: entender a superfície inteira antes de tocá-la. Leia o handler, quem o chama e o teste que o cobre — para que a correção ataque a forma real do código, não um chute.
Objetivo: nomear a causa de fato e desenhar a borda da correção. Sintoma: 500 com e-mail desconhecido. Causa: o handler chama user.hash antes de checar que user existe.
401, mensagem genéricaObjetivo: fazer a menor mudança que corrige a causa nomeada — um único guard que retorna um 401 genérico quando o usuário está ausente, antes de qualquer propriedade ser lida. Sem refatoração, sem nova abstração.
if (!user) return 401Objetivo: escrever um teste de regressão que falha no código antigo e passa no novo. Faça POST de um e-mail desconhecido; afirme que o status é 401, não 500. A verificação é a espinha do contrato inteiro.
401Objetivo: rodar a verificação no boundary real e escrever uma linha de registro. O EXECUTE não declara vitória — ele produz uma prova e uma entrada de log, depois entrega o veredito ao VERIFY.
LOOP-LOG.mdCada jogada só avança quando sua barra de saída é cumprida — e as barras são escritas como coisas que você consegue checar, não vibrações. "O diff é um punhado de linhas, um arquivo" é checável; "o código parece limpo" não é. É a mesma disciplina de gate que o VERIFY usa, aplicada dentro de um único EXECUTE para que a unidade permaneça honesta sob pressão de tempo.
A faixa parece linear, mas a Jogada 5 tem uma aresta de volta: uma prova que falha leva você de volta à Jogada 2 com o mesmo escopo. A tentação quando um teste não passa é "só mudar também" algo adjacente. Isso alarga a borda e quebra a atribuição. O contrato diz: replaneje dentro do limite, ou separe uma nova unidade — nunca aumente esta silenciosamente.
Cada segmento carrega done / active / todo; selecionar um troca o role="tabpanel" visível. Numa execução ao vivo, esses estados vêm do tracker, então a faixa reflete a realidade, não o plano como escrito.
A Jogada 3 diz "a coisa mais simples que atende ao escopo". Mas costuma haver mais de um jeito de corrigir a mesma causa. Antes de se comprometer, vale colocar os candidatos lado a lado e sentir os trade-offs deles contra o limite. Escolha uma correção abaixo; o diagrama e a nota de trade-off se atualizam juntos.
As três corrigem o 500. Elas diferem em quanto tocam, quanto risco adicionam e quão bem se encaixam no escopo de uma unidade. O contrato escolhe a que corrige a causa com menos partes móveis — observe os medidores.
Adiciona uma única linha que retorna um 401 genérico quando o usuário está ausente, antes de qualquer propriedade ser lida. Corrige a causa nomeada e nada mais.
Extrai um utilitário requireUser() compartilhado e roteia este handler — mais dois outros — por ele. Arrumado no abstrato, mas mais largo que a unidade.
Reestrutura o fluxo de login inteiro "já que estamos aqui" — sessões, caminhos de erro, logging. O clássico desvio de escopo que parece diligência.
A Correção B e a Correção C podem ser "melhor engenharia" no vácuo. Mas a jogada 3 do Contrato de Unidade é "a coisa mais simples que atende ao escopo" — e o escopo é um handler, um comportamento. A Correção A é a única candidata cuja borda é igual à borda da unidade, então é a única que um único Proof Gate cobre por inteiro e que um único revert desfaz de forma limpa. O helper e a reescrita são ideias reais — elas só pertencem a unidades próprias, registradas e classificadas pelo ANALYZE.
Manter três candidatas lado a lado é boa prática — é como você confirma que a mais simples de fato corrige a causa. A disciplina é que a exploração termina numa escolha, e a escolha respeita o limite. Você compara para estreitar, nunca para justificar fazer as três.
"A coisa mais simples que atende ao escopo" tem uma segunda metade silenciosa: ela também precisa ficar dentro das restrições do projeto. O escopo diz o que mudar; as restrições dizem como qualquer mudança deve se comportar — as regras de segurança, estilo e safety que valem em toda a base de código. Uma unidade que corrige o bug mas viola uma restrição não está pronta.
Abaixo estão as restrições que o projeto desta lição carrega, mostradas do jeito que um design system mostra seus tokens: um conjunto nomeado que você consegue percorrer, uma tabela que diz exatamente onde cada uma se aplica e pares fazer / não fazer para aquela que estamos prestes a tocar.
| Restrição | Regra | O que ela força nesta correção |
|---|---|---|
| no-enumeration | Respostas de auth genéricas | A mensagem do 401 não pode revelar se o e-mail existe. |
| status-codes | Auth nunca retorna 500 |
O ponto inteiro: um usuário ausente é um 401, não um crash. |
| validate-first | Cheque antes de ler | Faça guard de user antes de tocar user.hash. |
| one-behavior | Um comportamento, um arquivo | Só o caminho do usuário desconhecido muda; todo o resto está congelado. |
| proof-required | Entregue uma verificação | Um teste que falha no 500 e passa no 401 deve acompanhar o diff. |
no-enumerationUma resposta genérica para os dois modos de falha. Um atacante não consegue distinguir "conta inexistente" de "senha errada", então não consegue enumerar e-mails válidos — e o status é o 401 do contrato, nunca um 500.if (!user || !ok) return res.status(401).json({ error: 'bad_credentials' });
Retornar um 404 no_such_user distinto faz o 500 parar — mas agora vaza quais e-mails estão registrados, violando no-enumeration. O bug sumiu; a unidade ainda não está pronta.if (!user) return res.status(404).json({ error: 'no_such_user' });
O escopo é a borda desta unidade — quais linhas você pode tocar. As restrições são invariantes globais que toda unidade deve respeitar, não importa o que toque. São cercas independentes: você pode satisfazer o escopo (um diff minúsculo) e ainda falhar uma restrição (um 404 que enumera contas), ou honrar toda restrição enquanto estoura o escopo (uma reescrita completa limpa de restrições). O contrato exige os dois: dentro da borda e dentro das regras.
Num loop real, elas vêm do registro durável do projeto — o bloco de constraints do GOAL.md, um doc CONTEXT/ADR, a config do linter. O EXECUTE as lê como parte da jogada 1 ("ler por inteiro"), para que a correção mais simples seja escolhida a partir do conjunto que já as satisfaz, não remendada depois que um revisor pega uma violação.
O hábito mais importante no EXECUTE é manter a borda parada. Aqui está o mesmo ponto de partida levado de dois jeitos — um fica dentro do limite, o outro cresce silenciosamente até nada ser provável.
Nem toda mudança pequena é uma boa unidade delimitada. Há diferença entre um remendo rápido que esconde o sintoma e uma correção delimitada limpa que resolve a causa — as duas podem ser minúsculas. A matriz dispõe a mesma unidade de três jeitos para você ver qual "pequeno" de fato satisfaz o contrato.
Leia como uma grade: cada linha é uma propriedade com que o contrato se importa, cada coluna é uma abordagem. Depois os cartões dizem quando cada uma é a jogada certa.
| propriedade \ abordagem | remendo de sintoma | correção delimitada limpa | grande reescrita |
|---|---|---|---|
| ataca a causa? | não — esconde o 500 | sim — faz guard do deref | sim (e muito mais) |
| tamanho do diff | minúsculo | pequeno | grande |
| fica na borda do escopo? | sim | sim | não |
| provável por uma verificação? | só o sintoma | sim — 500 → 401 | não — ampla demais |
| veredito do contrato | falha (sem causa raiz) | passa | falha (desvio de escopo) |
remendo de sintoma
Esconde o sintoma envolvendo o crash. O 500 pode parar de aparecer, mas o null deref continua ali esperando.
Veredito do contrato: falha — sem causa raiz.
correção delimitada limpa
Resolve a causa com a menor mudança no escopo, honrando toda restrição, com uma verificação que a prova.
Veredito do contrato: passa — esta é a unidade.
grande reescrita
Corrige tudo e mais um pouco — mas a borda se foi e uma prova não a cobre. As partes boas pertencem a unidades próprias.
Veredito do contrato: falha — desvio de escopo.
O remendo de sintoma é o menor diff dos três — e é o que mais falha o contrato, porque não ataca a causa nomeada (jogada 2). "Pequeno" não é o objetivo; "a coisa mais simples que corrige a causa dentro do escopo" é. Um try/catch que transforma um 500 em outro 500 é movimento sem progresso: a próxima requisição com e-mail desconhecido ainda bate no mesmo caminho quebrado.
Só a coluna do meio tem uma verificação que distingue corrigido de quebrado num boundary real (POST de e-mail desconhecido → esperar 401). O remendo só consegue "provar" que um erro foi engolido; a reescrita é ampla demais para qualquer verificação única cobrir. Provabilidade-por-uma-verificação é um teste afiado para saber se uma mudança é genuinamente uma unidade delimitada.
Das seis jogadas, a mais frequentemente abandonada é a jogada 4 — adicionar a verificação que prova a mudança. Parece custo extra quando a correção "obviamente funciona". Mas uma correção sem prova é só uma alegação, e o loop roda sobre provas, não alegações. O truque que torna a verificação confiável: ela precisa falhar primeiro no código antigo.
// falha no handler antigo (500), passa depois do guard (401) test('unknown email returns 401, not 500', async () => { const res = await request(app) .post('/auth/login') .send({ email: 'nobody@example.com', password: 'x' }); expect(res.status).toBe(401); // não 500 expect(res.body.error).toBe('bad_credentials'); // genérico — sem enumeração });
A verificação atinge a rota de verdade através do app, não uma função stubada — é isso que "boundary real" significa. Rode só este teste enquanto itera:
# rode apenas o novo teste de regressão npm test -- -t "unknown email returns 401" # esperado: vermelho no commit pré-correção, verde depois do guard
Adicionar e rodar esta verificação faz parte do EXECUTE. O julgamento independente — "sim, isto de fato atende ao done-when do escopo" — é o próximo passo, VERIFY, e num time é feito por um Validador que não escreveu a correção. O trabalho do EXECUTE é entregar uma mudança que seja barata e honesta de verificar: aqui, um único comando cujo código de saída diz a verdade.
A última coisa que o EXECUTE faz antes de o VERIFY assumir é ler o próprio diff com olhos de revisor. Abaixo está a mudança real da nossa unidade — linhas verdes adicionadas, vermelhas removidas — com os selos de risco que um construtor cuidadoso anexaria e notas de revisor fixadas em linhas específicas. Clique em qualquer linha com um ponto cor de barro para ler sua nota.
Esta é uma auto-revisão: pegar os problemas óbvios antes que um Validador independente (ou um humano lendo o log) os veja. Uma das notas é bloqueante — veja se você a encontra antes de ler todas.
Transforma um 500 no login com e-mail desconhecido em um 401 genérico em POST /auth/login. 1 arquivo alterado · +3 −2
Cada linha com ponto cor de barro carrega uma nota. Uma é bloqueante e precisa ser resolvida antes de a unidade ser entregue — abra as notas para encontrá-la.
Ler o próprio diff com selos de risco e notas de linha faz parte da higiene das jogadas 3–4: a mudança ficou no escopo, honrou as restrições e veio com uma prova? É o lugar mais barato para pegar um vazamento de enumeração ou uma linha reformatada à toa. Mas não é o veredito.
No loop, o julgamento de que a unidade de fato atende ao done-when é do VERIFY — e num time pertence a um Validador independente que não escreveu o código. A auto-revisão deixa a entrega limpa; ela não substitui o gate independente. Essa separação é exatamente o que mantém uma execução AFK honesta: quem construiu nunca é quem certifica.
Juntando o contrato inteiro na nossa unidade recorrente, jogada por jogada — exatamente o que um Executor produz em um passo de EXECUTE.
Ler por inteiro: o handler lê user.hash na linha depois de findByEmail, sem checagem de null. Causa: null deref quando o e-mail é desconhecido. Correção mais simples no escopo: um guard retornando um 401 genérico antes do deref. Sem helper, sem reescrita.
const user = await db.users.findByEmail(email); + if (!user) return res.status(401) + .json({ error: 'bad_credentials' }); const ok = await verify(password, user.hash);
Adicionar a verificação: POST de e-mail desconhecido, esperar 401 (falha no código antigo, passa no novo). Rodar a prova: a suíte fica verde no boundary real. Registrar — uma linha, depois entregar ao VERIFY.
LOOP-LOG.md — uma entrada## volta 7 — EXECUTE unit: fix-login-500 cause: null deref em usuário ausente change: +1 guard, src/routes/auth.ts proof: npm test -t "unknown email" → PASS scope: 1 arquivo · 1 comportamento · dentro do limite next: → VERIFY (Validador, não quem construiu)
Cinco perguntas sobre o EXECUTE. Escolha uma resposta para ver se está certa e por quê — recuperar vence reler. Sem pista na formatação; leia cada opção pelos próprios méritos.
Q1O que "uma unidade delimitada" significa no EXECUTE?
B. Delimitada significa que a mudança tem uma borda visível — um comportamento, uma intenção do tamanho de um arquivo — para que uma única prova a cubra e um único revert a desfaça. O tamanho do seu dia e "o que está no arquivo" não são o limite.
Q2No meio da construção você nota uma segunda correção valiosa. O que o contrato diz?
C. Uma correção nova é uma unidade nova. Registre-a e deixe o ANALYZE classificá-la; não alargue a borda atual nem abandone a unidade escolhida. Absorvê-la silenciosamente quebra a atribuição e o contrato que deixa o loop rodar sem supervisão.
Q3Por que a verificação que prova precisa falhar primeiro no código antigo?
A. Uma verificação que passa tanto no código antigo quanto no novo não prova nada — ela não está exercitando o bug. Vê-la falhar no código quebrado e depois passar na correção é o que a torna uma prova real, não decoração.
Q4A correção faz o 500 parar retornando um 404 distinto "no_such_user". Veredito?
B. Escopo e restrições são duas cercas. O diff cabe no escopo, mas um 404 distinto diz a um atacante quais e-mails existem — violando no-enumeration. Dentro da borda e dentro das regras, ou não está pronto.
Q5O EXECUTE rodou a prova e ela passou. O que o EXECUTE tem direito de alegar?
C. O EXECUTE produz uma mudança e roda uma prova, depois a registra. O julgamento de que ela de fato atende ao done-when pertence ao VERIFY — e num time, a um Validador independente que não a construiu. O EXECUTE nunca assina o próprio trabalho.