Compatibility
An installed CLI is not updated when the portal is. Changing what the portal answers breaks machines nobody touched — and the breakage shows up nowhere until someone runs a pipeline that already worked.
There are two layers, they break in different ways, and they are easy to confuse.
Layer 1 — the response envelope
Section titled “Layer 1 — the response envelope”The fields the portal invents: namespace, nome, versao, efeitos,
privilegioMaximo, dependenciasAdicionais, depreciada.
Rule inside /api/v1: additions only.
| Change | Allowed | Why |
|---|---|---|
| new field | yes | the CLI ignores what it does not know |
| rename a field | no | the CLI reads by name |
| remove a field | no | same |
| change the type | no | "1" and 1 are not the same thing on the other side |
| always-present field becoming sometimes-absent | no | that is conditional removal |
Nulls travel explicitly: motivoDepreciacao arrives as null when there is no
deprecation, rather than disappearing. The shape of the response does not change
with the data.
Breaking any of these rules requires /api/v2 served in parallel — v1 does
not go away while there is an old CLI installed, and there is no way to know
there is not.
What guarantees it. CatalogoApiTest freezes the shape of every response,
including the field count. Renaming a record field turns the test red. It has
been verified that it actually fails when that happens — a contract test that
does not catch the break is worse than none, because it gives false confidence.
Layer 1b — the reason for the refusal
Section titled “Layer 1b — the reason for the refusal”When the portal refuses, the body is a ProblemDetail (RFC 9457) with three
things, each serving a different reader:
| Field | For whom | Stability |
|---|---|---|
| HTTP status | the CLI | contract |
codigo |
the CLI | contract |
detail |
the person | none — it is prose, and it changes language |
problemas |
the person | a list, only on 422 |
codigo exists because status alone is not enough: two 403s can call for
opposite actions. “You are not a member of the organisation” says to ask for the
role; “this task has maintainers and you are not one of them” says to ask for
maintenance — and sending the first to someone who is already a publisher makes
them go looking for access they have.
The CLI decided that by looking for the word mantenedores inside detail.
It worked, and it would have silently stopped working on the portal’s first
translation: wrong hint, no failure, same exit code. That is the worst kind of
break — the one that does not show.
The rules match layer 1: a new code is an addition, renaming or removing is not. The status is fixed per code: the same code answering 400 here and 403 there would force the CLI to look at both. And a CLI never fails on a code it does not know — it falls back to the status generic, which is what lets the portal gain new reasons before the CLIs are updated.
Language. detail comes out in the language of the request’s
Accept-Language — English by default, Portuguese if asked, English for
anything else. The response declares what it did in Content-Language. codigo
does not change with the language: that is the whole point of it existing.
What guarantees it. CodigoDeErroTest freezes the list by hand — deriving
it from the enum would make the test agree with the change it is supposed to
prevent. It also requires an English message for every code, otherwise the raw
key would leak to the screen. IdiomaDaRespostaTest goes end to end: what broke
once was the link between the resolver and the header, not each piece in
isolation.
Layer 2 — the document
Section titled “Layer 2 — the document”The documento is the task.yaml as it was published. The portal does not
invent it: it belongs to the spec, and is versioned by the apiVersion it
carries inside (oren.sh/v1).
Here the break is subtler, because it does not go through the portal. The
spec schemas are strict (additionalProperties: false in 4 places in the task
one, 7 in the implementation one). So:
If the spec gains a field, and someone publishes a document that uses it, every CLI older than that field rejects the document — with “does not satisfy the schema”, which explains nothing to whoever consumes it.
It is not a portal bug and the portal cannot avoid it alone. It follows from two
choices that remain correct: a strict schema (which is what would have caught
the incremenMajorVersion that survived 16 releases) and serving the document
intact.
Rule: within oren.sh/v1, the spec only admits optional additions. A new
field requires oren.sh/v2, and then it is the CLI that decides whether it
knows how to read it.
What the portal guarantees today. ValidadorDeDocumentos refuses at publish
time any document that does not satisfy the schema the portal knows. That
prevents the common case — publishing junk — but not the layer 2 case, which is
publishing something too valid for an old CLI.
Language
Section titled “Language”Three places, one rule: English by default, Portuguese when asked, English for any other language.
| Where | Where the choice comes from | Catalogue |
|---|---|---|
| CLI | OREN_LANG > LC_ALL > LC_MESSAGES > LANG |
cli/src/i18n/ |
| Portal (API) | the request’s Accept-Language |
portal/src/main/resources/messages*.properties |
| Portal (web) | the picker’s choice > navigator.languages |
portal/web/src/app/i18n/ |
| Docs | the URL (/ or /pt/) |
docs/src/content/docs/ |
English is canonical in all of them: in the TypeScript catalogues it defines the
TYPE the other languages must satisfy, and in messages.properties it is the
file WITHOUT a suffix, which is the fallback for any locale.
Both ends send Accept-Language with the language THEY chose, not the system’s.
Without that the screen comes out in one language and the error message inside
it in another — which is what happened to whoever chose Portuguese on a browser
configured in English.
What is NOT translated: command names (oren run is the same word
everywhere), help metavariables (<source>, [pipeline]), roles (publicador,
admin — they are database values and appear in the API), the problemas list
of a 422, which comes from the schema validator in English, and the human text
of the published contracts, which is immutable.
What is not contract
Section titled “What is not contract”- The order of implementations. It is presentation, computed by privilege.
Changing the criterion breaks nobody: the CLI does not choose by the ranking,
the
oren.yamldoes. - The columns derived in the database.
efeitos,privilegio_maxand company are recomputable from the document. The database schema can change; the response cannot.