AUR-608 — O modelo decide sobre a regex: gate.triage model por padrão, fail-closed e declarado
Problema
Direção do dono (2026-10-08): "O llm deve decidir com raciocínio. Uma regra estática vai gerar falso positivo". Antes deste card:
gate.triage.<fonte>ausente valianone(internal/config/gate.go): o raciocínio do modelo sobre a evidência determinística só contava se o repositório escrevessemodel.- Uma contestação (
disputed) semjustificationrebaixava igual a uma justificada. - A triagem agia mesmo sem gate declarado (um scanner retém a aprovação sem
gate:), e sem resposta do modelo nenhuma linha dizia que a triagem não aconteceu.
Desenho
| arquivo | responsabilidade |
|---|---|
internal/config/gate.go |
triageDefault = TriageModel; TriageMode/TriageModeFor devolvem o valor declarado ou model; toda chave que casa é lida e qualquer none vence (independe da ordem do mapa). TriageByModel/TriageByModelFor passam por elas. |
internal/review/assessments_dispute.go |
DisputeCounts: só disputed com justification não vazia (após TrimSpace) pode rebaixar; sem ela vale needs_context. |
cmd/aurumcode/review_gate.go |
triage(): com qualquer motivo de modelo não saudável (modelReason() != "": sem provedor, falha do provedor, parse falho ou degradado, limite de deliberação) não rebaixa nada; senão registra em Disputed só contestação justificada. triageSources(): por fonte, se o modelo pode rebaixar; só com gate declarado e sem política central; engines da mesma categoria combinam por E (um none mantém a categoria contando). modelReason(): o motivo do modelo pelo mesmo gate.RankReason. |
cmd/aurumcode/review_triage_notice.go |
reportTriageNotRun: com evidência de fonte triável (triageSources) e modelo sem resposta limpa, escreve no stderr e nas limitações do parecer que a triagem não ocorreu e a evidência contou integralmente (e que o bloqueio foi mantido, quando o gate reprovou). |
internal/i18n/catalog.yml |
notice.triage_not_run e notice.triage_not_run_clear em pt-BR e en. |
internal/gate/triage.go |
Comentário de Triage atualizado (sem mudança de comportamento). |
docs/configuration.md |
Seção gate.triage: padrão model, opt-out none, justificativa obrigatória, aviso sem modelo, política central inalterada. |
disputedEvidence() não mudou: continua alimentando as exceções propostas,
inclusive sob política central, então a saída sob política é a mesma (AC-006).
O prompt já pedia justification; nada nele mudou.
Resultado consolidado degradado: sob --perfis ou em lotes, um perfil ou lote
degradado marca o resultado inteiro como degraded_parse (profiles.go,
internal/review/batch_merge.go), mas as avaliações dos perfis/lotes bons
continuam lá. Sem a checagem de modelReason() em triage(), essas
contestações rebaixavam e, com gate.inconclusive: warn, a execução podia
passar (falha aberta) enquanto o aviso afirmava que a evidência contou
integralmente. Agora a triagem inteira é desligada.
ACs e testes
tests/acceptance/AUR-608.sh (cópia de go.mod go.sum cmd internal pkg; pacotes
internal/config, internal/review, cmd/aurumcode):
| id | prova |
|---|---|
| AC-001 | TestAUR608DefaultTriageDemotesJustifiedDispute (--base real, gate declarado sem triage, analysis E1 e semgrep E2 contestados com justificativa: exit 0 e gate.triage (analysis: model) / gate.triage (sast: model) no stderr), TestAUR608TriageLinesReachTheParecer (a linha entra no corpo publicado), TestGateTriageParse, TestAUR608GateTriageByModelForScannerDefault |
| AC-002 | TestAUR608ExplicitNoneKeepsEvidenceCounting (analysis: none, semgrep: none: exit 3, nada anunciado), TestAUR608GateTriageExplicitNoneOptsOut, TestAUR579PolicyFloorAndRepositoryTriage (caso analysis: none) |
| AC-003 | TestAUR608DisputeWithoutJustificationStillCounts (justificativa vazia e só espaços: exit 3), TestAUR608DisputeCountsRequiresJustification |
| AC-004 | TestAUR608WithoutModelKeepsDeterministicBlock (sem provedor, gate declarado: exit 3 e a linha no stderr, em pt-BR com review.language: pt-BR e em en-US no padrão), TestAUR608TriageLinesReachTheParecer (a linha entra nas limitações do parecer publicado; gate aprovado não fala em bloqueio), TestAUR608DegradedModelNeverDemotes (passos reais de gate e saída da sessão: resultado degraded_parse, contestação justificada, inconclusive: warn: exit diferente de 0, nada "deixou de contar", aviso (degraded_parse) no stderr e nas limitações) |
| AC-005 | TestAUR608TriageSilentWithoutDeclaredGate (SAST sem gate: e contestação justificada: exit 3, nada anunciado; sem modelo e sem gate: nada anunciado) |
| AC-006 | TestAUR608CentralPolicyUnchanged (política fail_on: [high] sem triage: contestação justificada continua reprovando e vira exceção proposta; sem modelo, nada anunciado), TestAUR579PolicyFloorAndRepositoryTriage |
Mutações
| id | mutação | teste que cai |
|---|---|---|
| MUT-001 | const triageDefault = TriageModel → TriageNone |
TestAUR608DefaultTriageDemotesJustifiedDispute |
| MUT-002 | strings.TrimSpace(a.Justification) != "" → != "mutant" (aceita contestação sem justificativa) |
TestAUR608DisputeWithoutJustificationStillCounts |
| MUT-003 | if s.modelReason() != "" { → if false { em triage() (resposta degradada volta a rebaixar) |
TestAUR608DegradedModelNeverDemotes |
Compatibilidade
- Repositório com gate declarado e sem
triage: uma contestação justificada do modelo passa a rebaixar a evidência determinística (antes contava). Para manter o comportamento anterior, escrevernonepor fonte. - Repositório que já escrevia
modelounone: sem mudança, exceto que contestação sem justificativa deixa de rebaixar. - Sem gate declarado: nada é rebaixado (antes uma engine com
triage: modelpodia rebaixar mesmo semgate:). - Sob política central: nada muda.
- Execuções sem modelo ou com resposta degradada, com evidência triável e gate
declarado, ganham uma linha no stderr e nas limitações do parecer, no idioma
de
review.language; nelas nada é rebaixado.