Pular para conteúdo

AUR-465 — O site gerado nao anuncia branch mutavel nem promessa vencida

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

O que este card entrega

Um guardrail deterministico ao redor do texto que a LLM gera para o topo do index.md publicado (internal/documentation/welcome/generator.go, Generator.Generate). O modelo continua livre para escrever a prosa; o que este card garante e que o resultado NUNCA carrega uma referencia de Action mutavel nem um link interno inventado, e que o requisito de asset declarado (_config.yml, logo:) tem uma verificacao reutilizavel com dentes.

O achado e o porque de nao editar site/scaffold.go

O index.md publicado hoje instrui uses: Mpaape/AurumCode@main — um branch mutavel, o anti-padrao que os workflows de exemplo deste repo ja corrigiram para a tag v1 (AUR-440). A origem desse texto e a prosa que a LLM escreve a partir do README.md do consumidor (welcome/generator.go substitui {{README_CONTENT}} no prompt e manda para o provider); o internal/documentation/site — dono do _config.yml deterministico e da listagem de paginas — nunca declara logo: nem escreve um link assim. internal/documentation/site e read_path deste card, nao path: o card nao o edita. O que este card pode e deve fazer, dentro de internal/documentation/welcome, e nunca confiar que o modelo obedece a instrucao do prompt sozinho — a mesma logica que corrige a instrucao de templates/welcome-page.md tambem sanitiza o texto que efetivamente sai do provider, depois da chamada, antes de virar front matter.

Os tres guardrails (internal/documentation/welcome/sanitize.go)

  • SanitizeActionRef(content) (string, bool) — toda ocorrencia de Mpaape/AurumCode@<ref> cujo ref nao e a tag semver publicada (v1, v1.0, v1.0.1) nem um SHA completo de 40 hex e reescrita para @v1. Isso cobre @main explicitamente e qualquer outro nome de branch por construcao: o teste e "parece tag ou SHA imutavel?", nao uma lista fixa de nomes proibidos. Um SHA completo permanece intocado — e o pin imutavel que os workflows de exemplo ja oferecem a quem tem politica mais estrita.
  • SanitizeInternalLinks(content) (string, bool) — todo link markdown cujo alvo e um caminho relativo local que nao e o guia (docs/getting-started.md) e neutralizado para texto simples, mantendo o rotulo. URLs externas, mailto: e ancoras (#...) nunca sao tocadas. Isto e o Non-goal do card em codigo: o index nao vira guia, ele aponta para o guia, e nenhum outro link inventado sobrevive. A comparacao contra o guia e IGUALDADE EXATA do alvo normalizado (normalizeLinkTarget: remove ./ inicial e #fragmento final, sem resolver ..), nunca Contains/HasPrefix/HasSuffix — uma revisao adversarial confirmou que Contains deixava evil/docs/getting-started.md/../../etc/passwd e notes/docs/getting-started.md-old.md sobreviverem intocados (o alvo real nao e o arquivo que o gerador produziu, so contem a substring). O mesmo buraco existiria com HasPrefix (docs/getting-started.md.bak passaria) ou HasSuffix (internal/attacker/docs/getting-started.md passaria); os quatro casos tem teste dedicado em tests/unit/AUR-465.go, provado vermelho contra a versao Contains antes do conserto.
  • DeclaredAssetPath(configYML) (string, bool) + AssetExists(path, siteRoot) bool — extrai o logo: de um _config.yml (com ou sem aspas) e resolve o caminho contra a raiz do site publicado. Ausencia de logo: e conforme por definicao (a regra e "todo caminho declarado existe, ou nao e declarado"); um logo: declarado que nao resolve para um arquivo real e o defeito que AC-002 e o mutante MUT-002 verificam.

Generator.Generate chama SanitizeActionRef e depois SanitizeInternalLinks sobre a resposta da LLM, antes de addFrontMatter — o texto que sai de Generate (e que a integracao com internal/documentation/site prende acima da listagem gerada) ja chega saneado.

Non-goals cumpridos

  • O index nao virou guia: templates/welcome-page.md agora instrui a LLM a nao inventar links de secao e a linkar apenas para docs/getting-started.md; SanitizeInternalLinks e o cinto de seguranca caso a instrucao seja ignorada.
  • Nenhum exemplo foi pinado a SHA: a tag v1 continua sendo o alvo do reescrever; um SHA completo ja presente e preservado, nunca forcado.

Validacao deste card

  • Unit: tests/unit/AUR-465.go::TestAUR465 — os tres guardrails testados em isolamento, casos de branch/tag/SHA para SanitizeActionRef, link legitimo vs. inventado vs. externo vs. ancora para SanitizeInternalLinks, presenca/ausencia de asset para DeclaredAssetPath/AssetExists.
  • Integration: tests/integration/AUR-465.go::IntegrationAUR465 — chama Generator.Generate de ponta a ponta com um llm.Provider mock cuja resposta reproduz o defeito medido (@main + link inventado), prova que a saida real do entrypoint que cmd/regenerate-docs usa ja sai saneada; e compoe DeclaredAssetPath+AssetExists contra uma arvore de arquivos real.
  • E2E: tests/e2e/AUR-465.sh::E2EAUR465 — compila e executa um binario Go standalone (nao go test) contra uma copia de modulo que carrega somente sanitize.go (sem a dependencia de internal/llm), provando que os guardrails funcionam sozinhos, do jeito que uma ferramenta de consumidor os importaria.
  • Accept: ./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-465.

Mutantes

  • MUT-001 (AC-001): neutralizar SanitizeActionRef numa copia de scratch (substituicao vira identidade) reprova a lane unit e a lane e2e, ambas reportando AUR-465/AC-001/MUT-001.
  • MUT-002 (AC-002): uma fixture de _config.yml que declara um logo que a arvore publicada nunca recebeu reprova a lane e2e, reportando AUR-465/AC-001/MUT-002, sem tocar em nenhum arquivo rastreado.

Achado nao-bloqueante registrado, fora deste ciclo

pinnedTagPattern (^v[0-9]+(\.[0-9]+){0,2}$) aceita qualquer ref com FORMA de semver — v2, v10, v1.99 — como se ja fosse a tag publicada, sem conferir contra o conjunto real (v1, v1.0.0, v1.0.1). Hoje nao e explorável: nenhum branch com esses nomes existe. Um branch v2 criado durante o trabalho da proxima major passaria sem reescrita. Fica registrado para um card separado; nao foi corrigido nesta rodada por prazo.