AUR-593: ergonomia medida pelo Aurum no Aurum
Medido antes (base f9a4f751)
Ao ligar o Aurum no próprio repositório (AUR-591) com a configuração mínima:
- Cobertura parcial por
ignoree binário.mergeReviewCoverage(cmd/aurumcode/review_coverage.go) somava os caminhos doignoree todo arquivo filtrado (binário, gerado, grande demais) emuncovered();partial()ficava verdadeiro e, cominconclusive: block, a revisão saíapartial_coverage.applyStructuralCoverageainda retinha a aprovação por qualquer binário. No--pr, um binário sem patch entrava no prompt e era contado também como "omitido pelo limite de tokens". - Semgrep na árvore inteira. Medido na imagem do produto
(
semgrep 1.172.0,p/security-audit+p/owasp-top-ten) sobre a árvore real: 23 resultados, a maioria em arquivos que nenhuma PR de card toca (Dockerfiles,demo/bad-code.go,dependabot.yml), e 13 erros nívelwarn(5Syntax error, 8PartialParsing). Causa dosast_invalid_output: (a) o campotypede umPartialParsingé um array (["PartialParsing", [...]]) e o adaptador o decodificava como string, então ojson.Unmarshaldo relatório inteiro falhava ("invalid JSON output"); (b) mesmo decodificado, qualquer entrada emerrorstornava o relatório inválido, inclusive um parse recuperado num arquivo que a PR não tocou. - Skill de diretório não citável. Só
review.context.skillsgerava regras (dynamicRulesFromLocalSkills/RemoteSkills); o catálogo de.aurumcode/skills/<nome>/SKILL.mdsó mandava texto ao prompt. Listar o arquivo nas duas fontes duplicava o texto, e o id eraSKILL#<slug>para toda skill de diretório (colisão entre skills). - govet sem
go. A imagem final (alpine) não tinha o toolchain:lint_unavailableem toda revisão.
O que mudou
- Cobertura (
cmd/aurumcode/review_coverage.go,passes.go,structural_coverage.go,review_pr*.go,internal/analyzer/textdiff.go,internal/i18n/catalog.yml): caminho doignoree binário (analyzer.NoticeReasonBinary) viramIgnoredPaths, fora departial(); um arquivo conta uma vez (ignorado vence binário). O aviso de cobertura lista os ignorados mesmo com a revisão completa. Gerado, grande demais, sem patch e orçamento continuam parciais e retêm a aprovação. No--pr,declareBinariestira o binário do diff antes do prompt (como o--base). - Escopo de mudança (
internal/scanner/added_lines.go, movido deinternal/scanner/govet/diff.go, um lugar só):scanner.AddedLines,LineSet.Keep/Touched,scanner.ErrNoRange. O govet usa o mesmo código. O semgrep (internal/scanner/semgrep) continua varrendo a árvore, guarda só achados em linhas adicionadas, decodificatypecru (json.RawMessage) e só trata como inválido o erro que alcança a mudança: nível diferente dewarn, erro sem arquivo,warnem arquivo tocado sem trecho, ou trecho sobre linha adicionada. Sem intervalo revisado: inconclusivo. O semgrep recebe só osafe.directorydo ambiente (ogit diffroda num checkout de outro usuário no CI). - Skills (
internal/context/skills/catalog.go,internal/review/skillrules.go,cmd/aurumcode/policygate.go,skill_catalog.go,review_base_analysis.go,review_pr_model.go): o catálogo guarda a camada de cada skill (LayerPolicy/LayerRepository) e expõeDocs(changed);dynamicRulesFromCatalogtransforma cada skill selecionada em regras com a origem da sua camada (uma skill do repositório nunca ganha origem de política).ExcludeListedtira do catálogo oSKILL.mdque a mesma camada lista emcontext.skills(texto uma vez). O id de umSKILL.mdé<diretório>#<slug>nas duas fontes. No--baseo catálogo é montado antes das regras, e as regras entram no digest do catálogo de regras (cache). - Imagem (
Dockerfile):COPY --from=builder /usr/local/goda imagemgolang:1.27.1-alpine3.24@sha256:8a59...(a mesma do build, a dogo.mod),PATH,GOPATH=/go,GOMODCACHE=/go/pkg/mod, e o build falha sego versionnão forgo1.27.1. Workflow (review.yml): passo "Prefetch Go modules for the lint engine" (go mod downloadcomGOTOOLCHAIN=local, sem segredos, só comgo.mod), cache montado em/go/pkg/modna review; download que falha é aviso e o govet ligado ficalint_execution_error(fail-closed). - Tutoriais:
revisao,gate,reaproveitamentoeauditoria-sarifusavam um PNG para forçar cobertura parcial; agora usam um arquivo gerado (repo-exemplo/gerado/gerado.go).revisao/falha-nao-revisadoganhou uma terceira parte (terceira parte do caso: PNG +inconclusive: block→ exit 0,logo.png (binary)listado).sast/govet-sem-gotira o Go doPATH(a imagem agora o traz). Todos osout/regravados com a imagem deste worktree; capturas regravadas. - Docs:
configuration.md(ignore/binário fora da cobertura, escopo do semgrep e dos erros de parse, id de skill de diretório, Go na imagem e prefetch),getting-started.md(seção "Configuração mínima"),architecture.md, tutoriaisrevisao,gate,qualquer-linguagem,sast.
Prova
Aceite (tests/acceptance/AUR-593.sh, go-shared)
$ bash tests/acceptance/AUR-593.sh all
AUR-593/AC-001/pass
AUR-593/AC-002/pass
AUR-593/AC-003/pass
AUR-593/AC-004/pass
AUR-593/AC-005/pass
aur593_test.go:49: exit=1, want 0 and no partial_coverage: an ignored path and a binary are declared, not partial
--- FAIL: TestIgnoredAndBinaryAreDeclaredNotPartial (0.11s)
AUR-593/MUT-001/rejected
aur593_test.go:111: exit=3, want 0
--- FAIL: TestSemgrepJudgesOnlyTheChange (0.11s)
AUR-593/MUT-002/rejected
AUR-593/all/pass
- AC-001:
TestIgnoredAndBinaryAreDeclaredNotPartial(tests/**ignorado + PNG,inconclusive: block→ exit 0, sempartial_coverage, "2 file(s) ignored" com os dois nomes);TestGeneratedFileStillPartialUnderBlock(fail-closed);TestAUR522PRBinaryIsDeclaredIgnoredeTestAUR522PRGeneratedFileIsNotReviewed(--pr). - AC-002:
TestSemgrepJudgesOnlyTheChange(achado pré-existente em arquivo não tocado + erro de parse fora da mudança → exit 0; achado numa linha adicionada → exit 3) e os testes da engine (change_scope_test.go: array emtypedecodifica; erro sobre linha adicionada, arquivo tocado sem trecho, erro sem arquivo e erro fatal continuamErrInvalidOutput; sem intervalo,ErrNoRange). - AC-003:
TestDirectorySkillIsCitableOnce: só diretório e diretório + listado; o gate reprova (exit 3) citandotamanho#tam-001-funcao-com-no-maximo-150-linhase o marcador do texto aparece 1 vez no prompt capturado. - MUT-001:
partial()volta a contarIgnored→ AC-001 vermelho (exit 1). - MUT-002: semgrep devolve
findingssemKeep→ AC-002 vermelho (exit 3 no caso pré-existente).
AC-004 na imagem do produto (Go real, sem fake)
docker build -t aurumcode-review:aur593 . deste worktree; repositório de
demo/tutoriais/sast/repo-exemplo/lint-base + lint-erro com dois commits:
$ docker run ... aurumcode-review:aur593 review --base main
calc/calc.go:9: [warning] fmt.Printf format %d has arg s of wrong type string (rule go-vet/printf)
aurumcode review: policy gate: go-vet/printf - ... (severidade warning, limiar warning, origem govet, secao repo)
rc=3
AC-005 na imagem do produto, sobre intervalos reais deste repositório
Clone do repositório; .aurumcode/ do card/AUR-591 com
{engine: semgrep, enabled: true, required: true} e
{engine: govet, enabled: true, required: true, fail_on: warning} (com
deliberation ligada, um scanner não required viraria ferramenta do modelo
e o provedor de fixture nunca o chamaria). Modelo: fixture de aprovação
limpa. Cache de módulos do projeto montado só leitura em /go/pkg/mod (o que
o passo de prefetch faz no CI). Wrappers no PATH registram cada execução:
$ docker run ... review --base main --auditoria /out/audit.json # card/AUR-591 (intervalo f9a4f751..c163d06a)
rc=0
gitleaks git --log-opts=f9a4f751...c163d06a --report-format json ...
semgrep scan --json --quiet --metrics=off --disable-version-check --config p/security-audit --config p/owasp-top-ten .
go env GOVERSION
go vet -json ./...
audit: "gate": {"decision": "pass"}, "coverage": {"complete": true, "omitted_files": [40 itens: tests/acceptance/*.sh, docs/assets/capturas/*.png (binary), ...]}
Cobertura da revisão
- 40 arquivo(s) ignorados (configuração `ignore` do repositório ou binários): ...
Sobre o intervalo deste card na cabeça final (main...71fde6d6, 109
arquivos, mesma configuração): os três scanners rodaram e nenhum ficou
inconclusivo (semgrep e govet sem achado nem erro). O resultado foi rc 3,
por dois motivos que não são deste card e ficam medidos:
gitleaks:sumologic-access-token(87 achados) emdocs/assets/capturas/capturas.json, nos commits que regravam as capturas: os digests sha256 do manifesto casam com a regra. O gitleaks lê o histórico e oignorenão o filtra. Toda PR que regrava capturas terá o mesmo achado: falso positivo a tratar (exceção com dono/validade ou allowlist), fora deste card.partial_coveragepelo limite de tokens (7 arquivos de 109 fora do prompt) e pelos 4gerado.godos tutoriais (gerados de propósito): parcial legítimo, não porignorenem binário (55 ignorados/binários listados à parte).
v3 (gitleaks limpo). O gerador scripts/docs/capturas.cjs grava
"insumo_sha256":"sha256:<hex>": o hex já não segue direto o separador e a
regra não casa; scripts/docs/capturas-check.sh exige o prefixo (valor nu
reprova: entrada invalida no manifesto, medido) e o remove antes de
comparar. Como o gitleaks lê cada commit do intervalo, os commits antigos da
branch com o manifesto nu ainda acusavam; a branch do card (nunca publicada)
foi achatada num commit sobre o main. Nova revisão do intervalo
996732f0..8c7fcdc8 (111 arquivos), mesma configuração, imagem reconstruída:
gitleaks, semgrep e go vet executados, nenhum achado de gitleaks e
nenhum scanner inconclusivo; rc 1 só por partial_coverage do limite de
tokens (6 arquivos fora do prompt), o limite conhecido registrado abaixo.
AUR-588.sh all e AUR-593.sh all: all/pass.
A seção "Configuração mínima" de getting-started.md, copiada literalmente
para um repositório de um módulo Go, passa no Parse estrito e sai rc 0 na
imagem (com rede; sem rede o pacote p/... não baixa e o semgrep fica
sast_execution_error, fail-closed).
Regressão
go vet ./... && go test -p 1 ./...(go-shared): verde.- Tutoriais: os 18
run.sh allem paralelo comaurum-tutoriais:aur593(todos rc 0) e--checkde cada um: verde. bash scripts/docs/capturas.shebash scripts/docs/build.sh: 0 warnings.- Aceites (go-shared): AUR-575, 577, 581, 565, 579, 584, 562, 563, 588
all/pass(ou a última linhaok). Falham também nomainsem este card (medido no checkout principal): AUR-590AC-003/go-list-offline-failede AUR-564 (corrigido abaixo). AUR-561 voltou a passar depois de o caso do binário virar parte defalha-nao-revisado(caso novo exigiria registro emdocs/specs/AUR-561.md, fora depaths). Na v2 do card (AUR-492.sheAUR-564.shempaths): o MUT-002 do AUR-492 muta agorainternal/scanner/added_lines.go(mesma âncoraif s[f.Path][f.Line] {, para onde este card moveu o mapeamento do diff) eAUR-492.sh allterminaAUR-492/all/pass; o bloco<!-- arquivo: .board/oci/profiles/go-unit-offline-v1.json -->dedocs/tutorials/operacao.mdnão acompanhou o repin do AUR-590 (lock_digest979c3003...no texto,026e9310...no arquivo); alinhado,AUR-564.sh allsai rc 0 (AC-004/ok (14 blocos identicos aos arquivos da demo...)). AUR-590AC-003/go-list-offline-failedfalha igual nomain.
Revisão: byte NUL não esconde código
O revisor provou falha aberta: um único byte NUL nos primeiros 8000 bytes
(grammar.LooksBinary) fazia um .sh com curl … | sh virar "binário
ignorado", e a revisão aprovava sob inconclusive: block sem o modelo ler o
arquivo. Conserto: só é declarado ignorado o arquivo cuja extensão está no
catálogo internal/grammar/catalog/binary_formats.yml (imagens, PDF,
fontes, mídia, zip; executáveis e bibliotecas ficam de fora de propósito)
e cujo conteúdo é binário (grammar.KnownBinaryFormat +
declaredBinary em cmd/aurumcode/structural_coverage.go, um lugar para
--base e --pr). Script, código, config, arquivo sem extensão (shebang),
extensão desconhecida ou catálogo ilegível: parcial e aprovação retida.
Testes: TestNULInCodeIsNeverDeclaredBinary (deploy.sh, app.js,
ci.yml, instala sem extensão, cada um com NUL, sob block → exit 1,
partial_coverage, sem Approve), TestAUR522PRBinaryWithoutPatchIsNotReviewed
(tool.bin no --pr, retido) e TestAUR522PRBinaryIsDeclaredIgnored
(logo.png real, ignorado). MUT-003 (qualquer conteúdo binário declarado,
sem o catálogo) deixa os três casos com extensão vermelhos (exit=0, want 1):
AUR-593/MUT-003/rejected
AUR-593/all/pass
Segundo fechamento: o revisor provou lib/payload.PNG (script + NUL)
declarado ignorado. O catálogo passou a exigir, por formato, a assinatura
(magic bytes em hex e offset: PNG 89504E470D0A1A0A, JPEG FFD8FF, GIF
474946383761/…3961, PDF 25504446, ZIP 504B0304/504B0506, WOFF
774F4646, WOFF2 774F4632, MP4 66747970 no offset 4, …); formato sem
assinatura é erro de carga. grammar.DeclaredBinaryFormat(nome, conteúdo)
exige extensão (qualquer caixa) + assinatura + conteúdo binário; o
analisador grava o resultado em DiffNotice.DeclaredFormat (lado novo, ou o
antigo de um arquivo apagado). Testes: lib/payload.png e lib/payload.PNG
com script e NUL → exit 1, retidos; docs/Captura.PNG real → ignorado.
MUT-004 (assinatura não conferida) deixa os dois payload vermelhos
(exit=0, want 1); AUR-593/all/pass.
O tutorial qualquer-linguagem/binario-e-gerado voltou a reter
ferramenta.bin e leia-me.txt (fora do catálogo); tutoriais e capturas
regravados.
Decisões e pendências
-
Limite conhecido: orçamento de tokens numa PR grande. Na revisão do intervalo deste card (109 arquivos), 7 arquivos ficaram fora do prompt pelo limite de tokens e a revisão saiu
partial_coverage. É parcial legítimo (o modelo não leu esses arquivos) e não muda neste card: uma PR grande continua inconclusiva sobinconclusive: blockaté um card tratar o orçamento (ex.: revisão em lotes). -
Um arquivo de formato binário do catálogo não retém mais a aprovação (antes AUR-522): nada nele se revisa lendo; ele é listado pelo nome. O segredo num binário continua com o gitleaks (intervalo de commits). Gerado, grande demais e sem patch seguem retendo.
- Erro de parse
warndo Semgrep num arquivo tocado, sem trecho indicado (Syntax errorde arquivo inteiro, ex.: algunsrun.shdeste repo), segue inválido: fail-closed sobre código que a PR adicionou e o Semgrep não leu. Uma PR que mexe num desses scripts com o semgrep ligado ficasast_invalid_output; o repositório pode tirar o caminho com.semgrepignore(sem política) — não medido aqui sobre um PR real. - O
ignoredo repositório não filtra os scanners (só o que o modelo lê); achados de scanner em caminho ignorado continuam contando se estiverem em linhas adicionadas. - Repositório Go com módulo privado: o prefetch falha (aviso) e o govet
ligado fica
lint_execution_error; credenciais de módulo privado ficam fora deste card. .aurumcode/config.ymldeste repositório não foi alterado (fora depaths); ligar semgrep/govet nele é do AUR-591.