Pular para conteúdo

0019 — Bibliotecas compartilhadas da casa

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-08-04 · Decidido em: 2026-08-04

Contexto

O monorepo X-Adm propaga infra por copy-paste, não por biblioteca. A auditoria de 2026-08-03 (grafo graphify-out/ + leitura dos repos) achou 28 nomes de classe idênticos em 3–4 clientes — a stack de auth inteira, RFC-7807, health, versão, Sentry — mais PowerSync-admin (~18) e storage (~6) idênticos entre onpetro e vantroba. Só 1,9% das arestas do grafo cruzam fronteira de app: não há espinha dorsal de código compartilhado, só N cópias que envelhecem em paralelo.

O custo é dobrado. Manutenção: corrigir um bug de auth é editar a mão em N repos, e cada um pode ficar para trás. Segurança: a stack de auth copiada casa com um IdP Firebase compartilhado (xadm-6ab81) — um drift no check de domínio de um app vira exposição cross-client, e o drift é invisível porque não há fonte única a conferir.

A constituição prescreve comum/core dentro de cada app (package-by-feature, Java/Micronaut), mas não tem mecanismo de biblioteca compartilhada entre apps. Sem ele, "não copie infra" é regra sem máquina — e regra sem máquina é o que já falhou.

Decisão

Infra idêntica cross-app vive num único repo xadm-commons, cada lib é um módulo Gradle com SemVer próprio, publicada como artefato Maven no Forgejo Packages; o app consome por versão, não por cópia.

  • Um repo multi-módulo (xadm-commons), não um repo por lib. Cada capacidade transversal é um módulo Gradle (xadm-seguranca, xadm-comum-web, xadm-armazenamento…) com seu gradle.properties/version independente — versionar a auth não obriga bumpar o storage. Um repo, um pipeline, N artefatos.
  • Base de pacote/group br.com.xadm — decidido, é o group Maven e a raiz de pacote de toda lib (br.com.xadm.comum.*, br.com.xadm.seguranca.*). O int-sascar, que hoje usa biz.xadm, migra para br.com.xadm ao adotar a primeira lib (o compilador é o gate do rename). Nada nasce em biz.xadm.
  • Artefato Maven no Forgejo Packages. O CI do xadm-commons publica cada módulo (publishToMavenRepository) no registry de pacotes do Forgejo da org. O app consome declarando o repositório e a dependência por versão fixa:
repositories {
    mavenCentral()
    maven {
        url = uri("${property("forgejoPackagesUrl")}")   // <VAR> — URL do registro, não segredo no repo
        credentials {
            username = System.getenv("FORGEJO_PACKAGES_USER")
            password = System.getenv("FORGEJO_PACKAGES_TOKEN")   // token de LEITURA em env/secret
        }
    }
}
dependencies {
    implementation("br.com.xadm:xadm-seguranca:1.4.0")
}

A URL do registro e a credencial entram por <VAR>/env (FORGEJO_PACKAGES_*), nunca hard-coded no build.gradle.kts nem em .ia/ — é secret da org no Forgejo / env do Coolify, como todo segredo da casa (Segurança §Segredos). O valor real vem out-of-band. - App consome por versão publicada, não por fonte local. Nada de includeBuild/composite apontando o checkout do xadm-commons como norma — o app compila contra o artefato publicado, que é o que a produção embarca (mesmo princípio "gate fiel ao artefato", constituição §6). Composite build fica para desenvolvimento local pontual da lib. - Extração é conservadora e caracterizada. Só sobe para a lib o que for byte-idêntico entre apps (a menos de CRLF e nome de pacote); classe divergente exige decisão explícita de reconciliação antes de subir — não se unifica corpo diferente às cegas. Cada fase de extração produz a matriz idêntico/divergente das cópias como primeiro work-item. - Ordem de extração (cada uma sub-lineage no repo-alvo, citando raiz/001): mecanismo (esta decisão) → segurança (xadm-seguranca, risco alto: centraliza o validador do IdP compartilhado, audience/domínio segue config por-app) → web (xadm-comum-web: RFC-7807, health, versão, request-id/MDC — ver contrato app↔app, 0020) → contrato de comms → registry anti-god-node → ingestão/storage. A extração em si não é escopo desta decisão; aqui fica como a lib existe.

Alternativas descartadas

  • Um repo por lib (xadm-seguranca, xadm-commons-web… cada um seu repo). N pipelines de release, N tags a coordenar, mais cerimônia para a mesma coisa. O multi-módulo dá SemVer independente por módulo sem multiplicar repositórios.
  • Git submodule. A "versão" de um submódulo é um SHA, não SemVer — acopla a fonte (o app carrega a árvore da lib), não o artefato, e some do radar de "que versão estou usando". Bump vira git cerimonioso em vez de mudar uma linha de dependencies.
  • Continuar com copy-paste + disciplina. É exatamente o que produziu 28 classes duplicadas e o risco de drift do IdP. Regra sem máquina não segura.

Consequências

  • Fim do copy-paste de infra. Corrigir um bug de auth passa a ser um bump de versão que todos os apps recebem no próximo implementation(...) — não N edições à mão. O drift do IdP compartilhado cai (um validador, não N cópias); audience/domínio continua config por-app.
  • xadm-commons ganha um dono. Alguém responde pelo repo, pela política de versão e pela fronteira de cada módulo (ArchUnit no gate: módulo não depende de app; comum não depende de feature). Sem dono, a lib vira mais um lugar que envelhece.
  • Toda adoção é um passo coordenado por repo. Adotar xadm-seguranca no onpetro/thoms migra o pacote securityseguranca (vantroba já é seguranca) — o compilador acusa o rename incompleto. É lift-and-shift + testes de paridade, não redesign da semântica de auth.
  • Precisa da coordenada do registro. URL e credencial do Forgejo Packages são pré-condição operacional (via <VAR>), levantadas fora deste ADR.
  • Reversível por módulo. Um app pode pinar uma versão antiga enquanto migra; a lib mantém compat na janela de deprecação (mesma disciplina do contrato, 0020).