Começar
Aparência

Vale na hora e fica lembrada neste navegador.

O modelo

06 · Modelo zerado e lacunas

O modelo zerado é um repositório que sabe tudo sobre como trabalhamos e nada sobre a tecnologia do produto. Onde falta essa informação, há uma lacuna marcada. A primeira ação de uma stack é preencher as lacunas — e o modelo impede que o desenvolvimento comece antes disso.

O modelo mora neste repositório, na pasta modelo/, versionado junto com as regras e as ORGs (ADR-0002). As ORGs não ficam dentro dele: o eng/new-stack.sh as copia de docs/decisoes/organizacao/ ao criar a stack e escreve o arquivo ARQUITETURA com a versão.

O que é uma lacuna

Uma lacuna é um marcador procurável num arquivo do modelo:

Texto
{{LACUNA:<id>}}
  • <id> é estável e único (stack.linguagem, persistencia.banco, app.lista…), para que a mesma lacuna em vários arquivos seja preenchida com o mesmo valor;
  • cada id tem a sua descrição num lugar só: o catálogo das lacunas, no fim do roteiro da Fase 1 (docs/guias/definir-a-stack.md), com o que preencher e se é de escolha ou de valor. O modelo de hoje tem 93;
  • o formato é ASCII de propósito: grep -rn '{{LACUNA:' . lista todas;
  • eng/gaps.sh faz essa busca e falha se encontrar alguma. Ele roda na CI desde o primeiro commit — enquanto houver lacuna, a CI fica vermelha, e isso é o esperado na Fase 1.
O modelo zerado chega com N lacunas. A ficha da stack (docs/stack.md) recebe as respostas e propaga os valores para as ADRs pré-escritas, o AGENTS.md, o openspec/config.yaml, o README e o ARCHITECTURE.md. O eng/gaps.sh procura lacunas: com zero, o portão 1 se abre; com alguma, volta-se à ficha. modelo zeradoN lacunasdocs/stack.mda fichaADRs pré-escritasAGENTS.mdopenspec/config.yamlREADME.mdARCHITECTURE.mdpropaga os valoreseng/gaps.sh0 lacunas:portão 1alguma lacuna? de volta à ficha (e a CI fica vermelha)

Dois tipos de lacuna

TipoO que éExemploOnde mora
De escolhaO que é: Uma decisão com alternativasExemplo: "linguagem e runtime", "banco de dados"Onde mora: ficha + ADR pré-escrita correspondente (contexto e alternativas)
De valorO que é: Um dado que decorre de escolhas já feitasExemplo: "porta local da aplicação", "prefixo no Bitwarden", "nome do realm"Onde mora: ficha + arquivos que usam o valor

Lacunas de escolha sempre viram texto de ADR (com alternativas consideradas); lacunas de valor só precisam do valor.

A ficha da stack (docs/stack.md)

A ficha é o único lugar onde a stack declara sua tecnologia. É curta, tabelada e lida em segundos. As ADRs explicam o porquê; a ficha diz o quê.

Abaixo, a ficha zerada como ela vem no modelo. Ao lado de cada lacuna, como ficaria no produto fictício dos exemplos, o atendimento (03), para dar o tom.

1. Produto e aplicações

CampoLacunaExemplo
Nome do produtoLacuna: {{LACUNA:produto.nome}}Exemplo: atendimento
PlataformaLacuna: {{LACUNA:produto.plataforma}}Exemplo: Enterprise
RepositórioLacuna: {{LACUNA:produto.repositorio}}Exemplo: enterprise-stack-atendimento
Propósito em uma fraseLacuna: {{LACUNA:produto.proposito}}Exemplo: atendimento a clientes com créditos pré-pagos
AplicaçõesLacuna: {{LACUNA:app.lista}}Exemplo: ver tabela abaixo
AplicaçãoPúblicoTipoAcessoEndereçoRealmFamília de corPorta local
{{LACUNA:app.nome}}Público: {{LACUNA:app.publico}}Tipo: {{LACUNA:app.tipo}}: web com backend · site estáticoAcesso: {{LACUNA:app.acesso}}: restrita · pública · mistaEndereço: {{LACUNA:app.endereco}}Realm: {{LACUNA:app.realm}}Família de cor: {{LACUNA:app.familia}}Porta local: {{LACUNA:app.porta}}
ex. atendimento-gestaoPúblico: colaboradoresTipo: web com backendAcesso: restritaEndereço: gestao-atendimento.enterprise.hibex.netRealm: hibex-teamFamília de cor: OceanoPorta local: 8081
ex. atendimento-clientePúblico: clientes externosTipo: web com backendAcesso: restritaEndereço: atendimento.hibex.appRealm: hibex-enterpriseFamília de cor: AuroraPorta local: 8080
ex. atendimento-sitePúblico: visitantesTipo: site estáticoAcesso: públicaEndereço: atendimento.hibex.coRealm: —Família de cor: MentaPorta local: 8070

2. Tecnologia das aplicações

CampoLacunaExemploFonte única da versão
Linguagem + versãoLacuna: {{LACUNA:stack.linguagem}}Exemplo: Java 21 (LTS)Fonte única da versão: {{LACUNA:stack.fonte-versao-linguagem}} (ex.: toolchain no build)
Framework de aplicação + versãoLacuna: {{LACUNA:stack.framework}}Exemplo: Spring Boot 4.1.1Fonte única da versão: {{LACUNA:stack.fonte-versao-dependencias}} (ex.: catálogo de versões)
Abordagem de UILacuna: {{LACUNA:stack.ui}}Exemplo: renderizada no servidor (Thymeleaf + htmx 2.0.x)Fonte única da versão: {{LACUNA:stack.fonte-versao-dependencias}}
Cliente OIDCLacuna: {{LACUNA:stack.oidc}}Exemplo: Spring Security OAuth2 ClientFonte única da versão: {{LACUNA:stack.fonte-versao-dependencias}} (ex.: BOM do framework)
Ferramenta de build + versãoLacuna: {{LACUNA:stack.build}}Exemplo: Gradle 8.14.3 (Kotlin DSL, wrapper)Fonte única da versão: {{LACUNA:stack.fonte-versao-build}} (ex.: wrapper versionado)
Gerenciador de dependênciasLacuna: {{LACUNA:stack.dependencias}}Exemplo: version catalog gradle/libs.versions.tomlFonte única da versão: —
Estrutura de módulosLacuna: {{LACUNA:stack.modulos}}Exemplo: core (domínio, JPA, migrations) + um módulo por aplicaçãoFonte única da versão: —

3. Persistência

O modelo não tem banco padrão: cada stack escolhe os seus armazenamentos (banco relacional, MongoDB, fila, armazenamento de arquivos, cache…) numa ADR, pelos critérios abaixo. O PostgreSQL é a opção já provada para dados relacionais, não uma imposição.

CritérioPergunta
BackupPergunta: Há ferramenta de backup e uma restauração que se pode testar localmente?
ImagemPergunta: Há imagem oficial com tag completa, para o Compose, os testes e o deploy?
TestesPergunta: Sobe em container nos testes de integração e no E2E, e se zera rápido?
EvoluçãoPergunta: Como o formato dos dados muda com o produto (migrations ou equivalente), e quem é o dono?
OperaçãoPergunta: O toolbox já sabe observar e guardar isso, ou é coisa nova para a plataforma?
CampoLacunaExemploFonte única da versão
Armazenamentos + versõesLacuna: {{LACUNA:persistencia.banco}}Exemplo: PostgreSQL 16.x (ou MongoDB, uma fila, um bucket…)Fonte única da versão: compose.yaml + deploy.yml (mesma tag)
Acesso a dadosLacuna: {{LACUNA:persistencia.acesso}}Exemplo: Spring Data JPA / HibernateFonte única da versão: BOM
MigrationsLacuna: {{LACUNA:persistencia.migrations}}Exemplo: Flyway, em core/src/main/resources/db/migrationFonte única da versão: BOM
Dono do schemaLacuna: {{LACUNA:persistencia.dono}}Exemplo: a aplicação de gestão migra no boot; as outras validamFonte única da versão: —
Bancos por aplicaçãoLacuna: {{LACUNA:persistencia.topologia}}Exemplo: um banco compartilhadoFonte única da versão: —
BackupLacuna: {{LACUNA:persistencia.backup}}; sem backup: {{LACUNA:persistencia.sem-backup}}Exemplo: prodrigestivill/postgres-backup-local:16-debian-<hash>; sem backup: nenhumFonte única da versão: deploy.yml

4. Testes

Em todas as camadas, o teste vem antes do código (TDD, ORG-0009).

CampoLacunaExemploFixo pela arquitetura
UnidadeLacuna: {{LACUNA:testes.unidade}}Exemplo: JUnit 5 + AssertJFixo pela arquitetura: sem rede, sem container
IntegraçãoLacuna: {{LACUNA:testes.integracao}}Exemplo: Spring Boot Test + Testcontainers 2.xFixo pela arquitetura: dependências reais em container
Integração com identidadeLacuna: {{LACUNA:testes.identidade}}Exemplo: Keycloak em Testcontainers (GenericContainer)Fixo pela arquitetura: Keycloak real, na imagem do contrato da plataforma
Fim a fimLacuna: —Exemplo: —Fixo pela arquitetura: Playwright {{LACUNA:e2e.versao-playwright}} + Node {{LACUNA:e2e.versao-node}}, TypeScript, Chromium do Playwright, harness hierárquico
Ambiente do E2ELacuna: {{LACUNA:e2e.ambiente}}Exemplo: PostgreSQL zerado por psql; e-mail num servidor SMTP de capturaFixo pela arquitetura: a interface dos preparadores e o perfil e2e do Compose (doc 11)
Comando que roda tudoLacuna: {{LACUNA:testes.comando}}Exemplo: ./gradlew buildFixo pela arquitetura: um comando só

5. Containers e implantação

CampoLacunaExemplo
Imagem de buildLacuna: {{LACUNA:container.imagem-build}}Exemplo: gradle:8.14.3-jdk21
Imagem de runtimeLacuna: {{LACUNA:container.imagem-runtime}}Exemplo: eclipse-temurin:21.0.x_y-jre
Site estáticoLacuna: {{LACUNA:container.imagem-estatico}}Exemplo: nginx:1.x.y-alpine
Host(s) da VPSLacuna: {{LACUNA:deploy.hosts}}Exemplo: web.ovhcloudlab.hibex.net
Registro de imagensLacuna: — (fixo: ghcr.io)Exemplo: ghcr.io/hibex-solutions/<app>
Prefixo no BitwardenLacuna: {{LACUNA:deploy.prefixo-segredos}} (um projeto para o produto)Exemplo: ATENDIMENTO_

6. Identidade

CampoLacunaExemplo
Realms usadosLacuna: {{LACUNA:identidade.realms}}Exemplo: hibex-team (colaboradores), hibex-enterprise (clientes)
Realms novos a criar no toolboxLacuna: {{LACUNA:identidade.realms-novos}}Exemplo: hibex-enterprise
Permissões e perfisLacuna: {{LACUNA:identidade.acesso}}Exemplo: em docs/acesso/<app>.md
IdPs externosLacuna: {{LACUNA:identidade.idps}}Exemplo: Google, um OAuth client por realm
KeycloakLacuna: — (fixo: o contrato da plataforma)Exemplo: quay.io/keycloak/keycloak:26.7.1

7. Observabilidade

CampoLacunaExemploFixo pela arquitetura
Log estruturadoLacuna: {{LACUNA:obs.log}}Exemplo: estruturado nativo do Spring Boot (ECS)Fixo pela arquitetura: JSON no stdout, uma linha por evento
MétricasLacuna: {{LACUNA:obs.metricas}}Exemplo: Micrometer + registry PrometheusFixo pela arquitetura: Prometheus em porta interna
TracesLacuna: {{LACUNA:obs.traces}}Exemplo: Micrometer Tracing + bridge OTel + exporter OTLPFixo pela arquitetura: OTLP para o Alloy
Porta internaLacuna: {{LACUNA:obs.porta}}Exemplo: 9090Fixo pela arquitetura: não publicada

8. Pré-requisitos de máquina

FerramentaVersãoPara quê
LinuxVersão: —Para quê: plataforma (fixo)
Docker Engine + Compose v2Versão: {{LACUNA:prereq.docker}}Para quê: ambiente local, testes, imagens (fixo)
Node.js + npmVersão: {{LACUNA:prereq.node}}Para quê: OpenSpec CLI, E2E, verificador do design system (fixo)
OpenSpec CLIVersão: — (o modelo traz a versão; a stack atualiza por change)Para quê: changes (fixo)
Git + Git LFSVersão: {{LACUNA:prereq.git}}Para quê: (LFS só se a stack mantiver acervo de capturas: {{LACUNA:acervo.manter}}, ADR-0011)
Python 3Versão: o do sistema, 3.10 ou mais novoPara quê: hooks do agente, eng/pinned-versions.sh (fixo)
KamalVersão: {{LACUNA:prereq.kamal}}Para quê: deploy — só o mantenedor (fixo)
Bitwarden SM CLI (bws)Versão: {{LACUNA:prereq.bws}}Para quê: segredos — só o mantenedor (fixo)
{{LACUNA:prereq.runtime}}Versão: Para quê: runtime da linguagem, se roda nativo

Fonte única de versão

Uma versão escrita em dois lugares diverge. Por isso a ficha diz onde cada versão mora, e todos os outros lugares a referenciam ou a conferem.

Cada versão mora num lugar só: o catálogo ou lockfile da linguagem, o wrapper do build, o lockfile e o .nvmrc do E2E, as tags de imagem no Dockerfile e no compose.yaml, as actions e o runner da CI, e as CLIs em eng/tools.versions. O eng/pinned-versions.sh confere todos, barra latest, faixas e tags sem patch, e confere que a mesma imagem tem a mesma tag no compose, no deploy e nos testes. eng/pinned-versions.shcatálogo / lockfileda linguagemwrapper do builde2e/package-lock.json.nvmrcDockerfile ·compose.yamltags de imagemworkflows da CIactions e runnereng/tools.versionsnode, openspecbarradolatest^1.2postgres:16ubuntu-latestnpx pacotea mesma imagem,a mesma tagcompose.yaml26.4.7deploy.yml26.4.7testes26.4.7

eng/pinned-versions.sh (modelo) falha quando encontra:

  • latest, *, ^x, ~x, >=x, 1.+, latest.release, faixas Maven e -SNAPSHOT nos manifestos que conhece: package.json, requirements*.txt, *.versions.toml, build.gradle(.kts), pom.xml, .nvmrc, .node-version, .python-version, .tool-versions e eng/tools.versions;
  • imagem Docker sem tag ou com tag sem x.y.z (postgres:16), no Dockerfile, no compose.yaml, nos acessórios do deploy.yml e nos workflows. Com digest, passa. Uma imagem que só publica x.y entra em eng/pinned-versions.exceptions, exata e com a razão;
  • npx <pacote> sem @x.y.z em scripts, workflows, Makefile, scripts do package.json e nos blocos de código do Markdown. Sem versão passa só o pacote que um package.json do repositório já declara com versão exata;
  • runs-on: ubuntu-latest ou uses: action@vN sem vX.Y.Z nem o SHA completo;
  • a mesma imagem com versões diferentes entre compose.yaml, deploy/*/config/deploy.yml, os Dockerfile, os workflows e o código (ex.: Keycloak do Testcontainers × Keycloak do Compose).

Lacunas e variáveis (${X}) ficam de fora: as lacunas são do gaps.sh, e a variável vem da fonte única. Um ecossistema que falte entra no script por mudança na arquitetura, com teste.

O roteiro da Fase 1 (docs/guias/definir-a-stack.md)

O modelo traz o roteiro que o mantenedor e o agente seguem, em conversa:

O roteiro da Fase 1 como uma prancheta com dez perguntas, na ordem: para quem é; quantas aplicações; que permissões e perfis; que tipo de interface; que linguagem e framework; que banco e quem é dono do schema; que ferramentas de teste; que versões exatas e onde mora cada uma; que módulos e pastas; e endereços, famílias de cor e prefixos de segredos. Depois, preencher as ADRs pré-escritas, revisar, aceitar e rodar gaps.sh. 1.Para quem é o produto? Quantaspopulações?2.Quantas aplicações? Uma por público oupapel3.Que permissões e perfis em cada uma?4.Que tipo de interface? Servidor, navegador,estático5.Que linguagem e framework?6.Que banco? Quantos? Quem é dono doschema?7.Que testes de unidade e integração?8.Que versões exatas? Onde mora cada uma?9.Que módulos? Que estrutura de pastas?10.Endereços, famílias de cor, prefixos desegredosdepois: preencher as ADRs pré-escritas, revisar, aceitar e rodar eng/gaps.shno modelo, o roteiro mora em docs/guias/definir-a-stack.md

Critérios mínimos que toda escolha de tecnologia precisa responder (vão para o "Contexto" da ADR de stack):

CritérioPergunta
OIDCPergunta: Há cliente OIDC maduro com Authorization Code + PKCE e leitura de client roles?
ObservabilidadePergunta: Dá para cumprir o contrato (JSON no stdout, Prometheus, OTLP) sem agent externo?
TestesPergunta: Há como subir banco e Keycloak reais em container nos testes de integração?
ContainerPergunta: Gera uma imagem enxuta, sem estado, que roda como usuário não-root?
Design systemPergunta: Consegue servir a cópia de vendor/hibex/ e renderizar o HTML de referência?
TimePergunta: Quem vai manter conhece? Há material atualizado para a versão escolhida?
Ciclo de vidaPergunta: A versão escolhida tem suporte (LTS) pelo horizonte do produto?

O que o modelo não decide e não deixa como lacuna

Algumas decisões são da stack, mas surgem ao longo da vida, não no nascimento. O modelo não as força na Fase 1 — elas viram ADRs próprias quando aparecerem:

  • formato de API entre aplicações (se um dia houver);
  • fila, cache, busca, agendador;
  • armazenamento de arquivos (o padrão Hibex até aqui é só links, sem upload);
  • internacionalização;
  • integrações externas específicas.