fix(cerca): o "Channel provider leak" passa a dizer QUAL linha nomeia o provider (#1322)

O erro listava o arquivo e a doutrina, e deixava a pessoa procurar. Num arquivo
de duzentas linhas com duas menções parecidas, procurar dá errado: em 19/09 um
maestro apontou a linha errada ao executor DUAS vezes, a segunda marcada como
urgente, e o conserto sugerido não teria tirado o vermelho. A cerca sabia a
resposta o tempo todo — ela só não a imprimia.

Agora imprime `linha N: <trecho>` para cada linha que casa, truncando em 96
caracteres.

E o erro passa a responder a pergunta seguinte, que é a que todo mundo faz
quando a linha acusada é um comentário: por que a cerca lê comentário?

Porque separar exigiria remover comentários antes de casar, e um removedor de
comentários apaga o que vem depois de `//` DENTRO de string — medido no repo:
`"http://waha:3000"` e as URLs `https://graph.facebook.com/...`. O resultado
seria falso negativo num gate que já teve exatamente essa classe de furo (issue
#118, onde `\b` não fechava entre `WAHA` e `_`). Um gate com furo é pior que
gate nenhum: o verde afirma que a doutrina está sendo respeitada.

Então a mensagem diz o que fazer: citar a seção da doutrina pelo assunto
(`a seção de anti-banimento`), não pelo provider.

Provado nos dois sentidos: contra a main, `ok (62 de dívida conhecida, nenhum
novo)`, exit 0; com um ofensor plantado, nomeia `linha 2` e o trecho.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rafael Melgaço
2026-09-19 15:15:52 -03:00
committed by GitHub
co-authored by Claude Opus 5
parent 551d3ddc3b
commit 28e24ba161
+30 -2
View File
@@ -236,6 +236,23 @@ function walk(dir: string): string[] {
});
}
/**
* As linhas que nomeiam provider, para o erro dizer ONDE — sem isto, quem leva o
* vermelho abre um arquivo de 200 linhas sabendo só que "alguma" delas nomeia.
* Medido em 19/09: um maestro apontou a linha errada ao executor, DUAS vezes, e
* a segunda veio marcada como urgente.
*/
function ondeNomeia(conteudo: string): { linha: number; texto: string }[] {
return conteudo
.split("\n")
.map((texto, i) => ({ linha: i + 1, texto: texto.trim() }))
.filter(({ texto }) => nomeiaProvider(texto))
.map(({ linha, texto }) => ({
linha,
texto: texto.length > 96 ? `${texto.slice(0, 96)}…` : texto,
}));
}
const offenders = ROOTS.flatMap(walk)
.filter((f) => !ALLOWED.some((re) => re.test(f)))
.filter((f) => nomeiaProvider(readFileSync(f, "utf8")));
@@ -247,10 +264,21 @@ if (novos.length) {
console.error(
"Nome de provider fora de lib/channels/ (doutrina restricao-de-canal, invariante 1):",
);
for (const f of novos.sort()) console.error(` ${f}`);
for (const f of novos.sort()) {
console.error(` ${f}`);
for (const { linha, texto } of ondeNomeia(readFileSync(f, "utf8"))) {
console.error(` linha ${linha}: ${texto}`);
}
}
console.error(
"\nPergunte uma CAPACIDADE (`capabilitiesOf`), peça o adapter (`getAdapter`) ou o\n" +
"identificador da sessão (`resolveSessionRef`) — nunca nomeie o provider.",
"identificador da sessão (`resolveSessionRef`) — nunca nomeie o provider.\n" +
"\nSe a linha acusada for um COMENTÁRIO: a cerca não distingue comentário de\n" +
"código, e isso é deliberado — separar exigiria remover comentários antes de\n" +
"casar, e um removedor de comentários apaga `\"http://waha:3000\"` e as URLs do\n" +
"Graph que moram DENTRO de string, criando falso negativo (a mesma classe da\n" +
"issue #118). Reescreva a prosa sem o nome: cite a seção da doutrina pelo\n" +
"assunto (`a seção de anti-banimento`), não pelo provider.",
);
}