0019 — Bibliotecas compartilhadas da casa¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12 · 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-comum-storage…) 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, registro público (
fonte.xadm.biz). O CI doxadm-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:
Correção 2026-08-10: o registro passou de Forgejo Packages privado para público em
fonte.xadm.biz— leitura anônima, sem credencial; publicar ainda pedeFORGEJO_PACKAGES_*(só o CI doxadm-commons).
repositories {
mavenCentral()
// Registro PÚBLICO — leitura anônima, sem credentials{} (a URL não é segredo).
maven { url = uri("https://fonte.xadm.biz/api/packages/xadm/maven") }
}
dependencies {
implementation("br.com.xadm:xadm-seguranca:0.2.0") // versão fixa
}
Ler não pede credencial (registro público); publicar sim — o CI do xadm-commons usa
FORGEJO_PACKAGES_USER/TOKEN (secret da org no Forgejo / env do Coolify), nunca hard-coded no
build.gradle.kts nem em .ia/, como todo segredo da casa
(Segurança §Segredos). A assimetria publish×read é o que
mudou.
-
Set publicado — oito módulos no ar em
https://fonte.xadm.biz/xadm/-/packages, groupbr.com.xadm:xadm-seguranca,xadm-comum-web,xadm-mensageria,xadm-comum-util,xadm-comum-storage,xadm-comum-teste,xadm-comum-powersync,xadm-ingestion-core. -
Versão corrente, piso e estado de entrega não moram nesta decisão.
Correção 2026-09-12: a versão corrente vem do registro; o piso, com o motivo, e a migração de cada versão vêm do CHANGELOG do módulo — ver Bibliotecas da casa. A tabela de versões e o registro de entregas por módulo que viviam aqui saíram: envelheciam entre releases da lib. A política de acompanhar a versão corrente é a 0034.
- App consome por versão publicada, não por fonte local. Nada de
includeBuild/composite apontando o checkout doxadm-commonscomo 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.
- A extração não pode REGREDIR segurança nem robustez — reconcilie pro baseline endurecido, não
pro mais simples. Byte-idêntico é gatilho de revisão, não meta: copiar uma classe fielmente
também copia os defeitos da origem — e uma vez na lib, o defeito alcança todo adotante
transitivo. Regra: (1) se um app de origem tinha endurecimento (comparação constant-time, guard
de vazio/fail-closed, default de config que não quebra boot), a lib porta o endurecimento; (2)
se as cópias divergiam, canoniza-se a mais forte em segurança/robustez, não a mais curta.
Toda classe que toca segredo/auth roda o checklist do revisor
na extração — diff semântico contra a origem, não textual. Incidente que originou a regra: o
StaticBearerTokenValidatorsubiu byte-idêntico e chegou na lib abaixo do piso da casa (String.equalstiming-vulnerável, sem guard de vazio → auth-bypass,@Valuesem default → boot quebrado pra quem nem usa a feature), forçando o adotante a re-endurecer via@Replaces— a extração não cumpriu o objetivo (parar de forkar). Piso de segredo/auth em Segurança §Authn/authz. - 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).
<svc>-api(contrato app↔app) reusa este mecanismo, mas NÃO éxadm-commons. O módulo de contrato tipado que um receptor publica (0020) usa o mesmo transporte (Maven no Forgejo, groupbr.com.xadm, consumo por versão), porém é artefato do app receptor, publicado do próprio repo — não entra noxadm-commons, que é infra transversal (auth, web, storage…), não o contrato de um app específico.