Versionamento e release¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-06-30
Como os apps da X-Adm são versionados e como uma release acontece. A norma está na constituição §5; esta página é o detalhe operacional.
SemVer — quando bumpar o quê¶
Todo app segue MAJOR.MINOR.PATCH:
| Bump | Quando | Exemplo |
|---|---|---|
| MAJOR | quebra contrato: API muda de forma incompatível, migração manual de dados/config, comportamento que o usuário percebe como "outro sistema" | trocar o formato da planilha aceita |
| MINOR | funcionalidade nova, compatível | novo relatório, novo endpoint |
| PATCH | correção ou ajuste interno, sem mudança de contrato | bugfix, bump de dependência |
Por stack:
- Java — versão no build (
build.gradle/gradle.propertiesoupom.xml). - Flutter —
version: X.Y.Z+Nnopubspec.yaml:X.Y.Zé o SemVer,+Né o build number (inteiro, só cresce — exigência das lojas). - Node — campo
"version"dopackage.json, mesma semântica. - Stack sem manifesto próprio (site estático, scripts) — arquivo
VERSIONna raiz do repo, contendo sóX.Y.Z. É o caso do próprio site central de documentação.
O que é uma release¶
Uma release é um commit + uma tag + uma entrada de changelog, sempre juntos:
- Bump da versão no arquivo de build/manifesto.
- Entrada nova no
CHANGELOG.md— formato Keep a Changelog, em pt-BR, seçõesAdicionado / Alterado / Corrigido / Removido. - Tag Git
vX.Y.Z(comv, igual à versão do passo 1). - Push de commit e tag.
Release se faz pela skill /xadm-release (Claude Code), que executa os
quatro passos na ordem e sem esquecer nenhum — não bumpe versão à mão.
A versão canônica da skill é o template
templates/xadm-release-skill.md
do mirror público (também na página de templates): copie para
.claude/skills/xadm-release/SKILL.md no repo do app, troque <app>/<repo> e
mantenha só o bloco de "Particularidades" da sua stack. Não mude o fluxo —
divergência entre apps é o que o template evita.
Projeto ainda sem CHANGELOG.md? Não crie à mão. A primeira
/xadm-release o cria do zero (Keep a Changelog, pt-BR) inferindo as entradas
do git log desde o início — o changelog nasce cobrindo o histórico na 1ª
release, não precisa existir desde o commit 1. Idem para projeto sem tags: a skill
trata "primeira release" e você escolhe a versão (ex. a que já está no build).
O deploy não muda: push em master continua sendo o gatilho do Coolify. A tag não deploya nada — ela é o marco rastreável de "o que estava no ar quando a versão X saiu".
Distribuição vs deploy (arquétipo CLI/worker)¶
O fluxo acima assume app servidor com deploy contínuo (Coolify, push em master).
Um CLI/worker distribuído como artefato (um .jar, um binário) não tem deploy
contínuo — ele é distribuído: a release publica/entrega o artefato versionado
(<app>-<versão>.jar), e quem consome instala/atualiza. A mecânica de versão é a mesma
(SemVer + tag + CHANGELOG, via /xadm-release); muda o destino: distribuição de
artefato, não deploy de serviço. Sem /health nem health check (não é servidor) — a
versão aparece no --version/log de startup (Java).
O marco é a Release do Forgejo, não só a tag. No Forgejo, tag ≠ Release: a tag
sozinha deixa a aba Releases vazia e o artefato sem proveniência ("qual jar é a
v0.1.6? quem buildou?"). Para app que distribui artefato, o release.yml (bloco
opt-in do template) builda o artefato no CI e publica a Release — tag + notas
da seção do CHANGELOG + o asset anexado —, que vira o marco real do que foi
liberado, com download reproduzível. O operador baixa o jar da Release, não
recebe uma cópia manual. Detalhe: o build precisa da imagem da stack (ci-java:<v>, a
ci-base não tem JDK); a API do Forgejo é chamada por curl com o
secrets.GITHUB_TOKEN (escopo de escrita em release; se restrito, token dedicado da org).
Health check obrigatório¶
Todo app deployável no Coolify define um health check (HEALTHCHECK na imagem ou
endpoint configurado no painel, ex. /health) — sem ele o rolling update troca o
container sem saber se o novo responde. Operação:
Coolify.
Versão visível em runtime¶
Diagnóstico de produção não pode depender de abrir o painel. Como já há
monitoramento (o watchdog), o /health deixou de ser formato livre: tem um
shape mínimo (a cláusula "monitoramento → shape vira contrato" foi acionada).
A versão em runtime vem sempre da fonte do release (o manifesto da stack —
gradle.properties/build.gradle.kts, pubspec.yaml, package.json, VERSION),
nunca um literal no código. Literal mente (mostra a versão de ontem) e
drifta a cada release, porque a /xadm-release só toca o manifesto + o
CHANGELOG — não caça strings espalhadas. Uma única fonte; o runtime a lê.
Shape mínimo do /health (tudo que é servido por HTTP)¶
{ "status": "ok", "versao": "1.4.2" }
- Obrigatório:
status(string de liveness truthy quando vivo — o valor exato não é normativo:"ok","UP", etc. valem; o/healthnativo do Micronaut responde"UP"e está conforme, sem remapear) eversao(SemVer legível, da mesma fonte do release — sem criar segunda fonte de versão). Responder 200 quando vivo. - Opcionais úteis:
commit,started_at. Acrescentar campo é livre; mudar/remover os dois obrigatórios é mudança de contrato — muda no padrão primeiro.
Receita por stack (como popular o versao)¶
- Java/Micronaut — uma task Gradle gera
version.properties(deproject.version) no classpath; umInfoSourceexpõe a versão em/infoe no/health, e a 1ª linha domainloga a versão. ⚠️ A versão do@OpenAPIDefinitioné contrato HTTP da API, outra coisa — não confundir com a versão de release. - Java CLI (picocli) — o
--versionlê de umIVersionProviderque carrega oversion.propertiesgerado doproject.version(a mesma task Gradle do Micronaut); nunca@Command(version = "0.1.0")literal — isso mente e drifta a cada release. - Node — ler a
versiondopackage.json. - Flutter web — é servido por HTTP, então tem
/healthe responde o shape mínimo{status, versao}(o nginx que serve o app monta a resposta, lendo a versão doversion.jsondo build). ⚠️ Não sirva oversion.jsoncru como/health: ele é outro endpoint (artefato do build, consumido pelo auto-update do app) e não temstatus— e seu campo éversion, nãoversao./health={status, versao};/version.json= artefato do build, separado. - Flutter mobile — sem servidor, sem
/health: a versão aparece in-app viapackage_info_plus. - App sem HTTP (cron, worker) — loga a versão na 1ª linha do startup.
- Site estático (ex. o central) — expõe o equivalente em
/versao.txt(+/commit.txt), não um JSON. O/versao.txté auto-rotulado (site X.Y.Z, não o número nu) para não ser confundido com a versão da constituição — ver abaixo.
Duas versões: site x constituição¶
O repo central tem dois números de versão desacoplados — confundi-los já levou um app a registrar a versão errada. Saiba distinguir:
| Versão do SITE/portal | Versão da CONSTITUIÇÃO | |
|---|---|---|
| O que versiona | o site docs.xadm.biz (deploy, HTML) |
o documento da constituição (o padrão em si) |
| Fonte | VERSION na raiz do repo central |
frontmatter versao: de docs/documentacao/constituicao.md |
| Onde aparece | /versao.txt (site X.Y.Z), tag vX.Y.Z, CHANGELOG.md, rodapé “Site vX.Y.Z” |
constituicao-versao.txt, app.json constituicao:, rodapé “Constituição vX.Y.Z” |
| Quem bumpa | /xadm-release (push em master) |
edição da constituição (+ regen do manifesto) |
Regra de ouro: a versão-base que um app registra (campo constituicao: do
app.json) é sempre a da constituição, nunca a do site. As duas evoluem em
ritmos diferentes (a constituição muda menos que o site).
Armadilha do clone local / bootstrap. Um app sem a /xadm-docs instalada só tem
o link da constituição como porta de entrada; e se houver um clone do repo central no
disco, o sinal mais gritante ali é o VERSION (= versão do site) e a tag vX.Y.Z
— que são o número errado para "versão da constituição". A versão da constituição é
sempre o versao: do frontmatter (ou constituicao-versao.txt no site), nunca o
VERSION. Na dúvida, é o número rotulado como “Constituição” no rodapé.
O que o CI valida¶
O valida-release.py (mantido no repo central e publicado neste site, como
o valida-frontmatter.py) confere:
- a versão do manifesto é SemVer e tem entrada
## [X.Y.Z]noCHANGELOG.md; - em push de tag, a tag
vX.Y.Zbate com a versão do manifesto.
Passo para o workflow de CI do app (no de build/teste — não no docs.yml):
- name: Validar release
run: |
curl -fsSL -o /tmp/valida-release.py \
https://docs.xadm.biz/toolchain/valida-release.py
python3 /tmp/valida-release.py .
Em workflow disparado por tag, acrescente --tag "${GITHUB_REF_NAME}" para
conferir tag × manifesto.
Validação local: o mesmo comando, na raiz do repo.
Repo adotando o padrão agora
Entre criar o manifesto/CHANGELOG.md e fazer a primeira /xadm-release,
o validador acusa "sem a seção da versão" — é o esperado. Rode a primeira
/xadm-release antes de ligar o passo no CI.