ADR 0004 — Cadastro de credenciais de git do usuário
Contexto
Item 3 da Fase 2 (ver CLAUDE.md): credenciais de git do usuário
(tokens do GitHub/GitLab usados pelo GithubProvider/GitlabProvider
pra operar em nome dele) precisam viver na mesma tabela
user_credentials das API keys de LLM (Fase 1), com a mesma envelope
encryption, e com teste de conexão obrigatório no cadastro.
Duas questões de shape:
user_credentials.providerjá erallm_providerenum (ollama/anthropic/...). Um token de git não é um provider de LLM — alargarllm_providermisturaria dois domínios num enum só usado também pormodels/token_usage(LLM-only de verdade). Reaproveitargit_provider(usado porproject_git_connections) também não serve: ele inclui'local', que não faz sentido como provider de uma credencial (não existe token pra git local).- Um token inválido/revogado só é descoberto ao tentar usá-lo — sem teste de conexão, o cadastro "sucede" e a falha só aparece no primeiro bootstrap de Gitflow, bem mais tarde e mais caro de diagnosticar.
Decisão
Enum dedicado credential_provider (migration 0007), com
CredentialProviderName = LLMProviderName | GitCredentialProviderName
em packages/shared — união dos dois domínios só no tipo que
UserCredentialRepository usa, sem misturar os enums de banco.
GitCredentialProviderName é Extract<GitProviderName, 'github' | 'gitlab'> — deriva do enum de provider de git, não duplica a lista,
mas exclui 'local' estruturalmente.
Teste de conexão SÍNCRONO e OBRIGATÓRIO antes de cifrar/persistir.
RegisterGitCredentialUseCase.execute (novo) chama
GitCredentialConnectionTester.test(provider, token) primeiro; só se
isso resolver é que o token é cifrado
(EncryptionService.encrypt) e gravado
(UserCredentialRepository.upsert). Numa falha, nada é escrito — ver
GitCredentialConnectionTestFailedError em
domain/git/git-errors.ts. A implementação real
(GitCredentialConnectionTesterImpl) faz a chamada mais barata
possível em cada API pra confirmar que o token autentica:
GET /user (Octokit users.getAuthenticated) no GitHub,
GET /user (Gitbeaker Users.showCurrentUser) no GitLab — nenhuma
tenta listar repos ou qualquer coisa que dependa de escopo além de
autenticação básica.
PAT sempre via token: no Gitbeaker, nunca oauthToken:. O
GitLab valida os dois de formas diferentes (header PRIVATE-TOKEN vs.
Authorization: Bearer) — não são intercambiáveis. oauthToken fica
reservado pro fluxo de OAuth de projeto
(project_git_connections, Fase 2 item 4+), que essa sessão não
altera.
Endpoint próprio só pro registro; GET/DELETE reaproveitados.
POST /users/me/git-credentials (novo,
GitCredentialsController) é o único caminho nesta sessão, porque só
o registro precisa do teste de conexão síncrono. Listagem e remoção
já existiam em CredentialsController (Fase 1, LLM) sobre a mesma
tabela/repositório — UserCredentialRepository foi alargado de
LLMProviderName pra CredentialProviderName (era o LLMProviderName
solto que sobrou e quebrava o build, ver delete() em
user-credential.repository.ts) em vez de duplicar
list/delete pra git. Nenhum @RequireRole no controller novo: é sobre
a credencial do próprio usuário autenticado, mesmo padrão do endpoint
LLM equivalente.
Falha de conexão mapeia pra 422, não 400 nem 409
(git-provider-error.filter.ts): não é payload malformado (400) nem
conflito com estado existente (409) — é uma entidade semanticamente
inválida (token que nunca autenticou), o caso clássico de 422.
Consequências
- Cadastrar uma credencial de git agora faz uma chamada de rede síncrona à API do provider antes de responder — o endpoint é mais lento que um cadastro "cego", de propósito (ver Decisão).
UserCredentialRepository(e sua migration0007) agora serve dois domínios (LLM e git) pela mesma tabela/enum de tipo — qualquer provider de credencial futuro (git ou não) entra porCredentialProviderName, não por um enum novo.- O teste de conexão não é retentado (
GitCredentialConnectionTesternão usawithRetry, ver 0003) — uma falha transitória de rede no cadastro exige que o usuário tente de novo manualmente; aceitável porque é uma ação interativa única, não uma operação de background.