Pular para o conteúdo principal

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ó:

audiencequemonde vale
publicanônimo na internetcomponente 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
tenantprincipal 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
platformprincipal do realm nnumbers-platform, e só dele (issuer VERIFICADO)surfaces, rotas, docs, help, trilhas, componentes de status
internalserviço↔serviçorotas 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):

itemcampopadrã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
surfacechave em surfaces do nnp/release.yaml + spec.audience do manifestosó tenant ou platform; manifesto ≠ chave = surface_audience_mismatch
item de navegaçãoherda da surfacepermissão com o scope da audience da surface (navigation_permission_audience)
tópico de helpaudienceOBRIGATÓ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)
docsaudience 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 audittrails.<id>.audiencetenant/platform; padrão platform
componente de statusaudiencepublic/platform; padrão platform. O antigo public: bool é recusado (field_removed)
catálogo de serviços, OpenAPI, buscaderivados dos itens acimafiltrados 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.

  • tenant aparece na Tenant Console (console.nnumbers.com.br), para os clientes; platform aparece 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); o new-module --surface tenant (padrão), --surface platform, --surface tenant,platform ou --no-ui e o "Quero criar Compute" = --surface tenant eram 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.nnumbers do egress_catalog, declarado em dependencies (nunca network.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} (CLI nnconsole 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.


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.