AUR-437 — Comentar o review no pull request (cliente GitHub restaurado)
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
O que este card entrega
A biblioteca Go internal/git/githubclient: um cliente da API do GitHub que
le as mudancas de um pull request e publica os problemas encontrados como
comentario, recusando publicar quando o token nao tem permissao de escrita.
O comando de usuario (aurumcode review --pr 42 --repo dono/projeto
--publicar) pertence ao AUR-438, que depende deste card e e dono de
cmd/aurumcode; aqui entra somente o cliente e seus fixtures httptest.
Origem: restauracao, nao reescrita
O cliente existiu no repositorio e foi removido em 2025-11-03 pelo mesmo lote
que apagou o motor de review. Ele foi restaurado do commit c12d7ab
(internal/git/githubclient/client.go, 774 linhas, junto com seus dois
arquivos de teste httptest historicos, client_test.go e
integration_test.go), e nao reescrito.
Duas adaptacoes de restauracao, sem mudanca de comportamento:
- Tipos de diff locais ao pacote (
diff.go): o historico importavaaurumcode/pkg/types, mas o aceite selado deste card materializa apenas ospaths/read_pathsdeclarados, entao o pacote precisa compilar sozinho.Diff,DiffFileeDiffHunkespelham campo a campo (com as mesmas tags) os tipos depkg/types, para conversao trivial no AUR-438. - Module path: os imports usam
github.com/Mpaape/AurumCode/..., o module path atual dogo.mod(o historico usavaaurumcode).
Os quatro defeitos medidos, corrigidos
Cada um tem teste que falha na restauracao crua e passa com a correcao
(seletor unit TestAUR437, subtests Defeito1..Defeito4; ha equivalentes
no proprio pacote):
- Retry de POST reenviava corpo ja consumido.
req.Clonecompartilha o mesmo reader do corpo; a retentativa enviava corpo vazio. O resgate interno do net/http (rewindBody) so ocorre em conexao reutilizada, entao um rate-limiter que respondeConnection: closeexpunha o defeito comohttp: ContentLength=N with Body length 0. Correcao: toda tentativa apos a primeira reobtem o corpo viareq.GetBody; corpo nao-replayavel e erro explicito. - 403 de rate-limit nao era retentado. So 429 contava como rate limit,
mas o limitador primario do GitHub responde 403 com
X-RateLimit-Remaining: 0(ouRetry-After). Correcao:isRateLimited403trata esse 403 como rate limit retentavel; 403 simples (negacao real de permissao) continua retornando imediatamente, sem retry. time.Sleepno retry ignorava o contexto. Um chamador cancelado esperava o backoff inteiro. Correcao:sleepContextaborta a espera assim quectx.Done()fecha, nos dois pontos de espera (backoff e retry-after).extractFilePathcasava o primeiro literalb/. Emdiff --git a/cmdb/settings.go b/cmdb/settings.goo primeirob/esta dentro decmdb/, produzindosettings.go. Correcao: ancora no ultimo separadorb/, que introduz o caminho do lado novo — resolve o caso medido (um segmento de diretorio terminado emb).
Caso residual, nao coberto por esta correcao: quando o proprio caminho
do lado novo contem o literal b/, o ultimo separador b/ da linha cai
DENTRO desse caminho, e extractFilePath ainda trunca o resultado.
Reproduzido em internal/git/githubclient/client.go:795-802: a linha
diff --git a/foo b/bar.go b/foo b/bar.go (caminho foo b/bar.go, que
contem b/) retorna bar.go em vez de foo b/bar.go. Este card nao
corrige esse caso.
A verificacao de permissao antes de publicar
HasWritePermission(ctx, owner, repo)le o objetopermissionsdeGET /repos/{owner}/{repo}(push || maintain || admin); resposta sem o objeto conta como sem escrita — a checagem falha fechada. O veredito e cacheado porowner/repona vida do cliente.PostIssueComment,PostReviewCommenteSetStatuspassam porrequireWritePermissionantes de montar qualquer POST e recusam com o erro sentinelaErrNoWritePermission(testavel comerrors.Is). Nenhum POST sai do processo com token somente-leitura.- MUT-001: o aceite muta uma copia de trabalho de
client.gofazendorequireWritePermissionretornarnilsem checar nada, reconstroi e prova que o vetor de token somente-leitura entao falha (readonly_not_refused). O binario de producao nao tem gancho de mutacao algum.
API publica
c := githubclient.NewClient(token) // api.github.com
c := githubclient.NewClientWithBaseURL(token, url) // testes/fakes
diff, err := c.GetPullRequestDiff(ctx, owner, repo, numero) // Accept v3.diff, cache ETag/304
files, err := c.ListChangedFiles(ctx, owner, repo, numero) // paginacao via Link rel="next"
ok, err := c.HasWritePermission(ctx, owner, repo)
err = c.PostReviewComment(ctx, owner, repo, numero, githubclient.ReviewComment{...}, chaveIdem)
err = c.PostIssueComment(ctx, owner, repo, numero, corpo)
err = c.SetStatus(ctx, owner, repo, sha, githubclient.CommitStatus{...})
Erros: HTTP nao-2xx vira HTTP <codigo>: <corpo>; recusa de publicacao
embrulha ErrNoWritePermission; cancelamento devolve o erro do contexto.
Retry: ate 3 retentativas para 5xx, 429 e 403 de rate-limit, com backoff
exponencial (1s base, teto 30s, jitter) sensivel ao contexto.
Exemplo executavel sem segredo
bash tests/e2e/AUR-437.sh E2EAUR437
Sobe um fake GitHub em loopback a partir de
tests/fixtures/scm/github/ (diff do PR 42 — incluindo o diretorio
cmdb/ que prova o defeito 4 —, paginas de arquivos, permissoes
read-write/read-only, resposta de comentario criado), compila um harness que
usa a biblioteca como o AUR-438 usara e imprime um transcript deterministico:
ARQUIVO_DO_DIFF ..., COMENTARIO_PUBLICADO path=cmdb/settings.go line=3,
POST_RECEBIDO=true no cenario de escrita;
RECUSADO_SEM_PERMISSAO_DE_ESCRITA, POST_RECEBIDO=false no cenario
somente-leitura. Tokens sao strings sinteticas montadas em runtime; nenhum
shape de credencial existe em arquivo rastreado.
Selectors e exit codes
- Aceite:
tests/acceptance/AUR-437.sh— selectorsAC-001(tudo),TestAUR437,IntegrationAUR437,E2EAUR437,AC-001-MUT-001. - Exit codes (convencao do board):
0comportamento provado;1RED comportamental;64selector desconhecido;79infraestrutura / inconclusivo (nunca evidencia de RED).
Diagnostico de falha
AUR-437/AC-001/behavior-missing: a biblioteca nao entrega o fluxo prometido (ver stderr do harness).readonly_not_refused/readonly_post_leaked: a recusa de publicacao sem escrita foi violada — exatamente o que o MUT-001 provoca.non_deterministic: duas execucoes identicas divergiram.infrastructure/missing_go,infrastructure/mktemp,loopback indisponivel(exit 79): ambiente, nunca RED.- Compatibilidade: nenhum comando ou formato publicado por outro card muda;
rollback = reverter somente os
pathsdeste card; nenhum estado externo.