CORTEX · doc

UNISCALE

Padrões Técnicos e de Nomenclatura

67 regras · v2.11.0 · em-evolucao · owner: plataforma · 85 KB · uniscale · sumário em JSON

Neste documento

UNISCALE — Padrões Técnicos e de Nomenclatura

Versão: 2.11.0 · Data: 2026-10-14 · Status: Em evolução · Owner: Plataforma Caminho canônico: cortex-docs/standards/UNISCALE_ENGINEERING_STANDARDS.md Contrato da fonte: cortex-docs/meta/DOC_SOURCE_CONTRACT.md (front-matter + slugs @rule)

Convenção de obrigatoriedade. Cada regra usa um destes níveis:

  • Obrigatório — deve ser seguido. Desvio só via processo de exceção (seção 31.3).
  • Recomendado — seguir salvo motivo técnico justificado, anotado onde o desvio ocorre.
  • Permitido — opção válida, livre escolha do time.

Este documento tem duas partes e dois fechamentos: Parte I — Nomenclatura (como nomear), Parte II — Modelagem e Tipos (como modelar e tipar), Padrões internos de serviço (seção 29), Fluxo de Git e entrega (seção 30) e Governança (seção 31).


PARTE I — NOMENCLATURA

1. Objetivo

Define os padrões de nomenclatura e modelagem usados nos projetos da Uniscale: repositórios, serviços, workers, conectores, adapters, bancos, schemas, tabelas, campos, tipos, containers, namespaces, eventos, variáveis de ambiente e artefatos técnicos. O objetivo é garantir consistência, previsibilidade e escalabilidade entre projetos atuais e futuros.

2. Regra geral de casing (Obrigatório)

  • kebab-case — infraestrutura, repositórios, containers, namespaces, eventos.
  • snake_case — bancos, schemas, tabelas, colunas, variáveis SQL.
  • UPPER_SNAKE_CASE — variáveis de ambiente.

Evitar: camelCase, PascalCase, nomes em português, siglas soltas, nomes de pessoas, nomes genéricos, abreviações pouco claras.

3. Princípio do leitor competente (Obrigatório)

O nome é escrito para um humano com conhecimento mínimo do domínio em que está mexendo — não para um leigo absoluto, nem para uma máquina. O nome carrega o estritamente essencial para esse leitor entender o que a coisa é, dado o contexto onde ela vive.

  • Não se repete contexto que o lugar já fornece (o nome da tabela dentro da própria coluna; a dimensão que a collection já trava; o "text" quando todo embedding é texto).
  • O nome não é uma descrição. Quando o conceito exige explicação além do essencial, isso vai para a descrição/comentário no banco (seção 9), nunca para dentro do nome.
  • Critério objetivo para disputas de nome: um humano competente nesse contexto entende? Então pare de adicionar palavra.

4. Idioma (Obrigatório)

Todos os nomes técnicos em inglês.

Corretos: wallet-ai-service, institutional-service, payment-connector, student_enrollments, created_at. Incorretos: carteira-ia-service, servico-pagamento, cad_aluno, data_criacao.

Exceção amarrada à descrição: nome de negócio institucional/legado em português só quando inevitável — e, nesse caso, a descrição/comentário no banco é obrigatória (seção 9). Um nome legado nunca anda sem documentação.

5. Repositórios (Obrigatório)

Formato: <project>-<component>.

Exemplos: cortex-docs, cortex-database, wallet-ai-service, wallet-ai-worker, uninjector-service, uninjector-frontend, uniscale-website, platform-infra.

Projeto pequeno/monolítico pode usar só o nome (uninjector); ao crescer, separa em uninjector-service, uninjector-frontend, uninjector-worker.

6. Componentes de repositório — lista controlada (Obrigatório)

Usar a lista: service, worker, frontend, infra, database, docs, shared, sdk, adapter, connector, jobs, prompts, website, cms.

Componente Uso
service API ou serviço principal
worker Processamento assíncrono / background
frontend Interface web/app
infra Kubernetes, deploy, containers, infraestrutura
database Migrations, seeds, schemas e scripts SQL
docs Documentação técnica e arquitetural
shared Bibliotecas comuns internas
sdk SDK de integração
adapter Adaptação técnica para fonte externa
connector Integração com sistema externo
jobs Rotinas agendadas ou batch
prompts Prompts, templates e versões de instruções IA
website Site institucional
cms Painel/editor de conteúdo

Componente novo entra via processo de exceção (seção 31.3).

7. Serviços, workers, conectores e adapters (Obrigatório)

  • Serviço: <domain>-service — wallet-ai-service, marketing-service, payment-service, cortex-core-service. Evitar backend, api, server, main-service, new-service.
  • Worker: <domain>-worker — wallet-ai-worker, payment-worker. Múltiplos no mesmo domínio só com motivo operacional real: wallet-ai-ingestion-worker, wallet-ai-embedding-worker.
  • Conector (comunica com sistema externo): <external-system>-connector — academic-connector, meta-ads-connector, jacad-connector, stripe-connector, mercado-pago-connector.
  • Adapter (traduz/normaliza dados, não integra): <source>-adapter — jacad-adapter, meta-ads-adapter, legacy-academic-adapter.
  • Regra prática: connector fala com fora; adapter traduz/normaliza formato.

8. Bancos, schemas e tabelas — nomenclatura (Obrigatório)

Apenas nomenclatura. Tipos, chaves e políticas estão na Parte II.

  • Banco: <domain>_db — cortex_db, wallet_ai_db, marketing_db, institutional_db. Evitar db_cortex, database, app_db, teste_db.
  • Schema: <domain> — core, wallet_ai, marketing, institutional, audit, integration.
  • Tabela: snake_case, plural, clara, em inglês — organizations, tenants, module_instances, data_sources, wallet_documents, wallet_chunks. Evitar cad_usuario, tb_cliente, tbl_config, walletData e singular (document).

9. Campos — regra de construção (Obrigatório)

Nome autoexplicativo por construção (seção 3): nem verboso, nem abreviado a ponto de exigir decifração.

Forma: [<qualificador>_]<conceito>[_<sufixo|unidade>]

  • conceito — substantivo central, em inglês, singular: name, email, amount, status.
  • qualificador (opcional) — contextualiza quando o conceito sozinho é ambíguo: user_name, lead_name, billing_email. É o que permite o mesmo conceito conviver em bancos diferentes.
  • sufixo/unidade (opcional; obrigatório quando aplicável) — desambigua tipo/unidade.

Sufixos padronizados (Obrigatório usar quando o conceito se aplica):

Sufixo Significado Exemplo
_id Identificador / referência organization_id
_at Instante (timestamptz) created_at, confirmed_at
_on Data sem hora due_on, birth_on
_count Contagem inteira retry_count
_amount Valor monetário total_amount
_seconds / _ms Duração com unidade explícita timeout_seconds
is_ / _is Booleano is_active, is_default

Equilíbrio verbosidade × abreviação:

  • Proibido abreviar a ponto de perder sentido: dt_ini, nm_usr, qtd, desc, cfg, nr, cod.
  • Proibido verbosidade redundante: não repetir o nome da tabela na coluna (em users, é name, não user_name, salvo ambiguidade real).
  • Permitido abreviações universais já consagradas e fechadas: id, url, ip, api, cpf, cnpj. Não é licença para abreviar livremente.

Regra de descrição obrigatória. Se o nome não for autoexplicativo para alguém de fora do serviço, ou se o campo carrega regra de negócio, unidade não óbvia, enum ou semântica histórica, a descrição no banco é obrigatória (COMMENT ON COLUMN).

COMMENT ON COLUMN wallet_executions.score IS
  'Confiança da execução (0.0000–1.0000). Fração, não percentual.';

(Tipos canônicos por conceito: Parte II, seção 22.)

10. Eventos — nomenclatura (Obrigatório)

Formato do nome: <domain>.<resource>.<action>, ação no passado.

Exemplos: wallet_ai.document.ingested, wallet_ai.execution.completed, marketing.lead.created, payment.transaction.confirmed.

Ações no passado: created, updated, deleted, started, completed, failed, ingested, processed, confirmed. Evitar create_document, documentEvent, wallet.ai.doc.ok.

O evento é o contrato de fronteira entre serviço e core. O nome segue esta seção; o envelope versionado é definido na Parte II, seção 28. Mudança incompatível de payload exige nova versão de schema.

11. Containers e namespaces (Obrigatório)

  • Container: nome = nome do repositório. Tags: latest, dev, hml, prd, v<major>.<minor>.<patch>. Release preferencial: v1.0.0 (ex.: wallet-ai-service:v1.0.0).
  • Namespace K8s: <environment> (dev/hml/prd) ou <project>-<environment> (cortex-prd, uninjector-dev) quando há vários projetos no mesmo cluster.

12. Variáveis de ambiente (Obrigatório)

UPPER_SNAKE_CASE: DATABASE_URL, REDIS_URL, QDRANT_URL, OPENAI_API_KEY, ENVIRONMENT, SERVICE_NAME, LOG_LEVEL.

Específicas prefixadas por domínio: WALLET_AI_VECTOR_COLLECTION, WALLET_AI_EMBEDDING_MODEL.

13. Termos proibidos em qualquer nome (Obrigatório)

  • Genéricos/temporais: new, old, test, teste, temp, final, v2, backup, main, server, api, backend, app, misc, utils, general.
  • Abreviações ilegíveis (mesma regra): dt_ini, nm_usr, qtd, desc, cfg, cod, nr.

Motivo: perdem significado rápido e dificultam manutenção.

14. Checklist e processo de exceção

Antes de criar qualquer nome técnico, validar: (1) está em inglês? (2) casing correto pro tipo? (3) indica domínio e responsabilidade? (4) um humano competente no contexto entende sem explicação extra? (5) evita termos genéricos? (6) o tipo segue o catálogo (seção 22)? (7) escala pra novos módulos? (8) não depende de nome de pessoa nem detalhe temporário? Qualquer "não" → revisar.

O processo de exceção está consolidado na seção 31.3.


PARTE II — MODELAGEM DE DADOS E TIPOS

21. Arquitetura de dados do ecossistema

O ecossistema segue o modelo banco por serviço + core consolidado por domínio.

Cada projeto/serviço é dono exclusivo do seu banco operacional (OLTP). Nenhum serviço lê ou escreve diretamente no banco de outro. A integração e a consolidação acontecem exclusivamente via eventos.

Camadas:

  • Banco de serviço (OLTP) — PostgreSQL. Sistema de registro do dado original. Modelo normalizado, orientado à operação do serviço.
  • Banco vetorial (opcional) — Qdrant. Presente apenas em serviços de IA. Armazena embeddings para busca semântica.
  • Core consolidado — PostgreSQL. Modelo consolidado por domínio de negócio, inicialmente relacional/analítico, podendo evoluir para modelagem dimensional conforme o uso. Recebe dado já tratado, alimentado por consumers que processam eventos.
  • Observabilidade — armazenamento de logs estruturados; implementação inicial: MongoDB. Transversal ao ecossistema. Não pertence a nenhum serviço.

Fluxo de dado para o core:

serviço (OLTP) → publica evento → fila (RabbitMQ) → consumer trata → core (consolidado)

Regras de fronteira (Obrigatório):

  • Um serviço nunca acessa o banco de outro serviço diretamente.
  • O evento é o contrato entre serviço e core (seção 28). Mudança incompatível no payload exige nova versão de schema.
  • O core não guarda dado bruto. Guarda o dado tratado e a identificação da origem (linhagem, seção 25), suficiente para rastrear cada linha de volta ao serviço.
  • A identidade da entidade é preservada ponta a ponta: o id gerado no serviço é o mesmo que aparece no evento e na linhagem do core.

22. Catálogo de tipos canônicos

Regra central da modelagem: todo conceito semântico tem um tipo único e obrigatório, independente do banco em que aparece. O nome do campo pode variar entre bancos (user_name, lead_name); o tipo do conceito não varia.

Proibido por construção: o mesmo conceito com tipos diferentes em bancos diferentes (timestamptz num, datetime noutro), ou tipos imprecisos (float para dinheiro, varchar para data).

A coluna "Evento (JSON)" é como o conceito viaja no payload do evento — JSON não tem timestamptz nem numeric, e o consumer precisa desserializar sem ambiguidade.

Identidade e tempo

Conceito PostgreSQL MongoDB Evento (JSON) Observação
Identificador de entidade uuid (v7) string (UUID) string (UUID) Nunca inteiro sequencial. UUIDv7 pela ordenação temporal.
Instante no tempo timestamptz date (UTC) string ISO-8601 com offset (Z) Sempre UTC. Nunca timestamp sem tz.
Data sem hora date string YYYY-MM-DD string YYYY-MM-DD Só quando a hora é irrelevante.
Duração integer (segundos) int (segundos) number (segundos) Unidade explícita no nome (_seconds).

Texto e identificadores de negócio

Conceito PostgreSQL MongoDB Evento (JSON) Observação
Nome curto (pessoa, entidade) varchar(255) string string Padrão para qualquer nome próprio.
Texto livre longo text string string Sem limite arbitrário.
Slug / código curto varchar(100) string string Identificador legível, sem espaços.
Enum / status varchar + check ou lookup table string string Vocabulário fechado. Nunca inteiro "mágico".
E-mail varchar(320) string string 320 = limite RFC. Validar na aplicação.
Telefone varchar(20) string string Armazenar em E.164 (+5545999999999).
URL text string string URLs estouram 255 com frequência.
CEP char(8) string string 8 dígitos, sem traço. É endereço, não documento.

Números e dinheiro

Conceito PostgreSQL MongoDB Evento (JSON) Observação
Dinheiro / valor monetário numeric(18,2) Decimal128 string (decimal) Nunca float/double. Em JSON, string para não perder precisão.
Quantidade inteira integer / bigint int / long number bigint quando puder estourar 2³¹.
Percentual / fração numeric(5,4) Decimal128 string (decimal) Guardar fração (0.1500 = 15%), não o "15".
Contador bigint long number

Booleano e estruturados

Conceito PostgreSQL MongoDB Evento (JSON) Observação
Verdadeiro/falso boolean bool boolean Nunca 0/1, nunca 'S'/'N', nunca varchar.
Dado semiestruturado jsonb objeto nativo objeto jsonb, nunca json no Postgres.
Lista de valores simples jsonb ou tabela filha array array Tabela filha se precisar consultar/indexar itens.
Metadados livres jsonb objeto objeto Campo metadata padrão (seção 23).

Regra de extensão (Obrigatório): conceito novo que não está no catálogo não escolhe tipo ad-hoc — é adicionado ao catálogo via atualização da doc (seção 31). O catálogo é a fonte única de verdade dos tipos.

22.1. Regra <conceito>_type + <conceito>_value (Obrigatório)

Sempre que um conceito tiver múltiplos formatos/tamanhos válidos (documentos, códigos por país), o padrão é um par tipo + valor com vocabulário de tipo fechado, em vez de uma coluna por variação. Mantém o campo autoexplicativo e validável, e cobre variações futuras sem nova coluna.

22.2. Documentos pessoais e de identificação (Obrigatório)

Documentos não são um campo único. São um par tipo + valor + país, aplicação direta da regra 22.1. Cobre documento brasileiro e estrangeiro (inclusive passaporte) sem coluna nova por tipo.

Campo Tipo Descrição
personal_document_type varchar(20) + check Tipo do documento. Vocabulário fechado (abaixo).
personal_document_value varchar(40) Valor apenas com caracteres significativos (dígitos e/ou letras), sem máscara.
personal_document_country char(2) País emissor em ISO-3166-1 alpha-2 (BR, US, PT).

Vocabulário de personal_document_type (fechado — estender só via seção 31.3):

cpf          → pessoa física BR (11 dígitos)
cnpj         → pessoa jurídica BR (14 dígitos)
rg           → identidade BR
passport     → passaporte (qualquer país)
foreign_id   → documento de identidade estrangeiro genérico
ssn          → social security (US)
other        → exige descrição obrigatória do que é

A validação de formato por tipo fica na aplicação (CPF = 11 dígitos, CNPJ = 14, passaporte = regra do país). O banco garante o tipo; a aplicação garante o formato conforme personal_document_type. CEP não é documento pessoal (é endereço) e segue o catálogo (seção 22).

23. Colunas obrigatórias e padrão de toda tabela

Toda tabela de banco de serviço (OLTP) nasce com um conjunto base de colunas, para que qualquer tabela, em qualquer projeto, tenha a mesma espinha.

23.1. Colunas base (Obrigatório em toda tabela)

Coluna Tipo Regra
id uuid (v7) PK. Gerada pela função/biblioteca oficial de geração da plataforma (seção 23.7). Nunca sequencial. Preservada ponta a ponta.
created_at timestamptz UTC. Preenchida na criação, imutável. Default now().
updated_at timestamptz UTC. Atualizada a cada alteração. Obrigatória em todas as tabelas.

23.2. Soft delete (Obrigatório em tabelas de negócio, com política explícita)

Coluna Tipo Regra
deleted_at timestamptz nullable NULL = ativo. Preenchido = logicamente excluído.
  • O padrão é soft delete em tabelas de negócio (entidades com ciclo de vida e valor de auditoria). DELETE físico nelas é exceção e precisa de justificativa registrada.
  • Tipos de tabela que podem usar política diferente: eventos técnicos, staging/landing temporário, snapshots, e tabelas puramente associativas sem valor de auditoria do vínculo. (Logs já não têm soft delete — expiram por TTL, seção 27.4.)
  • A dispensa de soft delete é decisão explícita e documentada por tabela (na descrição da tabela), não uma categoria que dispensa pensar. Atenção: uma tabela associativa que representa um vínculo de negócio (ex.: user_roles) frequentemente precisa de soft delete, porque "quando o vínculo foi removido" é auditoria.
  • Toda leitura de negócio filtra deleted_at IS NULL. Recomenda-se encapsular em view ou camada de acesso.
  • Índices únicos em tabela com soft delete devem ser parciais (WHERE deleted_at IS NULL).

23.3. Multi-tenancy (Obrigatório quando a tabela é multi-tenant)

Coluna Tipo Regra
tenant_id uuid Presente desde o dia 1 em toda tabela multi-tenant, independente do modo de isolamento.
  • O modo de isolamento varia por cliente: tenant_id em coluna (padrão, cliente sem exigência de sigilo) ou schema por tenant (cliente que exige isolamento de dados).
  • Mesmo no modo schema-por-tenant, tenant_id permanece na tabela. Permite migrar de coluna → schema isolado (ou inverso) sem alterar DDL — muda-se o roteamento.
  • Tabela não multi-tenant (catálogos globais) omite tenant_id, e a omissão é decisão explícita, anotada na descrição da tabela.

23.4. Colunas opcionais padronizadas (Recomendado)

Quando o conceito existir, usar estes nomes e tipos, não variações:

Coluna Tipo Uso
organization_id uuid Referência à organização, quando o domínio tem organização acima de tenant.
status varchar + check / lookup Estado de ciclo de vida. Vocabulário fechado. Nunca inteiro "mágico".
created_by / updated_by uuid Autor da ação (referência a users do mesmo banco — seção 23.6).
metadata jsonb Dados livres/extensão sem migração. Não usar para dado que merece coluna própria.

23.5. Ordem e consistência (Recomendado)

  • Ordem sugerida: id → colunas de negócio → status → metadata → tenant_id/organization_id → auditoria (created_at, updated_at, deleted_at, created_by, updated_by).
  • Nome e tipo dessas colunas são fixos no ecossistema. Nunca dt_criacao, data_criacao ou created — é sempre created_at: timestamptz.

23.6. Autoria e tipo de conta (Obrigatório)

  • created_by / updated_by (uuid) referenciam sempre um registro real na tabela users do mesmo banco onde a linha vive — nunca NULL para "sistema", nunca UUID fora de tabela. Como cada banco tem seu próprio users, isto não cria FK cruzando fronteira de serviço (seção 24.3): o ator é sempre local ao banco.
  • Ações automáticas (consumers, jobs, connectors) são atribuídas a uma conta de serviço: um usuário real, sem acesso a interfaces humanas. Permissionamento é responsabilidade da aplicação, não deste documento.
  • Isto vale também para o core: quando um consumer grava uma linha consolidada, o created_by aponta para a conta de serviço do core (account_type = 'service') — "o pipeline gravou isto". O core tem seu próprio users, como qualquer banco.
  • A tabela users distingue conta humana de conta de serviço via account_type (varchar + check), vocabulário fechado: human, service.

23.7. Geração de UUIDv7 (Obrigatório)

UUIDv7 é obrigatório para id (não preferencial) — a ordenação temporal do v7 importa para PK e índice. Para que isto não dependa do que cada dev tem à mão:

  • A plataforma define a função/biblioteca oficial de geração de UUIDv7 por stack (linguagem/runtime). O dev usa a função oficial, não escolhe lib ad-hoc nem inventa geração.
  • "Minha biblioteca não suporta v7" não é justificativa para cair em v4 — é motivo para usar a função oficial da plataforma. UUIDv4 só é aceitável como exceção documentada (seção 31.3) em caso de incompatibilidade real do banco (não da lib da aplicação).
  • A geração pode ser na aplicação ou no banco, desde que pela via oficial. Misturar fontes de geração no mesmo ecossistema é o que a função oficial existe para evitar.

24. Constraints, índices e chaves estrangeiras

Nome de constraint e índice é obrigatório ser explícito. Nunca deixar o banco gerar nome automático.

24.1. Esquema de prefixos (Obrigatório)

Prefixo Objeto Formato
pk_ Primary key pk_<table>
fk_ Foreign key fk_<table>__<referenced_table>
uq_ Unique constraint uq_<table>__<columns>
ux_ Unique index (parcial/funcional) ux_<table>__<columns>
ix_ Index comum ix_<table>__<columns>
ck_ Check constraint ck_<table>__<rule>

Separadores: _ entre palavras, __ entre blocos (tabela, referência, colunas).

24.2. Exemplos

CONSTRAINT pk_wallet_documents PRIMARY KEY (id)

CONSTRAINT fk_wallet_documents__organizations
  FOREIGN KEY (organization_id) REFERENCES organizations (id)

-- Múltiplas FKs para a mesma tabela: desambiguar pela coluna
CONSTRAINT fk_messages__users__sender
  FOREIGN KEY (sender_id) REFERENCES users (id)
CONSTRAINT fk_messages__users__recipient
  FOREIGN KEY (recipient_id) REFERENCES users (id)

CONSTRAINT uq_organizations__slug UNIQUE (slug)

CONSTRAINT ck_users__account_type
  CHECK (account_type IN ('human', 'service'))

CREATE INDEX ix_wallet_documents__tenant_id
  ON wallet_documents (tenant_id);

-- Unique index PARCIAL (soft delete)
CREATE UNIQUE INDEX ux_users__email
  ON users (email) WHERE deleted_at IS NULL;

24.3. Regras de chave estrangeira (Obrigatório)

  • Coluna <x>_id que referencia tabela no mesmo banco tem FK declarada, apontando para a id (PK) dela. Referência local implícita (coluna _id sem FK, havendo a tabela) é proibida.
  • Coluna <x>_id que referencia entidade de outro serviço (ex.: organization_id num banco que não hospeda organizations) não tem FK — a tabela não existe localmente. É um identificador de referência externa: o id preservado (seção 21), validado pela aplicação/consumer, nunca por FK física.
  • Reforçar via descrição obrigatória (seção 9) quando for referência externa, ex.: COMMENT ON COLUMN ... IS 'Referência externa a organizations (institutional). Sem FK local.'.
  • ON DELETE: padrão RESTRICT. CASCADE só com justificativa (raramente desejável com soft delete). SET NULL apenas em FK nullable cuja ausência é semanticamente válida.
  • FK não cruza fronteira de serviço (seção 21).

24.4. Regras de índice

  • Obrigatório: toda FK tem índice na coluna que a origina. Postgres não cria automaticamente.
  • Obrigatório: índice único em tabela com soft delete é parcial (WHERE deleted_at IS NULL), prefixo ux_.
  • Recomendado: indexar tenant_id em tabelas multi-tenant de alto volume; considerar índice composto liderado por tenant_id.
  • Não criar índice "por precaução" — tem custo de escrita.

24.5. Limite de tamanho de nome (Obrigatório)

PostgreSQL trunca identificadores em 63 bytes. Quando o nome padrão passar disso, abreviar o bloco de colunas (mantendo prefixo e tabela legíveis) e documentar a abreviação na migration. Nunca abreviar a ponto de perder o sentido.

25. Linhagem de dados no core

O core recebe dado já tratado via consumers, reorganizado em modelo consolidado por domínio (seção 21). Como o dado foi transformado e o bruto não é guardado, toda tabela do core carrega colunas de linhagem.

25.1. Colunas de linhagem (Obrigatório em toda tabela do core)

Coluna Tipo Regra
source_system varchar(50) Serviço de origem, alinhado ao <domain> (wallet_ai, marketing).
source_id uuid O id preservado da entidade na origem (seção 21).
source_event_id uuid id do evento que produziu/atualizou a linha (seção 28).
occurred_at timestamptz UTC. Quando o fato aconteceu na origem (tempo de negócio).
ingested_at timestamptz UTC. Quando o consumer gravou no core (tempo de carga).
transform_version varchar(20) + check SemVer da lógica de transformação aplicada.
CONSTRAINT ck_<table>__transform_version
  CHECK (transform_version ~ '^\d+\.\d+\.\d+$')

25.2. Por que estas colunas, e não o dado bruto

source_system + source_id respondem "de onde veio e o que é na origem" — e como o id é preservado, dá para achar o registro original no banco de serviço. source_event_id responde "por qual evento chegou". occurred_at vs ingested_at separam tempo de negócio de tempo de carga. transform_version responde "sob qual lógica foi tratado". Custo: poucas dezenas de bytes por linha; não duplica o dado de negócio.

25.3. Identidade no core (Obrigatório)

  • id (PK do core) é sempre gerado no core, UUIDv7, exclusivo do core. Nunca reusa source_id.
  • source_id é sempre o id preservado da origem, em coluna própria.
  • São sempre duas colunas distintas, sem exceção.
  • Combinação (source_system, source_id) é única por entidade — uq_<table>__source parcial, respeitando soft delete.

25.4. Idempotência da ingestão (Obrigatório)

  • A gravação no core é idempotente: reprocessar o mesmo source_event_id para a mesma (source_system, source_id) não cria duplicata (upsert por chave de origem).
  • source_event_id é o que permite detectar "já processei este evento". Sem ele, re-entrega vira duplicação silenciosa.

25.5. Catálogo de versões de transformação (Obrigatório)

A vigência de cada versão não se repete na tabela de dados — vive num catálogo no schema do core.

Tabela transform_versions (uma linha por versão, por origem):

Coluna Tipo Regra
id uuid (v7) PK do core.
source_system varchar(50) A qual pipeline/origem a versão se aplica.
version varchar(20) SemVer. Único por source_system.
effective_at timestamptz UTC. Desde quando a versão passou a valer.
description text O que mudou (obrigatório — memória do pipeline).
created_at timestamptz Auditoria padrão.
  • Unicidade: uq_transform_versions__source_version em (source_system, version).
  • Cada linha de dado carrega transform_version (o número); vigência e descrição ficam aqui.
  • Reprocessamento: nova versão é registrada no catálogo com seu effective_at antes de o consumer começar a gravar dados sob ela.

25.6. Escopo

Colunas de linhagem não vão para os bancos de serviço (lá o dado é o original). Linhagem é exclusiva do core e de camadas a jusante.

26. Qdrant (banco vetorial)

Bancos vetoriais existem apenas em serviços de IA. O Qdrant não é fonte de verdade — armazena embeddings para busca semântica e referencia o registro real no banco de serviço. Identidade preservada e tenant por filtro valem aqui também.

26.1. Nomenclatura de collection (Obrigatório)

Como vários modelos de embedding coexistem e vetores de modelos/modalidades diferentes não são comparáveis, o nome carrega o que torna a collection única e incomparável:

<domain>__<modality>__<content>__<model>
  • domain — domínio do serviço (wallet_ai).
  • modality — text, image, audio. Vocabulário reservado; entra no nome porque texto/imagem/áudio não se comparam entre si.
  • content — o que está vetorizado (chunks, documents, faqs).
  • model — modelo que gerou os vetores, normalizado e enxuto (openai-3-small, cohere-v3). Sem text-embedding (todo embedding é embedding) e sem dimensão (a collection já a trava fisicamente).

Exemplos:

wallet_ai__text__chunks__openai-3-small
wallet_ai__image__pages__clip-vit-l14

Trocar de modelo = nova collection, nunca sobrescrever. Vetores de modelos diferentes não são comparáveis; misturá-los corrompe a busca.

26.2. Multi-tenancy (Obrigatório)

  • Padrão: uma collection por domínio/modalidade/conteúdo, com tenant_id no payload e índice de payload nesse campo. Toda query de negócio filtra por tenant_id.
  • Exceção (mesmo critério da seção 23.3): cliente que exige isolamento físico ganha collection dedicada.
  • tenant_id está no payload desde o início, mesmo em collection dedicada.

26.3. Identidade do point (Obrigatório)

  • O id do point é o id (UUIDv7) do registro de origem que ele representa (o chunk no Postgres). Identidade preservada.
  • Quando uma entidade gera múltiplos vetores (documento partido em chunks), o point é o id do chunk; o documento é referenciado no payload.
  • O id do chunk já é determinístico e idempotente para o upsert — não se constrói point_id por hash de coordenadas (collection/doc/chunk). O mesmo id vale inclusive em blue-green (collection nova é outra collection, sem colisão).

26.4. Payload (Obrigatório — mínimo)

Guarda só o necessário para filtrar e referenciar, nunca conteúdo de negócio rico (que desatualiza contra o Postgres):

Campo Tipo Função
tenant_id UUID (string) Filtro de tenant. Índice de payload obrigatório.
source_id UUID (string) Referência ao registro de origem no Postgres.
document_id UUID (string) Referência ao documento-pai, quando aplicável.
status string Filtro de estado, se a busca precisar excluir inativos.
  • Conteúdo rico não vai no payload — após o match, busca-se no Postgres pelo source_id.
  • Campos usados em filtro têm índice de payload declarado.
  • Tipos seguem o catálogo (seção 22).

26.5. Distance metric (Obrigatório registrar)

A métrica (Cosine, Dot, Euclid) é definida na criação da collection e deve casar com o modelo de embedding (texto moderno: geralmente Cosine). É invisível no nome e crítica — registrada na documentação/catálogo de collections.

26.6. Conversão e modalidade (Obrigatório)

  • Mídia não-textual destinada à busca por conteúdo é convertida a texto na ingestão — OCR para imagem, transcrição (speech-to-text) para áudio/vídeo — e indexada na collection de texto. Caminho padrão do RAG.
  • Collections de outras modalidades (image, audio) servem à busca por semelhança da própria mídia (aparência, som), não por conteúdo. O vocabulário é reservado desde já; cada collection só é criada quando o caso de uso de semelhança daquela modalidade existir de fato.
  • Uma mesma origem pode produzir vetores em modalidades distintas; todos compartilham o mesmo document_id no payload.
  • A escolha de qual collection consultar é da aplicação (roteamento por intenção, ou LLM como seletor de ferramenta), nunca inferida pelo banco vetorial.

26.7. Configuração via ambiente (Obrigatório)

Nome de collection, modelo e dimensão são configuração (seção 12), não hard-code: WALLET_AI_VECTOR_COLLECTION, WALLET_AI_EMBEDDING_MODEL, WALLET_AI_EMBEDDING_DIMENSION.

26.8. Sincronização Postgres → Qdrant via outbox (Obrigatório)

O Postgres é a fonte da verdade; o Qdrant é reconstruível (seção 26). Manter os dois consistentes não se faz por dual write direto (gravar no Postgres e no Qdrant em chamadas separadas) — isso diverge silenciosamente em falha parcial: a busca volta incompleta ou stale, sem erro.

  • Outbox transacional: a gravação do registro de origem (ex.: chunk) e uma linha de outbox commitam na mesma transação Postgres. A intenção de sincronizar é atômica com o dado.
  • Worker idempotente por domínio (<domain>-ingestion-worker, seção 7) faz polling do outbox e aplica no Qdrant com retry/backoff e DLQ (seção 29.3). É o único que escreve no banco vetorial daquele domínio.
  • O evento de outbox é ponteiro, não payload. Diz "o registro X mudou"; o worker relê o estado atual no Postgres e deriva a ação (presente → upsert; ausente/tombstoned → delete). Reprocessar ou processar fora de ordem sempre converge, sem stale. Isto difere do contrato serviço → core (seção 28), que é event-carried state transfer na fronteira externa via RabbitMQ; a sincronização vetorial é intra-serviço e usa releitura.
  • Idempotência pela identidade preservada do point (point_id = id do registro, seção 26.3) — não por hash de coordenadas.
  • Update faz diff por hash de conteúdo (upsert do que mudou + delete do que sumiu, evitando vetor órfão). Troca de modelo é blue-green (collection nova, backfill do Postgres, troca de alias, drop da velha).
  • Rede de segurança obrigatória: job de reconciliação (contagem Postgres × Qdrant, re-enfileira drift), métrica de lag do outbox e DLQ para o que esgota retry.

Desenho de referência (tabelas, índices, fluxo): cortex-docs/architecture/RAG_AND_INGESTION.md.

27. MongoDB (observabilidade — logs)

O MongoDB é infraestrutura transversal, não banco de serviço nem core. Escopo: log estruturado de aplicação.

27.1. Escopo (Obrigatório)

  • Armazena logs de aplicação estruturados.
  • Métricas e tracing não vão para o Mongo. Vivem em ferramental dedicado (métricas: stack de séries temporais tipo Prometheus/Grafana; tracing: OpenTelemetry + Tempo/Jaeger).
  • Auditoria de negócio (quem mudou o quê numa entidade) não é log de observabilidade — vive no banco do serviço (created_by/updated_by ou tabela de auditoria no Postgres).

27.2. Collections por sinal (Obrigatório)

  • Separação por tipo de sinal, não por serviço. Collection: logs.
  • service e tenant_id são campos indexados no documento, não fronteira de collection (coerente com 26.2 e 23.3).
  • Não criar collection por serviço (mistura ritmos de expiração e não escala em número).

27.3. Estrutura mínima do documento de log (Obrigatório)

Campo Tipo Função
timestamp date (UTC) Instante do log. Indexado (base do TTL).
level string Severidade. Vocabulário fechado: debug, info, warn, error, fatal.
service string Serviço de origem, alinhado ao <domain>. Indexado.
environment string dev / hml / prd.
message string Mensagem legível.
tenant_id string (UUID) Quando atribuível a um tenant. Indexado.
trace_id string Correlação com o tracing externo, quando houver.
context object Dados estruturados livres (equivalente a metadata). Sem PII desnecessária.

trace_id é referência ao trace no ferramental dedicado, não o trace em si — a ponte entre log e tracing sem duplicar dado.

27.4. Retenção / TTL (Obrigatório)

  • A collection logs tem índice TTL sobre timestamp — documentos expiram após N dias.
  • N é configurável por ambiente (ex.: prd retém mais que dev), definido por política operacional. A doc fixa que existe TTL; o número é decisão operacional registrada.
  • Log não tem soft delete — expira de verdade (hard expire). Soft delete é padrão de dado de negócio, não de telemetria.

27.5. O que NÃO colocar em log (Obrigatório)

  • Segredos (tokens, senhas, chaves) — nunca.
  • PII além do necessário para diagnóstico — minimizar; documento pessoal, CPF etc. não vão para log.
  • Payload de negócio completo — log referencia por id, não copia o registro.

28. Contrato de evento

O evento é a fronteira entre serviço e core. O nome segue a seção 10; esta seção define o envelope e as regras de contrato.

28.1. Princípios (Obrigatório)

  • Estado completo no payload (event-carried state transfer): o evento carrega o estado suficiente para o consumer gravar sem consultar o serviço. O core nunca chama o serviço de volta.
  • Identidade preservada: os ids no evento são os mesmos do serviço e seguem para o core inalterados.
  • Idempotência: todo evento tem event_id único (seção 25.4). Re-entrega não duplica dado.
  • Contrato versionado: schema_version em SemVer. Mudança incompatível incrementa o MAJOR.

28.2. Envelope padrão (Obrigatório)

Todo evento é um JSON com envelope fixo + data. Tipos seguem o catálogo (seção 22).

Campo (envelope) Tipo Função
event_id string (UUID v7) Identificador único do evento. Vira source_event_id no core.
event_name string Nome do evento (seção 10).
schema_version string (SemVer) Versão do schema deste evento.
occurred_at string (ISO-8601 UTC) Quando o fato aconteceu na origem. Vira occurred_at no core.
tenant_id string (UUID) Tenant do evento, quando o domínio é multi-tenant.
source_system string Serviço produtor. Vira source_system no core.
source_id string (UUID) id da entidade na origem. Vira source_id no core.
data object Estado completo da entidade. Estrutura definida por event_name + schema_version.
{
  "event_id": "0190a7c2-...-v7",
  "event_name": "wallet_ai.document.ingested",
  "schema_version": "1.0.0",
  "occurred_at": "2026-05-26T14:32:10Z",
  "tenant_id": "0190a1b2-...",
  "source_system": "wallet_ai",
  "source_id": "0190a3d4-...",
  "data": {
    "document_id": "0190a3d4-...",
    "title": "Conta de energia - maio",
    "status": "ingested",
    "personal_document_type": "cnpj",
    "personal_document_value": "12345678000199",
    "personal_document_country": "BR",
    "total_amount": "247.00"
  }
}

Tipos no data seguem o catálogo (total_amount como string decimal; documento como tipo+valor+país da seção 22.2). O evento não inventa tipo próprio.

28.3. Mapeamento evento → core (Obrigatório)

Envelope Coluna no core
event_id source_event_id
occurred_at occurred_at
source_system source_system
source_id source_id
(carga do consumer) ingested_at = now() na gravação
(lógica do consumer) transform_version (não vem do evento)

transform_version é da lógica do consumer, não do evento — o evento diz o que aconteceu; a versão de tratamento é do core.

28.4. Versionamento do contrato (Obrigatório)

  • schema_version em SemVer, coerente com transform_version: MAJOR = mudança incompatível no data; MINOR = adição compatível (campo novo opcional); PATCH = correção sem mudança estrutural.
  • Consumer tolera campos desconhecidos (forward-compatible): MINOR novo no produtor não quebra consumer antigo.
  • Mudança incompatível (MAJOR) exige coordenação: consumer entende a nova versão antes do produtor publicá-la. Coexistência prolongada de duas versões maiores no mesmo fluxo recorre a versão no nome do evento (seção 10), registrada como exceção (seção 31.3).
  • O schema de cada event_name por versão é documentado (catálogo de eventos). Evento sem schema documentado não vai para produção.

28.5. Convenções RabbitMQ (Recomendado)

  • Exchange por domínio produtor, tipo topic: wallet-ai.
  • Routing key = event_name: wallet_ai.document.ingested.
  • Queue por consumer/finalidade: <consumer>__<purpose> — core-ingestion__wallet-ai-documents.
  • Dead-letter: toda queue de consumo tem <queue>__dlq (obrigatória — seção 29.3).

28.6. Ordem e garantias (Obrigatório)

Entrega é at-least-once (consumer idempotente por event_id — seção 25.4). A correção da sequência é garantida pelo estado da entidade, não pela ordem de chegada. Duas camadas, duas responsabilidades:

  • Validação de sequência é do serviço de origem, não do core. Regras como "refund só após pagamento" são lógica de negócio do serviço (ex.: payment-service), validadas no banco do próprio serviço antes da publicação. O serviço publica fatos já decididos.
  • O core não valida sequência. Consumers do core registram fatos já tratados. Aplicam última versão vence por occurred_at; eventos fora de ordem convergem ao estado correto. O core não segura, não rejeita por pré-condição, não arbitra transição.
  • Por isso as filas do core são indiferentes à ordem. Não há necessidade de fila ordenada para alimentar o core.
  • Cronologia no dashboard: visão em tempo real ordena por occurred_at na consulta ao core, não pela ordem de chegada.

28.7. Publicação não exige assinante (Obrigatório)

Num exchange topic, "ninguém tem bind nesta routing key" é estado normal e esperado — não é falha. O publicador não sabe quem ouve (ECOSYSTEM §5), e o consumidor pode simplesmente ainda não existir: o serviço que vai tratar aquele fato entra em produção semanas ou meses depois.

  • mandatory fica desligado na publicação de fato de negócio. Com mandatory=True, o broker devolve a mensagem como não-roteável, o relay do outbox interpreta como falha, e o serviço acumula fatos failed por falta de plateia — recriando o acoplamento publicador→consumidor que o pub/sub existe para eliminar.
  • A durabilidade não depende disso. Quem garante que o broker aceitou e persistiu são os publisher confirms (§29.3), que permanecem obrigatórios. Aceito sem assinante é sucesso.
  • Exceção, e só ela: canal dedicado com um consumidor conhecido e declarado, em que a ausência de fila é falha real e a mensagem não pode ser dada como entregue. Aí mandatory=True é obrigatório. Caso vivo: o canal direct de PII do tracking-service para o identity-service (ecosystem.services.tracking-pii-handoff) — dar a entrega como feita ali apagaria a única cópia cifrada do dado e perderia o lead.
  • A régua é a cardinalidade do destino: fato para quem quiser ouvir → sem mandatory; entrega para um destinatário nomeado → com mandatory.

Origem: encontrado no primeiro teste de integração real entre tracking e identity, com o publisher do cortex-template. O fato tracking.event.captured falhava porque cortex-service e marketing-connector ainda não existem para dar bind — nada de errado com o fato, apenas não havia quem ouvisse.

29. Padrões internos de serviço (mensageria e estado)

A seção 28 cobre a fronteira serviço → core. Esta seção fixa os padrões transversais que todo serviço segue internamente. A regra de negócio interna é livre; o que está aqui é obrigatório para todos.

29.1. Tecnologia de fila padrão (Obrigatório)

  • RabbitMQ é a tecnologia padrão de mensageria do ecossistema — tanto para integração serviço → core quanto para filas internas. Todo serviço usa RabbitMQ por default.
  • Não introduzir Kafka, Redis Streams ou outra mensageria por iniciativa de projeto. Tecnologia divergente por serviço multiplica conhecimento operacional, monitoração e modos de falha.
  • Adoção ou troca de tecnologia de mensageria é decisão de plataforma (vale para todo o ecossistema), registrada como exceção (seção 31.3) — nunca decisão isolada de um serviço.

29.2. Nomenclatura de mensageria (Obrigatório)

Segue o mesmo padrão do restante do projeto — sem dialeto próprio de fila:

  • Exchange: por domínio, kebab-case, tipo topic (payment, wallet-ai).
  • Routing key: estrutura hierárquica do evento (seção 10) — payment.transaction.confirmed.
  • Queue: <consumer>__<purpose> em kebab-case (payment-state__transactions, core-ingestion__wallet-ai-documents).
  • Dead-letter queue: <queue>__dlq, obrigatória para toda queue de consumo.
  • Filas internas seguem a mesma nomenclatura das de integração.

29.3. Configuração por tipos básicos (Obrigatório)

Parâmetro Padrão Regra
Durabilidade durável Filas e mensagens sobrevivem a restart do broker. Não-durável só para sinal efêmero justificado.
Acknowledgement manual (ack após sucesso) Nunca auto-ack em consumer de negócio — perde mensagem em falha.
Entrega at-least-once Consumer idempotente (seção 25.4) é pré-requisito, não opcional.
Retry com limite + backoff Tentativas finitas; esgotadas, vai para DLQ. Retry infinito é proibido.
DLQ obrigatória Toda queue de consumo tem __dlq.
Prefetch definido explicitamente Não usar o default cego do cliente; dimensionar conforme o worker.

Exceção a qualquer item é registrada (seção 31.3). Salvo exceções não mapeadas significa documentadas, não improvisadas.

29.4. Máquina de estados interna (Obrigatório quando há ciclo de vida)

  • Entidade com ciclo de vida (pedido, pagamento, documento) usa um campo status (enum / vocabulário fechado — seções 22, 23.4), nunca flags booleanas independentes (flag_pago, flag_enviado) que podem se contradizer.
  • O serviço valida as transições permitidas internamente — regra de negócio do serviço, validada no banco do próprio serviço.
  • A lógica específica de cada máquina de estados é livre por serviço; esta doc exige apenas que exista um status único como árbitro.
  • Fronteira: validação de sequência é responsabilidade do serviço de origem. O core não valida transição nem segura evento por pré-condição (seção 28.6).

29.5. Stack padrão de serviço (Obrigatório)

  • Python é a linguagem padrão dos backends do ecossistema — serviços, workers e connectors. Serviços HTTP usam FastAPI; workers/jobs sem HTTP usam Python sem framework web.
  • Versão do Python — preferir a mais nova, descer só o necessário. O alvo é a 3.12. Um serviço só usa versão menor quando um pacote obrigatório dele ainda não suporta a 3.12 — e, nesse caso, desce um degrau de cada vez (3.11, depois 3.10…) até a mais nova que o pacote aceita, nunca direto pra uma antiga. A versão escolhida e o motivo (qual pacote forçou) ficam registrados no pyproject.toml do serviço. Não é decisão de gosto: é o piso que a dependência impõe.
  • Não introduzir outra linguagem ou framework de backend por iniciativa de projeto. Mesma razão da tecnologia de fila (§29.1): stack divergente por serviço multiplica conhecimento operacional, build, imagem base, observabilidade e modos de falha.
  • Adoção ou troca de stack é decisão de plataforma (vale para todo o ecossistema), registrada como exceção (§31.3) — nunca decisão isolada de um serviço.
  • Frontends (componente frontend, §6) seguem a própria stack de UI; esta regra é do backend (serviço/worker/connector).
  • Desacoplamento de fornecedor é design, não detalhe: todo fornecedor externo (bucket, gateway, modelo, CRM) fica atrás de uma porta/interface do serviço, nunca amarrado ao núcleo — trocar ou somar fornecedor é somar uma implementação, sem tocar no núcleo. O desacoplamento se realiza de duas formas, conforme a natureza do externo:
    • Driver/adapter in-process (selecionado por variável de ambiente) — para substrato commodity síncrono, sem lógica de negócio, sem webhook, sem mão-dupla. O serviço encapsula o fornecedor dentro de si. Ex.: porta de armazenamento do storage-service (MinIO/S3/GCS por STORAGE_DRIVER); embedding-service encapsulando o modelo.
    • Connector separado (componente próprio — §6, §7) — para sistema de negócio com API rica, integração mão-dupla dirigida por evento/webhook, SLA e estado próprios. Ex.: payment-connector por gateway; academic-connector (jacad); crm-connector (bitrix).
    • Régua: músculo commodity síncrono → driver in-process; sistema de negócio com integração mão-dupla → connector. Não criar connector separado para substrato burro — é over-engineering (um deploy, um hop de rede e um modo de falha a mais para mediar zero lógica de negócio).

29.6. Autenticação serviço-a-serviço (Obrigatório)

Toda chamada entre serviços internos é autenticada. Estar na rede privada e "não exposto à internet" é exposição, não autenticação — sem autenticar, qualquer pod que alcance a rede lê o que o serviço serve; num serviço que devolve PII (o identity, ecosystem.services.identity-read-levels) isso é vazamento e torna não-confiável o registro de acesso que a LGPD exige.

  • O ator viaja dentro da credencial, nunca num cabeçalho livre. Um header como x-actor, que qualquer chamador escreve (e cai em "unknown" por default), não identifica ninguém — quem grava o log de acesso poderia forjar o nome. A identidade do serviço chamador e do ator humano por trás dele vêm assinadas na credencial, não em campo editável do request.
  • A chave que assina/verifica a credencial mora em local seguro — KMS/vault ou secret do orquestrador —, nunca no banco, nunca no código, nunca em variável de ambiente commitada. Comprometer essa chave é personificar qualquer serviço.
  • Escrita externa continua por fila (§28; ex.: ecosystem.services.identity-write-by-queue): esta regra é sobre quem chama uma leitura/consulta síncrona, não reabre escrita HTTP.
  • O mecanismo específico é decisão de plataforma/infra, escolhido uma vez para o ecossistema (não por serviço): está escolhido e registrado na §29.8 — JWT ES256, ator no claim act, privada em KMS/vault com pública por JWKS. Desvio dela é exceção por §31.3. O que esta regra fixa e não admite exceção: chamada entre serviços autenticada e ator dentro da credencial.

29.7. Sessão do usuário no front: armazenamento de token (Obrigatório)

A §29.6 é sobre a chamada serviço↔serviço. Esta é sobre a sessão do usuário humano entre o front (painel, §6) e a API. Credencial de sessão roubada personifica a pessoa, e o vetor real num front é o XSS: script que executa na página alcança tudo que o JavaScript alcança. A postura abaixo é padrão do ecossistema — todo front a segue, para nenhum re-derivar autenticação mais fraca por conta própria.

  • O access token vive só em memória, nunca em armazenamento persistente do navegador. Fechar a aba encerra o token de curta duração. Guardá-lo em localStorage/sessionStorage o entrega a qualquer XSS sem ganho — ele é curto e renovável.
  • O refresh token é rotativo, com detecção de reuso que derruba a família. Cada renovação rotaciona o token; o uso de um token já gasto é sinal de roubo e derruba toda a família de sessão — as duas sessões caem. O roubo deixa de ser silencioso. Isto não admite exceção.
  • A sessão tem teto de duração desde o login, independente da renovação. Token roubado tem validade limitada por construção, não só pela expiração do access. O valor do teto vive no template/config, não nesta regra.
  • A escolha de onde o refresh mora é guiada por ameaça, não por conveniência, e é decisão de plataforma. O alvo é fora do alcance do JavaScript — cookie HttpOnly; Secure; SameSite emitido e lido pelo servidor, que o XSS não lê. Manter o refresh em armazenamento alcançável por JS (ex.: localStorage) é dívida de hardening declarada (§31.3), não escolha livre: só é aceitável enquanto valerem as três mitigações acima e o serviço registrar a dívida com o gatilho de revisão explícito (cliente fora do painel, separação de domínio entre front e API, ou qualquer caminho de injeção de HTML no front). Migrar para cookie traz CSRF para a conta e muda o contrato de /auth/* — é redesenho de autenticação, por isso adia-se de forma registrada, não por omissão.

O que esta regra fixa e não admite exceção: access token só em memória, refresh rotativo com reuse-detection que derruba a família, e teto de sessão. O armazenamento do refresh admite o estado transitório acima apenas declarado por §31.3 com gatilho de revisão — o alvo é sempre fora do alcance do JS.

29.8. Decisão de token do ecossistema (Obrigatório)

A §29.6 fixa que a chamada é autenticada e que o ator viaja dentro da credencial; a §29.7 fixa onde a sessão do usuário mora. Nenhuma das duas diz que credencial é. Esta seção escolhe o mecanismo uma vez para o ecossistema (não por serviço): sem ela, cada serviço novo inventa o seu, e o primeiro consumidor que validar de um jeito mais frouxo vira exatamente o buraco que a §29.6 existe para fechar.

São duas credenciais com um mecanismo só e públicos diferentes: o token de sessão (usuário humano → API, §29.7) e o token de serviço (serviço → serviço, §29.6). Mesmo formato, mesma chave, mesma validação — o que muda são os claims.

Formato: JWT assinado em ES256 (par de chaves).

  • Assimétrico não é detalhe. Com HMAC (HS256), quem valida consegue emitir: todo serviço que lê passaria a poder forjar o ator de qualquer outro, e a §29.6 cairia por dentro, com a credencial certa. Com par de chaves, o consumidor só tem a pública — não há o que vazar do lado de quem lê.
  • ES256 e não EdDSA. A §29.6 manda a chave privada morar em KMS/vault, e Ed25519 não é assinável em todo KMS candidato (o da AWS assina RSA e curvas NIST, não Ed25519). ES256 é o alg que nenhuma decisão de infra ainda pendente pode bloquear. Gatilho de revisão: se a plataforma fixar Vault ou GCP KMS, EdDSA passa a ser admissível por revisão desta regra (MINOR) — nunca por escolha de um serviço.
  • Não token opaco com introspecção. Poria uma chamada síncrona ao emissor em toda leitura interna, num caminho que já tem dois saltos (painel → institutional → identity/tracking): indisponibilidade do emissor viraria indisponibilidade de tudo. A revogação imediata que a introspecção daria de graça já está coberta do outro lado — access curto mais família de refresh derrubada (§29.7).
  • O algoritmo aceito na validação é lista fixa de um (algorithms=["ES256"]). Confiar no alg que vem no header do token é o ataque clássico (alg: none, confusão de chave): quem valida decide o alg, não quem assina.

Claims mínimos. Comuns às duas credenciais: iss (quem emitiu), aud (para quem o token vale), iat, exp, jti (identidade do token — a detecção de reuso da §29.7 e qualquer registro de acesso precisam poder citar um token) e typ (access | service).

Claim Token de sessão Token de serviço
sub ator humano (user_id) nome do serviço chamador
act — ator humano por trás da chamada: { sub, name }
org tenant ativo — exatamente um tenant ativo que originou a chamada
sid família de sessão (§29.7) —
  • act é o que substitui o x-actor. É o claim de delegação do RFC 8693 e é literalmente o que a §29.6 pede: o ator dentro da credencial, assinado. act.name é admitido porque é o que o registro de acesso a PII grava (identity §9) e ninguém do outro lado pode resolvê-lo; o token é curto e não se registra inteiro em log.
  • Permissão não é claim de autorização. A permissão granular (institutional §6) é resolvida no servidor, por (sub, org), a cada requisição. Permissão no token é cache, nunca portão: com ela decidindo, revogar acesso só passa a valer quando o access expira, e o token cresce com o catálogo. Quem tem a tabela resolve em consulta local; quem não tem não deveria estar decidindo.
  • org é obrigatório e singular — ver abaixo.

Chave de validação: privada em KMS/vault, pública por JWKS.

  • A privada só no emissor, dentro do KMS/vault (§29.6). Nenhum consumidor guarda segredo para validar.
  • O emissor publica JWKS num caminho interno fixo (/.well-known/jwks.json) e o token carrega kid no header. O consumidor cacheia com TTL e não falha aberto: kid desconhecido, depois de uma releitura do JWKS, é token recusado — nunca aceito "porque a chave pode ser nova".
  • Rotação: a chave nova entra no JWKS antes de assinar qualquer coisa; a assinatura passa para o kid novo; a pública antiga fica no JWKS por, no mínimo, o TTL máximo de token em circulação; depois sai. Comprometimento é o mesmo passo sem a carência — tirar do JWKS invalida na hora, e as famílias de refresh caem (§29.7).
  • O emissor hoje é o institutional-service: é quem tem usuário, perfil e permissão (§5, §6) e já emite a sessão do painel. Como o consumidor acha a chave por iss + JWKS, extrair um serviço de autenticação depois é mudança de configuração, não de contrato.

Troca de organização ativa: reemite o token.

  • Um token carrega uma org. Trocar de organização é chamada autenticada ao emissor, que confere o vínculo (institutional §11), reemite o access com a nova org e rotaciona o refresh dentro da mesma família — o teto de sessão (§29.7) continua contando do login, senão trocar de org viraria o jeito de renovar sessão para sempre.
  • Não se admite token com várias orgs e a requisição dizendo qual. Isso devolve ao chamador a decisão de isolamento e recria o x-actor com outro nome: um campo que qualquer caminho de código preenche errado — e o erro, aqui, é cross-tenant silencioso, a falha mais grave do canon (identity §13, tracking §16). Com uma org no token, o portão é uma comparação: org do token contra o tenant_id do recurso.
  • O refresh é vinculado à org em que foi emitido. Sem isso, a renovação seguinte restauraria a org anterior sem ninguém pedir.

O que esta regra fixa e não admite exceção: JWT assimétrico com alg fixado na validação, ator em act e não em cabeçalho, privada em KMS/vault com pública por JWKS, permissão resolvida no servidor e uma org por token.


PARTE III — FLUXO DE TRABALHO E ENTREGA

30. Fluxo de Git, ambientes e entrega contínua (CI/CD)

Esta seção fixa o fluxo de branches e a esteira de entrega dos repositórios de código (serviços, workers, frontends, connectors). É padrão de plataforma — vale para todos, não é escolha de projeto.

Escopo (importante). Este fluxo é dos repositórios de código executável. O repositório de documentação (cortex-docs) segue um fluxo próprio de prosa — drafts → review → main — que não se confunde com o descrito aqui. Branch de código não é branch de documento.

30.1. Estrutura de branches (Obrigatório)

Três níveis, do efêmero ao estável: main ← develop ← <feature-branch>.

  • main — espelha produção. Só recebe merge vindo de develop, e só vira release por tag versionada (30.5). Nunca recebe push direto.
  • develop — acúmulo de trabalho já aprovado, ainda não promovido a produção. Integração dos PRs aprovados. Nunca recebe push direto.
  • <feature-branch> — onde o dev trabalha. Nasce de develop e volta para develop por PR.

Nomenclatura da feature branch (Obrigatório): <type>/<slug>, com type no vocabulário de Conventional Commits (feat, fix, refactor, chore, docs, test, ci) e slug em kebab-case curto e descritivo — feat/pix-fallback, fix/duplicate-lead-id. Sem nome de pessoa nem número solto como único identificador (seção 13).

30.2. A esteira, passo a passo (Obrigatório)

# Gatilho O que dispara Ambiente
1 Push na feature branch Nada de deploy. CI de validação (lint, teste, build) pode rodar (Recomendado). Nenhum
2 PR feature → develop Fica pendente de aprovação e dispara revisão automática de código, cujo resultado é publicado no próprio PR para quem vai aprovar. HML opcional (30.3)
3 Merge em develop (após aprovação) Integra o trabalho aprovado. Sem deploy automático. Nenhum
4 PR develop → main, aprovado e mergeado main vira candidato a release e dispara deploy automático em HML — gate de validação final. HML (automático)
5 Release: tag SemVer a partir de main Dispara deploy em PRD. PRD (automático)

Leitura curta: develop acumula o aprovado; HML reflete o que está prestes a virar produção (o candidato em main); produção só sai por release versionada.

30.3. Teste em HML antes de aprovar (Recomendado)

No PR feature → develop (passo 2), o revisor pode disparar um deploy manual da feature branch em HML para testar o comportamento real antes de aprovar. É manual, sob demanda do revisor, e não bloqueia quem não precisa. Não confundir com o HML do passo 4, que é o candidato a release em main, automático.

30.4. Portões obrigatórios (Obrigatório)

  • main e develop não recebem push direto — só merge por PR. Garantido por ruleset no repositório.
  • PR para develop e para main exige aprovação e status checks verdes (a revisão automática e o CI de build/teste) antes do merge.
  • develop → main só é aberto a partir de develop; feature → main (pular o develop) é proibido — mesma disciplina de fluxo escalonado.
  • Force-push e deleção bloqueados em main e develop.
  • Estes portões são infra (ruleset + CI), não confiança no processo manual — o pipeline recusa o que furar a regra.

30.5. Release e versionamento (Obrigatório)

  • Produção sai exclusivamente por tag SemVer v<major>.<minor>.<patch> a partir de main (coerente com as tags de container, seção 11, e com o SemVer do restante do ecossistema).
  • A tag é o único gatilho de deploy em PRD. Merge em main sem tag não vai a produção — fica como candidato em HML até a release ser cortada.
  • MAJOR / MINOR / PATCH seguem o significado usual de SemVer; mudança incompatível de contrato de evento (seção 28.4) acompanha o bump correspondente.

30.6. Suíte de verificação obrigatória (Obrigatório)

Todo repositório de código Python (§29.5) roda uma suíte de verificação padrão antes de subir, e o CI gateia nela (§30.4 — status check verde obrigatório). A suíte é fonte única (um runner versionado, ex.: scripts/checks.sh), reusada em três pontos para que passar local = passar no CI:

  • pre-commit (subset rápido por commit): lint + segredos.
  • skill /checks (pré-push): suíte completa + triagem e fix do seguro.
  • CI (PR): a mesma suíte como gate.

Conjunto mínimo da suíte:

Ferramenta Verifica
ruff (check + format) lint, bugs, imports, complexidade, async, segurança básica
mypy tipos
bandit segurança Python
pip-audit dependências com CVE
gitleaks segredos no código
semgrep SAST + regras custom que codificam estas normas (ex.: §22 dinheiro, §29.3 ack)
deptry dependências não-usadas/faltando
pytest + coverage testes + piso de cobertura
  • A config é compartilhada e versionada (no template de repositório que todo serviço clona) — não reinventada por projeto.
  • Segredo (gitleaks) e dependência com CVE (pip-audit) bloqueiam — não sobem.
  • Desvio (pular ferramenta, baixar o piso de cobertura) é exceção registrada (§31.3), nunca silenciosa.
  • Esta seção fixa que a suíte é obrigatória e que o CI gateia nela; o detalhe de versão/flags e os artefatos (runner, skill, config) são código, vivem no template de repositório — não nesta doc.

30.7. Cobertura mede onde a medida significa (Obrigatório)

Cobertura de linha é instrumento honesto só onde o código roda sem I/O. Em serviço com dependência de infra (banco, broker), a maior parte do valor está em transação, lock, índice, ack/nack e ordem de entrega — que mock não prova: mock verifica o que o autor supôs que o Postgres faria, não o que ele faz. Perseguir piso de linha nessas camadas premia mock que sobe o número e não pega nenhuma dessas classes de defeito.

  • A cobertura de linha incide sobre a camada de lógica pura — o domínio (normalização, regra, cálculo), sem I/O — com piso mais alto que o global de §30.6. A régua sobe, não desce. O piso é número, e número vive no template (§30.6); o nome da pasta também — esta regra fala do conceito de camada pura, não de um caminho fixo.
  • As camadas atadas a infra (adapters, services, api, workers) saem da medida de linha e passam a ser gateadas por uma suíte de integração obrigatória, executada contra Postgres e broker reais — nunca mock. Ela gateia a promoção: falhou, não sobe. (Onde roda — contra o ambiente recém-implantado ou contra infra provisionada pelo pipeline — é detalhe do template; a regra exige infra real, não a sua ausência coberta por mock.)
  • A contagem de verificações da suíte de integração é declarada e não pode cair sem justificativa no PR. É o que impede a suíte de virar assert True.
  • Risco residual, declarado: caminho de erro em service/worker que a integração não exercita fica sem cobertura de nenhum tipo, e nenhum número acusa. Mitigação recomendada (sem gate): medir cobertura também durante a integração e reportar, para a tendência ficar visível.
  • Baixar o piso da camada pura, dispensar a integração ou deixar a contagem de verificações cair é exceção registrada (§31.3), nunca silenciosa — recíproco do §30.6.

GOVERNANÇA

31. Governança do documento e exceções

31.1. Versionamento do documento (Obrigatório)

A doc segue SemVer no cabeçalho — coerente com schema_version (seção 28) e transform_version (seção 25).

  • MAJOR — invalida padrão existente (renomear convenção, mudar tipo canônico, trocar tecnologia de fila). Exige comunicação e possível migração.
  • MINOR — adição compatível (novo tipo no catálogo, novo componente, nova seção).
  • PATCH — correção editorial sem mudar regra.

Toda alteração registra entrada no changelog (abaixo): versão, data, autor, resumo. A doc tem um owner (a plataforma) responsável por revisar e aprovar mudanças.

31.2. Níveis de obrigatoriedade (recapitulação)

  • Obrigatório — desvio só via exceção registrada (31.3).
  • Recomendado — seguir salvo motivo técnico justificado, anotado no local do desvio.
  • Permitido — livre escolha do time.

31.3. Processo de exceção (Obrigatório)

Salvo exceções não mapeadas significa documentadas e aprovadas — nunca improvisadas. Toda exceção a uma regra Obrigatório é registrada antes de entrar em produção, em exceptions.md no mesmo repositório:

Campo Conteúdo
id Identificador da exceção.
rule Regra desviada (seção e item).
scope Onde se aplica (projeto, serviço, tabela, collection).
reason Por que o padrão não atende este caso.
decided_by Quem aprovou (owner ou delegado).
decided_at Data da decisão.
review_at Quando reavaliar. Obrigatório — exceção não é permanente por default.
  • Exceção sem registro é não-conformidade, não exceção.
  • Exceção recorrente é sinal de que o padrão está errado: o mesmo desvio em vários lugares vira revisão da regra (MINOR/MAJOR), não exceção eternizada.
  • review_at é obrigatório porque o que justificou o desvio pode deixar de valer — exceção esquecida vira o temp/v2 que a seção 13 proíbe.

31.4. Checklist de criação de nome (Obrigatório aplicar)

  1. Está em inglês?
  2. Casing correto para o tipo (kebab/snake/upper-snake)?
  3. Indica claramente domínio e responsabilidade?
  4. Um humano competente no contexto entende sem explicação extra? (seção 3)
  5. Evita termos genéricos e temporais (seção 13)?
  6. O tipo segue o catálogo canônico (seção 22)?
  7. Escala para novos módulos sem virar exceção?
  8. Não depende de nome de pessoa nem de detalhe temporário?

Qualquer "não" → revisar antes de criar. Se o nome precisar de explicação além do essencial, ela vai para a descrição no banco (seção 9), não para dentro do nome.

31.5. Localização do documento (Obrigatório)

  • Caminho canônico: cortex-docs/standards/UNISCALE_ENGINEERING_STANDARDS.md.
  • Fonte única. Cópias divergentes não têm autoridade — em conflito, prevalece a versão no caminho canônico.

CHANGELOG

Versão Data Autor Resumo
2.11.0 2026-10-14 Plataforma Acrescenta §29.8 (uniscale.service-internals.token-format): a decisão de token que a §29.6 e a §29.7 deixavam em aberto, escolhida uma vez para o ecossistema. JWT assinado em ES256 (assimétrico, porque com HS256 quem valida consegue emitir; ES256 e não EdDSA porque Ed25519 não é assinável em todo KMS candidato — EdDSA só por revisão desta regra, se a plataforma fixar Vault/GCP KMS), com alg em lista fixa na validação. Claims: iss/aud/iat/exp/jti/typ comuns; sessão leva sub (humano), org (tenant ativo) e sid (família); serviço leva sub (serviço chamador) e act com o ator humano — é o que aposenta o x-actor da §29.6. Permissão não é claim de autorização: resolvida no servidor por (sub, org), claim seria cache e nunca portão. Chave privada em KMS/vault, pública por JWKS com kid, rotação com carência ≥ TTL máximo e validação que não falha aberto; emissor hoje é o institutional-service, e extrair um serviço de auth depois é configuração, não contrato. Troca de org ativa reemite o token (uma org por token, refresh vinculado à org, mesma família para o teto continuar contando do login) — token multi-org com seletor na requisição recriaria o x-actor num erro cross-tenant silencioso. Surgiu da §11 do institutional (14/10), que fixou operador multi-org e remeteu o encoding da org ativa para cá. Regra nova → MINOR.
2.10.0 2026-10-01 Plataforma Acrescenta §29.7 (uniscale.service-internals.user-session): sessão do usuário humano entre front e API (a §29.6 é serviço↔serviço). Fixa como padrão do ecossistema — para nenhum front re-derivar auth mais fraca — access token só em memória (nunca em localStorage/sessionStorage), refresh rotativo com detecção de reuso que derruba a família de sessão (roubo deixa de ser silencioso) e teto de duração da sessão desde o login (valor no template). A escolha de onde o refresh mora é guiada pela ameaça de XSS e é decisão de plataforma: o alvo é fora do alcance do JS (cookie HttpOnly; Secure; SameSite); mantê-lo em armazenamento alcançável por JS é dívida de hardening declarada por §31.3, com gatilho de revisão explícito (cliente fora do painel, separação de domínio front/API, ou caminho de injeção de HTML), não escolha livre. Não admite exceção: memória-only + reuse-detection + teto; só o armazenamento do refresh admite o estado transitório, declarado. Surgiu da decisão do institutional-service (01/10) de adiar a migração do refresh de localStorage para cookie HttpOnly — adiada com as três mitigações no ar. Regra nova → MINOR.
2.9.0 2026-09-24 Plataforma Acrescenta §29.6 (uniscale.service-internals.service-auth): chamada entre serviços internos é autenticada (exposição ≠ autenticação), o ator viaja dentro da credencial e nunca num cabeçalho livre (x-actor com default unknown é o buraco), e a chave que assina a credencial mora em KMS/vault/secret — nunca no banco, código ou env commitada. O mecanismo específico (token assinado, mTLS, chave em secret) é decisão de plataforma/infra por §31.3; o que não admite exceção é chamada autenticada + ator na credencial. Surgiu do identity servir PII ao painel do institutional com x-actor livre, o que tornaria o pii_access_log (identity §9, exigência LGPD) não confiável. Regra nova → MINOR.
2.8.0 2026-09-23 Plataforma Acrescenta §30.7 (uniscale.git.coverage-layered): em serviço com dependência de infra, a cobertura de linha mede a camada de lógica pura (domínio) com piso mais alto — a régua sobe —, e as camadas atadas a banco/broker (adapters/services/api/workers) saem da medida de linha e ganham gate de suíte de integração obrigatória contra Postgres/broker reais (nunca mock), com contagem de verificações declarada e não-decrescente. Risco residual (caminho de erro não exercitado) fica declarado, com recomendação de medir cobertura na integração e reportar sem gate. Surgiu do tracking-service e do identity-service na subida para homologação (23/09): 80% de linha global sob mock premiava teste que não pega transação/lock/índice/ack. O número segue no template (§30.6 delega flags); a regra fixa onde a medida vale. Desvio via §31.3, recíproco do §30.6. Regra nova → MINOR.
2.7.0 2026-09-10 Plataforma Acrescenta §28.7 (uniscale.events.no-subscriber-required): publicar fato não exige assinante — num topic, ausência de bind é estado normal, e mandatory fica desligado na publicação de fato de negócio; a durabilidade continua vindo dos publisher confirms. Exceção única: canal dedicado com um consumidor nomeado, onde a mensagem não pode ser dada como entregue (canal de PII do tracking para o identity). Surgiu do primeiro teste de integração real entre os dois serviços, onde o publisher do cortex-template marcava como falha um fato correto que ninguém ainda consumia. Regra nova → MINOR.
2.6.0 2026-06-16 Plataforma Acrescenta à §29.5 (uniscale.service-internals.runtime-stack) a política de versão do Python: alvo 3.12; um serviço só usa versão menor quando um pacote obrigatório não a suporta, descendo um degrau de cada vez até a mais nova aceita, com a versão e o motivo registrados no pyproject.toml. Surgiu do primeiro build real (storage-service em 3.11). Adição compatível à regra existente → MINOR.
2.5.0 2026-06-16 Plataforma Adiciona a seção 30.6 — suíte de verificação obrigatória (Obrigatório): todo repo Python roda uma suíte padrão (ruff, mypy, bandit, pip-audit, gitleaks, semgrep, deptry, pytest+coverage) de fonte única reusada em pre-commit, skill /checks (pré-push) e CI como gate (§30.4), com config compartilhada e versionada no template de repositório; segredo e CVE bloqueiam; desvio só via exceção (§31.3). Fixa que a suíte é obrigatória e que o CI gateia — versão/flags e artefatos são código, fora desta doc. Adição compatível → MINOR.
2.4.0 2026-06-15 Plataforma Adiciona a seção 29.5 — stack padrão de serviço (Obrigatório): Python é a linguagem de backend do ecossistema (serviços, workers, connectors), FastAPI para serviços HTTP; stack divergente é decisão de plataforma via exceção (§31.3), não iniciativa de projeto (mesma trava da §29.1 de fila); frontends seguem stack de UI própria. Fixa também o desacoplamento de fornecedor como design: fornecedor externo atrás de porta/interface, nunca amarrado ao núcleo, com régua de realização — driver/adapter in-process (por env) para substrato commodity síncrono (bucket, modelo: storage-service, embedding-service); connector separado para sistema de negócio com integração mão-dupla/evento (payment-connector, academic-connector, crm-connector). Não criar connector para substrato burro. Adição compatível → MINOR.
2.3.2 2026-06-04 Plataforma Editorial (sem mudança de regra): no exemplo de <content> da §26.1, troca messages por faqs — a tabela messages foi aposentada (RAG_AND_INGESTION 1.1.0) e não há collection de histórico de conversa. Só o exemplo muda; a gramática de nomenclatura é a mesma. PATCH.
2.3.1 2026-06-02 Plataforma Editorial (sem mudança de regra): adiciona front-matter YAML e marcadores @rule (slug nominal + nível + since) em todas as seções de nível Obrigatório/Recomendado, conforme o contrato da fonte (cortex-docs/meta/DOC_SOURCE_CONTRACT.md). Torna cada regra endereçável por slug estável para consumo HTTP/MCP/RAG, sem alterar texto nem semântica das regras. PATCH.
2.3.0 2026-06-02 Plataforma Adiciona a seção 26.8 — sincronização Postgres → Qdrant via outbox transacional (Obrigatório): proíbe dual write direto (diverge silenciosamente), exige outbox commitado na mesma transação, worker idempotente por domínio como único escritor do vetor, evento-ponteiro com releitura de estado (distinto do event-carried state transfer da seção 28, que é a fronteira externa com o core), diff por hash no update, blue-green na troca de modelo e rede de segurança (reconciliação, lag, DLQ). Reforça na seção 26.3 que o point_id é determinístico pelo id do chunk — não por hash de coordenadas. Aponta para o documento de arquitetura RAG_AND_INGESTION.md. Adição compatível → MINOR.
2.2.0 2026-06-01 Plataforma Adiciona a Parte III — Fluxo de trabalho e entrega (nova seção 30): estrutura de branches main ← develop ← <feature-branch> para repositórios de código, nomenclatura de feature branch por Conventional Commits, esteira de CI/CD passo a passo (push → PR para develop com revisão automática → merge em develop sem deploy → PR develop→main com deploy automático em HML → release por tag SemVer com deploy em PRD), deploy manual opcional em HML para teste pré-aprovação, portões obrigatórios por ruleset/CI, e release exclusiva por tag versionada. Deixa explícito que esse fluxo é de código e não se confunde com o drafts → review → main do repositório de documentação. Governança renumerada de 30 para 31 (e referências atualizadas) para acomodar a nova seção. Adição compatível → MINOR.
2.1.0 2026-05-26 Plataforma Ajustes de executabilidade (sem quebrar padrão): core descrito como consolidado por domínio (inicialmente relacional, evolui para dimensional); observabilidade descrita como logs estruturados com implementação inicial em MongoDB; RabbitMQ como tecnologia padrão (não "única"), mantida a trava de decisão de plataforma; UUIDv7 mantido obrigatório com função de geração oficial por stack (seção 23.7); soft delete limitado a tabelas de negócio com dispensa documentada por tabela; autoria (created_by/updated_by) explicitada como ator local ao banco, incluindo conta de serviço do core.
2.0.0 2026-05-26 Plataforma Reestruturação completa. Adicionada Parte II (modelagem e tipos): arquitetura do ecossistema, catálogo de tipos canônicos, colunas obrigatórias, constraints/índices/FKs, linhagem no core, Qdrant, MongoDB, contrato de evento. Adicionadas seções de padrões internos de serviço (29) e governança (30). Princípio do leitor competente, processo de exceção e versionamento da doc formalizados.
1.0.0 — — Versão inicial: padrões de nomenclatura (repositórios, serviços, bancos, schemas, tabelas, campos, eventos, containers, namespaces, variáveis de ambiente).