Padrão do módulo
Texto da Baseline 1 com as decisões pós-freeze D93–D100 (SDK
sdk/v0.7.0, linha 0.7;console.nnumbers.io/v1alpha3): a nuvem por credencial tenant-scoped (§3, §27, §28; CONTRACTS D80, D82) e a audience por item, as surfaces e a linha 0.7 (§0; CONTRACTS D96–D100). A norma detalhada écontracts/v1alpha3/CONTRACTS.md.
Objetivo
Este documento deve permitir que outro engenheiro ou agent NNumbers implemente um módulo sem precisar conhecer internamente a Console.
Um módulo deve integrar-se exclusivamente pelos contratos publicados no SDK.
0. Audience, surfaces e kind (sdk/v0.7.0; CONTRACTS D96–D100)
A primeira pergunta de todo módulo é quem usa cada coisa. A resposta é a
audience, declarada por item, com uma semântica só:
| audience | quem | onde vale |
|---|---|---|
public | anônimo na internet | componente de status (página pública) e página de docs (portal público). Nunca rota de API (o gateway não tem rota anônima, D41) nem surface de Console |
tenant | principal de um realm de CLIENTE (qualquer Global Tenant cujo issuer não é o do realm de plataforma) | surfaces, rotas, docs, help, trilhas de audit, navegação |
platform | principal do realm nnumbers-platform, e só dele (issuer VERIFICADO) | surfaces, rotas, docs, help, trilhas, componentes de status |
internal | serviço↔serviço | rotas internas (auth: service). Nunca descobrível por humano: fora do shell, do catálogo, da docs, do help, do OpenAPI, de erro e do status |
Onde a audience se declara (e o padrão):
| item | campo | padrão e regra |
|---|---|---|
rota (nnp/api.yaml) | audience (opcional, preferido) | sem ela, DERIVADA de (visibility, auth): internal/service → internal, platform → platform, user → tenant. Com ela, visibility/auth são derivados e, se declarados, têm de concordar (audience_mismatch). public é recusada (audience_unsupported). Permissão scope: tenant na rota tenant, scope: platform na rota platform |
| surface | chave em surfaces do nnp/release.yaml + spec.audience do manifesto | só tenant ou platform; manifesto ≠ chave = surface_audience_mismatch |
| item de navegação | herda da surface | permissão com o scope da audience da surface (navigation_permission_audience) |
| tópico de help | audience | OBRIGATÓRIA (tenant/platform; sem ela, help_audience_required); permissões do tópico com o scope dela; help_topic de um item de navegação aponta tópico da MESMA audience da surface (help_topic_audience_mismatch) |
| docs | audience do docs.yaml (padrão das páginas) + pages[].audience (override) | OBRIGATÓRIA (public/tenant/platform; sem ela, docs_audience_required). Só páginas public vão ao portal público |
| trilha de audit | trails.<id>.audience | tenant/platform; padrão platform |
| componente de status | audience | public/platform; padrão platform. O antigo public: bool é recusado (field_removed) |
| catálogo de serviços, OpenAPI, busca | derivados dos itens acima | filtrados NO SERVIDOR |
kind não é audience. module.kind (module|platform) continua só como
identidade técnica e regra de assinatura (D12; ids de plataforma, nn_kinds).
Nada de navegação, agrupamento ou visibilidade por kind: um NNS kind: platform pode ter surface tenant (o IAM) e um kind: module pode ter
surface platform.
Surfaces (D97). Um módulo tem zero ou mais surfaces de Console, no máximo
uma por audience (tenant, platform); o id global é
<module_id>/<audience> (ex.: audit/platform). O caso comum é SÓ a
tenant (um NNC). Duas surfaces = dois NNC (nnc/ e nnc-platform/), um
build Vite por surface, sem chunk compartilhado, na MESMA release: assinam,
estagiam, ativam e fazem rollback juntas.
tenantaparece na Tenant Console (console.nnumbers.com.br), para os clientes;platformaparece na Platform Console (platform.nnumbers.com.br), para os operadores da NNumbers. São duas Consoles lógicas, com login, sessão e emissor próprios; uma surface nunca é montada nem servida na Console da outra audience.- O módulo declara SÓ a audience da surface, as permissões e as rotas. O shell mostra a surface só de módulo ATIVO e compatível e só a quem tem ≥ 1 permissão do produto; a rota de outra audience responde 404 igual ao de rota inexistente, e a permissão ausente, 403 (D99).
- Gerador:
nnconsole module create(o CLI Java do SDK); onew-module --surface tenant(padrão),--surface platform,--surface tenant,platformou--no-uie o "Quero criar Compute" =--surface tenanteram da linha Go, que saiu.
Linha 0.7 (D100). O upload exige builder, compatibility.sdk e SDK
ligado na linha 0.7 (≥ 0.7.0): pacote 0.6 é recusado (builder_unsupported
ou sdk_unsupported), e as releases 0.6 já armazenadas continuam rodando. O
shell carrega NNC com runtime: 1 e SDK ≥ 0.6.0 com MINOR ≤ a dele. A 1ª
release de um módulo na linha 0.7 não tem N-1 (o rollback cruzaria a linha);
o rollback acoplado volta a valer a partir da 2ª (§35).
1. Um módulo = um repositório
Exemplo:
nnumbers-console-infinite-data
Estrutura:
nnumbers-console-infinite-data/
├── nnc/ # a surface única (tenant, no caso comum)
│ ├── src/
│ ├── tests/
│ ├── package.json
│ └── console-module.yaml # spec.audience: tenant
│
├── nnc-platform/ # só com as DUAS surfaces: a platform (build próprio)
│ └── console-module.yaml # spec.audience: platform
│
├── nns/
│ ├── cmd/
│ ├── internal/
│ ├── api/
│ ├── tests/
│ ├── Dockerfile
│ └── helm/
│
├── nnp/
│ ├── release.yaml
│ ├── iam.yaml
│ ├── api.yaml
│ ├── audit.yaml
│ ├── placement.yaml
│ └── build/
│
└── docs/
2. NNC
NNC é frontend puro. Cada NNC é UMA surface, de UMA audience (tenant ou
platform, §0), montada só pela Console daquela audience.
Pode:
renderizar UI
consultar API pública
executar ações via API
usar Console SDK
usar o contexto da surface (useAudience; useTenant na tenant, null na platform;
usePlatformContext na platform, null na tenant)
usar Action Center
verificar permission
Não pode:
falar com OpenStack
falar com Kubernetes
falar com MySQL
falar diretamente com produto
ler Barbican
possuir cloud credential
executar Helm
3. NNS
NNS é backend/control service.
Pode implementar:
public REST API
jobs/actions
business logic
product adapters
cloud requests (OpenStack direto, com a credencial do tenant)
audit events
health
metrics
traces
Toda API externa passa pelo Public API Gateway.
4. NNP
NNP casa as surfaces (NNC) e o NNS numa release só.
Release:
module:
id: infinite-data
version: 1.5.0
surfaces:
tenant:
manifest: ...
artifact: ...
digest: ...
service:
image: ...@sha256:...
chart: ...
digest: ...
compatibility:
console: ...
sdk: ...
descriptors:
iam: iam.yaml
api: api.yaml
audit: audit.yaml
placement: placement.yaml
Os digests não são de autoria: o nnp build os calcula e grava no
release.json (um por surface). surfaces ausente = módulo sem UI;
platform: {manifest: ../nnc-platform/console-module.yaml, artifact: ../nnc-platform/dist} acrescenta a surface da Platform Console na MESMA
release. O bloco console: da linha 0.6 é recusado (console_block_removed).
compatibility na linha 0.7: {console: ">=0.7.0 <1.0.0", sdk: 0.7.0}.
5. Immutable release
Depois de publicada:
module + version
é imutável.
Se corrigir:
1.5.1
Não sobrescrever 1.5.0.
6. Helm obrigatório para NNS deployável
Cada NNS deve fornecer chart.
Requisitos mínimos:
Deployment
Service
ServiceAccount
ConfigMap/Secret refs
readiness
liveness
resources
PDB quando necessário
topology spread quando necessário
NetworkPolicy
Sem credenciais hardcoded.
7. Service endpoint
Chart expõe apenas Service interno.
Public API Gateway é a porta pública.
Não criar LoadBalancer por módulo salvo exceção explicitamente aprovada.
8. Placement
Todo módulo declara:
placement:
class: core
Valores suportados:
edge
core
heavy
Módulo não escolhe cluster pelo nome.
Module Manager resolve target.
9. Resources
Declarar requests realistas.
Não usar requests: none em produção.
Também não pedir recursos absurdos por segurança.
10. Node provisioning
Nunca criar node/nodegroup diretamente.
Se não houver capacidade:
deployment status = BLOCKED_CAPACITY
A plataforma resolve.
11. API
Se o módulo possui Public API, api.yaml declara prefix/routes.
Exemplo:
prefix: /v1/infinite-data
routes:
- method: GET
path: /clusters
permission: infinite-data.cluster.read
- method: POST
path: /clusters
permission: infinite-data.cluster.create
O serviço continua responsável por verificar a autorização recebida.
Cada rota tem uma audience (§0): tenant (clientes; permissão scope: tenant), platform (operadores da NNumbers; permissão scope: platform)
ou internal (serviço↔serviço; scopes). Sem o campo, ela é derivada de
visibility/auth (as rotas acima são tenant); declarada, é a forma
preferida:
routes:
- {id: items.list, audience: tenant, method: GET, path: /items, permission: demo.item.read}
- {id: platform.summary, audience: platform, method: GET, path: /platform/summary, permission: demo.platform.read}
- {id: reconcile, audience: internal, method: POST, path: /reconcile, scopes: ["demo:reconcile"]}
audience: public é recusada (audience_unsupported): não existe rota
anônima. Principal de outra audience recebe 404 igual ao de rota
inexistente (nunca 403 nem 405 que revele o caminho); permissão ausente
continua 403 (D99).
12. API versioning
Não alterar semanticamente um endpoint existente de maneira incompatível sem nova versão.
13. Internal API
Se o módulo precisar oferecer interface interna:
declarar explicitamente:
audience = internal (equivale a visibility = internal + auth = service)
Ela não é registrada no Public API Gateway.
Requer service authentication.
Nunca é descobrível por humano: fica fora do shell, do catálogo
(/v1/platform/services), do OpenAPI público, da docs, do help, de mensagem
de erro e do status.
14. IAM descriptor
Todo módulo que requer autorização declara permissions e roles.
Permissions devem refletir ações concretas.
Exemplo:
infinite-data.cluster.read
infinite-data.cluster.create
infinite-data.cluster.delete
Não criar roles vagas como superuser2 ou poweruser-final.
O scope da permissão acompanha a audience de quem a usa: rota, surface,
navegação e tópico de help tenant usam permissão scope: tenant; os
platform, scope: platform (§0).
15. Enforcement
UI pode esconder botão.
Isso é UX.
NNS deve ser autoridade final.
Exemplo:
POST cluster
verifica:
infinite-data.cluster.create
independentemente da UI.
16. tenant.owner
tenant.owner é uma role de plataforma.
Não redefinir dentro do módulo.
Ela implica acesso total ao módulo dentro daquele tenant: todas as
permissões scope: tenant, nunca as scope: platform.
17. Audit
Todo evento relevante deve usar o SDK comum.
Common envelope obrigatório.
Módulo pode possuir qualquer quantidade de trilhas.
18. audit.yaml
Exemplo:
product: infinite-data
trails:
provisioning:
events:
cluster.create:
schema_version: 1
cluster.delete:
schema_version: 1
access:
events:
permission.grant:
schema_version: 1
Não existe lista central fixa de event types.
Cada trilha pode declarar quem a consulta (§0): audience: tenant (a
atividade do próprio tenant) ou audience: platform (operadores; é o padrão
quando o campo falta, como nas trilhas acima). O catálogo do Audit é
filtrado pela audience da trilha.
trails:
activity:
audience: tenant
events: [item.create]
operations:
audience: platform
events: [item.scan]
19. Quando gerar Audit
Exemplos:
create/delete/update
permission changes
security operations
tenant provisioning
configuration changes
privileged reads quando necessário
Não gerar audit porque alguém abriu uma tela, paginou uma lista ou fez refresh normal.
20. Audit reliability
Uma mutação deve gravar:
domain change
+
outbox audit event
na mesma transação sempre que existir DB transacional local.
Worker entrega ao Audit Service.
21. Sem Kafka
SDK inicialmente envia outbox para Audit Internal API.
Não incluir client Kafka dentro dos módulos.
22. Correlation
Preservar:
request_id
correlation_id
action_id
do início ao fim.
23. Actions
Ações longas retornam:
202 Accepted
action_id
Não manter HTTP aberto por minutos.
24. Reads
Reads não criam Action.
25. Idempotency
Mutations que possam ser repetidas devem suportar:
Idempotency-Key
26. Error model
Usar erros tipados do SDK.
Exemplo:
permission_denied
not_found
conflict
validation_failed
dependency_unavailable
insufficient_capacity
27. Secrets
Nenhum secret em:
Git
NNP
NNC
Docker image
log
AuditEvent
browser
Usar referências de secret fornecidas pela plataforma: cada slot do
nns/service.yaml declara a ORIGEM do valor e a plataforma provisiona
(Barbican nnm/<módulo>/<slot> → Secret nns-<módulo>-<slot>):
db-migrator / db-runtime banco do módulo (porteiro), só com service.database
generator:random valor aleatório gerado pela plataforma
generator:ed25519-seed seed Ed25519 gerada pela plataforma
external valor de terceiro, gravado UMA vez pelo operador
cloud-tenant-credential credencial de nuvem POR TENANT (§28)
28. Cloud
Módulo que opera cloud declara um slot source: cloud-tenant-credential
com os serviços OpenStack que usa (cloud.services: block-storage,
compute, dns, image, load-balancer, network, object-store).
PCP (cloud-worker) só bootstrap: projeto do tenant + Application Credential
por (tenant, módulo), access_rules mínimas dos serviços
declarados, expiração; rotação, reparo e revogação
↓ Barbican → secret-projector
Secret do módulo uma chave <gid>.json por tenant habilitado
↓
NNS chama as APIs do OpenStack DIRETO, com a credencial do
tenant do contexto da requisição
- O PCP nunca está no caminho das operações de nuvem do módulo. Toda operação tenant-scoped (criar servidor, rede, volume, imagem) vai do cliente à Public API, dela ao NNS do módulo e dele às APIs do OpenStack (Nova, Neutron, Cinder, Glance…); um Compute novo não exige mudança no PCP, no MM nem no gateway (CONTRACTS D82).
- Rede: o NNS alcança as APIs do OpenStack pelo destino comum
openstack.public.nnumbersdoegress_catalog, declarado emdependencies(nuncanetwork.internet: o endpoint é infraestrutura; CONTRACTS §6.1). - O módulo nunca recebe credencial de admin de nuvem, de outro tenant nem
sem
access_rules. - A credencial existe para cada tenant HABILITADO para o módulo: a
plataforma habilita por
PUT /v1/modules/{id}/tenants/{gid}(CLInnconsole modules tenant-enable, CONTRACTS D92); o módulo não faz nada. - Disable do módulo, desabilitação do tenant e tombstone do tenant revogam a credencial (o tombstone chega pelo Tenant Service, D92).
- Sem a credencial de provisionamento da plataforma, o plano de deploy
mostra
capability_not_ready.
29. Product internal provisioning
Produto com tenant técnico (Imaginne, Zero) expõe a superfície de
materialização do tenant (nnumbers-org/v1, com id = Global Tenant ID;
CONTRACTS §5.5 e §5.8).
PCP chama, e só para produto do catálogo dele (produto novo = release do PCP, CONTRACTS §7).
Módulo público não precisa saber credenciais internas. Operação de nuvem nunca usa esse caminho (§28): módulo comum, inclusive um Compute, não precisa do PCP.
30. Package build
Cada repo deve fornecer:
make check
make build
make package
ou comandos equivalentes padronizados.
make package produz:
<module>-<version>.nnp
31. make check
Deve incluir, conforme aplicável:
Go tests
-race
TypeScript typecheck
lint
Vitest
OpenAPI validation
IAM descriptor validation
Audit descriptor validation
Helm lint
manifest validation
secret scan
SBOM
32. CI
CI roda esses mesmos gates.
Build local e CI devem usar os mesmos scripts.
33. Publish
NNP é enviado ao Module Manager.
Não fazer:
kubectl apply manual
helm install manual
copy JS manualmente
em produção.
34. Activation
Module Manager:
validate
deploy backend
health
register API
register IAM
register Audit
publish UI (todas as surfaces, cada uma na Console da sua audience)
activate
35. Rollback
Rollback sempre aponta para release anterior inteira.
Não rollback apenas NNC ou apenas NNS salvo operação de recuperação excepcional.
O rollback é ACOPLADO por release: todas as surfaces, o NNS, os contratos, o Status Probe, docs e help voltam juntos; não existe ciclo por surface. O Module Manager recusa (D100):
rollback_crosses_sdk_line a N-1 é de outra linha do SDK (0.6 com N na 0.7)
rollback_audience_changed um item presente nas duas releases (rota, página
de docs, tópico de help, componente de status,
trilha) muda de audience
A N-1 só é servida (arquivos e help) se for da linha 0.7 e tiver a MESMA
surface. Por isso a 1ª release de um módulo na linha 0.7 (a anterior é da
linha 0.6) não tem N-1: o
tests/conformance.yaml dela declara release: {previous: none, previous_reason: no_same_line_previous} e os itens rollback.* da
conformance saem not_applicable; o rollback acoplado volta a valer a partir
da 2ª release da linha.
36. API Gateway dynamic registration
O módulo nunca exige mudança de código do Gateway.
Se isso for necessário:
o contrato está errado ou falta capability de plataforma.
37. Rate limits
Não são obrigatórios.
Nenhum módulo precisa declarar rate limit.
O schema pode reservar:
policy_extensions: {}
para evolução futura.
Não implementar engine específica agora.
38. Usage/Metering
Reservar espaço para descriptor futuro:
meters.yaml
Não implementar Billing neste padrão inicial.
Audit e Usage são conceitos diferentes.
39. UI
NNC deve usar Cloudscape/composed components do SDK.
Não criar um design system próprio dentro do módulo.
Uma surface por audience, cada uma com o seu build e o seu base_path
(único POR AUDIENCE entre os módulos; a surface tenant e a platform do
mesmo módulo podem usar o mesmo). Nada de código, chunk ou tela de uma
audience dentro do bundle da outra.
40. Deep links
Toda rota de módulo deve suportar deep link.
41. Tenant isolation
Todos os queries/actions são tenant-scoped.
Não aceitar tenant_id arbitrário do browser como autoridade.
Contexto vem da identidade autorizada.
Na surface platform não há contexto de tenant (useTenant() é null): o
tenant técnico do realm de plataforma é só escopo técnico (Actions,
idempotência, envelope de audit) e nunca é mostrado como tenant.
42. Global Admin → Platform Console
Operações de plataforma podem existir fora de tenant context.
Devem exigir permission de plataforma explícita.
Elas moram na surface platform do módulo, na Platform Console
(platform.nnumbers.com.br), que substitui a antiga área Global Admin: ela
saiu da Tenant Console (emenda da D93; D98). Principal de plataforma é só
o do realm nnumbers-platform (issuer VERIFICADO), nunca nome de papel,
tenant_id, tenant.owner ou "*"; a permissão scope: platform exige
ainda os papéis frescos (≤ 15 min). Não há impersonação: o admin_context
foi aposentado. Nada é agrupado nem escondido por kind (§0).
43. Health
Todo NNS precisa de /live e /ready ou contrato equivalente padronizado.
44. Observability
Todo NNS usa OpenTelemetry padrão NNumbers.
Não inventar stack própria.
45. Logs
Structured logs.
Sem secrets.
Correlation ID obrigatório.
46. Portabilidade
Módulo passa se puder ser movido para outro cluster alterando apenas:
deployment target
DNS/service registry
network
secrets/config
Se código precisar mudar:
falhou o gate de portabilidade.
47. Checklist de entrega do engenheiro
Uma nova release precisa retornar:
module/version
source SHA
NNC digest (um por surface)
NNS image digest
NNP digest
Helm lint
API validation
IAM validation
Audit validation
tests
browser proof (uma por surface, na Console da audience dela)
audience proof (fase audience da conformance)
deployment health
48. Regra de ownership
O agente do módulo só edita o repo do módulo.
Se precisar de mudança no PCP, API Gateway, Console SDK ou produto real, faz handoff ao owner.
Não entra no repo alheio.
49. Não usar worktree
Worktrees não são mecanismo de coordenação entre agents.
50. Main only
O desenvolvimento operacional atual trabalha diretamente em:
main
com um único writer por repo.
Nunca dois agents escrevendo no mesmo repo simultaneamente.
Fonte canônica: docs/MODULE_STANDARD.md do repositório nnumbers-console, commit 898bb7807e98. Esta página é gerada: corrija o texto na fonte.
Esta página ajudou?
Reportar um problema nesta páginaNão envie senhas, chaves, tokens ou dados de clientes.