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 seugradle.properties/versionindependente — 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.*). Oint-sascar, que hoje usabiz.xadm, migra parabr.com.xadmao adotar a primeira lib (o compilador é o gate do rename). Nada nasce embiz.xadm. - Artefato Maven no Forgejo Packages. O CI do
xadm-commonspublica 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
gitcerimonioso em vez de mudar uma linha dedependencies. - 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-commonsganha 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;comumnão depende de feature). Sem dono, a lib vira mais um lugar que envelhece.- Toda adoção é um passo coordenado por repo. Adotar
xadm-segurancanoonpetro/thomsmigra o pacotesecurity→seguranca(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).