Skip to content

Audit assertions and fix observable defects - #1

Merged
dionipadilha merged 10 commits into
mainfrom
claude/fases-da-lua-analysis-dqnt2w
Jul 22, 2026
Merged

dionipadilha merged 10 commits into
mainfrom
claude/fases-da-lua-analysis-dqnt2w

Conversation

@dionipadilha

Copy link
Copy Markdown
Owner

Summary

This PR adds a fitness function for project assertions (what the codebase claims about itself) and fixes four observable defects discovered through external audit: a crash on malformed URIs, incorrect moon phase labels on specific slider positions, duplicate accessibility announcements, and English identifiers in the core domain.

Key Changes

New tooling:

  • ferramentas/auditar-afirmacoes.js — automated auditor that verifies four assertions:
    1. Portuguese identifiers in src/ (denylist-based detection after stripping strings/comments)
    2. Test count consistency between README, CI config, and actual node --test output
    3. All commit hashes cited in markdown files resolve in git
    4. Composition counts ("N doors, M adapters") match actual file/instance counts
  • Added npm run afirmacoes script and CI job with fetch-depth: 0

Domain fixes:

  • Renamed English identifiers to Portuguese: limitlimite, namenome, phasefase, calculatorcalculadora, nextproxima in ServicoDeFases, CalculadoraDaLua, AtualizarLua, and test files
  • Updated shadow thresholds (limiarEscura: 0.0244, limiarCheia: 0.9961) to prevent full-disk rendering under wrong phase labels; verified by sweep test across all 101 slider positions

Application fixes:

  • Wrapped decodeURIComponent in try-catch to handle URIError on malformed URIs (e.g., /%zz) → 400 response instead of process crash
  • Added aria-hidden="true" to #nome element; phase name now announced only via aria-valuetext (single channel, no emoji duplication)
  • Fixed AnimacaoDoCiclo to detect external jumps on first frame by initializing ultimaAplicada to current position instead of null

Architecture:

  • Added definirHemisferio(hemisferio) to PortaRenderizadorDaLua contract with test coverage
  • Fixed canvas rendering: crescents now draw with correct anticlockwise flag per hemisphere (south: true, north: false) to orient shadow bulge toward illuminated side
  • Updated test doubles to mirror real adapter quantization (range input rounds to 0–100 integer range)

Documentation:

  • Updated test count: 49 → 54 tests
  • Added Lesson 16 to APRENDIZADOS: "The self-referential blind spot — and external audit as a force"
  • Clarified comments on phase boundaries, elongation vs. phase angle, and 0.22↔0.8 asymmetry as intentional historical residue
  • Updated PLANO.md errata (9 → 8 untouched adapters)

Notable Details

  • Auditor runs in pure Node with zero dependencies, enabling any CI environment
  • Identifier detection uses state machine to strip strings/comments before regex matching; .property access (API usage) is explicitly allowed
  • Shadow threshold alignment is sub-pixel (1.6% of disk) at boundary; verified visually before commit per Lesson 13
  • Each commit narrates its purpose; auditor born red (failing) in first commit, green after fixes

https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg

claude added 10 commits July 22, 2026 01:45
Convenção da lição 11: reabertura consciente exige nomear a força real
em plano aprovado antes da execução. As forças são duas — correção
(defeitos confirmados por auditoria externa e reproduzidos: crash do
servidor, nome×desenho discordando em 5 posições, glifo no aria-live,
inglês no núcleo) e curricular (afirmações autorreferentes são defeitos
latentes invisíveis ao auditor interno; a resposta estrutural é uma
fitness function das afirmações no CI).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
…elha

O grafo audita as fronteiras; nada auditava o que o projeto AFIRMA
sobre si. E afirmação autorreferente é defeito latente da mesma classe
da lição 4: invisível até que a força certa a exponha — aqui, uma
auditoria externa, que achou duas falsas ("português em tudo, até nos
identificadores"; "9 adaptadores intocados") sobrevivendo a três
passadas de conformidade. Resposta estrutural (lição 9): a classe
inteira vira código.

Quatro checagens em Node puro, zero dependências: idioma dos
identificadores em src/ (denylist do glossário; acesso .propriedade é
API, permitido), contagem de testes prometida × executada (de quebra
mata o falso-verde de um glob sem match: 0 ≠ N), hashes citados em
crases nos .md resolvendo no repositório, e "N portas, M adaptadores"
do console contra a composição real.

O fóssil — a primeira execução, registrada:

  ❌ Idioma — o glossário promete português nos identificadores, mas:
     src/adaptadores/animacao-do-ciclo.js: "next" ×2
     src/aplicacao/atualizar-lua.js: "calculator" ×3
     src/dominio/servico-de-fases.js: "limit" ×8
     src/dominio/servico-de-fases.js: "name" ×8
     src/dominio/servico-de-fases.js: "phase" ×6
  ✅ testes: 49 = 49 · hashes: 19/19 · portas: 3 · adaptadores: 12

O vermelho é o argumento: a máquina reprova o discurso antes de o
código consertá-lo (próximo commit). CI ganha o terceiro job, com
fetch-depth 0 — hashes antigos não existem em checkout raso.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
…tor verde

O que o auditor reprovou no commit anterior, corrigido: limit/name/
phase viram limite/nome/fase no domínio (o arquivo exemplar do núcleo
contradizia a meta-afirmação mais enfática do projeto), calculator
vira calculadora na aplicação, next vira proxima na animação. E as
contrapartes que o auditor v1 não cobre, corrigidas pela mesma régua:
phase no dublê compartilhado, prev e os locais em inglês do oráculo
(phases, expected, value, limit, table) — os DADOS históricos do
oráculo ficam bit a bit intactos; só os nomes de variável mudam.

Zero mudança de comportamento: o oráculo dos 101 valores passa sem se
mover (terceiro pagamento — lição 3) e a suíte segue 49/49. Estes
resíduos sobreviveram a três passadas de conformidade (d347986,
efcbb44, ee569df) — regex não entende semântica (lição 7), e nenhuma
máquina lia a afirmação. Agora lê: npm run afirmacoes, verde.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
decodeURIComponent vivia FORA do try — e numa função async, a exceção
que escapa do handler vira rejeição não tratada e encerra o Node.
Reproduzido antes da correção: GET /%zz → URIError → processo morto →
connection refused para sempre. Uma requisição malformada matava o
servidor de desenvolvimento inteiro.

O parse inteiro entra no try; URIError responde 400, o resto segue
404. Verificado após a correção: /%zz → 400 com o processo vivo,
/ → 200, /inexistente → 404.

O único defeito de correção reproduzível da auditoria externa morava
no único arquivo declarado "fora da arquitetura" — a qualidade vive
exatamente onde a disciplina foi aplicada, e a falha onde ela foi
dispensada. Não há teste automatizado: exigiria refatorar o servidor
para injetar o handler, e elaboração fora da arquitetura é decoração
(lição 10); a prova manual acima é o registro.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
O README prometia "nome puro, sem glifo verbalizado" — e o
aria-valuetext cumpre. Mas a única região aria-live da página era o
#nome, montado com o emoji à frente: o leitor de tela falava o glifo
("lua cheia" pelo 🌕) e anunciava cada mudança DUAS vezes (valuetext +
live region). A promessa valia num canal e o outro a derrotava — o
mesmo problema de dois clientes de uma API, que a própria tabela do
README nomeia.

#nome vira aria-hidden: o aria-valuetext é agora o canal falado
único, exatamente o que o README declarava. Custo assumido e
registrado no glossário: "dia X de 29" sai do anúncio — enriquecer o
valuetext exigiria mudar a porta do anunciador, e o preço de um
contrato maior não se paga por um sufixo (lição 12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
…e disco cheio

Os limiares de snap (0.02/0.98) eram escolhidos à parte das fronteiras
de NOME — que vivem em fração nas posições históricas (0.05 → ≈0.0245;
0.48 → ≈0.9961). Na banda entre os dois critérios, os sliders 46, 47,
52, 53 e 54 desenhavam o disco totalmente cheio (nos dois substratos —
a paridade por construção garantiu que os renderizadores concordassem
entre si, inclusive no engano) sob o rótulo "Gibosa".

Novos limiares: 0.0244 / 0.9961, logo aquém das fronteiras. O painel
continua folha — a paridade nome↔sombra não vem de import, vem de um
teste de varredura dos 101 valores: disco cheio ⇒ "Lua Cheia", disco
escuro ⇒ "Lua Nova". Alinhamento perfeito é impossível com um único
limiar (sliders 48 e 52 têm a MESMA fração e nomes diferentes — as
fronteiras de nome são estritas de um lado só); o resíduo aceito é
uma lasca sub-pixel no slider 48 sob "Lua Cheia", conferida de olho
(lição 13): a gibosa quase-cheia agora parece exatamente isso.

Suíte: 50 testes (o auditor de afirmações cobrou a atualização do
README e do ci.yml — funcionando como projetado).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
A composição chama renderizador.definirHemisferio() e o Alternável a
propaga a TODAS as estratégias — todo renderizador precisa do método
— mas a porta só declarava renderizar(). A capacidade vivia fora do
contrato, escapando da regra "métodos lançam erro se não
implementados": um terceiro renderizador (SVG, digamos) que a
esquecesse só falharia como TypeError em runtime, na troca de
hemisfério.

Tensão honesta, registrada: foi essa omissão que permitiu à feature
de hemisfério cumprir "diff zero em portas" (lição 2). O contrato
correto custa admitir que aquela previsão foi cumprida com um débito
— o PLANO da coexistência já dava o veredito para tocar estas
camadas: "deve ser tratado como achado, não como ajuste". É o achado,
tratado. RenderizadorFalso ganha a implementação mínima; 51 testes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
iniciar() zerava ultimaAplicada = null, e avancar() só adota
movimento externo quando ela não é null: um salto de teclado entre o
play e o primeiro avanço de tempo era silenciosamente descartado — a
animação continuava da posição capturada no play. O teste novo prova
o defeito (falha no código antigo: anda para 0.1 em vez de adotar 0.5
e seguir a 0.6) e a correção: ultimaAplicada nasce com a posição
atual, o que de quebra elimina a repintura redundante da largada.

Junto, o dublê da entrada fica honesto (lição dos dublês): mover()
passa a quantizar em centésimos como o <input type="range"> real —
um dublê mais liso que o adaptador esconderia exatamente a interação
que os dirigentes precisam sobreviver. 52 testes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
A fumaça do canvas só aferia o semieixo b e o raio — os ângulos e
sentidos do arco e o flag anticlockwise da meia-elipse (a decisão de
para ONDE o bojo do terminador vira, que separa crescente de gibosa
e sul de norte) não eram observados por teste algum. Um refactor que
invertesse um flag desenharia a fase oposta com a suíte verde. E o
ramo crescente inteiro (bojo para o lado iluminado, sombra cobrindo
mais de meio disco) nunca era exercitado.

Duas fumaças novas travam os quatro graus de liberdade: crescente no
sul (limbo escuro à direita, bojo anticlockwise à esquerda) e o
espelho no norte. A camada continua tabela de tradução — a decisão
segue no núcleo — mas agora a tabela inteira tem guarda. 54 testes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
A moeda deste projeto é a precisão sobre si mesmo; três erratas a
recompõem: o PLANO admite que 8 (não 9) adaptadores ficaram intocados
na coexistência; o comentário da calculadora troca "ângulo de fase"
por elongação (a fórmula sempre esteve certa — o nome do ângulo, não:
o ângulo de fase astronômico é o suplemento); e a assimetria 0.22↔0.8
das fronteiras ganha a linha de documentação que faltava — resíduo
histórico preservado pelo oráculo, não física.

E a lição 16 registra o que esta reabertura ensinou: afirmações
autorreferentes são defeitos latentes invisíveis ao auditor interno,
expostos apenas por auditoria externa — e a resposta estrutural foi
transformar a classe inteira em código (2008a23 vermelho, 7fa5a52
verde). Os hashes citados nesta lição são validados pelo próprio
auditor que ela descreve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCJV7nKZ1kxi1CtSbvywDg
@dionipadilha
dionipadilha merged commit 47641e6 into main Jul 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants