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.mdContrato 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. Evitarbackend,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:
connectorfala com fora;adaptertraduz/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. Evitardb_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. Evitarcad_usuario,tb_cliente,tbl_config,walletDatae 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ãouser_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
idgerado 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". |
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).
DELETEfí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_idem 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_idpermanece 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_criacaooucreated— é semprecreated_at: timestamptz.
23.6. Autoria e tipo de conta (Obrigatório)
created_by/updated_by(uuid) referenciam sempre um registro real na tabelausersdo mesmo banco onde a linha vive — nuncaNULLpara "sistema", nunca UUID fora de tabela. Como cada banco tem seu própriousers, 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_byaponta para a conta de serviço do core (account_type = 'service') — "o pipeline gravou isto". O core tem seu própriousers, como qualquer banco. - A tabela
usersdistingue conta humana de conta de serviço viaaccount_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>_idque referencia tabela no mesmo banco tem FK declarada, apontando para aid(PK) dela. Referência local implícita (coluna_idsem FK, havendo a tabela) é proibida. - Coluna
<x>_idque referencia entidade de outro serviço (ex.:organization_idnum banco que não hospedaorganizations) não tem FK — a tabela não existe localmente. É um identificador de referência externa: oidpreservado (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ãoRESTRICT.CASCADEsó com justificativa (raramente desejável com soft delete).SET NULLapenas 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), prefixoux_. - Recomendado: indexar
tenant_idem tabelas multi-tenant de alto volume; considerar índice composto liderado portenant_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 reusasource_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>__sourceparcial, respeitando soft delete.
25.4. Idempotência da ingestão (Obrigatório)
- A gravação no core é idempotente: reprocessar o mesmo
source_event_idpara 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_versionem(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_atantes 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). Semtext-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_idno payload e índice de payload nesse campo. Toda query de negócio filtra portenant_id. - Exceção (mesmo critério da seção 23.3): cliente que exige isolamento físico ganha collection dedicada.
tenant_idestá no payload desde o início, mesmo em collection dedicada.
26.3. Identidade do point (Obrigatório)
- O
iddo point é oid(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
iddo chunk; o documento é referenciado no payload. - O
iddo chunk já é determinístico e idempotente para o upsert — não se constróipoint_idpor hash de coordenadas (collection/doc/chunk). O mesmoidvale 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_idno 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=iddo 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_byou 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. serviceetenant_idsã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
logstem índice TTL sobretimestamp— documentos expiram após N dias. - N é configurável por ambiente (ex.:
prdretém mais quedev), 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_versionem 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_versionem SemVer, coerente comtransform_version: MAJOR = mudança incompatível nodata; 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_namepor 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_atna 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.
mandatoryfica desligado na publicação de fato de negócio. Commandatory=True, o broker devolve a mensagem como não-roteável, o relay do outbox interpreta como falha, e o serviço acumula fatosfailedpor 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 dotracking-servicepara oidentity-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 → commandatory.
Origem: encontrado no primeiro teste de integração real entre
trackingeidentity, com o publisher docortex-template. O fatotracking.event.capturedfalhava porquecortex-serviceemarketing-connectorainda 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, tipotopic(payment,wallet-ai). - Routing key: estrutura hierárquica do evento (seção 10) —
payment.transaction.confirmed. - Queue:
<consumer>__<purpose>emkebab-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.tomldo 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 porSTORAGE_DRIVER);embedding-serviceencapsulando 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-connectorpor 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).
- 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
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 claimact, 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/sessionStorageo 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; SameSiteemitido 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ê. ES256e nãoEdDSA. A §29.6 manda a chave privada morar em KMS/vault, eEd25519nã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,EdDSApassa 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 noalgque 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 ox-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 carregakidno header. O consumidor cacheia com TTL e não falha aberto:kiddesconhecido, 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
kidnovo; 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 poriss+ 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 novaorge 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-actorcom 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:orgdo token contra otenant_iddo 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 dedevelop, 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 dedevelope volta paradeveloppor 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)
mainedevelopnão recebem push direto — só merge por PR. Garantido por ruleset no repositório.- PR para
develope paramainexige aprovação e status checks verdes (a revisão automática e o CI de build/teste) antes do merge. develop → mainsó é aberto a partir dedevelop;feature → main(pular odevelop) é proibido — mesma disciplina de fluxo escalonado.- Force-push e deleção bloqueados em
mainedevelop. - 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 demain(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
mainsem 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 otemp/v2que a seção 13 proíbe.
31.4. Checklist de criação de nome (Obrigatório aplicar)
- Está em inglês?
- Casing correto para o tipo (kebab/snake/upper-snake)?
- Indica claramente domínio e responsabilidade?
- Um humano competente no contexto entende sem explicação extra? (seção 3)
- Evita termos genéricos e temporais (seção 13)?
- O tipo segue o catálogo canônico (seção 22)?
- Escala para novos módulos sem virar exceção?
- 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). |