Pular para conteúdo

Versionamento e release

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-10-01

Aplica-se a: todo perfil e toda stack.

Como os repos da X-Adm são versionados, como uma release acontece e o contrato do /health. A norma está na constituição; esta página é o detalhe.

SemVer — quando bumpar o quê

Todo repo segue MAJOR.MINOR.PATCH:

Bump Quando Exemplo
MAJOR quebra contrato: API incompatível, migração manual de dados ou 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

Onde mora a versão:

  • Java — gradle.properties ou o build script (version).
  • Flutter — version: X.Y.Z+N no pubspec.yaml: X.Y.Z é o SemVer, +N o build number (só cresce).
  • Node — "version" do package.json.
  • Sem manifesto próprio (site estático, repo de config) — arquivo VERSION na raiz, só X.Y.Z.
  • Lib (xadm-commons) — cada módulo tem a sua versão, e a tag é <modulo>-vX.Y.Z.

O que é uma release

Uma release é um commit + uma tag + uma entrada de CHANGELOG, sempre juntos, feitos pela skill /xadm-release — commit e tag nunca à mão:

  1. bump da versão no manifesto da stack, ou a versão que já subiu à mão;
  2. entrada no CHANGELOG.md (Keep a Changelog, pt-BR, seções Adicionado / Alterado / Corrigido / Removido, e a ### Migração quando há o que migrar);
  3. tag anotada vX.Y.Z, igual à versão do passo 1;
  4. push de commit e tag.

Versão subida antes da release: a versão do manifesto pode subir à mão antes da release — para publicar no mavenLocal e testar o consumidor, por exemplo —, com o CHANGELOG ainda em [Unreleased]. Versão acima da última tag e sem tag própria é a pendente: a /xadm-release mantém o número, avisa se ele é menor do que os commits pedem e aborta se ele já está no registro.

A skill canônica é o template xadm-release-skill.md, que a /xadm-docs instala. Projeto sem CHANGELOG.md ou sem tag: a primeira /xadm-release cria o arquivo a partir do git log e trata a "primeira release".

A tag vX.Y.Z é o release: o pipeline.yml builda os alvos do trailer Deploy: da tag (fallback build.targets do docs/app.json) e chama o control-plane. Push em master sem tag só publica o dev/ da doc.

Distribuição (CLI/worker)

CLI ou worker distribuído como artefato não tem deploy: a release publica o artefato versionado (<app>-<versão>.jar) e quem consome instala. A mecânica de versão é a mesma; não há /health — a versão aparece no --version e no log de startup (Java). O marco é a Release do Forgejo, não só a tag: o job release-forgejo do pipeline.yml (opt-in) builda o artefato no CI e publica a Release com as notas da seção do CHANGELOG e o asset anexado, e o operador baixa o artefato de lá. O job chama a API do Forgejo com secrets.RELEASE_TOKEN (escopo write:repository, secret do repo no GitHub).

Health check obrigatório

Todo app deployável define um health check (HEALTHCHECK na imagem) — sem ele o Coolify troca o container sem saber se o novo responde (Operação no Coolify).

  • É liveness, nunca readiness: o /health do HEALTHCHECK responde "o processo está vivo?" e nunca checa dependência — reiniciar o container porque o banco piscou não conserta o banco e dispara restart em cascata. Checagem de dependência é monitoramento, em mecanismo separado.
  • Boot longo se cobre com retries, não com start-period: o Coolify espera a janela start-period inteira antes de promover, então ela fica pequena (~5s), com interval curto (~15s) e retries alto (~15, cerca de 225s de tolerância). O Micronaut roda o Flyway antes de aceitar conexão, então um /health/ready separado não ajudaria sem boot assíncrono. Os números vivem no HEALTHCHECK dos Dockerfiles do kit.

Versão visível em runtime

A versão em runtime vem sempre da fonte do release (o manifesto da stack), nunca de literal no código — literal mostra a versão de ontem e se afasta mais a cada release, porque a /xadm-release só toca o manifesto e o CHANGELOG.

Shape do /health

Esta seção é a dona do contrato do /health de tudo que é servido por HTTP:

{ "status": "UP", "versao": "1.4.2", "flavor": "native", "commit": "3f2a…" }
  • Obrigatório: status (truthy quando vivo; o valor exato não é normativo) e versao (SemVer, da mesma fonte do release). Responde 200 quando vivo, anônimo, flat (campos no raiz, sem agregar indicadores de dependência).
  • commit: o sha do build, obrigatório em app que roda o smoke de produção — é como ele confere a identidade do que está no ar.
  • flavor ("native" ou "jvm"): o que roda de fato, para distinguir dois deploys da mesma versão.
  • Opcionais: started_at e o que mais ajudar o diagnóstico. Acrescentar campo é livre; mudar ou remover os obrigatórios é mudança de contrato e muda aqui primeiro.
  • Java/Micronaut — o HealthController e o InfoController da xadm-comum-web servem o /health flat (com versao, flavor e commit) e o /info ({versao}); o app desliga os endpoints de management (endpoints.health.enabled: false, endpoints.info.enabled: false), senão as rotas colidem e toda request responde 400. A versão chega por um version.properties gerado do project.version, e a primeira linha do main a loga. A detecção do flavor lê org.graalvm.nativeimage.imagecode dentro do método, nunca num static final — sob native-image o inicializador estático pode rodar no build, onde a property vale "buildtime". A versão do @OpenAPIDefinition é a do contrato HTTP, outra coisa.
  • Java CLI (picocli) — o --version lê o mesmo version.properties por um IVersionProvider.
  • Node — a version do package.json.
  • Flutter web — é servido por HTTP e tem /health com {status, versao, commit}, gerado no build (o Dockerfile o escreve e o nginx o serve): versao vem do version.json que o flutter build web gera, e commit do ARG XADM_COMMIT que o CI passa. O version.json não ganha campo e não é servido como /health: é o artefato do auto-update do app (smoke de produção).
  • Flutter mobile — sem servidor e sem /health: a versão aparece no app, via package_info_plus.
  • App sem HTTP (cron, worker) — loga a versão na primeira linha do startup.
  • Site estático (o central) — /versao.txt (rotulado site X.Y.Z) e /commit.txt.

Três versões: norma, kit e site

O repo central tem três números, desacoplados:

Norma (constituição) Kit Site
O que versiona as regras templates, scripts e skills o portal docs.xadm.biz
Fonte frontmatter versao: da constituição arquivo KIT na raiz do central arquivo VERSION na raiz do central
Publicado em /toolchain/constituicao-versao.txt /toolchain/kit-versao.txt /versao.txt, tag vX.Y.Z, CHANGELOG.md
No repo consumidor docs/app.json constituicao docs/app.json kit —
Sobe quando a norma muda um artefato do kit muda sai uma release do central
flowchart TB
    subgraph central["repo central"]
        N["versao: da constituição<br/>(norma)"]
        K["arquivo KIT<br/>(templates, scripts, skills)"]
        V["arquivo VERSION<br/>(site)"]
    end
    N --> P1["/toolchain/constituicao-versao.txt"]
    K --> P2["/toolchain/kit-versao.txt"]
    K --> M["manifesto.json<br/>mudou_em por artefato"]
    V --> P3["/versao.txt · tag vX.Y.Z · CHANGELOG"]
    P1 --> H{"checa-constituicao.sh<br/>compara com o app.json"}
    P2 --> H
    M --> H
    H -- "kit defasado" --> R["/xadm-docs re-deriva<br/>sem perguntar"]
    H -- "norma defasada" --> A["/xadm-docs re-audita<br/>com aprovação"]
    R --> J["docs/app.json do repo<br/>grava constituicao e kit"]
    A --> J

Bump da norma: PATCH = redação ou esclarecimento, sem obrigação nova; MINOR = obrigação nova sobre um repo; MAJOR = mudança de estrutura ou de contrato que exige migração de todo repo (âncoras, campos, perfis).

Bump do kit: PATCH = ajuste de artefato; MINOR = artefato novo ou removido; MAJOR = redefinição do propósito de um artefato (um re-pull cego quebraria o repo — leva nota de migração no CHANGELOG).

  • Versão-base: o docs/app.json de cada repo registra constituicao e kit; é o único lugar do número. VERSION, /versao.txt e a tag do central são a versão do site, nunca a da norma — num clone local do central, a norma é o versao: do frontmatter.
  • Manifesto: manifesto.json registra, por artefato do kit, o hash, a versão do kit em que mudou (mudou_em), perfis, stacks e destino, a lista opcionais, a lista remover e a seção scripts (sem destino: o CI e as skills baixam frescos, e a cópia deles no repo é sobra); o scripts/gera-manifesto.py --update carimba, e o --check do CI do central falha se o manifesto não refletir o estado (norma, kit ou hash).
  • Carimbo no CHANGELOG do central: cada heading de release traz o sufixo (constituição C.C.C · kit K.K.K), e a mensagem do commit de release traz as três versões.
  • Defasagem: o hook checa-constituicao.sh diz, ao abrir a sessão, se defasou a norma, o kit ou os dois; a /xadm-docs re-deriva o kit sem perguntar e, com a norma nova, re-audita doc e código contra as páginas de engenharia lidas ao vivo (/toolchain/raw/engenharia/<pagina>.md).
  • Validador fresco: os scripts publicados imprimem na primeira linha constituição C.C.C · kit K.K.K; quem valida baixa na hora e confere o kit contra o kit-versao.txt antes de confiar no resultado.

O que o CI valida

O valida-release.py (publicado em /toolchain/) confere que a versão do manifesto é SemVer e tem entrada ## [X.Y.Z] no CHANGELOG.md, sem link ](docs/…) nessa seção, e, na tag, que a tag bate com o manifesto. O link reprova porque o CHANGELOG.md entra no site embutido em docs/, onde vira docs/docs/…: o mkdocs build --strict só o acusaria no job docs, depois do gate. Roda como último step do job gate do pipeline.yml, só na tag e mesmo com teste vermelho; o gate vermelho por ele barra o docs e o deploy da tag.

- name: Validar contrato de release (SemVer, CHANGELOG e tag)
  if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/v') }}
  run: |
    curl --connect-timeout 10 --max-time 60 -fsSL -o /tmp/valida-release.py https://docs.xadm.biz/toolchain/valida-release.py
    python3 /tmp/valida-release.py . --tag "${GITHUB_REF#refs/tags/}"
  • Lib: o gate do xadm-commons roda check javadoc (erro de javadoc não pode aparecer só no publish, depois da tag), e o release de um módulo valida com valida-release.py <pasta-do-módulo> --tag <modulo>-vX.Y.Z.
  • Repo adotando o padrão: entre criar o CHANGELOG.md e a primeira /xadm-release, o validador acusa "sem a seção da versão" — é o esperado.