Pular para conteúdo

Changelog

Todas as mudanças relevantes deste projeto são documentadas aqui.

O formato segue o Keep a Changelog e o projeto adere ao SemVer — ver versionamento.

Unreleased

1.2.1 - 2026-10-02 (constituição 2.2.2 · kit 2.0.11)

Alterado

  • Túnel do Sentry no web por Object.defineProperty (norma 2.2.2): no bi-comercial e no bi-transporte, o patch que envolvia o Sentry.init não pegava, porque o sentry_flutter 9.x recarrega o SDK do CDN no boot e reatribui o init, e os envelopes iam direto para o GlitchTip. A página do Flutter passa a dar a receita que intercepta a propriedade init com setter, e a armadilha ganha a cura certa.
  • build_web sem cache gha (kit 2.0.11): o cache-to: type=gha,mode=max subia a imagem Flutter inteira a cada release, de 2 a 9 min, e nenhuma camada era lida: o cache do Actions é por ref, a tag não lê o de outra tag e o master não builda desde o 2.0.10. Sem ele o build_web cai de 10–14 min para ~4,5 min (onpetro-bi e vantroba-bi). O harness ganha a asserção e o mutante.

Migração

  • Kit 2.0.11, ao re-derivar o pipeline.yml de app Flutter web: saem as linhas cache-from e cache-to do docker/build-push-action do build_web.

1.2.0 - 2026-10-01 (constituição 2.2.1 · kit 2.0.10)

Adicionado

  • Migration de raw table congela a própria DDL (norma 2.2.0): no bi-transporte, as migrations das raw tables derivavam colunas e índices da lista viva, então uma futura ALTER TABLE ADD COLUMN quebraria todo install novo com duplicate column, sem aparecer no dev. A página do PowerSync passa a exigir migration aplicada imutável e com DDL literal, travada por teste de paridade: migrations num banco vazio = DDL viva = syncedColumns, e o upgrade chega ao mesmo esquema do install novo.
  • Coordenadas do sourcemap do web cobradas no gate (kit 2.0.9): no bi-comercial, o upload do sourcemap é degradável, então uma coordenada faltando no app.json passava calada e o evento chegava minificado no GlitchTip. O valida-frontmatter.py passa a reprovar o app com web em build.targets e features.glitchtip.enabled=true quando falta o DSN com host, o features.glitchtip.project ou o smoke.glitchtip.org, que são as três coordenadas que o build_web deriva.

Alterado

  • Menos minutos do GitHub Actions por release (kit 2.0.10, norma 2.2.1): em 30 dias os 20 repos gastaram ~2.016 minutos cobrados, acima da cota de 2.000, e o Actions bloqueou os jobs em 30/09. O GitHub cobra cada job arredondado ao minuto, e 518 dos 805 jobs duraram menos de 60 s. O commit chore(release) no master deixa de alocar runner (o if: do decide pula o job, também no pipeline-config.yml e no build-site.yml do central, que ignora ainda push só de backlog e andaime). O release-check vira o último step do gate, só na tag e mesmo com teste vermelho, e o smoke vira o último step do deploy. O docs passa a esperar o gate: no push de tag só publica com ele verde e, verde, libera o deploy; falha só de publicação no Garage vira aviso, sem atualizar o versions.json. A constituição muda só de redação (o contrato de release é passo do gate). O CI do central passa a rodar o actionlint 1.7.12 (pinado com sha256) nos dois templates e no build-site.yml, porque o harness lê texto e não avalia expressão do Actions.

Migração

  • Kit 2.0.10, ao re-derivar o pipeline.yml (ou o pipeline-config.yml): os jobs release-check e smoke somem, e quem personalizou o template refaz à mão:
  • tirar o release-check do needs: do deploy e pôr o docs;
  • mover os steps próprios do job smoke para o fim do deploy, lendo steps.pre.outputs.*;
  • quem tem o release-forgejo descomentado leva o startsWith(github.ref, 'refs/tags/v') no if:, senão ele cria Release no Forgejo a cada push no master.

1.1.1 - 2026-10-01 (constituição 2.1.0 · kit 2.0.8)

Adicionado

  • Sync travado do PowerSync vira evento no GlitchTip (norma 2.1.0): no app Flutter com PowerSync, o downloadError só virava breadcrumb e o sync parado não gerava evento nenhum; um cliente do bi-comercial ficou mais de 5 min com dado velho sem nada chegar ao GlitchTip. A página do PowerSync passa a exigir que o app reporte o primeiro downloadError de cada tipo e rode um vigia de sync travado (download sem avançar, ou sem conexão com erro, por 5 min; ocioso não conta), testado com relógio injetado.
  • Armadilha do gatilho pendente no PowerSync: cache em memória que recarrega no checkpoint e só quando a tabela mudou ficava um checkpoint atrás, porque o onChange com throttle chega depois do statusStream do mesmo checkpoint. A tabela de armadilhas do cliente ganha a cura: o gatilho sem escrita vista fica pendente e dispara quando o onChange chega.
  • Pipeline mudado não passa calado até a tag (kit 2.0.7): no xadm-commons, o rename do env do publish passou por todos os gates e só falhou no job da tag, com a tag já no ar. O filtro code do pipeline.yml do app passa a incluir .github/workflows/**, então o push que mexe no pipeline roda o gate; a /xadm-release detecta pipeline mudado desde a última tag, confere com o dev os nomes de env que o build lê e avisa no relatório. A página de CI e testes ganha a seção Caminho que só roda na tag.

Corrigido

  • Todo curl do pipeline com timeout (kit 2.0.8): com um dos dois IPs da zona *.xadm.biz fora do ar, o curl sem timeout ficava pendurado no connect desse IP e o job morria no timeout-minutes. Todo curl do pipeline.yml e do pipeline-config.yml passa a levar --connect-timeout 10 --max-time 60 (600 s no upload de asset de release), e o testa-guardas-pipeline.py reprova curl sem eles. Os deploys pelo control-plane falham com o nome do CENTRAL_DEPLOY_TOKEN quando o secret falta, e o aviso ao central vira ::warning:: em vez de um 401.

1.1.0 - 2026-09-30 (constituição 2.0.2 · kit 2.0.6)

Adicionado

  • Guarda dos ícones do Flutter web: o app instalado como PWA saía com o logo do Flutter, porque os ícones de web/icons/ eram os do flutter create e nenhum gate olhava para eles. O gate da variante Flutter web passa a rodar o checa-icones-web.py, que reprova PNG de web/ idêntico ao do scaffold e avisa, sem reprovar, quando o alvo do apple-touch-icon ou um ícone maskable tem transparência. A página do Flutter ganha a receita dos ícones do web.
  • Chamada de app para app pela URL pública, com DNS local por host (norma 2.0.2, decisão 0039): a zona *.xadm.biz passou a ter dois registros A, e o hairpin NAT de dentro do host falha por um deles. Chamada de um app ao domínio público de outro falhava às vezes com No route to host (GlitchTip #1356, thoms-webstorm → int.thoms), e a varredura achou o mesmo caminho em integradores, pied, central-backend, apps de planilha e no SENTRY_DSN. A decisão fixa a URL pública como único caminho entre apps (nunca IP, alias Docker nem --add-host), o xadm-dns em todo host Coolify e o --dns 10.0.1.1 --dns 1.1.1.1 em todo recurso. A página do Coolify ganha a seção da chamada de app para app, o provisionamento do xadm-dns no baseline do host, o passo no criar recurso e no checklist, e a checagem periódica. O lint do compose do pipeline-config.yml avisa serviço sem dns:.

Corrigido

  • Túnel do Flutter web resolve o destino por pedido: a receita do túnel same-origin punha o host no proxy_pass, e o nginx resolve esse nome ao carregar a config. Em 2026-09-27 a VM de produção religou com a WAN fora: os dois BI saíram com host not found in upstream em crash-loop e ficaram parados até o redeploy manual, enquanto o resto da frota voltou sozinho. A receita passa a usar variável com resolver (DNS do Docker), e o pipeline.yml ganha a guarda proxy_pass resolvido no boot, que reprova host literal no proxy_pass (IP, localhost e upstream nomeado passam).
  • Scripts do kit fora da conferência de presença, e a cópia local vira sobra: a /xadm-docs só explicava a presença das entradas de arquivos, e a leitura literal acusava como ausentes os scripts, que não têm destino porque o CI e as skills os baixam frescos. Ao mesmo tempo, repos guardavam cópias antigas em scripts/: o agente rodou uma delas, que ainda imprimia a constituição 1.4.8, e recebeu "0 erros" antes de baixar o validador fresco. A skill passa a dizer que scripts não entram na conferência e que qualquer cópia deles no repo sai com git rm. Essa regra genérica substitui as duas entradas do remover que tratavam os scripts do sync-rules um a um. A referência de docs da /x-planejar, que mandava rodar scripts/valida-frontmatter.py local, passa a baixar os scripts frescos.

1.0.4 - 2026-09-25 (constituição 2.0.1 · kit 2.0.4)

Adicionado

  • Guardas de doc antes do commit: a /x-documentar roda checa-nav e checa-modelagem no fechamento, porque o ADR fora do nav e a migration sem modelagem.md só apareciam no pré-flight da release. O checa-modelagem compara a base com a árvore de trabalho (antes via só o commitado e, com o base HEAD~1, só o último commit), e o pipeline.yml usa o before do push. Na /xadm-release, a correção de pré-flight ganha destino: amend no commit do dev antes do CHANGELOG, ou commit separado quando o HEAD já está no origin.
  • Manual de integração: roteiro prático de como o dado sai do X-Adm para a nuvem e de como o dado de fora entra no X-Adm, com o que configurar e em que ordem; a página Plataforma de Integração fica alinhada ao PIED.
  • Área Plataforma com porta de entrada: índice com entrada por tarefa e diagrama do que se replica por cliente contra o que é instância única, e o inventário da nuvem (apps, instâncias de sincronização e serviços de plataforma). A Infraestrutura passa de Transversais para Plataforma.

Alterado

  • Skills do kit ajustadas ao modelo atual: a /x-refinar prioriza achados de alto impacto em vez de um teto numérico, a /x-documentar pede as três facetas e deixa a delegação a subagentes opcional, e a /xadm-setup troca as narrativas de incidente pela regra no presente.
  • Gate Java no Windows: a página da stack dá o gradlew.bat pelo PowerShell ou o exclude_commands do rtk, já que o rtk gradlew trava sem saída; a edição em lote passa a cobrir encoding, porque o -Encoding utf8 do PowerShell 5.1 grava BOM e quebra o javac.
  • Repo novo nasce com a pasta no slug: o app-novo orienta criar a pasta com o slug da decisão 0038; os repos existentes não são renomeados.

Removido

  • API Reference sai do nav: a condição de sincronização era inalcançável e nenhum artefato chegou ao bucket; a API gerada continua servida dentro do site de cada app.

Corrigido

  • Carimbo de commit do portal: o /commit.txt respondia dev porque a variável chegava vazia ao build; o Dockerfile trata vazio como ausente, e a página do Coolify registra a conferência pós-deploy (ligar o marcador Available at Buildtime segue sendo ação no painel).

1.0.3 - 2026-09-22 (constituição 2.0.1 · kit 2.0.3)

Corrigido

  • O aviso de link do Forgejo deixa de confundir imagem de registry com link de repo: a guarda publicada no kit 2.0.2 casava qualquer menção a fonte.xadm.biz/xadm/, e o mesmo host serve o registry de imagens — medido na frota, 23 dos 32 avisos eram o nome da imagem (…:native-amd64) dentro de code span, que a norma não restringe. Guarda com falso positivo ensina a ignorar aviso. Agora a regra casa só a forma de navegação (link markdown, /src, /issues, /compare, .git) e ignora code span e bloco de código; sobraram 18 links reais, tratados nos repos.

1.0.2 - 2026-09-22 (constituição 2.0.1 · kit 2.0.2)

Adicionado

  • Entrada por tarefa na porta do site: o index ganha a seção "Preciso fazer uma coisa", que mapeia as onze tarefas frequentes para a página que já as cobre — antes a porta organizava só por assunto, servindo quem quer estudar o padrão e não quem chegou para fazer. O glossário entra no nav (nenhuma página o linkava).
  • Diagrama nas páginas de fluxo: versionamento (as três versões e a detecção de defasagem), ci-testes (o que gateia o deploy) e libs (o caminho da lib até o app) trocam prosa por fluxograma Mermaid TB, lintado no CI.
  • Norma 2.0.1 — identidade no piso: a constituição passa a afirmar o invariante da decisão docs/decisoes/0038-uma-identidade-por-app.md (app_id, slug e projeto de erro são o mesmo valor), que o validador já reprovava sem a norma dizer. A referência de classes (dev/api/) vira opt-in explícito nos dois lados — bloco comentado no pipeline.yml e aviso na constituição — em vez de promessa que o kit não cumpria (resultado medido: link 404 no site publicado). O valida-frontmatter passa a avisar sobre link de repo fora de lugar.
  • Guarda de legibilidade no checa-redacao.py (avisa, não falha): frase acima de 35 palavras e página com mais de 10% das palavras em negrito. Mede por parágrafo, não por linha — a quebra do Markdown esconderia toda frase longa. O glossário ganha a seção Vocabulário da casa (piso, guarda, gate, smoke, carimbo, ponto de injeção, desvio, rede de segurança).

Alterado

  • Anglicismo com tradução direta sai da norma viva e dos templates: seam vira ponto de injeção, drift vira desvio, backstop vira rede de segurança, wrapper vira invólucro. As decisões ficam como estão — ADR é retrato datado, não se reescreve. As três páginas mais pesadas (powersync, publicar-docs e integração) tiveram as frases longas quebradas sem mudar o conteúdo técnico.

Corrigido

  • docs/documentacao/templates.md restaurado: a edição em lote do passe de redação apagou 66 linhas (as seções Diagramas (Mermaid), Figura autoral em SVG e Riscos consolidados) e injetou dois bytes NUL no lugar de uma cerca de código. Com as cercas rompidas, exemplos dentro de bloco viraram links reais e o mkdocs build --strict passou a abortar com cinco links e âncoras quebrados — o site não subiria. É a armadilha que a regra de edição em lote já descreve: script que reescreve arquivo versionado confere o --numstat antes de fechar.
  • Cópias de skill re-derivadas dos templates: references/flutter.md das x-definir, x-desenhar, x-planejar e x-refinar estavam defasadas em .claude/skills/, com o checa-dogfood.py vermelho.
  • O exemplo de app.json na norma deixa de ensinar a divergência que o próprio gate reprova.
  • Redirect relativo, para a navegação não saltar por http; links quebrados do CHANGELOG publicado e redirect dos slugs antigos.

1.0.1 - 2026-09-22 (constituição 2.0.0 · kit 2.0.1)

Adicionado

  • Decisão 0038 — uma identidade por app: o app_id do docs/app.json é igual ao slug, e esse nome é o mesmo no repo, na imagem do registry, no recurso Coolify, no bucket da doc, no catálogo do db_auth e no projeto do GlitchTip. Substitui a cláusula "slug pode ≠ app_id" da 0024 (o resto dela segue valendo) e registra a migração em duas fases para os apps que compilam o próprio app_id no bundle.
  • Guarda de identidade no valida-frontmatter.py: o validador barra app_id ausente em app com build.targets, app_id ≠ slug e features.glitchtip.project ≠ slug. Era a checagem que pegaria sozinha os dois defeitos que motivaram a 0038 — projeto de erro inexistente e rename pela metade —, ambos falhas silenciosas.

Alterado

  • app.json: nome deriva do slug (slug sem o prefixo <cliente_id>-, em caixa alta, hífen de família virando -) e stack é rótulo curto <linguagem ou runtime>/<framework>; cenário e variante vão na descricao, não no nome nem na stack.
  • A tabela apps do db_auth é espelho do app.json, e só a /xadm-setup a escreve: o POST /api/setup/provision leva o bloco de identidade inteiro (app_id, nome, descricao, grupo, cliente_id, slug, production_url, stack, toolchain), não só os campos que o broker exige. Editar o app.json não muda a Central — o valor novo chega na próxima /xadm-setup daquele repo. Documentado em docs/documentacao/publicar-docs.md e no templates/xadm-setup-skill.md.

1.0.0 - 2026-09-16 (constituição 2.0.0 · kit 2.0.0)

Constituição 2.0.0 · kit 2.0.0 — release MAJOR. A norma foi reestruturada: núcleo curto com âncoras fixas, versão do kit separada da versão da norma, perfis de repo, pipeline.yml corrigido e ADRs governadas. Leia a seção Migração antes do primeiro push depois do deploy do central.

Mudado

  • Núcleo da constituição reescrito (1.134 → 432 linhas): só princípios e pisos, até três frases por regra, com link para a norma derivada dona. Headings sem número e com âncora fixa (#convencoes, #definicao-de-pronto…); seção se cita pelo nome, nunca por número. Um checa-redacao.py cobra a redação (sem caso, data, item de backlog, versão de release nem seção por número) no núcleo, nas normas derivadas, nas páginas satélite (documentação, transversais, glossário e início) e nos templates do kit.
  • Três números, três coisas: a norma (versao: da constituição, constituicao-versao.txt), o kit (arquivo KIT, kit-versao.txt) e o site (VERSION). Mudança de template, script ou skill bumpa o kit, não a norma; o app.json registra os dois (constituicao, kit). O manifesto ganha kit, perfis, stacks e destino por artefato e a lista remover; o validador publicado imprime constituição C · kit K na primeira linha.
  • Perfis de repo (perfil no app.json: app, config, lib): cada perfil recebe do manifesto só o que é dele, e as stacks PowerSync e o xadm-commons deixam de viver de exceção.
  • Server Micronaut: native por padrão, jar declarado com motivo (0033) — o app é native se e só se o build.targets contém native. O pipeline.yml traz o native ativo e o deploy jar comentado.
  • Libs da casa na versão corrente (0034): atrás é pendência e a /xadm-release bumpa aplicando as ### Migração em ordem; abaixo do piso ela recusa. Os pisos vivem no pisos-libs.json (publicado em /toolchain/pisos-libs.json) e aparecem na página nova Bibliotecas da casa.
  • Deploy só com gate verde e release válida: o deploy exige o gate e, em tag, o release-check com sucesso, sem caminho que pule o gate; um step de deploy por alvo; o job docs builda e valida sempre, inclusive em PR; o CENTRAL_DOCS_URL é derivado do CENTRAL_DEPLOY_URL; a versão da toolchain vem do app.json.
  • Normas derivadas enxutas: java-micronaut 2.225 → 466 linhas; engenharia, infraestrutura e ia.md 4.645 → 3.148. Nasce a área Plataforma (central-de-apps, integracao, com redirect das URLs antigas).
  • ADRs governadas: decisão aprovada só recebe > **Correção …:** visível; os comentários HTML e os blocos "Emenda/Evolução" viraram Correção ou ponteiro para a norma; 0003 e 0015 passam a obsoleto; entram a 0033, a 0034, a 0035 (eleição de instância no relay M2M) e a 0036, que registra o porquê desta reestruturação.
  • Linha de feedback: §6: nada a reportar vira Feedback: nada a reportar / Feedback: candidato — <o quê> nas regras de IA, nas skills e nos templates.
  • O CLAUDE.md do central virou roteador (4.923 → 156 linhas); as pendências foram para o backlog.md.

Adicionado

  • templates/views-jte/kit/pager.jte (kit de UI, opcional): o paginador das listagens, para Page e Slice, que preserva os filtros e deixa o ?sort= da URL de fora.
  • templates/pipeline-config.yml (perfil config): lint das sync rules, lint do compose, smoke de carga da imagem pinada e deploy pelo control-plane com target=compose; com os scripts lint-sync-rules.mjs, smoke-sync-rules.sh e compose-sem-coolify.py.
  • Guardas novas no pipeline.yml: toolchain × .fvmrc/FROM, settings.local.json fora do git, locale pt-BR no native, placeholder no Dockerfile, libs no piso (aviso) e controller fora do event loop (que honra o marcador // event-loop-ok:). O harness passa a cobrir as 21 guardas (142 casos, 52 mutantes).
  • scripts/envs-coolify.py: a /xadm-release preenche "Coolify — antes do deploy" e "depois do deploy" pela diferença de placeholders entre a última tag e o HEAD, e pede confirmação do "antes".
  • Hook de defasagem que separa norma e kit, acusa cache inconsistente e manda baixar a skill fresca quando o repo atrasou uma major.
  • A /x-desenhar e a /x-definir mandam ler a norma que a feature toca: as seções da constituição que o pedido alcança, a norma derivada dona e, quando a norma do repo atrasou, a seção Migração do CHANGELOG. O manifesto diz o que re-derivar; só a leitura da norma diz o que a feature tem de respeitar.
  • Comentário de linha ou de bloco: uma linha, só para o porquê que o código não mostra (constituição, Engenharia; Stack da casa). Javadoc e dartdoc de API seguem o padrão da linguagem e documentam o contrato inteiro (@param, @return, @throws), no presente e sem história. A história do bug ou da feature vai para o commit, o CHANGELOG ou a decisão; armadilha de linguagem ou framework vira linha na seção Armadilhas da página da stack. Os templates do kit foram enxugados pela regra, e os títulos das seções de armadilha das páginas passaram a usar esse nome (âncoras mantidas).
  • validation.links.anchors: warn no site do central: âncora quebrada reprova o build.
  • Postgres de teste com um padrão só (Java/Micronaut §Testes): o singleton da xadm-comum-teste, com tmpfs. O jdbc:tc: sem TC_DAEMON=true recria o Postgres a cada classe, e o Micronaut Test Resources deixa servidor órfão; os dois migram quando o repo tocar os testes. Trava vira falha: timeout da task test (15 min), timeout JUnit por teste (2 min) e Hikari de teste com connection-timeout de 5 s. A limpeza entre testes vem da xadm-comum-teste (IntegracaoComPostgres ou PostgresTestResource.limparTabelas()), poupando o histórico do Flyway, e a fábrica RegrasArquitetura traz as fronteiras por tipo do package-by-feature. Treze armadilhas da xadm-commons e do central-backend entraram na tabela do Java/Micronaut, entre elas o Duration qualificado no Kotlin DSL, o get() herdado do TestPropertyProvider, os serdes gerados que reprovam a regra de controller e o sub-bloco hikari: que o datasource ignora, agora reprovado no gate. A norma de segurança registra a exceção nominal dos dois tokens do central-backend, um por escopo de rota.
  • Versão sobe à mão antes da release, e o mavenLocal entra no build filtrado (versionamento, libs): a lib sobe o número, publica no mavenLocal e o app testa contra ela antes do deploy. Versão acima da última tag e sem tag própria é a pendente, e a /xadm-release a mantém. O mavenLocal() fica depois do registro e só para br.com.xadm, então a versão publicada sempre vem do registro, e a release recusa dependência da casa que não está nele. Com o registro fora do ar, o Gradle não cai no local: ./gradlew --offline. A versão mínima de uma API externa que o código exige é restrição e cabe em comentário (Stack da casa).
  • Segurança e operação Java/Micronaut (feedback do bi-comercial-xls e do int-pied): o @Secured decide antes do intercept-url-map e das regras próprias, então anônimo vai no método, nunca na classe (Segurança); o build native declara -march=compatibility; dedup de notificação externa mora no banco, reivindicado por UPDATE condicional, nunca só na RAM; tabela semeada por migration fica fora do TRUNCATE da lib; e histórico que a UI exibe é dado, em /app/data, não em /app/logs (Coolify).
  • checa-rotas.py aceita família de sub-rotas (CI): MÉTODO /prefixo/… (ou /...) passa se o OpenAPI tem ao menos uma rota do método abaixo do prefixo. Antes, a reticência caía e a rota virava o prefixo, reprovada pelo método.
  • valida-release.py reprova link ](docs/…) na seção da versão (Versionamento): o CHANGELOG embutido em docs/ o transforma em docs/docs/…, e só o job docs acusava, com o app já deployado. Agora pega nas guardas finais da /xadm-release e no release-check, de que o deploy depende.
  • /xadm-release no perfil config (feedback do onpetro-powersync): o deploy, o gate de docs e a via "só push" deixam de descrever o fluxo do app para quem usa o pipeline-config.yml. Lá não há build no CI nem job docs, o Coolify builda o recurso compose do git e push em master que muda a config deploya.
  • Paginação e ArchUnit (feedback do onpetro-bi-xls): listagem paginada devolve o Page/Slice do micronaut-data como está, com o default-page-size declarado (sem ele, o default é 100); @Query com Pageable declara no FROM o alias que o ORDER BY usa; e as regras de fronteira escrevem a fatia com o nome completo, com a origem vazia passando só na anti-vácuo.
  • Feedback passa por revisão adversarial no central (Feedback): entra o que corrige, generaliza ou substitui uma regra, reescrita no lugar; cada aceite mostra o que saiu ou foi fundido, e o que só acrescenta leva justificativa no backlog. O caso de um repo só é remendo e é recusado, e cada ponto volta ao repo como Resposta do central: aceito | já coberto | recusado | pendência.
  • Guarda de toolchain resolve ARG, e a /xadm-docs remove sobra de outra stack (feedback do bi-comercial): FROM img:${FLUTTER_VERSION} passa a ser comparado pelo default do ARG, e artefato de outra stack que o kit antigo copiou sai junto do remover. A regra de comentário ganha o limite de coluna da stack e a posição acima do código, e o --dart-define vazio chega como "", sem o defaultValue.
  • Build native com configure<GraalVMExtension> (feedback do onpetro-bi-xls): o exemplo deixa o accessor graalvmNative {}, que falta nos apps da frota sem o plugin de Test Resources, e a regra de paginação diz como testar a forma do Page sem contexto.
  • Zero negativo no web e comentário dentro de expressão (feedback do bi-comercial, medido): nasce a seção Armadilhas do Flutter, com o -v que no dart2js dá -0,00 e a cura 0 - v; e a regra de comentário corrige o que dizia do Dart — o comentário em linha própria dentro de uma expressão também re-flui o código, e o layout torto em volta dele se cura encurtando-o.
  • Rodada e2e sobre a 2.0.0 (feedback do etc/tests): o e2e-local chama o manifesto de build de BUILD-INFO.md, e só a rodada verde e limpa o promove; o harness recusa lib da casa pendente que não seja a do HEAD e republica o ~/.m2 atrasado; a ferramenta da toolchain sai do checkout local do central; gate que não roda sai como PULADO, nunca OK. A suíte native declara name: no compose, builda o Dockerfile.native com XADM_COMMIT quando não há lib pendente e confere a imagem pelo label de identidade — o commit injetado por env do container não prova nada. O e2e do cliente PowerSync afirma o numeric como texto no fio, e cada bump do pin o prova de novo.
  • Smoke com uma rota de cada classe de credencial (Smoke de produção): o /health sozinho não pega a tela em 500 nem a rota que o AOT podou. A guarda Classe servida sem rota no smoke do gate reprova session (app com xadm-seguranca e view) ou m2m (app.api-token ou ROLE_API) sem rota declarada; a classe que só tem rota com efeito colateral declara o motivo em smoke.sem_rota, que o valida-frontmatter.py confere.
  • Sessão dos apps com prazo de revogação (0037, feedback do bi-comercial): o TTL do access token é o prazo em que a revogação e a troca de papel chegam ao app, e fica em no máximo 60 min; o app renova pelo refresh do central, sem a senha, e a sessão tem teto absoluto de no máximo 7 dias (Segurança). O TTL de produção só cai depois de o refresh existir no central e nos apps que renovam sessão.
  • JWT no Flutter web: o piso diz o que protege (feedback do bi-comercial): no web o flutter_secure_storage ofusca e não defende de XSS, porque a chave AES fica no mesmo localStorage; o token roubado renova pelo refresh, e quem limita o estrago é a revogação e o teto da sessão (Segurança). A race da primeira gravação entrou nas Armadilhas do Flutter.
  • Badge de cobertura no repo do app (feedback do etc/tests): o % publicado sai da rodada e2e local, em dois badges — cobertura unit+int e cobertura +e2e — gravados em docs/assets/badges/ e exibidos no README.md e no site (CI e testes). A constituição abre a exceção nomeada para esse gerado versionado. O scripts/checa-badges.py, novo no kit, é a fonte única da regra "diff só do bloco", para a /xadm-release e o harness do etc/tests; a skill leva no commit do release o que ele lista e avisa quando o sha medido tem código diferente do HEAD.
  • Integrador-server para native (feedback): no app multi-instância, o jar da troca vai para int-jar.<cli>.xadm.biz e fica no build.targets até a última instância trocar (Coolify). O dedup de notificação externa ganha a reivindicação por INSERT … ON CONFLICT e passa a nomear a sobreposição do rolling deploy; o skip de integração sem Docker se descreve pela forma, não por uma anotação que nenhuma lib entrega; e o guia-do-codigo.md linka o como-rodar.md que a norma exige. Duas armadilhas novas no Java: o ?sort= da URL numa listagem de tela e o WITH … RETURNING numa @Query de escrita.
  • Bi-transporte na 2.0.0 (feedback): sendDefaultPii ligado em toda stack, com o Flutter seguindo a Segurança. O build_web deriva do docs/app.json o destino, a org e o projeto do upload de sourcemap, e as vars SENTRY_URL/SENTRY_ORG/SENTRY_PROJECT saem. O job docs instala o mkdocs-redirects, e o mkdocs.yml do kit traz o plugin comentado para o redirect que a constituição exige ao renomear slug. A /xadm-docs não trata o Dockerfile do Flutter como sobra de outra stack, e o manifesto manda apagar a xadm-compound, que a /x-documentar substituiu. O connector do PowerSync renova pelo refresh do central, com a regra de queda; as armadilhas do SDK do PowerSync, do go_router, do get_it/watch_it, do Sentry, do Aptabase e do Flutter web entram nas tabelas.

Removido

  • templates/build-deploy.yml e templates/build-deploy-web.yml: o pipeline.yml único os substitui, e o manifesto manda o repo apagar o .github/workflows/build-deploy.yml.
  • Campos native e deploy do app.json.
  • O fallback GT_* do smoke.py: a base do GlitchTip vem do DSN; o GLITCHTIP_URL que o pipeline.yml 1.x passa segue aceito.

Migração

Quando fica vermelho. O pipeline baixa o validador e os scripts do site a cada execução, então as regras novas valem no primeiro push depois do deploy do central, qualquer que seja a versão que o repo segue. O que fica vermelho é o job docs (o deploy não depende dele). Medido em 2026-09-12 nos 14 app.json da frota — reconferir no dia do release:

Regra nova (erro) Repos que falham
docs/dev/como-rodar.md 10 de 10 com build.targets: int-pied, bi-comercial, bi-comercial-xls, webstorm-ecom, bi-transporte, bi-transporte-xls, central-backend, central-ui, int-sascar, integrador-server
docs/dev/guia-do-codigo.md 5: bi-comercial, webstorm-ecom, central-ui, int-sascar, integrador-server
campo native presente 7: int-pied, bi-comercial-xls, webstorm-ecom, bi-transporte-xls, central-backend, int-sascar, integrador-server
smoke.routes, toolchain, alvo fora de {jar,native,web}, em-revisão com acento, sub-bloco hikari: sob datasources 0

Perfil app (perfil ausente ou app):

  1. Rode a /xadm-docs: ela re-deriva o kit (pipeline, skills, settings.json, Dockerfiles, templates) sem perguntar e grava o kit no app.json; a norma nova ela re-audita com a sua aprovação. Com a skill local anterior à 2.0.0, a primeira rodada re-deriva a própria /xadm-docs (o manifesto a marca em 2.0.0): pare ali e rode de novo, já com a skill nova.
  2. No docs/app.json, tire native (vale o build.targets) e deploy; confira smoke.routes, com /health no mínimo, e toolchain.
  3. Crie docs/dev/guia-do-codigo.md e docs/dev/como-rodar.md; o README passa a linkar os dois.
  4. Apague o .github/workflows/build-deploy.yml, se ainda existir.
  5. No CLAUDE.md, troque o bloco de regras pelo de templates/claude-regras.md (linha de feedback no lugar de §6:) e repontem os links para âncoras da constituição pelo mapa abaixo.
  6. Suba as libs da casa para a versão corrente — a próxima /xadm-release recusa abaixo do piso.
  7. No smoke.routes, declare uma rota GET sem efeito colateral de cada classe de credencial que o app serve, ou o motivo em smoke.sem_rota: a guarda Classe servida sem rota no smoke reprova o gate. Medido em 2026-09-15, reprova 5 de 7: bi-comercial-xls (m2m, session), bi-transporte-xls (m2m, session), integrador-server (m2m, session), int-sascar (session) e webstorm-ecom (m2m).
  8. Quem roda o smoke.py à mão (o ensaio do e2e native) passa GLITCHTIP_API_TOKEN: o fallback GT_URL/GT_TOKEN saiu.
  9. App com usuário que loga no central-backend renova a sessão pelo refresh do central e desloga pelo código de revogação (0037), antes de o TTL de produção cair para 60 min — sem isso, o usuário re-loga a cada TTL.
  10. App Flutter: sendDefaultPii = true no SentryFlutter.init. Apague as vars SENTRY_URL, SENTRY_ORG e SENTRY_PROJECT do repo: o build_web as deriva do docs/app.json, e o Dockerfile declara os ARG sem default.

Perfil config (stacks PowerSync): app.json mínimo (slug, perfil: config, constituicao, kit), o hook, as skills, VERSION + CHANGELOG e o templates/pipeline-config.yml no lugar do .github/workflows/pipeline.yml (um workflow ou o outro, nunca os dois); apague scripts/lint-sync-rules.mjs e scripts/smoke-sync-rules.sh (o pipeline baixa da toolchain) e a .claude/skills/xadm-setup/ (não há feature a provisionar). O lint de compose reprova hoje nas seis stacks — Dockerfile sem digest e com RUN, e o serviço de init sem limite e reserva de memória —, e é o custo de adoção do perfil.

Perfil lib (xadm-commons): app.json mínimo com perfil: lib e toolchain, o hook e as skills; o pipeline, a /xadm-release e o índice de CHANGELOGs são customização local; o site mínimo segue Bibliotecas da casa. Apague a .claude/skills/xadm-setup/. Controller de lib com I/O bloqueante leva @ExecuteOn; o sem bloqueio declara // event-loop-ok:, que a guarda do event loop honra.

Mapa de âncoras da constituição:

1.5.0 2.0.0
#1-principios (§1.1–1.9) #principios (e uma âncora por princípio: #doc-perto-do-codigo, #interfaces-decisoes-uso, #docs-as-code, #registro-e-referencia, #enxuto, #migracao-oportunista, #consulta-e-compreensao, #portugues, #duas-fontes)
#2-os-seis-niveis #niveis · livro, Resumo e nav: #livro
#3-estrutura-padrao-de-cada-repo-de-aplicacao #repo · perfis: #perfis
#4-frontmatter-obrigatorio #frontmatter
#5-convencoes doc: #convencoes · código e plataforma: #engenharia
#6-governanca #governanca · #definicao-de-pronto · #versoes · #redacao · #regras-de-ia · #feedback
#7-central-de-apps #central-de-apps
#8-plataforma-de-integracao #integracao
#9-entrega-deploy #entrega

0.39.4 - 2026-09-11 (constituição 1.5.0)

Adicionado

  • /xadm-docs detecta template obrigatório ausente, não só defasado (feedback do int-sascar). O mudou_em só acusa o que mudou depois da base do app; um obrigatório nunca copiado passava invisível — o mkdocs.yml do int-sascar apontava stylesheets/xadm.css, o arquivo não existia e o site servia 404 no CSS de identidade. O passo 2 confere a presença de todo artefato fora de opcionais, com filtro de stack (checkstyle só em Java, analysis_options.yaml só em Flutter), e a tabela de destinos passa a cobrir xadm.css, checkstyle.xml/suppressions.xml e analysis_options.yaml — e troca as entradas dos workflows Forgejo aposentados pelo pipeline.yml.
  • O texto de feedback §6 se identifica (constituição §6, regras copiadas para os apps, /x-documentar). Abre com o repo e a versão da constituição que ele segue, dá o contexto — o que se fazia, o que foi medido e o que é suposição — e, quando fizer sentido, aponta a fonte com link do Forgejo em vez de parafrasear o código. Sem isso o central reconstrói o caso às cegas ou precisa pedir autorização para ler o repo do app.
  • Prontidão do Postgres em harness: pg_isready -h 127.0.0.1 (e2e-local §13). Sem -h, o pg_isready consulta o socket unix e responde "pronto" já no servidor temporário do initdb, que não escuta TCP e reinicia logo depois; o app que conecta nessa janela falha com EOFException na negociação SSL do pgjdbc, com cara de defeito do app.
  • Inteiro escalado no cliente como forma exata de ler numeric (PowerSync §Tipos). O número segue viajando como texto; o app pode convertê-lo num inteiro na escala da coluna mexendo só na string, sem Decimal, sem BigInt e sem ponto flutuante — o Decimal do Dart pesa na web. A página diz também o que não serve (escalar multiplicando em ponto flutuante, que trunca 0.29 * 100 em 28) e o teto de 2^53 do int na web, que vale para o total.

Corrigido

  • A guarda "Destino de proxy cravado" nunca rodava na stack que ela protege (pipeline.yml). Ela estava só no gate-Java, que o app Flutter web apaga ao adaptar o template. Agora a variante Flutter traz uma cópia idêntica, e o testa-guardas-pipeline.py confere a paridade entre as duas.
  • O anti-vácuo do ArchUnit agora reconhece a fábrica da casa (pipeline.yml). O regex não conhecia RegrasArquitetura.importNaoVazio() (xadm-comum-teste ≥ 0.3.0) — quem adotava a lib caía em ::warning:: eterno. Caso verde + mutante no testa-guardas-pipeline.py (53 casos, 20 mutantes).
  • O step de deploy do pipeline.yml engolia a falha do curl -f e saía vermelho depois de deployar. echo "…$(post …)" saía 0 com o POST falhando, e o step ficava verde sem deploy. Na forma [ … ] && …, com dois alvos descomentados, a última linha com o alvo desligado saía 1 e virava o exit do step: job vermelho depois de ter deployado. Agora é if … then resp=$(post …); echo …; fi: o curl -f que falha aborta o step, e o alvo desligado sai 0. Conferido no bash -e nos dois sentidos.
  • Massa de dados barrada em todo docs/, não só em public/ (valida-frontmatter.py). O mkdocs copia o arquivo para o site e o publica mesmo fora do nav — um .xlsx real em docs/projeto/anexos/ passou verde e foi publicado. Exceções: sob privado/ e o .docx de pré-projeto (nível 1 admite Word, constituição §4.1). O caso de teste que dizia "anexos/ é livre" foi invertido, com fixtures das duas exceções. Migração: o pipeline dos apps baixa o validador do site a cada execução, então a trava vale no primeiro push depois do deploy do central, qualquer que seja a versão da constituição que o app segue. Antes do próximo push, mova planilha, dump e banco de docs/ para privado/ (ou tire do repo) — inclusive modelo de planilha com dado fictício, que antes podia morar em anexos/.
  • 0022 e coolify.md reconciliados com 0026/0027 (emenda in-place datada). A 0022 ainda prescrevia build-deploy.yml com ponte SSH no presente e dizia "job build" (é build_native). O coolify.md mandava "a ponte SSH dispara", dava build-deploy*.yml como workflow vigente em nove pontos e trazia a tag imutável como <git-sha> — ela é <git-sha>-native, o alvo do rollback do smoke. O gatilho vigente é o POST /api/ci/deploy feito pelos jobs do pipeline.yml; os build-deploy*.yml ficam rastreados como legado.
  • O túnel do Sentry na receita do Flutter web respondia 403 (Flutter §Deploy web). O bug.xadm.biz é GlitchTip, que exige a key no pedido (X-Sentry-Auth ou query) — o DSN no envelope não basta (medido no bi-comercial: 403 sem a key, 200 com). A receita deriva também a key do DSN, põe o cabeçalho e troca os nomes BUGSINK_* por GLITCHTIP_*: foi o nome errado do servidor que sustentou a suposição; a nota de sourcemap do Flutter web também deixou de chamar o servidor de Bugsink.

0.39.3 - 2026-09-10 (constituição 1.4.10)

Adicionado

  • e2e-local ganha a pasta por rodada, o cenário isolado e o marcador de fim (feedback do etc/tests, 10/09). Cada execução escreve em out/rodada-<AAAAMMDD-HHMMSS>/ (§2); cada cenário roda com try/catch próprio, e o crash de um vira resultado daquele cenário em vez de derrubar a run inteira — o relatório conta OK, FALHA e crash, para "não rodou" nunca passar por "passou" (§7/§8); e o último ato do finally é o FIM.txt da rodada. O caso que motivou: a pré-condição de um cenário novo caiu com throw, o trap encerrou tudo, os cenários seguintes não rodaram e o dump de cobertura se perdeu.
  • "Terminou" é o marcador, não o pipe (agentes, seção nova). Quem acompanha tarefa longa decide o fim pelo EOF do stdout, que só chega quando morre o último processo que herdou o handle — medido: a notificação chegou cerca de 40 minutos depois do fim real, quando um daemon Gradle órfão morreu. Tarefa longa vai com saída para arquivo, o fim é o marcador daquela execução, e "a notificação não chegou" não é evidência de que a tarefa está viva.
  • Assert negativo exige prova de que o ciclo rodou (e2e-local §8). "Nada reabre" passa verde de qualquer jeito se o job agendado morreu; e Start-Sleep 35 com um comentário apontando o intervalo de 30 s vira decoração no dia em que alguém sobe o intervalo. A suíte espera evidência positiva do ciclo (tick no log, contador, timestamp de última execução) e só então afirma o nada; o teto da espera é derivado do knob que ela mesma configurou.
  • Barreira de daemon Gradle órfão em harness (Java/Micronaut §Armadilhas): depois de cada gradlew com Testcontainers, o harness encerra só o daemon com identificação positiva — os três critérios juntos: nascido na janela daquela chamada, pai morto (ou PID do pai reciclado) e ao menos um filho cuja linha de comando aponta para o repo da chamada. "Pai morto" sozinho não basta: o daemon é reutilizado por outros clientes via socket, e a versão sem o terceiro critério encerrou, ao vivo, o daemon de outra sessão. Filhos primeiro, PID reconferido na hora — nunca por nome de processo, nunca por imagem de container.
  • Destino de proxy do nginx vem do app.json, derivado no build (constituição §5, Flutter §Deploy web, guarda nova no pipeline.yml; feedback de app). Os dois BIs tunelam Aptabase e Bugsink pelo próprio nginx e cravavam o host à mão — duas vezes por location —, enquanto o features.analytics.aptabase_host do app.json não era lido por nada. Quando analytics.xadm.biz saiu do ar, o app.json já estava certo e o túnel seguiu apontando para o host morto: 503 em produção. A fonte única passa a valer também entre arquivos do repo; a receita renderiza o template com envsubst de lista explícita no estágio final do Dockerfile, falha o build com destino vazio e fecha com nginx -t. O túnel same-origin entra como receita opcional, e a guarda reprova host do app.json escrito literal em .conf/.conf.template (ARG do Dockerfile fica como ponto cego declarado).

Corrigido

  • Postgres 18+ monta o volume em /var/lib/postgresql (Java/Micronaut §Testes, e2e-local §13, Coolify §Banco). A imagem oficial mudou PGDATA para /var/lib/postgresql/18/docker e o VOLUME para /var/lib/postgresql; volume nomeado no caminho antigo fica vazio sem erro, e o dado mora num volume anônimo que um down + up não reaproveita. Atinge as suítes do etc/tests e o compose de dev do central-backend. Produção roda 18.4 e o ponto de montagem do recurso ainda precisa ser conferido no host.
  • O reaper do item 118 mandava ./gradlew --stop sem ressalva — ele derruba todos os daemons daquela versão do Gradle, inclusive os do IDE. Segue como gesto manual de último recurso, e passa a dizer que nunca é passo de harness.
  • Run sobre árvore suja não promove mais o manifesto de build (e2e-local §4): a âncora da última run verde apontava para uma árvore que já não existia. A run suja grava o patch completo na pasta da rodada — patch reproduz, hash só confere.
  • A hint native do SentryAppender prescrevia só a classe (Java/Micronaut §Observabilidade e §Native, decisões 0022 e 0019; feedback do central-backend). O logback acha os setters por getMethods(), que no native só devolve método registrado — com a classe sozinha, <options> e <minimumEventLevel> são ignorados em silêncio. O DSN sobrevive porque o SDK o lê da env, e o nível perdido coincide com o default; o que some é o environment dos eventos. A receita passa a trazer as três entradas com métodos (appender, SentryOptions, Level.valueOf), e a hint que a xadm-comum-web vai embarcar tem de levar as três — senão distribui o mesmo defeito para a frota.
  • /xadm-docs passo 5 re-lê o que o CLAUDE.md do app declara e confere as guardas antigas do pipeline.yml (feedback de app). Trocar só o número da versão no CLAUDE.md deixava escrita uma divergência que a migração acabara de desfazer ("preservar o vendor local"), contradizendo o código. E, ao re-derivar o pipeline.yml, só se conferia se as guardas novas tinham entrado — guarda existente com corpo velho seguia com o defeito já corrigido na casa.
  • A defesa anti-vácuo do ArchUnit dava aviso falso em assertFalse(classes.isEmpty()) (guarda do pipeline.yml). Passa a reconhecer também assertTrue(classes.size() > 0); assertTrue(x.isEmpty()), que afirma o contrário, segue avisando.
  • A guarda do reflect-config da JTE quebrava no Windows (WinError 193) quando o dev a rodava local: chamava ./gradlew, que é script sh. Agora usa .\gradlew.bat via cmd no Windows — com caminho explícito, porque com NoDefaultCurrentDirectoryInExePath=1 (o ambiente do agente) o cmd não busca na pasta atual e gradlew.bat puro não é reconhecido — e normaliza a barra do caminho que o glob devolve, que também quebraria o corte do nome da classe.
  • Estático para usuário anônimo é decidido pelo intercept-url-map, não pela whitelist Java (Java/Micronaut §UI). A regra do YAML roda antes (ordem -100) e, quando algum padrão casa o caminho, devolve ALLOWED ou REJECTED sem consultar a regra de view — com o fail-closed da casa, liberar só na whitelist Java não tem efeito. Verificado no fonte do micronaut-security 5.0.0.

0.39.2 - 2026-09-09 (constituição 1.4.8)

Alterado

  • Tabela de pisos do 0019 atualizada com as publicações de hoje (constituição 1.4.8). xadm-seguranca sobe 0.5.1 → 0.7.2: a 0.5.1 fazia o placeholder não resolvido recusar o boot, e a 0.7.2 fechou o par que faltava — app.api-token declarado e vazio também recusa (BearerTokenGuard); abaixo dela, feature-ligada/env-ausente passa em silêncio e o app sobe healthy com /api/** em 401 a tudo. Entra xadm-comum-teste piso 0.3.0: a lib exporta o ArchUnit por api(...), então a versão dela vaza para o test-classpath de quem não pina — abaixo da 0.3.0 sai archunit-junit5 1.3.0, cujo ASM não conhece o class major 69 e pula as classes no import, deixando a suíte de arquitetura vácua em todo adotante de uma vez.
  • Mecânica do fail-fast de auth registrada, agora que foi entregue (segurança §Segredos): a lib sabe que a feature está ligada pela presença da property — declarar o token é declarar a feature —, não por flag paralela, que seria uma segunda fonte de verdade capaz de discordar da primeira; e a guarda mora num bean @Context, não @Singleton lazy, porque o objetivo é falhar no boot.
  • Entregas registradas no 0019 (conferidas no maven-metadata.xml, não na nota de release): o seam da eleição saiu na xadm-mensageria 0.4.0 — RelayM2m.executarSeEleito(), com o contrato de três entradas (executar() passe direto · executarSeEleito() sob eleição · tick() não é API) —, e a guarda de boot na xadm-seguranca 0.7.2. Os dois saem da faixa "prescrito/implementado e não publicado".

Corrigido

  • Ponto cego declarado na guarda do piso do ArchUnit (templates/pipeline.yml): versão herdada de lib não aparece no grep — app que não pina não declara a coordenada, a guarda não acha nada e sai fail-open com a suíte rodando vácua. Quem cobre esse caso é o piso do xadm-comum-teste, cobrado pelo /xadm-docs; resolver dependência transitiva exigiria rodar o Gradle, o que o gate estático não faz.

Adicionado

  • Feature de auth LIGADA com o segredo vazio = boot recusado (constituição 1.4.7; segurança §Segredos, invariante (3) refinado + checklist do revisor). O invariante dizia só "config que não quebra o boot de quem não usa a feature" — e tratava como uma coisa só duas situações diferentes: feature desligada (dormente, correto: não derruba quem não usa) e feature declarada ligada com o segredo vazio, que é misconfig. O efeito da segunda é ruim justamente por ser fail-closed: ninguém autentica, o endpoint responde 401 a tudo, o app sobe healthy, e o sintoma chega horas depois como "a integração parou", sem nada no boot. É a mesma classe do placeholder não resolvido, que já recusa o boot desde a xadm-seguranca 0.5.1 — tratar os dois de formas opostas é incoerente. Fica explícito que o risco aqui é indisponibilidade silenciosa, não auth-bypass: o vazio já nega por construção (invariante 2), com teste na lib.

Removido

  • Dez andaimes .ia/ consumidos — 015 (control-plane → decisão 0026), 020 (sync event-driven → 0028), 021 (higiene de nomes → 0024) e 022 (release scenario-aware → job decide do pipeline.yml), auditados contra o repo (decisão publicada e/ou checkboxes do plano), não pelo título. Ficam 006 e 007 (marcados FUTURA, e a 007 é citada na constituição como gating que ainda não existe) e 014 (feature flags, citada 5× como protocolo vigente). As duas referências que ficariam apontando para o vazio foram reconciliadas: a 0006 passa a apontar a decisão 0026, e o comentário do pipeline.yml deixa de citar a spec removida.
  • O /xadm-docs passa a COBRAR o hook de defasagem, não só cravá-lo (constituição 1.4.6; item novo no passo 3b). O hook checa-constituicao.sh só avisa se duas peças existirem — o .sh em .claude/ e a entrada SessionStart no .claude/settings.json que o dispara — e a instrução de cravar existe desde a 0.31.3. O que faltava era cobrança: repo que copiou o settings.json verbatim (que não traz hooks por desenho), ou que instalou antes de a fusão existir, fica com o .sh presente e nunca acionado, e o silêncio é circular — quem não tem o hook é exatamente quem não fica sabendo que precisa re-sincronizar. Agora a skill confere as duas peças e reporta a ausência, o que alcança o app que já está nesse estado, não só o próximo a instalar (mesma lição do item 132: a instrução existia, faltava quem cobrasse).

Corrigido

  • Seam público da eleição do relay registrado como "implementado, não publicado" (0019). O RelayM2m.executarSeEleito() está no código do xadm-commons e sai na 0.4.0, que não existe no registro (release corrente 0.3.4, conferido em 2026-09-09) — pela régua a tag, não o commit, o adotante ainda não pode declarar a dependência. Sai da faixa quando o maven-metadata.xml servir a 0.4.0.
  • Verde declarado por agente vem do ARTEFATO, não do exit code (ci-testes, seção nova). No CI o sinal continua sendo o veredito — o step roda ./gradlew check puro sob bash -e e o runner reprova sozinho. Fora dali muda: quem declara verde lendo a saída de um comando (o agente no pré-flight, o dev antes de taggar) depende de um exit code que atravessou wrapper, shim, shell e PowerShell, e cada camada pode reescrevê-lo. Norma: para dizer "os testes passaram", agregue tests/failures/errors dos build/test-results/**/*.xml e confira o mtime de todos — XML antigo significa task pulada (up-to-date do Gradle), isto é, você está lendo outra rodada e chamando de verde; zero XML não é verde, é "não rodou". Origem: relato de ./gradlew saindo 0 com BUILD FAILED no stdout — não reproduzido aqui (o rtk err propaga o código; o pipeline.yml não mascara), o que aponta para propriedade do ambiente de quem roda e reforça a regra: sinal que depende de onde você está não serve de carimbo. É a disciplina que a casa já pratica com outros nomes — validador fresco do site, identidade pelo /health e não pelo retorno do POST de deploy, cobertura do .exec daquela invocação.
  • Recurso global que a lib ocupa tem de ser NOMEÁVEL pelo adotante (java-micronaut §Específico — face documental da norma do bean bom-cidadão, item 124). O @Requires resolve colisão em runtime, mas há recurso global que não colide e ainda assim precisa ser visível: advisory lock, fila, tabela, prefixo de rota, chave de cache. Quando a lib deriva o nome de uma property (hashtext('<micronaut.application.name>_relay_m2m')), ele não existe em lugar nenhum que o adotante consiga procurar — não sai no grep do app, não está no application.yaml, e só se descobre lendo o fonte da lib. Norma: o release nomeia a chave e a regra de derivação no §Migração do CHANGELOG do módulo, e diz como ela se relaciona com o que o app já tem. Caso concreto: quem tinha lock próprio do relay (<app>_relay) e adota a eleição da xadm-mensageria fica com duas chaves distintas — de propósito, porque lock compartilhado serializaria relay e jobs do app sem motivo —, logo migrar é apagar o reagendamento local, não renomear o lock.

Corrigido

  • O flavor do /health estava registrado como "sai na 0.7.0"; saiu na 0.7.1 (0019 e java-micronaut §Específico). A 0.7.0 existe no CHANGELOG da lib e não no registro (0.6.1 → 0.7.1 → 0.8.0 → 0.9.0) — caso-escola da régua a tag, não o commit, e o motivo de a versão corrente se perguntar ao maven-metadata.xml, nunca à nota de release.
  • Faixa "prescrito e não entregue" do 0019 reconferida contra os CHANGELOG dos módulos: o 404 autenticado em ERROR saiu na xadm-comum-web 0.7.1 (sai da faixa); o reflect-config do SentryAppender e o filtro de request-id/MDC seguem ausentes — agora com a verificação carimbada em 0.9.0, não mais em 0.6.1. Entrou na faixa o seam público para o adotante exercitar a eleição do relay (o tick() da xadm-mensageria é package-private; o executar() público não tem lock), pedido em 2026-09-09.

0.39.1 - 2026-09-09 (constituição 1.4.5)

Adicionado

  • Versão de lib da casa: a corrente vem do REGISTRO, o central declara só o piso (constituição 1.4.5; tabela de pisos no 0019, item novo no passo 3b do /xadm-docs, ponteiros em constituição §5 e java-micronaut §Específico). A ideia era listar as versões do xadm-commons na constituição para o /xadm-docs cobrar; o levantamento mostrou que a lista escrita à mão é o problema, não a solução: a tabela do 0019 dizia xadm-comum-web 0.2.0 enquanto o registro servia 0.9.0, e três dos oito módulos estavam defasados no mesmo dia em que a página foi editada. O maven-metadata.xml do registro é público, anônimo e sempre verdadeiro — então a corrente passa a ser consultada lá (um curl), e o que a casa escreve é o piso: a versão abaixo da qual estar é defeito, com o motivo, que é justamente o que o registro não sabe dizer. Piso inicial: xadm-seguranca 0.5.1 (Bearer fail-closed; abaixo, 401-em-massa silencioso), xadm-comum-web 0.5.0 (seam de code/title + log incondicional do erro; abaixo, 5xx não chega ao GlitchTip), xadm-mensageria 0.3.4 (eleição de instância; abaixo, entrega em dobro sob canário jar × native). Os demais não têm piso — adotar bump é decisão do app. O /xadm-docs reporta em dois pesos: abaixo do piso entra destacado com o motivo; atrás da corrente mas acima do piso é informação, não pendência (senão todo bump de patch vira dívida e o relatório vira ruído). Sem gate de CI — a casa não derruba build de frota por versão de lib. O allowlist do templates/settings.json ganhou o host do registro, escopado em /api/packages/xadm/maven/*.
  • §Tipos no schema do cliente (powersync) — a página não dizia nada sobre tipo de coluna, e a convenção da casa morava no javadoc de um cliente. Agora a norma sai do mapeamento que o serviço faz: o SDK tem três tipos (text/integer/real), o schema declara o que vai chegar, e declarar diferente não dá erro — a doc é explícita ("if a value doesn't match, it is cast automatically"), o cast é silencioso. Tabela por tipo do Postgres (bool → integer 1/0; int2/4/8 → integer; numeric/decimal → text; float4/8 → real; date/timestamp → text ISO; json/jsonb → text; id implícito, não declarar) e as três consequências: booleano chega 1/0 e código que espera true/false lê tudo falso, sem log; numeric declarado real funciona por cast e troca a precisão exata por double — em BI o erro acumula na agregação — a convenção não perde precisão, e não há caso em que perder seja aceitável: quem escolheu numeric no Postgres já decidiu que o número tem de fechar, então declare text e converta para BigDecimal/Decimal na leitura, com real restrito ao que já nasce float na origem (float4/float8). O custo da regra vem resolvido, não ignorado: decimal em text ordena lexicograficamente no SQLite, e as duas saídas exatas são somar/ordenar na aplicação ou o dono da tabela publicar uma coluna inteira escalada (valor_milesimos bigint) — CAST(x AS REAL) no SQL local não é saída, é o mesmo double por outro caminho; integer declarado text faz o SQLite comparar lexicograficamente ('10' < '9') e quebra ORDER BY, BETWEEN e índice. Fica registrado que a casa tem duas convenções: o integrador-client declara tudo text com o porquê escrito (números viram BigDecimal, nunca double), o bi-comercial declara Column.real sobre numeric(15,3)/numeric(10,2) — que agora é dívida a migrar, não trade-off a documentar. Trave por teste contra o SQLite real, e afirme o decimal contra o valor exato (nunca um double com tolerância: asserção com margem passa justamente no caso que a regra existe para pegar).
  • Teste de guarda de cluster precisa de SESSÃO distinta, não de thread distinta (java-micronaut §Testes). pg_try_advisory_lock é por sessão e as requisições empilham: a conexão que já detém o lock o readquire sempre. Teste com duas threads sobre o mesmo pool pode receber a mesma conexão, as duas "instâncias" entram, e ele fica verde afirmando o contrário do que a guarda promete. Receita determinística: segurar o lock numa conexão fora do pool (DriverManager, com a URL que o Testcontainers publicou) e então chamar a guarda, esperando coalesce. Dois detalhes que evitam trocar de assunto no meio: a guarda vem injetada (o @Connectable só aplica em bean proxiado — em objeto new-ado o teste exercita outro caminho) e o teste roda @MicronautTest(transactional = false) + @TestInstance(PER_CLASS).
  • @Scheduled que vem de lib: veja se a lib já elege antes de reagendar (java-micronaut §Banco). Job agendado dentro de uma lib dispara em cada réplica igual ao do app, e a correção mora na lib. Trazer o job para dentro do lock do app é o caso restrito — lib que não elege, ou app que precisa do trabalho dela e do próprio sob a mesma eleição (locks distintos elegem instâncias distintas). Aí a receita é desligar a property do agendador da lib e chamar o método público sob o lock do app, o que funciona porque o gate costuma estar no tick, não no método público — com a ressalva de conferir no fonte da lib: gate no método público faz o reagendamento virar no-op silencioso e o job simplesmente para.
  • DTO de resposta de contrato leva @JsonInclude(ALWAYS) (java-micronaut §Armadilhas, refinando a regra que já existia do lado do consumidor). A causa ganhou nome e default: micronaut.serde.serialization.inclusion vale NON_EMPTY (lido no schema de configuração do micronaut-serde-api 3.0.0), e NON_EMPTY não some só com coleção e mapa vazios — some também com string vazia, então um String observacao = "" desaparece pelo mesmo mecanismo. Anotação no DTO, não a property global (mudar a inclusão do app inteiro engorda todo payload e altera endpoints que ninguém revisou); o defensivo do consumidor continua valendo, porque produtor de terceiro segue omitindo.
  • Tabela que o PowerSync replica: o @Id da entidade é a PK real do banco (java-micronaut §Banco). O Micronaut Data aceita @Id sobre coluna apenas UNIQUE e a divergência só cobra do outro lado, onde a REPLICA IDENTITY default é a PK. Com a razão certa: não é que o DELETE chegue "sem o id" — o serviço casa a linha pelo hash da replica identity guardado no current_data, e PK composta funciona; o que a regra evita é a divergência entre a chave que o serviço casa e o id que o cliente vê, que torna ilegível o único diagnóstico disponível quando o delete some.

Corrigido

  • 0019 e 0021 davam a eleição de instância do RelayM2m como "implementada e não publicada" — a xadm-mensageria 0.3.4 saiu (tag xadm-mensageria-v0.3.4, 2026-09-09) com mensageria.relay.cluster-lock default ligado. Registro por versão atualizado no 0019 e o estado da entrega no 0021; adotar é subir a versão, sem DDL nem config. Quem contornou desligando o relay da lib e reagendando sob lock próprio pode devolver a guarda para a lib.

Adicionado

  • Sync Streams edition: 3 — o que a subconsulta libera, e o teto de 1000 que ela traz junto (powersync §Config). O formato praticado nas seis stacks é streams: com config: edition: 3 (várias migradas de bucket_definitions:) — e a distinção importa na hora de escrever query: sync rules clássicas não aceitam subquery, JOIN nem CTE; Sync Streams aceitam IN (SELECT …), subconsulta aninhada, INNER JOIN (colunas selecionadas de uma só tabela) e CTE no bloco with:, sendo o with: global o que exige edition: 3. GROUP BY/ORDER BY/LIMIT/UNION estão fora nos dois formatos. Ganho: recortar a filha pelo estado da mãe sem denormalizar. Preço: subconsulta é parameter query, e o PSYNC_S2305 limita 1000 resultados (e 1000 buckets) — estourar devolve HTTP 500 no endpoint de sync e trava o cliente inteiro em connecting, levando junto os demais streams; o dano não fica contido na query. Duas mecânicas que decidem o dimensionamento: o contador soma os streams da conexão e a parameter query roda uma vez por stream (32 streams com o mesmo recorte pagam 32×; a resposta do mantenedor a esse caso real foi fundir os streams, não encolher o dado), e a CTE baixa a conta porque o contado passa a ser o resultado dela — dimensione pelo que a subconsulta devolve, nunca pelo tamanho da tabela.
  • Como o DELETE encontra a linha: replica identity, não o id da sync rule (powersync §Fonte). Cada linha é casada por um hash das colunas da replica identity; no delete o Postgres manda só essas colunas, e o serviço não avalia a sync rule com esse pedaço de linha — busca o registro anterior no current_data pelo hash e remove os buckets já gravados no insert. Decorrências documentadas: tabela sem PK e sem índice único sai fatal (No replication id found … Configure a primary key on the table); o modo de falha silencioso é o registro anterior faltando, com assinatura própria no log (Cannot find previous record for delete on <tabela>: <replicaId> / <id>) e a linha ficando no cliente para sempre; REPLICA IDENTITY FULL resolve por força bruta e dobra as operações (todo update vira delete+insert); trocar a identity re-replica a tabela inteira. Higiene da casa: id é a PK simples — não porque o delete precise do id no evento, mas porque PK simples faz identidade e id coincidirem, e aí o diagnóstico acima basta.
  • O serviço tem diagnóstico próprio, e ele vem com o comando da correção (powersync §Fonte). POST /api/admin/v1/diagnostics e POST /api/admin/v1/validate devolvem os erros por tabela já resolvidos — tabela fora da publication sai como Table <t> is not part of publication 'powersync'. Run: ALTER PUBLICATION powersync ADD TABLE <t>., RLS sem BYPASSRLS sai como PSYNC_S1145 com o ALTER ROLE escrito. Exigem api_tokens na config (sem token: Authentication disabled) — o que explica a impressão de que esses erros são silenciosos: o serviço acusa, ninguém pergunta.
  • Gate cuja lista é datada por versão de terceiro prova o vermelho a cada bump (ci-testes, seção nova; receita concreta em powersync §Deploy). Gate que codifica o que a versão X rejeita — o lint de sync rules contra a tag pinada é o caso da casa — fica verde para tudo quando a versão nova passa a aceitar a construção proibida ou muda a string de erro procurada: a regra passa a valer por omissão e ninguém nota, porque verde é o esperado. Norma: ao subir o pin, rodar o gate contra entrada sabidamente inválida e confirmar que reprova; não reprovou, a regra saiu de validade (reescrever ou remover). É a disciplina de mutante das guardas do pipeline.yml com o gatilho deslocado — lá o mutante roda a cada mudança nossa, aqui a prova roda a cada mudança deles.

Corrigido

  • A §Fonte prescrevia declarar a lista de tabelas na publication; a casa cria FOR ALL TABLES (powersync §Fonte) — prescrição aspiracional numa página que se declara só o praticado, contra o que as seis stacks fazem (CREATE PUBLICATION powersync FOR ALL TABLES). Reconciliado ao praticado, com o trade-off escrito nos dois sentidos: publication ampla custa WAL e memória a mais e faz tabela nova entrar sozinha; lista explícita exige ALTER PUBLICATION … ADD TABLE no mesmo PR da migração (mais o GRANT SELECT), e esquecer não quebra deploy — a tabela só não replica. Fica registrado que o caminho não é reversível de graça: o Postgres não aceita ALTER PUBLICATION … ADD/DROP TABLE numa publication criada FOR ALL TABLES. Mesma correção no irmão que o operador de fato copia: o bloco SQL de coolify §Banco que é fonte do PowerSync trazia CREATE PUBLICATION powersync FOR TABLE a, b com a nota "FOR ALL TABLES só em dev" — drift entre irmãos, as duas páginas prescreviam contra a prática.

0.39.0 - 2026-09-09 (constituição 1.4.4)

Adicionado

  • Nome de tabela que o app cria em schema-espelho: prefixo <app>_ também para a tabela de DOMÍNIO, e o substantivo é decisão (integração §Taxonomia — dona da convenção; eco em java-micronaut §Banco, onde quem cria a tabela lê). A convenção existia mas cobria só staging e status por chave natural; a unidade de trabalho do app (lote de entrega, remessa, agrupamento de envio) ficava sem regra, e num schema que espelha sistema de terceiro é o prefixo que separa "nosso" de "deles". Agora: <app>_ para toda tabela do app, composto <app>_<fluxo>_ quando o app serve vários fluxos e a tabela é de um só; prefixo é sempre o app, nunca a origem — <origem>_ já significa coluna do vocabulário do parceiro (só rastreabilidade, decisão 0009), e o mesmo prefixo com dois sentidos confunde quem a convenção deveria ajudar. O prefixo resolve o schema; o substantivo resolve a conversa: confira o termo contra o vocabulário do sistema espelhado antes de nomear — lote como unidade de entrega, num schema em que lote quer dizer lote de estoque (loteest, itenslote), não colide no banco e colide na reunião; homônimo inevitável vai para o glossário do repo do app. E tabela sem prefixo que já existe (request, push_enviada) é legado, não exemplo — rename oportunista por expand/contract quando a tabela for tocada, nunca big-bang.
  • scripts/testa-guardas-pipeline.py — as guardas inline do pipeline.yml ganham teste permanente (constituição 1.4.4; wire no build-site.yml e no pré-flight da /xadm-release; norma em ci-testes §Guarda inline do pipeline.yml nasce com caso de teste). As ~12 guardas do job gate rodam na frota inteira, em todo push, e nenhuma tinha teste: a verificação era ad-hoc, na sessão que escrevia a guarda, e evaporava com ela — numa única sessão o extrator do YAML foi reescrito 7×, e das 5 guardas tocadas em set/2026 4 tinham defeito, todos achados por um app rodando (itens 151/186). O harness extrai o bloco do próprio templates/pipeline.yml pelo name do step (nada extraído = vermelho, se o formato mudar), roda cada caso numa árvore temporária e aplica mutação declarada — mutante vivo = falha, porque a linha mutada não estava sendo exercida por nenhum caso. Entrada: 6 de 12 guardas, 38 casos, 13 mutantes; cobertura oportunista (§1.6). Meta-guarda: guarda declarada no harness que suma do YAML reprova, em vez de virar teste que não testa nada. Provado nos dois sentidos — mutar a guarda real no pipeline.yml deixa o harness vermelho, e renomear um step dispara a meta-guarda.

Corrigido

  • Comentário duplicado na guarda do piso do ArchUnit (templates/pipeline.yml), resquício do item 186 — o bloco explicando assertTrue(...hasNext()) aparecia duas vezes.
  • Réplicas do MESMO app competiam pela outbox — a partição da 0021 só separava apps diferentes (constituição 1.4.3; emenda in-place na 0021, receita em java-micronaut §Banco, ponteiro em integração §Entrega garantida, registro por versão no 0019). O filtro por tipo registrado (0.2.0) particiona a SAIDA entre apps; réplicas do mesmo app registram os mesmos tipo, casam o mesmo predicado e drenam a mesma linha — e N > 1 é o estado normal da casa (canário jar × native mantém os dois recursos no ar atrás do mesmo host), então o efeito externo saía em dobro, sistematicamente, não por corrida rara. Norma: o tick do relay roda sob pg_try_advisory_lock(hashtext('<app>_relay_m2m')) — chave por app (compõe com o filtro, não o substitui), por sessão e não transacional (o PUT acontece fora da transação, um pg_advisory_xact_lock ou um FOR UPDATE SKIP LOCKED soltaria antes do envio), e try (a réplica perdedora coalesce). O contrato não muda: segue at-least-once, a idempotência do receptor por chave natural segue precondição e o exactly-once segue deferido — o gatilho da alternativa descartada ("só se um receptor comprovadamente não puder ser idempotente") não disparou. A distinção que faltava nomear: duplicação evitável (duas réplicas fazendo o mesmo trabalho — a lib remove) × inerente (crash entre o efeito externo e o commit — o receptor absorve).

Adicionado

  • Guarda que avisa: @Scheduled sem eleição de instância (templates/pipeline.yml). O achado é maior que o relay — todo job agendado dispara em cada réplica, e sob canário isso é o default, não a borda. A guarda lista os @Scheduled quando nenhuma fonte de produção mostra eleição (advisory_lock/SchedulerLock/LeaderElect); // agendado-ok: <por quê> no arquivo silencia aquele arquivo, como o -- destrutivo-ok: do Flyway. Só ::warning:: — job idempotente por desenho é legítimo e a detecção não sabe distinguir (ponto cego declarado). Sandbox 9/9 + 4 mutantes mortos (filtro de gerados, marcador, strip de comentário, self-skip de stack).

0.38.6 - 2026-09-09 (constituição 1.4.2)

Corrigido

  • SOURCE_COMMIT é variável do Coolify, e em recurso pull-only ele a SOBRESCREVE com HEAD — a env de identidade da casa passa a ser XADM_COMMIT (constituição 1.4.2; templates/dockerfile-java, templates/dockerfile-java-native, templates/dockerignore-java, templates/pipeline.yml, scripts/smoke.py, coolify §Carimbar o commit, smoke-produção §6, docs/decisoes/0031-smoke-de-producao-pos-deploy.md). O sintoma é mudo e caro, visto no primeiro release do central-backend com o /health novo: o build passou --build-arg SOURCE_COMMIT=<sha>, a imagem no registry carregava ENV SOURCE_COMMIT=<sha>, e produção respondia "commit":"HEAD" — o PaaS injeta a própria variável em runtime, por cima do ENV da imagem, porque num deploy por imagem não há commit a resolver. Três estragos em cadeia: a camada 1 do smoke nunca casaria, a release do GlitchTip viraria HEAD (o log-guarantee preso no fallback por tempo, para sempre) e o alvo de rollback da entrega seguinte seria a tag inexistente <imagem>:HEAD-<target> — falha no pior momento. A cura é o namespace, não o valor: disputar o nome com o PaaS em runtime é briga que se perde no próximo release dele. Exceção deliberada: o Dockerfile do próprio site mantém SOURCE_COMMIT — ali quem builda é o Coolify, o valor é real e não há env de runtime em jogo.
  • HEAD passa a valer como AUSENTE dos dois lados — lib (xadm-comum-web ≥ 0.9.0, com SOURCE_COMMIT como fallback legado para e2e local e docker run) e scripts/smoke.py. App não migrado omite o campo em vez de publicar identidade falsa, e HEAD no PREVIOUS deixa de virar alvo de rollback.
  • A camada 1 do smoke deixava passar verde contra o container VELHO (scripts/smoke.py, docs/decisoes/0031-smoke-de-producao-pos-deploy.md). Pedido um EXPECT_COMMIT e vindo um /health sem o campo, ela degradava silenciosamente — mas o deploy é assíncrono, então o container que responde segundos depois do POST costuma ser o anterior, de uma lib que ainda não publica commit: verde provando menos. Agora a degradação para versao exige EXPECT_VERSION declarada; sem ela, a ausência do campo é vermelha. testa-smoke.py cobre os três casos novos (26 casos).
  • Duas âncoras mortas em docs/decisoes/0031-*.md e docs/infraestrutura/index.md, que apontavam coolify.md#carimbar-o-commit-do-deploy — slug real termina em -nao-leia-o-githead-no-build.

0.38.5 - 2026-09-08 (constituição 1.4.1)

Corrigido

  • Vendorizar o Bootstrap criou um prefixo que a segurança dos apps não cobria: /js/** (templates/views-jte/kit/layout.jte, java-micronaut §UI, constituição 1.4.1). Regressão da própria 1.4.0: antes o JS vinha do CDN e nem passava pela segurança do app; vendorizado, o layout.jte passou a linkar /js/bootstrap.bundle.min.js — e os estáticos são servidos pelo app, cuja config (intercept-url-map + whitelist da regra de view) o kit não controla. Quem re-deriva sem acrescentar a regra serve 401 no bundle, inclusive na tela de login, onde por definição não há sessão. O sintoma engana: página estilizada e sem JS (navbar-toggler/collapse mortos), sem nada no log. O cabeçalho do kit passa a dizer "ao re-derivar, libere /js/** como já libera /css/**", com os prefixos que ele linka listados.
  • A guarda de estáticos do gate não cobre isso, e foi dito por escrito: ela afirma que o arquivo existe, não que é servido a anônimo — e guarda estática não resolveria, porque as regras vêm de duas fontes (o YAML do intercept-url-map e a whitelist em Java da regra de view): seria cega a metade e avisaria em quem está correto. A trava mora no app: teste que lê os href/src do próprio layout.jte e afirma que nenhum responde 401/403 sem credencial — na forma que não envelhece quando o kit linkar um prefixo novo. Receita em java-micronaut §UI.

0.38.4 - 2026-09-08 (constituição 1.4.0)

Corrigido

  • Duas prescrições da casa se cancelavam: o jteGenerate vaza no POM e mata o compileOnly (java-micronaut §Native e §UI, docs/decisoes/0032-tela-de-login-servida-pela-lib.md). A página manda a lib que embarca view aplicar a extensão jteGenerate("gg.jte:jte-native-resources:…") — e manda o JTE entrar como compileOnly para não empurrar motor de view a adotante API-only. As duas juntas não funcionam: o plugin faz implementation.extendsFrom(jteGenerate) (verificado no bytecode do jte-gradle-plugin 3.2.4), então a extensão de build sai no POM como dependência de runtime de todo adotante. O defeito é invisível no check e no jar — só o POM publicado revela. A cura (setExtendsFrom filtrando o jteGenerate) entrou colada à receita que a causa, com a instrução de conferir no POM gerado.
  • A pergunta em aberto da 0032 fechou: compileOnly BASTA, verificado no artefato (jar com a Jte…Generated sob namespace + reflection-config.json gerado; POM sem entrada gg.jte) — os planos B (implementation, ou o módulo xadm-seguranca-views) saem da mesa. Registrado com a condição: o POM só fica limpo porque o vazamento acima foi desfeito.

Adicionado

  • Duas notas de view servida por lib (java-micronaut §UI): a view da lib renderiza no teste dela (TemplateEngine.createPrecompiled + render("<namespace>/<view>.jte", …), o mesmo caminho do adotante) — senão quem descobre template quebrado é o app em produção; e o ContentType.Html escapa por contexto, inclusive dentro de <script> (um ${x} em bloco JS sai com / como \/), que é o que impede valor hostil de fechar a tag — asserte o valor escapado, não o literal cru.

0.38.3 - 2026-09-08 (constituição 1.4.0)

Adicionado

  • A tela de login vira da casa: servida pela lib, configurada por property (constituição 1.4.0; java-micronaut §UI, emenda na docs/decisoes/0025-views-server-render-com-jte.md, templates/views-jte/kit/layout.jte, templates/public/**). A frota tinha cinco login.jte — e o que estava duplicado era justamente o que não deveria: o mecanismo de auth (mesmo SDK, mesmo popup, mesmo callback com CSRF) era idêntico nos cinco, enquanto o que divergia era moldura — bloco de marca em 3, título em quatro formatos. Um bug de auth custava 5 PRs. Agora o xadm-seguranca serve a view e o app configura xadm.views.login.* (marca, tagline, e título derivado do app-nome, que mata os quatro formatos por construção); adoção é migração — sobe a lib e apaga a view, no mesmo PR.
  • Norma: view empacotada em LIB mora sob namespace, nunca na raiz. O JTE transforma subdiretório em pacote, então login.jte na raiz de uma lib gera a mesma FQCN que a view do app — duas classes iguais no classpath, uma sombreando a outra, sem erro e sem aviso. A mensageria.jte da xadm-mensageria está nesse estado (colisão latente), a reconciliar.
  • Bootstrap VENDORIZADO no kit (templates/public/css/bootstrap.min.css, templates/public/js/bootstrap.bundle.min.js, rastreados): o layout.jte deixa de linkar cdn.jsdelivr.net — a tela de login é a página mais exposta do app e o <link> ia sem integrity. Versão 5.3.8 (a linha atual; a frota estava 5 patches atrás), baixada e conferida contra o hash SRI que o próprio projeto publica; o cabeçalho de cada arquivo registra versão, origem e hash, então o próximo bump é auditável. O sourceMappingURL foi removido — sem o .map servido junto ele produziria o mesmo 404 silencioso que esta leva conserta. O SDK do Firebase segue no gstatic, de propósito: vendoriza-se apresentação, não o SDK do provedor de identidade.
  • Decisão docs/decisoes/0032-tela-de-login-servida-pela-lib.md registra a mudança de fronteira (a lib manda no fluxo e na tela) com as cinco alternativas descartadas — template rastreado no kit, lib-só-do-mecanismo, bean customizador, slot JTE e módulo xadm-seguranca-views.
  • Emenda à 0032, no mesmo dia: a view da lib é AUTOCONTIDA, não usa o kit.layout. A decisão original dizia que a tela usaria o kit, como as telas dos apps — e isso não compilaria: @template é resolvido em tempo de geração, não em runtime (o JteloginGenerated de um app real contém a chamada Java direta kit.JtelayoutGenerated.render(...)), então a view empacotada no jar exigiria o kit/layout.jte dentro do build da lib, que não o tem. A xadm/login.jte passa a trazer o próprio documento mínimo — logo, nome e versão —, com a identidade vindo do CSS (custom-theme.css), sem dependência de template entre repos. Custo declarado: ~10 linhas de header duplicadas no jar, com divergência visual possível ao longo do tempo. Descartadas com o custo escrito: lib carregar o kit sob namespace próprio (skew quando o central atualiza o kit e a lib ainda não), mover o kit para a lib (98 views em 6 apps mudam a chamada, e correção no kit passa a exigir release da lib + bump por app) e kit como artefato de template publicado (mecanismo novo).
  • Guarda: estático que o kit linka e não existe reprova o gate (templates/pipeline.yml). O layout.jte linka /css/app.css incondicionalmente e o JTE não tem como saber, em render, se o arquivo existe — sem ele o app serve 404 em toda página, invisível sem abrir o DevTools. A /xadm-docs já mandava criá-lo desde a 0.23.1: faltava cobrança, não prescrição (a frase da skill, que se lia como "não se preocupe com ele", passou a distinguir não re-derivar de tolerar ausente). A guarda deriva a lista do próprio layout.jte, então cobre também o Bootstrap vendorizado, o tema, o logo e o favicon — e não exige o asset novo de quem ainda tem o kit antigo (que linkava o CDN), o que evitaria falso-positivo na janela de migração.

Corrigido

  • Três guardas do pipeline.yml corrigidas por feedback §6 de app (templates/pipeline.yml, constituição 1.3.1). (1) O cache-dance do build_jar estava pela metade — o template trazia o buildkit-cache-dance mas não o actions/cache que persiste o diretório entre runs, nem o id no setup-buildx (sem ele não dá nem para escrever o builder:). São duas metades: sem a que persiste, a dance re-hidrata a partir do vazio e o --mount=type=cache,id=gradle-<slug> do dockerfile-java nasce frio em todo run — um step que não economizava nada. Adotada a forma completa, que um app já carregava. (2) A defesa anti-vácuo do ArchUnit só reconhecia AssertJ — Assertions.assertTrue(classes.iterator().hasNext()) é forma legítima e caía em ::warning:: falso, ensinando a reescrever teste já correto; a alternação passou a aceitá-la, mas dentro de uma asserção (hasNext solto não conta — qualquer while (it.hasNext()) no arquivo silenciaria o aviso). (3) Guardas varriam diretório GERADO — build/resources/main/, bin/ e afins guardam cópias dos mesmos arquivos: no runner (checkout limpo) é inócuo, mas o dev roda a guarda local antes de pushar e reprovava por artefato velho com o src/ já corrigido. Eram três guardas, não uma (placeholder do Micronaut, persistência micronaut-data e DDL destrutivo do Flyway) — todas passaram a filtrar build/bin/out/target/.gradle/node_modules. Verificado por sandbox da cópia extraída do YAML, com mutação (desligar o filtro deixa o caso do build/ vermelho de novo).

  • Três emendas ao smoke, vindas do PRIMEIRO ensaio real (scripts/smoke.py, templates/pipeline.yml, docs/engenharia/smoke-producao.md, constituição 1.3.2). O etc/tests passou a rodar o mesmo smoke.py do pipeline antes do deploy, contra o binário native local — e a 2ª run ficou vermelha. (1) class é a CREDENCIAL que a rota exige, não o público-alvo da tela. O webstorm-ecom declarou / como session sendo a home pública: com o seam funcionando a rota passa; no dia em que o seam degradasse, o smoke bateria a mesma rota sem credencial, veria 200, classificaria como "rota exposta" e reverteria um deploy sem defeito. Além do aviso na página, o smoke passou a detectar sozinho: em rota m2m/session ele sonda também sem credencial e trata 200 anônimo como defeito de manifesto — falha sem reverter, e a bomba de efeito retardado vira erro imediato. A sonda não roda em public, onde 200 anônimo é o correto. (2) Seam ausente nem sempre é 404: onde há regra de view global, ela é consultada justamente na rota inexistente e o 404 sai como 401 (ou 302, conforme o Accept) — medido no mesmo binário. Gate sobre o seam afirma "não emite sessão" (404/401/403/302), nunca == 404. (3) Nome de env fora da norma: GT_URL/GT_TOKEN eram abreviação inventada, contra seguranca §Segredos (nome pelo destino, para grep achar o conjunto a rotacionar). Agora GLITCHTIP_URL + GLITCHTIP_API_TOKEN, com o nome legado aceito por uma release, com aviso.

  • Declarar smoke.glitchtip sem cadastrar o token passou a REPROVAR (era aviso). Declarar o bloco é ato explícito: sem o secret a camada 3 não roda e o job fica verde provando 2 de 3 — o "verde provando menos" que a página combate, e o estado real em que o piloto estava. É defeito de configuração: falha, não reverte. Quem ainda não quer a camada 3 não declara o bloco.

0.38.2 - 2026-09-08 (constituição 1.3.0)

Adicionado

  • Smoke de produção pós-deploy: o pipeline só fica verde se produção responder (constituição §9, decisão docs/decisoes/0031-smoke-de-producao-pos-deploy.md, nova página docs/engenharia/smoke-producao.md, scripts/smoke.py + scripts/testa-smoke.py, templates/pipeline.yml, templates/app.json, templates/dockerfile-java{,-native}). O POST /api/ci/deploy é assíncrono: retornava sucesso e o pipeline ficava verde antes de o container novo servir — "deployado" sem nada ter verificado o resultado. Um job smoke passa a rodar depois do deploy, em quatro camadas: identidade (o commit do /health é o desta entrega), rotas críticas declaradas em app.json → smoke.routes, log-guarantee (nenhuma exceção NOVA no GlitchTip) e veredito — reprovou, reverte para a tag imutável <imagem>:<sha>-<target>, confirma a reversão e fica vermelho. Motivo: /health verde é liveness, não "as telas funcionam" — a frota entregou tela nova em 500 com o app healthy, e segredo ausente que só apareceu como 401 no cliente. A mesma lista de rotas serve dois gates: o e2e native afirma ≠404 no binário antes do deploy; o smoke afirma o status esperado depois. Critério do log-guarantee: firstRelease desta entrega reprova; reincidência de exceção antiga avisa, não reprova (reprovar por atividade na janela faria ruído crônico deixar o smoke vermelho para sempre — e a reação previsível seria desligá-lo); com guarda de fail-open para o projeto que não marca release. Reversão conservadora nas bordas: não reverte sob deploy concorrente, divergência entre instâncias, ausência de alvo, erro de manifesto nem indisponibilidade do GlitchTip.
  • Identidade de build canalizada até a imagem (SOURCE_COMMIT como build-arg nos três jobs de build
  • ARG/ENV nos dois Dockerfiles rastreados). A convenção da casa existia (CLAUDE.md §2) mas ficara órfã de mecanismo desde a migração do build para o GitHub Actions: nenhum template passava ou declarava a variável, e o campo commit do /health era documentado como "opcional útil" sem caminho para preenchê-lo. O mesmo valor é o commit do /health, a release no Sentry e o sufixo da tag imutável — um identificador atravessa pipeline, imagem, /health e GlitchTip. O ARG é declarado tarde (depois do COPY, no estágio final): declarado no topo, invalidaria o cache de tudo abaixo a cada commit — no native, os ~13 min do nativeCompile em todo build.
  • A tag imutável de imagem vira ativo operacional (constituição §9): política mínima de retenção — 10 últimas por (imagem, target), e nunca a que está no /health de um recurso em produção. Podar o alvo transforma o rollback em "não confirmado" no pior momento.
  • smoke.routes no contrato do app.json (templates/app.json, valida-frontmatter): bloco ausente é válido (self-skip da adoção em ondas); declarado, o shape é cobrado — campo torto vira gate que não roda, falha silenciosa em vez de erro.

  • O contrato do seam de sessão nasce reconciliado com a lib (docs/engenharia/smoke-producao.md §4, decisão docs/decisoes/0031-smoke-de-producao-pos-deploy.md): a credencial vai em X-Smoke-Token, não em Authorization: Bearer — naquela rota o Bearer entra na cadeia de autenticação por token antes do controller e vira 401 antes de chegar ao seam; a sessão volta em Set-Cookie; e o papel de smoke acompanha o papel de leitura em vez de substituí-lo (isolado, não passaria em @Secured nenhum), com o "só-leitura" imposto por filtro de método — garantia por verbo, que não prova ausência de efeito colateral num @Get.

Corrigido

  • O mecanismo do /health do Flutter web nunca estivera prescrito (versionamento §Receita, flutter §Deploy web): a doc descrevia o efeito ("o nginx monta a resposta lendo o version.json"), não o passo. Agora está escrito que o arquivo é gerado no build, de dois insumos — versao do version.json e commit do ARG SOURCE_COMMIT —, e por que o version.json não ganha campo (é o artefato que o app consome para auto-update, servido no-cache a todo cliente).

0.38.1 - 2026-09-08 (constituição 1.2.2)

Adicionado

  • Política de glitch: todo 5xx e todo 404 AUTENTICADO vão ao GlitchTip (java-micronaut §Erros, §Native; e2e-local §10; 0019). Handoff de incidente: GET /incidentes do integrador-sascar respondia 200 no jar e 404 no binário native — rota podada pelo AOT, sem erro de build — e o processor de erro da casa logava 404 em DEBUG, então o erro real ficou invisível (não chegou ao Sentry). O invariante do item 142 (só 5xx) passa a incluir o 404 autenticado, e o porquê do critério ser a credencial fica escrito: em runtime o app não pode decidir por inventário de rotas — a rota ter sumido é o bug —, mas cliente com token acreditava que a rota existe (bug real) e scanner não manda token (ruído). Descartadas allowlist de prefixos por app e cross-check com o OpenAPI (driftam / custam por request). O mecanismo é da lib (request.getUserPrincipal() ou header Authorization) e carrega uma premissa a verificar no binário: se o filtro de segurança popula a Authentication numa requisição sem rota casada — se não popular, o principal vem vazio no exato caso que a regra existe para pegar. Classe nova nomeada em §Native: o AOT pode podar rota registrada (404 no native × 200 no jar), irmã do SentryAppender e do resource-config; a defesa de build é o e2e native afirmando as rotas-chave (≠404), que ganhou parágrafo próprio na §10.
  • flavor no /health entregue pela lib; o 0019 passa a registrar prescrito × entregue (0019, java-micronaut §Específico/§Erros, versionamento §Receita, e2e-local §10). Retorno de repo de lib: o flavor ({status, versao, flavor}, "native"|"jvm") está implementado na xadm-comum-web e sai na 0.7.0 — herdado por todo app que sobe a dependência; até a 0.6.1, a última publicada, o campo não existe no artefato. O /info não ganhou o campo (decisão do dono da lib — a pergunta se faz contra o /health; em código, VersaoInfo.flavorAtual()). Refinamento absorvido na receita: a property lê-se dentro do método, nunca num static final — sob native-image o inicializador estático pode rodar no build, onde org.graalvm.nativeimage.imagecode vale "buildtime", e o valor cacheado passa a depender da política de initialize-at-build-time. O e2e native ganha a asserção flavor == "native": é o único gate que prova o ramo native (a JVM não exercita).

Corrigido

  • CENTRAL_DEPLOY_URL do templates/pipeline.yml carregava o flavor de build — apontava central-backend-jar.xadm.biz, exatamente o antipadrão que a constituição §9 proíbe ("URL que outro sistema consome não carrega flavor de build nem instância"), regra escrita depois do incidente em que parar o jar do central-backend numa promoção a native derrubou o POST /api/ci/deploy da frota (503). O template rastreado seguia com o valor velho: norma escrita, artefato da casa não seguiu. Agora aponta o domínio estável central-backend.xadm.biz. Constituição 1.2.2 (1 rastreado re-carimbado: templates/pipeline.yml).
  • Doc do central envelhecida nos dois sentidos sobre capacidade de lib (java-micronaut §Erros). Verificado nos jars publicados: o seam de code/title está entregue desde o xadm-comum-web 0.3.0 (o processor lê getRootCause() e honra a PortadoraDeProblema) e o log incondicional do erro desde a 0.5.0 (5xx em ERROR com a causa-raiz, 4xx em DEBUG) — mas as duas passagens seguiam escritas como "pendência de repo de lib" / "o que a lib deve entregar no 0.3.0", mandando app manter @Replaces de fork que já não precisa. Corrigidas in-place (padrão da casa para doc que mente). Causa comum ao caso do flavor (prescrito e não entregue): prescrição do central cujo código mora na lib não tinha rastro de entrega — agora o 0019, dono das versões, carrega o bloco prescrito × entregue, com a versão em que se verificou a ausência quando ainda não veio. Primeiro caso registrado como ainda não entregue: a hint de reflect-config do SentryAppender (0022, java-micronaut §Native) — ausente até a 0.6.1, então app confere o META-INF/native-image/ da versão que usa antes de largar a cópia local.
  • Varredura completa prescrito × entregue nos 8 módulos do xadm-commons (0019, 0021, 0022, java-micronaut §storage/§Erros/§Native, versionamento, e2e-local). Com o rastro criado, a auditoria dos CHANGELOG e das tags dos módulos achou seis divergências, além do flavor: (a) o flavor estava anunciado como entregue na 0.7.0, mas o commit não tem tag — a afirmação foi corrigida para implementado, sai na 0.7.0; (b) a 0021 dava o filtro da SAIDA como "prescrição nova, pendência de repo de lib; adotantes contornam por convenção" — está entregue no xadm-mensageria 0.2.0 (relay reivindica só tiposRegistrados(), enfileirar falha-rápido); (c) a receita de consumo da xadm-comum-storage mandava montar ponte @Factory + @Replaces(S3Config) para o namespace garage.* e excluir o netty-nio-client transitivo — a 0.2.0 renomeou s3.*→garage.* (BREAKING) e já exclui o Netty no POM, ou seja, a receita mandava escrever o que a lib faz há um mês (reescrita, com o seam ObjetoStorage somado); (d) o 0019 descrevia o guard dos controllers flat como @Requires(property=…) da 0.6.0, mas a 0.6.1 reverteu para incondicional (nem property sobrevive ao AOT) — emenda registrada; (e) o filtro de request-id/MDC segue não entregue, agora com a versão da verificação (nenhuma até 0.6.1); (f) idem o reflect-config do SentryAppender. O bloco do 0019 passou a listar entregue / implementado mas não publicado / prescrito e não entregue, e diz como conferir (CHANGELOG do módulo, tag xadm-<módulo>-vX.Y.Z, META-INF/native-image/ do jar).

0.38.0 - 2026-09-06 (constituição 1.2.1)

Adicionado

  • Régua de teste sobe em componente compartilhado; comportamento dependente de tempo nasce com relógio injetável (constituição §6, ci-testes, java-micronaut §Testes; constituição 1.2.1). Handoff de cobertura da xadm-seguranca. A casa já tinha o princípio "defeito na lib alcança todo adotante" (0019, seguranca §Segredos, java-micronaut) mas nunca tirou dele a consequência para teste — agora a §6 diz que em componente compartilhado o risco é multiplicado pelos adotantes (o mesmo defeito vira N incidentes, chega a quem nem usa a feature, e às vezes o adotante não consegue contornar), logo "ramo defensivo de baixo valor" deixa de ser desculpa no que é segredo, auth ou controle de abuso. Em ci-testes, a consequência prática ao priorizar cobertura numa lib — e a observação de que cobertura creditada por e2e não fecha essa conta: o e2e passa pelo caminho feliz e deixa janela, reset e bordas descobertos (um filtro de rate-limit de login ficou com 43% na lib enquanto "tinha teste" no e2e). Segunda norma, cross-stack: comportamento que depende de tempo (janela deslizante, TTL, expiração de sessão/token, backoff, cutoff) nasce com relógio injetável — sem ele o ramo de expiração, que é o que decide segurança, só se testa com sleep (lento, flácido, primeiro a ser desligado) ou fica descoberto; mecânica Java (Clock/ InstantSource com default Clock.systemUTC(), leituras via Instant.now(relogio)) em java-micronaut, e o Flutter já tinha o _now equivalente.
  • Control-plane de deploy alcança recurso COMPOSE (emenda à 0026); deploy sem gate vira dívida nomeada; URL de serviço não carrega flavor (0026, deploy.md, constituição §9; constituição 1.2.0). Handoff da inclusão das stacks PowerSync no control-plane. Emenda in-place na 0026 (mesmo padrão do bloco Evolução que ela já tinha): a âncora deixa de ser "slug de imagem" e passa a ser o slug do artefato que aquele recurso consome — a imagem, quando o recurso puxa imagem; o slug do repositório git, quando o Coolify builda do git. Motivo: recurso build_pack=dockercompose (as stacks PowerSync: mongo + powersync-service + init) não tem imagem — docker_registry_image_name vem nulo, o reconcile o pulava e o POST /api/ci/deploy dava 404 no_deploy_target, deixando como único gatilho o auto-deploy do Coolify no push, isto é, deploy sem gate (e sync rule inválida sobe healthy). Consequências registradas: target ganha compose; o contrato M2M tem dois modos (com image, ou app_id + target obrigatório, com 400 missing_anchor/400 missing_target); o reconcile de compose pula recurso sem git e ignora build_pack desconhecido; e a assunção nova — um repo git = uma stack compose, sob pena de duas stacks do mesmo repo colidirem na âncora e uma sumir do mapa em silêncio (a segunda exige instance). Divergência registrada, não resolvida: o handoff descreve a unicidade como (registry_slug, target, …) e a deploy.md como (app_id, target, …) — coincidem enquanto app_id == registry_slug; fica anotado para conferir contra a migration. Norma nova na §9: recurso deployável é disparado pelo control-plane com gate verde, auto-deploy do Coolify desligado — com exceção declarada e datada dos dois que o Coolify ainda builda do git (o site de documentação e o scraper node), dívida nomeada e não isenção. Segunda norma: URL que outro sistema consome não carrega flavor de build nem instância — apontar central-backend-jar.… fez parar o jar numa promoção a native derrubar o deploy da frota inteira (503); vale para webhook, callback e JWKS.
  • Fake @Replaces vaza para todo o source-set de teste; cobertura mede-se em invocação única (java-micronaut §Testes, ci-testes §Definição de pronto). Feedback de app, duas normas cross-repo. (1) @Singleton @Replaces(X) escrito numa classe de teste é bean comum — entra no contexto de qualquer @MicronautTest do source-set, então o teste do vizinho, que existia para exercitar o X real, recebe o fake e passa verde sem provar nada. Correção ao feedback: as curas são duas, não uma — @MockBean (método/classe interna do teste) é a via oficial de isolamento (a doc do micronaut-test: o bean "will be active only for the scope of the test"; aceita fake escrito à mão, não exige mock-framework), e @Requires(property=…) + @Property serve quando o fake é compartilhado por várias classes. (2) Quarto modo de o relatório de cobertura mentir (irmão do class-major, do up-to-date e do gerado no denominador): invocação parcial re-executa o test e sobrescreve o .exec com a rodada menor, enquanto o XML e o HTML da rodada anterior continuam no disco e são lidos como atuais — regra: uma invocação só, com tudo que o gate roda, e limpe build/reports/jacoco/build/jacoco/*.exec na dúvida. Nota de rota: o arranjo em que esse erro aparece (ligar a integração por -PdockerTests) já é anti-padrão na casa — sem flag não há invocação parcial a confundir. (3) O sintoma virou heurística transversal em ci-testes: classe com cobertura ~0% sob teste de integração verde é sinal de investigar, não ruído — cobrir de novo o que já deveria estar coberto empilha teste sobre o defeito.
  • Repo de configuração deployável também tem gate; e query de bucket gateado cita o parâmetro de papel (constituição §6 No CI (config), powersync §Config/§Deploy, 0029, e2e-local §1; constituição 1.1.17). Handoff do repo de stack PowerSync de um cliente — repo sem CI nenhum, onde nasceu um bug de sync rule que só não foi a produção porque o e2e o pegou por acaso, um dia antes do deploy. Já era norma (feito no ciclo anterior): a pegadinha do IN, "healthcheck verde não atesta config válida", o grep de "type":"fatal" no pré-flight e o pin da imagem. Novo: (1) cláusula na §6 — repo que não tem código mas é deployado (stack self-hosted, powersync.yaml de cliente, compose com config em volume) tem gate em todo push, porque o artefato dele é a config: lint das formas que a tag pinada rejeita + smoke de carga (sobe a imagem pinada só para ler a config, reprova pelo log, não pelo healthcheck; sem Postgres/Mongo — a validação acontece no carregamento). Repo de config não usa o pipeline.yml de app; o que validar é da página da tecnologia — receita concreta em powersync §Deploy, com a ressalva de que a lista do lint é datada pela tag (subiu o pin e a forma passou a valer, a regra sai: lint que sobrevive à versão que o motivou vira superstição). (2) Fail-closed por construção: toda query de um bucket gateado por papel cita o parâmetro de papel — query nova sem o filtro não quebra nada e entrega o dado a quem não tem o papel (fail-open por esquecimento); é regra estática, cobrada no lint, e cobre parte do que ninguém prova em runtime hoje (0029 ganhou a obrigação no lado do dado). Reconciliado: powersync §Deploy e e2e-local §1 diziam que o tier local/ é "o único lugar onde a config é carregada de verdade" — com o smoke passa a ser compila (smoke) × filtra o que devia (e2e); deixar como estava faria a doc mentir sobre o gate novo. Sem template de workflow (deferido, REGRA Nº 3): só um repo exercitou a forma — quando o segundo tenant repetir o esqueleto, reavaliar um pipeline-config.yml rastreado.
  • JaCoCo: o micronaut-serde 3.x GERA serializer por tipo — a doc dizia o contrário; e "endpoint é config, nunca constante" sobe à constituição §6 (java-micronaut §Testes, constituição 1.1.16). Feedback de app. Correção de FATO na doc: o bullet de código gerado afirmava que "o micronaut-serde é dirigido pela introspecção, não gera *Serializer por tipo" — verdadeiro na era 2.x, falso desde a 3.x: verificado por javap no micronaut-serde-processor 3.0.0, que tem o pacote sourcegen e a classe SerdeSourceGenClassNaming com PREFIX = "Serde" + SERIALIZER_SUFFIX/DESERIALIZER_SUFFIX, emitindo Serde<Pacote_Tipo>Serializer/…Deserializer top-level (a 2.15.1 do mesmo cache não tem sourcegen). Como são top-level, não casam nenhum dos marcadores $… da nossa lista — foi por isso que a lista ficou incompleta e dois apps independentes reportaram as Serde* dominando o ranking de não-coberto (uma delas mascarando ~13 pontos de %). Norma corrigida: os excludes levam **/Serde* além de **/*$Introspection*, **/*$Definition*, **/*$IntrospectionRef* e **/*$Intercepted*. Elevação: endereço de serviço externo é CONFIG, nunca constante — base URL, endpoint e JWKS em property/--dart-define com default de produção — passa a ser cláusula da definição de pronto (§6), ao lado do seam de config, porque vale cross-stack e era o que impedia testar o transporte de produção.
  • PowerSync: a FONTE (replicação lógica do Postgres) ganha dono no central; Testcontainers com flag de servidor (powersync §Fonte, coolify §Banco, java-micronaut §Testes). Handoff de cobertura do bi-comercial-xls: a peça de pior cobertura do repo administra o slot de replicação lógica que o PowerSync consome — e o central nunca documentou a fonte (grep por replicação lógica/slot/ wal_level em docs/ = zero; só existia um meio-bullet sobre ALTER ROLE … REPLICATION no Coolify). Conhecimento sem dono, do tipo que já mordeu a casa (cache do nginx, container: do CI). Registrado, com os requisitos verificados na doc oficial do PowerSync: wal_level = logical é flag do servidor (vale para todos os bancos do postgresql18 e exige restart — decisão de plataforma, não ajuste de app), publication de nome fixo powersync (FOR ALL TABLES só em dev), role com REPLICATION/BYPASSRLS/SELECT, e o slot como peça frágil: ausente = replicação parada (sync não chega, o app não acusa erro) e inativo = WAL retido, enchendo o disco do servidor compartilhado — slot órfão de consumidor morto é incidente de plataforma, não do app. Reset de slot é destrutiva (runbook, nível 5). E, no lado do teste: alvo que exige flag de servidor sai do singleton — container próprio com withCommand(... -c wal_level=logical), com a flag espelhando produção (ligar no singleton mudaria o ambiente de toda a suíte e faria o verde atestar um servidor que não existe). Nada mais do handoff era gap central: a "nota de ambiente" (@Tag("docker") atrás de -PdockerTests, "aceito hoje") contradiz norma vigente — é a cobertura órfã proibida em ci-testes/java-micronaut, com guarda no pipeline.yml, e a razão histórica (dind do runner Forgejo) morreu na 0027; "lógica só exercitada de raspão pelo e2e" e "cobertura não é meta de número" também já são norma.
  • Micronaut: default de placeholder com URL precisa de crases (bug shippado na nossa receita), transporte próprio testa com fake em porta efêmera e URL de externo nasce em property (java-micronaut §Testes/§Armadilhas, ci-testes, e2e-local, templates/pipeline.yml; constituição 1.1.15). Handoff de cobertura do central-backend. Achado principal (bug nosso): a receita do seam de JWKS mostrava ${AUTH_FIREBASE_JWKS_URL:https://…} sem crases — e o DefaultPropertyPlaceholderResolver re-parseia um default que ainda contenha : como nova expressão (ESCAPE_SEQUENCE = (.+)?: entre crases). Medido em harness com micronaut-inject 5.0.4 real e property ausente: sai //www.googleapis.com/x/y (o https: vira nome de property) e ${DB_URL:jdbc:postgresql://localhost/x} sai //localhost/x. Com a env setada o default nem é usado — por isso passou despercebido: quebra só no fallback (dev, teste, deploy sem a variável). Corrigidas as duas cópias da receita (java-micronaut, e2e-local), armadilha nomeada ao lado da do placeholder aninhado, e a guarda do pipeline.yml estendida: bloqueia default com :// sem crase, avisa nos demais : (cascata ${A:B:padrao} é legítima — confirmado no mesmo harness). Guarda exercitada em 11 cenários rodando a cópia extraída do YAML. Norma generalizada: URL de serviço externo vive em property com default de produção, nunca em constante — o seam deixa de ser privilégio do validador de JWKS (constante hardcoded é o que impede testar o transporte). Norma nova: transporte próprio (SMTP por Socket, HTTP à mão) testa-se com o servidor fake na própria JVM em porta efêmera (ServerSocket(0), com.sun.net.httpserver), sem docker — cobre ordem do diálogo, um RCPT TO: por destinatário, dot-stuffing, headers, 5xx e porta fechada. E o princípio que faltava (ci-testes): e2e como única prova de um componente é gap, não cobertura — o inverso do "escreva no nível mais baixo". Já era norma da casa (só ack): excluir o gerado do denominador do JaCoCo — a lista de marcadores ganhou **/*$Intercepted* (AOP).
  • Flutter: fakeAsync para Timer/debounce, dev_dependencies de transitiva e contrato de arquivo estático (flutter §Testes, 0029 §Consequências). Dois feedbacks de app, ambos de stack. (1) O seam do debounce é a ZONA, não um parâmetro — o fakeAsync intercepta o Timer hardcoded da produção, logo não se injeta duração só para testar; verificado em sandbox com fake_async 1.3.3 (elapse(299ms) não dispara, o milissegundo seguinte dispara). A armadilha do callback async tem duas faces, e o feedback só citava uma: sem await no retorno, nada depois do primeiro await roda e o teste passa vazio (falso-verde — o pior caso); com await, o Future nunca completa e o teste trava até o timeout (as duas provadas em sandbox). (2) Package importado em test/ vai em dev_dependencies mesmo vindo transitivo — regra nomeada, vale para matcher/collection, não só fake_async: cadeia confirmada na fonte (depend_on_referenced_packages está no lints/core.yaml que o flutter_lints inclui; acusa em info; flutter analyze --help da 3.44.0 mostra --fatal-infos default on) → import que compila derruba o gate. (3) Teste de contrato de arquivo estático lê o arquivo real do disco (dart:io), nunca uma cópia em test/ — fixture duplicada testa a fixture e o artefato que vai ao ar drifta em silêncio (mesma família do "fixture capturada, não inventada", §6) — e afirma o conjunto de chaves, não os textos: amarrar rótulo de UI ao label do contrato faz de corrigir uma palavra na tela uma mudança no contrato do consumidor. A 0029 ganhou o ponteiro no lado do dono (o catálogo é artefato de deploy: trave-o por teste).
  • PowerSync: sync rules válidas ≠ serviço saudável — o pré-flight passa a ler o log (powersync §Config/§Deploy, e2e-local §1, templates/xadm-release-skill.md; constituição 1.1.14). Feedback de app, achado no e2e antes de um deploy: 15 queries de gate por papel iam para produção inválidas. Duas pegadinhas registradas. (1) auth.parameter('x') IN (…lista literal…) não existe — o README do packages/sync-rules só aceita <coluna> IN auth.parameter('array') e auth.parameter('x') IN <coluna array JSON>; parâmetro contra lista de literais é rejeitado como fatal, e a forma que vale é igualdade encadeada com parênteses. Correção ao feedback: a regra não é "IN é proibido" — id IN auth.parameter('ids') é a forma canônica e continua valendo; a versão genérica mataria o uso certo. (2) Sync rule inválida não derruba o serviço — sobe, responde /probes/liveness 200 e só loga o erro (as probes documentadas cobrem "serviço vivo", e o changelog do serviço confirma que erros de compilação ficam persistidos pós-deploy); o sintoma chega horas depois como "não sincronizou nada", com cara de bug de app. Logo o docker compose up --wait não é o gate inteiro: o bloco Docker Compose do pré-flight ganhou o passo de ler o log da stack de pé (docker compose … logs bare + inspeção por Read/Grep, nunca pipe) e reprovar em "type":"fatal"/"errors" — escrito genérico ("serviço que carrega config em runtime"), não só PowerSync. E o e2e-local §1 fixa que config carregada em runtime só é validada no tier local/ (mesma imagem pinada, config do cliente) → mudou sync rule, roda o e2e daquele cliente antes de taggar. Correção à sugestão operacional do feedback: grep em pipe fura o allow-list — a doutrina da casa é comando bare + inspeção pela ferramenta. Achado extra: npx powersync validate roda em self-hosted e dá linha/coluna do erro no YAML — registrado como atalho do dev, nunca substituto (quem decide é o binário da tag que produção roda).

Alterado

  • Subject do commit de release do central passa a carregar as duas versões — chore(release): vX.Y.Z site — C.C.C constituição (era só chore(release): vX.Y.Z). As duas versões são desacopladas (§5) e o git log sozinho não dizia qual release de site carrega qual constituição — agora diz, sem abrir o CHANGELOG. Sem risco de CI: o único consumidor do subject é o decide do templates/pipeline.yml, que casa prefixo ancorado (^chore\(release\)). Só o central — templates/xadm-release-skill.md (app) segue chore(release): vX.Y.Z, porque app não bumpa constituição.

0.37.0 - 2026-09-04 (constituição 1.1.13)

Adicionado

  • Gate de numeração de seção — checa-numeracao.py (constituição §6, build-site.yml, pré-flight da /xadm-release). Bug meu, achado ao renumerar a e2e-local.md: a página carregava duas seções ## 12. havia meses e nenhum gate acusava — pro mkdocs --strict são dois headings válidos com slugs distintos, o checa-nav olha arquivo órfão e o checa-admonition olha marcador cru; foi achada no olho, o gate que a §6 diz não servir (mesma classe do admonition indentado errado, item 92). O checker (stdlib) exige, na página que numera seções, sequência 1..n sem duplicata e sem salto, por nível de heading; ignora heading dentro de bloco de código (a doc exibe markdown de exemplo) e pula página sem numeração. testa-checa-numeracao.py cobre 9 casos e mata os dois mutantes óbvios (tirar o skip de cerca; achatar os níveis). Central-only, não publicado — a convenção de seção numerada é das páginas longas daqui; app não tem norma de numeração (decisão revisável se surgir caso).
  • Terceiro tier de e2e: staging/ — dado real de produção, avaliação humana (e2e-local §1/§2/§11, constituição §6, ci-testes). A casa passou a ter três tiers: local/ e native/ são zero interação humana (um comando, exit 0/1, schema fresco) e o staging/ é explicitamente manual — builda, sobe e para, para provar o que os outros dois não provam: a migração aplicada sobre o dado real (constraint violada por linha antiga, NULL onde o código novo assume NOT NULL) e a jornada que só um humano julga. Não roda em CI, não propaga veredito. Não reabre o "staging" que a §6 veta — o vetado é ambiente deployado + clique de operador; este roda local/docker e toca produção uma vez, em leitura. Regras obrigatórias registradas: prod só-leitura no pull; dump cru é dado de produção em disco local (gitignored, apagado no fim do mask, nunca commitado, não sai da máquina — o artefato reutilizável é o seed mascarado); anonimizar antes de qualquer app subir, incluindo segredo de provedor (DSN, chave de analytics, id de recurso de deploy); guarda do mask em duas camadas (heurística anti-regressão + fail-closed em coluna nova não declarada, que é o que fecha o drift de schema); guard de prod fail-closed lendo estado efetivo (config + env herdada + env de cada app), nunca string montada de constante local; builds-latest/manifesto.md versionado; e achado automatizável desce para o tier automático — o manual não acumula cobertura própria. A §2 passa a mostrar o layout etc/tests/<tier>/<suite>/, e a numeração das seções deixa de ter duas §12.
  • Norma: edição em lote por script escreve BYTES, e o fechamento confere o git diff --numstat (engenharia/agentes). Achado na própria sessão: reescrever .md com pathlib.write_text no Windows troca a EOL do arquivo inteiro, e o CLAUDE.md — com duas linhas de mudança real — saiu como +3080/−3080. A causa de o git não absorver isso sozinho apesar do core.autocrlf=true: o arquivo tinha um CR solitário (CR sem LF), e nele o git desliga a normalização de EOL — o vizinho sem CR solitário não reproduz o sintoma. O CR solitário veio do próprio agente, ao escrever a sequência de duas letras num heredoc (numa linha que citava tr -d de CR); corrigido no acervo. A norma registra as duas defesas (montar a barra em runtime; conferir o byte, não o grep num pipe) e torna o numstat verificação de fechamento.
  • Duas decisões novas de auth de usuário no central + as normas §6 do staging de acesso (0029, 0030, constituição §7, seguranca, java-micronaut, flutter, powersync). [0029] — o app declara seus papéis (roles-catalog.json servido por ele; JSON estático no Flutter web, rota anônima no Micronaut) e o central-backend ingere por upsert: papel sumido inativa (não apaga — grants sobrevivem) e o sync reporta o que inativou; catálogo é público, logo só role+label; o ingest exige app_id casando o alvo, https/host do production_url, timeout e limite. Mata a curadoria manual, que era a fonte do drift (papel novo fora do picker, papel morto ofertável). [0030] — todo app que autentica pelo central usa um projeto Firebase (o da X-Adm): habilitar provider é config única — Google e Apple já em produção —, app novo registra app no projeto existente. Consequência que faltava estar escrita: iss/aud identificam o projeto, não o app ⇒ autorização por app vem sempre do grant, nunca do token; e Apple manda nome só no 1º login + e-mail podendo ser relay ⇒ identificar por subject, UI tolerante.
  • Normas de acesso/UI vindas do staging: superfície /api/** de app com UI em outra origem nasce com CORS, com origens em env (<APP>_CORS_EXTRA_ORIGINS), nunca hardcoded nem * com credencial (java-micronaut §Específico + piso §Authn); negativa de acesso carrega o contato do admin no corpo do 403 (usuário pendente não tem JWT para consultar) e pendente ≠ rejeitado; tela de acesso do Flutter mostra id/nome/versão, rota administrativa entra no menu, e a UI tolera nome ausente/e-mail de relay; o JWT de sessão grava-se SÍNCRONO no login — o credential-provider do PowerSync lê, não é o único a escrever (era 401 por corrida).
  • Login social no Flutter web é POPUP, e os headers de cross-origin isolation saem (flutter §Deploy web). O signInWithRedirect trava o boot sob COEP: require-corp (iframe de auth bloqueado ⇒ tela branca). Verificado na doc do PowerSync: a partir do SDK Dart/Flutter 2.2.0 o OPFS roda em Chrome/Firefox/Safari e "these headers can be removed" — cura é subir o SDK e tirar COOP e COEP, não o meio-termo COOP: unsafe-none com COEP mantido, que perde o isolamento (exige COOP same-origin e COEP) e mantém o custo. Se o COOP tiver de existir, same-origin-allow-popups; restrict-properties não é opção (Chrome em hold desde abr/2025).
  • Piso do ArchUnit vira guarda de CI + a armadilha do @Requires(property=…, notEquals="") (templates/pipeline.yml, engenharia/java-micronaut). A norma do piso (ASM ≥ 9.8 / ArchUnit ≥ 1.4.1 em Java 25) e a defesa anti-vácuo (isNotEmpty sobre o import) existiam desde o item 113 — nada as verificava, e um repo de app ficou meses com a guarda de fronteiras dormindo verde (ArchUnit abaixo do piso pula classe que o ASM não conhece: a regra roda sobre 0 classe e passa vácua). Guarda nova no job gate: resolve a versão do ArchUnit (coordenada literal ou alias do libs.versions.toml) e reprova abaixo do piso — fato objetivo, ::error::; a ausência de asserção anti-vácuo na suíte só avisa (::warning::) — heurística, a defesa pode estar escrita de outro jeito. Versão não resolvível vira nota + segue (backstop humano no §3b da /xadm-docs).

  • Extração de texto em Dockerfile = CR-safe + gitattributes-flutter (constituição §3, templates.md, app-novo, /xadm-docs, engenharia/flutter). A cobertura de CRLF da casa era toda gradlew-específica (item 97: shebang #!/bin/sh\r); faltava nomear a classe — CRLF quebra qualquer extração de texto por shell (sed/grep/awk) de arquivo versionado num docker build, não só o gradlew. Achado de app: o Dockerfile web do central-ui extrai a versão do pubspec.yaml por sed sem filtrar \r → quebra em checkout Windows (CRLF); CI Linux passa, docker build local no Windows quebra — falha frágil, invisível na CI (o irmão bi-comercial já lia o version.json gerado, CR-safe). A receita web do central usa jq (tolerante a CRLF) e não shippa o bug; a norma cobre o próximo app. Cura em duas pontas (precedente item 155): cinto | tr -d '\r' no comando + suspensório novo template rastreado templates/gitattributes-flutter (pubspec.yaml text eol=lf), par Flutter do gitattributes-java. Nota na receita Flutter §Config, princípio nomeado em §3.

  • Extração de texto do site vigente à prova de cache-CDN na /xadm-docs (templates/xadm-docs-skill). O passo 1 tratava constituicao-versao.txt como fonte única da versão vigente; o CDN serviu essa .txt stale (1.1.4) enquanto o manifesto.json veio fresco (1.1.8) — sem defesa, o early-exit "em dia" dispararia na .txt stale e mascararia a defasagem inteira (gate JTE incluso). Cura em duas pontas (item 155): cache-bust obrigatório (?cb=<ts> + Cache-Control: no-cache) nos dois curls + cross-check .txt vs campo constituicao do manifesto.json (mesma publicação, carimbo autoritativo — item 151); divergência = aborta alto, nunca reporta "em dia" pelo menor.

  • CLI Python: bare primeiro, python -m <mod> no fallback (engenharia/ferramentas, publicar-docs, /xadm-release). O entry-point bare (mkdocs, gates .py) nem sempre resolve no PATH — Windows com Python da Microsoft Store não põe o Scripts\ no PATH → mkdocs --version dá "command not found" mesmo instalado. A /xadm-release roda mkdocs build --strict local antes de pushar; bare-só trava a cerimônia numa máquina Windows (ou pula o gate em silêncio). O allow-list já lista python -m mkdocs *; faltava a prosa mandar preferir a forma-módulo quando a bare não resolve. Regra genérica em ferramentas §allow-list, nota em publicar-docs §Ambiente local, parêntese no gate da /xadm-release.

Alterado

  • auth → central-backend em toda a doc (constituição §7, central-de-apps §Papéis, ADRs 0005/ 0008/0022, powersync, java-micronaut, coolify, garage, publicar-docs, /xadm-setup). auth era o nome antigo do mesmo serviço; a tabela §Papéis passa a ser indexada por central-backend.xadm.biz (host legado auth.xadm.biz segue roteado — deploy), e a linha do central.xadm.biz nomeia o central-ui e registra que são origens distintas (a razão de o CORS existir).

Corrigido

  • @Requires(property=X, notEquals="") não exige a property — ausente ele PASSA (engenharia/java-micronaut §Armadilhas + §Específico). Verificado na fonte (MatchesPropertyCondition.matches, micronaut-inject 5.x): property ausente e sem defaultValue, o ramo NOT_EQUALS devolve true — o bean sobe e o @Value sem default só estoura quando o bean é instanciado (singleton é lazy) → 500 no primeiro request, com o startup verde. O idiom só entrega "setada e não-vazia" com a chave declarada no application.yaml (mesmo vazia). Regra: feature opcional guarda-se com pattern=".+" (já era a receita da casa, agora com o porquê e o contraste). Sem gate: cobrar "property declarada" exigiria caminhar YAML aninhado — ponto cego declarado.

0.36.4 - 2026-09-03 (constituição 1.1.8)

Adicionado

  • View JTE em app native: reflect-config auto-gerado + hard gate (constituição §5, 0025, java-micronaut, templates/pipeline.yml). A doc prescrevia a lista de reflect-config das templates JTE à mão e a chamava "não drifta" — falso: view nova esquecida = classe podada no AOT = 404 só no native, passando por unit/integração e e2e-native (que amostra telas). Foi 404 real em produção (int-sascar, atencao/incidentes). Martelo: o padrão da casa passa a ser a extensão oficial que auto-enumera todas as templates — jteExtension('gg.jte.nativeimage.NativeResourcesExtension') + dep jteGenerate('gg.jte:jte-native-resources:<versão>') (gera reflection-config.json completo no build); lista à mão proibida. Hard gate novo no pipeline.yml (job gate, escopo JTE+native): reprova se falta a extensão (estático) ou se o reflection-config.json não cobre toda *Generated (output-compare) — belt-and-suspenders porque prod-404 passou por todos os tiers. Corrige o claim "não drifta" em java-micronaut §UI/JTE + 0025.

  • /xadm-release scenario-aware — classifica o diff e roteia a ação (0027/0004, templates/pipeline.yml + xadm-release-skill.md). A skill vira o cérebro: classifica o diff desde a última release e recomenda — docs/auxiliar = não é release (push, sem bump/tag/binário); código = release por tag com trailer Deploy:; só-jar/só-native = release patch do alvo. O decide do pipeline.yml na tag passa a LER o trailer (que a skill já cravava e ninguém lia — a causa do maxsul-pied, em que fix só-docs disparou deploy completo): liga só os alvos do trailer, fallback app.json build.targets quando não há (nunca "sem binário" — Flutter/central seguem deployando); decide com fetch-depth: 0 p/ ler o corpo do anotado. Fix docs-only republica a versão vigente além de dev/ (o site vivo redireciona pra maior SemVer — refina o 0004: doc de versão vira mutável p/ correção de texto), sem rodar publica-versao.py. Corrigida a referência stale build-deploy.yml→pipeline.yml na skill.

  • Higiene de nomes & disciplina de rename da frota (constituição §9, 0024/0026/deploy.md/coolify.md/ glossário). O nome do recurso Coolify — único dos 4 nomes por app sem norma — passa a ser derivável do slug: <slug>-[<instance>-]<target>-pull, kebab-case, -pull marca control-plane, <instance> só em app multi-instância (integrador). O reconcile (0026) casa por regra única ^<slug>-(<instance>-)?(jar|native|web)-pull$ no lugar dos 4 padrões ad-hoc que a frota nem seguia. Instrução determinística de normalização da frota (deploy.md, ação de infra §3 — executada por operador com API do Coolify: lista→computa→rename in-place, uuid estável). Nova seção §Renomear um slug ou recurso em coolify.md: slug rename obriga limpar orphan Garage + reapontar todo ponteiro pro slug velho + re-semear reconcile, no mesmo movimento; guard de orphan central-side declarado FUTURO (§3). Glossário desambigua "slug" (página × app/0024).

  • Migração de banco backward-compatible — norma expand/contract + guarda que avisa (constituição §6, java-micronaut §Flyway). Flyway CE é forward-only e "rollback" na casa = redeploy da imagem anterior; a migration já rodou e não se desfaz, então o schema novo não pode quebrar o app velho. DDL destrutivo (DROP COLUMN/DROP TABLE, RENAME, DROP CONSTRAINT) segue expand (para de usar, schema fica) → contract (dropa na release seguinte, só depois de estável); RENAME em fases (add-new→backfill→dual-write→drop-old). Marcador canônico -- destrutivo-ok: contract; ... no .sql. Guarda nova no pipeline.yml (job gate): avisa (não bloqueia — o marcador é auto-declarado) quando acha DDL destrutivo inequívoco sem o marcador; SET NOT NULL/ALTER TYPE ficam fora (seguros em coluna nova/widening). Elevado à definição de pronto (§6). Satélites: ci-testes (guarda irmã) e coolify §Rollback (o rollback recria a imagem, não o schema). Origem: incidente de prod (DROP de coluna, rollback não recriou). Frota adota via /xadm-docs.

Corrigido

  • Reconcile do control-plane casa por SLUG DE IMAGEM, não por nome do recurso (deploy.md, 0026, constituição §9). Auditoria empírica da Fase 2 em produção (2026-09-03) refutou o modelo: o reconcile lê o docker_registry_image_name de cada recurso e casa contra registry_slug (== app.json slug, 0024) — o nome canônico <slug>-[<instance>-]<target>-pull é derivado/legível, não a chave. Prova: central-ui/onpetro-bi casavam com nome fora da fórmula. Consequência: os 20 renames de recurso foram higiene (legibilidade), não pré-requisito do reconcile — §Normalização remarcada CONCLUÍDA (frota 100% canônica, app_deploy_targets 21/21). Propriedade nova explicitada: casar por slug de imagem desacopla o control-plane do nome do recurso (cutover/rename não quebra o mapa). Coluna registry_slug adicionada ao esquema documentado do app_deploy_targets.
  • Ponteiros stale pro slug velho integrador no próprio central — o rename integrador→integrador-server (0024) deixou 6 links servindo do orphan morto: o exemplo do _importado/ em publicar-docs.md e 5 links aplicacoes/integrador/projeto/ arquitetura/ (integracao.md ×2, powersync.md, 0018, 0007 ×2). Reapontados pro slug canônico. O templates/pipeline.yml ganha bloco comentado de fetch _importado/ (consumidor de contrato descomenta — o --8<-- pendurava/quebrava o --strict sem o step). Sem pin (0014 intacta): fato importado é o atual do dono; estável = versão de API (/api/v1/).

0.36.3 - 2026-09-01 (constituição 1.1.4)

Corrigido

  • tini como PID1 nos Dockerfiles (dockerfile-java + dockerfile-java-native) — sem um init de PID1, o curl do HEALTHCHECK reparenteia pro app (PID1) e vaza curl <defunct> sem teto (116 zumbis medidos numa VM de prod, ago/2026); JVM e binário native não colhem órfão de PID1. tini colhe o zumbi e repassa SIGTERM (shutdown intacto). Guarda nova no pipeline.yml (job gate): ENTRYPOINT em forma exec começa com um reaper de PID1. Nota cruzada em java-micronaut §Deploy, 0022 §Topologia e coolify §Baseline do host (o zumbi era pista falsa de Testcontainers/dind órfão). Frota adota via /xadm-docs + redeploy.

0.36.2 - 2026-08-31 (constituição 1.1.3)

Removido

  • Fábrica de imagens de CI (ci-images) aposentada da superfície viva (constituição 1.1.3). Com o CI 100% GitHub Actions (docs/decisoes/0027-ci-github-actions.md), a toolchain vem das actions (setup-java/subosito/flutter-action) e a matriz-baseline.json não gateia mais nada. Removidos: a pasta ci-images/ (Dockerfiles base/java/flutter, build-push.sh, computar-matriz.py, matriz-baseline.json), o publish de padroes/matriz-baseline.json no Dockerfile do site, e o runbook infraestrutura/fabrica-imagens-ci.md (+ nav). As decisões 0003/0022/0027 ficam como registro histórico (a fábrica resolveu problemas reais; 0027 é a superseding) — a purga é da superfície viva, não do histórico de ADR.
  • Workflows Forgejo legado removidos (constituição 1.1.3): templates/ci.yml, templates/docs.yml, templates/release.yml — superados pelos jobs gate/docs/release-check do pipeline.yml (0027) e agora que o runner Forgejo aposentou (0 apps), saem do manifesto (tirados de RASTREADOS). Varredura das páginas vivas trocando ci.yml/docs.yml/release.yml → pipeline.yml (os ADRs em docs/decisoes/ ficam como história; build-deploy*.yml seguem legado). O pipeline.yml segue rastreado.

Alterado

  • Refs vivas à fábrica removidas (constituição 1.1.3): constituicao.md, glossario.md (entrada "Fábrica de imagens de CI"), coolify.md, forgejo.md, ci-testes.md, java-micronaut.md, publicar-docs.md, app-novo.md, flutter.md, java.md, 0004, e os templates rastreados app.json/xadm-docs-skill.md/xadm-release-skill.md (a paridade Linux de teste OS-desabilitado passa a citar eclipse-temurin:<v>-jdk no lugar da imagem ci-java apagada). Bump PATCH (3 rastreados re-carimbados).

Corrigido

  • manifesto.json: mudou_em fantasma 0.38.0 → 1.0.0 (templates/app.json, xadm-docs-skill.md, xadm-release-skill.md). Os 3 mudaram na migração 0027, carimbados 0.38.0 antes de a constituição ser renumerada de 0.x pra 1.0.0 (mesma leva); os hashes não mudaram depois, então o gera-manifesto preservou o mudou_em histórico — mas 0.38.0 nunca virou release da constituição (renumerada pra 1.0.0). O CHANGELOG rotula a mudança (constituição **1.0.0**), então o passo-2 da /xadm-docs (relevância-por-stack, que procura a string de mudou_em no CHANGELOG) buscava 0.38.0, não achava, e caía no fallback de diff. Alinhado ao rótulo real. Sem bump: correção de metadado, nenhum hash de template mudou (gera-manifesto --check verde). (§6, candidato do Gustavo.)

0.36.1 - 2026-08-31 (constituição 1.1.2)

Alterado

  • pipeline.yml: 3 otimizações de minutos faturados no GitHub Actions (constituição 1.1.2). (A) build_native ganha um passo libera-disco em paralelo (&+wait) antes do build — native-image come ~6 GB e o / cheio arrastava o build até o timeout; (B) cache-from/cache-to do gha ganham scope= por flavor (jar/native/web) — sem o scope os três dividiam um cache só e se evictavam no teto de 10 GB/repo; (C) job docs cacheia os downloads de pip (wheels) + npm via actions/cache (chave = hash do workflow, reinvalida no bump de versão de ferramenta). Não foi adicionado deps-cache-dance no native: mode=max+cache-dance ali já estourou o timeout (exporting to image de stages GraalVM multi-GB) — o comentário do job foi reforçado com o caso. (§6, handoff de app.)

0.36.0 - 2026-08-31 (constituição 1.1.1)

Corrigido

  • Bootstrap de deploy do central invertido (constituição 1.1.1; emenda à 0026, decorrência da 0027). A cláusula original — "o central deploya direto na API do Coolify, sem auto-referência" — ficou inexequível com a CI 100% GitHub (runner de IP arbitrário × API restrita ao IP da casa; a ponte SSH que contornava foi removida). Invertida: o central se deploya pelo próprio /api/ci/deploy (roda dentro da casa → alcança o Coolify; job byte-idêntico ao de qualquer app), com break-glass (central fora do ar → deploy manual pela UI/API do Coolify) como única exceção. Reconciliados 0026 (§Evolução), constituição §9, infraestrutura/deploy.md (§Bootstrap + runbook break-glass). O central-backend removeu a guarda self_deploy_refused (RD local). Também: token do /_sync (0028) renomeado DOCS_SYNC_TOKEN → DOCS_API_TOKEN (alinha a <DESTINO>_API_TOKEN).

Adicionado

  • Docs sync event-driven pelo central (constituição 1.1.0; decisão 0028). Mata o poll de rsync do sync-apps.sh (a cada ~90s, LIST/HEAD massivo no garage — o maior hog de CPU do host, item 134): o publish dispara o sync. O job docs do pipeline.yml avisa o central (POST /api/ci/docs-published, best-effort não-fatal); o central cutuca o endpoint interno POST /_sync?slug= do container docs (busybox httpd em :8080, Bearer DOCS_API_TOKEN); o sync-apps.sh sincroniza só aquele prefixo (função sync_slug + debounce trailing-edge + guarda de slug anti-traversal preservada). O poll de 90s vira backstop (reconcile no boot + poll de 60min). Novo scripts/sync-listener.{cgi,sh} + scripts/testa-sync-apps.sh (guarda de slug, no gate) + delta do Dockerfile e do job docs do pipeline.yml. Fase 2 (endpoint /api/ci/docs-published) = central-backend, outro repo.
  • CI/CD 100% GitHub Actions — pipeline.yml único (constituição 1.0.0; decisão 0027, emenda 0003/0026). Funde ci.yml+docs.yml+release.yml (Forgejo) + build-deploy*.yml (GitHub) num workflow à la carte com 3 variantes provadas (Java native-canary, Java jar-only, Flutter web) por bloco opt-in de app.json build.targets; toolchain pelas actions (setup-java/setup-gradle/ subosito/flutter-action), Testcontainers no docker nativo, deploy control-plane (0026, inclusive web), aviso de falha = notificação nativa do GitHub (email), DOCS_S3_* = secret por repo (conta user-wide, não org). Novo templates/pipeline.yml rastreado; ci.yml/docs.yml/release.yml/build-deploy*.yml ficam legado até 0 apps no runner Forgejo. A fábrica ci-images/0003 é superseded para CI (sem matriz-baseline.json); forgejo.md reescrita (git host + push-mirror + Release-artefato, sem CI), fabrica-imagens-ci.md arquivada; o CI do próprio central migrou (.github/workflows/build-site.yml; .forgejo/workflows/ removido). has_actions=false no Forgejo é passo obrigatório da migração. Reconciliados constituição §2/§5/§6, ci-testes, app-novo, publicar-docs, templates.md, flutter, java-micronaut, stack, versionamento, glossario, app.json/xadm-docs-skill/xadm-release-skill (rastreados). Golden test Flutter normatizado fora do gate (visual regression; local + e2e).
  • Taxonomia de modos de deploy em infraestrutura/deploy.md: dois eixos (-pull off-host no CI × -builder build no host do Coolify) × artefato — Java/Micronaut = jar-pull+native-pull, Flutter = web-pull (control-plane), auxiliar Node (ex. Sulplata HO Scraper) = node-builder, docs = web-builder (os dois -builder ficam fora do control-plane por ora).
  • Guarda ESCRITA_DECLARA_CONSUMES em engenharia/java-micronaut §Guarda de fronteiras: regra ArchUnit para app server-rendered — controller de view (com @View/@Produces(TEXT_HTML)) declara @Consumes nos write-endpoints (@Post/@Put/@Patch), fechando a classe do 415 silencioso no submit de <form>. Escopo estreito (só view; API /api/** puro-JSON fora); cobra a anotação presente, não o media type. Feedback §6.

0.35.1 - 2026-08-30 (constituição 0.37.3)

Adicionado

  • Control-plane de deploy no central-backend (ADR 0026 + §9 nova) — o CI dispara o deploy por API M2M, não por ponte SSH. O deploy off-host passa a ser orquestrado pelo central-backend (POST /api/ci/deploy, token dedicado por filtro próprio em /api/ci/**), que resolve app_id+target → recurso Coolify por um mapa auto-curável (tabela app_deploy_targets + reconcile por convenção de nome, nunca por uuid à mão) e chama a API do Coolify; a ponte SSH + forced-command + allow-list saem. Motivo: o allow-list ficava órfão a cada cutover blue-green (uuid novo criado, uuid velho deletado), recusando a frota inteira — só não quebrou porque todo deploy foi via API direta. Constituição §9 (Entrega / Deploy) nova, docs/decisoes/0026-deploy-control-plane.md, página docs/infraestrutura/deploy.md, emenda a docs/decisoes/0022-native-image-alvo-de-deploy.md (topologia da ponte → ponteiro pra 0026). Bump PATCH constituição 0.37.2→0.37.3.
  • Regra "quem builda decide onde a config de build-time vive" (build off-host / pull-only). Com jar/native buildando off-host e o Coolify só puxando (0.37.0), o build-arg do Coolify morre pro que virou pull-only: config de build-time (ARG, --dart-define do Flutter web) passa a viver onde o CI alcança (ARG default no git / docs/app.json / GitHub secret), env de runtime fica no recurso, e a build-time vestigial no recurso pull se limpa. Constituição §5 (corolário), coolify.md §Build Arguments (reescrita pros dois regimes: Coolify-builda × pull-only + receita de migração mover+limpar) + checklist, flutter.md. Bump PATCH constituição 0.37.0→0.37.1. (§6, feedback de app.)
  • Alvo web do build off-host — templates/build-deploy-web.yml (Flutter web → nginx). Provado por 3 repos (central-ui, vantroba-bi, onpetro-bi) com o workflow byte-idêntico exceto o slug → templatizado (par do build-deploy.yml do server, single job). Builda off-host no GitHub, publica :web-amd64, dispara a ponte SSH com o UUID lido de docs/app.json deploy.web.coolify_uuid (≠ do server, que usa secret). O deploy do app.json passa a ser lido pelo web (era só forward-looking); scaffold app.json + contrato publicar-docs, flutter.md (receita web→nginx), templates.md, mapa da /xadm-docs, coolify.md. Dockerfile web fica como receita por app (o fluxo de sourcemap difere GlitchTip×Bugsink — não templatizado). Bump PATCH constituição 0.37.1→0.37.2. (§6, feedback de app.)
  • Armadilha java-micronaut §Banco — field-add em record @MappedEntity é breaking no ctor canônico posicional (o Micronaut Data binda esse ctor; somar campo quebra todo new Entity(...), 27 call-sites num piloto, maioria seed). Prescreve factory de test-data pra colapsar o raio a 1 lugar (norma, cross-link §Testes) + tática de migração pontual (sed no rabo uniforme). Página dev, sem bump. (§6, feedback de app.)

0.35.0 - 2026-08-29 (constituição 0.37.0)

Alterado

  • Build off-host vira o default da casa (jar E native). O shadowJar ainda buildava no host de produção (Coolify Build Pack Dockerfile, docker build na VM) — o mesmo build-spike (CPU + I/O + build-cache +13,4 GB/dia) que foi a face do freeze e que o native já evitava. Agora jar e native buildam off-host no CI (build-deploy.yml, jobs por alvo) e o Coolify só puxa (Build Pack Docker Image); auto-deploy no push sai. Deploy passa a ser release-driven: a /xadm-release crava o trailer Deploy: <alvos> na tag por diff de path, o workflow builda só os alvos, a ponte SSH dispara. Constituição §5, 0022 §Topologia (revista in-place), coolify.md, java-micronaut.md, app.json (build.targets). POC thoms-webstorm (jar off-host 3m13s, zero CPU/disco na VM). O template do workflow foi renomeado build-native.yml → build-deploy.yml (o nome antigo enganava — buildava jar num "build-native").
  • HEALTHCHECK: tolerância a boot-longo mora nos retries, não em start-period generoso. Migração Flyway grande (~3min) estourava o healthcheck no boot → rollback. Cadência nova nos dois Dockerfiles: start-period 5s + interval 15s + retries 15 (≈225s) — boot rápido promove no 1º check, boot lento é coberto pelos retries (o Coolify espera a janela start-period inteira). Split /health/live+/ready rejeitado (Micronaut roda Flyway antes de aceitar conexão; liveness flat + retries resolve). versionamento.md, java-micronaut.md.

Adicionado

  • Contrato app.json: build.targets (default estático dos alvos off-host — [native]/ [jar]/[jar,native]) + deploy (mapa por-alvo host/uuid, forward-looking multi-host). publicar-docs.md, templates/app.json.
  • coolify.md §Canário e troca blue-green — lições de 503 da POC: canário nunca por proxy/dynamic/*.yaml à mão (ação de recurso zera o arquivo); blue-green transfere o fqdn pro novo antes de deletar o antigo (ordem inversa órfã router+cert).

0.34.7 - 2026-08-28 (constituição 0.36.5)

Corrigido

  • Receita do flavor no /health (versionamento.md §Receita) lia per-app: dizia "o flavor opcional detecta-se em runtime: System.getProperty(...)" como se cada app o adicionasse — quando /health é servido pelo HealthController do xadm-comum-web (contrato flat único da casa). flavor é campo desse controller: acrescentá-lo é mudança na lib, feita uma vez e herdada por todos os apps — a única exceção era esse campo (todo o resto do contrato de /health já era edição da lib). Esclarecido em versionamento.md §Receita (a detecção mora na lib, não em cada app) e em java-micronaut.md §Específico (o flavor é campo do HealthController, cross-link à §Shape mínimo). Páginas dev, não rastreadas → sem bump.

0.34.6 - 2026-08-28 (constituição 0.36.5)

Corrigido

  • Ponte SSH de deploy native (templates/build-native.yml): a chave privada DEPLOY_SSH_KEY gravada com CRLF/BOM — o que o PowerShell no Windows faz (Get-Content -Raw | gh secret set converte LF→CRLF) — chega ao runner com \r em cada linha e o ssh falha com error in libcrypto / Permission denied (publickey). Como o Deploy é o último step, o build passa e só o deploy quebra (imagem publicada, recurso native não sobe) — mordeu prod (onpetro/vantroba, ago/2026). O step passou a filtrar \r (printf … | tr -d '\r'), imune à origem do secret; de carona, o secret vai por env: (não expande a chave crua no run:) e é materializado em $RUNNER_TEMP/deploy_key (efêmero). Onboarding LF documentado (Git Bash, não PowerShell) no cabeçalho do template e em coolify §Atualizar. Constituição 0.36.5.

0.34.5 - 2026-08-28 (constituição 0.36.4)

Adicionado

  • Guarda de paridade Dockerfile/Dockerfile.native no templates/ci.yml: app native carrega os dois arquivos (0022 §Topologia) e os passos de filesystem/owner sob /app (mkdir -p /app/<x> && chown app:app /app/<x> antes do USER app) têm de bater nos dois. Dir criado só num flavor = AccessDeniedException só naquele deploy (USER app num /app do root; build e teste passam) — bug real do /app/logs no bi-comercial-xls (ago/2026), que nasceu no Dockerfile.native sem o mkdir que o JVM já tinha. A guarda compara o conjunto de dirs /app/<x> em mkdir/chown, ignorando COPY (paths de build divergem legitimamente) e comentário. Espelhado no /xadm-docs §3b (sweep de app que já diverge) e em nota de paridade nos dois templates + java-micronaut §Deploy. Constituição 0.36.3.

Alterado

  • Deploy native: publish-only → publish + gatilho via ponte SSH. Com Build Pack Docker Image o Coolify não puxa a imagem nova sozinho e não há webhook de repo (não é push de código) — publicar deixava a imagem no registry e o recurso rodava a velha até redeploy manual. O templates/build-native.yml ganhou um step Deploy que faz ssh numa chave forced-command na VM da casa: o wrapper só aceita deploy <uuid> (whitelist dos recursos native, sem shell, lista por vírgula p/ app multi-instância) e faz o curl na API local do Coolify; o token do Coolify fica na VM, fora do GitHub (postura mais forte que expor COOLIFY_WEBHOOK_URL). 0022 §Topologia reescrito (supera "não deploya"); receita da ponte em coolify §Atualizar. Constituição 0.36.4.

0.34.4 - 2026-08-28 (constituição 0.36.2)

Adicionado

  • Gate native PRÉ-TAG no pré-flight da /xadm-release (feedback §6 de app): app com Dockerfile.native builda o Dockerfile.native (docker build -f Dockerfile.native), não o JVM, como gate de container. Roda nativeCompile num container limpo antes da tag e fecha duas frestas que o docker build JVM não fecha: (1) compila native pré-tag — o check não roda nativeCompile e o build-native.yml só dispara na tag, então falha só-de-native aparecia pós-tag (verde local, vermelho pós-tag); (2) resolve toda dep pelo registry (sem mavenLocal) — uma dep -SNAPSHOT local que o e2e não pega falha aqui. Reconciliado em java-micronaut §Native-image, 0022 §Gate, templates.md, e2e-local §1.
  • Armadilha OpenAPI: metadata usada como nome de arquivo tem de ser ASCII/fs-safe (feedback §6): @Info(title=…) em pt-BR (acento/em-dash) vira path em META-INF/swagger/ pelo micronaut-openapi → nativeCompile quebra; o build JVM passa (check não compila native), a falha só aparecia no workflow native pós-tag. Regra: title/name de tag/schema = ASCII estável; description segue Unicode/pt-BR (não vira artefato). Não reabre o falso rastro do Unicode-em-render (item 146). Em java-micronaut §Armadilhas.
  • /health ganha flavor opcional (native|jvm) (feedback §6): server que deploya JVM e native expõe o que roda de fato (System.getProperty("org.graalvm.nativeimage.imagecode") != null → native), fechando metade do ponto cego "app mostra versão, não se é native nem o digest" (a outra metade, o digest, o app não conhece — confere-se no log do Coolify). Em versionamento §runtime.
  • Cobertura Flutter visível no ci.yml (feedback §6): o gate Flutter roda flutter test --coverage + resumo portável do lcov.info que imprime o % (sem depender de lcov na imagem), honrando a norma "o gate imprime o %" (ci-testes §Definição de pronto), antes só cumprida no Java (JaCoCo). Alinhadas as listagens em ci-testes e app-novo.

Corrigido

  • Regra da tag de imagem native no Coolify (feedback §6): o recurso native puxa <slug>:native-amd64 (== 0024), :latest proibida (apontar pra :latest puxava imagem diferente da recém-publicada), tag :<sha> = auditoria imutável, conferir o digest no log do deploy. Em coolify.md.

0.34.3 - 2026-08-26 (constituição 0.36.1)

Adicionado

  • /xadm-docs passo-2 ganhou fallback obrigatório (feedback §6 de app): CHANGELOG stale/sem carimbo (o site publicado atrasa o bump da constituição; ou entrada antiga) → diffar o template (raw vigente × local) e julgar pela mudança real, não pela prosa. Antes, não achar a string da versão mudou_em mascarava defasagem como "não é minha stack".
  • Convenção — heading de site carimba (constituição C.C.C): a /xadm-release (central) passa a sufixar cada heading do CHANGELOG com a versão da constituição vigente, com gate no pré-flight (§8). Sem o sufixo a versão da constituição não aparecia em lugar nenhum do CHANGELOG e o passo-2 do /xadm-docs não mapeava um template mudou_em à sua entrada. Documentado em engenharia/versionamento.md §Duas versões.
  • /xadm-docs §3c ganhou o tipo (C) — migração de stack por ADR/norma central (ex.: views em JTE, docs/decisoes/0025-*.md; micronaut-data; deploy native) adotada sem RD-ponteiro local vira candidato de autoria: cobra um RD curto que aponta o ADR central e data a adoção neste app (não re-litiga a decisão da casa). Mesmo guardrail heurístico do tipo (B).

Corrigido

  • Guard "Integration no gate" do ci.yml dava falso-positivo em app native (feedback §6): excludeTags("native") — a suíte e2e native roda no build-native.yml, não no check — era tratado como cobertura órfã. O guard passou a parsear os literais de tag e só dispara em tag integration-class (integration/docker/it/e2e); native e qualquer outra tag não contam. Sandbox de 9 cenários. O guard do item anterior era anterior à norma de deploy native.
  • Backfill dos headings sem carimbo da constituição: ## [0.33.0] (constituição 0.35.0) e ## [0.34.0]/[0.34.1]/[0.34.2] (constituição 0.36.0) — verificado pelo frontmatter no commit de cada release. Fecha a lacuna do passo-2 do /xadm-docs para os templates alterados em 0.35.0/0.36.0.

Alterado

  • templates/dockerfile-java-native pina o builder por digest de índice (feedback §6, supply-chain): antes só o runtime (debian:12-slim) era pinado por @sha256; o builder (native-image-community:25-ol9) era tag móvel. Num deploy native o compilador é parte da cadeia de suprimento do binário (builder móvel = binário não-determinístico; adulterado = binário adulterado). Agora builder e runtime pinam o manifest list (índice multi-arch) via docker buildx imagetools inspect <ref>:<tag> → Digest: — corrigido também o comando do runtime (docker pull dava o digest da plataforma do host, não o do índice).

0.34.2 - 2026-08-26 (constituição 0.36.0)

Corrigido

  • Norma /health+/info flat sob native: o controller flat é INCONDICIONAL, não guardado por property (java-micronaut §Native, §Específico e nota do Dockerfile). O guard por @Requires(property="endpoints.health.enabled", value="false") documentado na 0.34.1 (fix da xadm-comum-web 0.6.0) não sobrevive ao AOT: um @Requires de classe num @Controller é avaliado em build-time e a rota é podada sob native — vale tanto para missingBeans quanto para property (provado no canário native jar×native — o jar registrava o flat, o native não → /health sem versao, /info 404). Fix definitivo (xadm-comum-web 0.6.1): HealthController e InfoController incondicionais (sem @Requires), então a rota entra sempre no grafo do AOT; o app desliga os dois endpoints de management (endpoints.health.enabled: false e endpoints.info.enabled: false) pra o flat incondicional não colidir com o management (default-on → duas rotas GET /health → 400).

0.34.1 - 2026-08-26 (constituição 0.36.0)

Adicionado

  • Infra de views JTE compartilhada no commons + fix native de observabilidade (feedback de app; dobra no ciclo da aposentadoria do Thymeleaf, 0025). Registrado em java-micronaut §UI/JTE, na nota "Evolução observada" da 0019 e no baseline de libs (constituição §5):
    • Formata (xadm-comum-util 0.2.0) — formatadores null-safe de exibição pras views JTE (trim/data/abrevia, @import br.com.xadm.comum.util.Formata), substituindo os #temporals/#strings que o JTE não tem; a frota @importa em vez de forkar um Fmt por app.
    • XadmViewModel (xadm-seguranca 0.6.0) — o ViewModelProcessor de referência que injeta os globais do kit/layout.jte, opt-in por xadm.views.app-nome e tolerante a model imutável; o app apaga o seu GlobalViewModel local. Mora em seguranca (lê AuthSupport/AuthSettings; comum-web seria circular).
    • /health + /info flat idênticos jar↔native (xadm-comum-web 0.6.0) — o guard dos controllers flat passou de @Requires(missingBeans=…) (build-time, não sobrevive ao AOT quando a definição do bean existe no classpath) pra @Requires(property=…) (runtime); novo InfoController flat (GET /info→{versao}), com o management do Micronaut — frágil sob AOT — fora do caminho. Norma em java-micronaut §Native, cross-link 0022.

0.34.0 - 2026-08-26 (constituição 0.36.0)

Adicionado

  • ADR 0025 — views server-render usam JTE (compilado), não Thymeleaf. Sob native (0022) o Thymeleaf/OGNL reflete todo tipo de view (whack-a-mole ilimitado, drifta, falha em runtime — 15 tipos / 3 rebuilds no piloto int-sascar + o MensagemM2m da lib). JTE compila o .jte em classe Java: type-check no build + native com reflexão FECHADA (só as classes geradas gg.jte.generated.precompiled.*, registradas à mão em reflect-config.json / reachability-metadata na lib; no modo generate() não há .bin — o conteúdo vai inline no .java gerado — logo sem tracing-agent; não drifta). Mantém o princípio da 0010 (UI server-render unificada), troca o motor. Rocker descartado (manutenção parada + XSS menos content-aware). Kit da casa vira JTE (kit/layout.jte + headerRight.jte). A norma "@ReflectiveAccess em todo tipo de view" (abaixo) fica legado (superada por JTE).
  • Três normas de native-image em java-micronaut §Native (feedback §6 do e2e native de app): (1) método/propriedade de QUALQUER tipo (enum/record/entity/DTO, inclusive accessors aninhados) num template Thymeleaf é reflexão sob AOT → @ReflectiveAccess no tipo (@Introspected/@MappedEntity NÃO cobrem — verificado); enumerar todos de uma vez (miss = 1 rebuild); tipo compartilhado (mensageria Direcao/Status/MensagemM2m) vai na lib; app view-heavy registra por PACOTE (Feature), não tipo-a-tipo. (2) recurso de classpath (version.properties lido pelo VersaoInfo) precisa de resource-config → a xadm-comum-web embarca a reachability-metadata (0.5.1), propaga à frota. (3) factory JAXP por service-lookup (DocumentBuilderFactory.newInstance()) usa FactoryFinder (reflexão) → use newDefaultInstance(). Os três são a classe "compila verde, quebra em runtime" que o e2e native existe pra pegar.

Alterado

  • Commit inclui o .claude/settings.json editado (REGRA Nº 2): se a sessão mexeu no settings.json rastreado do repo (allowlist/hooks/env), ele entra no mesmo commit; o settings.local.json gitignored (máquina-específico) fica de fora.

Removido

  • Pegada Thymeleaf aposentada (a frota inteira migrou pra JTE, 0025): apagados o kit templates/views/layout.html, o gate scripts/checa-views.py + testa-checa-views.py (validavam o shape dos fragments .html; sob JTE o motor type-checa no compile, não há .html de view) e a receita "Kit Thymeleaf — LEGADO" da java-micronaut. Removida a fiação do checa-views do build-site.yml, templates/ci.yml, Dockerfile, gera-manifesto e do pré-flight da /xadm-release. As ADRs 0015/0017 (mecânica do kit .html) ganharam nota de superadas pela 0025 (histórico preservado). App que ainda copiava o kit Thymeleaf re-deriva o kit JTE via /xadm-docs.

0.33.0 - 2026-08-25 (constituição 0.35.0)

Adicionado

  • Constituição mais opinativa — fechados 4 casos de discrição app/dev. Dois gates novos no valida-frontmatter.py e dois ajustes de texto normativo, para tirar do app/dev escolhas que divergiam em silêncio (a varredura separou discrição real de escopo condicional — este último, como mapeamento.md/flowchart/riscos/SVG opcionais, é aplicabilidade e ficou intacto):
    • entregue: OBRIGATÓRIO em toda etapa de projeto/ (constituição §2) — o validador falha sem ele; isenta o livro (index.md) e diversos.md. Antes opcional, caindo num heurístico (status do DOC como proxy da entrega da FEATURE) que fazia o Resumo Executivo mentir quando o app omitia o campo. O heurístico sobrevive só como socorro a etapa legada não-migrada — o visao-tecnica.py fica intocado; app novo declara o campo.
    • decidido_em: OBRIGATÓRIO em decisão status: aprovado (constituição §4) — cumpre o "retrato datado" (§1.4); rascunho/em-revisão/obsoleto isentos (legado/em progresso migra oportunista). Dogfood: a decisão 0001 do central era a única sem o campo — o gate a pegou.

Alterado

  • Nav "recomendado" → "nav canônico" (constituição §2): a ordem do mkdocs.yml é normativa; desvio é decisão declarada (REGRA Nº 3), não escolha silenciosa — site consistente entre repos.
  • Contradição interna resolvida: anexos README passou de "recomendado" (§5) para obrigatório, reconciliando com §3.
  • Contrato app↔app: o mecanismo agora é módulo tipado do receptor, não codegen (decisão 0020 atualizada in-place; não tinha entrado no ar). O openapi-generator (codegen no consumidor) saiu — casa 100% Java/Micronaut server-server → o benefício poliglota vale ~zero e a capacidade do gerador era risco não-provado (0020 §1.2). Entra o módulo <svc>-api que o receptor publica do próprio repo (DTOs @Serdeable + interface @Client, Maven Forgejo, group br.com.xadm), consumido por versão → enforcement em compile-time (rename incompatível não compila). micronaut-openapi fica (spec + openapi.yaml versionado + openapi-diff recomendado); duas classes de consumidor (Java → módulo tipado; não-Java/skill/externo → spec publicado É o contrato). Reconciliado em java-micronaut §Específico, stack.md, integracao.md §Arestas, publicar-docs.md §O contrato, nota na 0019 (<svc>-api ≠ xadm-commons). Escopo Java real medido = só integrador-server ↔ int-sascar (arestas A+B); central-backend = spec-only.

Corrigido

  • Armadilha do rename de pacote na guarda ArchUnit (feedback §6, int-sascar biz.xadm.intsascar → br.com.xadm.integracao.sascar). Renomear o pacote base deixa string-literals stale — sobretudo @AnalyzeClasses(packages=...) (master switch) e resideInAPackage("...") — que o ./gradlew check não pega: ArchUnit avalia 0 classes e passa vácuo (o mesmo verde falso do limite nº 2, por outro gatilho). Nomeada como limite conhecido nº 3 em java-micronaut §Guarda: varrer o literal a montante (grep -rl "<pacote>" sobre .java/.xml/.properties, não só package/import), o isNotEmpty como defesa a jusante, + ripple do checkstyle LineLength (FQN mais longo; awk conta bytes). Página dev não rastreada → sem bump.

Adicionado

  • Código gerado (Serde/introspection) sai do denominador do JaCoCo — round-trip é contract test, não cobertura (feedback §6). Terceiro modo de falso-baixo de cobertura (irmão do falso-0% do class-major e do re-exec só-unit): o processador do Micronaut emite $Tipo$Introspection/$Tipo$Definition que contam no denominador, então todo app Micronaut-Serde nasce com % artificialmente baixo pelo que não escreveu. io.micronaut.core.annotation.Generated é CLASS-retention (JaCoCo ≥0.8.2 exclui a classe anotada), mas não as aninhadas (jacoco#1815) — e são as $…$Introspection/$…$Definition que sobram. Norma: excluir o gerado do denominador via excludes do JaCoCo (**/*$Introspection*, **/*$Definition*); o round-trip via ObjectMapper do contexto não é a forma de cobrir o gerado (testa o framework, deixa ramos descobertos = "toca sem asserir"), mas vale como contract test do DTO que cruza fronteira. Norma em java-micronaut §Testes + linha na política de cobertura em ci-testes. Páginas dev não rastreadas → sem bump.

0.32.4 - 2026-08-25

Adicionado

  • Cobertura tem ponto de coleta garantido (re-exec forçado) + integração pula por Docker-ausente com aviso ALTO (feedback §6, refina o item 149). Fecha as duas camadas que o 149 não tocou. [coleta] A cobertura (JaCoCo) roda com --rerun-tasks/cleanTest test, nunca no up-to-date do Gradle: o test fresco é pulado, o .exec mesclado que a ferramenta lê fica só-unit e a cobertura subconta — falso-baixo silencioso (caso onpetro: "41%" que virou 75,6% ao forçar o re-exec da integração), irmão do falso-0% do class-major do JaCoCo. O % só vale desse gate (o do CI que re-executa), jamais de um ./gradlew test de dev (só-unit). Norma em ci-testes §Definição de pronto + mecânica em java-micronaut §Testes. [polaridade] Idioma recomendado: a integração roda por default e só pula com Docker genuinamente ausente, com aviso ALTO no log — @DisabledIfDockerUnavailable custom (DockerClientFactory.instance().isDockerAvailable() + log no skip), não o disabledWithoutDocker do @Testcontainers, que cala. Complementa a guarda do 149 (não-exclua-por-flag) com "se pular, grite". Páginas dev não rastreadas (ci-testes, java-micronaut) → sem bump.

Corrigido

  • Partição da fila outbox em schema compartilhado é só o registro de enviador — dos dois lados (feedback §6, reverte o MENSAGERIA_RELAY_ENABLED=false do item 149). O item 149 abençoou desligar o relay por flag como escape do receptor-puro; o feedback mostrou que é competing consumers mal-feito — resolver colisão de consumidores por config por-deploy não compõe (o app que envia E recebe não pode se desligar) e some no dia em que o app passa a enviar. O filtro por tipo/EnviadorMensagem já basta: receptor-puro registra zero enviador → seu relay ignora toda linha (no-op inócuo), sem flag. Novo: fail-fast no enqueue — só enfileira na SAIDA quem tem o EnviadorMensagem do tipo, senão grava linha órfã que ninguém roteia; a posse da mensagem fica cravada pelo registro de enviador nos dois lados (só enfileira quem sabe enviar, só drena quem sabe rotear). MENSAGERIA_RELAY_ENABLED=false demovido a otimização de loop-ocioso (nunca resolvedor de colisão); coluna de origem e relay-broker único nomeados como anti-padrões. Norma em 0021 §Decisão + mecânica em java-micronaut §Banco. RD e página dev não rastreadas → sem bump.

0.32.3 - 2026-08-24

Alterado

  • /health = flat {status, versao} liveness é o contrato ÚNICO da casa (feedback §6, constituição 0.32.0 → 0.33.0). O /health é servido flat (dois campos no raiz, 200-fixo, anônimo, liveness) pelo HealthController do xadm-comum-web; o management health nativo do Micronaut fica desligado (endpoints.health.enabled: false) pra não colidir com a rota (o incidente do 400 em toda request) — o nativo não é alternativa conforme (não carrega versao, agrega indicadores de dependência no raiz = readiness, DB-aware). Norma nova liveness ≠ readiness: o HEALTHCHECK do container é liveness ("o processo responde?"), nunca readiness ("o banco está up?") — dependência que pisca não pode reiniciar o container (restart em cascata); checagem de dependência é monitoramento separado. Reescrita a cláusula de versionamento §Health que dizia "o /health nativo do Micronaut é conforme, sem remapear" (era enganosa: o nativo não tem versao). Item 139 removido: com management off não há /health/liveness (grupo native), então a receita do indicador @Liveness pra curar o UNKNOWN no native ficou sem objeto — o flat é native-safe por construção (@Singleton compile-time, sem java.lang.management); o e2e native afirma /health == UP. Ação (repo de app, §3): central-backend migra do /health DB-aware próprio pro flat (a lib xadm-comum-web assume; já está na 0.5.0) — recupera o versao. Páginas dev/site não rastreadas (versionamento, java-micronaut, e2e-local); bump text-only da constituição.

Adicionado

  • Build native padronizado (modelo GitHub Actions) + ADR 0024 slug de registry + IT dispara o entrypoint real (dois feedbacks §6, constituição 0.33.2 → 0.34.0). [A] A frota (7 apps) convergiu o build native pro modelo do app de referência: o repo do app carrega Dockerfile (JVM, fallback) e Dockerfile.native, e o native é buildado por .github/workflows/build-native.yml no runner do GitHub (não no Forgejo self-hosted nem na VM de prod — native-image come ~6 GB), publicando a imagem no registry Forgejo (+ mirror GHCR); o Coolify puxa (Build Pack Docker Image). Isso substitui a 0022 §Topologia ("native builda em outro repo / uma cara JVM"). Novo template rastreado templates/build-native.yml (par do dockerfile-java-native). Reconciliações no dockerfile-java-native: binário fixo em application (imageName.set("application") obrigatório — rootProject.name diverge do slug e quebra o COPY); removido o --no-configuration-cache (era do caminho do plugin — o nativeCompile é CC-safe, a frota builda com CC ligado; reconcilia o item 140); microdnf install findutils no builder; SENTRY_DSN por ENV no native (ENTRYPOINT puro, sem ponte jq-do-app.json). [F] Novo ADR 0024: slug de registry = <cliente_id>-<projeto> (cliente) / <projeto> (xadm), == nome da imagem no registry, pode divergir do app_id (identidade estável do broker); "nome de projeto de cliente é acordado comercialmente". [B] Regra nova em ci-testes + java-micronaut §Testes: código que roda por entrypoint sem-request (@Scheduled, @EventListener, thread, boot, CLI) testa-se disparando o entrypoint real — chamar o método sob o contexto de conexão do @MicronautTest passa verde e estoura NoConnectionException em runtime (falso-verde estrutural; caso canônico "IT verde, runtime vermelho"); @Connectable/@Transactional vivem no código de produção. Bump MINOR (novo template rastreado = obrigação nova ao app native; revisável). Sem gate em B (heurístico; ponto cego declarado).
  • Integration não fica órfã do gate + relay não compete em schema compartilhado (dois feedbacks §6, constituição 0.33.1 → 0.33.2). [F1] A casa roda integration (@MicronautTest + Testcontainers) dentro do check, que roda na CI com Docker (o runner injeta DOCKER_HOST, dind). Um app que esconde a integration atrás de @Tag("docker") + -PdockerTests que a CI não passa deixa a suíte no repo mas fora do gate: cobertura órfã — verde no PR, só cai no e2e (o teste mais caro). Norma estrita em ci-testes §Integração: ou roda no check (default, gateado na CI via dind), ou — se genuinamente un-CI-able — vira e2e-local nomeada (outro repo); nunca atrás de flag não-gateado. Guarda nova no templates/ci.yml (python3 stdlib, escopo Micronaut): reprova exclusão de tag de integration condicionada a -P que a CI não passa (ou incondicional sem includeTags que a re-rode); sandbox 6/6. [F2] Estende a decisão 0021: em DB/schema compartilhado, os dois RelayM2m drenam a mesma SAIDA; o relay do app que não registra o tipo pega a linha e loga Sem EnviadorMensagem registrado. Norma: o relay filtra a SAIDA pelos tipos que registra (ignora o alheio, não pega+ERROR) — default robusto; receptor-puro pode MENSAGERIA_RELAY_ENABLED=false. Também foldado no 0021 o ponto de posse única de migração (item 121). Filtro é prescrição nova (impl no xadm-mensageria, §3); F1 ci-testes/java-micronaut são páginas dev não rastreadas. Bump PATCH (1 rastreado: ci.yml).
  • Persistência da casa fixada: micronaut-data-jdbc obrigatório + guarda de CI (feedback §6, constituição 0.33.0 → 0.33.1). Um app Micronaut nasceu com SQL cru via DataSource.getConnection() ("sem micronaut-data") — deslize de criação, não decisão: todo server Micronaut que persiste usa micronaut-data-jdbc (@JdbcRepository, @Transactional), sem Hibernate/JPA; SQL cru como camada de persistência é anti-padrão (bulk JDBC à mão num @Singleton segue OK). O pego silencioso: sem annotationProcessor('io.micronaut.data:micronaut-data-processor') os interceptors de @Transactional não são gerados → vira no-op, quebra com "No current connection present" em runtime, sem erro de compilação. Custo do deslize: adotar lib da casa que arrasta micronaut-data-jdbc (ex. xadm-mensageria) torna o DataSource transaction-aware e quebra o raw-SQL em massa, forçando migração no meio do projeto. Fixado em java-micronaut §Banco (bullet-líder); guarda nova no templates/ci.yml (python stdlib: app Micronaut + datasources configurado + sem micronaut-data-processor → FALHA no push; exceção stateless por ausência de datasources) e no /xadm-docs §3b (conformidade). Origem: feedback de app (int-pied → convergência mensageria micronaut-data).
  • Toda listagem Micronaut é paginada — Pageable + Page, default 20 (feedback §6). Endpoint de API ou tela server-render que lista tabela que cresce recebe Pageable e devolve Page/Slice, nunca findAll() cru — evita o travamento com o Postgres compartilhado crescendo. Config da casa: micronaut.data.pageable.default-page-size: 20 + max-page-size: 100 (teto anti-?size=999999). Norma em java-micronaut §Específico (mecânica Micronaut, Page vs Slice, ponto cego do OFFSET profundo → keyset, pergunta de review) e §UI (face server-render, prev/próxima no template). Sem gate (heurístico, falso-positivo em lookup pequeno). Página dev não rastreada.
  • Pirâmide de testes: e2e é o mais caro — audite o que já existe, escreva no nível mais baixo (feedback §6, constituição 0.32.0 → 0.33.0). Antes de cobrir um gap, auditar os testes que já existem no repo e escrever no menor nível que o prova (unit antes de integration antes de e2e); o e2e não re-testa o que unit/integration prova mais barato (ex.: negativos do validador Firebase e fail-closed de /api/admin/** fecham abaixo; "health com banco down → 503" fecha em integration com PostgreSQLContainer.stop()). Toda suíte e2e-local carrega um COBERTURA.md com a tabela gap → nível → onde fecha (norma). Disciplina em ci-testes §Definição de pronto; artefato em e2e-local §11. Origem: auditoria dos e2e-local que cobriam o que já é unit/integration.

  • Constituição §5: "adoção é migração, no mesmo PR" + escopo do xadm-seguranca (feedback §6, constituição 0.31.4 → 0.32.0). Bumpar um módulo do xadm-commons que promove capacidade antes local exige remover o código local no mesmo PR (dois @Controller("/") com /login colidem no boot); o contrato de migração mora no CHANGELOG do módulo (§Migração); o e2e-local pega o drift de contrato antes da produção. O bullet xadm-seguranca da §5 passa a nomear o fluxo de login Google server-side das views (não só primitivas); a evolução observada (0.5.0 login flow + rate-limit, 0.5.1 Bearer fail-closed, dep. de xadm-comum-web) foi registrada na tabela do 0019. Checklist operável de adoção em java-micronaut §Específico. Origem: feedback de app (varredura e2e-local + promoção do login para xadm-seguranca).

  • Duas armadilhas Micronaut de config de adoção (feedback §6, [Unreleased], páginas dev não rastreadas): (1) java -jar deduz o perfil dev e carrega application-dev.yml, sobrepondo env de produção em silêncio — cura = MICRONAUT_ENVIRONMENTS explícito ou deduceEnvironment(false); (2) e2e-local crava a imagem de terceiro na versão de produção, nunca :latest (o skew esconde bug de versão até o deploy).

Corrigido

  • Thymeleaf: th:if="${x == '1'}" compara char, não String — prefira boolean no modelo (feedback §6). Dentro de th:if="${…}" as aspas duplas do atributo forçam aspas simples na expressão, e aspas simples em OGNL é literal de char ('1' = char) → ${x == '1'} compara String == char, não casa, e o elemento não renderiza sem erro (build verde). Cura = boolean no modelo + th:if="${flag}" (o que o ${appModo}/podeAgir do kit já fazem). Bullet nomeado no cluster §Armadilhas (família OGNL/record) + reforço do gancho em §UI. O ponto 2 do feedback (não-ASCII no title quebra render) foi descartado como falso rastro (decisão do Gustavo): o em-dash/acento é válido em UTF-8; a causa do span não-renderizado era a comparação de char, não o não-ASCII. Sem bump (java-micronaut.md é página dev, não rastreada).

0.32.2 - 2026-08-20

Corrigido

  • Receita de leitura XLSX com FastExcel-reader: withCellFormat é obrigatório com datas Excel reais (feedback §6). O recipe (java-micronaut §Específico, item 137) detecta data pelo getDataFormatId()/getDataFormatString(), mas omitia que o fastexcel-reader não resolve o formato da célula por default: sem abrir o workbook com new ReadableWorkbook(is, new ReadingOptions(true, false)) (withCellFormat=true), esses getters vêm null, a detecção de data nunca dispara, e uma data Excel (serial, ex. 46083) sai crua. Sintoma traiçoeiro: fixture sintética com datas como texto passa verde, mas golden-master/e2e com dado real dropa linha em silêncio — achado da migração POI→FastExcel do bi-comercial. Adicionado o flag ao recipe §Específico; a armadilha de fixture do §Testes ganhou nota distinguindo as duas causas do mesmo getDataFormatString()==null (roundtrip do writer × flag de leitura ausente). Sem bump (java-micronaut.md é página dev, não rastreada).

  • Native no repo do app = nativeCompile verde; runtime é gate do repo de e2e — sem e2e/boot-smoke native no repo do app (feedback §6). Feedback propunha um boot-smoke native in-repo (Testcontainers GenericContainer da imagem native + Postgres, @Tag("native")). Decisão do Gustavo: não — o repo do app só precisa do nativeCompile compilar; os bugs de runtime native são pegos pelo e2e native no repo de e2e de fluxo (separado), coerente com o native buildar fora do repo do app (0022 §Topologia). Um smoke in-repo seria redundante (mesmos bugs, repo errado). Cravada a fronteira em java-micronaut §Deploy §Native-image e no 0022 §Gate, pra o próximo app não reinventar o smoke. Também reforçado o hint de reflect-config do SentryAppender: confirmado byte-idêntico em 3 apps native (bi-transporte-xls, central-backend, webstorm-ecom), a hint sobe pra reachability-metadata da lib xadm-comum-web (META-INF/native-image/… no jar → native-image detecta sozinho, elimina a cópia local na frota; pendência de repo de lib §3). Sem bump (páginas dev e decisão não rastreadas).

  • checa-nav.py divergia da validação nativa do mkdocs em README.md — alinhado (feedback §6, constituição 0.31.4). O checa-nav.py pulava todo README.md incondicionalmente (convenção "README de pasta não é página de nav"), mas a validação nativa do mkdocs (validation.nav.omitted_files: warn, item 135 / 0.30.4) não tem essa exceção: um README fora do nav: é órfã e reprova o --strict a menos que esteja em not_in_nav:/exclude_docs:. Divergência: um README órfão passava no checa-nav e reprovava no --strict — foi por isso que o site pôs anexos/README.md no not_in_nav. Decisão do Gustavo (AskUserQuestion): remover o skip-README do checa-nav (não documentar a divergência como intencional, nem aposentar a detecção de órfã): agora um README é OK só se casar not_in_nav/exclude_docs — idêntico ao mkdocs. Verificado: o central segue verde (os dois README — anexos/README.md no not_in_nav, anexos/privado/README.md sob exclude_docs: privado/ — já caem nos globs, sem depender da exceção). O checa-nav mantém a detecção de chave YAML duplicada (única dele, o mkdocs não pega — item 94). Caso README-nao-e-orfao do testa-checa-nav trocado por dois (README órfão reprova; README em not_in_nav passa — 19 casos). Bump PATCH (1 rastreado re-carimbado: scripts/checa-nav.py). Nota: checa-nav é central-only (item 132) — apps não o rodam; o alinhamento fecha a divergência-de-lógica quando um app adotar ambos.

  • Hook SessionStart do checa-constituicao fica presente porém nunca acionado numa instalação verbatim (feedback §6, constituição 0.31.3). O templates/settings.json (0.29.1) traz só env+permissions, mas a constituição §6 afirmava que o settings.json versionado carrega o hook SessionStart, e o checa-constituicao.sh/app-novo.md mandavam registrá-lo à mão — passo fácil de pular: o .sh acaba copiado, o hook nunca cabeado, o aviso de defasagem nunca dispara. Decisão do Gustavo (AskUserQuestion): o /xadm-docs crava o hook ao instalar (o template segue só env+permissions — pessoal/wiring fora da base). A skill passa a fundir o array SessionStart ao sincronizar o settings.json — garante a entrada do checa-constituicao (adiciona se ausente) e preserva as demais do app (caveman/token-check) + additionalDirectories/grants locais. Reconciliado o cabeçalho da skill, app-novo.md (passo obrigatório), ferramentas.md §higiene (o template não traz hooks; a instalação crava) e a §6 da constituição (nuance: o template traz só env+allow-list). Bump PATCH (1 rastreado re-carimbado: templates/xadm-docs-skill.md). Pendência (repos de app): re-derivam a /xadm-docs e ganham o hook cabeado; quem instalou verbatim sem o hook passa a tê-lo no próximo sync.

0.32.1 - 2026-08-19

Adicionado

  • Receita: monitorar o host/Docker read-only de um container Coolify (feedback §6). Split infra+Micronaut. Postura de segurança em coolify §Monitorar o host: nunca o socket cru (= root no host) — docker-socket-proxy com allowlist mínima (CONTAINERS=1, POST=0, EXEC=0) + mount read-only de superfície mínima (/proc:ro + probe de 1 arquivo público pro disco). Mecânica de leitura em java-micronaut §Monitor de host: Engine API stats?stream=false (one-shot, precpu já populado — sem 2 leituras), CPU% = (cpuΔ/systemΔ)×online_cpus×100, RAM = usage − inactive_file (cgroup v2), disco via Files.getFileStore (Java sem statvfs), envelope de degradação parcial (200 + fontes/erros, nunca WARN mudo), e recon de fixture real antes do parser (§6). Páginas dev/site, sem bump.

  • Gotcha serde: @Serdeable omite mapa/lista vazios no JSON de saída (feedback §6). Coleção vazia some do JSON serializado → o consumidor recebe a chave ausente (null), não {}/[], e estoura NPE ao iterar (custou 1 NPE de teste). Regra em java-micronaut §Armadilhas: o contrato trata ausente como vazio (consumidor coalesce; teste de contrato afirma ausente ≡ vazio), ou o produtor garante a emissão se o contrato exige a chave. Página dev, sem bump. Pendência (repos de app): consumidor que presume {}/[] no fio passa a coalescer via /xadm-docs.

  • Liveness native = UP: registre um indicador @Liveness explícito (feedback §6, medição A/B em rollout canário native). /health/liveness diverge JVM×native — na JVM é UP, sob GraalVM AOT fica UNKNOWN: o grupo liveness agrega só os HealthIndicator @Liveness, e o default (detector de deadlock via java.lang.management/ThreadMXBean) não reporta no native → o DefaultHealthAggregator devolve UNKNOWN pro conjunto vazio. /health e /readiness não sofrem (têm indicadores JDBC/disk), HTTP segue 200 e o HEALTHCHECK do container usa /health — não quebra deploy, mas não é paridade 100%. Cura native-safe (bean @Singleton = compile-time, sem reflect-config): um LivenessHealthIndicator que afirma UP ("responde, logo vivo"; liveness ≠ readiness). Recipe + snippet em java-micronaut §Native-image; o e2e native passa a afirmar status==UP no liveness. Página dev, sem bump. Pendência (repos de app native): adicionar o indicador + a asserção via /xadm-docs.

Corrigido

  • Invariante de observabilidade do processor de erro da casa — 5xx mapeado pelo seam não pode sair silencioso (feedback §6). A norma de níveis de log (java-micronaut §Erros) presumia "exceção não-tratada já estoura e o SentryAppender captura sozinha" — premissa que fura quando o app adota o seam: exceção de domínio que o seam PortadoraDeProblema / ExceptionHandler mapeia a 5xx é tratada (devolve resposta), logo o RouteExecutor não a loga como "Unexpected error" → o 5xx sai silencioso, invisível ao GlitchTip; era a observabilidade que o app perdia ao trocar o handler dedicado que logava pelo processor da casa. Registrado o invariante (decisão do Gustavo): todo 5xx chega ao GlitchTip exatamente 1×, com contexto de request (method/path/request-id via MDC); 4xx = culpa do cliente, não vai a ERROR. Default-on, não flag opt-in (o feedback pedia opt-in — 5xx silencioso é justo o modo de falha que ninguém quer poder desligar por config). Central fixa o invariante; o mecanismo (processor por default × ponto de mapeamento) é desenho do xadm-comum-web — segunda metade do que a lib entrega no 0.3.0, ao lado do seam de code/title (pendência de repo de lib). Sem bump (java-micronaut.md é página dev, não rastreada); sem gate (comportamento de log não é detectável estático — ponto cego declarado).

  • App native "com duas caras": topologia da migração definida no 0022 (feedback §6, constituição 0.31.2). A 0.31.0 publicou o templates/dockerfile-java-native (à mão), mas apps reais entregaram native pelo plugin dockerBuildNative (base wolfi-base, imagem empurrada pelo próprio repo) — um app native "conforme" podia ter duas caras, e a página não dizia se o caminho do plugin era equivalente ou divergência. Resolução (decisão do Gustavo): durante a migração, o repo do app mantém o Dockerfile JVM (.jar); a imagem native é buildada por um build à parte, em OUTRO repositório (a fábrica de imagens, 0003), que roda o nativeCompile com a receita da casa e empurra a imagem; o Coolify puxa (Build Pack Docker Image). Isso dá uma cara só (JVM) no repo do app; o native vive na fábrica, com o Dockerfile à mão como receita única (debian-slim + curl pro HEALTHCHECK §5 — a wolfi-base do plugin sem curl quebra o §5). App que hoje builda native no próprio repo via plugin = caminho interino a reconciliar. Nova seção §Topologia no 0022 + bullet em java-micronaut §Deploy + nota no header do template. Bump PATCH (1 rastreado re-carimbado: templates/dockerfile-java-native). Pendências declaradas (§1.6): reconciliar o header "destino: raiz do repo" (é o estado final) e o status exato do caminho plugin nos apps que já entregaram assim (ação de repo de app, §3). Correção factual ao feedback: o native não é buildado pelo Coolify — builda off-host e o Coolify puxa a imagem pronta; a hint de reflect-config do SentryAppender foi confirmada pelo app.

  • config-cache × native colidem por default: --no-configuration-cache no build native (feedback §6, constituição 0.31.1). A base de perf (gradle.properties, 0.30.0) liga org.gradle.configuration-cache=true, mas as tasks native do plugin Micronaut (dockerBuildNative/dockerfileNative) não são CC-safe — o NativeImageDockerfile lê a saída do generateResourcesConfigFile sem dependência declarada e estoura Querying the mapped value of :generateResourcesConfigFile outputFile before task has completed. Com a 0.31.0 tornando native o default do server elegível, as duas bases colidem sem aviso. Cura: o dockerfile-java-native passa --no-configuration-cache na linha do nativeCompile (o CC dá zero ganho num compile cold único — container efêmero, sem daemon, cache nunca reusado — então dodge do bug sem perda); a nota do gradle.properties e o novo bullet em java-micronaut §Deploy nomeiam a colisão como o caso concreto do opt-out. A base mantém configuration-cache=true — o app JVM se beneficia; só o caminho native abre mão. Bump PATCH (2 rastreados re-carimbados: templates/dockerfile-java-native, templates/gradle.properties). Pendência (repos de app native): re-derivam o dockerfile-java-native via /xadm-docs (ganham a flag); quem builda/roda native localmente passa --no-configuration-cache.

  • Porta de teste fixa quebra no Windows (faixa excluída) + TEST_DB_URL/DB_URL divergentes (feedback §6). App fixou TEST_DB_URL=localhost:55432 no application-test.properties; 55432 cai numa faixa reservada do Windows (Hyper-V/WSL/Docker Desktop reservam blocos dinâmicos — bind falha sem nada ouvindo), Test Resources não binda → initializationError em massa (verificado: 55432 ∈ 55429–55528 via netsh interface ipv4 show excludedportrange protocol=tcp). A norma da casa já era a cura — a URL de teste vem injetada pelo Testcontainers/TestPropertyProvider (porta efêmera), não de env — mas não avisava contra a porta fixa nem nomeava o hazard. Documentado o hazard em agentes §Loop (afeta toda porta fixa: explicitPort do shared server, docker-compose publish, debug) e a regra de DB de teste + naming (DATASOURCES_*, não DB_URL/TEST_DB_URL) em java-micronaut §Testes. Páginas dev, sem bump. Pendência (repos de app): quem fixou porta de teste migra pra URL injetada e larga o TEST_DB_URL via /xadm-docs.

0.32.0 - 2026-08-19

Adicionado

  • native-image é o padrão de deploy do server Micronaut elegível; ler XLSX com FastExcel, não Apache POI (constituição 0.31.0, feedback §6 do piloto bi-transporte-xls). A frota já tinha vários servers em GraalVM native-image (RSS ~3× menor, imagem ~9× menor, sem build-spike de RAM no host — a causa do freeze da VM de produção), mas o central nunca documentou o padrão: conhecimento nos repos de app, sem dono citável. Ratificado em duas decisões — 0022 (native é o alvo de deploy do server Micronaut elegível; Java-8 integrador-client e Flutter fora por stack; JVM = exceção declarada) e 0023 (ler XLSX com org.dhatim:fastexcel-reader, não POI: XMLBeans quebra native com ClassCastException no StylesTable, sem caminho estável na CE; POI só em testImplementation). Novos: campo native: true no docs/app.json (discriminador do gate e do e2e), template rastreado dockerfile-java-native (builder GraalVM CE + nativeCompile + runtime glibc com curl), guarda no ci.yml que reprova poi-ooxml em escopo não-test de app native (discrimina por native:true/ Dockerfile native, não pelo plugin — o io.micronaut.application traz o GraalVM transitivamente). Recipes em java-micronaut §Deploy (perfil GraalVM CE — -Os/-Ob/--gc=serial; G1/PGO/build-report são Oracle-only; -O3==-O2 na CE), §Específico (leitura FastExcel), §Observabilidade (reflect-config do SentryAppender), §Armadilhas (POI silencioso no build) e §Testes (a fixture do ramo "numérico-data → data" vem de ferramenta externa — o roundtrip FastExcel writer→reader perde getDataFormatString); seção e2e native em e2e-local; app-novo nasce native por default. Bump MINOR 0.30.5 → 0.31.0 (obrigação nova sobre o app server elegível — native default + e2e native na definição de pronto). Pendências humanas (repos de app): migrar bi-comercial-xls e integrador-server (POI → FastExcel, cada um com golden-master + e2e native); a lib xadm-comum-web registra o reflect-config do SentryAppender (§3). Refino (feedback §6 do app central-backend): a receita ganhou os números medidos em produção (boot ~1 s, RSS ~43 MB, imagem ~217 MB), a confirmação de que o stack da casa (AWS SDK v2, json-smart, Flyway, Hikari, serde, JCA stock) roda sem reflect-config manual, e o contrato de deploy no Coolify — o native usa Build Pack: Docker Image (imagem buildada off-host, empurrada pro Forgejo registry, o Coolify puxa pronta), nunca o Dockerfile default, que buildaria native no host (freeze).
  • Auth e serviço externo também são e2e-local — não "staging manual" (constituição 0.30.5, feedback §6). O piso da definição de pronto (§6-(1)) já cobrava "runtime real", mas não fechava o caso de auth: serviço nosso (ex. o login do central-backend) roda de verdade, local e automatizado, em CI — não existe "a auth é real, então tem que ser staging". Ambiente deployado + clique de operador é smoke de deploy, não nível de teste mantido verde; "staging semi-manual com dado de produção" polui o prod e não atesta lógica (é a mesma do caminho automático que o e2e-local já dirige — ex. "Baixar Manual" e a macro chamam o mesmo DespachoService.despachar). Serviço externo não-executável entra por emulador oficial ou, quando o emulador não assina (validador exige token RS256 e o emulador emite alg:none), por JWKS próprio com a busca repontada por um seam de config (default = produção intacta, override só em teste, ex. auth.firebase-jwks-url). App que valida token de terceiro por URL/JWKS hardcoded ganha esse seam mínimo (retrocompatível) em vez de exigir ambiente deployado — e o override tem de ser de config (o harness do e2e é externo ao processo; um if (teste) in-process não conta). Piso curto na constituição §6-(1); receita em e2e-local § Serviço externo que assina (princípio + o seam) e mecânica Micronaut (property override, emulador × RS256) em java-micronaut § Testes. Bump PATCH 0.30.4 → 0.30.5 (refino do piso §6, text-only; nenhum template rastreado muda — a norma propaga por leitura). Sem gate (seam é heurístico, régua dos itens 93/103/108).

  • Gate de docs pré-tag: nav-drift vira bloqueador + regra "sem link relativo pra fonte" (constituição 0.30.4, feedback §6 — 2 releases seguidos do BI Comercial OnPetro quebraram no docs.yml depois da tag). Metade do feedback (espelhar os gates de docs no pré-flight da /xadm-release) já estava feita (item 132 tornou o Gate de DOCS drift-proof: "rode cada gate que o teu docs.yml invoca", inclui mkdocs --strict); recorrência = skill stale. Furo real que sobrava: nav-drift (.md fora do nav) saía como INFO e o --strict não bloqueava — app comum não tem checa-nav (central-only). Agora o templates/mkdocs.yml (e o mkdocs.yml central, dogfood) liga validation.nav.omitted_files: warn + links.not_found: warn → órfã e link pra fora de docs/ viram bloqueador de --strict em todo app, sem script novo. Regra de autoria nova (prevenção) na constituição §1 + publicar docs §Validação no CI: docs referenciam fonte por code span (`lib/.../foo.dart`) ou URL absoluta do Forgejo (https://fonte.xadm.biz/xadm/<repo>/src/branch/master/...), nunca link relativo pra fora de docs/ (o --strict aborta). Bump PATCH 0.30.3 → 0.30.4 (templates/mkdocs.yml rastreado re-carimbado). Lint dedicado descartado: --strict

  • validation já cobrem a detecção; a norma é prevenção.
  • Baseline de operação do host do Coolify (constituição 0.30.3, feedback §6 — incidente de freeze 2× da VM de produção bi-ubuntu durante deploy, 2026-08-18). O central documentava o app (Dockerfile, envs, libs), nunca o host onde o Coolify roda. Nova seção §Baseline de operação do host em Operação no Coolify: swap dimensionado (≥ RAM/2, swappiness=10), earlyoom que mata antes do livelock e poupa os bancos, limite + reserva de memória por recurso (o gotcha: recurso nasce limits_memory=0 = ilimitado; app Java sem teto derruba a VM — tabela por perfil: Server Micronaut 768M/256M, XLS 1G/384M, Ingest 1.5G/512M), não buildar no host de produção (dind = pico de RAM, o gatilho do freeze; cross-link decisão 0003) e runbook de diagnóstico. Norma-ponteiro na constituição §5 ("app deployável declara limite + reserva; build fora do host") — doc de plataforma, não de app, como os três mecanismos de entrega. Bump PATCH 0.30.2 → 0.30.3 (norma-ponteiro, sem obrigação nova de conteúdo; coolify.md/constituição são prosa, não template rastreado — a norma propaga por leitura, não por download do manifesto).
  • Rodar testes Micronaut/Gradle sem travar (concorrência + performance) (constituição 0.30.0, pesquisa a pedido do Gustavo: vários gradlew juntos travam — pile-up de container, colisão de porta do embedded server, daemon wedged). Núcleo em java-micronaut § Testes:
    • Test Resources COMPARTILHADO como default da casa — sharedServer = true + explicitPort no build.gradle de cada app Micronaut: 1 server e 1 conjunto de containers reusado entre builds/repos concorrentes. Norma nova de ciclo de vida (server sobrevive; parada limpa = stopTestResourcesService), reconciliada com a §Armadilhas de órfão (compartilhar é intencional ≠ órfão obsoleto); hazard de kill no Windows + reaper do caso compartilhado; pin de container compatível para reuse cross-repo.
    • Novo templates/gradle.properties (rastreado, opcional Java/Gradle): base de performance (parallel/caching/configuration-cache/daemon/jvmargs), cada chave comentada, opt-out local do CC. Fork policy documentada (unit paraleliza; integração modesta por causa do Postgres compartilhado + seed committado). Split unit×integração clarificado como por environment, não source set Gradle.

Corrigido

  • /xadm-release — Gate de DOCS do pré-flight vira drift-proof: fonte da verdade = o docs.yml do repo, não uma lista fixa (constituição 0.30.2, feedback §6 de app: pré-flight rodou só o gate de código, tag pushada, pipeline de docs estourou pós-push). O gate de docs já existia desde ≤0.18.4 — a causa deste caso foi skill stale (app não re-sincronizou). Mas o feedback expôs fresta de clareza real: o pré-flight enumerava checa-admonition.py sob "se o repo os tiver (central)", quando todo app o roda no docs.yml (curl da toolchain), e punha gera-manifesto.py --check (conceito central-only) no bucket "sempre". Agente app-side lendo a lista podia achar o gate quase-central e pular. Decisão do Gustavo (AskUserQuestion): refino drift-proof — o Gate de DOCS agora lidera com "abra teu .forgejo/workflows/docs.yml (build-site.yml no central) e rode cada gate que ele invoca" (o CI real do repo é a fonte da verdade; enumerar aqui driftaria); o conjunto de todo app (valida-frontmatter + lint-mermaid + mkdocs --strict + checa-admonition) fica explícito, e o superset rastreado (gera-manifesto --check, checa-nav, checa-dogfood, checa-views, testa-*) vira soma do central — "app comum não tem, não os invente". Só o template (templates/xadm-release-skill.md); a cópia local .claude/skills/xadm-release/SKILL.md é especialização de repo único (enumera os scripts central corretos) — sem a confusão app-side, não espelhada (precedente itens 110/111). [Unreleased] → seção versionada, deixando conteúdo já-lançado órfão e sem ## [1.5.2] no corpo). Causa: a opção "Pular CHANGELOG" do passo 5 (só bump+tag) era oferecida incondicionalmente e, com [Unreleased] cheio, estranhava as notas e taggava sem seção; a rede de segurança (valida-release.py) só rodava no release.yml pós-push da tag (tarde e não-gating, item 62). Decisões do Gustavo (AskUserQuestion ×2): gate local + reformar a opção e remover o "Pular CHANGELOG". valida-release.py --tag v<nova> passa a rodar no passo 8 (guarda final, pré-tag, todos os repos) ao lado do gera-manifesto --check do central — vermelho = não taggar; e o passo 5 perde a opção de pular (constituição §5 obriga a seção; intervalo vazio = release não deveria ocorrer). Template + cópia local da skill. complementa a cura durável do gitattributes-java, item 97). O bullet do gate já documentava causa e cura; ganhou a frase de diagnóstico: git ls-files --eol gradlew — blob i/lf = artefato local do autocrlf (Coolify checkout LF no Linux, produção OK) = não bloqueador; blob i/crlf = bloqueia. Template-only (a cópia local da skill não tem gate de container).

  • Base rastreada do .claude/settings.json — templates/settings.json (constituição 0.29.1, feedback §6 de app: bootstrap Java/Micronaut sem allow-list base, agente reinventava e prefixava JAVA_HOME=… inline, quebrando Bash(./gradlew *)). Decisões do Gustavo (AskUserQuestion): template rastreado mínimo (não só prosa) e duas curas JAVA_HOME paralelas (não eleger primária).

    • Novo templates/settings.json (rastreado, core): env.PYTHONUTF8 + allow-list base universal por-ferramenta em Bash e PowerShell (git, rtk, python×3, node, mkdocs, docker, curl escopado). Adições por stack documentadas (JSON não aceita comentário): Java ./gradlew/jar/javap/unzip, Flutter flutter/dart, Node npm. /xadm-docs re-deriva a base e preserva hooks/additionalDirectories/grants locais (customizado-por-app, como o ci.yml).
    • Correção ao feedback (loop §6): env.JAVA_HOME não pode ir no versionado — o valor é path absoluto de máquina, difere por dev → vive no .claude/settings.local.json gitignored (coerente com a doutrina §Higiene). O grant de leitura da memória do agente idem. Só o allow-list base (ponto 2 do feedback) é versionado; JAVA_HOME (ponto 1) e memory-read (ponto 3) são regra de arquivo local, documentada.
    • Regra viva em ferramentas § higiene do settings.json: base + adições por stack + env.JAVA_HOME como cura paralela ao sdk default java 25 do §rtk (a env é per-repo, o sdk default é global — qualquer uma dispensa o prefixo inline que fura o allow Bash(./gradlew *) e o hook do rtk). §rtk Java/Gradle reconciliado.
    • Satélites (§8): constituição §6 (ponteiro à base + exemplo python3 * corrigido do brittle python3 scripts/*), templates.md (linha + seção ## Config do agente), app-novo passo 3 (bullet de cópia + nota do JAVA_HOME local), xadm-docs-skill (mapa de destino). Bump PATCH 0.29.0 → 0.29.1 (template novo sem obrigação de conteúdo sobre apps existentes — revisável; 2 rastreados carimbados: templates/settings.json novo, templates/xadm-docs-skill.md).
  • Padrão e2e-local — suíte de teste ponta a ponta local de fluxo de integração (constituição 0.29.0, feedback §6 de app: a suíte e2e-maxsul-pied provou a forma; a casa não tinha norma nem receita citável para teste de fio multi-app). Decisões do Gustavo (AskUserQuestion): norma normativa na constituição + bump, âncora §8, sem ADR (promovível), receita doc-only (scaffold rastreado deferido — REGRA Nº 3 — até uma 2ª suíte provar o esqueleto idêntico), processo de integração §6 direta.
    • Nova página engenharia/e2e-local.md com o padrão completo (11 seções): princípios (apps reais/fake só nas bordas; build local carimbado por commit; tudo por arquivo; zero interação; exit 0/1; runtime descartável), estrutura de pastas canônica, config por projeto, builds-latest carimbado, fakes nas bordas, blocos de script + run.ps1, as 4 verificações obrigatórias e as armadilhas de PowerShell/Micronaut.
    • Constituição §8 ganha o bullet "Verificação ponta-a-ponta local" (norma: fluxo de integração tem suíte e2e-local; projeto novo nasce com, existente adota por migração oportunista §1.6). Bump MINOR 0.28.3 → 0.29.0 (obrigação nova ao fluxo de integração; text-only, nenhum template rastreado mudou — gera-manifesto --update re-carimba o campo constituicao).
    • Manifesto de build committado (recomendado) no §4 (2ª leva de feedback §6, mesma sessão): arquivo pequeno versionado que registra <app> SHA-full assunto por build, pra o git guardar a proveniência que o jar gitignored perde. Decisões do Gustavo (AskUserQuestion): deriva do nome do .jar em builds-latest\ (prova qual jar está no disco, fecha o risco git-HEAD-ao-vivo ≠ binário), opção recomendada (não norma), sem timestamp volátil (evita diff sujo a cada run). .gitignore do §9 ganha o !builds-latest/manifesto.md.
    • Ponteiros (satélites §8): ci-testes §E2E, glossário (termo e2e-local), integracao.md §Entrega garantida, engenharia/index.

Corrigido

  • Exceção @Async na norma de seed de teste Micronaut (feedback §6 de app; refina o item 103). O bullet de seed de teste (java-micronaut.md §Testes) dizia "não cure com transactional=false por reflexo" sem ressalva — armadilha pra quem testa pipeline @Async: a thread do TaskExecutor tem tx própria (thread-bound), não vê o seed não-commitado do corpo do teste e commita os próprios writes de verdade; abort de FK envenena a conexão se o pipeline segue sem rollback. Decisões do Gustavo (AskUserQuestion): refinar o bullet do 103 inline (a exceção fica colada à regra, impossível ler uma sem a outra) e ponto cego declarado (sem gate — "write cross-thread sob transactional=true" é heurístico, mesma régua do 103/108/113). Adicionada a exceção: teste de @Async + commits reais usa transactional=false + limpeza por conexão direta (não há tx ambiente pra rollback — mesma disciplina do NoConnectionException que o 103 já cita). Ponteiro em ci-testes §E2E estendido (a página não pode implicar que @BeforeEach é o único padrão). Sem bump (páginas dev, não rastreadas).

0.31.3 - 2026-08-11

Adicionado

  • Check de conformidade doc-de-projeto ↔ construído nas skills da casa (constituição 0.28.1, feedback §6 de app: /xadm-docs audita template/engenharia/frontmatter mas nunca cruza o conteúdo do desenho — docs/projeto/, docs/decisoes/ — contra o que o app construiu; dois drifts reais passariam batidos — app com decisão sem registro, arquitetura.md prevendo PowerSync enquanto o build fez poll+poke). Decisões do Gustavo (AskUserQuestion): host duplo, escopo local, 3c sinaliza os dois tipos, sem RD, bump PATCH.
    • /xadm-docs ganha o sub-passo 3c (templates/xadm-docs-skill.md): varredura repo-wide, passe leve em toda rodada (roda mesmo com a versão em dia — drift doc↔build independe da versão; o early-exit "em dia → parar" só dispara depois de 3c limpo). Sinaliza (A) desenho status: aprovado que o build contradiz e (B) decisão de design no código sem docs/decisoes/0NNN. Guardrail: só status: aprovado de estado-atual cobra bater; roadmap isento. Não é gate — item de julgamento humano, saída = propostas de autoria deferíveis (passo 5 não auto-aplica). Escopo local (sem repos irmãos, §3).
    • /x-documentar reforça o passo 3 (templates/x-documentar/SKILL.md): fecha o delta não-coberto — decisão que a sessão tomou e não virou docs/decisoes/0NNN (RD faltante, não só desatualizado) + nomeia docs/projeto/ (arquitetura) entre os docs a conferir. Bump PATCH 0.28.0 → 0.28.1 (2 rastreados re-carimbados: xadm-docs-skill.md, x-documentar/SKILL.md; refino de skill, sem obrigação nova ao app).
  • Receita: consumir a xadm-comum-storage (lib da casa) em engenharia/java-micronaut §storage (feedback §6 de app). Camada acima do consumo de Garage/S3 com o SDK cru: injete o S3Client bean (a S3ClientFactory é @Factory de método package-private, não é para chamar direto); prefixo fixo s3.* → ponte pro namespace semântico garage.* via @Factory + @Replaces(S3Config.class); exclua o netty-nio-client transitivo da lib quando usa url-connection-client (senão dois HTTP clients, o 2º Netty colide com o do Micronaut). Código da lib mora no xadm-commons (outro repo, §3) — norma registrada aqui. Sem bump (java-micronaut.md é página dev, não rastreada).

Corrigido

  • Toolchain da /xadm-docs: dois furos de determinismo no passo 2 (constituição 0.28.2, feedback §6 de app). Decisões do Gustavo (AskUserQuestion): A1 = prosa da skill; B/A2 = receita completa + fix mecânico.
    • Scaffold copy-once fora do manifesto era descoberto por tentativa. O kit linka /css/app.css (404 sem o arquivo), mas app.css/gitignore-flutter são copy-once e não entram no manifesto por desenho (re-derivar apagaria trabalho do app) — o passo 2 mecânico, que lê só o manifesto, os perdia. Correção: o passo 2 da skill ganha a lista explícita dos copy-once + raw path (<raw_base>templates/<nome>) + a regra "copie SE AUSENTE, nunca sobrescreva". templates/app.css já era publicado raw (Dockerfile) — o furo era só de discoverability.
    • CHANGELOG da constituição sem URL pública. O passo 2 manda checar a entrada do CHANGELOG da versão mudou_em para decidir se um defasado multi-stack toca a stack, mas nenhum path resolvia (/toolchain/* = 404: a página /changelog/ é nav top-level, fora de /padroes/, não cai no dual-publish). Correção: o Dockerfile publica CHANGELOG.md raw em /toolchain/CHANGELOG.md (ao lado da constituicao.md); a skill ganha a URL + a nota de que os cabeçalhos são versão de site (desacoplada), então procura-se a string da versão da constituição no corpo, não um heading. Bump PATCH 0.28.1 → 0.28.2 (xadm-docs-skill.md re-carimbado).
  • Checkstyle ConstantName reprovava os campos @ArchTest do ArchUnit (constituição 0.28.3, feedback §6 de app). As regras do ArchUnit são campos static final ArchRule em camelCase (é como a lib as descobre) — declarações de regra, não constantes semânticas; o ConstantName (UPPER_SNAKE) as reprovava e deixava o checkstyleTest vermelho em todo app com teste de arquitetura assim que o cache invalidasse. Correção: templates/suppressions.xml suprime ConstantName no teste (test-wide, espelhando o MethodName que a casa já relaxa — naming de teste ≠ produção), em vez de relaxar o ConstantName no checkstyle.xml (valeria pra produção). Nota cruzada em engenharia/java-micronaut §Guarda de fronteiras. Bump PATCH 0.28.2 → 0.28.3 (suppressions.xml re-carimbado).

0.31.2 - 2026-08-11

Adicionado

  • Três normas de extração/adoção de lib compartilhada (feedback §6, dois incidentes de adoção: xadm-comum-web 0.3.0 /health; xadm-seguranca 0.2.0 StaticBearerTokenValidator). Raiz comum: componente compartilhado que regrediu frente à origem, ou ocupou recurso global sem o adotante poder sobrepor — nenhum pego no code review por falta de norma citável. Decisões do Gustavo:
    • Norma 1 — extração não pode REGREDIR segurança/robustez. Byte-idêntico é gatilho de revisão, não meta: copiar fielmente copia os defeitos da origem, que uma vez na lib alcançam todo adotante. Reconcilie pro baseline endurecido (não o mais simples); classe de segredo/auth roda o checklist do revisor na extração (diff semântico, não textual). Emenda no 0019 + item 7 no checklist de engenharia/seguranca.md.
    • Norma 2 — bean de lib em recurso global é condicional + sobreponível. Bean vindo de lib que ocupa rota/ErrorResponseProcessor/filtro/contrato não pode ser incondicional: guarde com @Requires (missingBeans/property) — cede por default, ativa por ausência. @Replaces troca o bean mas não desfaz a rota; método executável (@Get/@Post) nunca private/package-private (quebra subclasse do adotante). Incidente: @Controller("/health") incondicional + management transitivo = duas rotas GET /health = 400 em toda request. Norma em engenharia/java-micronaut.md §Específico + armadilha do método executável em §Armadilhas.
    • Norma 3 — validador de segredo: três invariantes numa classe. Constant-time (MessageDigest.isEqual sobre bytes) · fail-closed no segredo vazio (esperado ausente/branco NEGA, jamais casa com entrada vazia — a metade auth-bypass do incidente) · config que não quebra boot (@Value com default ou @Requires, validador dormente quando a feature não é ligada). Reforça o bullet constant-time de engenharia/seguranca.md §Authn/authz (constant-time já era norma; as outras duas eram implícitas). Sem gate central — detecção (equals em segredo; @Controller de lib sem @Requires) é heurística e/ou mora no CI do xadm-commons (outro repo); cura = teste do adotante + checklist. Sem bump — 0019/seguranca.md/java-micronaut.md são RD/páginas dev, não rastreadas no manifesto.

0.31.1 - 2026-08-11

Corrigido

  • Norma: o processor de erro da casa (xadm-comum-web) honra code/title da exceção, não só do status. Dois feedbacks §6 convergiram no mesmo defeito do UnifiedErrorResponseProcessor 0.2.0: fixa code = status.name() (auth — perde ~28 machine-codes consumidos pela Central) e title = status.getReason() (int-sascar — perde o contrato de erro titulado), sem ler a causa-raiz → ambos forkam o processor via @Replaces, o oposto do que a lib existe pra eliminar. A metade code já contradiz a norma escrita em engenharia/java-micronaut.md (§Corpo de erro: code estável e uniforme por classe). Decisões do Gustavo: (1) ponto de extensão na lib (não documentar processor local como caso especial); (2) seam honra code e title (título de tipo, RFC 9457 §3.1.4; ocorrência fica no detail); (3) registro em java-micronaut.md §Corpo de erro + nota no 0020. Sem bump — páginas dev/RD não rastreadas. Pendência humana (repo de lib): fix no xadm-comum-web 0.3.0 (seam via interface portadora code()/title() ou exceção-base); em 0.2.0, app de taxonomia rica mantém o processor local até o 0.3.0.

0.31.0 - 2026-08-11

Adicionado

  • Constituição ratifica xadm-commons como baseline da casa + entrega garantida M2M + maven público (constituição 0.28.0; passo CONST da plataforma libs+contrato, Fase 0/itens 112). A Fase 0 criou o mecanismo (0019) e o contrato (0020); as Fases 1–3/5 publicaram 8 libs (br.com.xadm, registro público fonte.xadm.biz). Agora a constituição fecha a norma:
    • Baseline (§5): app Micronaut elegível consome as libs da casa (xadm-seguranca, xadm-comum-web, xadm-mensageria, xadm-comum-util, xadm-comum-storage, xadm-comum-teste, xadm-comum-powersync, xadm-ingestion-core), não recopia infra transversal. Elegível = server Micronaut; integrador-client (Java 8) e os Flutter ficam fora por stack (fronteira, não débito). A constituição fica com o princípio + nomes; as versões são donas do 0019 e o operável (bloco repositories{}) é da Engenharia (§1.9). Obrigação nova sobre os apps → bump MINOR.
    • Decisão 0021 — entrega garantida M2M at-least-once: a casa entrega via outbox at-least-once (relay sem lock/dedup) → receptor idempotente por chave natural é precondição, não opção; exactly-once (dedup/lock na lib) deferido (REGRA Nº 3). Os 3 mecanismos de entrega (HTTP M2M · fila PowerSync · ZIM/dXpEnvio) e o quando cada um entram em documentacao/integracao.md §Entrega garantida, com ponteiro do §8 da constituição.
    • Decisão 0019 alinhada ao maven público (in-place): o registro migrou de Forgejo Packages privado para público — consumir é leitura anônima (bloco repositories{} sem credentials{}); publicar ainda usa FORGEJO_PACKAGES_*. Tabela dos 8 módulos+versão como prova; nome de módulo reconciliado (xadm-armazenamento→xadm-comum-storage). Receita de consumo em java-micronaut.md §Específico; assimetria publish×read em seguranca.md §Segredos. URL confirmada contra o consumidor real (integrador-server).
    • Glossário: termos novos At-least-once e xadm-commons.
  • Engenharia (Micronaut): recipe de arquivos Garage/S3 (consumidor) (feedback §6 de app). Lacuna: java-micronaut.md não tinha receita de S3-consumer. Padrão validado em produção agora em §Específico — AWS SDK v2 s3 com path-style (forcePathStyle(true); o Garage não resolve bucket por virtual-host DNS) e url-connection-client no lugar do async Netty (o netty-nio-client do SDK conflita com o Netty do Micronaut no classpath), credencial de feature opcional via @Requires(property=…, pattern=".+") (sem a env, o bean do S3Client não sobe e o contexto não estoura), envs GARAGE_* via placeholder single-level no application.yaml, e teste com adobe/S3Mock (não MinIO). Página dev, não rastreada no manifesto → sem bump por si.
  • Engenharia (Micronaut): guardrail das duas ordens no registry de handler (feedback §6 de app). A seção Espelho X-Adm — registry de handler por tabela ganha o caso em que a ordem de aplicação (upsert, pai-FK antes do filho) e a de reversão (delete, filho antes) diferem: o handler expõe ordemPut e ordemDelete separados (um order só quando coincidem). Trava por teste — a reversão por soft-delete não dispara constraint de FK (só marca a coluna), então um ordemDelete errado passa verde e o reorder quebrado só aparece quando um delete real bate no FK em produção; o teste afirma a ordem de reversão explicitamente. Amarra ao anti-deadlock do §Banco (DELETE trava em ordem de scan, não a de put). Impl-referência: integrador-server .ia/010. Página dev, não rastreada → sem bump por si.
  • Engenharia (Micronaut): posse de migração em DB compartilhado — consumidor não migra em prod (feedback §6 de app, iniciativa xadm-mensageria). O owner-writes cobria consumidor que lê via PowerSync; faltava o caso do outbox: consumidor que lê a tabela compartilhada direto (JDBC/Micronaut Data). Regra nova em java-micronaut §Banco — o dono único cria e migra o DDL (remetente/outbox = integrador); o consumidor não tem migração de produção para a tabela alheia (dois Flyways na mesma tabela divergem por checksum; CREATE/ALTER do consumidor recria schema que não é seu). O schema dos testes do consumidor vem de migração test-only (flyway.locations por ambiente: prod = próprias tabelas; teste adiciona db/migration-test com o DDL emprestado), e o deploy é coordenado (dono migra primeiro; consumidor sobe depois). Cross-link em integracao.md §Fronteiras (posse de escrita ≠ posse de migração) + Fase 0 (0019/0020). Páginas dev, não rastreadas → sem bump por si.

Corrigido

  • Infra/Skill: envs do Garage e contrato do /provision (feedback §6 de app). (1) infraestrutura/garage.md listava as envs do app como S3_*; o broker do /xadm-setup (auth) injeta GARAGE_* — derivação <PROVIDER>_<KEY> do CoolifyClient: GARAGE_ENDPOINT/GARAGE_BUCKET/GARAGE_REGION/ GARAGE_ACCESS_KEY_ID/GARAGE_SECRET_ACCESS_KEY (sufixos AWS SDK) — a tabela foi corrigida para os nomes reais. (2) o payload do /provision exige nome e production_url, mas o §3 da skill /xadm-setup e o contrato em central-de-apps.md §Auth listavam só app_id/feature/grupo/cliente_id: sem nome → 400 nome_obrigatorio; sem production_url → secret não injetado (o app cai no gate "mobile/local" e o server fica sem write-key silenciosamente). Ambos declarados como obrigatórios nos dois lados — mesma classe do drift (app_id, feature) que já deu 500 em produção; fecha a inconsistência com o §Auth, que já usava production_url. Bump PATCH da constituição 0.27.6 → 0.27.7 (1 rastreado re-carimbado: templates/xadm-setup-skill.md; garage.md/central-de-apps.md/java-micronaut.md são páginas de site, não rastreadas). O bug garega/typo do broker e AUTH_CIPHER_KEY de 0 bytes são do repo central-backend (Camada 2) → relay ao mantenedor, sem mudança no central.
  • Engenharia (Micronaut): check que pendura por Testcontainers órfão + regra anti-reuse (feedback §6 de app). O bullet de órfão da casa cobria só o Test Resources (sintoma service not available); faltava o modo de falha vizinho: check que arrasta por minutos (mata-e-roda "resolve"), de container do Testcontainers empilhado + daemon Gradle wedged. Verificado que o pile-up real só ocorre em reuse mode (.withReuse(true)/testcontainers.reuse.enable=true — o reusável é excluído do Ryuk de propósito) ou por SIGKILL repetido; na norma da casa (singleton JVM-static) o Ryuk reapa e nada empilha (a atribuição do feedback a "Ryuk órfão" foi corrigida). java-micronaut.md §Armadilhas ganha o reaper de último recurso (./gradlew --stop + docker rm -f $(docker ps -aq --filter "label=org.testcontainers=true") + re-run em camadas; --offline se as deps já estão cacheadas) e a regra nomeada: não usar .withReuse(true) (quem opta aceita o pile-up como custo). Página dev, não rastreada no manifesto → sem bump da constituição.
  • Engenharia (PowerSync): write-back precisa de watchdog (feedback §6 de app). Bug upstream do PowerSync Kotlin core (≤1.14.1): o connector não re-agenda o uploadData após uma falha — o loop de write-back para e só volta ao reconectar, a fila de CRUD acumula e o campo editado nunca sobe, sem erro visível (reproduzido com e2e nos dois clientes Kotlin da casa; mesma classe já corrigida no SDK JS). Contorno da casa elevado a norma em powersync.md §Cliente: cliente Kotlin de write-back roda um watchdog que re-dispara o uploadData enquanto houver CRUD pendente — com marca de remoção quando o powersync-kotlin corrigir upstream. Página dev, não rastreada no manifesto → sem bump da constituição.
  • Engenharia (Micronaut): enriquecedor global de view deve tolerar modelo imutável (feedback §6 de app). O próprio ViewModelProcessor de referência da casa (java-micronaut.md §UI) tinha o bug: fazia putIfAbsent no modelo do controller quando presente, e um controller que devolve Map.of(...) imutável — default de @Controller vindo de lib (xadm-mensageria e futuras, Fase 0/decisões 0019/0020) — estourava UnsupportedOperationException → HTTP 500, só naquelas rotas. A regra antiga "view controller sempre devolve modelo mutável" é inaplicável a controller de lib (não se impõe a @Controller de terceiro). Elevado a princípio geral (§Armadilhas): todo enriquecedor global de view copia para um LinkedHashMap antes de mutar (new LinkedHashMap<>(mv.getModel().orElse(Map.of())) + setModel) — a tolerância mora no enriquecedor, que a plataforma controla; "controller mutável" vira higiene secundária. Snippet de referência corrigido. Página dev, não rastreada no manifesto → sem bump da constituição.

Alterado

  • Engenharia: observabilidade multi-tenant + PII como padrão de casa + javap p/ verificar API (feedback §6 de app). java-micronaut.md §Observabilidade — para tag global de tenant e controle de PII a app faz Sentry.init programático (SentryOptions) e o SentryAppender do logback anexa ao hub: setTag("cliente", …) carimba todo evento → um projeto GlitchTip/Bugsink filtra N tenants (régua: só quando 1 processo = 1 tenant; ponto cego declarado — app compartilhado 1-instância-N-tenants precisa de tag por request, não prescrito). seguranca.md §Segredos — setSendDefaultPii(true) é padrão de casa deliberado (o SDK vem false; a casa liga porque IP/headers aceleram o debug e bug.xadm.biz é self-hosted → o dado não sai da infra, LGPD por não-transferência); numa lib compartilhada (Fase 0) o default alcança todo adotante transitivo → mora como padrão explícito e no CHANGELOG do módulo, nunca flag silenciosa (mesma lógica do enriquecedor global de view). ferramentas.md §javap — verificar assinatura de lib de terceiro sem sources jar (javap -cp <jar-do-cache-gradle> <classe> | grep), a disciplina "verifica antes de afirmar". Páginas dev, não rastreadas no manifesto → sem bump da constituição.
  • Engenharia (Micronaut): threading do controller + guarda ArchUnit que dorme em Java 25 (feedback §6 de app). java-micronaut.md §Específico — controller roda no event loop por default (thread-selection: MANUAL); JDBC (Micronaut Data) e SOAP (JAX-WS) bloqueiam o loop em silêncio (o detector do Micronaut é só do BlockingHttpClient), então "corrigir no aviso" cobre 1 de 3 fontes de I/O. Casa 100% imperativa → default micronaut.server.thread-selection: AUTO (offload automático do imperativo; @ExecuteOn(TaskExecutors.BLOCKING) vira override local), com o ponto cego declarado (AUTO decide pelo tipo de retorno). §Guarda de fronteiras — limite conhecido nº 2: ArchUnit lê bytecode via ASM; em Java 25 (major 69) com ASM < 9.8 / ArchUnit < 1.4.1 a classe é pulada no import e todas as regras de fronteira passam vácuas (guarda dormindo, verde) — piso de versão na baseline + defesa assertThat(importedClasses).isNotEmpty(). Página dev, não rastreada no manifesto → sem bump da constituição.
  • Engenharia (Micronaut): 6 armadilhas de montar lib com Data JDBC + teste de plataforma (feedback §6 de app). java-micronaut.md §Testes — TestPropertyProvider exige @TestInstance(PER_CLASS) (senão as props não entram, silencioso); Testcontainers withInitScript é frágil (shading do commons-io regride, visto no 1.20.4 e reincide na 2.0.x) → aplique DDL por JDBC direto, version-agnóstico; lib de servidor que sobe contexto no teste precisa de micronaut-serde-jackson (runtime), não só serde-api (senão "No bean of type JsonMapper"). §Armadilhas — @ConfigurationProperties aninhado tem de ser construtor-injetado (new inline ignora overrides em silêncio); rota @View precisa de @Produces(MediaType.TEXT_HTML) (senão 406). seguranca.md §Authn/authz — SecurityRule REJECTED mapeia o status sozinho: 401 sem auth × 403 autenticado. Guarda nova no templates/ci.yml (decisão do Gustavo): FALHA se módulo Micronaut declara serde-api + tem @MicronautTest (sobe contexto) + nenhuma impl de runtime — lib API-only sem @MicronautTest é pulada (ponto cego declarado). Bump PATCH da constituição 0.27.5 → 0.27.6 (1 rastreado re-carimbado: templates/ci.yml; páginas de engenharia não são rastreadas).

0.30.0 - 2026-08-04

Adicionado

  • Decisão 0019 — bibliotecas compartilhadas da casa. Infra idêntica cross-app (28 classes duplicadas na auditoria de 2026-08-03) passa a viver num único repo xadm-commons, cada lib = módulo Gradle com SemVer próprio, publicada como artefato Maven no Forgejo Packages; o app consome por versão (implementation("br.com.xadm:xadm-<lib>:X.Y.Z") + registro Forgejo por <VAR>/env). Base de pacote/group br.com.xadm (int-sascar migra de biz.xadm). Rejeitados repo-por-lib e git submodule (versão=SHA). Fim do copy-paste de infra; reduz o risco de drift do IdP Firebase compartilhado.
  • Decisão 0020 — contrato app↔app. Estende a 0012 (rota/método) fechando o corpo: quem recebe dita o contrato — o receptor publica OpenAPI (micronaut-openapi), o consumidor gera @Client+DTOs (openapi-generator) no build; versionamento com compat (janela de deprecação); envelope M2M (Bearer <APP>_API_TOKEN por callee, RFC-7807+code, 1 token/instância); status por caso (200-sempre onde PowerSync/fila exige, status real no par novo sem fila); request-id/MDC propagado entre hops; contract test consumidor×spec. Riscos §1.2 (capacidade do openapi-generator, janela de deprecação) declarados para a Fase 3.

Alterado

  • Engenharia: padrões de plataforma da leva 0019/0020. java-micronaut.md — HTTP app↔app dentro de Micronaut é @Client+@Retryable (OkHttp só em plain-Java), resolvendo a contradição com o transversal; camadas de lib em br.com.xadm.comum.* (pt-BR); nova seção registry de handler por tabela (XadmTableHandler<E>) como a forma anti-god-node de modelar o espelho X-Adm (refactor da Fase 4); o "gap declarado" de request-id/MDC vira norma (filtro no xadm-comum-web). integracao.md — nova tabela Arestas concretas (call-graph) (caller→callee : transporte : endpoint : auth). stack.md e powersync.md alinhados ao 0020 (OkHttp×@Client; o callback sempre-200 é o caso que exige 200 no status-por-caso). Constituição 0.27.5 (PATCH — sem artefato rastreado alterado; ADRs e páginas de engenharia são conteúdo de site, não templates do manifesto).
  • /xadm-release: a lista de venenos da skill passa a nomear redirect (2>&1, > arquivo) ao lado do pipe — comando com redirect é composto e não casa allow-list por ferramenta. O gap era só a skill rastreada: a 0.27.3 documentou o sonde-bare em agentes.md/ferramentas.md (páginas de site, que não vão pro repo de app), mas o agente no repo de app só enxerga a SKILL, onde o veneno "pipe" não mencionava o 2>&1. java -version tenta o reflexo de anexar 2>&1 (escreve versão no stderr) — o bare já casa java * e o harness captura o stderr. Espelhado na cópia local .claude/skills/xadm-release/SKILL.md. Constituição 0.27.4 (PATCH — templates/xadm-release-skill.md re-carimbado).

0.29.7 - 2026-07-31

Adicionado

  • /xadm-release: pré-flight passa a detectar teste OS-desabilitado e avisar sobre CI-verde. @DisabledOnOs(OS.WINDOWS)/@EnabledOnOs(OS.LINUX) é pulado no check local (dev = Windows), o gate fica verde cego e a divergência só aparece na CI Linux, pós-tag (caso real: v0.0.5 tagueada, CI vermelha depois). Agora o pré-flight Java procura essas anotações e, se acha, declara no relatório que N testes não rodaram (degradado, não silêncio) + recomenda rodar ./gradlew check no container ci-java:<v> (paridade Linux). O gate CI-verde é aviso, não bloqueio (o Coolify auto-deploya no push, item 62 — gatear o deploy é futuro): o relatório lembra de confirmar a run verde. Allowlist ganhou docker run *. Doutrina nova em ci-testes.md (§«Teste OS-desabilitado é gate cego no pré-flight») + ponteiro em java.md §Testes. Constituição 0.27.2 (PATCH — templates/xadm-release-skill.md re-carimbado).

Alterado

  • /xadm-release: allowlist do .claude/settings.json passa a ser por ferramenta (git *, node *, npm *, python */python3 */py *, mkdocs *, docker *, rtk *), espelhado nos namespaces Bash(...) e PowerShell(...), no lugar do allow-list por prefixo exato de argumento. O prefixo exato era quebradiço — cada variação de forma (interpretador python/py, npm install vs npm i, flag antes do subcomando, npm de dependência do lint-mermaid ausente) voltava a pedir prompt e travava o release no meio. Wildcard por ferramenta mantém o escopo (só as ferramentas do release) e elimina a fragilidade; o curl segue escopado de propósito. A garantia "só commita no release" continua sendo a REGRA Nº 2 (conduta), não o prompt. Venenos de comando composto (pipe, $(...), multi-linha) seguem valendo: ferramentas.md §Higiene e agentes.md §Loop de verificação ganham o mesmo padrão (sonde toolchain com comando bare, um por chamada — java -version, não java -version 2>&1 | head). Constituição 0.27.3 (PATCH — templates/xadm-release-skill.md re-carimbado).

0.29.6 - 2026-07-31

Adicionado

  • /xadm-release: gate de container para stack Docker Compose (serviço self-hosted, ex. PowerSync) no pré-flight. docker build prova que a imagem compila, não que a stack sobe — bootstrap (load de sync rules, migração) e healthcheck só rodam no up, e o primeiro a subir a stack de verdade é o Coolify em produção. Gate: docker compose -p <slug>-preflight up -d --wait (volume novo, healthchecks que a constituição §5 já obriga) + down -v; substitui o docker build se a imagem é pull (sem Dockerfile), soma se o repo builda. Opcional e degradável (sem Docker / falta .env / porta em uso = ambiente, não bug — declara no relatório). Allowlist ganhou docker compose * nos dois namespaces. Ponteiro em powersync.md §Deploy. Constituição 0.27.1 (PATCH — templates/xadm-release-skill.md re-carimbado).
  • Kit de UI: dois slots opcionais no navbar (layout.html) — ${appModo} (pill de ambiente à direita, renderiza quando presente) e ${appVersao} (versão discreta) — nos dois fragments (navbar e navbarComMenu), com estilo no custom-theme.css. Mata o hand-roll que já produzira drift (um app forkou o layout.html, outro pôs no conteúdo/navMenu). Receita em java-micronaut.md §UI com o ViewModelProcessor de referência que popula os dois em toda view: appModo só quando o ambiente ≠ produção (o layout não compara string), appVersao da config; detecção de prod e versão ficam app-specific. Constituição 0.27.0 (MINOR — funcionalidade nova, compatível; layout.html e custom-theme.css re-carimbados).
  • Armadilhas de render Thymeleaf em java-micronaut.md §Armadilhas: Thymeleaf standalone não tem os objetos implícitos de web (param/session/request, são do Thymeleaf-Spring) — controller lê do request e passa ao modelo; link com variável de path é @{/x/{id}(id=${..})}, não concatenação de String. Cura da família: bullet em §Testes prescrevendo o teste @Client que faz GET e renderiza de verdade (o ./gradlew check não renderiza template) + ponteiro em ci-testes.md §E2E (view server-render é fronteira de contrato).

Alterado

  • Publicidade de view sob micronaut-security: @Secured(SecurityRule.IS_ANONYMOUS) na rota vira o idioma primário (rota-exata por construção, sem allowlist paralela pra driftar); isPublicView() na SecurityRule custom fica para allowlist programática/dinâmica; segue proibido intercept-url-map greedy. Rota de view sem decisão explícita = 401. Atualizado em java-micronaut.md §UI, seguranca.md §Authn/authz e no checklist do revisor.

0.29.5 - 2026-07-30

Adicionado

  • Armadilha Gradle 9.5 + shadow + application: jar base e shadowJar com o mesmo app.jar quebram startScripts/build. Com o plugin application, startScripts/distZip consomem o output do jar; dividir o nome app.jar entre jar base e shadowJar faz o Gradle 9.5 acusar "uses this output of task ':shadowJar' without declaring … dependency". ./gradlew check é imune e o Coolify usa o shadowJar direto (produção não quebra) — morde só o dev que roda ./gradlew build. Cura: app.jar só no shadowJar (dê nome/archiveClassifier distinto ao jar base), ou startScripts { dependsOn(shadowJar) }. Bullet em java-micronaut.md §Armadilhas + nota cruzada no §Deploy; ponto cego declarado (sem gate — nada no pipeline roda build). Página dev não rastreada → sem bump.

Corrigido

  • Guarda de placeholder aninhado (templates/ci.yml) reprovava comentário — falso-positivo. A regex \$\{[^}]*\$\{ rodava em toda linha do application.yaml/.properties, inclusive comentário: um app que documenta a armadilha (# não use ${A:${B}}, full-line ou inline) fazia o CI falhar com ::error:: — o gate contra a armadilha proibia falar dela. Agora o step tira o comentário antes da regex (# no início ou pós-espaço) — o Micronaut não resolve comentário, então ignorá-lo é semanticamente correto; # literal em value (url http://a#b, properties k=a#b) fica intacto, e placeholder aninhado real (mesmo com comentário na cauda) segue reprovando. Constituição PATCH 0.26.6→0.26.7 (re-carimbo de templates/ci.yml).

0.29.4 - 2026-07-30

Adicionado

  • Norma "servidor local de verificação pega porta efêmera" (loop de verificação): subir servidor para conferir — app em modo debug, E2E local, preview — não fixa porta (8080/8000); pede porta efêmera e lê a URL que o processo imprime. Porta chutada colide com processo vivo ou zumbi e o sintoma engana (bind falha, ou responde outro servidor com build velho — episódio do mkdocs serve na 8000). Norma em agentes.md (§Loop de verificação) + ponteiro em ci-testes.md §E2E; mecânica por stack: Micronaut (MICRONAUT_SERVER_PORT=-1, ler Server Running:; EmbeddedServer.getURI() no E2E) em java-micronaut.md, Flutter web (flutter run já é random + imprime a URL; antipadrão é fixar --web-port) em flutter.md. Páginas dev não rastreadas → sem bump.

Alterado

  • Release da /xadm-release roda sem prompt de ponta a ponta — tag de 1 linha + allowlist Python por SO: a tag anotada era multilinha (string com \n não casa regra de prefixo) e pedia autorização em todo release — agora é resumo de uma linha (git tag -a v.. -m "v.. — resumo", o detalhe fica no CHANGELOG), casa git tag *, zero prompt. E o allowlist passou a cobrir o interpretador Python por SO (python3 no Linux, python/py no Windows) nos dois namespaces (Bash(...)/PowerShell(...)) — sem isso os gates do pré-flight pediam prompt um a um na máquina do dev. Vale para todos os apps: re-derivam a /xadm-release via /xadm-docs e alargam o próprio allowlist. Constituição PATCH 0.26.5→0.26.6 (re-carimbo de templates/xadm-release-skill.md).

0.29.3 - 2026-07-30

Adicionado

  • Seed de teste de endpoint Micronaut: @BeforeEach/@Sql, nunca no corpo do @Test + HTTP (loop §6, feedback de app): @MicronautTest default (transactional=true) faz rollback do corpo do teste, e o servidor embutido atende em outra conexão → não vê o não-commitado; o default SEPARATE_TRANSACTIONS commita o @BeforeEach, por isso seed lá fica visível e seed no corpo some (teste verde medindo vazio). Regra + mecânica em java-micronaut §Testes e fixtures, ponteiro em ci-testes §E2E. transactional=false não é a cura reflexa (quebra JDBC cru do @BeforeEach). Ponto cego declarado: nenhum gate estático pega. Páginas dev não rastreadas → sem bump.

0.29.2 - 2026-07-30

Corrigido

  • Placeholder aninhado no application.yaml do Micronaut NÃO funciona — receita da 0.29.1 corrigida (loop §6, bug de produção): o item 101 documentou o fallback de migração de env inter-app como ${NOVO:${ANTIGO:}} "o mesmo do SENTRY_DSN" — mas o SENTRY_DSN mora no logback.xml (motor que aninha, operador :-) e o Micronaut é OUTRO: o DefaultPropertyPlaceholderResolver casa o primeiro }, não o balanceado, então ${A:${B}} resolve pra lixo (token/URL inválido) sem erro de build → quebrou apps em produção. Correção: a receita de migração de env agora prescreve rename atômico ou resolve-in-code (duas props single-level), nunca cascata no yaml; armadilha nomeada em java-micronaut §Armadilhas (com a distinção logback×Micronaut); guarda nova no templates/ci.yml (grep de ${…${ só em app Micronaut — Spring aninha certo) pega reincidência no CI do próprio app. Constituição PATCH 0.26.4→0.26.5 (re-carimbo de templates/ci.yml).

0.29.1 - 2026-07-28

Adicionado

  • Convenção — ENV de auth/URL inter-app nomeada pelo DESTINO (loop §6): um Bearer/base-URL entre apps mora em <APP>_URL + <APP>_API_TOKEN ("como chamar o <APP>"); cada app valida o próprio <SELF>_API_TOKEN, cada chamador seta <DESTINO>_*. Token tem nome global com mesmo valor no callee (valida) e nos callers (mandam) → mapa de segredo coerente e sem colisão num app que é chamador e chamado (o API_BEARER_TOKEN homônimo com valor diferente por servidor foi o bug N-hop da integração Thoms). Um token por callee (trade-off declarado: revogar 1 caller rotaciona B pra todos). Migração de env sem quebrar produção por rename atômico ou resolve-in-code.

Regra no piso (seguranca.md §Segredos), receita de config em java-micronaut §Específico, ponteiro em integracao.md. Escopo: só HTTP inter-app (+ terceiros PARCEIRO_*); DB/ Firebase/observabilidade seguem como estão. Páginas dev — sem bump. - Java/Micronaut — 3 refinamentos vindos de feedback de app (loop §6): (a) convenção de rota /api/<origem>/<recurso> para callback/poke de um app a outro (<origem> = app chamador; integrador é o caso comum → /api/integrador/produto): /api/** é Bearer (ROLE_API), então o prefixo dá auth uniforme e contrato de rota previsível (§Específico + ponteiro em integracao.md §Transporte); (b) view pública se libera na SecurityRule da própria view (isPublicView(), rota exata), nunca por intercept-url-map greedy — o ConfigurationInterceptUrlMapRule roda em ordem -100 e ganha da SecurityRule custom, então um pattern /*//** com ALLOWED vaza rota irmã (ex. /admin) sem erro nem sinal (refina o piso em seguranca.md §Authn/authz + checklist do revisor; receita em java-micronaut §UI); (c) read-model de tabela de terceiro marca @Nullable o campo que a fonte pode deixar null — sem isso o Micronaut Data estoura "non-null constructor argument" e derruba o job inteiro (1 registro anômalo mata o sweep); trate ausência como anomalia (Sentry/WARN), não falha fatal (§Banco). Páginas dev, não rastreadas no manifesto → sem bump. - Constituição §6 — ponteiro para a higiene do .claude/settings.json (0.26.4) — um app perguntou se a constituição recomenda allow-list de permissões e concluiu "não" por olhar só a constituição-arquivo; a regra existe, mas vive em engenharia/ferramentas.md + app-novo.md (versionar o settings.json com padrões LARGOS dos comandos comuns — Bash(git *), Bash(mkdocs *), Bash(python3 scripts/*)… —, pessoal em settings.local.json gitignored; grant hiper-específico solta a tag da release via --amend). §6 ganhou um bullet-ponteiro (com a âncora da regra) para fechar a fresta de descoberta de quem só lê a constituição. Bump text-only.

0.29.0 - 2026-07-28

Adicionado

  • Gradle assado na imagem-fábrica ci-java (constituição 0.26.2) — feedback §6 (bi-transporte-xls v2.1.3): em cache frio o ./gradlew baixava a dist do Gradle de host externo e um UnknownHostException transiente pintava o gate de vermelho aleatório — a exceção acidental ao "zero download por run" (decisão 0003). Agora a ci-java pré-aquece a dist do wrapper (versão escalar gradle na matriz-baseline.json, casa-inteira) sob GRADLE_USER_HOME=/opt/gradle, no caminho-hash canônico; o ci.yml passa a cachear só deps. Egress do runner medido aberto (Maven Central, plugins, dist alcançam) → sem 2º furo. Adendo na decisão 0003: pareamento com gradle-wrapper.properties (versão E variante -bin), premissa distributionBase, ordem de bump anti-deadlock. Validado no runner (cold-cache não baixa; o smoke da fábrica assere a dist assada em /opt/gradle/wrapper/dists/); os 8 apps Java já pinam a URL canônica.
  • Manifesto EOL-agnóstico + guarda pré-commit da /xadm-release (constituição 0.26.3) — o gera-manifesto hasheava bytes crus (read_bytes), então gerá-lo no Windows (autocrlf/CRLF) divergia do Linux (LF) e deixou um drift subir silencioso no 0.26.1 (a entrada do xadm-release-skill.md não batia nenhum commit; o CI não-gating do item 62 só avisa). Agora hash_conteudo normaliza \r\n→\n antes de hashear (rastreia conteúdo, não bytes; binário preservado), com testa-gera-manifesto.py novo no CI. E o §8 da /xadm-release roda gera-manifesto --check como último passo pré-commit, fechando a race carimba→edita→commita.
  • Norma anti-deadlock no batch upsert (java-micronaut §Banco) — feedback §6 de app (Onpetro bi-comercial-xls v1.4.5, deadlock 40P01 real em produção): o diff-aware (ON CONFLICT DO UPDATE … WHERE … IS DISTINCT FROM) tira row lock na arbitragem do conflito, antes de avaliar o WHERE — linha "inalterada" trava igual, e duas transações concorrentes com dimensões sobrepostas em ordens diferentes deadlockam. Bullet novo: todo batch upsert ordena as linhas pela chave natural antes de executar (executeBatch → sorted com nullsFirst; COPY + TEMP TABLE → ORDER BY no INSERT … SELECT), com a nuance multi-tabela e o teste proporcional (capturador de batch afirmando a ordem). Segundo feedback (bi-transporte-xls v2.1.3, mesmo risco corrigido preventivamente) completou o bullet: transação de mescla multi-fase (upsert + DELETE seletivo) serializa a fase de banco com pg_advisory_xact_lock(<chave fixa do app>) — o DELETE trava em ordem de scan, que ORDER BY não controla — e a declaração de que o deadlock em si não é testável deterministicamente: testa-se a pré-condição estrutural. Página não rastreada — sem obrigação nova.
  • Constituição 0.26.0→0.26.1 — allowlist Windows na /xadm-release e gitattributes-java (feedback §6 de app: release rodado em Windows nativo). (a) O bloco "Permissões — release sem prompt" do template xadm-release-skill.md só tinha regras Bash(...); no Windows o harness usa a ferramenta PowerShell, cujo namespace de regra é próprio (PowerShell(...)) — nenhuma regra Bash casa, e todos os gates promptavam na mão. O bloco Bash também não cobria os gates (só git + echo) — os gates promptavam no Linux idem. Agora os dois blocos são simétricos: git + gates de código/container/docs por stack ("mantenha só as da sua stack"; curl escopado em docs.xadm.biz). Cortados da proposta original os Get-Content/Select-String/etc. — o template já manda inspecionar arquivo pela ferramenta Read, nunca shell. Mais dois venenos Windows na lista: prefixo de env inline ($env:X='...'; cmd é composto — cura estrutural: "env" no settings.local.json) e sufixo ; echo "EXIT=$LASTEXITCODE" (idem; o harness já reporta exit code). Segundo feedback (bi-transporte-xls, ~15 prompts num release Windows) confirmou o diagnóstico e somou a regra python -m mkdocs build * (Windows raramente tem mkdocs no PATH), o par curl … http://localhost:8000/* pro preview local (o outro gerador recorrente de prompt) e a nota "URL antes do -o" no download de validadores; a receita git archive … | docker build - proposta lá foi rejeitada com o porquê no template (pipe é veneno de prompt, PowerShell 5.1 corrompe stream binário no pipe, e buildaria o HEAD em vez da árvore em curso) — a cura canônica do CRLF é o .gitattributes. (b) Template novo rastreado e opcional templates/gitattributes-java (gradlew text eol=lf): checkout Windows com autocrlf deixa o gradlew com CRLF, o docker build COPYa a árvore como está e o /bin/sh do Alpine morre com ./gradlew: not found — o --chmod=0755 do Dockerfile cura o bit de execução, não o fim de linha. Referenciado em constituição §3, templates.md (tabela + seção Container), app-novo.md §7, java-micronaut §Deploy, gate de container do release e mapeado pela /xadm-docs (gitattributes-java → .gitattributes). Bump PATCH; 4 rastreados re-carimbados (xadm-release-skill.md, xadm-docs-skill.md, dockerfile-java, gitattributes-java).

0.28.0 - 2026-07-23

Adicionado

  • Constituição 0.25.1→0.26.0 — plataforma de integração ganha topologia e fluxos em SVG autoral, o livro ganha capa obrigatória, e a taxonomia um 5º papel: app compartilhado. Pedido interno: a diretoria precisa ver a plataforma em uma tela (X-Adm no cliente, nuvem, terceiros). A documentacao/integracao.md era só prosa — sem figura, o chefe tinha que ler taxonomia + tipos + fronteiras para montar a topologia na cabeça. Adicionado no topo da página: §Topologia — depois de rodadas contra o layout automático (Dagre inverte ranks por ciclo/aresta; block-beta dá grid mas sem rowspan nem título de composite), a figura final é SVG autoral (docs/assets/topologia-integracao.svg — base visual desenhada pelo Gustavo, paleta re-tokenizada para X-Adm royal #064490 + tipografia Inter, decisão 2026-07-22), embutida via --8<--, com Mermaid-base colapsado logo abaixo (??? info — pymdownx.details habilitado no mkdocs.yml) como fonte semântica lintada: mudou a topologia, atualiza o base primeiro e espelha no SVG. Norma nova: bullet "Figura autoral (SVG) — a exceção de capa" na constituição §5 + receita completa em templates.md §"Figura autoral em SVG" (as armadilhas reais: linha em branco ou <svg> sem wrapper <div> fatiam o SVG em <p> — python-markdown não trata <svg> como bloco; <style> sem prefixo vaza pro site; <img> não herda a webfont, daí --8<--), com o ponto cego declarado: nenhum gate valida o SVG em si — quem é lintado é o Mermaid-base. CSS auxiliar em docs/stylesheets/xadm.css: .md-typeset .mermaid com overflow-x: auto (vale para os demais diagramas Mermaid do site). Rótulos da figura (por pedido interno): Hub = "Plataforma de Integração" + int.<cliente>.xadm.biz + ps.<cliente>.xadm.biz; banco = "PostgreSQL Cliente ¹" com nota de rodapé "Um banco de dados por cliente"; apps específicos = "Apps Específicas" (agrupa pied e excel); app compartilhado = "Apps Compartilhados". Papel novo — App compartilhado: 1 instância multi-tenant que nasce quando o parceiro é global (mesmo endpoint para N clientes, cada um com sua credencial); não escreve no barramento (mantém §Fronteiras "um dono por tabela" intacta), conversa com o hub, que grava. Combina tipos ③+④ quando o parceiro é bidirecional. Motivo concreto: sascar.xadm.biz — o hub envia MDF-es abertos (chave/placa) ao app compartilhado, que faz polling na Sascar do cliente com credenciais próprias; quando o motorista chega ao destino, devolve encerrado ao hub, que atualiza o status; o cliente Java lê via PowerSync e importa no ERP. Reator removido da taxonomia — reservado para uso futuro. O único caso concreto (thoms → e-commerce) foi confirmado como REST puro ao ler a UI do app rodando (POST /sync/erp em webstorm-ecom.thoms.xadm.biz, sem PowerSync no servidor). A definição "não recebe de fora, consome o banco via PowerSync, empurra para fora" contradizia o caso real; thoms virou app específico (ecommerce.<cliente>) que recebe do hub via REST e faz POST no parceiro (WebStorm). O papel Reator fica reservado para casos futuros de notificação (email, push, alertas Telegram) — quando aparecer, volta à taxonomia. Aplicado em: figura (zonas Servidor X-Adm Cliente com ERP X-Adm ↔ Integrador Cliente — as saídas partem do Integrador, label "push tempo real"/"write-back" — / Terceiros / Nuvem 2x2 / Usuários com mockups de navegador e celular; Sascar → Apps Compartilhados via pull · MDF-e, odômetro e Plataforma ↔ Compartilhados via REST · baixa MDF-e, odômetro; PowerSync PG → Usuários e PG → Integrador; write-back dos Usuários por fora; sem badges numerados — os 4 tipos são numerados no texto, não na figura); §Taxonomia (linha Reator removida, App consumidor menciona Web/Mobile); §Os 4 tipos (item 4 reescrito; admonition "Reator — reservado para uso futuro"); §Fronteiras (regra de tabela de status separada agora fala em app específico); §Transporte (PowerSync serve consumidor + cliente Java; hub↔apps específicos é REST); tabela de prefixos (<app>_ cita xls_*, thoms_envio); constituição §8 (Taxonomia sem reator; PowerSync bidirecional distingue consumidor de app específico REST; fronteira §7 sem reator; contrato sem "packaging de reator"); glossário (seção Reator removida); engenharia/powersync.md (2 lugares). Rejeitado replicar sascar.<cliente> (N deploys idênticos batendo na mesma API, isolamento sem benefício), rota no hub (reintroduz o erro (a) da §integracao.md), e app compartilhado escrevendo no barramento (dois donos à tabela-espelho — quebra §Fronteiras). Terminologia — rename da taxonomia: "vertical" → app específico; "adaptador compartilhado" → app compartilhado (feedback interno: a taxonomia da plataforma agora fala em apps por papel, não em jargões técnicos). Aplicado em integracao.md (§Taxonomia + §Os 4 tipos + §Fronteiras + §Transporte), constituição §8, glossário (2 seções + Owner-writes), decisão 0018 (título + corpo; slug preservado 0018-adaptador-compartilhado.md para não quebrar links; nota de terminologia dentro do arquivo) e nav do mkdocs. Também trocado "shape" por "tipo de fluxo" na página e no §8 — "shape" era jargão a mais para a equipe. "Shape" segue em outros contextos da constituição (formato de resposta HTTP, shape de fixture), onde não colide. Trocado "pola a API" por "faz polling na API" — anglicismo técnico consagrado, "pola" era gíria. Removido bi-commons da doc normativa (integracao.md, constituição §8, glossário, powersync.md): a biblioteca não foi implementada ainda, e a doc presumindo sua existência viola §1.6 (não inventar). Decisões 0007 e 0014 preservam a menção como registro histórico da intenção — o dia que a biblioteca existir, volta à norma. Adicionado: decisão 0018 + linha na tabela §Taxonomia + nota "combinação ③+④" em §Os 4 tipos; §8 da constituição inclui app compartilhado na taxonomia (anti-drift §8). Detalhe operacional (auth por cliente, schema do banco de trabalho, contrato exato com o hub) fica na arquitetura-alvo do integrador (§1.9: concreto no repo dono). §Fluxos novo na integracao.md: 3 SVGs autorais verticais (um por fluxo, cima→baixo: fluxo-saida.svg, fluxo-entrada.svg, fluxo-ciclo-mdfe.svg, mesma UI da topologia; escolhidos contra a variante combinada horizontal, que perdia legibilidade no downscale da coluna — a vertical cabe inteira, fonte no px desenhado) — saída X-Adm→Usuários (rotas tempo real e batch XLSX), entrada Externo→X-Adm (PIED, com devolução de status) e o ciclo completo do MDF-e (Sascar) — cada um com a faixa COMO ACOMPANHAR (exceções de software vão automaticamente ao GlitchTip com notificação à equipe; exceções de operação geram e-mail à equipe do cliente). Capa obrigatória no livro (cap. 0): a constituição §2 passa a exigir que a Documentação Completa abra com figura de topologia + fluxos principais com "como acompanhar" (Mermaid por padrão, SVG autoral permitido via §5); templates/projeto-index.md ganha a seção "0. Capa" com o marcador HUMANO — projeto novo nasce com capa, app existente adota por migração oportunista (§1.6). Rename editorial: "hub" → "Plataforma de Integração" em toda a doc normativa (integracao.md com desambiguação plataforma-conjunto × Plataforma-serviço, constituição §1.9 e §8, glossário, powersync.md, decisão 0018 — decisões 0007/0014/0016 ficam, são histórico) e "empurra" → "envia" (registro). Bump MINOR — a capa é obrigação nova sobre o livro (o resto é permissão + esclarecimento + rename); 0 rastreados mudaram (projeto-index.md é scaffold de uma vez, não rastreado — quem o cobra é a constituição §2, não o manifesto).

  • engenharia/java-micronaut.md §Banco — bullet novo: chave-de-mudança single-writer via @DateUpdated. Feedback de app (§6). A doc já tinha o par multi-writer (trigger Postgres, entidade não mapeia — evita null-wipe) mas nada para o caso simples: app dono único da tabela onde @DateUpdated do Micronaut Data resolve de graça (carimba a cada write, rejeita valor externo, serve ORDER BY updated_at a consumidor externo). Bullet novo entre §Espelho fiel e §multi-writer fecha a matriz "dono da escrita → onde vive o bump" (single → ORM mapeia; multi → trigger, entidade não mapeia). Sem bump — página não rastreada; mudança de conselho, não de contrato.

  • /xadm-release pré-flight — aviso soft do bloco 'Publicar Release' comentado. Feedback de app (§6). O templates/release.yml traz o bloco de asset e o permissions: contents: write comentados por default (OPT-IN "só app que distribui artefato"). App que copiava o template e distribuía artefato mas esquecia de descomentar → valida-release.py verde, tag pushada, aba Releases vazia, jar sem asset — descoberto só quando o operador ia baixar. Irmão silencioso do item 95 (descomentar sem trocar container: é barulhento — guarda + 403 — ; NÃO descomentar quando deveria era o cego). Adicionado bullet no pré-flight do templates/xadm-release-skill.md: se .forgejo/workflows/release.yml existir, lê pela ferramenta Read e procura DOIS marcadores literais (# - name: Publicar Release no Forgejo e # contents: write); se algum aparecer comentado, avisa no relatório final (não bloqueia — app só-deploya/doc-only ignora legitimamente). Sem release.yml → skip. Rejeitada a alternativa "cobrar campo distribui_artefato no app.json" (bandeira nova a manter, sem gate para cobrar); o próprio release.yml carrega os marcadores do template quando foi copiado → detecção sem inferência, sem falso positivo. xadm-release-skill.md re-carimbado; cópia do central (.claude/skills/xadm-release/SKILL.md) inalterada — o central não distribui artefato (site MkDocs, deploy por push).

0.27.2 - 2026-07-20

Alterado

  • Constituição 0.25.0→0.25.1 — o checa-rotas.py confere rota, não forma: a fronteira agora é princípio, não nota de rodapé. Feedback de app: publicar um arquivo-fato de contrato ao lado de um OpenAPI cujo schema mentia (required/enum/nullability ≠ runtime) passou os dois gates — quem pegou foi o teste de fio, não a toolchain. O ponto cego já estava declarado ("o check não sabe nada sobre corpo — só rota e método"), mas como nota mecânica. Avaliada e recusada a extensão a um check de schema-drift prosa×spec: o spec gerado é oráculo confiável de rota (path/verbo saem fielmente das annotations) mas frágil de forma (não vê validação imperativa, default, serialização custom), então um gate prosa×spec passaria verde com ambos mentindo juntos (o teste tautológico do item 88 por outro caminho) ou acusaria a prosa correta quando ela acerta o runtime e o spec erra — falso conforto. O oráculo independente de forma é o runtime = teste de fio, que já é piso §6 (E2E na fronteira de contrato). Reforço em três lugares: §Contrato REST (o princípio "rota é checável contra o spec, forma não é"), a docstring do checa-rotas.py, e o padrão do arquivo-fato em publicar-docs.md (o autor que escreve schema em prosa "a partir do código" atesta a forma por fio, não por gate). Sem mudança de comportamento — esclarecimento do escopo já existente; o checa-rotas.py (rastreado) foi re-carimbado.

Adicionado

  • Constituição 0.24.1→0.25.0 — o kit de UI ganha app.css: CSS de componentes do app, sempre linkado. O head(title) do layout.html passa a linkar /css/app.css depois do custom-theme.css, e a /xadm-docs scaffolda o arquivo de templates/app.css na criação do app. Dois CSS, dois donos (§1.9): o custom-theme.css é a identidade da casa — rastreado, verbatim, não se edita por app; o app.css é dos componentes do app (timeline, tiles, stepper, badges de domínio), não é rastreado e o central nunca o re-deriva. A 0015 fechou o slot de <head> com razão (dois <head> quebram toda view) mas deixou o app sem lugar para o CSS dele, e a saída prescrita — "escreva o <head> à mão nessa tela" — não escala para 16 telas. Não era hipótese: dois apps já contornavam a lacuna, de formas diferentes — o int-pied emitindo o <link> no <body> via fragment local, e o bi-comercial-xls forkando o layout.html. O fork é o dado que decide: o layout forkado ainda carregava um fragment morto que o central removera há duas versões — fork sai do radar do /xadm-docs e para de receber correção. Os dois apps escolheram o mesmo path (/css/app.css) independentemente: o kit passa a prescrever o que já era fato. Detalhe em 0017 e na receita Java/Micronaut §UI.

    Nenhum app quebra: as views seguem chamando head('Título') com um parâmetro, e não há segundo <head> — a 0015 continua valendo, slot de markup arbitrário no <head> segue não existindo. Migração: app que usa o kit e re-derivar o layout.html precisa ter public/css/app.css, nem que vazio (sem o arquivo, 404 em toda página). Os dois contornos atuais continuam funcionando — ninguém precisa correr.

Corrigido

  • Constituição 0.24.1→0.25.0 — templates/mkdocs.yml declarava exclude_docs: duas vezes: o privado/ sumia em silêncio. Bug introduzido na 0.24.0 (arquivo-fato): o segundo exclude_docs:, 48 linhas abaixo do primeiro, sobrescrevia o bloco de cima — e aquele bloco excluía privado/, o segmento reservado a confidencial de cliente (§5). Todo app que re-derivasse o template verbatim passaria a publicar docs/privado/ no site. YAML não avisa sobre chave duplicada, e o --strict fica verde por estar certo: para o MkDocs a chave apagada nunca existiu. Repro: python3 -c "import yaml; print(yaml.safe_load(open('mkdocs.yml'))['exclude_docs'])". Cura: os dois blocos fundidos num só, com o porquê no topo.

  • O checa-nav.py passa a reprovar chave YAML repetida no mkdocs.yml — a classe, não o sintoma: dois nav:, dois plugins:, dois theme: falham igual. Parser de indentação stdlib (o check roda no gate de código do app, onde não há PyYAML — e yaml.safe_load não serviria: ele é quem come a duplicata em silêncio; para vê-la é preciso olhar o texto). Verificado nos dois sentidos: reprova o templates/mkdocs.yml real de antes da correção (exclude_docs na linha 91, já declarada na 43) e passa nos 7 mkdocs.yml reais da casa, sem falso positivo. A guarda de privado/ do docs.yml continua sendo a rede de baixo, mas só pega o sintoma daquela vez, depois do build e só nos apps que a têm. Detalhe em CI, gates e testes.

  • Constituição 0.24.0→0.24.1 — docs.yml e release.yml: o container: e o bloco Java agora se amarram. O container: (topo) e o bloco de geração de API (~120 linhas abaixo, comentado) eram acoplados em silêncio: descomentar o bloco sem trocar o container: de ci-base para ci-java:<v> derruba o workflow no push com JAVA_HOME is not set. O defeito é invisível a todos os gates — o ci.yml já vem com ci-java e passa verde, e o pré-flight da /xadm-release builda na máquina do dev, onde há JDK; só o push descobre, e no release.yml a tag já está no ar. Pior: a mensagem nativa do gradlew manda setar JAVA_HOME, que é o antipadrão da casa (decisão 0003 — a toolchain vem da imagem, sem setup-java). Agora o comentário do bloco cobra a troca do container: e uma guarda (command -v java) transforma o erro numa instrução que aponta o container:. O release.yml levou a mesma guarda: ele já tinha o comentário e o mesmo defeito.

0.27.1 - 2026-07-17

Corrigido

  • Constituição 0.24.0→0.24.1 — o Dockerfile canônico manda o app rodar como USER app num WORKDIR que é de root. O WORKDIR /app cria a pasta como root (o --chown do COPY é do jar, não dela); depois do USER app (uid 1001) qualquer escrita sob /app — log em caminho relativo, cache, upload, diretório de trabalho — falha com AccessDeniedException em runtime, na produção: o build passa, o teste passa e o docker build do pré-flight também. Reproduzido com Docker de verdade (mkdir /app/logs e touch /app/x → Permission denied). No bi-comercial-processador-xls isso derrubou todo o processamento (logback em logs/app.log, createDirectories(/app/logs) falhando antes de qualquer trabalho). Agora o templates/dockerfile-java traz, no ponto exato antes do USER app, a regra + a receita comentada (RUN mkdir -p /app/logs && chown app:app /app/logs), com o aviso de não usar chown app:app /app inteiro (o app sobrescreveria o próprio app.jar — metade do motivo de rodar não-root) e de conferir o arquétipo antes (servidor loga em stdout, quem loga em arquivo é CLI/worker — logs/ num servidor costuma ser logback.xml do arquétipo errado). O java-micronaut.md §Deploy ganha o bullet-dono; o coolify.md troca o exemplo /app/logs de Storages (arquétipo errado) e documenta a herança de dono: volume nomeado herda o dono do diretório da imagem — se o diretório não existir, nasce root e quebra igual —, bind mount não herda (o dono no host é que precisa ser 1001:1001).

0.27.0 - 2026-07-16

Adicionado

  • Constituição 0.23.3→0.24.0 — arquivo-fato ganha hospedeira obrigatória, e sai do build com exclude_docs:. A 0.23.0 mandou o dono publicar o fato, mas a receita dizia que ele "vive em docs/public/ como qualquer página" e leva not_in_nav: — duas metades que não fecham: sem uma página que o embuta, o fato só existe no raw/, invisível no site do dono. E not_in_nav: não esconde, só cala o aviso de órfã: o fragmento continua sendo buildado, ganha um H1 inventado do nome do arquivo ("Xadm ingest"), fica alcançável por URL e entra na busca — procurar "Envelope" devolve a hospedeira e a órfã, com o mesmo texto. Agora: a hospedeira (public/contratos/index.md, no nav:, com frontmatter) é obrigação do dono, e os fatos saem do build com exclude_docs: + ! reincluindo a hospedeira. O --8<-- e o rclone/cp do raw/ seguem funcionando — leem do disco, não do build. Vale nos dois lados: _importado/ também é exclude_docs:, senão o site do consumidor serve o contrato do dono como se fosse página dele — a cópia que o mecanismo existe para matar. not_in_nav: fica para o que deve sair no site sem estar no índice (o Javadoc em dev/api/). Decisão 0016.

    Migração — quem já publica fato troca not_in_nav: por exclude_docs:

    Um repo publica arquivo-fato hoje (o hub de integração) e já tinha escrito a hospedeira por conta própria. Trocar o not_in_nav: pelo exclude_docs: do templates/mkdocs.yml tira a órfã do ar. Sem a troca nada quebra: o site só segue servindo a página órfã e duplicando o texto na busca.

  • Página em docs/public/ leva frontmatter — inclusive a da API. Havia duas convenções: o templates/public-api.md sem frontmatter, a hospedeira do hub com. Governança por documento (§6) não abre exceção por a página ser pública, e para quem consome um contrato o cabeçalho é o que importa: atualizado: diz se o contrato está vivo, status: se dá para confiar, responsavel: a quem perguntar. O hook expõe o nome, nunca o e-mail.

  • Constituição 0.23.1→0.23.2 — checa-admonition.py: admonition cru vira gate. O mkdocs build --strict não pega marcador mal indentado: para ele não há erro, só um parágrafo que começa com !!! — renderiza literal e sai verde, e quem paga é o leitor. Mesma classe do Mermaid que não parseia (a razão do lint-mermaid.mjs), e foi achado no olho de um humano, que é o gate que a §6 diz não servir. O check roda depois do build e confere o HTML, não o Markdown: ali não há ambiguidade — !!! em <pre> é código exibido (esta plataforma mostra o runbook.md, que tem um !!! warning de exemplo), em <p> é bug. No build-site.yml, no docs.yml dos apps e no pré-flight da /xadm-release.

Corrigido

  • checa-nav.py — a negação ! de exclude_docs: era ignorada, e os globs vinham sem ordem. Dois bugs que só apareceram ao prescrever a hospedeira, e que a teriam transformado numa órfã silenciosa: o veredito parava no primeiro glob que casava (então /public/contratos/*.md dava a hospedeira como excluída e ela podia sumir do nav: sem ninguém acusar), e os globs eram acumulados num set — sem ordem, num formato em que a ordem decide (última que casa vence, como no .gitignore). Um terceiro: o regex que varre o mkdocs.yml capturava os próprios globs de exclusão como "menção de nav", dando passe livre a fato que nenhum exclude cobria. Três casos novos de regressão; mutação confirmada (o _excluido antigo deixa caso vermelho).

  • testa-checa-views.py — a fixture agora é o kit real, com guarda de mutação. O LAYOUT_OK era cópia à mão do layout e já tinha divergido dele uma vez (ficou com o layout quebrado da 0.21.0 e a suíte seguiu verde durante todo o bug). Agora lê templates/views/layout.html. A guarda existe porque ler o real só é melhor se a mutação for garantida: muta() exige que o alvo apareça exatamente uma vez — pega tanto o no-op (o kit mudou, o alvo sumiu) quanto o alvo ambíguo. Não é hipótese: ao trocar a fixture, os dois casos da regra G passaram a injetar o <head> defeituoso dentro do comentário de cabeçalho do kit (onde o parser não olha) e ficaram verdes testando nada — a guarda os pegou.

  • Constituição 0.23.2→0.23.3 — o kit de UI da 0.21.0 estava quebrado no ar: slot de head removido. O fragment headComExtras(title, extras) era um segundo elemento <head> no layout.html, e um documento HTML tem um só: o parser funde o segundo no primeiro, e o head('Título') passou a arrastar o ${extras} do irmão — Error resolving fragment: "${extras}", 500 em toda view, inclusive nas que não usavam o slot. A afirmação da 0.21.0 de que "as telas que já chamam head('Título') seguem intactas" era falsa. Reproduzido com Thymeleaf 3.1.5 antes de decidir: não é colisão de nome (renomear não cura) nem "head dentro de body" (dois <head> irmãos quebram igual) — basta serem dois. O slot foi removido em vez de consertado: 0 views o usavam, 126 chamavam head(). Tela que precise de markup próprio no <head> escreve o <head> à mão e traz o caso ao central. Decisão 0015.

    Migração — quem copiou o kit 0.21.0/0.22.x re-deriva com /xadm-docs

    O layout.html volta a ter só head(title). Nenhum app usava o slot, então não há chamada a corrigir: quem estava com o kit quebrado volta a renderizar; quem nunca adotou não faz nada.

  • checa-views.py — regra G: <html>/<head>/<body> duplicado no mesmo arquivo reprova. É a guarda contra a reincidência do bug acima. Vale mesmo com nomes distintos e mesmo fora do <body> — o que quebra é o parser fundir elemento, não o nome (a regra A já cobria nome). Só declaração real conta: o cabeçalho do kit documenta o uso com um <head th:replace=...> dentro de comentário, e os três apps Micronaut reais têm isso — a regra lê o parser, nunca regex. Verificada nos dois sentidos: reprova o layout.html real da 0.21.0 e passa nos 46 views reais dos três apps.

  • testa-checa-views.py — a fixture LAYOUT_OK era o layout quebrado. O "layout conformante mínimo" da suíte trazia o segundo <head>: o teste do checker atestava contra um defeito, que é a fixture inventada que a §6 nomeia — e o motivo de a suíte não ter pego nada. Corrigida, mais quatro casos da regra G (o bug real, os dois <head> irmãos, <body> duplicado e o negativo do <head> em comentário).

  • checa-rotas.py — a mensagem de erro dizia onde pôr o escape de forma ambígua. Dizia "<!-- checa-rotas: ignorar --> na linha"; um dev de app leu como "na linha de cima" e perdeu um ciclo de gate. Agora diz na MESMA linha da rota, e que o marcador não aparece na página (é comentário HTML). Só a string mudou — o escape sempre foi por linha, e segue.

  • Nota de migração da 0.23.1 renderizava crua na /changelog/ — o !!! note estava indentado com 2 espaços dentro do bullet e saiu como texto literal na página publicada. Python-Markdown usa tab_length = 4: parágrafo de continuação de item de lista aceita 2 espaços (lazy continuation), mas bloco novo — que é o que o admonition é — exige 4; com 2, ele cai fora da lista e vira parágrafo. O texto ao redor sai perfeito, então o erro não chama atenção no diff.

0.26.0 - 2026-07-16

Adicionado

  • Constituição 0.21.0→0.22.0 — o contrato REST passa a ser checado contra o OpenAPI gerado: checa-rotas.py (loop §6, feedback de app). A §6 mandava a doc mudar no mesmo PR que muda o contrato, e nada detectava quando isso não acontecia — a própria §6 admitia não ter "detector automático de doc seguiu código". Num app, uma rota de upload foi renomeada no controller; o OpenAPI, que o micronaut-openapi deriva dos @Controller ao compilar, acompanhou sozinho; a prosa não. Por 10 dias o mkdocs build --strict, o checa-nav e o ci.yml ficaram todos verdes enquanto o site publicado servia o contrato novo e a prosa com a rota morta, lado a lado. O checa-rotas.py (rastreado, stdlib, baixado fresco, tolerante) confere prosa → spec e roda no ci.yml (depois do ./gradlew check, que é quando o spec existe — o rename é evento de código) e no docs.yml (prosa editada sem tocar código, inclusive pela web do Forgejo). Só cobra rota cujo primeiro segmento existe no spec, para não reprovar API de parceiro que o app só consome nem rota de app vizinho; aceita prefixo de família em diagrama; pula documento status: obsoleto; e tem escape de linha <!-- checa-rotas: ignorar --> para a decisão de remoção, que não tem como nomear a rota removida sem escrevê-la. Aplicado aos dois apps que geram OpenAPI, achou o drift do feedback e um segundo bug inédito (doc cuja seção diz "Envie um PUT" e cujo exemplo diz POST, numa rota que só expõe PUT/DELETE). Decisão 0012; detalhe em CI, gates e testes.

Corrigido

  • Constituição 0.23.0→0.23.1 — o kit de UI mandava o CSS para um diretório que não o serve (loop §6, feedback de app). A /xadm-docs mapeava templates/views/** → src/main/resources/views/**, mas templates/views/ tinha dois arquivos com destinos diferentes: o layout.html é view, o custom-theme.css não — quem serve views/ é o Thymeleaf, e o CSS precisa do static-resources (public/css/), como o próprio arquivo dizia no cabeçalho e o @{/css/custom-theme.css} do layout referenciava. Seguir o glob ao pé da letra sobe a tela sem estilo. Bug latente, nunca exercitado: os dois apps Micronaut reais têm o CSS no lugar certo porque nasceram antes do kit — o primeiro app novo a seguir a skill é que quebraria. A cura foi a origem, não o mapeamento: o custom-theme.css foi para templates/public/css/, e a árvore do kit passa a espelhar a do app — os dois globs viram verdade (views/** → views/**, public/** → public/**), sem exceção por arquivo a manter em sincronia. Listar arquivo-por-arquivo na skill (o que o feedback pedia) duplicaria a tabela que já é dona no java-micronaut.md — recriando o segundo dono que causou o bug. Guarda nova: o checa-views.py ganha a regra F (arquivo não-.html em views/ reprova, apontando public/), que mata a classe e não só este arquivo.

    Nota de migração — só o path de ORIGEM muda

    O destino no app não muda: quem já tem public/css/custom-theme.css está correto e nada precisa fazer. Mudou o caminho no manifesto (templates/views/custom-theme.css → templates/public/css/custom-theme.css), então a /xadm-docs re-baixa do raw novo. App que tenha o CSS em views/ (nenhum conhecido): mova para public/css/ — a tela está sem estilo.

Alterado

  • Constituição 0.22.1→0.23.0 — fato de outro repo: o dono publica em raw/, o consumidor importa no build (loop §6, feedback de app). A 0.22.1 abriu transclusão para fato do mesmo repo e manteve "cross-repo é link, sempre". O caso que sobrou é o runbook de implantação de um vertical, que vive entre dois apps: o que ele implanta é seu, o contrato de ingestão que ele consome é do hub. O diagnóstico mudou a pergunta — o hub não publica o OpenAPI que ele já gera (step comentado), não tem docs/public/, e seu contrato vive espalhado em dez arquivos de prosa. Não havia o que linkar: a cópia era sintoma, o dono que não publica é a causa, e a regra parava no consumidor. Agora: o --8<-- não atravessa repo, mas o build atravessa — o dono publica o .md cru na raiz do seu prefixo (docs-sites/<slug>/raw/, aditivo, todo push, pelo mesmo critério que já põe o app.json lá: é contrato entre apps, não conteúdo versionado), e o consumidor faz curl -f para docs/_importado/ (efêmero: .gitignore + not_in_nav:) e embute. O fato chega fresco a cada build; curl -f falha o build se o dono despublicar. Publicar é obrigação do dono, e quem rege a evolução de um repo não vira dono dos fatos dele — o projeto-mãe escreve o PR no dono e importa aqui, senão um hub compartilhado ganha um dono por consumidor. Decisão 0014; receita em Publicar docs.

  • Constituição 0.22.0→0.22.1 — o runbook transclui do arquivo-dono; re-digitar é que é proibido (loop §6, feedback de app). A 0.20.2 fechou "o livro não entra no runbook" e mandou o runbook linkar; um dev aplicou a regra e reverteu, porque a versão conforme ficou pior para o uso real — mostrar a integração à equipe numa página. O ponto procede por um motivo mais forte que o custo ao leitor: a regra contradizia o princípio que deveria implementar (o §1.9 já dizia "linkam ou embutem (pymdownx.snippets), não copiam"). Agora: onde o operador precisa do fato na mesma página, o runbook transclui do arquivo-dono — não há cópia para envelhecer. Transclui-se do arquivo-fato, nunca "da seção do livro" (o livro é narrativa e ele próprio embute os fatos); cross-repo continua link, que é onde o exemplo-que-mente nasceu. O §2 passa também a nomear o que fica no runbook (topologia de operação com diagrama, superfície, variáveis, passos/verificação/ reversão): ele só enumerava proibições, e a migração revertida cortou junto o Mermaid de topologia que o próprio molde prescreve — regra que só diz o que sai é aplicada cortando demais. Registrada a armadilha do --8<-- (é pré-processador de linha: transclui dentro de bloco de código e de comentário HTML; escape é ;) em Templates. Runbook que linka continua conforme — transcluir é permissão, não obrigação. Decisão 0013.

  • Piso §6 — "o teste não reimplementa a lógica que atesta" (loop §6, feedback de app). A definição de pronto cobrava "teste proporcional ao risco" e já nomeava a fixture inventada como falso atestado, mas não o irmão dela: o teste que refaz no próprio corpo a conta da produção passa por construção — o expected deixou de ser oráculo e virou um segundo palpite da mesma cabeça que escreveu o código. Caso real: um teste de agregação somava as linhas no test e comparava com a soma do serviço, verde sobre uma agregação errada. O piso agora exige oráculo de fora (valor literal, saída capturada, invariante independente), e o ci-testes registra o teste de mutação (PIT) como ferramenta de diagnóstico sob demanda — não gate, pela mesma razão de "cobertura é visível, não bloqueante".

  • Constituição 0.20.2→0.21.0 — o kit de UI ganha o gate que faltava: checa-views.py (loop §6, feedback de app). O kit de UI foi o primeiro template rastreado executável (os demais são Markdown, ou Python com teste de regressão), e para ele o central não tinha guarda nenhuma: o manifesto atesta que a cópia do app é idêntica à do central (sha256), nunca que ela funciona. Foi exatamente essa fresta que deixou a 0.19.0 publicar um layout.html com dois th:fragment="navbar" homônimos — sha íntegro, --check verde, 500 em toda view de quem adotasse. O novo check é stdlib e offline (sem JVM): reprova fragment homônimo, chamada a fragment que o layout não declara, aridade divergente, chamada posicional contra fragment sem signature (o defeito da 0.19.2) e markup passado declarado fora do host do th:replace. Roda no ci.yml de todo app com view (tolerante: sem src/main/resources/views/, sai 0) e no CI do central contra templates/views/ — na origem, antes de publicar, que é onde o defeito da 0.19.0 teria morrido. Verificado contra o layout.html real da 0.19.0 (reprova) e contra as 38 views reais do integrador e do int-produtos-ecom (verde, sem falso positivo). Acompanha testa-checa-views.py (12 casos, central-only).

  • headComExtras(title, extras) no kit de UI — a tela que precisa de markup próprio dentro do <head> (típico <noscript><meta http-equiv="refresh"> de fallback sem JS, inválido e ignorado fora do <head>) não tinha lugar canônico e empurrava o conteúdo para o <body>. Irmão nomeado do head(title), como o navbarComMenu é do navbar: sem overload por aridade, e as telas que já chamam head('Título') seguem intactas.

Corrigido

  • A receita não dizia ONDE a página declara o markup que passa (~{::navMenu}): o th:replace substitui o elemento host inteiro — host e conteúdo somem do output. Declarado solto no <body>, o th:fragment renderiza duas vezes; o certo é aninhá-lo dentro do elemento que faz o replace, e isso só aparecia no exemplo para quem já soubesse. É silencioso (não dá 500, só sai errado na tela). Agora está no bloco de código da receita, no cabeçalho do layout.html, e o checa-views.py reprova.
  • th:fragment="styles" morto no <head> do kit — fragment vazio herdado do layout pré-kit do integrador na templatização da 0.19.0. Zero uso nos dois apps Micronaut, não documentado em lugar nenhum: sinalizava um slot que não entregava. Removido; quem precisa de markup no <head> usa o headComExtras.

0.25.1 - 2026-07-16

Adicionado

  • Constituição 0.20.1→0.20.2 — runbook de implantação vira molde, e o §2 ganha as três regras que o pior dia cobra (loop §6, feedback de app). O nível 5 promete "runbooks técnicos: deploy, recuperação, destrutivas", mas o único template era o runbook.md, de procedimento pontual (operação única, disparada por incidente, num app só) — quem precisava documentar subir um sistema não tinha base, e a diferença não é de tamanho, é de eixo: o procedimento responde "como executo esta operação"; a implantação, "como este sistema fica de pé, e como eu o desligo". Agora há templates/implantacao.md, irmão do runbook.md (não substituto): topologia de operação, superfície de operação, variáveis por servidor com os pares que têm de casar, passos, verificação, reversão.
  • Três regras no piso §2 (bullet Runbook) — cada uma de um defeito real que passou pela revisão humana num doc de implantação escrito sem base: (1) a Reversão enumera todas as vias e o que cada desligar isolado não para (sistema com webhook + varredura precisa da tabela caminho → efeito, com os caminhos que não funcionam marcados — kill switch não conferido contra o código é kill switch imaginário, e é lido no pior dia possível); (2) todo passo de Verificação declara o resultado esperado conferido no código — mesmo payload ≠ mesma versão: onde a chave-versão é bumpada por escrita (@DateUpdated, sem dirty-check), idempotência prova-se com uma escrita e vários ciclos do consumidor, nunca com duas; (3) valor de contrato externo sem fonte do outro lado é <PLACEHOLDER> + bloco "A confirmar" com o efeito de errar — fixture nossa não confirma contrato de terceiro (javadoc "ex.:", seed e whitelist concordam entre si porque descendem da mesma suposição). Valem para os dois moldes: as duas primeiras cobram seções que todo runbook já tem.

Corrigido

  • §2 ganha a metade que faltava da fronteira: "o livro não entra no runbook" — corolário simétrico do já escrito "o deploy não entra no livro". O doc que originou este feedback re-digitava três capítulos do livro (topologia-arquitetura, fluxo de eventos, contrato de entrada), contra §1.9 (um dono por fato) e §1.2 (o factual vem da fonte). Não é purismo: o contrato re-digitado longe do dono foi o que virou exemplo-que-mente. O molde novo linka o livro e escreve só o que é de operar.

Adicionado

  • Constituição 0.19.2→0.20.1 — o container do app vira artefato prescrito (loop §6, feedback de app: nasceu doc-only, virou código, passou em teste e e2e local e quebrou no deploy do Coolify por não ter Dockerfile). A plataforma nunca prescreveu container: todo o conhecimento vivia no registro "não pise nesta mina" (armadilhas de .dockerignore/.git/HEAD), e o registro "isto é o que você constrói" nunca foi escrito — coolify.md dizia "Build Pack: Dockerfile" tratando o arquivo como fato da natureza. Havia contradição interna: a §5 exige health check obrigatório, mas a Estrutura padrão (§3) não listava o artefato onde o HEALTHCHECK vive. E era assimétrico: flutter.md tinha receita, java-micronaut.md não citava Dockerfile em linha nenhuma — a stack que mais deploya era a sem receita. Agora: piso na §3 (app deployável leva Dockerfile + .dockerignore desde o 1º commit de código); templates rastreados e opcionais dockerfile-java + dockerignore-java; ## Deploy — o Dockerfile em java-micronaut.md; gate docker build no pré-flight do /xadm-release (degradado sem daemon, declarado no relatório); trilha "repo que era só doc e passou a ter código" em app-novo.md; decisão 0011. Motivo do gate: o ci.yml não builda imagem e o Coolify builda o Dockerfile do repo — era o único artefato que nenhum CI tocava, e a produção era a primeira a exercitá-lo.

  • Flutter §Deploy web — cache no nginx (loop §6, feedback de app). Receita nova: main.dart.js é no-cache, não cache longo — o Flutter Web não versiona URL de filho (verificado no build 3.44: buildConfig traz "mainJsPath":"main.dart.js" e o loader monta a URL sem query nem hash; o único ?v= é do flutter_service_worker.js, e a casa builda --pwa-strategy=none). Errar isso é invisível no deploy e mata o auto-update: o banner de "nova versão" aparece, o usuário clica e o browser serve o mesmo bundle do cache por até 30 dias. Regra + map de exemplo + os assets de nome fixo que também precisam revalidar (MaterialIcons-Regular.otf, sqlite3.wasm, powersync_db.worker.js) + curl -sI …/main.dart.js como verificação pós-deploy. Motivo de existir a receita: o bi-comercial corrigiu o bug em 2026-05-13 e o bi-transporte ficou dois meses com o comentário oposto e max-age=2592000 em produção — o central não tinha dono do assunto.

Alterado

  • SENTRY_DSN é o env canônico do DSN em toda stack (antes GLITCHTIP_DSN). É o nome que o SDK do Sentry lê do ambiente por convenção (Java e Dart) e o GlitchTip é Sentry-compatível, então o nome canônico elimina o código que existia só para traduzir env. A constituição mandava GLITCHTIP_DSN, mas metade dos repos Java (integrador, int-produtos-ecom, bi-transporte-xls) e todo o Flutter já usavam SENTRY_DSN — a mudança ratifica a maioria. Vale igual no Flutter (--dart-define=SENTRY_DSN) e é o nome que o /xadm-setup injeta no Coolify (exceção glitchtip.dsn → SENTRY_DSN na tabela ENVS_CANONICOS do broker). Atenção — o rename é SILENCIOSO, não barulhento: trocar o nome no Coolify sem o código já ler o novo não dá erro — o ${…:-} resolve para vazio, o appender vira no-op e o app sobe normal, enquanto todo erro de produção para de ser reportado sem sinal nenhum. Ordem segura: código primeiro (com fallback ${SENTRY_DSN:-${GLITCHTIP_DSN:-}}), deploy, aí o rename no Coolify, aí remove o fallback. A injeção do broker é upsert: reprovisionar cria SENTRY_DSN e deixa o GLITCHTIP_DSN órfão no recurso — apagar na mão.

Corrigido

  • Cache mount do Gradle no Dockerfile: id default = target + sharing default = shared (achado ao dogfoodar o template novo — buildá-lo contra um app real em vez de publicá-lo por inspeção). Os dois apps Java da casa usam --mount=type=cache,target=/root/.gradle bare; como o id de um cache mount default para o próprio target, todos os apps Java dividem um único cache no servidor do Coolify, e o sharing default shared ("can be used concurrently by multiple writers") é justamente o que o Gradle não tolera. Dois deploys concorrentes — ou um build morto que deixou lock stale — falham com Timeout waiting to lock journal cache (/root/.gradle/caches/journal-1). It is currently in use by another process, intermitente e com cara de bug do app. Reproduzido, não inferido. Template usa id=gradle-<slug> + sharing=locked (o que a doc do Docker prescreve para o apt, pela mesma razão); nota em java-micronaut.md §Deploy. Migração oportunista nos apps existentes.

  • .dockerignore não é .gitignore — a diferença é silenciosa e um dos padrões quebraria o build. Verificado empiricamente (Docker 29.5), contra fonte que afirmava o contrário: o Docker casa padrões com filepath.Match do Go, onde * não cruza /. Consequências que os repos atuais carregam sem saber: build bare exclui só a raiz (sub/build/ viaja pro daemon — inócuo em single-module, mina armada ao modularizar), e **/*.jar quebraria o build ao excluir o gradle/wrapper/gradle-wrapper.jar (o gradlew morre com "Could not find or load main class org.gradle.wrapper.GradleWrapperMain"). O template usa **/build e mantém *.jar raiz-only de propósito, com o porquê escrito. A exceção !docs/app.json do item 79 foi verificada de fato funcionando (e só funciona depois de docs: vence a última linha que casa, não a mais específica).

  • Constituição 0.19.1→0.19.2 — kit de UI: layout.html declarava dois fragments navbar homônimos (loop §6, feedback de app). O layout tinha th:fragment="navbar" e th:fragment="navbar(menu)", mas o Thymeleaf resolve fragment por nome e não faz overload por aridade (confirmado na doc oficial) → as duas chamadas que a receita ensinava quebravam no render (500): ~{layout :: navbar} casava a variante com signature ("Error resolving fragment: ${menu}") e ~{layout :: navbar(~{::navMenu})} casava a sem signature ("Signature navbar declares no parameters, but fragment selection did specify parameters"). Qualquer app que adotasse o kit da 0.19.0 batia nisso. A variante com menu virou navbarComMenu(menu) — nomes distintos, chamadas posicionais válidas nos dois lados, sem th:if no fragment (que cairia na armadilha de precedência th:replace(100) < th:if(300) e contrariaria o corolário de "duas superfícies, dois fragmentos" já normatizado). Receita do Java/Micronaut §UI corrigida; apps re-derivam o kit via /xadm-docs.

  • Constituição 0.19.0→0.19.1 — checa-nav casava globs com fnmatch, não com o formato .gitignore (loop §6, feedback de app). Os padrões de not_in_nav:/exclude_docs: seguem o formato .gitignore (doc do MkDocs), e o script os casava com fnmatch — semânticas diferentes, divergindo em três frentes: barra inicial (/privado/* nunca casava → falso positivo, gate vermelho num exclude legítimo — o caso do feedback), barra final (privado/ não casava em nível algum — é o exclude_docs: do próprio central, inócuo só porque ainda não há .md sob privado/) e * cruzando / em padrão ancorado (rascunhos/*.md absolvia rascunhos/mais/b.md → falso negativo, órfão real passando). Trocado por um mini-matcher .gitignore em stdlib (o check roda no gate de código do app, onde não há mkdocs nem pathspec).
  • checa-nav: menção em comentário do mkdocs.yml valia como declaração de nav: (mesmo feedback). O script varria o YAML cru com regex ([\w./*\-]+\.md), então um .md citado num comentário absolvia o arquivo órfão — falso negativo que escondia o falso positivo acima. Comentários agora são removidos antes do regex (preservando âncora pagina.md#secao, que não é comentário).
  • testa-checa-nav.py (novo, central-only): regressão do checa-nav em 9 casos versionados — os 4 bugs acima reprovam a versão anterior. Roda no CI (build-site.yml) e no pré-flight da /xadm-release, como o testa-valida-frontmatter.

0.24.0 - 2026-07-16

Adicionado

  • Constituição 0.18.11→0.19.0 — UI unificada para apps server-render (loop §6, feedback de app). A constituição regia doc e engenharia mas não tinha convenção de UI: cada app que renderiza HTML no servidor (Micronaut Views/Thymeleaf) — telas de admin/ops/dev, mesmo internas e atrás do gate de auth do Coolify — reinventava o visual (integrador padronizado × tradutor ecom com <table border="1"> cru), drift que só se pega no olho. Piso novo no §5 (UI dos apps): todo app server-render segue o layout padrão X-Adm (fragment base único head/navbar/scripts, Bootstrap 5, header escuro com logo, idiomas da casa table-striped/badge/alert) na paleta única do xadm.css; o app só escreve o conteúdo de cada tela. Kit rastreado opcional no manifesto (templates/views/layout.html + custom-theme.css + logo.png + favicon.ico; sincroniza pela /xadm-docs, ausente em app headless/Flutter não é defasagem) + item de conformidade §3b da /xadm-docs (tem views mas não usa o layout → não-conformidade). Detalhe operável + snippet static-resources em engenharia/java-micronaut.md §UI; re-derivado limpo na paleta canônica (o kit do integrador usava #0066CC, cor errada); decisão 0010.
  • Constituição 0.18.10→0.18.11 — espelho fiel ao X-Adm (nível de coluna) (loop §6, feedback da integração PIED). A integração PIED promoveu chaves do vocabulário do parceiro (codigo_alt, x_ped) a identidade de 1ª classe na tabela-espelho; a §Fronteiras só regia o prefixo de tabela, nada regia a identidade de coluna dentro do espelho. Piso novo no §8 (ao lado de owner-writes): a tabela-espelho modela a entidade do X-Adm, não a do parceiro — identidade em coluna nativa (conferida contra a spec de entidades do ERP), campo de origem externa entra prefixado (<origem>_) e só como rastreabilidade, nunca chave/join. Detalhe operável + tabela de coluna em documentacao/integracao.md §Fronteiras; eco server-side em engenharia/java-micronaut.md §Banco; decisão 0009.
  • Dois gotchas de stack no java-micronaut.md (loop §6, feedback de app; engenharia-only, sem bump). (1) §Armadilhas: @Client config-gated — declarar micronaut.http.services.<id>.url cria um ServiceHttpClientConfiguration eager (@EachProperty) que quebra o startup do contexto inteiro se a URL estiver vazia (Failed to inject value for parameter [url]; sintoma = N @MicronautTest em massa no NettyHttpServer.start/beforeAll). Cliente HTTP só-em-alguns- deploys: @Client("${prop.url}") na interface + BeanProvider<Client> (lazy), assim o bean nem nasce. (2) §Banco: chave-de-mudança de espelho multi-writer vive no banco (trigger), não no app — updated_at por trigger Postgres (BEFORE INSERT OR UPDATE, bump só em IS DISTINCT FROM) cobre todos os escritores (apply, admin CRUD, PowerSync) num lugar; coluna trigger-managed não se mapeia na entidade (Micronaut Data faz UPDATE só do mapeado → null-wipe no update).
  • Flyway em Postgres compartilhado — app secundário exige baseline (loop §6, feedback de app; engenharia-only, sem bump). §Banco do java-micronaut.md: com a história por app, o segundo app sobe com a sua tabela de histórico ainda inexistente contra um schema não-vazio (tabelas do app dono já lá) → o Flyway aborta ("Found non-empty schema(s) without schema history table"). Cura: baseline-on-migrate: true + baseline-version: 0 — o primeiro deixa o Flyway marcar o schema como linha-base em vez de abortar; o segundo faz a própria V1 do app rodar (o default 1 pula a V1 do secundário silenciosamente). Só no app secundário; o dono do schema sobe do zero.

0.23.7 - 2026-07-14

Adicionado

  • Higiene do .claude/settings.json vira norma da casa (engenharia/ferramentas.md, com ponteiro no app-novo.md): o arquivo versionado leva só o compartilhado (hooks + additionalDirectories relativo + allow-list de padrões largos que absorvem os comandos comuns); grants pessoais/de-máquina vão para .claude/settings.local.json gitignored. Sem isso o allow-list incha a cada sessão e o git commit --amend resultante troca o hash sob a tag, soltando a release. O template gitignore-flutter passa a ignorar o settings.local.json.
  • Estilo terso (caveman) ativado no repo central via hook SessionStart (.claude/caveman.sh) — dogfood do padrão que agentes.md/app-novo.md/ferramentas.md já prescreviam; regra de higiene de arquivos afiada em ferramentas.md ("Bash executa, não lê" + blocklist sed/ls -la/grep -rn).

Corrigido

  • Typo no .gitignore (.claude/.settings.local.json → .claude/settings.local.json) que deixava os grants pessoais fora da proteção do repo (só o ignore global da máquina cobria).

0.23.6 - 2026-07-14

Adicionado

  • Constituição 0.18.9→0.18.10 — o fechamento passa a recomendar /compact depois do commit (pedido do Gustavo; altitude decidida por AskUserQuestion = skill + constituição §6). O /x-documentar é o passo que persiste o durável em docs/, então compactar o contexto ao fim do ciclo recupera tokens sem perda (linha de economia dos itens 56/57/66). A ordem importa: commit antes do /compact, senão a mensagem pronta pode ir embora no resumo e o dev ainda não salvou. Bullet do fluxo recomendado no §6 da constituição + item 4 no fechamento (§5) da skill /x-documentar (template rastreado → re-carimbo + cópia dogfood re-derivada). Bump PATCH.
  • engenharia/java-micronaut.md §Armadilhas (loop §6, feedback de app): Thymeleaf bloqueia expressão String em atributo de evento (th:onclick/th:onload/th:on*). Por segurança (defesa contra injeção de JS via dado de usuário), o Thymeleaf 3.x só aceita nesses atributos expressão que retorna número ou boolean → String lança TemplateProcessingException ("Only variable expressions returning numbers or booleans are allowed…") no render, com o build verde. Padrão da casa p/ linha/ card clicável: dado num data-* (th:attr="data-href=@{/rota/{id}(id=${x})}") + handler em JS no rodapé — nunca th:onclick com a URL. Nova armadilha na família de view server-side (irmã do record/OGNL e do th:replace). Bônus (corolário atado ao bullet de precedência th:replace× th:if, decisão do Gustavo — AskUserQuestion): duas superfícies de UI distintas (chrome público × navbar de operador) = dois arquivos de fragmento de layout separados, não um layout único com th:if no menu. Engenharia-only, sem bump (java-micronaut.md não é rastreada).
  • engenharia/java-micronaut.md §Armadilhas (loop §6, feedback de app): ViewModelProcessor + Map.of() do controller = HTTP 500 só nas rotas que devolvem imutável. Um ViewModelProcessor injeta dado transversal no modelo de toda view (usuário/tenant/versão no layout — padrão layout.html
  • GlobalViewModel do molde do integrador) via model.put(...); se o controller devolveu um Map.of(...) (imutável, comum no ramo "não encontrado"), o put estoura UnsupportedOperationException → 500, só naquelas rotas. Regra: view controller sempre devolve modelo mutável (LinkedHashMap), nunca Map.of. Guarda barata: teste GET numa rota de not-found esperando 200. Nova armadilha na família de view server-side (irmã do record/OGNL e do th:replace), engenharia-only, sem bump (java-micronaut.md não é rastreada).
  • engenharia/java-micronaut.md §Erros e logging + §Armadilhas (loop §6, feedback de app): cliente HTTP + config vazia = 500 + registro preso. URI montada de config (baseUrl + rota) com base vazia vira URI relativa → HttpRequest.newBuilder lança IllegalArgumentException (não a exceção de domínio) → escapa do catch → 500; pior, se a máquina de estados marcou o registro "em voo" (ENVIANDO) antes da chamada, ele fica preso sem transição de saída. Dividido em dois donos (decisão do Gustavo, AskUserQuestion): norma durável de resiliência em §Erros e logging (validar a montagem da chamada → exceção de domínio tratável; catch de chamada externa largo o bastante pra RuntimeException inesperada, garantindo transição pra estado terminal em qualquer exceção) + a armadilha concreta em §Armadilhas apontando pra norma. Engenharia-only, sem bump.
  • Constituição 0.18.8→0.18.9 — armadilha "Dockerfile lê docs/app.json ⇒ precisa da exceção !docs/app.json no .dockerignore" (loop §6, feedback de app; verificado no .dockerignore do integrador, que exclui docs). A ponte de config da Central de Apps tem duas metades que viajam no mesmo PR: o Dockerfile ler docs/app.json (Flutter via jq→--dart-define em build-time; um servidor via COPY→leitura em runtime) e a exceção no .dockerignore — separar quebra o deploy (arquivo fora do contexto de build). Dono stack-neutro: infraestrutura/coolify.md, ao lado da gêmea "não leia .git/HEAD no build", com o contraste preciso (aqui a negação !docs/app.json resolve — o arquivo é committado e está no contexto; lá o .git nem chega ao contexto). Cross-link da receita jq docs/app.json em engenharia/flutter.md. A /xadm-setup passo 5 (que manda "crie a ponte") passa a exigir as duas metades → bump PATCH e re-carimbo do xadm-setup-skill.md. Decisão do Gustavo (AskUserQuestion): altitude = também a skill rastreada.
  • engenharia/powersync.md §Auth (loop §6, feedback de app PowerSync): a §Auth passa a distinguir dois modos de consumidor — app com usuário real (JWT do usuário via JWKS do central-backend, ou jwks_uri do Google no Firebase) vs cliente máquina-a-máquina (o cliente Java do ERP, os reatores — sem humano), que nasce self-signed + JWK inline: o cliente assina o próprio JWT com key privada em env e o PowerSync valida contra o JWK público inline — o mais simples e sem peça nova (sem broker, sem IdP), reservando Firebase/pAbast para quem tem usuário de verdade. Casa com três normas já existentes (Kerckhoffs em seguranca.md §Segredos, inline > jwks_uri da própria §Auth, reusar-antes-de-inventar). A pegadinha do jwks_uri (timeout ~3s + NAT hairpin → falha intermitente, recorrente em produção) já estava documentada; ganhou nota de que vale para os dois modos e de que o self-sign já nasce inline. Ressalva anti-confusão: este M2M ≠ o M2M do broker de setup (lá quem assina é o central-backend; aqui, o próprio cliente). Engenharia-only, sem bump (powersync.md não é rastreada). Decisões do Gustavo (AskUserQuestion): altitude engenharia-only (não subir ao §8) e só a §Auth (sem eco na seguranca.md, que já cobre Kerckhoffs).

0.23.5 - 2026-07-13

Adicionado

  • engenharia/java-micronaut.md (loop §6, feedback de app — três gotchas de stack Micronaut/Thymeleaf): (a) §Armadilhas — o ERROR Failed to inject … jsonMapper (JsonMessageHandler) no fim de uma run multi-classe @MicronautTest é artefato de teardown (o Micronaut derruba o contexto compartilhado e loga a injeção de um bean interno num contexto já desmontado), não bug: se a suíte fecha verde, não caçar — mesmo balde do falso-0% do JaCoCo e do Test Resources órfão. (b) §Armadilhas — Thymeleaf: th:replace/th:insert (precedência 100) rodam antes de th:if/th:unless (300), então th:if no mesmo tag de um th:replace não segura (o elemento já foi substituído quando o th:if seria avaliado); cura = th:if num elemento pai (ou <th:block>). (c) §Testes — costura para testar controller cuja ação chama serviço com HTTP externo: o HttpClient injetado por DI (base-url vazia em teste) aponta pro servidor embutido, não pra porta dinâmica do WireMock → teste o serviço com client construído à mão na porta do WireMock e o controller só nos ramos sem rede. Placement de (a) decidido pelo Gustavo (§Armadilhas, a família "parece bug, não é"). Sem bump (java-micronaut.md não é rastreada).

  • engenharia/java-micronaut.md §Armadilhas (loop §6, feedback de app): record no modelo do Thymeleaf estoura Property … not found. Micronaut Views usa o Thymeleaf standalone, cuja linguagem de expressão é o OGNL, que resolve propriedade por getter JavaBean (getFoo()); um record gera acessor foo(), não getFoo(), então ${obj.foo} falha no render (erro de template, não null). Saídas: passar um Map no modelo ou invocar o método (${obj.foo()}). Sem bump (java-micronaut.md não é rastreada). Fora (decisão do Gustavo, não normatizado): "preferir cliente REST mão-livre (JDK HttpClient) a @Client declarativo" — é consistência interna de 1 app, não prática da casa (≥2 repos); @Client é idioma Micronaut válido.

  • Constituição 0.18.8 — piso no §6 (Definição de pronto): contrato de entrada entre componentes NOSSOS (skill/cliente → endpoint first-party, ex. o broker POST /api/setup/provision que o /xadm-setup consome) tem os campos obrigatórios travados nos DOIS lados — o cliente declara o contrato e o provedor tem teste que rejeita payload faltante (400 legível, não 500). Sem o par, driftam em silêncio: o guia dizia (app_id, feature), o broker passou a exigir mais → 500 em produção em vários apps antes de alguém notar (loop §6, feedback de app). Distinto do que já existia ("fixture de API externa é capturada, não inventada" = shape de resposta de terceiro; este = contrato de entrada first-party não-verificado no provedor). Detalhe cross-stack em engenharia/ci-testes.md §Definição de pronto (bullet novo, ao lado da fixture capturada); contrato operável do broker em documentacao/central-de-apps.md (bloco de identidade do app.json: app_id, feature, grupo, cliente_id); síntese enganosa (app_id, feature) corrigida no template rastreado xadm-setup-skill.md (l.47) e no diagrama de central-de-apps.md. Bump PATCH 0.18.7→0.18.8 (xadm-setup-skill.md re-carimbado). Handoff para o app central-backend (§6, Gustavo colar lá): o broker precisa do teste que rejeita /provision sem os campos obrigatórios.

  • engenharia/java-micronaut.md §Erros e logging + engenharia/seguranca.md §Validação de input (loop §6, feedback de app): quando o corpo problem+json carrega um code de máquina que um consumidor NOSSO ramifica (ex. a Central lê o code do broker), esse discriminador tem de ser uniforme para toda a falha da mesma classe — a armadilha é o Bean Validation na borda (@Valid @NotBlank) produzir um code derivado do status (BAD_REQUEST genérico) enquanto uma checagem no service produz o code próprio da exceção de domínio, deixando o consumidor com metade das faltas de campo obrigatório num code e metade noutro. Norma: conserte o contrato, não o placement — mantenha o @Valid na borda (norma da seguranca) e faça o ErrorResponseProcessor de ponto único carimbar um code estável também nas falhas de Bean Validation (com a lista de campos). Refina o lado provedor do contrato de entrada travado dos dois lados (item 75): não basta rejeitar faltante com 400 — o 400 consumido por máquina precisa de code uniforme. Sem bump (java-micronaut.md/seguranca.md não são rastreadas). Handoff para o app central-backend (§6, Gustavo colar lá): validar o campo obrigatório de contrato no mesmo lugar que os demais (ou carimbar o code de máquina no processor também para o Bean Validation) — hoje campos validados na borda saem com code derivado do status, inconsistente com os validados no service que a Central consome.

  • engenharia/java-micronaut.md §Armadilhas (loop §6, feedback de app): a armadilha do Test Resources órfão ganha um segundo gatilho além do SIGTERM — rodar o gate com --no-daemon e apagar build/ entre execuções órfã o serviço de TR (sem daemon ele não sobrevive à JVM efêmera; o rm -rf build/ remove os arquivos de descoberta da porta) → mesma falha "Test resource service is not available" → initializationError em massa, que parece bug de código mas é infra. Lado da prevenção: rodar o gate com daemon (default) e não rm -rf build/ no meio de uma sessão de testes; se travar, o sintoma é service not available, não o código. Sem bump (java-micronaut.md não é rastreada).

Corrigido

  • engenharia/java-micronaut.md §Observabilidade (loop §6, feedback de app Java): a receita do SentryAppender colocava o <dsn> flat no appender, mas em io.sentry:sentry-logback 8.x o SentryAppender só expõe setOptions(SentryOptions) — dsn é propriedade do SentryOptions, não do appender. <dsn> fora de <options> não casa com setter nenhum e o logback ignora em silêncio (só um WARN no status interno; build verde) → DSN nunca setado → no-op invisível, e o erro real some em produção sem ninguém notar. Corrigido para <options><dsn>${GLITCHTIP_DSN:-}</dsn></options> (o <minimumEventLevel> é setter do appender e segue flat), com a nota do porquê e do porquê a rota /test/glitchtip existe (pega justo esse buraco). Confirmado na doc oficial do Sentry (logback). Também ajustada a prosa da §Níveis de log que dizia "DSN no próprio appender". Sem bump (java-micronaut.md não é rastreada).

0.23.4 - 2026-07-11

Adicionado

  • Constituição 0.18.7 — regra de layout de diagrama no §5: fluxograma Mermaid é TB (vertical) por padrão. A página tem largura fixa e rolagem vertical infinita, então em TB cada linha usa a largura toda e o texto fica legível sem zoom; em LR o diagrama cresce na horizontal e aperta as caixas. Norma pede labels curtos e direction TB também dentro dos subgraph; LR/RL só para pipeline curto e linear. Enforcement: o lint-mermaid.mjs (rastreado) agora falha o build num flowchart LR/RL com mais de 6 caixas, pedindo TB — escape hatch para o pipeline curto legítimo é um comentário %% lint-mermaid: LR-ok no bloco. Bump PATCH 0.18.6→0.18.7. Loop §6 (feedback de app).

0.23.3 - 2026-07-10

Corrigido

  • Constituição 0.18.6 — o gate de formatação Dart checava reescrevendo: o comando da casa era dart format --set-exit-if-changed ., e o default do dart format é --output=write (grava em disco). O gate saía 1 e consertava os arquivos de passagem — rodar de novo dava verde, o vermelho parecia transitório e a correção ficava não-commitada (o git add do release leva só manifesto + CHANGELOG), de modo que o CI, com checkout limpo da tag, reprovava o que "passou" na máquina. Reabria exatamente o furo que a 0.18.5 fechou, e era invisível no CI (checkout efêmero). Agora o comando canônico é dart format --output=none --set-exit-if-changed . (Flutter e Dart) em todos os pontos que o enunciam: /xadm-release, /x-implementar (+ reference Flutter), /x-planejar, /xadm-meta-audit-work, ci.yml, analysis_options.yaml, engenharia/flutter.md, engenharia/ci-testes.md e documentacao/app-novo.md. Princípio durável novo em ci-testes.md §Definição de pronto: verificador não muta a árvore de trabalho — gate é check, nunca fix (vale para spotlessApply/ktlintFormat do lado Java; use spotlessCheck/ktlintCheck, que o ./gradlew check já agrega). Corolário: se o gate mudou arquivo, o gate estava errado. O bloco Flutter do /xadm-release ganhou também a nota do prefixo fvm em repo com .fvmrc (dentro do container ci-flutter:<v> o SDK está no PATH e o prefixo não se usa). Loop §6, app Flutter. Bump PATCH 0.18.5→0.18.6.

  • Constituição 0.18.5 — o pré-flight do /xadm-release roda o gate de CÓDIGO da stack, não só o de docs (loop §6, app Java). O template enumerava os gates de docs por nome e tratava o código por uma frase genérica ("rode todos os checks do CI") — e o checkstyle, que só dispara dentro do ./gradlew check e não é coberto pela análise estática feita lendo o diff, escapava até o push da tag (CI vermelho com a tag criada, exigindo release de correção). Agora cada bloco de stack em Particularidades traz a linha Gate de código (Java ./gradlew check --no-daemon; Flutter dart format --output=none --set-exit-if-changed . && flutter analyze && flutter test; Node npm run lint && npm test) e o pré-flight tem dois bullets nomeados — código e docs —, vermelho em qualquer um = bloqueador. Máquina sem Docker/Testcontainers roda o degradado (checkstyleMain checkstyleTest) e o relatório final declara o gate degradado. Eco da norma em engenharia/ci-testes.md §CI de código. Bump PATCH 0.18.4→0.18.5.

0.23.2 - 2026-07-08

Adicionado

  • Constituição 0.18.4 — princípio "fórmula de derivação de token é segredo se não há key separada" (loop §6 do central-backend) em engenharia/seguranca.md §Segredos: se um token de auth é derivável de uma fórmula sem um segredo à parte, a fórmula É o segredo — não vai em javadoc/comentário em claro (quem lê o fonte cunha um token válido = bypass); o certo é Kerckhoffs — derivar por HMAC com a key em env/secret (algoritmo público, só a key secreta).
  • engenharia/flutter.md §Testes — verificação visual headless (loop §6, bi-transporte): a receita de screenshot (kScreenshotMode + seed InMemoryAppDatabase) dobra como loop de verificação de mudança de pixel — renderizar pro PNG e ler a imagem confirma a mudança sem app rodando.
  • engenharia/flutter.md §Arquitetura (loop §6, bi-transporte): padrão "extrair side-effect de serviço de estado — delegação, não listener". Quando o estado rastreado (intenção do usuário) ≠ o ValueListenable exposto (efetivo/derivado, com ajustes automáticos), um listener emite eventos espúrios; o serviço delega observer.onChange(prev, next) no ponto da mudança de intenção e move a máquina (buffer/debounce/SDK de analytics) para fora. Ref. FilterAnalyticsObserver.
  • Constituição 0.18.3 — disciplina "caracterização precede refactor" (loop §6): antes de virar tarefa de plano um "unificar/deduplicar N classes de mesmo nome", ler os corpos e confirmar a identidade (caso real: 6 variantes _Measure que pareciam 1). Em engenharia/ci-testes.md §Definição de pronto e no /x-desenhar (§2 explorar — design-time).

Alterado

  • /xadm-setup (constituição 0.18.4, loop §6 do central-backend): a nota de auth M2M passa a proibir derivar o token de serviço da fórmula mesmo com o fonte do central-backend à mão (o filtro de validação à vista) — peça o token ao usuário; computar da fórmula é bypass, não conveniência. Template rastreado → bump PATCH 0.18.3→0.18.4.
  • engenharia/java-micronaut.md (loop §6, refinamentos verificados no integrador): corrige a receita da armadilha Test Resources órfão (era 0.23.1) — ./gradlew --stop sozinho não resolve (a task internalStartTestResourcesService fica UP-TO-DATE); o remédio universal é apagar o estado stale (rm -rf .micronaut/test-resources .gradle/configuration-cache). Nova nota no §Guarda de fronteiras: a guarda de arquitetura é bytecode — não vê dependência via constante static final inlinada (limite conhecido, não violação a caçar). Expande a nota configuration-cache-safe (sem capturar layout/project; ler o ÚLTIMO <counter> do XML do JaCoCo).
  • engenharia/flutter.md §Piso: reforça que os lints de segurança assíncrona (unawaited_futures/ cancel_subscriptions/close_sinks) pegaram 3 bugs reais na adoção — ligar cedo (na caracterização), não no fim.

0.23.1 - 2026-07-08

Adicionado

  • Princípio de engenharia "reusar a infra da casa antes de inventar mecanismo" (loop §6): ao desenhar transporte/gatilho entre serviços, confirmar a infra que já existe (PowerSync acoplado à fonte da verdade) antes de propor um mecanismo novo — evita decisão auto-contraditória. Em engenharia/index.md §Princípios.
  • Constituição 0.18.1 (loop §6) — cobertura de código: política + JaCoCo amarrado ao Java. A política (visível não bloqueante por norma; piso bloqueante opt-in por app) entra em engenharia/ci-testes.md §Definição de pronto; a tech em engenharia/java-micronaut.md §Testes, com a pegadinha crítica: JaCoCo <0.8.15 não instrumenta Java 25 (class major 69) → relatório válido na aparência mas vazio (falso 0% silencioso) — amarre a toolVersion ao Java (Java 25 → ≥0.8.15) + nota configuration-cache-safe. Novo item de conformidade no /xadm-docs (§3b) checa isso.
  • Armadilha (loop §6) em engenharia/java-micronaut.md §Armadilhas: gate morto por SIGTERM órfã um serviço na porta (daemon do Gradle ou Micronaut Test Resources) → initializationError em massa na run seguinte; remédio ./gradlew --stop + matar o órfão (e a norma da casa é o container singleton, que o Ryuk reapa).
  • Constituição 0.18.2 — disciplina de tokens propagada aos apps. O claude-regras.md (CLAUDE.md que os apps herdam) ganha o ponteiro de gate-discipline (gates bare / rtk err / sem pipe-git -C-2>&1; a frugalidade-na-fonte é o portável sem rtk) — antes só cobria caveman+enxuto, e o vazamento medido (rtk discover: ~561K tokens/30d) vivia nos apps. Allow-list do central ampliada (rtk read/grep/ls/find/wc/git diff).

0.23.0 - 2026-07-07

Adicionado

  • Constituição 0.18.0 — cliente_id é a chave canônica do tenant (loop §6, decisão 0008). Desconfla o cliente (rótulo de exibição) do cliente_id (chave clientes.cliente_id, minúscula) que a plataforma vinha derivando do display — a raiz de um incidente. Campo cliente_id novo no templates/app.json (obrigatório quando grupo=cliente); a /xadm-setup lê a chave, valida contra o catálogo do central-backend (fail-closed) e nunca deriva; guarda offline no valida-frontmatter (rejeita cliente_id malformado — o incidente) com fixtures versionadas (incidente + happy-path); norma em §7 e central-de-apps.md.
  • Constituição 0.18.0 — Plataforma de Integração vira norma central (§8), no molde do §7 (decisão 0007). Nova seção §8 (norma curta: organiza-se por shape; taxonomia hub/vertical/reator/app consumidor, ortogonal ao grupo do §5; owner-writes; transporte por direção; PowerSync bidirecional; bi-commons); página de detalhe documentacao/integracao.md (4 shapes, fronteiras, transporte); tech engenharia/powersync.md (connector, write-back, JWKS inline, edition:3, callback-200); invariante owner-writes no §Banco do java-micronaut.md; 7 verbetes no glossário (shape, hub, vertical, reator, barramento, owner-writes, bi-commons). O desenho concreto/volátil (roadmap, D0–D13) migrou para a arquitetura.md publicada no repo do integrador — a §8/página linkam, não copiam.

Alterado

  • Deploy do site volta ao auto-deploy do Coolify — o job deploy gated pelo CI (v0.22.0, faixa 3) foi revertido: exigia auto-deploy desligado no painel + os secrets COOLIFY_WEBHOOK_URL/ COOLIFY_API_TOKEN, que não foram configurados (o job falhava no push com secret ... ausente). Por ora o Coolify auto-deploya no push e a falha do CI avisa no Telegram (webhook nativo da org, Forgejo ≥12); o deploy gated fica como melhoria futura (job comentado no build-site.yml; ver infraestrutura/coolify.md).

0.22.0 - 2026-07-06

Adicionado

  • Faixa 3 da auditoria (constituição 0.17.36): (a) deploy gated pelo CI — o build-site.yml ganha o job deploy (needs: validar, só push em master) disparando o webhook do Coolify; CI vermelho = site antigo no ar (antes o auto-deploy ia pro ar com o CI vermelho; os dois modelos documentados em infraestrutura/coolify.md; requer auto-deploy desligado no painel + secrets COOLIFY_WEBHOOK_URL/COOLIFY_API_TOKEN). (b) Fábrica de imagens: build-push.sh ganha smoke test pré-push (java -version/ flutter --version/toolchain da base — imagem quebrada não entra no registry) e isolamento de falha por item (upstream quebrado de uma stack não bloqueia o rebuild das outras; resumo das falhas no fim, exit 1). (c) references/docs.md nas 5 skills x-* com references — a stack do próprio central (concerns de entrevista/spec/refino/ plano/execução para repo MkDocs: slug/redirect, dono do fato, satélites, artefato rastreado → bump, gate docs completo); 5 novos rastreados no manifesto.

  • CI do central = pré-flight nas duas direções: build-site.yml ganhou checa-nav.py (órfão de nav não falha o --strict — foi assim que um órfão real nasceu), valida-frontmatter.py docs/ e o publica-versao.py --self-test (grátis, stdlib).

  • sync-apps.sh: guarda de slug ([a-z0-9-]) nos nomes vindos do bucket — a key de escrita é única da org; slug malicioso/acidental com ../, espaço ou aspas podia virar traversal no docroot, quebrar o loop ou corromper o index.json de todos os apps.
  • /x-documentar: antes de apagar o andaime .ia/, confere git log -- .ia/NNN-* — arquivo nunca commitado (ciclo inteiro numa sessão) não é apagado, entra no commit do ciclo (senão "o histórico fica no Git" era promessa falsa e o rastro L# se perdia).
  • /xadm-release: disable-model-invocation: true no frontmatter — só o usuário invoca a única skill autorizada a commitar/pushar (fecha o vetor de release não-comandado, REGRA Nº 2).

  • Constituição 0.17.31: piso "saída do agente enxuta por default" na §6 — corta prosa conectiva e status óbvio (gate verde não se narra; nomeia-se só o que falhou), não re-descreve o que o diff/tool já mostram nem ecoa a escolha do usuário, e a prosa do fechamento §6 é terse (o literal — commit + linha §6 — não). Nunca comprime o carve-out. Vale com ou sem rtk/caveman. Detalhe e exemplos de mensagens recorrentes em engenharia/agentes.md (nova seção "Status e fechamento — nomeie só o que falhou").

Alterado

  • Constituição 0.17.36 — consolidação textual SEM mudança de norma (§6, §1.9, §2): o bullet "Definição de pronto" (27 linhas, 4 níveis de parênteses) virou 3 sub-bullets com o racional movido para ci-testes.md (que ganhou os bullets "deferir é custo × valor" e "fixture capturada" na seção de detalhe); "No CI (código)" enxugado com a fidelidade-ao-artefato apontando pro dono; §1.9 estruturado em 4 consequências nomeadas (andaime / segredo / código×doc / um-fato-um-dono); §2 ganhou subseções navegáveis (livro, nav+resumo, conteúdo mínimo); o cabeçalho deixou de prometer "documento curto e estável" e passou a dizer o que é (o piso; detalhe nas áreas).
  • engenharia/flutter.md §Específico reorganizado por tema (Build e versão do SDK / Config e Central de Apps / Observabilidade / Receitas) — era sedimento de feedbacks; bullets intactos.

  • Faixa 2 da auditoria — o "miolo do código" vira norma escrita (constituição 0.17.35; destilado dos pilotos com leitura autorizada: auth, bi-transporte-xls, bi-comercial-xls, bi-transporte, bi-comercial, authui):

  • Arquitetura v0.1 por stack: engenharia/flutter ganha §Arquitetura (layout feature-first, taxonomia de DI get_it/watch_it com lazy+dispose+dependsOn, estado no manager como valor observável, command_it, como nasce uma feature, variações declaradas) e engenharia/java-micronaut idem (package-by-feature travado por ArchUnit, constructor injection puro, controller fino, record @Serdeable vs @MappedEntity, @ConfigurationProperties, nomes pt-BR citando decisões).
  • Erros e logging por stack: exceção tipada na borda + erro-como-valor + trio loading/vazio/erro + AppLog com breadcrumbs/tags (Flutter); HttpStatusException + RFC 7807 num ponto único + semântica ERROR/WARN/INFO consciente do SentryAppender + logback com tuning comentado (Micronaut). Gap de request-id declarado como pendência, não fingido como prática.
  • Testes e fixtures: convenção golden-master datada com receita de captura no javadoc e anonimização obrigatória; WireMock com shape capturado (§6); Testcontainers com container compartilhado pinado à versão do deploy; unit×integração por environment (Micronaut) — e in-memory DB com DDL de produção, builders, seams nomeados, pushNewScope, teste de contrato do app.json (Flutter).
  • engenharia/seguranca.md (nova página): authn/authz fail-closed com tri-estado, tenancy pela claim (servidor decide), comparação em tempo constante, validação jakarta obrigatória em request novo, client-grade ≠ segredo, checklist do revisor.
  • Banco (Postgres compartilhado) em java-micronaut: Flyway VN__pt_br com história nomeada por app e destrutiva justificada; pool Hikari deliberadamente pequeno (o Postgres é compartilhado); CRUD via Micronaut Data + bulk via JDBC manual.
  • Gate Dart completo: templates/analysis_options.yaml canônico (novo rastreado, flutter_lints + regras de segurança assíncrona do piloto) e dart format --set-exit-if-changed . no gate Flutter/Dart (ci.yml, páginas, skills x-planejar/ x-implementar/meta-audit — a varredura de satélites do 0.17.34 pegou os 5 pontos).
  • Teste de regressão do valida-frontmatter: fixtures versionadas (scripts/fixtures-valida-frontmatter/, 10 casos) + testa-valida-frontmatter.py no CI e no pré-flight da release — era o script de maior blast radius sem cobertura; de quebra, frontmatter com YAML quebrado agora vira erro legível em vez de traceback.

  • Constituição 0.17.34 — dois pisos de token viram norma escrita (pedido do Gustavo): (a) §6 (Regras de IA): o CLAUDE.md, além de roteador, é carregado inteiro em toda sessão — backlog/histórico concluído migra para arquivo-acervo não carregado (padrão BACKLOG-HISTORICO.md; caso medido no central: −15k tokens/sessão); (b) §6: a saída do agente é tersa (estilo caveman) por default — telegráfica no chat, com carve-out explícito que agora inclui conteúdo de documentação (texto em docs/ sai em prosa normal pt-BR, §1.8) além da mensagem de commit, código, linha §6, tabelas e .ia/. Ecos: carve-out de engenharia/agentes.md ganha o item documentação; comentário do templates/claude-regras.md (rastreado, recarimbado) carrega os dois lembretes para os apps.

  • Constituição 0.17.33 — §6 enxuto em tokens: os dois bullets operacionais de rtk/caveman ("saída enxuta" e "disciplina de tokens") encolhem para o piso + ponteiro; o como (envelope rtk err nos gates, comando bare, exemplos de fechamento) já tinha dono em engenharia/agentes.md e engenharia/ferramentas.md (um fato, um dono, §1.9). Norma inalterada.

  • CLAUDE.md do central em dieta: o histórico do backlog (itens 0–52) migrou para BACKLOG-HISTORICO.md (acervo, não carregado pelo Claude Code) — o CLAUDE.md era ~20k tokens carregados em toda sessão, ~85% histórico; fica com regras + contexto + pendências + itens recentes (~7k). Ganha também: bloco TOKENS (gates via rtk err, comando bare), regra de migração contínua (>~10 itens → histórico) e a varredura de satélites no §8 (bump da constituição → conferir glossário/templates/publicar-docs/ app-novo/ia/READMEs — trata o candidato §6 da rodada anterior: o manifesto protege os artefatos copiados, as páginas do site só têm essa varredura).

  • Constituição 0.17.30: o pré-flight do /xadm-release deixa claro que worktree limpo com commits à frente do origin é o caso NORMAL — a IA não commita (REGRA Nº 2), então o conteúdo da sessão é commitado pelo usuário e o release apenas segue, sem auditar a autoria dos commits à frente. Evita o falso alarme de "commit que eu não fiz" no início do release. Tocado o template rastreado xadm-release-skill.md (+ cópia instalada).

Corrigido

  • Constituição 0.17.32 — rodada de reconciliação de drift (auditoria interna do central, 4 pareceres): (a) templates/xadm.css re-derivado da fonte do site — a cópia distribuída ainda carregava navy/burgundy, paleta que o logo real não tem (a vigente é azul royal #064490 + prata; comentários stale em templates/mkdocs.yml, mkdocs.yml e templates.md corrigidos juntos); (b) templates/docs.yml passo versions.json usava o placeholder <app> hardcoded em vez de "$SLUG" (contradizia a fonte única do slug e quebrava o 1º release versionado do app); (c) templates/anexos-readme.md prometia guarda extinta ("o CI bloqueia massa de dados") — agora diz a regra real (validador só bloqueia em docs/public/; fora de privado/ publica, responsabilidade do autor); (d) xadm-meta-audit-work realinhada ao pipeline atual — auditava a extinta /xadm-work (estágios *_phase, exigia mensagem de commit que o run concluído corretamente NÃO entrega desde a ordem implementar → documentar → commit); (e) instrução de instalação de skills unificada (app-novo diz 10 core incluindo xadm-setup; templates.md ganhou a linha/seção da /xadm-setup e aposentou os nomes pré-rename spec/refine/plan/work; ia.md §Como adotar delega ao manifesto em vez de mandar copiar só 2); (f) exemplos que ensinavam errado: app.json de publicar-docs.md sem aptabase_host (o contrato corrigido no 0.17.23), templates/app.json com Flutter 3.35.0 fora da baseline (gate do release barraria), templates/riscos.md com nomenclatura PROJ- legada, glossário 2 reorgs atrás (manual como nível 4, superado-por: sem aspas — formato que o validador rejeita) e estrutura legada (manual/, img/) em publicar-docs.md.

0.21.0 - 2026-07-04

Adicionado

  • Constituição 0.17.29: §6 (No CI/código) ganha o piso "o gate é fiel ao artefato que embarca" — test roda no classpath, mas o que sobe é o fat jar/container; ordem, descoberta e configuração podem divergir (merge de META-INF, native-image, shading). O default é eliminar a divergência (determinismo/paridade), não "smoke em prod"; só onde a divergência é inevitável o gate exercita o artefato empacotado (sobe o jar/container e bate num endpoint). Detalhe do "como" em engenharia/ci-testes.md; regra concreta de stack (@ServerFilter honra @Order, @Filter legado não → ordem indefinida no fat jar) em engenharia/java-micronaut.md. Feedback §6 de app Micronaut.
  • Gate toolchain ∈ baseline da fábrica: a matriz-baseline.json passa a ser publicada em /toolchain/matriz-baseline.json; o pré-flight do /xadm-release bloqueia e o /xadm-docs 3b avisa cedo quando um app declara versão de stack (app.json/.fvmrc) não homologada na fábrica — pega o "manifest unknown" antes da tag, não mais só no 1º release (constituição 0.17.28, decisão 0003).

0.20.0 - 2026-07-04

Alterado

  • Constituição 0.17.24: o passo analytics do /xadm-setup pergunta o arquétipo do app (produto vs interno/admin) antes de sugerir eventos — produto mantém o baseline de product-analytics; interno/admin passa a mirar a ação sensível (quem fez), e não screen_view/retenção. Adicionado o caveat de que evento client-side é visibilidade operacional, não audit trail (auditoria de verdade pertence ao backend). Espelhado em central-de-apps.md.
  • Guia de IA (transversais/ia.md): seção opcional de otimização de custo de tokens — ferramentas de arquivo/busca em vez de despejo de shell, comunicação enxuta onde não custa clareza, e ferramentas pessoais opcionais que o agente pode oferecer (nunca impor).
  • Constituição 0.17.25: higiene de saída de comando — nova seção em engenharia/ci-testes.md (corte o verboso na fonte; reporter frugal sempre; nativas de arquivo sobre shell; proxy é complemento) + comando frugal por stack (flutter test -r failures-only, ./gradlew test --quiet --console=plain, …) nas páginas de stack, e a §6 afirma que o gate roda com reporter enxuto (verboso só sob falha).
  • Nova página engenharia/agentes.md (Trabalho com agentes de IA): consolida a disciplina de tokens do agente — input frugal (link p/ ci-testes) + output terso + carve-out verbatim (nunca comprimir: código, mensagem de commit, linha §6, tabelas de dado, .ia/). O estilo terso vira prática recomendada via hook SessionStart (caveman.sh, mesmo padrão do checa-constituicao; funciona sem plugin); passo no checklist de app novo. transversais/ia.md reconciliada. Sem bump (só doc/engenharia, não rastreadas).
  • Nova página engenharia/ferramentas.md (Ferramentas de token — rtk): o rtk (proxy que resume a saída de CLI antes do contexto) vira recomendação da casa — install/verify, habilitar (hook rtk hook claude), configurar como padrão (ignore_dirs por stack, tee em falha) e regras por stack (Gradle bare sem pipe + sdk default java p/ dispensar o prefixo JAVA_HOME= que fura o hook; git bare sem --porcelain; nativas sobre cat/find; rtk não cobre flutter). Reconcilia o conflito | tail da norma frugal (flags de reporter ficam; pós-processamento com pipe cede ao bare+rtk). ci-testes.md/agentes.md/ia.md reconciliadas. Sem bump (não rastreada).
  • engenharia/ferramentas.md passa a cobrir rtk (input) + caveman (output) como ferramentas recomendadas simétricas, cada uma com install/habilitar/configurar. Install real por plataforma dos repos oficiais (rtk-ai/rtk: brew/curl/cargo/Windows + rtk init -g; juliusbrussee/caveman: plugin do Claude Code / instalador universal). A receita do hook caveman.sh migrou de agentes.md (que ficou com o princípio + carve-out e aponta pra cá). Sem bump (não rastreada).
  • Constituição 0.17.26: auto-análise de uso das ferramentas de token no fechamento §6. A §6 e a skill /x-documentar ganham o passo (condicional a usar rtk/caveman): ao fechar, o agente mede (rtk gain/rtk discover; caveman = qualitativo), auto-corrige (bare/nativas/terso+carve-out) e roteia o gap sistêmico — recipe de stack → §6 (outros apps aprendem); comando verboso sem handler nativo → envolver com rtk err/rtk summary (ajuste de uso, resolve a maioria), RTK tracker só o residual. Nova seção "Auto-análise" em engenharia/agentes.md + hook opcional leve token-check.sh (eficiência do rtk gain no start); passo no app-novo.

0.19.5 - 2026-07-04

Alterado

  • Client_grade de analytics inclui aptabase_host (Aptabase self-hosted) (constituição 0.17.23; feedback §6). A key A-SH- self-hosted é inútil no cliente sem o host — o SDK Flutter exige InitOptions(host:) (≠ do DSN do GlitchTip, que já embute o host). Sem isso o /xadm-setup tinha que hardcodar/adivinhar o host e gravá-lo à mão no app.json (sujeito a clobber num re-provision). Agora o client_grade = aptabase_key + aptabase_host; central-de-apps + xadm-setup atualizados. Handoff central-backend: o AptabaseProvider devolve aptabase_host no client_config.

  • Metabase organiza por sub-pasta (grupo/cliente → app_id → dashboard + cards) (feedback §6 — o central-backend implementou, decisão 0014). Antes o central documentava dashboard+cards soltos numa collection (exigia sufixar os cards com app_id pra não colidir). Agora cada app ganha uma sub-pasta (collection filha, nome = app_id) dentro da collection do grupo/cliente, contendo o dashboard + os cards com nome canônico (sem sufixo, já isolados). Espelha o app.json. central-de-apps atualizado; andaime .ia/016-* apagado (feature pronta). Sem bump (docs page).

  • §6: fixture de API externa é CAPTURADA da instância real, não inventada (constituição 0.17.22; feedback §6). Um teste (ex. WireMock) verde sobre um shape fabricado é falso atestado — o mesmo "validador stale = falso 0 erros" que o §6 já combate, aplicado a fixtures de API (quebrou 2× em prod: Aptabase e Metabase, com app_key/id:-1 inventados). Agora a §6 (norma) e o x-documentar (§3 auditoria de lacuna) exigem: fixture de API externa capturada da instância real, e provider de API externa só marca "ligada" com ≥1 resposta real capturada.

  • /xadm-release: pré-flight roda o build de docs + CHANGELOG sem link relativo (constituição 0.17.21; feedback §6). (1) Referência no CHANGELOG = texto puro ou URL absoluta, nunca [texto](docs/…) relativo — o CHANGELOG é embutido em docs/ via snippet, o link vira docs/docs/… e quebra o --strict. (2) O pré-flight roda o BUILD de docs que o docs.yml roda (mkdocs build --strict com hooks + javadoc/dartdoc) — um gate de infra vermelho (dind) mascara os gates seguintes no CI, então rodá-los local evita descobrir em prod.

  • /xadm-docs: relevância por stack (evita falso-positivo do mudou_em stack-cego) (constituição 0.17.20; feedback §6). O mudou_em do manifesto é por-arquivo-global, não por-stack — uma mudança Java-only (ex. ci.yml Testcontainers) marca ci.yml defasado em app Flutter. Agora o /xadm-docs, para arquivo multi-stack customizado-por-app (o ci.yml, cujo diff verbatim é esperado), manda checar a entrada do CHANGELOG da versão mudou_em para ver se a mudança toca a stack do app — se não toca, não é defasagem pra ele (mantém a customização). Decisão: guidance, não campo stacks: no manifesto — o ci.yml é multi-stack, e um stacks: [java] esconderia updates Flutter-relevantes (nav-check etc.).

Corrigido

  • ci.yml Testcontainers: DOCKER_HOST via runner, não docker.sock (topologia dind isolado) (constituição 0.17.19; feedback §6 — corrige o v0.19.4). O DooD por docker.sock que saiu no v0.19.4 não funciona no runner da org (act_runner com dind privilegiado → o mount do socket do host vira "not a valid volume"), e todo @Testcontainers falha no CI. Fix org-wide mantendo o isolamento: o config.yaml do act_runner injeta DOCKER_HOST=tcp://172.17.0.1:2375 (gateway do dind) + TESTCONTAINERS_HOST_OVERRIDE + RYUK_DISABLED nos jobs → o templates/ci.yml fica agnóstico (removido o docker.sock). Detalhe/config em forgejo.md. Pendência do Gustavo: configurar o act_runner.

0.19.4 - 2026-07-03

Alterado

  • Definição de pronto (§6): deferir teste é custo × valor, não racionalização (constituição 0.17.18; feedback §6). "Exercitado end-to-end" e "baixo risco" são justo as frases que deixam a guarda de regressão barata escapar — sobretudo em segurança/privacidade/contrato. Agora a §6 (norma) e as skills x-implementar/x-documentar exigem, por lacuna de teste, uma classificação explícita custo × valor: default = escrever a guarda barata e proporcional agora; só deferir no quadrante caro E de baixo valor; comportamento sensível → escrever a guarda agora. Bump 0.17.17→0.17.18 (as duas skills rastreadas + §6).

  • Fluxo do pipeline: /x-implementar → /x-documentar → commit (constituição 0.17.17). O /x-implementar, ao concluir o plano, deixa de entregar a mensagem de commit — aponta pro /x-documentar, que faz a revisão geral (avalia lacuna de doc e de teste, §6), destila o durável pra docs/, apaga o andaime (.ia/NNN-*) e entrega a mensagem de commit do ciclo. Antes o commit era sugerido no x-implementar; agora o ciclo fecha no x-documentar. Bump 0.17.16→0.17.17 (as duas skills rastreadas + ia.md).

  • Feedback §6 (docs de engenharia/central): paleta por grupo, receita de screenshots, gotchas, formas de provider — sem bump (engenharia/documentacao não são rastreadas). (1) A paleta do cliente passa a ser gatilhada pelo grupo: cliente do app.json (marca derivada do logo do cliente), não mais o "veste a marca declarado" do #31 — mais simples (engenharia/flutter). (2) screenshots-manual ganha o seed com DDL do schema PowerSync + o gotcha registerSingletonAsync. (3) gotcha do ink_sparkle.frag em bump de SDK (flutter clean+pub get) em engenharia/flutter. (4) central-de-apps destila as três formas de provider (find-before-create / forjar-token / reuso-persistido) e java-micronaut ganha 2 armadilhas REST (PUT substitui coleção → read-modify-write; nomear por escopo em container compartilhado).

  • Check de nav-drift (checa-nav.py) no gate de código (constituição 0.17.16; feedback §6). Num repo stack+MkDocs o gate de código não roda mkdocs → um .md novo fora do nav: só estoura no docs.yml (que não roda a cada PR de código) e órfãos acumulam (o app relatou 3 decisões + 1 runbook órfãos por 2 fases). Novo scripts/checa-nav.py (stdlib): FALHA se um .md de docs/ não está no nav: nem excluído (isenta README.md de pasta); rodado ativo no ci.yml (tolerante sem docs/), publicado como os outros validadores. §6 esclarecida: "doc do estado atual" inclui aparecer no índice, não só existir. Dogfood: pegou o decisoes/0006 órfão no próprio central (corrigido no nav).

  • Constituição §2: provider automagico não precisa de runbook de "como usar" (constituição 0.17.15; feedback §6). O que roda sozinho (ex. provider build-time da Central de Apps — "como usar" = a skill, "porquê" = a decisão) não ganha runbook; sobra config-de-operador (envs + como obter) + diagnóstico, numa seção "Operar" da própria decisão, não num runbook que duplica o design. Os runbooks provedor-*.md (no central-backend) morrem — cleanup do central-backend (handoff).

  • Auto-config do Metabase no /xadm-setup — fatia do central (constituição 0.17.14; feature 016, desenhada via /x-desenhar→/x-definir→/x-refinar→/x-planejar→/x-implementar). Depois do Aptabase, o broker auto-configura o Metabase (API real): collection(cliente) → dashboard(app) → cards baseline que adaptam aos eventos escolhidos; idempotência por id estável; resposta honesta com sub-estados aptabase+metabase; 6 queries ClickHouse = template canônico no broker; grava metabase_dashboard_id (pro embed futuro). central-de-apps (provider analytics) + xadm-setup-skill (passo). A implementação do broker Metabase é do repo central-backend (handoff).

  • Rota de diagnóstico /test no baseline de observabilidade (opt-in) (constituição 0.17.13; feedback §6). Verificar pós-deploy que o erro chega no GlitchTip era improvisado por app; agora engenharia/flutter (tela oculta login-gated com botão "enviar exceção de teste") e engenharia/java-micronaut (endpoint autenticado /test/glitchtip) trazem a receita, e o /xadm-setup oferece scaffoldar (opt-in, com o OK do dev) junto da instrumentação. Não obrigatório (backend/cron não é forçado a ter UI de diagnóstico).

  • Aptabase no /xadm-setup: provisão full-auto forjando o token (sem API) (constituição 0.17.12, mecanismo corrigido em 0.17.15; feedback §6 de um app). O Aptabase self-hosted não tem API e autentica por sessão. Descoberto empiricamente (decisão 0013 do central-backend): o token é um JWT simétrico (HS256) assinado com o secret do próprio tool → o broker forja o token (secret no env do central-backend) e abre sessão direto, depois cria o app → A-SH-…. (O "magic-link dos logs do Coolify" não funciona — o Coolify não expõe logs de service.) É o padrão "provider sem API → forjar o token"; guardas: fail-fast, versão fixada, secret forja sessão de qualquer usuário. O central-de-apps documenta o fluxo + o padrão; o passo do /xadm-setup passa a perguntar ao dev quais eventos registrar e scaffolda o helper só para os escolhidos.

  • Ponte app.json→--dart-define no build Flutter-web + /xadm-setup honesto (constituição 0.17.11; feedback §6 de um app Flutter-web da Central de Apps). O /xadm-setup passo 5 assumia que "o Dockerfile/CI já lê o app.json", mas o scaffold hardcodava ARGs e nem tinha jq. engenharia/flutter ganha a receita concreta (instalar jq + flutter build … --dart-define= $(jq -r .features.<x>.<ref> docs/app.json)) e reconcilia "config no repo" (= app.json + ponte, não hardcode; client-grade não precisa de build-arg do Coolify — só segredo). O passo 5 passa a mandar garantir/criar a ponte (aponta pra receita), não "confira que já existe".

  • Identidade visual: app que veste a marca do cliente usa a marca do cliente, não a da casa (feedback §6 do bi-comercial/OnPetro). A engenharia/flutter tratava "paleta = a da casa" como absoluto; agora a identidade da casa é o default (app da X-Adm / app de cliente sem marca própria) e um app exclusivo que veste a marca do cliente usa a paleta/logo/tipografia do cliente (declarado no app.json). O gatilho é vestir a marca do cliente, não o mero cliente:; o mecanismo da casa (tema por ColorScheme, app-icon, feature-first) segue valendo em todo app. Sem bump (engenharia é regra viva, não rastreada).

0.19.3 - 2026-07-03

Corrigido

  • checa-dogfood.py reconhece skills em diretório (destrava o deploy do v0.19.2). O gate de CI comparava .claude/skills/<nome>/SKILL.md só com templates/<nome>-skill.md (plano) e acusava as 6 x-* (modelo diretório) como "sem template canônico" → job validar vermelho → deploy travado. Agora compara o diretório inteiro (SKILL.md + references/) para as x-* e mantém o modelo plano para as xadm-*.

Alterado

  • Pré-flight do /xadm-release roda todos os checks do CI (constituição 0.17.10; feedback §6 do incidente acima). O gate local rodava só lint-mermaid + gera-manifesto --check + valida-frontmatter, mas o CI roda também checa-dogfood.py e checa-export-download.py — por isso o drift passou no pré-flight e só estourou no CI depois da tag pushada. O §1 agora manda rodar todos os checks do CI do repo (condicional "se o repo os tiver", pois são central-only).

0.19.2 - 2026-07-03

Adicionado

  • Recipe GlitchTip/Sentry para Micronaut e armadilha @Consumes em endpoint de <form> na página engenharia/java-micronaut.md (feedback §6 do central-backend). Sentry via io.sentry:sentry + sentry-logback (SentryAppender no logback.xml, minimumEventLevel=ERROR, DSN vazio → no-op, zero código no negócio; espelha o bloco Flutter). @Consumes: @Post assume JSON → 415 em form x-www-form-urlencoded; armadilha de teste (o HttpClient manda JSON por default).

Alterado

  • Contrato do /provision (Central de Apps): injetar APLICA a config (reinicia), não só grava (constituição 0.17.9; feedback §6 do central-backend). Env upsertado não recarrega em processo vivo → o broker reinicia o recurso Coolify ao injetar, e injected:true passa a significar "injetado E em vigor"; reinicia só quando o valor mudou (lê o atual + compara), pra não derrubar serviço sensível (central-backend) à toa em re-run. Provado no central-backend (decisão 0011).

  • templates/ci.yml Java dá Docker ao Testcontainers (DooD) (constituição 0.17.9; feedback §6 do central-backend). O job container: ci-java não montava o socket do host → todo @Testcontainers (padrão da casa p/ "banco real") falhava no CI com "could not find a valid Docker environment", deixando o gate §6 "No CI (código)" vermelho crônico. Correção: container.volumes monta /var/run/docker.sock; pré-requisito do runner (act_runner valid_volumes) documentado em infraestrutura/forgejo.md. TESTCONTAINERS_RYUK_DISABLED=true já vinha no ambiente, mas não bastava — faltava o socket.

  • Skills de workflow renomeadas e realinhadas ao ACT 1.0 (constituição 0.17.8). O pipeline spec → refine → plan → work → compound virou x-desenhar → x-definir → x-refinar → x-planejar → x-implementar → x-documentar, adotando o split entrevista/spec do ACT 1.0 (x-desenhar conduz a entrevista e grava o prompt .ia/NNN-*-prompt.md com o rastro L# embutido; x-definir escreve a spec referenciando os L#), tarefa = corpo de Work Item embarcado no plano, e especialização de stack via references/<stack>.md (Flutter portado do ACT + Micronaut autorado) carregada pelo §0 — sem variantes-skill. Preserva os invariantes da casa (NÃO commita, §0, gate da stack, §6, ghost mode, lineage .ia/, storage .ia/ sem .act/). Cada skill agora é um diretório templates/<nome>/ (SKILL.md + references/). Manifesto, gera-manifesto.py, xadm-docs e as referências (CLAUDE.md, ia.md, constituição, app-novo, templates.md, engenharia) atualizados. Apps migram oportunisticamente via /xadm-docs. Ver docs/decisoes/0006-skills-workflow-act1.md.

  • Constituição 0.17.7 — "idempotente" = procura-antes-de-cria (feedback §6: dois projetos com o mesmo nome no GlitchTip). O contrato dizia só a palavra "idempotente", que a implementação leu como cria-depois-procura — e como o GlitchTip permite nome duplicado, um re-run (ou falha no find + nova chamada) duplicou o projeto. central de apps §Provedores agora define o mecanismo: o provider checa se o recurso já existe (por nome/app_id) e reusa, nunca cria às cegas; onde o provedor permite nome duplicado, o find é obrigatório. O guard find-before-create em si é Camada 2 (auth — o 377c050 corrigiu o find). Bump PATCH 0.17.6→0.17.7.

  • Constituição 0.17.6 — /provision responde estado real + /xadm-setup instrumenta o código (feedback §6 da implementação do central-backend). Três furos: o /provision respondia 200 com enabled:true sem dsn quando o provider falhava (indistinguível de sucesso); a injeção no Coolify falhava em silêncio (best-effort, só WARN — token 401, DSN não injetado, ninguém soube); e o setup ligava a integração sem instrumentar o código. Correções em central de apps: a resposta do /provision carrega o estado real por provider — provisão (state/last_error) e injeção (injected + motivo), com não-2xx se nada ligou; o /xadm-setup confere e não grava enabled sem o ref, avisa em injected:false (setar o env à mão) e não re-chama provision "pra confirmar" (isso gerava projeto GlitchTip duplicado). E a skill passa a instrumentar o código (scaffold + guia, com OK do dev): instalar o SDK, envolver o runApp (SentryFlutter.init), guiar os pontos de captura — recipe em engenharia/flutter. O guard find-before-create do provider é Camada 2 (auth). Bump PATCH 0.17.5→0.17.6.
  • Constituição 0.17.5 — convenção: projeto GlitchTip nomeado pelo app_id (feedback §6 da implementação do central-backend). O slug do projeto GlitchTip é sempre slugify(nome) (o slug do corpo é ignorado, imutável por PUT) e o dsn usa o id numérico, não o slug. Convenção registrada na skill xadm-setup (passo glitchtip) e em central de apps §Provedores: o broker nomeia o projeto pelo app_id (slug estável e curto), decorrência do app_id canônico. A trivia da API do GlitchTip (slug ignorado no corpo, PUT, DSN por id) fica no GlitchTipProvider do central-backend (Camada 2), não no contrato central. Bump PATCH 0.17.4→0.17.5.
  • Identidade visual reconciliada com o logo + norma visual pra apps Flutter (feedback §6). O xadm.css afirmava paleta "extraída do logo", mas o xadm-logo.png é azul royal + prata, sem o burgundy #8b3a3a documentado (verificado amostrando o PNG). Decisão do Gustavo: derivar do logo real — a paleta da casa passa a ser azul royal #064490 (primária) + médio #2a5b97 (accent) / claro #7092bb / escuro #042c5e + prata #939495 (neutro); sai o navy/burgundy. docs/stylesheets/xadm.css vira a fonte-da-verdade dos tokens (docs e apps consomem), e engenharia/flutter ganha a seção Identidade visual (paleta→ColorScheme, logo + receita de app-icon flutter_launcher_icons, tipografia/densidade como decisão). O ThemeData compartilhado, o kit mínimo de componentes e a variação quadrada do logo ficam para uma spec do padrão de identidade visual de apps (barato agora, resto na spec). Sem bump da constituição (docs/assets, não artefato rastreado).
  • Constituição 0.17.4 — broker de setup autentica por token de serviço (M2M) (feedback §6 da implementação do central-backend). O contrato pressupunha login humano (JWT xadm admin/Firebase) pra chamar o /api/setup/** — mas é endpoint máquina-a-máquina (o /xadm-setup), e nenhum CLI tem como obter esse JWT (loopback-OAuth inexistente). Agrava que o /provision recebe production_url e injeta env no recurso Coolify → num endpoint aberto, a auth importa de verdade. Nova norma (§7 + central de apps seção "Auth do broker de setup" + xadm-setup-skill): token de serviço, com força casada à exposição (interno/VPN → simples; público → segredo forte ou rotativo por HMAC-de-data, nunca fórmula pública); a regra de geração vive fora de banda (servidor + admins), jamais na skill ou em doc publicada — a skill conhece só o formato e pede o valor. Registra o gap "como obter a credencial de setup". Bump PATCH 0.17.3→0.17.4; manifesto regenerado (xadm-setup-skill).
  • Constituição 0.17.3 — Central de Apps: injeção de config (não só secret) e web ≠ server (feedback §6 da implementação do central-backend). A tabela "duas naturezas" juntava web/server e dizia que o client-grade vai como build-arg — só vale pra web (bundle). Um app server lê config do env em runtime, então o client-grade dele (ex. SENTRY_DSN) também é env injetada no Coolify, não build-arg. Correções em central de apps: linguagem "injeção de secret" → "injeção de config" (client-grade + secret); tabela com colunas web / server / mobile; checkpoint de deploy garante a config (não só secret); ponteiro pro endpoint de env do Coolify. Bump PATCH 0.17.2→0.17.3. (Mantido o modelo de 3 checkpoints — não adotada a opção de dobrar a injeção no /provision.)

0.19.1 - 2026-07-01

Corrigido

  • Diagrama Mermaid da Central de Apps + lint no gate local. O sequenceDiagram de central-de-apps.md tinha um ; numa mensagem (separador de statement no Mermaid) que quebrava o lint-mermaid do CI e travava o deploy desde o push do build-time — sem aparecer em nenhum check local, porque mkdocs build --strict não parseia diagramas. Corrigido o ;; e o lint de Mermaid entra no gate local (constituição 0.17.2): /xadm-work e /xadm-plan (verify docs), pré-flight do /xadm-release (bloqueia antes de taggar) e o gate docs documentado em ci-testes. Bump PATCH 0.17.1→0.17.2; manifesto regenerado (skills work/plan/release).

0.19.0 - 2026-07-01

Adicionado

  • Constituição 0.17.0 — Central de Apps (Camada 1, paradigma BUILD-TIME). Nova ## 7 + central de apps: a integração de um app com a infra X-Adm (erros/GlitchTip, arquivos/Garage, analytics Aptabase+Metabase, docs) é configurada em build-time, não amarrada em runtime. O app declara no app.json (features vira objeto por funcionalidade); a skill nova /xadm-setup (entre codar e /xadm-release) pergunta o que ativar, provisiona via o central-backend-broker (que tem os tokens admin) e grava o resultado client-grade no app.json — o build embarca, sem buscar config no boot. Runtime só o mínimo: corretores (embed Metabase/presign Garage) e feature flags (protocolo próprio — spec 014). central-backend = broker + configurador de secrets (Coolify/CI) + corretor; central.xadm.biz = dashboard (mostra o configurado + links, aprova acesso de terceiros). 3 checkpoints (setup → guard soft na /xadm-release → guard de deploy no ci.yml); 2 naturezas × plataforma (client-grade web=build-arg / mobile=--dart-define; secret web=Coolify / mobile=org-CI); fronteira de dados (auth×app; cliente_id claim×FK). Tocados: templates/app.json (features objeto), valida-frontmatter.py (rejeita chave desconhecida), skill xadm-setup nova, guard soft na xadm-release, placeholder de deploy no ci.yml, ecos em publicar-docs/app-novo/flutter; Bugsink→GlitchTip reconciliado (grep=0). Bump MINOR 0.16.1→0.17.0. Substitui o desenho runtime anterior (não shipado). Camada 2 (código auth/central/apps) + feature flags (014) em specs próprias.

Alterado

  • Constituição 0.17.1 — corretor autoriza por app + provisão por app_id (achados do plano de implementação da Camada 2 no central-backend). O contrato do corretor exigia só JWT válido (autenticação); agora a §7 e central de apps exigem autorização por app — o claim do JWT casa com o app do recurso (403 cross-app): usuário logado no app A não cunha embed/presign de recurso do app B. E a §Catálogo separa provisão (por app_id) de acesso (por (cliente, app), derivado dos oauth_access_grants) — não há tabela de pares para a provisão. Bump PATCH 0.17.0→0.17.1 (só docs de padrão; nenhum artefato rastreado mudou).
  • Constituição 0.16.1 — migração de conteúdo cross-repo + template×piloto (feedback §6 do authui). A regra de migração só cobria conteúdo do próprio repo (guias/ por conteúdo); agora a §3 e a /xadm-docs dizem que doc/runbook que pertence a outro repo, achado neste, é realocado para o repo dono — não absorvido/relabelado no operacao/ local. E o app-novo.md esclarece template × piloto: o template (sincronizado via manifesto) é a única fonte de cópia; o app piloto/de referência é exemplo provado/destilado (Engenharia), nunca base de cópia. Bump PATCH 0.16.0→0.16.1; manifesto regenerado.
  • Constituição 0.15.3 — /xadm-refine-spec com passada de contexto profunda. O refine da casa confirmava que os artefatos existiam, não que os fatos afirmados sobre eles eram verdadeiros — então não pegou o que o refine nativo do ACT pegou (ex.: app.json real sem slug, app_id divergente, conflito de audiência do authui). Correções no template e na cópia instalada: §1 vira passada profunda (ler o conteúdo de cada referência; conferir cada fato contra o artefato real; mapear tabelas/endpoints/identidade/namespaces; rastrear uma instância real ponta a ponta; e, se paralelizada, fazer isso UMA vez antes das dimensões); dimensão 2 cobra "fato conferido LENDO o artefato"; e uma lente transversal do implementador ("se eu fosse construir isto agora, onde travaria?"). Bump PATCH 0.15.2→0.15.3 (artefato rastreado mudou); manifesto regenerado.
  • Doc de infra — endpoint de env/secret da API do Coolify. O coolify.md passa a documentar o PATCH /api/v1/applications/{uuid}/envs/bulk (upsert em lote, Bearer; "Secret" = env marcada como Secret, não um tipo à parte; flags Build/Runtime) que o guard de deploy da Central de Apps usa para injetar os secrets de build — tira a Fase 4 do broker no central-backend do "spike do zero" (fato verificado na doc oficial do Coolify).

0.18.3 - 2026-06-30

Corrigido

  • Constituição 0.15.2 — desambiguação versão-do-site × versão-da-constituição. Um app em bootstrap (sem /xadm-docs instalada, só com o link da constituição) ficou em dúvida entre 0.15.1 (constituição) e 0.18.2 (site): o número do site aparece nu e gritante (VERSION, /versao.txt, tag vX.Y.Z, CHANGELOG, e mais ainda num clone local do repo central), enquanto o da constituição é discreto. Correções: a §6 da constituição passa a explicitar "duas versões, não confundir" (com a armadilha do clone local e do bootstrap); nova seção Duas versões: site x constituição em engenharia/versionamento.md; guard na skill /xadm-docs (a vigente vem só de constituicao-versao.txt, nunca de VERSION/tag); nota roteadora no CLAUDE.md do central; e correção do path stale docs/padroes/constituicao.md na skill /xadm-release.
  • /versao.txt auto-rotulado (site X.Y.Z). O Dockerfile passa a gravar o número do site rotulado em vez de nu, para não ser confundido com a versão da constituição.
  • Nota da distinção também no checklist de app novo (app-novo.md §3): o que o app rastreia é só constituicao-versao.txt; VERSION//versao.txt/tag são do site.
  • templates/mkdocs.yml — ordem dos plugins corrigida. O print-site estava antes do swagger-ui-tag, contrariando o próprio comentário ("DEVE ser o último") e quebrando o mkdocs build --strict (achado na adoção real). Reordenado para print-site por último.
  • Bootstrap sub-instalava as skills. O checklist de app novo (app-novo.md) listava só xadm-docs e xadm-release, e o manifesto.json não distinguia skill core de opcional — quem fazia bootstrap pela leitura instalava 2 de 8 (caso real). Correções: o manifesto.json ganha o campo opcionais (hoje só xadm-docx; gera-manifesto.py emite e o --check guarda); o app-novo.md passa a mandar instalar todas as *-skill.md do manifesto exceto as opcionais, listando as 8 core e apontando o manifesto como a lista mecânica completa; a /xadm-docs pula as opcionais na conferência de defasagem (opcional não-instalado não é defasagem).

0.18.2 - 2026-06-26

Adicionado

  • Navegação Anterior/Próximo no rodapé das páginas (navigation.footer). Habilitada a feature nativa do MkDocs Material no site central e no templates/mkdocs.yml dos apps: cada página ganha links "Anterior" (esquerda) / "Próximo" (direita) derivados da ordem do nav:, servindo o modo "ler como livro" (§1.7) — mais útil ainda no manual e no livro do projeto dos apps. Bump PATCH 0.15.0→0.15.1 (artefato rastreado mudou); apps re-baixam o template via /xadm-docs na migração oportunista.

Alterado

  • Constituição 0.15.0 — screenshot do manual entra na definição de pronto (§6). A metade (2) "doc do estado atual" agora lista o screenshot do manual entre os "estados atuais que mentem": mudou UI retratada numa imagem (botão novo, rótulo, layout) → regenerar o PNG afetado é parte do pronto, e a verificação inclui abrir a imagem e confirmar o elemento novo, não só rodar o gerador (gerador que roda sem semear o estado certo "passa" mostrando a tela velha — a mesma falha da metade (1)). O runbook Screenshots do manual ganhou a seção "Revisar o diff antes de commitar" com a armadilha do diff de só-ruído (marca d'água/elemento aleatório → reverter os PNGs de só-ruído, manter o que teve mudança visual real). Loop §6 de um app de BI Flutter (vantroba-bi v1.5.0: botão de export novo, texto atualizado, .png ficou na tela velha — pego só depois).

Adicionado

  • Engenharia/Flutter: padrão de export XLSX sem lib licenciada. Registrado na página de Flutter: usar o excel (justkawal, MIT) + pós-processar o OOXML com archive para freeze pane/autoFilter/outline (que o excel 4.0.6 não faz — verificado no fonte), em vez de trocar por Syncfusion (licenciado); com as armadilhas (Archive imutável, download Web manual, edição idempotente + smoke real) e a nota de hierarquia × sort. Imagem/logo no XLSX ficou fora de escopo (sem receita de injeção). Loop §6 de um app de BI Flutter.

Corrigido

  • Menu de abas: o vão Teoria/Prática sumia na própria página Aplicações. A regra que empurra o grupo da direita (Aplicações · API Reference · Mudanças) mirava pelo href ([href*="aplicacoes"]), mas o Material reescreve o link da aba ativa para ./ — que não contém "aplicacoes" — então, ao abrir Aplicações, o margin-left:auto não casava e todas as abas agrupavam à esquerda. Passou a mirar por posição (nth-last-child(3) = início do grupo da direita), imune ao estado ativo. Regra é exclusiva do xadm.css do portal central; apps não usam.

  • Release: pré-flight pedia autorização (constituição 0.14.34): o .claude/settings.json central não tinha git status/git log no allow-list (o template da skill já os prescrevia — estavam dessincronizados), e o pré-flight foi rodado num bloco multi-linha com git --no-pager … (duas coisas que o matcher recusa: comando composto não casa com um padrão, e a flag antes do subcomando quebra o git status *). Allow-list ganhou git status/git log/git diff; a skill (template + cópia) deixa explícito: um comando por chamada Bash (sem bloco multi-linha) e git puro sem --no-pager (4º veneno).

0.18.1 - 2026-06-26

Adicionado

  • slug no app.json — fonte única do identificador do app (constituição 0.14.32): o slug (pasta no bucket docs-sites/, path do site_url, nome do .docx) vivia duplicado em mkdocs.yml, docs.yml, CLAUDE.md e na skill de release, mas não no app.json, seu lar natural. Agora o template do app.json tem slug (kebab-case, "para sempre"); o docs.yml deriva dele o target do rclone e o site_url (em vez de hardcodar <app>); o export_docx usa-o no nome do .docx (fallback p/ o diretório-raiz); e o valida-frontmatter confere que o site_url do mkdocs.yml bate com o slug (igual ao cross-check de cliente:). Opcional p/ app legado (migração oportunista).
  • Gate de CI da lista de download da /xadm-docx (scripts/checa-export-download.py): confere que a lista hardcoded de arquivos baixados (skill + página) bate com o pipeline publicado (scripts/export-docx/ + reference/logo). Fecha o loop §6 do admonitions.py que defasou a lista.

Alterado

  • Export .docx: nome de entrega auto-descritivo (constituição 0.14.31): sem 2º argumento, o export_docx.py gera <slug>-<nível>[-v<versão>].docx na raiz do repo (antes: projeto.docx/pre-projeto.docx genérico, que colidia entre apps/releases na entrega). slug = nome do diretório-raiz; nível = pre-projeto|projeto (pelo caminho); versão = git describe --tags → VERSION → omitida; fallback genérico se não derivar. A skill /xadm-docx e a página passam a usar o default; o .gitignore do app (e o gitignore-flutter) ganha /*.docx (root-anchored — não pega um pré-projeto .docx commitado, §1.3). Correção junto: o download fresco da skill não baixava o admonitions.py (passo do pipeline desde 0.14.26) — incluído na lista.

  • Site central: ordem das abas, home e layout da Aplicações. Abas do topo reordenadas (Mudanças foi para o fim: Início · Documentação · Engenharia · Transversais · Aplicações · API Reference · Mudanças); a home ganhou um item explicando Transversais (IA, infraestrutura, glossário) com link; e as páginas Aplicações e API Reference (conteúdo montado via JS, sem headings no markdown → TOC vazio) receberam hide: toc, colapsando a coluna direita vazia que deixava o layout "agrupado". Só apresentação do site central; nada de artefato rastreado.

Corrigido

  • App publicado só em dev/ aparecia em "Outros (sem app.json)" (constituição 0.14.33): no layout versionado (decisão 0004), o central lê a app.json da raiz do bucket (docs-sites/<slug>/app.json), mas o docs.yml só a sincronizava em <slug>/dev/ — e o passo que mexe na raiz é release-only (publica-versao.py nem toca na app.json). Logo, app que só deu push em master nunca categorizava (caía em "Outros"). O docs.yml agora faz rclone copyto site/app.json …/<slug>/app.json (aditivo) em todo push. Caso real: thoms-integracao-produto-ecom.

  • Release sem prompt — inspeção de arquivo pela ferramenta Read, não por shell (constituição 0.14.30): a skill /xadm-release mandava ler o CHANGELOG.md mas não dizia como, e uma inspeção com pipe (sed … | grep, rtk grep … | head) disparou o único prompt de um release (pipe é recusado por princípio, fora do allow-list). A skill (template + cópia deste repo) agora manda inspecionar VERSION/CHANGELOG/diff pela ferramenta Read (ou git diff), nunca por sed/grep/cat/rtk grep/| head — 3º veneno documentado. O Read não precisa de allow-list nem confirma.

0.18.0 - 2026-06-26

Adicionado

  • Gate de dogfood das skills no CI (scripts/checa-dogfood.py): somos a vitrine, então as skills instaladas (.claude/skills/<nome>/SKILL.md) têm de bater com o template canônico (templates/<nome>-skill.md). O CI falha em drift — descoberto via loop §6: o próprio repo rodava /xadm-spec//xadm-refine-spec//xadm-plan defasados (sem NNN/segredo/lib-capability) porque as cópias nunca eram re-derivadas. As 3 cópias defasadas foram sincronizadas. xadm-release é adaptada por repo e fica de fora do gate.

Alterado

  • Spec/refine: verificar capacidade de lib de terceiro na FONTE antes de especificar (constituição 0.14.27): a /xadm-spec (§1) passa a mandar confirmar, na fonte/changelog da versão em uso, que um recurso de lib/SDK externo do qual a feature depende existe — com o pesquisador de docs da stack (ex. act-flutter-docs-researcher) — antes de especificar; especificar API inexistente é inventar capacidade (§1.2). A dim. 2 (Premissas) da /xadm-refine-spec reforça: recurso de lib assumido sem conferir no fonte = achado crítico. Origem: loop §6 do BI Transporte (feature de export xlsx — o researcher confirmou no fonte da lib 4.0.6 que não havia API de imagem/outline, evitando retrabalho).

  • Export .docx: fim do auto-anexo de .md irmãos (constituição 0.14.28): o export_docx.py deixava de fora o que era embutido, mas anexava ao fim todo .md irmão não-embutido — slurpando páginas que o livro só linka/resume (ex. etapas de projeto/), gerando conteúdo duplicado e um anexo solto após as Decisões. Agora o .docx é o index + só o que ele embute via --8<-- (igual ao site: link não entra, o link já é a relação). Pré-projeto passa a embutir seus anexos no fim do index.md (cada # ainda ganha quebra de página). Avisado na página Exportar para .docx.

  • Export .docx: links para fora do Word viram texto (constituição 0.14.29): o livro linka legitimamente para ADRs (docs/decisoes/*.md) e outras páginas de nav — funcionam no site, mas no .docx (só o livro) virariam hyperlinks mortos (apontam para um .md inexistente). Um filtro lua (frontmatter.lua) remove o href de links relativos (alvo fora do .docx), mantendo o texto; URL absoluta e âncora interna seguem clicáveis, e imagens (Mermaid) não são tocadas. Guidance na página: cross-ref de conteúdo só-no-site se cita por nome/número, não por link. Docx-only — no site os links funcionam.

0.17.1 - 2026-06-26

Adicionado

  • Export .docx: admonitions viram callout (constituição 0.14.26): !!! tipo "Título" / ??? tipo (que o pandoc não entende e despejava literal no Word) agora passam por um pré-processador (admonitions.py, antes do pandoc) que os converte em blockquote com título em negrito → estilo Block Text (callout) do xadm-reference.docx, com o tipo como rótulo pt-BR (Nota/Aviso/Dica/…). O autor mantém o !!! na fonte (box nativo no site); só o .docx é adaptado. Docx-only — o site já renderiza admonitions.

  • Export .docx: legenda por diagrama + estilo de código + Título 4–6 (constituição 0.14.25): (a) render_mermaid lê %% caption: <texto> no bloco e emite a legenda na figura do .docx; (b) o xadm-reference.docx e o make_reference.py ganharam SourceCode/VerbatimChar (monoespaçada, fundo e borda leves) — antes ```json/código inline saíam na fonte do corpo — e Título 4–6 na identidade X-Adm (Verdana azul, decrescente), para o conteúdo que chega a H4+. (c) Documentada a convenção de que fragmento --8<-- se autora no nível do embed (modelagem.md em ###, não #) — nada rebaixa heading, no site nem no .docx (constituição §5). Só docx/reference; o site já estilizava código e todos os níveis via Material+CSS.

Corrigido

  • Export .docx: diagnóstico de Mermaid quando falta o Chrome (constituição 0.14.24): com mmdc instalado mas sem Chrome (de sistema ou do puppeteer — caso típico de CI/runner limpo), o erro dizia "mmdc ausente — instale o mmdc", mandando pro lado errado. Agora distingue "mmdc ausente" de "mmdc presente, Chrome falhou" e aponta as saídas offline (instalar chromium · PUPPETEER_EXECUTABLE_PATH · npx puppeteer browsers install chrome-headless-shell), sem empurrar pro .ink. Nota de troubleshooting na página Exportar para .docx. (A detecção automática de Chrome do sistema + --no-sandbox já existia na v0.17.0.)

0.17.0 - 2026-06-26

Adicionado

  • Pipeline de exportação para .docx no padrão X-Adm (constituição 0.14.23): centraliza na toolchain o pipeline Markdown→Word (pandoc + reference-doc + Mermaid→PNG), derivado e saneado da PoC poc-pied. Python-only cross-platform (Linux/Windows/macOS) + o filtro nativo do pandoc (frontmatter.lua); reference client-neutral, Mermaid offline por default (mmdc; .ink só opt-in com aviso de confidencialidade, §1.9), pré-processador de --8<-- (o pandoc não expande snippets). Distribuído como os validadores: scripts em scripts/export-docx/ rastreados no manifesto.json e publicados em /toolchain/raw/scripts/export-docx/; o reference+logo publicados crus (binários, fora do manifesto). Inclui a skill /xadm-docx (baixa o pipeline fresco e gera o Word, sem commitar), a página Exportar para .docx e o §1.3 acionável — distinguindo asset de tooling (versionado: css, reference.docx, logo) de documento gerado (não commitado: o .docx, o site).

  • Segredo nunca entra no Git — nem no andaime .ia/ (constituição 0.14.22, §1.9): o .ia/ é versionado e o -prompt.md (cola crua do pedido) é onde uma credencial real vaza por descuido — aconteceu. Regra nova: nenhum arquivo do repo (.ia/, docs/, config) carrega segredo real; usar placeholder (<TOKEN>, ${VAR}) + ponteiro pro valor real (secret da org no Forgejo, env, cofre); se vazou pro histórico, rotacionar (apagar o arquivo não basta — o Git guarda; mesma resposta dada ao vazamento das keys DOCS_S3). .ia/ segue versionado (a trilha do andaime é o objetivo — não se gitignora). Eco no fluxo de IA e nas skills /xadm-spec e /xadm-plan.

Corrigido

  • .ia/NNN-* — NNN é o id da linhagem, não sequência global de arquivos (constituição 0.14.21): as skills /xadm-spec e /xadm-plan nomeavam .ia/NNN-titulo-spec.md / NNN-titulo-plan.md sem dizer o significado de NNN, e o agente tratava como sequência global de arquivos, ainda inventando um slug novo por estágio (ex. errado: 001-definicao-prompt → 002-integracao-...-spec), quebrando a rastreabilidade. Os dois templates passam a especificar que NNN identifica a linhagem (a feature): prompt, spec e plan da mesma linha compartilham NNN e slug, mudando só o sufixo de estágio; feature nova recebe o próximo NNN. Mesma definição reforçada no fluxo de IA (dono conceitual da regra).

  • Bootstrap de app novo aponta para o mirror público, não o Forgejo privado: o checklist app novo e publicar docs mandavam copiar os templates de fonte.xadm.biz/.../src/branch/master/templates/… — o Forgejo privado, que dá 404 sem auth (bootstrap de app novo, inclusive por agente de IA, falhava ou improvisava de um clone local defasado). Agora o bootstrap é manifesto-driven como a /xadm-docs: baixa do mirror público docs.xadm.biz/toolchain/raw/…, ancorado no manifesto.json como fonte única, com nota de que fonte.xadm.biz é edição/hosting (privado) e consumo é sempre via mirror. Mesmo swap no link irmão de versionamento.

  • gitignore-flutter publicado no mirror: o templates/gitignore-flutter não estava no manifesto, então toolchain/raw/templates/gitignore-flutter dava 404 — o link da engenharia Flutter já apontava pra lá (404 latente) e o de stack apontava pro Forgejo privado. O Dockerfile passa a publicá-lo cru no raw/templates/ (artefato copy-once de bootstrap, fora do manifesto — cada app customiza o .gitignore, não há o que re-sincronizar), e stack.md aponta pro mirror.

0.16.10 - 2026-06-25

Corrigido

  • Release sem prompt — pré-flight: o pré-flight ainda pedia autorização porque (a) git describe/git rev-parse não estavam no allow-list e (b) usava substituição de comando $(git describe …), que o matcher de permissão recusa por princípio. Adicionadas as regras de leitura (git status/rev-parse/describe/log/echo) ao allow-list e ao template da skill, e documentado o veneno do $(...) (pegar o valor num passo, usar literal no próximo). Constituição v0.14.20.

0.16.9 - 2026-06-25

Adicionado

  • Release sem prompt (opt-in): seção "Permissões" no template da skill /xadm-release documentando o allow-list do .claude/settings.json (git add/commit -m/tag/push origin) para o release não pedir autorização — com o trade-off explícito (remove a rede de segurança; a garantia vira a REGRA Nº 2) e a dica de rodar cada git separado (compound com pipe não casa o allow-list). Aplicado neste repo (.claude/settings.json + skill local). Constituição v0.14.19.

0.16.8 - 2026-06-25

Alterado

  • CI Flutter mais rápido (auditoria dos 3 apps + central): a imagem ci-flutter agora traz clang/lld/cmake/ninja/pkg-config baked (native assets do PowerSync/sqlite3) — o app não instala mais toolchain nativa por run (~2m20 a menos no ci.yml). Os templates ci.yml/docs.yml ganharam o passo Cache do pub (~/.pub-cache, key por pubspec.lock) e a nota de que a toolchain vem na imagem. Constituição v0.14.17. Migração: app Flutter re-deriva o ci.yml (dropar o passo "Toolchain nativo") via /xadm-docs.
  • CI de docs mais rápido: o docs.yml ganhou cache de pip + npm (~/.cache/pip + ~/.npm) — mkdocs e mermaid passam a instalar do cache (sem re-download por run, ~20-35s/run). Avaliado e descartado assar numa imagem ci-docs: o lint-mermaid.mjs é ESM e não resolve módulo global (nem via NODE_PATH), e imagem nova só pelo mkdocs (~15s) seria over-engineering. Constituição v0.14.18.

0.16.7 - 2026-06-25

Alterado

  • Fábrica de CI (decisão 0003): a fonte das versões passou a ser a ci-images/matriz-baseline.json — a leitura agregada dos app.json do bucket foi removida. Ela criava um deadlock de bootstrap (o app.json chega ao bucket via docs.yml, que roda na imagem que a fábrica deveria ter buildado), travando o 1º app Flutter version-pinned (bi-transporte, flutter: 3.44.0). Adoção de versão nova = PR na baseline + push (a fábrica builda) antes de o app apontar o container:. Baseline já com flutter: 3.44.0; ci-images.yml sem o passo do bucket/DOCS_S3. Decisão 0003 ganhou nota datada; app-novo, engenharia/flutter e o runbook documentam a ordem. §6 do bi-transporte.

0.16.6 - 2026-06-24

Corrigido

  • Fábrica de CI (ci-images.yml): o docker build do job falhava com "Cannot connect to the Docker daemon" — o runner da org usa DinD por TCP (sem socket unix no job). O passo de build agora deriva DOCKER_HOST do gateway default do job (= daemon do DinD). Runbook fabrica-imagens-ci.md corrigido: o socket-do-host do passo 1 só vale para runner sem DinD.

0.16.5 - 2026-06-24

Alterado

  • templates/docs.yml: passo Cache do Gradle no bloco de javadoc (paridade com o ci.yml; o comentário já prometia mas não entregava) — todo app Java que gera Javadoc no CI de docs ganha o cache ao descomentar. Referência ao JDK deixou de citar versão fixa (era "JDK 21") e aponta a imagem ci-java:<v>. Constituição v0.14.16. (§6 de app Java.)

Corrigido

  • Imagem ci-java:<v> da fábrica de CI estava sem rclone — o docs.yml de app Java falhava no "Publicar no Garage" (rclone: command not found), apesar de o comentário do template anunciar "toolchain pré-instalada". Adicionado ao ci-images/Dockerfile.java (a ci-base e a ci-flutter já tinham). Requer rebuild+push da ci-java. (§6 de app Java.)

Revertido

  • O fallback de /commit.txt via .git/HEAD (introduzido na v0.16.4) quebrava o build no Coolify: COPY .git/HEAD falha com "/.git/HEAD": not found porque o Coolify não inclui o .git no contexto de build — o deploy não subia. /commit.txt volta a dev (cosmético conhecido); a receita em coolify.md virou aviso de "não leia o .git/HEAD no build — use Build Argument". O fix do export TAG no release.yml (v0.16.4) permanece — é independente.

0.16.4 - 2026-06-24

Corrigido

  • templates/release.yml (bloco opt-in de Release no Forgejo): a tag saía como var de shell sem export, então o python3 -c filho não a enxergava (KeyError: 'TAG' → corpo do POST vazio → 422). Agora export TAG=…. Também documentado que a escrita no repo (permissions: contents: write ou RELEASE_TOKEN) é obrigatória — sem ela dá 403 antes do 422. Constituição v0.14.15. (§6 do maxsul-pied-simples, 1º app a habilitar o bloco.)
  • /commit.txt carimbava dev: o Coolify não passa SOURCE_COMMIT como build-arg. O Dockerfile agora tem fallback que lê o commit do .git/HEAD (checkout destacado do deploy = SHA); .dockerignore deixa só o HEAD entrar no contexto.

Adicionado

  • Receita em infraestrutura/coolify.md para carimbar o commit do deploy via .git/HEAD quando o Coolify não passa SOURCE_COMMIT (opcional, para app que embute o commit; não é exigência da constituição).

0.16.3 - 2026-06-24

Corrigido

  • overrides-main.html e changelog.md agora são rastreados (publicados raw + manifesto) — os dois scaffolds da doc versionada (overrides/main.html do banner; docs/changelog.md da página "Mudanças") são estáticos-idênticos entre apps (como o checkstyle.xml), mas estavam fora do manifesto.json e do raw/ do site → davam 404 e a /xadm-docs não detectava defasagem nem conseguia re-baixá-los (só com checkout local do central). Adicionados ao RASTREADOS (auto-publicados em toolchain/raw/templates/ pelo Dockerfile) + destino documentado na skill /xadm-docs. Feedback de uso real via loop §6 (maxsul-pied-simples).

Alterado

  • release.yml: rótulo do artefato Flutter mais preciso — o comentário dizia "Flutter → bundle (apk/web)", misturando Flutter Web (que é deploy via Coolify, não artefato) com mobile/desktop (artefato distribuível → Release). Esclarecido: Web segue o caminho deploy (sem Release/asset); mobile (apk/IPA) e desktop (win/linux/mac) geram Release com asset. O como buildar cada alvo fica a definir quando surgir o 1º app (não normatizar no escuro, §1.2). Feedback de uso real via loop §6.
  • release.yml: token da Release configurável + permissions — o GITHUB_TOKEN automático do Actions costuma ser só Actions (não cria Release → 403). O bloco opt-in agora orienta declarar permissions: contents: write no job para elevar o auto-token e, se o instance restringir mesmo assim, usar um token dedicado da org RELEASE_TOKEN (escopo write:repository) — o curl usa RELEASE_TOKEN se existir, senão o GITHUB_TOKEN. Setup documentado em infraestrutura/forgejo. Feedback de uso real via loop §6 (maxsul-pied-simples).

0.16.2 - 2026-06-24

Alterado

  • Dogfood: o central usa as próprias skills de workflow — instaladas xadm-spec/refine-spec/plan/work/compound/meta-audit-work em .claude/skills/ (antes só a xadm-release); o CLAUDE.md recomenda o fluxo /xadm-* para feature não-trivial aqui. E o CI do próprio central (build-site.yml) passou a usar a imagem ci-base (antes montava git+python via apt a cada run). Mudanças internas — não alteram o site publicado.

0.16.1 - 2026-06-24

Corrigido

  • Build do site no Docker (deploy da v0.16.0) — o mkdocs build --strict do Dockerfile falhava porque o COPY seletivo do build stage não trazia da raiz o overrides/ (do theme.custom_dir — banner de versão antiga) nem o CHANGELOG.md (embutido pela página "Mudanças" via snippet). Adicionados os dois COPY. Build local passava; o contexto enxuto do Docker não.

0.16.0 - 2026-06-24

Adicionado

  • Release do Forgejo com artefato anexado (engenharia/versionamento) — no Forgejo tag ≠ Release: a tag sozinha deixava a aba Releases vazia e o jar sem proveniência. O templates/release.yml ganhou um bloco opt-in, stack-aware que, para app que distribui artefato (jar/CLI/worker), builda o artefato no CI e publica a Release (tag + notas da seção do CHANGELOG + asset anexado), via API do Forgejo por curl + secrets.GITHUB_TOKEN. O marco do que foi liberado passa a ser a Release (download reproduzível), não só a tag; o operador baixa o jar da aba Releases. Requer a imagem ci-java:<v> (a ci-base não tem JDK) e token com escopo de escrita em release. Skill /xadm-release e o modelo de distribuição atualizados. Feedback de uso real via loop §6 (maxsul-pied-simples).
  • Documentação versionada por release (decisão 0004 / spec 011) — cada tag vX.Y.Z publica um snapshot imutável da doc em docs-sites/<app>/<X.Y.Z>/, e um seletor de versão nativo do Material (extra.version.provider: mike) no topo lista as releases; a raiz abre na maior SemVer; master vira o preview dev/ (fora do dropdown). Implementado: templates/mkdocs.yml (provider) + templates/docs.yml (gatilho tags, site_url por-versão, publish na subpasta — nunca rclone sync da raiz) + novo scripts/publica-versao.py (monta versions.json+redirect na raiz via rcat, publicado no site) + sync-apps.sh (detecta api/ na versão default). Invariantes duros: app.json fica na raiz (senão quebra a fábrica de imagens); forward-only (sem backfill). Spike confirmou na fonte do Material que o site_url precisa terminar na versão. Adoção por app é pós-release (o docs.yml baixa o publica-versao.py do site). Dogfood do central e poda de storage: diferidos (spec 012 + brief).

  • Página "Mudanças" (CHANGELOG) no site de cada app — o CHANGELOG.md da raiz do repo agora aparece no site, em entrada de nav própria no topo, embutido via pymdownx.snippets (--8<-- "CHANGELOG.md", fonte única §1.8/§1.9 — sem cópia, sem editar em dois lugares). templates/mkdocs.yml ganhou a entrada; novo scaffold templates/changelog.md. Aplicado no central (dogfood) e nos pilotos. Casa com a doc versionada (spec 011): cada snapshot de versão mostra o changelog daquela release.

Alterado

  • release.yml passa a usar a imagem de CI ci-base (decisão 0003) — faltou na migração inicial (só ci.yml/docs.yml tinham ido): rodava em node:22-bookworm-slim e instalava git+python via apt a cada release (~22s). Agora usa ci-base (já traz git+python+curl) — só roda o valida-release.py. −~22s por release.

0.15.0 - 2026-06-22

Adicionado

  • Fábrica de imagens de CI por stack+versão + cache do Gradle (decisão 0003) — o runner Forgejo da org é efêmero e os templates ci.yml/docs.yml montavam o ambiente do zero a cada run (apt/pip, download de JDK e git clone do Flutter SDK — o maior custo). Agora: imagens por stack+versão servidas no registry do Forgejo (ci-base, ci-java:<v>, ci-flutter:<v> com o SDK baked), montadas por uma fábrica central (ci-images/ + .forgejo/workflows/ci-images.yml) que lê o campo novo toolchain dos app.json agregados do bucket (sem token de Git entre repos) + uma baseline, e builda/empurra a matriz. Os templates apontam o container: da stack; actions/cache@v4 cacheia o Gradle. Java 25 fixado como LTS da casa (stack). Pré-requisitos de infra (do operador): runner buildando imagem + push pelo endereço interno do registry (dribla o Traefik/499) + token de pacote da org — runbook em infraestrutura/fabrica-imagens-ci.md (removido depois — ver 0027); o workflow já vem parametrizado (CI_REGISTRY_HOST/REGISTRY_TOKEN). Consolida o feedback de CI/deploy do piloto Flutter (loop §6): subosito quebrado, drift da versão do SDK (fonte única no .fvmrc, sem FLUTTER_VERSION no Coolify), asset gerado antes do test, config pública hardcode vs build-arg.
  • toolchain obrigatório no app.json para app Java/Flutter (decisão 0003) — o validador agora falha se um app de stack Java/Micronaut não declarar toolchain.java (ou Flutter/Dart sem toolchain.flutter): sem a declaração a fábrica não builda a imagem-versão e o container: do CI aponta para imagem inexistente, quebrando com erro de pull obscuro. Falha cedo e clara em vez de tarde e confusa. App docs-only (ci-base, sem versão) não declara. Documentado no contrato do app.json (publicar-docs), engenharia e checklist app-novo.
  • Nota fixa "Build Argument × Environment Variable" no Coolify (infraestrutura/coolify + eco em engenharia/flutter) — pega-pé recorrente (mordeu 3× numa sessão: FLUTTER_VERSION, SENTRY_DSN): valor que o build embute na imagem (--dart-define, ARG) precisa ser Build Argument (+ "Available at Buildtime"), não env de runtime — como runtime chega vazio ao bundle e o app sobe sem ele, sem erro. Mecânica bidirecional canônica em coolify.md (o sentido inverso — segredo de runtime como build arg vaza como ARG — é o caso dos DOCS_S3_*); eco específico de --dart-define no flutter.md. Feedback de uso real via loop §6.

0.14.1 - 2026-06-22

Adicionado

  • Conformidade de engenharia no loop de sincronização (constituição §6 + /xadm-docs) — as regras de engenharia (engenharia/*) eram lidas ao vivo pelas skills mas ficavam invisíveis ao /xadm-docs, que só olhava o manifesto de templates; um app não detectava defasagem de regra de engenharia (e o raw das páginas dava 404). Agora o site publica as páginas de engenharia também cruas (.../toolchain/raw/engenharia/<stack>.md) e o /xadm-docs ganhou o passo 3b: ao detectar bump, re-audita o código da stack do app contra a regra vigente (exit codes, libs, observabilidade, gate, definição de pronto) e propõe correções com aprovação — re-auditar, não re-baixar. As páginas não entram no manifesto por desenho (regra viva lida ao vivo ≠ artefato copiado que envelhece). Feedback de uso real via loop §6.
  • Exit code 4 ocupado/lock na taxonomia de CLI/worker (engenharia/java) — a lista 0 ok · 1 parcial · 2 config/auth · 3 externo não cobria "execução concorrente serializada por lock; nada feito; seguro repetir", desfecho recorrente em job reentrante (cron + gatilho sob demanda dividindo estado). Fixado também que a lista é o contrato canônico da casa (app não inventa código; desfecho novo entra por §6) e que o app documenta a saída no runbook de operacao/. Feedback de uso real via loop §6 (auditoria maxsul-pied-simples, que já shippou exit 4 na sua ADR 0003).

Corrigido

  • Frontmatter YAML nos 6 templates de skill de workflow (xadm-spec/refine-spec/plan/work/compound/meta-audit-work) — faltava o bloco --- name / description --- que o Claude Code usa para descobrir e rotear a skill (as xadm-docs/xadm-release já tinham). Sem ele, ao copiar verbatim para o app a skill aparecia com a description virando o comentário-cabeçalho e a invocação quebrava. Feedback de uso real via loop §6. description no formato "o que faz + Use quando…".

0.14.0 - 2026-06-22

Adicionado

  • Conjunto de skills de workflow X-Adm (constituição v0.13.0; decisão do Gustavo, 010c §13): xadm-spec → xadm-refine-spec → xadm-plan → xadm-work → xadm-compound, modelado no ACT mas próprio da casa. Stack-aware — detectam a stack e seguem a base de conhecimento da Engenharia daquela stack. xadm-work não commita (REGRA Nº 2); xadm-compound destila para docs/ e refina a base de conhecimento (loop §6). Templates rastreados no manifesto (o /xadm-docs + o hook de sessão acusam defasagem). Catálogo na IA e Claude Code.
  • Skills de workflow reescritas a fundo + xadm-meta-audit-work (constituição v0.14.0; Gustavo comprou o ACT PRO vitalício e quis o workflow da casa "o mais parecido possível, inclusive os insights"): as 5 skills ganharam a profundidade do ACT adaptada à casa — contrato duro + invariantes que falham o workflow (xadm-work), map_flows/preview_outline/complexidade adaptativa (xadm-spec), regras não-negociáveis + formato de achados + review gate (xadm-refine-spec), pesquisa + style contract + checklist de qualidade (xadm-plan), extração paralela + quality bar (xadm-compound); preservados os inegociáveis X-Adm (não-commitar, destilar p/ docs/, gate por stack, pt-BR, definição de pronto §6). Nova /xadm-meta-audit-work audita um run da /xadm-work (confere não-commit, reconciliação do plano, gate da stack e a linha §6).
  • Princípio de engenharia "Simplicidade primeiro (sem over-engineering)" na Engenharia — busque o caminho mais simples que satisfaz a necessidade real e presente; abstração/camada/generalização só com necessidade concreta (YAGNI). As skills de workflow ecoam o princípio onde a IA decide escopo/desenho/implementação (xadm-spec, xadm-plan, xadm-work) e a xadm-refine-spec ganhou a 7ª dimensão de checagem (pega over-engineering já na spec). Irmão de engenharia do "enxuto por padrão" da constituição §1.5 (que vale para a doc).

0.13.0 - 2026-06-19

Adicionado

  • Área Engenharia construída (docs/engenharia/) — o lado "como construir" do padrão: Stack da casa (matriz app→stack + defaults opinativos), páginas por stack (Java/Micronaut servidor com guarda de fronteiras, Java app simples/CLI/worker, Flutter cliente), CI, gates e testes, e versionamento + health check. (Phases 1–5 do plano de Engenharia.)
  • checkstyle.xml + suppressions.xml centralizados (templates/, rastreados no manifesto, publicados em /toolchain/raw/) — fonte única de estilo Java; para de copiar app-a-app.
  • Seção "Critérios de aceite e teste" no template de etapa (doc-projeto.md) + marcador "regras arquiteturais guardadas por teste/lint" no livro — a ponte para quem valida.

Alterado

  • Regras de engenharia movidas da constituição para a área Engenharia (CI por stack, SemVer/release, health check, branch master, níveis de teste, deploy) com ponteiro curto — a constituição fica menor; o piso da definição de pronto permanece (§6).
  • Home (index.md) reescrita — declara a constituição de arquitetura, engenharia e documentação, com os dois caminhos (Documentação · Engenharia); "Tudo numa página" sai do menu de abas; separador no menu entre as áreas de teoria e prática; menções a ZIM/ERP sem adjetivo.

0.12.0 - 2026-06-19

Adicionado

  • Portal em duas áreas distintas: Documentação × Engenharia (constituição v0.12.0; decisão do Gustavo). O site reorganiza em abas (navigation.tabs): Documentação (constituição, templates, publicar, app-novo, decisões), Engenharia (NOVA) e Transversais (IA, infraestrutura, glossário). As pastas no source espelham: docs/documentacao/, docs/engenharia/, docs/transversais/. A constituição passa a se chamar "Constituição da Documentação e Engenharia"; o site, "Padrões da Plataforma X-Adm".
  • Área Engenharia + Stack da casa (docs/engenharia/): matriz opinativa app→stack (Micronaut servidor · +Thymeleaf view simples · Flutter cliente/web/mobile · Java puro app simples/CLI · ZIM legado) + defaults transversais (package-by-feature universal, OkHttp, Gradle KTS, JUnit5+WireMock, BigDecimal, Logback, PostgreSQL+Flyway, picocli). Default forte + exceção documentada.
  • .gitignore Flutter de referência (templates/gitignore-flutter; feedback via loop §6). Corrige dois bugs que o template legado da org propagava: pubspec.lock agora versionado num app (build reproduzível, sem *.lock); FVM usa .fvmrc versionado (sem o legado !.fvm/fvm_config.json). Referenciado na Stack da casa.

Alterado

  • Toolchain de máquina migrado para /toolchain/ (constituição v0.12.1). Os artefatos que os apps baixam no CI (validadores, hooks, constituicao-versao.txt, raw/, manifesto.json) saem de /padroes/ para /toolchain/ — namespace neutro às duas áreas. O Dockerfile dual-publica (/padroes/ segue vivo na transição → apps não quebram, re-derivam oportunisticamente). URLs de página antigas (/padroes/X) ganham redirect (mkdocs-redirects) para a área nova.
  • Campo entregue: true|false na etapa (constituição v0.11.1, §2; feedback via loop §6). Separa a entrega da feature do status do doc no Resumo Executivo — permite "design aprovado, build pendente" (status: aprovado + entregue: false). Opcional e retrocompatível: sem o campo, cai no heurístico legado (status aprovado → entregue). Toca visao-tecnica.py, valida-frontmatter.py, constituição §2 e o template de etapa.
  • Convenção .ia/ para andaime (constituição v0.11.1, §1.9; feedback via loop §6). Artefato de trabalho (spec, plano) vive em .ia/NNN-*.md, numerado por repo, fora de docs/ (não é conteúdo publicado).

0.11.1 - 2026-06-19

Adicionado

  • Template Guia do código (templates/guia-do-codigo.md) (constituição v0.11.0, §2/§4; feedback via loop §6 dos pilotos BI Transporte). Página Dev padrão de todo app com código: narrativa autorada da organização do código, com blocos por stack (Java package-by-feature / Flutter feature-first) e o marcador opcional de inventário.
  • xadm.css centralizado (templates/xadm.css) (constituição v0.11.0; feedback via loop §6). A identidade visual vira fonte única no central (antes duplicada por app) — inclui a regra .md-typeset a:not(.headerlink) (links de conteúdo com burgundy + sublinhado, antes pouco identificáveis). Apps copiam do central; mudança visual num lugar só.

Alterado

  • Nav do template por ciclo de vida + seções Pré-projeto/Projeto (constituição v0.11.0, §2; feedback via loop §6). O mkdocs.yml canônico passa a recomendar Resumo → Pré-projeto → Projeto → Operação → Dev → Público → Etapas → Decisões → Glossário, com Projeto = especificação e Etapas = execução explicitados (antes "Etapas" virava catch-all). O resumo-executivo.md ganha o bloco "O que você encontra aqui".
  • Hook visao-tecnica.py remove <!-- GERADO: inventario --> não preenchido (feedback via loop §6). O inventário de classes é preenchido por um passo Javadoc/dartdoc no docs.yml do app; sem o passo, o hook compartilhado remove a linha (nunca embarca o marcador cru) — contrato documentado na publicar-docs.md.
  • Definição de pronto: a verificação exercita a plataforma real (constituição v0.10.15, §6; feedback via loop §6). O piso de teste ganha que a verificação tem que exercitar a plataforma/runtime real (Web, mobile, o build do app), não só --strict/fixture — o risco recorrente em mudança de migração/toolchain é o gate "passar" sem tocar o sistema de verdade.
  • Branch principal padronizado em master (constituição v0.10.17, §5; feedback via loop §6). Os templates docs.yml/ci.yml vinham com branches: [main] e um comentário pedindo ajuste manual — o passo que falhava: num app em master, a CI nunca rodava em silêncio (só a release, por tag). Agora o template já vem branches: [master], sem ajuste por repo, e a convenção está na §5.
  • REGRA Nº 2: skills que commitam por padrão rodam em no-commit (constituição v0.10.16, REGRA Nº 2 / §6; feedback via loop §6). Skill de execução que commita sozinha (ex. ACT act-workflow-work, que commita por fase) deve ser invocada sempre com a flag de não-commitar (--do-not-commit) — delegar a execução não delega a decisão de commit, que continua do dev ou da /xadm-release.
  • /health: status aceita liveness truthy (versionamento.md; auditoria do piloto BI Transporte). O valor exato deixou de ser normativo — "ok", "UP", etc. valem; o /health nativo do Micronaut ("UP") está conforme, sem remapear. Contrato segue: 200 + status vivo + versao legível.
  • /health do Flutter web responde {status, versao}, não o version.json cru (versionamento.md; auditoria do piloto BI Transporte Flutter). A receita por stack contradizia a §5 (que já exige o shape inclusive no Flutter web): o nginx monta o {status, versao} lendo a versão do version.json; o /version.json segue como endpoint separado do auto-update.
  • Versão em runtime nunca é literal hardcoded + receita de Java CLI (versionamento.md; feedback via loop §6 do xadm-pied-cliente). Princípio explícito: a versão em runtime vem sempre do manifesto do release, nunca de um literal no código (literal mente e drifta, porque a /xadm-release só toca manifesto+CHANGELOG). Receita nova de Java CLI (picocli): --version via IVersionProvider lendo o version.properties, não @Command(version="...").
  • CI de docs falha cedo se faltar DOCS_S3_* (templates/docs.yml; feedback do piloto). Guarda no passo "Publicar no Garage": secret vazio/ausente aborta com mensagem clara, em vez do críptico no such host do rclone após ~3min de retry.

Corrigido

  • Banner de supersessão renderizava o número errado (scripts/valida-frontmatter.py, frontmatter-cabecalho.py, templates/decisao.md; auditoria do piloto). superado-por: 0016 sem aspas era lido como octal pelo YAML (0016 → 14), e a decisão obsoleta mostrava "superada pela 14" no site — e o validador checava a cadeia contra a decisão errada, em silêncio. Agora o validador falha se superado-por/supersede vier sem aspas, o template mostra "NNNN" e o hook renderiza 4 dígitos.

0.11.0 - 2026-06-18

Adicionado

  • projeto/mapeamento.md opcional (constituição §2/§5): fonte embutida do cap. 4 do livro (mapeamento entrada↔dados — "origem → destino + transformação"), fragmento sem frontmatter, isento no validador (como modelagem.md). Template novo.

Alterado

  • Release: versão Java também em build.gradle.kts + bootstrap de release mais visível (constituição v0.10.14; feedback via loop §6 — arquétipo Java CLI). O valida-release.py e a skill /xadm-release passam a ler a versão de gradle.properties ou do build script (build.gradle.kts/build.gradle, linha de topo version = "..."), não só de gradle.properties. O app-novo ganhou a instalação da skill /xadm-release (faltava — só listava a /xadm-docs) e a nota de que o CHANGELOG.md nasce na 1ª release (inferido do git log, cobre o histórico); versionamento.md idem. Fecha o gap do PoC que adotou docs mas pulou o wiring de release.

  • Modelo de dados generalizado: banco E/OU contrato de saída (constituição v0.10.13, §2/§5; feedback via loop §6 — arquétipo CLI sem banco). modelagem.md deixa de ser "só projeto com banco": vale também para o contrato dos artefatos de saída/integração (JSON, payload), e o cap. 3 do livro acomoda os dois. Corrige um over-fit ao piloto Java/Postgres.

Corrigido

  • Scripts publicados também em raw/scripts/ (sha do manifesto verificável) (feedback via loop §6). O manifesto.json traz o sha do arquivo cru dos scripts, mas o Dockerfile só publicava a forma carimbada (/padroes/<script>.py, versão na 1ª linha) — o sha nunca batia e raw/scripts/ dava 404. Agora o Dockerfile publica também a forma crua em /padroes/raw/scripts/<script> (casa com o manifesto), mantendo a carimbada para execução. Contrato documentado em publicar-docs.

0.10.0 - 2026-06-17

Adicionado

  • Definição de pronto unificada (mudou código → teste + doc no mesmo PR) (constituição v0.10.12, §6; feedback via loop §6). Fecha a assimetria: o §5 já pedia doc-no-mesmo-PR, mas faltava a metade simétrica de teste, e nenhuma era item de fechamento. O §6 passa a fixar o piso unificado — teste proporcional ao risco (mín.: nenhuma regressão sem teste de regressão) + doc do estado atual (mín.: nenhuma mudança de comportamento sem doc); pular metade é decisão explícita (REGRA Nº 3). É disciplina de fechamento + o gate do ci.yml, não um gate que detecta "doc seguiu código" sozinho. A doutrina detalhada de teste (níveis, e2e em fronteira de segurança, paridade em refactor) fica para a área de Engenharia (futura). Item de fechamento condicionado a "mudou código" em claude-regras.md/CLAUDE.md.

0.9.2 - 2026-06-17

Adicionado

  • Ghost mode nas mensagens de commit geradas pela IA (constituição v0.10.8, §6; feedback via loop §6). A mensagem que o agente escreve para o dev copiar sai sem Co-Authored-By, "Generated with" ou qualquer atribuição de IA — regra que sobrepõe o default do harness (que carimba co-autoria). Antes só a /xadm-release tinha ghost mode; agora vale para toda mensagem gerada. Em templates/claude-regras.md (REGRA Nº 2), §6 e no CLAUDE.md do central.
  • Oferta §6 no fechamento: obrigatória, acoplada ao commit e afirmativa (constituição v0.10.9, §6; feedback via loop §6). A regra existia em prosa mas dependia de o agente lembrar — e ele pulava. Agora a linha §6 (§6: nada a reportar ou §6: candidato a feedback — <o quê>) sai sempre junto da mensagem de commit, na mesma resposta; a ausência dela é sinal visível de passo pulado, não silêncio. Em templates/claude-regras.md (REGRA Nº 2), §6 e no CLAUDE.md do central.

  • Guia "Como compilar e testar localmente" obrigatório no nível Dev (constituição v0.10.10, §2; pedido do Gustavo). App que executa traz em docs/dev/ um guia de como rodar localmente (modo simulação): pré-requisitos (runtime/versão, Docker p/ banco), passos e recomendações de ambiente. O detalhe fino por stack é da área de Engenharia (futura); aqui fica o requisito-de-existir.

Alterado

  • §1.9 — destilar é obrigatório; apagar o andaime é recomendado, não obrigatório (constituição v0.10.11; refino pós-auditoria adversarial). Ao concluir uma spec/trabalho, destilar o conhecimento durável para docs/ é obrigatório (inclui o que ferramentas como /compound geram nos docs); apagar o artefato .ia/ deixa de ser obrigatório — pode ficar versionado como andaime/histórico, desde que o durável já esteja em docs/. Alinhado em templates/claude-regras.md.

Corrigido

  • <!-- HUMANO --> pendente falha o build em página status: aprovado (constituição v0.10.11, §2; auditoria adversarial). Antes só RECONCILIAR falhava — um livro (projeto/index.md) cheio de HUMANO buildava verde e publicava em branco (o comentário some no HTML). Agora o hook visao-tecnica.py falha simétrico ao RECONCILIAR; em rascunho/em-revisão é WIP, preservado.

0.9.1 - 2026-06-16

Corrigido

  • templates/mkdocs.yml (constituição v0.10.2): o comentário do hook visao-tecnica.py ainda descrevia o modelo antigo (montar visao/index.md a partir de capitulo:) — contradizia a própria 0.10.x, em que o livro é autorado (projeto/index.md) e capitulo: é legado. Comentário atualizado para "processa os marcadores do livro autorado". Feedback via loop §6.

Adicionado

  • Lint de diagramas Mermaid no CI (constituição v0.10.4, §5; feedback via loop §6). A constituição manda Mermaid (ER em modelagem.md, fluxos nas etapas), mas diagrama com erro de sintaxe renderiza cru no site e passa pelo --strict — caso real: erDiagram com uk usado como TIPO renderizou cru por 6 versões. Novo lint-mermaid.mjs extrai cada bloco ``mermaid e rodamermaid.parse(Node + jsdom, **sem Chromium**); diagrama que não compila **falha o build**. Ligado nodocs.yml(apps) e nobuild-site.yml` (central); publicado e carimbado.
  • tipo: livro para a Documentação Completa (constituição v0.10.5, §4; feedback via loop §6). O livro (projeto/index.md) era forçado a se declarar tipo: projeto — e não é uma etapa. Agora tem tipo próprio; modulo: não se aplica (o livro é do app inteiro); o valida-frontmatter.py espera tipo: livro nesse arquivo (e segue isentando projeto/modelagem.md, fragmento sem frontmatter).
  • Template do livro + guia de modelagem (constituição v0.10.6, §5; feedback via loop §6). templates/projeto-index.md (ex-visao.md) publica o esqueleto dos 8 capítulos com o embed do modelagem.md e o <!-- GERADO: changelog --> no lugar. E o templates/modelagem.md cravou o meio (antes improvisado caso a caso): ER Mermaid só para o overview de relacionamentos; o dicionário por tabela vai em tabela Markdown (coluna | tipo | chave | nulo? | desde | nota), porque o erDiagram não expressa NOT NULL nem chave composta e fica ilegível em tabela larga; fluxo de dados opcional.

Alterado

  • Ordem recomendada do nav do app (constituição v0.10.7): no templates/mkdocs.yml, as duas portas (Resumo Executivo, Documentação Completa) primeiro; depois referência (Dev/API, Operação, Público); depois histórico (Etapas, Decisões); Glossário no fim.
  • Massa de dados: regra invertida para o modelo privado-por-padrão (constituição v0.10.3, §5; feedback via loop §6). O valida-frontmatter.py bloqueia massa de dados (.xlsx/.csv/.sql/...) só em docs/public/ (a superfície exposta), não mais "em todo docs/ fora de anexos/" — a mensagem antiga ainda dizia "docs/ é público", desalinhada do §5. Em pasta privada é livre; risco declarado: até o login (spec 007), o portal serve docs/ aberto exceto privado/, então dado confidencial deve ficar em privado/, sob responsabilidade do autor.

0.9.0 - 2026-06-16

Adicionado

  • Modelo de dados obrigatório para projeto com banco: docs/projeto/modelagem.md (ER Mermaid consolidado, fonte da verdade do schema, evoluído no mesmo PR), embutido no livro via pymdownx.snippets (constituição §5).
  • checa-modelagem.py: aviso opt-in no CI de código quando o diff mexe numa migration sem tocar o modelagem.md (com suspeita de DROP/"legado" no SQL), tolerante e nunca fatal — publicado no site e carimbado.

Alterado

  • Documentação Completa autorada no estado atual (constituição v0.10.x): o livro deixa de ser montado por aspecto das etapas e passa a ser um esqueleto autorado (projeto/index.md) que mostra o sistema como ele é hoje, em ordem lógica que termina nos contratos públicos. O hook visao-tecnica.py só injeta marcadores (etapas no Resumo Executivo, changelog no livro) e falha o build em <!-- RECONCILIAR --> não resolvido. Configuração entra no livro, deploy fica em operacao/, capitulo: vira legado.
  • valida-frontmatter.py: supersessão de decisões verificada em cadeia (obsoleta tem que chegar a uma viva — referência quebrada/ciclo/dead-end = erro) e docs/projeto/modelagem.md isento da validação de frontmatter.

0.8.0 - 2026-06-15

Alterado

  • Constituição v0.9.0 — Documentação Completa por ASPECTO + Resumo Executivo das etapas (feedback do Gustavo sobre a novo-4): o livro empilhava etapas inteiras e virava copia-e-cola — porque os capítulos são aspectos (arc42) mas as folhas são etapas que cruzam todos. Agora o hook extrai as seções ## de cada etapa e as reagrupa por capítulo (o "Modelo de dados" reúne o de todas as etapas, as "Decisões" vêm de decisoes/), com atribuição "(Etapa NN)" — o livro arc42 que se lê por aspecto. Roteamento pelo nome da seção (canônicas do template de etapa), capitulo: vira fallback. E o Resumo Executivo (index.md) ganha o marcador <!-- etapas -->: o hook injeta "entregue/planejado" do campo novo entrega: (frase de negócio) + status de cada etapa. Templates doc-projeto.md e resumo-executivo.md atualizados; §2/§4 + app-novo.

Adicionado

  • Constituição v0.8.3 — REGRA Nº 3: deferir é explícito, não silencioso. A migração oportunista (§1.6) é legítima, mas vira escotilha silenciosa quando o "deferi reescrever o conteúdo das etapas para a barra técnica nova" passa como rodapé. Nova regra de IA (claude-regras.md + CLAUDE.md do central): adiar trabalho que o padrão recomenda é uma decisão — declarada em voz alta no fechamento ("deferido X porque…"); e se o repo ainda não publicou (sem slug no ar), o rename/redirect custa zero → fazer agora, não deferir como legado. Gancho na §1.6 e passo de fechamento na skill /xadm-docs. Feedback via loop §6.

Corrigido

  • Constituição v0.8.2 — manifesto regenerado a cada bump (inclusive text-only): um bump que mexe só no texto da constituição (como o v0.8.1) não tocava artefato rastreado, então o --check passava e o campo manifesto.constituicao ficava atrás da versão vigente (0.8.0 vs 0.8.1) — a /xadm-docs que confiasse nele subdetectaria mudanças. Prevenção: o gera-manifesto.py --check agora falha se manifesto.constituicao != versão vigente; como o CI do central roda --check, todo bump passa a exigir --update (que re-carimba o campo) para passar — o esquecimento fica impossível. §6 atualizado. Feedback via loop §6.

0.7.0 - 2026-06-15

Alterado

  • Constituição v0.8.1 — etapa de projeto tecnicamente completa (§2): avaliação do doc fundador do piloto (PROJ-2026-001) mostrou que dava pra ler mas não dava pra implementar — faltava o modelo de dados (ER, tabelas, colunas, chaves), os contratos e a config; o template ainda dizia "não liste toda coluna". Novo critério: cada etapa é tecnicamente completa para o seu escopo (um dev/IA que nunca viu o código implementa o que ela introduz/muda), por composição sem repetir (referencia o que herda, re-enuncia só o que altera — §1.4); o livro (Documentação Completa) compõe o desenho completo do sistema. Seções de completude técnica no §2 (arquitetura, modelo de dados ER+tabelas+colunas+chaves, contratos, fluxos+estados, config, qualidade), com a navalha do §1.2 (design e contrato duráveis, não spec linha a linha). Template doc-projeto.md reescrito (modelo de dados com colunas/tipos/chaves; seções Contratos/Configuração/Qualidade).

  • Constituição v0.8.0 — "Documentação Completa" (o livro) + Resumo Executivo + Etapas do Projeto (feedback do Gustavo sobre a novo-3): a antiga "Visão técnica" era só links e não virou livro — agora o hook visao-tecnica.py concatena o CONTEÚDO das folhas por capítulo (rebaixa títulos, reescreve links/imagens, preserva Mermaid), virando a página Documentação Completa que se lê de cima a baixo. A home do app vira Resumo Executivo (porta do diretor: problema + solução sem jargão → Documentação Completa). E projeto/ deixa de ser "PROJ-AAAA-NNN": são Etapas do Projeto — NN-titulo.md (duas casas, sem PROJ-/ano), avulso em projeto/diversos.md. §2/§3/§5 + templates (mkdocs nav, doc-projeto, visao-capitulo) + app-novo atualizados. Legado PROJ- aceito até migração oportunista.

  • Constituição v0.7.3 — imagem de manual público vai em docs/public/img/: a guarda de public/ (constituição §5) trata ![](...) como referência, então um print em docs/img/ (fora de public/) falhava — corretamente: imagem fora de public/ 404 quando o portal trancar. Convenção alinhada: prints do manual (que é público) vivem em docs/public/img/; docs/img/ fica para imagens de páginas internas (raras — diagramas são Mermaid). §2/§3 + screenshots-manual.md

  • template do manual atualizados; mensagem da guarda agora cita imagem. Feedback do bi-transporte-xls via loop §6.

0.6.0 - 2026-06-15

Adicionado

  • Constituição v0.7.2 — referência de API gerada pelo CI de docs (§5): TODA doc, inclusive a ref de API, é gerada e publicada pelo pipeline de docs, nunca pelo build/deploy do app. O gerador roda antes do mkdocs build, para dentro de docs/ → o link é Markdown normal e o --strict valida (acaba o anti-padrão do HTML-cru que driblava o strict e 404 no preview, e a duplicação por repo). Split de visibilidade: Javadoc/dartdoc (árvore de classes) → interno docs/dev/api/; OpenAPI (contrato) → público docs/public/api/ embutido via mkdocs-swagger-ui-tag (spec gerado do código, ex. micronaut-openapi — não do app deployado). templates/docs.yml ganha o passo "Gerar referência de API" por stack; templates/public-api.md novo; pins no toolchain. Corrige também: o docs.yml não instalava o mkdocs-print-site-plugin (bug da v0.6.0). Feedback do bi-transporte-xls via loop §6.

  • Constituição v0.7.1 — modelo de acesso SIMPLIFICADO (supera o v0.7.0): o v0.7.0 (frontmatter publico: + build duplo + dois domínios) foi commitado mas nunca shipou — o Gustavo simplificou: docs/ privado por padrão; o público vive em docs/public/. O que é público é o que está na pasta public/ (public/index, public/manual/, public/api/swagger) — sem marcação por página, sem build separado, um site só, zero infra. O manual do usuário e a API exposta passam a morar em public/. Guarda: o validador falha se uma página em public/ linkar para fora de public/ (senão quebraria quando o portal trancar). anexos/privado/ segue excluído do build (guarda do que é sensível enquanto não há login). Toolchain do padrão (/padroes/) é pública por design (CIs dependem). Decisão do Gustavo: adiar dois domínios e login — viram specs futuras 006 (dois domínios) e 007 (Google auth); a 005 fecha aqui. Fase 3/4 de .ia/005.

  • Constituição v0.6.1 — validador fresco (validador stale é falso atestado): os scripts publicados imprimem na 1ª linha a versão da constituição que implementam (carimbada no publish pelo Dockerfile); a skill /xadm-docs (e toda validação local) sempre baixa fresco e confere versão impressa == vigente antes de confiar no "0 erros" — nunca reusa cache de turno. Os scripts (valida-frontmatter.py, valida-release.py, frontmatter-cabecalho.py, visao-tecnica.py) entram no manifesto.json numa seção scripts à parte dos templates: mudar um script obriga bump da constituição (o --check falha), o que torna o carimbo confiável. Origem: um validador da era 0.5.5 deu "0 erros" numa migração a 0.5.20, dando conformidade falsa (feedback §6).
  • Constituição v0.6.0 — Visão técnica (espinha narrativa): os seis níveis são bons para consulta, mas uma pilha de folhas não se lê como livro. A Visão técnica é uma página de leitura ordenada montada no build a partir do campo capitulo: de cada folha (hook scripts/visao-tecnica.py, on_files + File.generated), na ordem de um esqueleto canônico (9 capítulos, negócio → técnico) — restaura o "ler de cima a baixo para entender" e a porta do diretor, sem desfazer a granularidade (as folhas seguem no seu nível). Plugin mkdocs-print-site-plugin (2.8) gera a página-única/PDF. Intro de capítulo opcional (docs/visao/NN-<capitulo>.md, templates/visao-capitulo.md). Origem: auditoria arc42×seis-níveis (.ia/004/005). Fase 1 da evolução (.ia/005).

Alterado

  • §5 invertido ("docs/ privado por padrão; público em docs/public/") + as três classes de acesso registradas. §2/§3: o manual do usuário (nível 6) passa a viver em docs/public/manual/; estrutura ganha a pasta public/. Validador: reconhece public/manual/ (tipo manual) e ganha a guarda de public/ (link que sai de public/ falha).
  • §1.7 reescrito: de "pronto para IA" para "dois modos: consulta E compreensão" — otimizar só a consulta é a regressão típica da doc-de-folhas.
  • §4: novo campo capitulo: (opcional; enum do esqueleto; validador erra em valor inválido — um typo sumiria a folha do livro).
  • Hook e plugin distribuídos aos apps: templates/mkdocs.yml (hook + print-site + nav), templates/docs.yml (curl do hook), Dockerfile (publica em /padroes/), requirements.txt (pin do print-site), app-novo.md (passo de adoção). Manifesto regenerado.

0.5.0 - 2026-06-13

Adicionado

  • decidido_em: no frontmatter de decisões (constituição §4) — data em que a decisão foi tomada, distinta de atualizado: (última edição). Cumpre a promessa de "retrato datado" quando o registro nasce retroativo numa migração. Opcional no validador (só confere formato AAAA-MM-DD se presente, para não quebrar decisão legada). Feedback do bi-transporte-xls via loop §6.
  • Constituição v0.5.20 — 9 mudanças da auditoria arc42×seis-níveis (BI Vantroba; pauta §6, todas avaliadas e aprovadas pelo Gustavo):
  • Hook scripts/frontmatter-cabecalho.py renderiza Status · Responsável · Atualizado em no corpo de cada página (o frontmatter sumia no build) e abre decisão obsoleta com aviso de supersessão. Publicado no site e baixado pelo docs.yml do app (igual ao validador); wired nos dois mkdocs.yml + Dockerfile. Constituição §6. (propostas #3 e #9 — a data vem do atualizado:, sem o plugin git-revision-date, que quebraria com o .git fora do build.)
  • navigation.path (breadcrumbs) nos dois mkdocs.yml — "onde estou" ao chegar via busca. (#6)
  • Contrato do Javadoc não-órfão: docs.yml falha se site/api/ existir sem nenhuma página linká-lo; receita em publicar-docs.md + dica no template. Constituição §5. (#1)
  • Template opcional templates/riscos.md (risk register transversal, consolida por link). Constituição §5. (#8)
  • Princípio anti-drift no §1.9 — um fato, um dono canônico; as demais páginas linkam (ou pymdownx.snippets), não copiam. (#7)
  • Mapa ADR-legado → decisão NNNN na 1ª migração arc42→seis-níveis (app-novo.md + skill /xadm-docs). (#10)

Validação

  • valida-frontmatter.py ganhou: aviso (não-fatal) sobre identificador de código volátil em título (*Test/Bulk* etc.) (#4); e erro quando a tabela de decisões de um doc de projeto reafirma um status que contradiz a decisão real (a decisão é a fonte única, §1.9/§5) (#5). Coluna "Status" removida do template doc-projeto.md.

Alterado

  • Constituição v0.5.19 — §1.4 e §2 passam a distinguir correção factual de superação num registro de decisão: fato incidental errado sobre o código (número, property inexistente) corrige-se em-lugar com nota datada; só premissa/raciocínio errado (a escolha poderia ter sido outra) exige superação (status: obsoleto + superado-por:). Antes, a regra "não se reescreve, supera-se" era absoluta e forçaria superar uma decisão por causa de um número errado.
  • §2 (Decisão) — "Alternativas consideradas" lista só as realmente deliberadas; reconstruir alternativas nunca cogitadas (comum em registro retroativo) é inventar deliberação (§1.2). Eco no template templates/decisao.md.

0.4.2 - 2026-06-13

Adicionado

  • Template templates/release.yml (constituição §6) — separa a validação de contrato de release (push de tag → valida-release.py) do ci.yml, que na v0.5.16 virou gate de qualidade e colidiu com o ci.yml antigo dos apps (que era o validador de release). Brecha do ho-scraper. Rastreado no manifesto; embutido em templates.md.

Alterado

  • Constituição v0.5.18 (§6, manifesto): redefinição de propósito de um template rastreado (mudar o que ele é, não só o conteúdo — ex. ci.yml release → qualidade) exige nota de migração no CHANGELOG e em app-novo.md; um re-pull cego quebra o app. Regra que faltava quando a 0.5.16 abriu a brecha do ci.yml.
  • Constituição v0.5.17 (§6): explicita dois workflows de código — ci.yml (gate de qualidade: análise estática + testes, push/PR) e release.yml (rede de segurança: valida-release.py, push de tag). Skill /xadm-release realinhada (aponta o release.yml, não "o CI" genérico). Mínimo para app só-script: lint sempre (piso), testes da lógica pura onde existir, sem forçar suíte em script trivial. Migração: app com ci.yml antigo de release renomeia para release.yml e adota o novo ci.yml.

0.4.1 - 2026-06-12

Alterado

  • Constituição v0.5.16 — aviso de falha do CI agora é webhook nativo da org (Forgejo 15; spec .ia/001 executada e apagada). O passo manual "Avisar falha no Telegram" (if: failure()) saiu do build-site.yml e do templates/docs.yml — a notificação vira config única na organização, sem YAML por workflow, cobrindo todos os repos. forgejo.md reescrito (BotFather → getUpdates → webhook Telegram na org com evento Action Failure). Os secrets TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID da org ficaram órfãos (o webhook guarda token/chat na própria config) — podem ser removidos.

Adicionado

  • Template templates/ci.yml canônico (constituição §6, "No CI (código)"): gate de análise estática + testes por stack (Java/Gradle ./gradlew check, Flutter/Dart analyze && test, Node lint && test), com base node:* para o checkout (lição do ho-scraper) e sem passo de aviso (o webhook da org cobre). Rastreado no manifesto; embutido em templates.md.
  • Constituição v0.5.15 (§2 + screenshots-manual.md): fecha a receita de screenshots Flutter (estava "em aberto"; spec .ia/002 apagada). Provada no bi-transporte (Vantroba), 9 telas com 1 comando. Correção à direção anterior: takeScreenshot não roda no desktop Linux → captura com RepaintBoundary.toImage(); alvo é o build Linux desktop (mesmo Skia do web, sem chromedriver); seed via seam de DI em memória; flag kScreenshotMode (equivalente Flutter da dica do .fade); veredito pela existência dos PNGs, não pelo exit code; custo de adoção (suporte linux/, toolchain nativo) documentado. Convenção nova: nome de PNG semântico quando há 1 imagem por tela (dre.png), senão <slug>-NN.

0.4.0 - 2026-06-12

Alterado

  • Constituição v0.5.14 (§5 + versionamento.md): o /health deixa de ser formato livre e ganha shape mínimo ({status, versao} + opcionais), pois o monitoramento (watchdog) acionou a cláusula "shape vira contrato". Receita de versão em runtime por stack (contribuição §6): Java/Micronaut (task Gradle → version.properties → InfoSource em /info e /health + log no main; versão do @OpenAPIDefinition é contrato HTTP à parte), Node (package.json), Flutter web (tem /health, no app ou no nginx), Flutter mobile (sem HTTP → versão in-app via package_info_plus), app sem HTTP (log no startup), site estático (/versao.txt). Pertence à área Engenharia (spec 003) — fica em versionamento.md até migrar.

Adicionado

  • Página Screenshots do manual (padroes/screenshots-manual.md, constituição v0.5.13 §2): receita reproduzível para os PNGs do manual (regenerável com 1 comando; tooling em scripts/screenshots/, fora de docs/), separada por stack — web renderizada no servidor (Micronaut/Java) tem a receita provada com shot-scraper + seed.sh + shots.yml (inclui a dica do .fade/modal do Bootstrap); Flutter usa mecanismo próprio (integration_test/flutter drive), com a receita canônica em aberto (shot-scraper não serve em canvas). Ponteiro no template manual-usuario.md. Contribuição do loop §6.
  • Constituição v0.5.12 (§2/§3/§5): define o lar de artefatos não-prosa e tooling, lacuna achada nos bi-*-processador-xls. O teste é a publicabilidade (docs/ é público): referência não-prosa segura de publicar (schema de contrato, exemplo de payload, .http sem segredo) vive em docs/ no nível a que pertence (ex. docs/dev/, ao lado dos .md); ferramenta executável, insumo de build e fixture (seed, config de shot-scraper, src/.../resources) ficam fora de docs/. scripts/ reconhecido na árvore do §3 como lar de helpers de dev/ops.
  • Constituição v0.5.11 (REGRA Nº 2 / §6): no ponto de fechamento de um trabalho, junto da sugestão de commit, a IA passa a oferecer (em uma linha) avaliar se o trabalho revelou lacuna/ambiguidade/atrito no padrão — o loop §6 deixa de depender de o dev lembrar. Sincronizado em templates/claude-regras.md e no CLAUDE.md do repo.
  • Constituição v0.5.10 (§6, "No CI (código)"): o gate de build/test do app passa a exigir o conjunto análise estática + testes da stack, não só os testes — lint/estilo que só roda em check/build (checkstyle, flutter analyze) precisa falhar o PR. Comandos por stack (Java/Gradle → ./gradlew check; Flutter/Dart → analyze && test; Node → lint && test) + nota em app-novo.md. Brecha pega no bi-transporte-xls (violação de checkstyle chegou ao master). O ci.yml canônico por stack fica para a migração ao Forgejo 15 (nasce sem o passo manual do Telegram).

Corrigido

  • Template xadm-docs-skill.md ganha frontmatter (name/description): sem ele a skill /xadm-docs não é reconhecida pelo Claude Code, e cada app vinha adicionando na mão — a divergência silenciosa que o template existe para evitar (feedback do loop §6).
  • Hook checa-constituicao.sh distingue a direção da diferença de versão: base atrás (migrar), ausente (registrar) ou à frente da publicada (anomalia — central atrasado/versão registrada cedo; não migrar pra baixo). Antes, qualquer diferença mandava "migrar", o que não fazia sentido com a base à frente. Constituição v0.5.9.

0.3.2 - 2026-06-12

Alterado

  • Constituição v0.5.8 (§5): privado/ vira segmento reservado — qualquer pasta privado/ em docs/ é confidencial, não só anexos/privado/. Os guards passam a casar o padrão privado/ em vez da string fixa (exclude_docs, guard do site/ no docs.yml, --exclude do rclone no sync-apps.sh), fechando o risco de um caminho privado novo escapar em silêncio (feedback). A regra de identificadores de código em doc durável passa a citar caminho de arquivo explicitamente: fixar path só quando é contrato (URL, estrutura padrão, config canônica); onde um arquivo mora internamente se descreve pelo papel.
  • Constituição v0.5.7 (REGRA Nº 2 / §6): a IA passa a sugerir o commit de forma proativa ao fechar um trabalho e escrever a mensagem pronta para copiar (sem nunca commitar sozinha). Fixado o formato: assunto de uma linha (conventional commit) + um parágrafo curto de porquê, sem lista de arquivo por arquivo. Sincronizado em templates/claude-regras.md e no CLAUDE.md do repo.

Adicionado

  • Constituição v0.5.6 (§6): manifesto de templates — o site publica padroes/manifesto.json com o hash e a versão mudou_em de cada artefato copiado para os apps (workflow docs.yml, mkdocs.yml, app.json, hook e skills), e os arquivos crus em padroes/raw/<arquivo>. A /xadm-docs passa a listar mecanicamente quais templates do app estão defasados (mudou_em > versão-base) e re-baixá-los, em vez de inferir da leitura — fechando o modo de falha em que uma mudança de template passou despercebida (ho-scraper). scripts/gera-manifesto.py (--check no CI, --update ao mexer num template); o --check falha o build se um template mudou sem o manifesto refletir, e o --update cobra o bump da constituição.

0.3.1 - 2026-06-11

Adicionado

  • Constituição v0.5.5 (feedback do bi-transporte-xls): princípio §1.9 "Duas fontes da verdade: o código e docs/" — artefato de trabalho (spec, plano, prompt) é andaime: destila para docs/ ao concluir e é apagado ("feature pronta" inclui poder apagá-lo sem perda); CLAUDE.md é roteador, não acervo (§6: regras + versão-base + ponteiros; conhecimento durável vive em docs/); convenção no §5 sobre identificadores de código em doc durável (citar nome concreto só quando ele é a interface; mecânica interna se descreve pelo papel).

0.3.0 - 2026-06-11

Adicionado

  • valida-frontmatter.py cruza o cliente: do frontmatter com o app.json: app de cliente exige o campo igual em todo doc (pega ausência e typo); app da X-Adm não leva o campo. Sem app.json, não checa (feedback do BI Transporte Vantroba).
  • Checklist de app novo: site/ no .gitignore (build local gera a pasta; site gerado nunca é commitado) e conferência da constituicao-versao.txt antes E depois de copiar os templates (release da constituição no meio de uma adoção já passou despercebido).
  • Versionamento: formato do payload do /health declarado livre — o contrato é 200 quando vivo + versão legível (o version.json do build Flutter satisfaz); shape só vira contrato se houver monitoramento, e mudará no padrão primeiro.
  • Aviso de falha do CI no Telegram: passo canônico if: failure() no final de cada job (mensagem com repo, workflow/job, ref e link da run), com secrets TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID herdados da organização xadm. Incluído no templates/docs.yml e no workflow do site central; passo a passo (BotFather, chat_id, secrets) em infraestrutura/forgejo.md. Sem os secrets, o passo só loga — não falha.

0.2.3 - 2026-06-11

Adicionado

  • Constituição v0.5.4 (§3 e §5): regra "docs/ é público" (nada de dado real de cliente, credencial ou massa de dados solta — brecha apontada pelo bi-transporte-xls) e pastas padrão docs/anexos/ (anexo público deliberado) e docs/anexos/privado/ (confidencial, ex. planilha-base com dados reais — versionada no Git, nunca publicada), cada uma com README obrigatório (templates canônicos novos).
  • Três guardas independentes para anexos/privado/: exclude_docs no mkdocs.yml canônico (e no do site central), falha no CI do app se a pasta aparecer no site/ (templates/docs.yml) e --exclude no rclone do sync-apps.sh — o portal recusa servir o caminho mesmo publicado por engano.
  • valida-frontmatter.py bloqueia massa de dados (.xlsx, .csv, .sql, .zip...) em docs/ fora de anexos/; .docx do pré-projeto continua liberado.

0.2.2 - 2026-06-11

Corrigido

  • Constituição v0.5.3 (§3, nota de legado): guias/ migra por conteúdo, não por rename fixo — manual de usuário → manual/, runbook técnico → operacao/. A pasta legada abrigava os dois níveis; o mapa 1:1 anterior mandava runbook para manual/ errado (brecha apontada pelo ho-scraper). Mesmo ajuste no templates/xadm-docs-skill.md.

Adicionado

  • Padrões: seção "Ambiente local (Linux/Ubuntu)" em publicar-docs.md — MkDocs global via pipx (sem venv manual, respeitando o PEP 668 do Ubuntu 24.04+), com as versões pinadas da toolchain; link no checklist de app novo.

0.2.1 - 2026-06-11

Corrigido

  • CI do site: o job de validação quebrava no checkout por falta de node no container (python:3.12-slim); o workflow agora usa node:22-bookworm-slim com git e Python instalados antes do checkout — a falha impedia o próprio deploy (a v0.2.0 não tinha chegado a subir).

0.2.0 - 2026-06-11

Adicionado

  • Constituição v0.4.0: modelo de seis níveis com nível Operação (runbooks técnicos em docs/operacao/, tipo: operacao) e nível Dev (API reference gerada + docs/dev/ opcional para doc escrita à mão).
  • Template runbook.md (O que é / Quando usar / Pré-requisitos / Passos / Verificação / Reversão), embutido na página de templates.
  • Validação do contrato de release no CI do site (valida-release.py no build-site.yml; push de tag v* confere tag × VERSION).
  • Constituição v0.4.1, governança: regra "IA não decide sozinha" — todo repo que usa Claude Code abre o CLAUDE.md com a regra de confirmar com o usuário antes de implementar quando houver ambiguidade ou mais de um caminho possível.
  • Constituição v0.5.1, governança: regra "IA não faz commit/push sozinha" (única exceção: a skill /xadm-release) e bloco canônico templates/claude-regras.md com as duas regras de IA, pronto para colar no CLAUDE.md de cada repo.
  • Constituição v0.5.0, governança: rastreio de versão-base — o app registra em constituicao: (docs/app.json) a versão que a doc segue; o site publica a vigente em /padroes/constituicao-versao.txt e o fonte cru em /padroes/constituicao.md; skill canônica /xadm-docs (compara, avalia e migra com aprovação) e hook SessionStart (checa-constituicao.sh) que avisa ao abrir o Claude Code no repo.
  • Rodapé do site passa a exibir a versão vigente da constituição e a versão do próprio site + copyright X-Adm (hook scripts/copyright-versao.py, que lê o frontmatter da constituição e o arquivo VERSION no build).
  • Página IA e Claude Code (padroes/ia.md): o que é o Claude Code, as regras de governança de IA, o catálogo de skills da plataforma (/xadm-release, /xadm-docs), o hook de sessão e como adotar num repo; constituição v0.5.2 referencia a página no §6.

Alterado

  • Nomenclatura definitiva das pastas de documentação: design/ → projeto/, rd/ → decisoes/, guias/ → manual/ (tipos projeto | decisao | dev | operacao | manual); o validador segue aceitando os nomes antigos até a migração oportunista dos repos.
  • Templates renomeados: doc-projeto.md, decisao.md, manual-usuario.md.
  • Skill de release renomeada de /release para /xadm-release (template xadm-release-skill.md e a cópia instalada neste repo).

0.1.0 - 2026-06-11

Adicionado

  • Site central de documentação (MkDocs Material, pt-BR, identidade visual X-Adm) com constituição, glossário da plataforma e docs de infraestrutura.
  • Constituição da documentação v0.3.6 (níveis, frontmatter, convenções, governança com loop de feedback dos apps).
  • Templates canônicos: design doc, RD, guia de usuário, mkdocs.yml, workflow docs.yml, app.json e skill /release.
  • Pipeline descentralizado: apps publicam no bucket docs-sites e o site sincroniza (~90s) com log de mudanças por app.
  • Listagem automática de aplicações e API references via app.json + index.json.
  • Validadores valida-frontmatter.py e valida-release.py, publicados no site público para consumo pelos CIs dos apps.
  • Checklist de app novo e página de versionamento/release.
  • Health check da imagem e carimbos de versão (/versao.txt) e commit (/commit.txt).