Compatibilidade
Uma CLI instalada não é atualizada quando o portal é. Mudar o que o portal responde quebra máquinas que ninguém tocou — e a quebra não aparece em lugar nenhum até alguém rodar um pipeline que já funcionava.
São duas camadas que quebram de formas diferentes, e é fácil confundi-las.
Camada 1 — o envelope da resposta
Seção intitulada “Camada 1 — o envelope da resposta”Os campos que o portal inventa: namespace, nome, versao, efeitos,
privilegioMaximo, dependenciasAdicionais, depreciada.
Regra dentro de /api/v1: só adição.
| Mudança | Permitida | Por quê |
|---|---|---|
| campo novo | sim | a CLI ignora o que não conhece |
| renomear campo | não | a CLI lê pelo nome |
| remover campo | não | idem |
| trocar o tipo | não | "1" e 1 não são a mesma coisa do outro lado |
| campo sempre-presente virar às-vezes-ausente | não | é remoção condicional |
Nulos viajam explícitos: motivoDepreciacao vem como null quando não há
depreciação, em vez de sumir. A forma da resposta não muda conforme o dado.
Quebrar qualquer uma dessas regras exige /api/v2 servido em paralelo — v1
não sai do ar enquanto houver CLI antiga instalada, e não há como saber que não
há.
O que garante. CatalogoApiTest congela a forma de cada resposta, incluindo
a contagem de campos. Renomear um campo de record deixa o teste vermelho. Foi
verificado que ele falha de verdade quando isso acontece — um teste de contrato
que não pega a quebra é pior que nenhum, porque dá confiança falsa.
Camada 1b — o motivo da recusa
Seção intitulada “Camada 1b — o motivo da recusa”Quando o portal recusa, o corpo é um ProblemDetail (RFC 9457) com três
coisas, e cada uma serve a um leitor diferente:
| Campo | Para quem | Estabilidade |
|---|---|---|
| status HTTP | a CLI | contrato |
codigo |
a CLI | contrato |
detail |
a pessoa | nenhuma — é prosa, e muda de idioma |
problemas |
a pessoa | lista, só em 422 |
O codigo existe porque status sozinho não basta: dois 403 podem pedir ações
opostas. “Você não é membro da organização” manda pedir o papel; “esta task tem
mantenedores e você não é um deles” manda pedir a manutenção — e mandar a
primeira a quem já é publicador faz a pessoa procurar um acesso que ela tem.
A CLI decidia isso procurando a palavra mantenedores dentro do detail.
Funcionava, e teria parado de funcionar em silêncio na primeira tradução do
portal: dica errada, sem falha, mesmo código de saída. É o pior tipo de quebra —
a que não aparece.
Regras, iguais às da camada 1: código novo é adição, renomear ou remover não. O status é fixo por código: o mesmo código respondendo 400 aqui e 403 ali obrigaria a CLI a olhar os dois. E uma CLI nunca falha diante de um código que não conhece — cai no genérico do status, que é o que permite ao portal ganhar motivos novos antes de as CLIs serem atualizadas.
O idioma. O detail sai no idioma do Accept-Language da requisição —
inglês por padrão, português se pedido, inglês para qualquer outro. A resposta
declara o que fez em Content-Language. O codigo não muda com o idioma:
é o ponto todo de ele existir.
O que garante. CodigoDeErroTest congela a lista à mão — derivá-la do enum
faria o teste concordar com a mudança que ele deveria impedir. Ele também exige
mensagem em inglês para todo código, senão a chave crua vazaria para a tela.
IdiomaDaRespostaTest vai pela ponta: o que quebrou uma vez foi a ligação entre
o resolver e o cabeçalho, não cada peça isolada.
Camada 2 — o documento
Seção intitulada “Camada 2 — o documento”O documento é o task.yaml como foi publicado. O portal não o inventa: ele
pertence ao spec, e é versionado pelo apiVersion que carrega dentro
(oren.sh/v1).
Aqui a quebra é mais sutil, porque não passa pelo portal. Os schemas do spec
são estritos (additionalProperties: false em 4 lugares no de task, 7 no de
implementation). Então:
Se o spec ganhar um campo, e alguém publicar um documento que o use, toda CLI anterior àquele campo rejeita o documento — com “não satisfaz o schema”, que não explica nada para quem consome.
Não é um bug do portal e ele não pode evitar sozinho. Decorre de duas escolhas
que continuam certas: schema estrito (foi o que teria pego o
incremenMajorVersion que passou 16 releases) e servir o documento íntegro.
Regra: dentro de oren.sh/v1, o spec só admite adições opcionais. Um campo
novo exige oren.sh/v2, e aí é a CLI que decide se sabe ler.
O que o portal garante hoje. ValidadorDeDocumentos recusa na publicação
qualquer documento que não satisfaça o schema que o portal conhece. Isso impede
o caso comum — publicar lixo — mas não o caso da camada 2, que é publicar algo
válido demais para uma CLI velha.
Três lugares, uma regra: inglês por padrão, português quando pedido, inglês para qualquer outro idioma.
| Onde | De onde vem a escolha | Catálogo |
|---|---|---|
| CLI | OREN_LANG > LC_ALL > LC_MESSAGES > LANG |
cli/src/i18n/ |
| Portal (API) | Accept-Language da requisição |
portal/src/main/resources/messages*.properties |
| Portal (web) | escolha no seletor > navigator.languages |
portal/web/src/app/i18n/ |
O inglês é o canônico nos três: nos catálogos em TypeScript ele define o TIPO
que as outras línguas precisam satisfazer, e no messages.properties ele é o
arquivo SEM sufixo, que é o fallback de qualquer locale.
As duas pontas mandam Accept-Language com o idioma que ELAS escolheram, não o
do sistema. Sem isso a tela sai numa língua e a mensagem de erro dentro dela em
outra — o que acontecia com quem escolhia português num navegador configurado em
inglês.
O que NÃO é traduzido: os nomes dos comandos (oren run é a mesma palavra em
todo lugar), os metavalores do help (<source>, [pipeline]), os papéis
(publicador, admin — são valores do banco e aparecem na API), e a lista
problemas de um 422, que vem do validador de schema em inglês.
O que não é contrato
Seção intitulada “O que não é contrato”- A ordem das implementações. É apresentação, calculada por privilégio.
Mudar o critério não quebra ninguém: a CLI não escolhe pelo ranking, quem
escolhe é o
oren.yaml. - As colunas derivadas do banco.
efeitos,privilegio_maxe companhia são recalculáveis do documento. O schema do banco pode mudar; a resposta, não.