N-0b — Registry (Mapa do Formigueiro)
Parte do Núcleo — O Chão do Formigueiro
Propósito
Seção intitulada “Propósito”O registry é o idioma oficial do sistema. Define os tipos de eventos que existem, os schemas de dados que cada tipo carrega e os contratos que cada colônia deve cumprir para publicar ou consumir. É a fonte da verdade sobre o que pode trafegar no barramento.
Não executa lógica de negócio, não valida conteúdo e não decide permissão. Apenas cataloga, versiona e expõe contratos. Suas responsabilidades são:
- Catalogar todos os tipos de evento com nome, versão e schema JSON Schema.
- Versionar schemas: uma alteração gera nova versão com semver, nunca substitui a anterior.
- Expor contratos como documentação estruturada para cada colônia — a dependência única que todas importam do núcleo.
- Ser a fonte da verdade sobre o idioma do sistema.
Alterar o registry sem versionar é a única operação proibida no núcleo.
1. Estrutura do Módulo NestJS
Seção intitulada “1. Estrutura do Módulo NestJS”O Registry é um módulo NestJS do núcleo. É importado pelo EventBusModule (N-0a) para a validação no caminho de publicação. Não é @Global(): cada módulo que precisa de schemas ou tipos importa RegistryModule explicitamente.
Árvore de diretórios
Seção intitulada “Árvore de diretórios”src/nucleo/n-0b-registry/├── registry.module.ts├── registry.service.ts # registrar(), validar(), validarPayload(), sincronizarCatalogo()├── dto/│ └── evento-definicao.dto.ts # Definição de um tipo: nome, domínio, entidade, ação, versão inicial e schema├── repositories/│ ├── event-type.repository.ts # Acesso a core.event_types│ └── schema-version.repository.ts # Acesso a core.schema_versions└── schemas/ ├── index.ts # CATALOGO_EVENTOS, o catálogo canônico em TypeScript └── catalogo.spec.ts # Validade estrutural dos schemas e do catálogoModule definition
Seção intitulada “Module definition”@Module({ providers: [ RegistryService, EventTypeRepository, SchemaVersionRepository, ], exports: [RegistryService],})export class RegistryModule {}O módulo não declara controller no MVP.
Pontos de atenção
Seção intitulada “Pontos de atenção”- O módulo não é
@Global(). OEventBusModuleé global, e oObservabilityModule(N-0c) também. Colônias que precisam de schemas importamRegistryModuleexplicitamente. - O módulo não importa
EventEmitterModule. O Registry não publica nem consome eventos de negócio. É uma dependência passiva, consultada de forma síncrona pelo Event Bus no caminho depublicar(). - Não há controller REST no MVP. A definição canônica é o catálogo TypeScript em
src/nucleo/n-0b-registry/schemas/index.ts(CATALOGO_EVENTOS, 45 tipos). Não existem arquivosschemas/*.json. O catálogo é sincronizado no banco de forma idempotente no boot (onModuleInit→sincronizarCatalogo()): tipos novos são inseridos, schemas divergentes têmschema_jsonatualizado e versões suplementares ausentes são adicionadas. Não existe migration de seed.
Interface pública — RegistryService
Seção intitulada “Interface pública — RegistryService”Os métodos implementados, em português:
buscarPorTipo(tipo: string): Promise<TipoEventoConsultado | null>;obterVersaoMaisRecente(tipo: string): Promise<string | null>;obterSchema(tipo: string, versao?: string): Promise<JSONSchema | null>;registrar(dto: EventoDefinicao): Promise<EventType>;adicionarVersaoSchema(tipo: string, versao: string, schema: JSONSchema, changelog?: string): Promise<SchemaVersion>;depreciarTipo(tipo: string, motivo: string): Promise<void>;listarTiposAtivos(): Promise<EventType[]>;obterHistoricoTipo(tipo: string): Promise<{ tipo: EventType; versoes: SchemaVersion[] } | null>;validar(evento: { tipo: string; versao_schema?: string }): Promise<boolean>; // caminho de publicação do Event BusvalidarPayload(tipo: string, versao: string, payload: Record<string, unknown>): Promise<string[]>; // lista de errossincronizarCatalogo(): Promise<number>; // quantidade de tipos registrados no bootlimparCache(): void;Colônia pura de infraestrutura
Seção intitulada “Colônia pura de infraestrutura”A N-0b não tem BFF acoplado, não processa demanda e não tem controller REST no MVP. A única interface externa é o RegistryService, consumido pelo Event Bus e, de forma opcional, pelas colônias para validação de payload próprio. O catálogo em schemas/index.ts é a documentação viva.
2. Banco de Dados — Schema e Entidades
Seção intitulada “2. Banco de Dados — Schema e Entidades”Schema core
Seção intitulada “Schema core”Todas as tabelas do Registry residem no schema core do PostgreSQL, compartilhado com N-0a (Event Bus) e N-0c (Observabilidade). Nenhuma colônia de negócio acessa essas tabelas diretamente — todo acesso passa pelo RegistryService.
Tabela core.event_types
Seção intitulada “Tabela core.event_types”Catálogo de todos os tipos de evento que já existiram ou existem no sistema. Um tipo nunca é removido fisicamente. Seu status é alterado de active para deprecated, mas o registro permanece no banco.
CREATE TABLE core.event_types ( id UUID NOT NULL, tipo VARCHAR(255) NOT NULL, dominio VARCHAR(100) NOT NULL, entidade VARCHAR(100) NOT NULL, acao VARCHAR(100) NOT NULL, descricao TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', deprecated_at TIMESTAMPTZ(2), deprecated_motivo TEXT, created_at TIMESTAMPTZ(2) NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT event_types_pkey PRIMARY KEY (id));
CREATE UNIQUE INDEX event_types_tipo_key ON core.event_types (tipo);CREATE INDEX event_types_status_idx ON core.event_types (status);CREATE INDEX event_types_dominio_idx ON core.event_types (dominio);O Prisma não gera CHECK de status. A validação de active/deprecated fica em aplicação, e o campo no banco é VARCHAR.
Colunas — detalhamento
Seção intitulada “Colunas — detalhamento”| Coluna | Tipo | Descrição |
|---|---|---|
id |
UUID PK | Identificador interno do registro. Desacoplado do nome do tipo. |
tipo |
VARCHAR(255) UNIQUE | Nome completo conforme convenção {dominio}.{entidade}.{acao}. Ex: demanda.recebida. |
dominio |
VARCHAR(100) | Domínio do evento — parte antes do primeiro ponto. Facilita agrupamento e consultas. |
entidade |
VARCHAR(100) | Entidade afetada — parte entre primeiro e segundo ponto. |
acao |
VARCHAR(100) | Ação ocorrida — parte após o último ponto. |
descricao |
TEXT | Descrição legível do propósito do evento. |
status |
VARCHAR(20) | active ou deprecated, validado em aplicação. |
deprecated_at |
TIMESTAMPTZ(2) | Preenchido quando status muda para deprecated. |
deprecated_motivo |
TEXT | Justificativa da depreciação. Obrigatório ao depreciar. |
created_at |
TIMESTAMPTZ(2) | Data de criação do registro. |
updated_at |
TIMESTAMPTZ(2) | Data da última alteração (status ou descrição). |
Tabela core.schema_versions
Seção intitulada “Tabela core.schema_versions”Histórico de versões de schema para cada tipo de evento. Cada versão é um registro separado. A versão latest é marcada por flag.
CREATE TABLE core.schema_versions ( id UUID NOT NULL, event_type_id UUID NOT NULL, versao VARCHAR(20) NOT NULL, schema_json JSONB NOT NULL, changelog TEXT, is_latest BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT schema_versions_pkey PRIMARY KEY (id), CONSTRAINT schema_versions_event_type_id_fkey FOREIGN KEY (event_type_id) REFERENCES core.event_types(id) ON DELETE RESTRICT ON UPDATE CASCADE);
CREATE INDEX schema_versions_event_type_id_idx ON core.schema_versions (event_type_id);CREATE UNIQUE INDEX schema_versions_event_type_id_versao_key ON core.schema_versions (event_type_id, versao);Colunas — detalhamento
Seção intitulada “Colunas — detalhamento”| Coluna | Tipo | Descrição |
|---|---|---|
id |
UUID PK | Identificador interno da versão de schema. |
event_type_id |
UUID FK | Referência ao tipo de evento em event_types. FK com ON DELETE RESTRICT: um tipo com schema versionado não pode ser removido fisicamente. |
versao |
VARCHAR(20) | Versão semântica (semver) do schema: MAJOR.MINOR.PATCH. Validada em aplicação, sem CHECK no banco. |
schema_json |
JSONB | Definição do schema no formato JSON Schema (draft-2020-12). Descreve a estrutura esperada do campo payload do evento. |
changelog |
TEXT | Descrição legível do que mudou nesta versão em relação à anterior. |
is_latest |
BOOLEAN | Flag que indica a versão mais recente. Gerida em aplicação: adicionarVersaoSchema() marca a anterior como não recente antes de inserir a nova. |
created_at |
TIMESTAMPTZ(2) | Data de criação da versão. |
Restrições e decisões de schema
Seção intitulada “Restrições e decisões de schema”ON DELETE RESTRICTna FK deschema_versions -> event_types: um tipo com schema versionado nunca pode ser removido fisicamente. O caminho correto é depreciar (status = 'deprecated'), não deletar.- Sem FK entre
event_typeseevent_log: a validação de tipo nopublicar()é lógica viaRegistryService, não via constraint de banco. Tipos deprecated que já tiveram milhões de eventos no log permanecem referenciáveis. schema_jsoncomo JSONB: permite validação estrutural do próprio JSON Schema e consultas futuras (ex: “quais tipos têm campodemanda_idno payload?”).is_latestcomo flag gerida em aplicação: a consulta da versão mais recente de um tipo usaWHERE event_type_id = ?com o índiceschema_versions_event_type_id_idx. Não há índice parcial.UNIQUE (event_type_id, versao): garante que um tipo nunca tenha duas versões com o mesmo número.versaocomo VARCHAR(20): semver (ex:1.0.0) cabe em 20 caracteres com folga. Sem CHECK no banco, a validação de formato é feita em aplicação e nos testes.
Migrations esperadas
Seção intitulada “Migrations esperadas”As duas tabelas nascem na migration 20260809101544_create_d1a_schema, junto do schema d1a. Não existe migration de seed de tipos. O catálogo é populado no boot pelo sincronizarCatalogo() (onModuleInit do RegistryService), idempotente: insere tipos novos, atualiza schema_json divergente (comparação por JSON.stringify) e não remove nada.
Relações internas
Seção intitulada “Relações internas”A única relação dentro do schema core para o Registry é a FK de schema_versions.event_type_id -> event_types.id. Não há FKs entre tabelas do Registry e tabelas do Event Bus — o acoplamento é lógico, não estrutural. Se o Event Bus precisar ser migrado para outro banco no futuro, o Registry permanece íntegro e vice-versa.
3. Catálogo de Eventos — Contratos Detalhados
Seção intitulada “3. Catálogo de Eventos — Contratos Detalhados”3.1 Convenção de nomenclatura
Seção intitulada “3.1 Convenção de nomenclatura”{dominio}.{entidade}.{acao}
dominio — agrupamento macro (demanda, conselheiro, agenda, lugar, empresa, cidadão, ranking, anexo, parâmetros, peso_situacional, sorteio, vínculo, votação, duplicidade, anomalia, ...)entidade — o sujeito ou objeto afetadoacao — verbo no particípio passado, voz passiva
Exemplos: demanda.recebida — uma demanda foi recebida pelo sistema conselheiro.sorteado — um conselheiro foi sorteado agenda.gerada — uma agenda foi gerada ranking.atualizado — o ranking foi atualizado parâmetros.atualizados — os parâmetros foram atualizadosRegras:
- Tudo em minúsculas. Acentos são permitidos e usados no catálogo real (
demanda.concluída,parâmetros.atualizados,conselheiro.atualização_registrada). dominioé sempre singular.acaoé sempre particípio passado.- Separador entre partes: ponto final (
.). - O nome é validado pelo regex
^[\p{Ll}_]+(\.[\p{Ll}_]+){1,2}$, que aceita 2 ou 3 segmentos.
3.2 Estrutura do evento (envelope + payload)
Seção intitulada “3.2 Estrutura do evento (envelope + payload)”Um evento no barramento tem duas camadas: o envelope (campos comuns a todos os eventos) e o payload (dados específicos do tipo). O envelope é definido pelo Event Bus (N-0a). O Registry define o schema do payload.
Envelope (responsabilidade do Event Bus — N-0a):
interface EventoConsultado { sequence_number: bigint; // BIGSERIAL — monotônico global event_id: string; // UUID v4 tipo: string; // ex: 'demanda.recebida' versao_schema: string; // semver do schema do payload timestamp: string; // ISO-8601 origem: string; // código da colônia publicadora correlacao_id: string | null; // UUID de rastreamento payload: Record<string, unknown>;// corpo conforme JSON Schema deste documento criado_em: string; // ISO-8601 de persistência}Payload (responsabilidade do Registry — N-0b): documentado nos schemas a seguir.
3.3 Convenção de versionamento semântico (semver)
Seção intitulada “3.3 Convenção de versionamento semântico (semver)”Cada schema de payload segue semver com as seguintes regras:
| Incremento | Quando usar | Exemplo |
|---|---|---|
PATCH (1.0.x) |
Correção de descrição, clarificação de campo sem mudança estrutural. Nenhum campo novo, nenhum removido. | Corrigir description de um campo no JSON Schema. |
MINOR (1.x.0) |
Adição de campo opcional. Todos os campos existentes permanecem. Consumidores que ignoram campos novos continuam funcionando. Schema anterior ainda é válido para eventos em trânsito. | Adicionar observacao?: string ao payload. |
MAJOR (x.0.0) |
Remoção de campo obrigatório, alteração de tipo de campo existente, mudança de semântica. Schema anterior não é mais compatível. | Substituir coordenadas: { lat, lng } por localizacao: GeoJSON. |
A versão default quando a colônia não especifica versao_schema no publicar() é o literal '1.0.0', não uma consulta à versão mais recente. A versão gravada no event_log é a vigente no momento da publicação e nunca é alterada retroativamente.
3.4 Catálogo de tipos de evento — Fase 1 (MVP)
Seção intitulada “3.4 Catálogo de tipos de evento — Fase 1 (MVP)”Cada tipo de evento é listado com:
- Nome completo (conforme convenção)
- Domínio, entidade, ação
- Colônia produtora
- Colônias consumidoras
- Descrição
- Schema do payload (JSON Schema draft-2020-12)
O catálogo canônico vive em src/nucleo/n-0b-registry/schemas/index.ts (CATALOGO_EVENTOS, 48 tipos) e é sincronizado no banco no boot pelo sincronizarCatalogo(). Cada tipo tem uma versão inicial e versões suplementares; cada seção abaixo lista o contrato da versão mais recente. Os blocos de schema trazem um $id de documentação no formato {tipo}/{versao}; o schema registrado no banco não inclui esse campo.
3.4.1 demanda.recebida
Seção intitulada “3.4.1 demanda.recebida”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | recebida |
| Produtor | D-1a (Captura) |
| Consumidores | D-1b (Normalização), D-1c (Anexos) |
| Descrição | Input bruto do cidadão aceito pelo sistema. Ainda não normalizado, não categorizado, não georreferenciado. A versão corrente é a 1.2.0: a 1.1.0 adicionou categoria_id e subcategoria_id escolhidos na captura e a 1.2.0 aceita captura só com mídia, com texto_bruto vazio. As versões 1.0.0 e 1.1.0 permanecem no catálogo. |
Schema do payload (versão 1.2.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.recebida/1.2.0", "title": "demanda.recebida", "type": "object", "required": ["demanda_id", "texto_bruto", "localizacao_bruta", "cidadao_id", "canal"], "properties": { "demanda_id": { "type": "string", "format": "uuid", "description": "Identificador único da demanda gerado pela D-1a." }, "texto_bruto": { "type": "string", "maxLength": 5000, "description": "Texto livre submetido pelo cidadão. Vazio na captura só com mídia." }, "tipo_midia": { "type": "string", "enum": ["texto", "foto", "audio"], "description": "Tipo de mídia predominante na submissão." }, "localizacao_bruta": { "type": "object", "required": ["lat", "lng"], "properties": { "lat": { "type": "number", "minimum": -90, "maximum": 90 }, "lng": { "type": "number", "minimum": -180, "maximum": 180 } }, "description": "Coordenadas brutas fornecidas pelo dispositivo do cidadão." }, "cidadao_id": { "type": "string", "format": "uuid", "description": "Identificador do cidadão que submeteu a demanda." }, "canal": { "type": "string", "enum": ["app", "web", "sms", "156"], "description": "Canal de entrada da demanda." }, "timestamp_criacao": { "type": "string", "format": "date-time", "description": "Timestamp de criação no dispositivo do cidadão. Opcional — se ausente, o timestamp do evento é usado." }, "categoria_id": { "type": "string", "maxLength": 20, "description": "Categoria escolhida pelo cidadão na captura, desde a versão 1.1.0." }, "subcategoria_id": { "type": "string", "maxLength": 50, "description": "Subcategoria escolhida pelo cidadão na captura, desde a versão 1.1.0." } }}Campo adicional assistencia. O payload pode carregar assistencia fora do schema declarado, no padrão de midia_urls (o schema não usa additionalProperties: false). O campo registra a auditoria de uso da assistência de texto (usada, motor, versao e aplicadas), não contém dado pessoal, não guarda o texto anterior nem as sugestões e é preservado pela redação do core.event_log. Sem bump de versão.
3.4.2 demanda.normalizada
Seção intitulada “3.4.2 demanda.normalizada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | normalizada |
| Produtor | D-1b (Normalização) |
| Consumidores | D-1d (Moderação, quando conteudo_suspeito=true), D-2 (Georreferenciamento), D-3 (Categorização), D-7 (Transparência), D-12 (Detecção de Duplicidade) |
| Descrição | Demanda com texto limpo, entidades extraídas e campos estruturados. Derivada do dado bruto — o original permanece preservado. A versão corrente é a 1.4.0: a 1.1.0 adicionou categoria_id e subcategoria_id escolhidos na captura, a 1.2.0 adicionou conteudo_suspeito e termos_suspeitos da denylist de texto da D-1b, a 1.3.0 adicionou midias_descritas com a descrição e a tradução automáticas de cada imagem da captura e a 1.4.0 separou a autoria: a descricao_limpa passa a conter exclusivamente o texto do cidadão e aceita string vazia. As versões 1.0.0 a 1.3.0 permanecem no catálogo. |
Schema do payload (versão 1.4.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.normalizada/1.4.0", "title": "demanda.normalizada", "type": "object", "required": ["demanda_id", "titulo", "descricao_limpa", "coordenadas_validadas", "confianca_normalizacao"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "titulo": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Título derivado do texto do cidadão (primeiros tokens significativos); sem texto, usa a primeira legenda exibível truncada." }, "descricao_limpa": { "type": "string", "maxLength": 5000, "description": "Texto do cidadão com ruído removido, normalizado Unicode e idioma detectado. Aceita string vazia desde a versão 1.4.0." }, "tipo_midia_processada": { "type": "string", "enum": ["texto", "foto", "audio"], "description": "Tipo de mídia após processamento — pode diferir do original se áudio foi transcrito para texto." }, "coordenadas_validadas": { "type": "object", "required": ["lat", "lng"], "properties": { "lat": { "type": "number", "minimum": -90, "maximum": 90 }, "lng": { "type": "number", "minimum": -180, "maximum": 180 } } }, "confianca_normalizacao": { "type": "number", "minimum": 0, "maximum": 1, "description": "Score de confiança da normalização (0 = sem confiança, 1 = confiança total)." }, "entidades_extraidas": { "type": "object", "description": "Entidades nomeadas extraídas do texto. Opcional — presente apenas quando detectadas.", "properties": { "endereco": { "type": "string" }, "cep": { "type": "string", "pattern": "^\\d{5}-?\\d{3}$" }, "nome_rua": { "type": "string" } } }, "idioma_detectado": { "type": "string", "minLength": 2, "maxLength": 5, "description": "Código ISO 639-1 do idioma detectado (ex: pt, en, es)." }, "categoria_id": { "type": "string", "maxLength": 20, "description": "Categoria escolhida pelo cidadão na captura. Campo opcional da versão 1.1.0." }, "subcategoria_id": { "type": "string", "maxLength": 50, "description": "Subcategoria escolhida pelo cidadão na captura. Campo opcional da versão 1.1.0." }, "conteudo_suspeito": { "type": "boolean", "description": "Indica que a denylist de texto da D-1b encontrou termos suspeitos no conteúdo. Campo opcional da versão 1.2.0." }, "termos_suspeitos": { "type": "array", "items": { "type": "string", "maxLength": 100 }, "maxItems": 50, "description": "Termos da denylist encontrados no texto. Campo opcional da versão 1.2.0." }, "midias_descritas": { "type": "array", "maxItems": 10, "description": "Descrição automática de cada imagem da captura. Campo opcional da versão 1.3.0.", "items": { "type": "object", "required": ["object_key", "descricao_original", "traducao_aplicada", "descricao_idioma"], "properties": { "object_key": { "type": "string", "maxLength": 1024, "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]*$", "description": "Chave temporária do upload da captura." }, "descricao_original": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "Legenda original do Florence." }, "descricao_traduzida": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "Tradução para português. Omitida quando a tradução não foi aplicada." }, "traducao_aplicada": { "type": "boolean", "description": "Indica se a tradução foi aplicada." }, "descricao_idioma": { "type": "string", "minLength": 2, "maxLength": 10, "description": "Idioma da legenda original." } } } } }}3.4.3 anexo.processado
Seção intitulada “3.4.3 anexo.processado”| Propriedade | Valor |
|---|---|
| Domínio | anexo |
| Entidade | anexo |
| Ação | processado |
| Produtor | D-1c (Gestão de Anexos) |
| Consumidores | D-1d (Moderação), D-7 (Transparência — timeline e projeção de lugares) |
| Descrição | Arquivo anexado a uma demanda foi validado, armazenado e classificado quanto a conteúdo sensível. O campo via distingue a origem da evidência. A versão corrente é a 1.4.0, que adicionou object_key_temp com a chave temporária da captura, usada para casar a descrição automática da imagem; as versões 1.0.0 a 1.3.0 permanecem no catálogo. |
Schema do payload (versão 1.4.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "anexo.processado/1.4.0", "title": "anexo.processado", "type": "object", "required": ["demanda_id", "anexo_id", "object_key", "tipo_mime", "tamanho_bytes", "hash_sha256", "via", "categoria_sensivel", "motivo_sensivel", "moderacao_status"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "anexo_id": { "type": "string", "format": "uuid", "description": "Identificador único do anexo." }, "object_key": { "type": "string", "maxLength": 1024, "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]*$", "description": "Chave do objeto permanente no storage." }, "object_key_temp": { "type": "string", "maxLength": 1024, "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]*$", "description": "Chave temporária do upload da captura. Campo opcional da versão 1.4.0, usado pela D-1d para casar a descrição automática da imagem." }, "url_canonica": { "type": "string", "maxLength": 2048, "description": "URL canônica do objeto no storage." }, "url_acesso": { "type": "string", "format": "uri", "description": "URL de acesso ao arquivo. Pré-assinada com expiração quando o storage exigir." }, "tipo_mime": { "type": "string", "description": "MIME type validado por magic bytes (ex: image/jpeg, audio/mp4)." }, "tamanho_bytes": { "type": "integer", "minimum": 1, "maximum": 20971520, "description": "Tamanho do arquivo em bytes. Máximo 20 MB." }, "hash_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Hash SHA-256 do conteúdo do arquivo. Para verificação de integridade e deduplicação." }, "possui_dado_sensivel": { "type": "boolean", "description": "Indica se a classificação encontrou dado sensível." }, "duplicata_de": { "type": ["string", "null"], "format": "uuid", "description": "Anexo original quando o arquivo é duplicata de outro já processado." }, "via": { "type": "string", "enum": ["captura", "confirmacao", "conclusao"], "description": "Origem da evidência: captura, confirmação social ou conclusão." }, "categoria_sensivel": { "type": ["string", "null"], "enum": ["nsfw", "pessoa", "documento", null], "description": "Classificação de sensibilidade. Null quando não aplicável." }, "motivo_sensivel": { "type": ["string", "null"], "description": "Motivo da sinalização de sensibilidade." }, "moderacao_status": { "type": "string", "enum": ["nao_aplicavel", "pendente", "aprovado", "bloqueado"], "description": "Posição do anexo na fila de moderação." } }}3.4.4 demanda.georreferenciada
Seção intitulada “3.4.4 demanda.georreferenciada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | georreferenciada |
| Produtor | D-2 (Georreferenciamento) |
| Consumidores | D-3 (Categorização — projeção local de geo), D-4 (Priorização — complementa o contexto territorial), D-7 (Transparência) |
| Descrição | Demanda com unidade cívica resolvida e cadeia de UCs pai completa. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.georreferenciada/1.0.0", "title": "demanda.georreferenciada", "type": "object", "required": ["demanda_id", "unidade_civica_id", "nivel_minimo_resolvido", "cadeia_ucs", "metodo_resolucao", "confianca_geo"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC de menor nível resolvida (micro-unidade cívica)." }, "nivel_minimo_resolvido": { "type": "integer", "minimum": 1, "maximum": 7, "description": "Nível da UC de menor nível resolvida. Informa a granularidade da resolução." }, "cadeia_ucs": { "type": "array", "minItems": 1, "maxItems": 7, "items": { "type": "string", "format": "uuid" }, "description": "Lista de UC ids do menor ao maior nível (nível 1 a nível 7)." }, "metodo_resolucao": { "type": "string", "enum": ["gps", "endereco", "inferencia"], "description": "Método usado para resolver a localização." }, "confianca_geo": { "type": "string", "enum": ["alta", "media", "baixa"], "description": "GPS direto = alta. Endereço digitado = média. Inferência por descrição = baixa." } }}3.4.5 demanda.categorizada
Seção intitulada “3.4.5 demanda.categorizada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | categorizada |
| Produtor | D-3 (Categorização) |
| Consumidores | D-4 (Priorização), D-7 (Transparência), D-12 (Detecção de Duplicidade) |
| Descrição | Demanda classificada dentro da taxonomia de categorias. Inclui nível de precedência e score horizontal. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.categorizada/1.1.0", "title": "demanda.categorizada", "type": "object", "required": ["demanda_id", "categoria_id", "nivel_precedencia", "score_horizontal", "confianca_categorizacao", "metodo"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "categoria_id": { "type": "string", "pattern": "^\\d+\\.\\d+$", "description": "Identificador da categoria principal (ex: 1.1, 3.4)." }, "nivel_precedencia": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Nível de precedência estrutural da categoria (1 = infraestrutura de sobrevivência, 5 = inovação e estrutura cívica)." }, "score_horizontal": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Posição relativa da categoria dentro do seu nível (desempate)." }, "confianca_categorizacao": { "type": "number", "minimum": 0, "maximum": 1, "description": "Score de confiança do classificador. Abaixo do limiar parametrizado, revisão manual." }, "metodo": { "type": "string", "enum": ["automatico", "manual", "revisao_pendente"], "description": "Como a categorização foi determinada." }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC de menor nível resolvida. Campo opcional da versão 1.1.0: permite que a D-4 posicione a demanda no ranking da UC sem projeção local de geo." }, "nivel_minimo_resolvido": { "type": "integer", "minimum": 1, "maximum": 7, "description": "Nível da UC de menor nível. Campo opcional da versão 1.1.0." }, "sugestoes_alternativas": { "type": "array", "items": { "type": "object", "required": ["categoria_id", "score_confianca"], "properties": { "categoria_id": { "type": "string" }, "score_confianca": { "type": "number", "minimum": 0, "maximum": 1 } } }, "description": "Categorias candidatas com score de confiança, ordenadas por relevância." } }}3.4.6 demanda.ranqueada
Seção intitulada “3.4.6 demanda.ranqueada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | ranqueada |
| Produtor | D-4 (Priorização e Ranking) |
| Consumidores | D-5 (Agenda), D-7 (Transparência) |
| Descrição | Demanda com score final calculado e breakdown completo dos fatores. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.ranqueada/1.1.0", "title": "demanda.ranqueada", "type": "object", "required": ["demanda_id", "unidade_civica_id", "categoria_id", "nivel_precedencia", "score_final", "breakdown", "posicao_no_ranking", "total_demandas_na_uc", "versao_parametros", "timestamp_calculo"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC para a qual a posição foi calculada." }, "categoria_id": { "type": "string", "description": "Categoria da demanda, usada pela D-5 na projeção do backlog." }, "nivel_precedencia": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Nível de precedência da categoria." }, "score_final": { "type": "number", "minimum": 0, "description": "Score calculado: peso_nacional x peso_situacional x score_horizontal." }, "breakdown": { "type": "object", "required": ["peso_nacional", "peso_situacional", "score_horizontal"], "properties": { "peso_nacional": { "type": "number", "description": "Peso estrutural do nível de precedência." }, "peso_situacional": { "type": "number", "description": "Peso dinâmico do gap da UC naquele nível." }, "score_horizontal": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Score horizontal da categoria." } }, "description": "Fatores que compõem o score_final. Multiplicar os três valores reproduz o score. Fatores de ajuste (urgência, risco) são Fase 2." }, "posicao_no_ranking": { "type": "integer", "minimum": 1, "description": "Posição da demanda no ranking da UC. 1 = topo." }, "total_demandas_na_uc": { "type": "integer", "minimum": 1, "description": "Total de demandas no ranking da UC no momento do cálculo." }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros de priorização vigentes no cálculo. MVP: 'mvp-v1'." }, "timestamp_calculo": { "type": "string", "format": "date-time", "description": "Momento do cálculo do score." } }}3.4.7 ranking.atualizado
Seção intitulada “3.4.7 ranking.atualizado”| Propriedade | Valor |
|---|---|
| Domínio | ranking |
| Entidade | ranking |
| Ação | atualizado |
| Produtor | D-4 (Priorização e Ranking) |
| Consumidores | D-5 (Agenda), D-7 (Transparência) |
| Descrição | Publicado quando o ranking de uma UC muda de forma significativa. Evento agregado, não individual. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "ranking.atualizado/1.0.0", "title": "ranking.atualizado", "type": "object", "required": ["unidade_civica_id", "motivo", "demanda_id_gatilho", "top_10", "total_demandas", "versao_parametros", "timestamp"], "properties": { "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC cujo ranking foi atualizado." }, "motivo": { "type": "string", "enum": ["demanda_top10", "primeira_posicao_alterada"], "description": "Motivo da publicação: nova demanda entrou no top 10 ou primeira posição foi alterada." }, "demanda_id_gatilho": { "type": "string", "format": "uuid", "description": "Demanda que causou a mudança significativa." }, "top_10": { "type": "array", "maxItems": 10, "items": { "type": "object", "required": ["posicao", "demanda_id", "score_final"], "properties": { "posicao": { "type": "integer", "minimum": 1 }, "demanda_id": { "type": "string", "format": "uuid" }, "score_final": { "type": "number" }, "categoria_id": { "type": "string" } } }, "description": "Top 10 demandas do ranking após a atualização." }, "total_demandas": { "type": "integer", "minimum": 0, "description": "Número total de demandas no ranking da UC." }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros vigentes. MVP: 'mvp-v1'." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da publicação." } }}3.4.8 agenda.gerada
Seção intitulada “3.4.8 agenda.gerada”| Propriedade | Valor |
|---|---|
| Domínio | agenda |
| Entidade | agenda |
| Ação | gerada |
| Produtor | D-5 (Agenda) |
| Consumidores | D-7 (Transparência) |
| Descrição | Backlog de uma UC foi (re)construído a partir do ranking, com distribuição por decaimento e agrupamentos territoriais. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "agenda.gerada/1.0.0", "title": "agenda.gerada", "type": "object", "required": ["unidade_civica_id", "total_itens", "distribuicao_decay", "top_10", "grupos_territoriais", "versao_parametros", "timestamp"], "properties": { "unidade_civica_id": { "type": "string", "format": "uuid" }, "total_itens": { "type": "integer", "minimum": 0, "description": "Número total de itens no backlog após decaimento e clustering." }, "distribuicao_decay": { "type": "object", "required": ["nivel_nao_vencido", "itens_nivel_prioritario", "itens_outros_niveis"], "properties": { "nivel_nao_vencido": { "type": ["integer", "null"], "minimum": 1, "maximum": 5, "description": "Nível de precedência com maior peso situacional acima do limiar. Null quando nenhum nível está acima do limiar." }, "itens_nivel_prioritario": { "type": "integer", "minimum": 0, "description": "Quantos itens vieram do nível não vencido (alocados pela fórmula de decaimento geométrico)." }, "itens_outros_niveis": { "type": "integer", "minimum": 0, "description": "Quantos itens vieram dos demais níveis (alocados pela fórmula de decaimento geométrico)." } }, "description": "Resultado da distribuição por decaimento geométrico aplicada sobre o ranking." }, "top_10": { "type": "array", "maxItems": 10, "items": { "type": "object", "required": ["posicao", "demanda_id", "score_final", "categoria_id"], "properties": { "posicao": { "type": "integer", "minimum": 1 }, "demanda_id": { "type": "string", "format": "uuid" }, "score_final": { "type": "number" }, "categoria_id": { "type": "string" }, "grupo_id": { "type": ["string", "null"], "description": "ID do agrupamento territorial. Null se demanda solo." } } }, "description": "Top 10 itens do backlog." }, "grupos_territoriais": { "type": "integer", "minimum": 0, "description": "Quantos grupos DBSCAN foram gerados. 0 se nenhum." }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros vigentes. MVP: 'mvp-v1'." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da geração da agenda." } }}3.4.9 agenda.item_disponível
Seção intitulada “3.4.9 agenda.item_disponível”| Propriedade | Valor |
|---|---|
| Domínio | agenda |
| Entidade | item |
| Ação | disponível |
| Produtor | D-5 (Agenda) |
| Consumidores | D-6a (Sorteio e Atribuição) |
| Descrição | Uma demanda entrou no topo do backlog e está disponível para atribuição a um conselheiro. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "agenda.item_disponível/1.0.0", "title": "agenda.item_disponível", "type": "object", "required": ["demanda_id", "unidade_civica_id", "posicao_no_backlog", "score_final", "categoria_id", "nivel_precedencia"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "posicao_no_backlog": { "type": "integer", "minimum": 1, "description": "Posição no backlog da UC (após decaimento e clustering). 1 = topo da fila." }, "score_final": { "type": "number", "minimum": 0 }, "categoria_id": { "type": "string" }, "nivel_precedencia": { "type": "integer", "minimum": 1, "maximum": 5 }, "grupo_id": { "type": ["string", "null"], "description": "ID do agrupamento territorial. Presente apenas quando a demanda pertence a um grupo DBSCAN." } }}3.4.10 conselheiro.cadastrado
Seção intitulada “3.4.10 conselheiro.cadastrado”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | conselheiro |
| Ação | cadastrado |
| Produtor | Interface de cadastro (BFF da D-1a ou módulo dedicado) |
| Consumidores | D-6a (Sorteio e Atribuição) |
| Descrição | Novo cidadão se cadastrou como candidato a conselheiro em uma unidade cívica. |
Schema do payload (v1.1.0 — MINOR: campo capacitacao_concluida obrigatório para a declaração de capacitação):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.cadastrado/1.1.0", "title": "conselheiro.cadastrado", "type": "object", "required": ["conselheiro_id", "cidadao_id", "unidade_civica_id", "nivel_atuacao", "capacitacao_concluida"], "properties": { "conselheiro_id": { "type": "string", "format": "uuid", "description": "Identificador único do registro de conselheiro. No MVP é o próprio cidadao_id (identidade unificada)." }, "cidadao_id": { "type": "string", "format": "uuid", "description": "Identificador do cidadão. Um cidadão pode ser conselheiro em múltiplos níveis." }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC onde o conselheiro se candidatou." }, "nivel_atuacao": { "type": "integer", "minimum": 1, "maximum": 7, "description": "Nível da UC de atuação pretendida." }, "capacitacao_concluida": { "type": "boolean", "description": "Se o cidadão concluiu a capacitação básica obrigatória. Obrigatório em v1.1.0. Na 1.0.0 o campo não existe e a D-6a assume default true." }, "status": { "type": "string", "enum": ["disponivel", "ocupado", "inativo"], "default": "disponivel" } }}3.4.11 conselheiro.sorteado
Seção intitulada “3.4.11 conselheiro.sorteado”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | conselheiro |
| Ação | sorteado |
| Produtor | D-6a (Sorteio e Atribuição) |
| Consumidores | D-6b (Relatoria), D-5 (Agenda), D-7 (Transparência) |
| Descrição | Um conselheiro foi sorteado e atribuído a uma demanda. O seed do sorteio é público e verificável. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.sorteado/1.0.0", "title": "conselheiro.sorteado", "type": "object", "required": ["conselheiro_id", "demanda_id", "unidade_civica_id", "metodo_sorteio", "seed_publico"], "properties": { "conselheiro_id": { "type": "string", "format": "uuid" }, "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "metodo_sorteio": { "type": "string", "enum": ["fisher_yates"], "description": "Algoritmo de ordenação usado no sorteio." }, "seed_publico": { "type": "string", "description": "Seed derivado de hash do último evento do barramento + timestamp. Permite verificação independente." }, "lista_elegiveis_hash": { "type": "string", "description": "Hash SHA-256 da lista de conselheiros elegíveis no momento do sorteio." }, "posicao_sorteada": { "type": "integer", "minimum": 0, "description": "Índice do conselheiro selecionado na lista ordenada pelo seed (0-based)." }, "total_elegiveis": { "type": "integer", "minimum": 1, "description": "Número total de conselheiros elegíveis no momento do sorteio." } }}3.4.12 sorteio.sem_candidatos
Seção intitulada “3.4.12 sorteio.sem_candidatos”| Propriedade | Valor |
|---|---|
| Domínio | sorteio |
| Entidade | sorteio |
| Ação | sem_candidatos |
| Produtor | D-6a (Sorteio e Atribuição) |
| Consumidores | Sistema de notificação, D-7 (Transparência) |
| Descrição | Há demanda disponível para atribuição mas nenhum conselheiro elegível na UC. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "sorteio.sem_candidatos/1.0.0", "title": "sorteio.sem_candidatos", "type": "object", "required": ["demanda_id", "unidade_civica_id", "total_elegiveis", "motivo"], "properties": { "demanda_id": { "type": "string", "format": "uuid", "description": "Demanda que ficou sem conselheiro." }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "total_elegiveis": { "type": "integer", "minimum": 0, "description": "Número de conselheiros elegíveis encontrados." }, "motivo": { "type": "string", "enum": ["nenhum_cadastrado", "todos_ocupados", "todos_inativos"], "description": "Razão pela qual não há candidatos disponíveis." } }}3.4.13 conselheiro.demanda_iniciada
Seção intitulada “3.4.13 conselheiro.demanda_iniciada”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | demanda |
| Ação | iniciada |
| Produtor | D-6b (Relatoria e Acompanhamento) |
| Consumidores | D-5 (Agenda — transição atribuido → em_progresso), D-7 (Transparência) |
| Descrição | Conselheiro fez o primeiro contato formal. Publicado automaticamente na primeira atualização registrada para o par conselheiro⇄demanda, sem exigir ação explícita de “iniciar”. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.demanda_iniciada/1.0.0", "title": "conselheiro.demanda_iniciada", "type": "object", "required": ["demanda_id", "conselheiro_id", "unidade_civica_id", "tipo_primeira_acao", "timestamp_inicio"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "conselheiro_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "tipo_primeira_acao": { "type": "string", "enum": ["contato_realizado", "protocolo_aberto", "documento_anexado", "entrave_registrado", "status_atualizado", "prazo_registrado"], "description": "Tipo da atualização que disparou o início do acompanhamento." }, "timestamp_inicio": { "type": "string", "format": "date-time", "description": "Momento em que a primeira atualização foi publicada." } }}3.4.14 conselheiro.atualização_publicada
Seção intitulada “3.4.14 conselheiro.atualização_publicada”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | atualização |
| Ação | publicada |
| Produtor | D-6b (Relatoria e Acompanhamento) |
| Consumidores | D-7 (Transparência — timeline da demanda), D-24 (Memória de Caminhos — campos estruturados do caminho) e D-1d (Moderação — relato sinalizado) |
| Descrição | Atualização do conselheiro processada, validada e publicada. Contém o texto final e os campos estruturados. A versão corrente é a 1.2.0: a 1.1.0 adicionou origem_texto, modelo_transcricao e confianca_transcricao opcionais para marcar o relato que veio de transcrição automática, e a 1.2.0 adicionou conteudo_suspeito e termos_suspeitos para marcar o relato retido pela denylist de texto. A atualização digitada limpa continua na 1.0.0, sem os campos novos. O campo opcional caminho carrega cópia sanitizada dos campos do caminho usados pela D-24, para o rebuild reconstruir o perfil sem depender do conteudo_estruturado, que a redação do log remove. |
Schema do payload (versão 1.2.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.atualização_publicada/1.2.0", "title": "conselheiro.atualização_publicada", "type": "object", "required": ["atualizacao_id", "demanda_id", "conselheiro_id", "unidade_civica_id", "tipo", "texto_estruturado", "descricao_sanitizada", "numero_sequencial"], "properties": { "atualizacao_id": { "type": "string", "format": "uuid", "description": "ID interno da atualização." }, "demanda_id": { "type": "string", "format": "uuid" }, "conselheiro_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "tipo": { "type": "string", "enum": ["contato_realizado", "protocolo_aberto", "documento_anexado", "entrave_registrado", "status_atualizado", "prazo_registrado"], "description": "Tipo estruturado de atualização." }, "texto_estruturado": { "type": "string", "maxLength": 10000, "description": "Texto final revisado pelo conselheiro. É o texto publicado." }, "descricao_sanitizada": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "Descrição após o pipeline de redação de PII, usada na projeção pública." }, "conteudo_estruturado": { "type": "object", "description": "Campos estruturados específicos do tipo de atualização (ex: protocolo_numero, orgao, data_contato). Schema varia por tipo." }, "caminho": { "type": "object", "description": "Cópia sanitizada dos campos do caminho (canal, contato_orgao, orgao, protocolo_numero, tipo_documento, descricao, orgao_envolvido, descricao_entrave e data_estimada) usada pela D-24 e preservada pela redação para o rebuild." }, "origem_estruturacao": { "type": "string", "enum": ["conselheiro", "ia_assistida"], "description": "Se a estruturação foi feita diretamente pelo conselheiro ou com assistência de IA." }, "origem_texto": { "type": "string", "enum": ["conselheiro", "automatico"], "description": "Marca o texto que veio de transcrição automática. Ausente na atualização digitada." }, "modelo_transcricao": { "type": "string", "maxLength": 200, "description": "Modelo que produziu a transcrição automática." }, "confianca_transcricao": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confiança da transcrição, conforme o limiar publicado." }, "numero_sequencial": { "type": "integer", "minimum": 1, "description": "Sequência da atualização no acompanhamento (1, 2, 3...)." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da publicação." }, "conteudo_suspeito": { "type": "boolean", "description": "Indica que a denylist de texto encontrou termos no conteúdo. Campo opcional da versão 1.2.0." }, "termos_suspeitos": { "type": "array", "items": { "type": "string", "maxLength": 100 }, "maxItems": 50, "description": "Termos da denylist encontrados no texto. Campo opcional da versão 1.2.0." } }}3.4.15 conselheiro.prazo_próximo
Seção intitulada “3.4.15 conselheiro.prazo_próximo”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | prazo |
| Ação | próximo |
| Produtor | D-6b (Relatoria e Acompanhamento — CronJob a cada 6h) |
| Consumidores | Sistema de notificação (Fase 2), D-7 (Transparência) |
| Descrição | Um prazo registrado está próximo do vencimento. Publicado N dias antes da data estimada (padrão: 3 dias). |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.prazo_próximo/1.0.0", "title": "conselheiro.prazo_próximo", "type": "object", "required": ["demanda_id", "conselheiro_id", "unidade_civica_id", "prazo_id", "data_estimada", "dias_restantes"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "conselheiro_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "prazo_id": { "type": "string", "format": "uuid", "description": "ID do prazo em d6b.prazos." }, "data_estimada": { "type": "string", "format": "date", "description": "Data limite." }, "dias_restantes": { "type": "integer", "description": "Dias até a data estimada." }, "descricao_prazo": { "type": "string", "description": "Descrição do que se espera até a data." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da publicação do alerta." } }}3.4.16 conselheiro.ciclo_concluído
Seção intitulada “3.4.16 conselheiro.ciclo_concluído”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | ciclo |
| Ação | concluído |
| Produtor | D-6b (Relatoria e Acompanhamento) |
| Consumidores | D-6a (Sorteio — libera conselheiro), D-7 (Transparência), D-16 (Controle de Mandato, Fase 2) |
| Descrição | Ciclo de acompanhamento do conselheiro foi encerrado. Publicado após demanda.concluída quando a demanda é resolvida; publicado isoladamente em caso de fim de mandato ou desistência. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.ciclo_concluído/1.0.0", "title": "conselheiro.ciclo_concluído", "type": "object", "required": ["conselheiro_id", "demanda_id", "unidade_civica_id", "motivo_encerramento", "total_atualizacoes", "data_inicio", "data_fim"], "properties": { "conselheiro_id": { "type": "string", "format": "uuid" }, "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "motivo_encerramento": { "type": "string", "enum": ["demanda_concluida", "fim_mandato", "desistencia"], "description": "Razão do encerramento do ciclo." }, "total_atualizacoes": { "type": "integer", "minimum": 0, "description": "Número total de atualizações publicadas durante o ciclo." }, "data_inicio": { "type": "string", "format": "date-time", "description": "Timestamp do início efetivo do acompanhamento (primeira atualização). Nulo se o conselheiro nunca iniciou." }, "data_fim": { "type": "string", "format": "date-time", "description": "Timestamp do encerramento do ciclo." }, "dias_duracao": { "type": "integer", "minimum": 0, "description": "Dias entre data_inicio e data_fim. 0 se nunca iniciou." }, "resumo_final": { "type": "string", "maxLength": 3000, "description": "Resumo do desfecho em linguagem neutra." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento de publicação do evento." } }}3.4.17 conselheiro.atribuicao_recusada
Seção intitulada “3.4.17 conselheiro.atribuicao_recusada”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | atribuição |
| Ação | recusada |
| Produtor | BFF da D-1a |
| Consumidores | D-6a (Sorteio e Atribuição) |
| Descrição | Conselheiro recusou explicitamente a atribuição de uma demanda. O BFF recebe a requisição do front-end, publica o evento e retorna confirmação. A D-6a processa o re-sorteio de forma assíncrona. |
Schema do payload (1.1.0 — MINOR: motivo risco_pessoal no enum, sem penalização):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.atribuicao_recusada/1.1.0", "title": "conselheiro.atribuicao_recusada", "type": "object", "required": ["conselheiro_id", "demanda_id", "unidade_civica_id", "timestamp"], "properties": { "conselheiro_id": { "type": "string", "format": "uuid", "description": "Conselheiro que recusou a atribuição." }, "demanda_id": { "type": "string", "format": "uuid", "description": "Demanda cuja atribuição foi recusada." }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC onde ocorreu o sorteio." }, "motivo": { "type": "string", "enum": ["recusa_explicita", "risco_pessoal", "timeout_resposta", "fora_do_territorio"], "default": "recusa_explicita", "description": "Razão da recusa. risco_pessoal, da versão 1.1.0, encerra a atribuição e re-sorteia sem contar no contador de recusas e sem suspensão. timeout_resposta e fora_do_territorio são Fase 2." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da recusa." } }}Versão 1.0.0: permanece no catálogo com o enum ["recusa_explicita", "timeout_resposta", "fora_do_territorio"].
3.4.18 conselheiro.atualização_registrada
Seção intitulada “3.4.18 conselheiro.atualização_registrada”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | atualização |
| Ação | registrada |
| Schema version | 1.0.0 |
| Produtor | BFF da D-1a |
| Consumidores | D-6b (Relatoria e Acompanhamento) |
| Descrição | Conselheiro revisou, confirmou e publicou uma atualização. O BFF recebe a confirmação do front-end, publica este evento no barramento e retorna confirmação síncrona ao usuário. A D-6b processa de forma assíncrona. O payload contém três blocos: texto bruto (original), texto estruturado (versão final do conselheiro) e sugestão da IA (para auditoria). A versão corrente é a 1.1.0: ela adiciona audio_object_key opcional e permite texto vazio quando há áudio, que a D-1b transcreve em segundo plano. |
Schema do payload (versão 1.1.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.atualização_registrada/1.1.0", "title": "conselheiro.atualização_registrada", "type": "object", "required": ["conselheiro_id", "demanda_id", "unidade_civica_id", "tipo", "texto_bruto", "texto_estruturado", "timestamp"], "properties": { "conselheiro_id": { "type": "string", "format": "uuid" }, "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "tipo": { "type": "string", "enum": ["contato_realizado", "protocolo_aberto", "documento_anexado", "entrave_registrado", "status_atualizado", "prazo_registrado"], "description": "Tipo estruturado de atualização." }, "texto_bruto": { "type": "string", "maxLength": 10000, "description": "Texto original do conselheiro, antes de qualquer processamento. Preservado para auditoria. Vazio quando o relato foi gravado em áudio." }, "texto_estruturado": { "type": "string", "maxLength": 10000, "description": "Versão final revisada pelo conselheiro. Vazio quando o relato foi gravado em áudio, com o texto aplicado na transcrição." }, "audio_object_key": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]*$", "description": "Chave do objeto de áudio no caminho privado do cidadão. Restrita à auditoria." }, "conteudo_estruturado": { "type": "object", "description": "Campos estruturados específicos do tipo de atualização. Schema varia por tipo. Ex: protocolo_numero, orgao, data_estimada." }, "sugestao_ia": { "type": "object", "description": "O que a IA sugeriu antes da revisão do conselheiro. Presente apenas se origem_estruturacao = 'ia_assistida'.", "properties": { "titulo_sugerido": { "type": "string" }, "texto_sugerido": { "type": "string" }, "campos_estruturados": { "type": "object" }, "score_confianca": { "type": "number", "minimum": 0, "maximum": 1 } } }, "origem_estruturacao": { "type": "string", "enum": ["conselheiro", "ia_assistida"], "default": "conselheiro", "description": "Se a estruturação foi feita diretamente pelo conselheiro ou com assistência de IA." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da confirmação pelo conselheiro." } }, "allOf": [ { "if": { "not": { "required": ["audio_object_key"] } }, "then": { "properties": { "texto_bruto": { "minLength": 1 }, "texto_estruturado": { "minLength": 1 } } } } ]}A 1.0.0 permanece no catálogo e exige texto não vazio. Na 1.1.0, o texto só pode vir vazio quando audio_object_key está presente; sem a chave, o mínimo de 1 caractere continua valendo.
3.4.19 lugar.recebido
Seção intitulada “3.4.19 lugar.recebido”| Propriedade | Valor |
|---|---|
| Domínio | lugar |
| Entidade | lugar |
| Ação | recebido |
| Produtor | BFF da D-1a |
| Consumidores | L-1 (Cadastro de Lugares) |
| Descrição | Input bruto de lugar submetido pelo cidadão. Análogo a demanda.recebida no fluxo de lugares. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "lugar.recebido/1.0.0", "title": "lugar.recebido", "type": "object", "required": ["tipo_lugar", "posicao", "cidadao_id"], "properties": { "tipo_lugar": { "type": "string", "enum": ["residencia", "organizacao", "equipamento_publico", "poligono_uc"], "description": "Tipo macro do lugar." }, "subtipo": { "type": "string", "description": "Subtipo do lugar (ex: farmacia, mercado, ubs, escola)." }, "nome": { "type": "string", "maxLength": 200, "description": "Nome do lugar." }, "descricao": { "type": "string", "maxLength": 2000, "description": "Texto livre opcional." }, "posicao": { "type": "object", "required": ["lat", "lng"], "properties": { "lat": { "type": "number", "minimum": -90, "maximum": 90 }, "lng": { "type": "number", "minimum": -180, "maximum": 180 } } }, "horario_funcionamento": { "type": "string", "maxLength": 200, "description": "Horário de funcionamento como texto livre." }, "cidadao_id": { "type": "string", "format": "uuid" }, "canal": { "type": "string", "enum": ["app", "web"], "default": "app" } }}Campo adicional assistencia. O payload pode carregar assistencia fora do schema declarado, no padrão de midia_urls (o schema não usa additionalProperties: false). O campo registra a auditoria de uso da assistência de texto (usada, motor, versao e aplicadas), não contém dado pessoal, não guarda o texto anterior nem as sugestões e é preservado pela redação do core.event_log. Sem bump de versão.
3.4.20 lugar.cadastrado
Seção intitulada “3.4.20 lugar.cadastrado”| Propriedade | Valor |
|---|---|
| Domínio | lugar |
| Entidade | lugar |
| Ação | cadastrado |
| Produtor | L-1 (Cadastro de Lugares) |
| Consumidores | L-2 (Georreferenciamento e Tipificação), L-3 (Validação e Qualidade), D-7 (Transparência) |
| Descrição | Input de lugar validado e registrado no sistema. Status pendente_georreferenciamento. A versão corrente é a 1.2.0: a 1.1.0 adicionou alerta_proximidade e a 1.2.0 adicionou descricao como campo opcional. |
Schema do payload (versão 1.2.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "lugar.cadastrado/1.2.0", "title": "lugar.cadastrado", "type": "object", "required": ["lugar_id", "tipo_lugar", "coordenadas_brutas", "cidadao_id"], "properties": { "lugar_id": { "type": "string", "format": "uuid", "description": "Identificador único do lugar gerado pela L-1." }, "tipo_lugar": { "type": "string", "enum": ["residencia", "organizacao", "equipamento_publico", "poligono_uc"] }, "subtipo": { "type": "string" }, "nome": { "type": "string", "maxLength": 200 }, "descricao": { "type": "string", "maxLength": 5000, "description": "Conteúdo público do lugar, exibido no painel de detalhes. Campo opcional da versão 1.2.0." }, "coordenadas_brutas": { "type": "object", "required": ["lat", "lng"], "properties": { "lat": { "type": "number", "minimum": -90, "maximum": 90 }, "lng": { "type": "number", "minimum": -180, "maximum": 180 } } }, "alerta_proximidade": { "type": "object", "description": "Metadado de deduplicação preliminar. Presente apenas quando a L-1 detecta lugar próximo.", "required": ["lugar_id_proximo", "distancia_metros"], "properties": { "lugar_id_proximo": { "type": "string", "format": "uuid", "description": "lugar_id do registro mais próximo já existente." }, "distancia_metros": { "type": "number", "minimum": 0, "description": "Distância em metros entre os dois pontos." } } }, "cidadao_id": { "type": "string", "format": "uuid" }, "canal": { "type": "string", "enum": ["app", "web"] }, "horario_funcionamento": { "type": "string", "maxLength": 200 } }}3.4.21 lugar.georreferenciado
Seção intitulada “3.4.21 lugar.georreferenciado”| Propriedade | Valor |
|---|---|
| Domínio | lugar |
| Entidade | lugar |
| Ação | georreferenciado |
| Produtor | L-2 (Georreferenciamento e Tipificação) |
| Consumidores | E-1 (Cadastro Institucional) |
| Descrição | Lugar com UC resolvida, cadeia de UCs pai e tipificação com nível de confiança. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "lugar.georreferenciado/1.0.0", "title": "lugar.georreferenciado", "type": "object", "required": ["lugar_id", "unidade_civica_id", "nivel_minimo_resolvido", "cadeia_ucs", "metodo_resolucao", "confianca_geo", "confianca_tipificacao"], "properties": { "lugar_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "nivel_minimo_resolvido": { "type": "integer", "minimum": 1, "maximum": 7, "description": "Nível da UC resolvida. Informa a granularidade da resolução territorial." }, "cadeia_ucs": { "type": "array", "minItems": 1, "maxItems": 7, "items": { "type": "string", "format": "uuid" } }, "metodo_resolucao": { "type": "string", "enum": ["gps", "endereco", "inferencia"] }, "confianca_geo": { "type": "string", "enum": ["alta", "media", "baixa"] }, "tipo_lugar": { "type": "string", "enum": ["residencia", "organizacao", "equipamento_publico"], "description": "Tipo macro do lugar." }, "subtipo_declarado": { "type": "string", "description": "Subtipo original do payload de lugar.cadastrado." }, "subtipo_confirmado": { "type": "string", "description": "Subtipo após enriquecimento. Igual ao declarado se confirmado, diferente se houve divergência, NULL se enriquecimento não encontrou correspondência." }, "confianca_tipificacao": { "type": "string", "enum": ["alta", "media", "baixa", "nao_avaliada"], "description": "Confiança na tipificação após enriquecimento com bases externas (OSM). nao_avaliada = OSM indisponível ou tipo_lugar = 'residencia'." }, "enriquecimento": { "type": "object", "description": "Dados adicionais obtidos de bases externas. Opcional — ausente quando nao_avaliada.", "properties": { "fonte": { "type": "string", "enum": ["osm", "cnpj", "cnes", "inep"] }, "dados": { "type": "object" }, "divergencia": { "type": "object", "description": "Presente quando OSM sugere tipo diferente do declarado.", "properties": { "declarado": { "type": "string" }, "encontrado": { "type": "string" }, "distancia_metros": { "type": "number", "minimum": 0 } } } } } }}3.4.22 empresa.cadastrada
Seção intitulada “3.4.22 empresa.cadastrada”| Propriedade | Valor |
|---|---|
| Domínio | empresa |
| Entidade | empresa |
| Ação | cadastrada |
| Produtor | E-1 (Cadastro Institucional) |
| Consumidores | E-2, E-3 (Simulação Econômica — projeção local de empresas) |
| Descrição | Organização registrada no sistema com razão social, porte, setor e localização declarada. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.cadastrada/1.0.0", "title": "empresa.cadastrada", "type": "object", "required": ["empresa_id", "razao_social", "porte", "setor_id", "quantidade_enderecos", "representante_id", "termos_aceitos_em", "status"], "properties": { "empresa_id": { "type": "string", "format": "uuid", "description": "Identificador único da empresa no sistema." }, "cnpj": { "type": "string", "maxLength": 18, "description": "CNPJ com máscara (XX.XXX.XXX/XXXX-XX). Opcional no MVP." }, "razao_social": { "type": "string", "minLength": 1, "maxLength": 300, "description": "Razão social declarada." }, "nome_fantasia": { "type": "string", "maxLength": 300, "description": "Nome fantasia declarado. Opcional." }, "porte": { "type": "string", "enum": ["micro", "pequena", "media", "grande"], "description": "Porte declarado pela empresa." }, "setor_id": { "type": "string", "maxLength": 20, "description": "Identificador do setor CNAE macro (ex: comercio, industria, saude)." }, "site": { "type": "string", "format": "uri", "maxLength": 500, "description": "URL do site da empresa. Opcional." }, "quantidade_enderecos": { "type": "integer", "minimum": 1, "description": "Número de endereços de operação declarados. Cada endereço gera um lugar.recebido no barramento." }, "representante_id": { "type": "string", "format": "uuid", "description": "cidadão_id do representante que cadastrou a empresa." }, "termos_aceitos_em": { "type": "string", "format": "date-time", "description": "Timestamp de quando o representante aceitou os termos de transparência." }, "status": { "type": "string", "enum": ["cadastrada", "socializada", "pendente", "recusada"], "description": "Status da empresa no sistema. Inicia como cadastrada." } }}Campo adicional assistencia. O payload pode carregar assistencia fora do schema declarado, no padrão de midia_urls (o schema não usa additionalProperties: false). O campo registra a auditoria de uso da assistência de texto (usada, motor, versao e aplicadas), não contém dado pessoal, não guarda o texto anterior nem as sugestões e é preservado pela redação do core.event_log. Sem bump de versão.
#### 3.4.23 `empresa.associação_territorial_definida`
| Propriedade | Valor ||---|---|| Domínio | `empresa` || Entidade | `associação_territorial` || Ação | `definida` || Produtor | E-1 (Cadastro Institucional) || Consumidores | E-4 (Fase 2 — Impacto Territorial) || Descrição | Empresa vinculada a uma unidade cívica. Publicado incrementalmente, um evento por endereço resolvido pela L-2. A E-3 não consome este evento: o rateio por UC saiu do MVP. O campo `quantidade_ucs_empresa` permanece para consumo da E-4. |
**Schema do payload:**
```json{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.associação_territorial_definida/1.0.0", "title": "empresa.associação_territorial_definida", "type": "object", "required": ["empresa_id", "endereco_id", "unidade_civica_id", "cadeia_ucs", "tipo_associacao", "lugar_id", "confianca_geo", "quantidade_ucs_empresa"], "properties": { "empresa_id": { "type": "string", "format": "uuid" }, "endereco_id": { "type": "string", "format": "uuid", "description": "Endereço que originou esta associação. FK lógica para e1.empresa_enderecos." }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "UC de menor nível resolvida para este endereço." }, "cadeia_ucs": { "type": "array", "minItems": 1, "maxItems": 7, "items": { "type": "string", "format": "uuid" }, "description": "Cadeia completa de UCs pai, do menor ao maior nível." }, "tipo_associacao": { "type": "string", "enum": ["primaria", "secundaria"], "description": "primaria = endereço tipo sede, secundaria = endereço tipo filial." }, "lugar_id": { "type": "string", "format": "uuid", "description": "lugar_id gerado pela L-1 que originou esta associação." }, "confianca_geo": { "type": "string", "enum": ["alta", "media", "baixa"], "description": "Confiança da resolução geográfica." }, "quantidade_ucs_empresa": { "type": "integer", "minimum": 1, "description": "Total de UCs já associadas à empresa (incluindo esta). Permite que consumidores saibam se todos os endereços foram resolvidos." } }}3.4.24 empresa.folha_submetida
Seção intitulada “3.4.24 empresa.folha_submetida”| Propriedade | Valor |
|---|---|
| Domínio | empresa |
| Entidade | folha |
| Ação | submetida |
| Produtor | E-2 (face BFF da folha) |
| Consumidores | E-3 (Simulação Econômica — total_colaboradores para cálculo de valor_por_trabalhador) |
| Descrição | Empresa submeteu a folha salarial anonimizada para diagnóstico: totais e hash dos cargos, sem salários individuais no evento. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.folha_submetida/1.0.0", "title": "empresa.folha_submetida", "type": "object", "required": ["empresa_id", "submissao_id", "total_colaboradores", "quantidade_cargos", "cargos_hash"], "properties": { "empresa_id": { "type": "string", "format": "uuid" }, "submissao_id": { "type": "string", "format": "uuid", "description": "Identificador único da submissão." }, "total_colaboradores": { "type": "integer", "minimum": 1, "description": "Número total de colaboradores declarados." }, "quantidade_cargos": { "type": "integer", "minimum": 1, "description": "Quantidade de cargos distintos declarados." }, "cargos_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Hash SHA-256 da lista de cargos. Sustenta a idempotência da submissão sem expor os salários." } }}3.4.25 empresa.diagnóstico_salarial_publicado
Seção intitulada “3.4.25 empresa.diagnóstico_salarial_publicado”| Propriedade | Valor |
|---|---|
| Domínio | empresa |
| Entidade | diagnóstico_salarial |
| Ação | publicado |
| Produtor | E-2 (Transparência Salarial e Folha) |
| Consumidores | E-3 (Simulação Econômica) |
| Descrição | Diagnóstico salarial: razão atual, redistribuição necessária, impacto na folha. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.diagnóstico_salarial_publicado/1.0.0", "title": "empresa.diagnóstico_salarial_publicado", "type": "object", "required": [ "empresa_id", "submissao_id", "menor_salario", "maior_salario", "razao_atual", "razao_parametrizada", "custo_total_antes", "custo_total_depois", "redistribuicao_total", "colaboradores_acima_teto", "colaboradores_abaixo_minimo", "versao_parametros" ], "properties": { "empresa_id": { "type": "string", "format": "uuid" }, "submissao_id": { "type": "string", "format": "uuid" }, "menor_salario": { "type": "number", "exclusiveMinimum": 0 }, "maior_salario": { "type": "number", "exclusiveMinimum": 0 }, "razao_atual": { "type": "number", "exclusiveMinimum": 0, "description": "Razão entre maior e menor salário." }, "razao_parametrizada": { "type": "number", "exclusiveMinimum": 0, "description": "Razão máxima permitida pelo parâmetro vigente." }, "custo_total_antes": { "type": "number", "minimum": 0 }, "custo_total_depois": { "type": "number", "minimum": 0, "description": "Custo total após redistribuição simulada. Igual a custo_total_antes." }, "redistribuicao_total": { "type": "number", "minimum": 0 }, "colaboradores_acima_teto": { "type": "integer", "minimum": 0 }, "colaboradores_abaixo_minimo": { "type": "integer", "minimum": 0 }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros de razão salarial usados no cálculo. MVP: 'mvp-v1'." } }}3.4.26 empresa.balanço_submetido
Seção intitulada “3.4.26 empresa.balanço_submetido”| Propriedade | Valor |
|---|---|
| Domínio | empresa |
| Entidade | balanço |
| Ação | submetido |
| Produtor | E-3 (face BFF do balanço) |
| Consumidores | E-3 (Simulação Econômica) |
| Descrição | Empresa submeteu dados financeiros para simulação de excedente. Autodeclarado. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.balanço_submetido/1.0.0", "title": "empresa.balanço_submetido", "type": "object", "required": ["empresa_id", "submissao_id", "receita_bruta", "custos_operacionais", "referencia_submissao_folha"], "properties": { "empresa_id": { "type": "string", "format": "uuid" }, "submissao_id": { "type": "string", "format": "uuid" }, "receita_bruta": { "type": "number", "minimum": 0, "description": "Receita bruta declarada no período de referência." }, "custos_operacionais": { "type": "number", "minimum": 0, "description": "Custos operacionais declarados (excluindo folha salarial)." }, "referencia_submissao_folha": { "type": "string", "format": "uuid", "description": "submissao_id da folha salarial que serve de base." }, "periodo_referencia": { "type": "object", "required": ["inicio", "fim"], "properties": { "inicio": { "type": "string", "format": "date" }, "fim": { "type": "string", "format": "date" } }, "description": "Período contábil de referência dos dados submetidos." } }}3.4.27 empresa.simulação_econômica_publicada
Seção intitulada “3.4.27 empresa.simulação_econômica_publicada”| Propriedade | Valor |
|---|---|
| Domínio | empresa |
| Entidade | simulação_econômica |
| Ação | publicada |
| Produtor | E-3 (Simulação Econômica) |
| Consumidores | E-4 (Fase 2 — Impacto Territorial) |
| Descrição | Resultado da simulação de excedente: valor calculado, divisão parametrizada (caixa interno, trabalhadores, retorno ao sistema) e valor por trabalhador. A versão 2.0.0 remove quantidade_ucs e destinos_uc e renomeia percentual_uc para percentual_fomento e valor_uc_total para valor_fomento_total. A versão 1.0.0 permanece como histórico. |
Schema do payload (versão 2.0.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "empresa.simulação_econômica_publicada/2.0.0", "title": "empresa.simulação_econômica_publicada", "type": "object", "required": [ "empresa_id", "submissao_id", "receita_declarada", "custos_declarados", "folha_reequilibrada", "excedente_calculado", "total_colaboradores", "divisao", "referencia_submissao_folha", "diagnostico_id", "periodo_referencia", "versao_parametros" ], "properties": { "empresa_id": { "type": "string", "format": "uuid" }, "submissao_id": { "type": "string", "format": "uuid" }, "receita_declarada": { "type": "number", "minimum": 0 }, "custos_declarados": { "type": "number", "minimum": 0 }, "folha_reequilibrada": { "type": "number", "minimum": 0, "description": "Custo total da folha após reequilíbrio (custo_total_depois do diagnóstico)." }, "excedente_calculado": { "type": "number", "description": "receita - custos - folha_reequilibrada. Pode ser zero ou negativo." }, "total_colaboradores": { "type": "integer", "minimum": 1, "description": "Total de colaboradores usados no cálculo do valor_por_trabalhador." }, "divisao": { "type": "object", "required": [ "percentual_fomento", "percentual_reinvestimento", "percentual_trabalhadores", "valor_fomento_total", "valor_reinvestimento", "valor_trabalhadores_total", "valor_por_trabalhador" ], "properties": { "percentual_fomento": { "type": "number", "minimum": 0, "maximum": 100, "description": "Percentual de retorno ao sistema (fomento)." }, "percentual_reinvestimento": { "type": "number", "minimum": 0, "maximum": 100, "description": "Percentual de caixa interno (reinvestimento)." }, "percentual_trabalhadores": { "type": "number", "minimum": 0, "maximum": 100 }, "valor_fomento_total": { "type": "number", "minimum": 0, "description": "Valor total de retorno ao sistema. Fluxo único, sem rateio por UC." }, "valor_reinvestimento": { "type": "number", "minimum": 0 }, "valor_trabalhadores_total": { "type": "number", "minimum": 0 }, "valor_por_trabalhador": { "type": "number", "minimum": 0, "description": "Valor por trabalhador se distribuído igualmente. 0 se total_colaboradores = 0." } } }, "referencia_submissao_folha": { "type": "string", "format": "uuid", "description": "submissao_id da folha salarial usada como base." }, "diagnostico_id": { "type": "string", "format": "uuid", "description": "diagnostico_id do diagnóstico salarial da E-2 usado como base." }, "periodo_referencia": { "type": "object", "required": ["inicio", "fim"], "properties": { "inicio": { "type": "string", "format": "date" }, "fim": { "type": "string", "format": "date" } }, "description": "Período contábil de referência da simulação." }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros de divisão do excedente. MVP: 'mvp-v1'." } }}3.4.28 cidadão.cadastrado
Seção intitulada “3.4.28 cidadão.cadastrado”| Propriedade | Valor |
|---|---|
| Domínio | cidadão |
| Entidade | cidadão |
| Ação | cadastrado |
| Produtor | BFF da D-1a (MVP) / C-1 (Fase 2) |
| Consumidores | Colônias com projeção de perfil |
| Descrição | Novo cidadão registrado. Identidade básica estabelecida. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "cidadão.cadastrado/1.0.0", "title": "cidadão.cadastrado", "type": "object", "required": ["cidadao_id", "auth_provider"], "properties": { "cidadao_id": { "type": "string", "format": "uuid" }, "nome": { "type": "string", "maxLength": 200, "description": "Nome do cidadão. Nulo para device anônimo." }, "email": { "type": "string", "format": "email", "description": "Email. Nulo para device anônimo." }, "avatar_url": { "type": "string", "format": "uri", "description": "URL do avatar. Nulo para device anônimo." }, "auth_provider": { "type": "string", "enum": ["anonymous", "google"], "description": "Provider de autenticação usado no cadastro." } }}3.4.29 cidadão.perfil_atualizado
Seção intitulada “3.4.29 cidadão.perfil_atualizado”| Propriedade | Valor |
|---|---|
| Domínio | cidadão |
| Entidade | perfil |
| Ação | atualizado |
| Produtor | BFF da D-1a (MVP) / C-1 (Fase 2) |
| Consumidores | Colônias com projeção local de perfil |
| Descrição | Cidadão alterou dados de perfil: nome, endereço ou UC de residência autodeclarada. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "cidadão.perfil_atualizado/1.0.0", "title": "cidadão.perfil_atualizado", "type": "object", "required": ["cidadao_id", "campos_alterados"], "properties": { "cidadao_id": { "type": "string", "format": "uuid" }, "campos_alterados": { "type": "array", "minItems": 1, "items": { "type": "string", "enum": ["nome", "email", "avatar_url", "endereco", "uc_residencia"] }, "description": "Lista de campos alterados nesta atualização." }, "nome": { "type": "string", "maxLength": 200 }, "email": { "type": "string", "format": "email" }, "avatar_url": { "type": "string", "format": "uri" }, "endereco": { "type": "string", "maxLength": 500, "description": "Endereço autodeclarado." }, "uc_residencia": { "type": "string", "format": "uuid", "description": "Unidade cívica de residência autodeclarada." } }}3.4.30 cidadão.vinculado
Seção intitulada “3.4.30 cidadão.vinculado”| Propriedade | Valor |
|---|---|
| Domínio | cidadão |
| Entidade | cidadão |
| Ação | vinculado |
| Produtor | BFF da D-1a (MVP) / C-1 (Fase 2) |
| Consumidores | Colônias com projeção de perfil |
| Descrição | Device ID anônimo vinculado a uma conta Google. Demandas anteriores são migradas. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "cidadão.vinculado/1.0.0", "title": "cidadão.vinculado", "type": "object", "required": ["cidadao_id_origem", "cidadao_id_destino", "auth_provider"], "properties": { "cidadao_id_origem": { "type": "string", "format": "uuid", "description": "Device ID anônimo que foi vinculado." }, "cidadao_id_destino": { "type": "string", "format": "uuid", "description": "ID da conta Google/gov.br para a qual os dados foram migrados." }, "auth_provider": { "type": "string", "enum": ["google", "govbr"], "description": "Provider da conta de destino." } }}3.4.31 parâmetros.atualizados
Seção intitulada “3.4.31 parâmetros.atualizados”| Propriedade | Valor |
|---|---|
| Domínio | parâmetros |
| Entidade | parâmetros |
| Ação | atualizados |
| Produtor | Sistema de parametrização (MVP: arquivo de configuração) / D-19 (Fase 3) |
| Consumidores | D-4 (Priorização), E-2 (Transparência Salarial), E-3 (Simulação Econômica) |
| Descrição | Conjunto de parâmetros do sistema foi atualizado. No MVP, pesos são fixos em configuração — o tipo existe para contrato definido. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "parâmetros.atualizados/1.0.0", "title": "parâmetros.atualizados", "type": "object", "required": ["versao", "parametros"], "properties": { "versao": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "Versão semver do conjunto de parâmetros." }, "parametros": { "type": "object", "required": ["pesos_nacionais", "scores_horizontais", "razao_salarial_maxima", "divisao_excedente"], "properties": { "pesos_nacionais": { "type": "object", "description": "Peso nacional por nível de precedência. Chave = nível (1-5).", "patternProperties": { "^[1-5]$": { "type": "number", "minimum": 0 } } }, "scores_horizontais": { "type": "object", "description": "Score horizontal por categoria_id. Chave = categoria_id.", "patternProperties": { "^\\d+\\.\\d+$": { "type": "integer", "minimum": 0, "maximum": 100 } } }, "razao_salarial_maxima": { "type": "number", "exclusiveMinimum": 1, "description": "Razão máxima entre maior e menor salário (ex: 10)." }, "divisao_excedente": { "type": "object", "required": ["fomento", "reinvestimento", "trabalhadores"], "properties": { "fomento": { "type": "number", "minimum": 0, "maximum": 100 }, "reinvestimento": { "type": "number", "minimum": 0, "maximum": 100 }, "trabalhadores": { "type": "number", "minimum": 0, "maximum": 100 } }, "description": "Percentuais de divisão do excedente. Devem somar 100." }, "distribuicao_decay": { "type": "object", "properties": { "concentracao": { "type": "integer", "minimum": 0, "maximum": 100, "default": 70 }, "distribuicao": { "type": "integer", "minimum": 0, "maximum": 100, "default": 30 }, "limiar_nivel_vencido": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.1 } } }, "limiar_confianca_categorizacao": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.7 }, "reajuste_peso_situacional_meses": { "type": "integer", "minimum": 1, "default": 18 } } }, "motivo": { "type": "string", "description": "Descrição do motivo da atualização." } }}3.4.32 peso_situacional.atualizado
Seção intitulada “3.4.32 peso_situacional.atualizado”| Propriedade | Valor |
|---|---|
| Domínio | peso_situacional |
| Entidade | peso_situacional |
| Ação | atualizado |
| Produtor | D-4 (Priorização e Ranking — recalculado periodicamente) |
| Consumidores | D-4 (internamente — recalcula scores das demandas da UC) |
| Descrição | Peso situacional de uma UC para um nível de precedência foi recalculado. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "peso_situacional.atualizado/1.0.0", "title": "peso_situacional.atualizado", "type": "object", "required": ["unidade_civica_id", "nivel_precedencia", "peso_anterior", "peso_novo", "cobertura_atingida", "cobertura_meta", "versao_parametros"], "properties": { "unidade_civica_id": { "type": "string", "format": "uuid" }, "nivel_precedencia": { "type": "integer", "minimum": 1, "maximum": 5 }, "peso_anterior": { "type": "number", "minimum": 0, "maximum": 1 }, "peso_novo": { "type": "number", "minimum": 0, "maximum": 1, "description": "Fórmula: 1 - (cobertura_atingida / cobertura_meta)." }, "cobertura_atingida": { "type": "number", "minimum": 0, "description": "Cobertura real medida na UC para este nível." }, "cobertura_meta": { "type": "number", "minimum": 0, "description": "Meta de cobertura parametrizada pelos comitês técnicos." }, "versao_parametros": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" } }}3.4.33 demanda.concluída
Seção intitulada “3.4.33 demanda.concluída”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | concluída |
| Produtor | D-6b (Relatoria e Acompanhamento) |
| Consumidores | D-5 (Agenda — transição para concluido), D-7 (Transparência) |
| Descrição | A demanda foi concluída. Publicado quando o conselheiro marca a demanda como resolvida via atualização de tipo status_atualizado com novo_status = 'concluido'. Publicado antes de conselheiro.ciclo_concluído na mesma transação, garantindo que o backlog seja atualizado antes da liberação do conselheiro. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.concluída/1.0.0", "title": "demanda.concluída", "type": "object", "required": ["demanda_id", "unidade_civica_id", "data_conclusao", "conselheiro_id"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "data_conclusao": { "type": "string", "format": "date-time", "description": "Timestamp de conclusão da demanda." }, "conselheiro_id": { "type": "string", "format": "uuid", "description": "Conselheiro responsável pela conclusão." }, "categoria_id": { "type": "string", "description": "Categoria da demanda concluída." }, "dias_ate_conclusao": { "type": "integer", "minimum": 0, "description": "Dias entre sorteio e conclusão. Para métricas de desempenho." }, "total_atualizacoes": { "type": "integer", "minimum": 1, "description": "Total de atualizações publicadas durante o ciclo." }, "resumo_final": { "type": "string", "maxLength": 3000, "description": "Resumo do desfecho redigido pelo conselheiro." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da publicação do evento." } }}3.4.34 conselheiro.elegibilidade_atualizada
Seção intitulada “3.4.34 conselheiro.elegibilidade_atualizada”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | elegibilidade |
| Ação | atualizada |
| Produtor | D-17 (Controle de Mandato e Progressão — Fase 2) |
| Consumidores | D-6a (Sorteio e Atribuição, handler stub no MVP) |
| Descrição | Mudança nos critérios de elegibilidade de conselheiro. A D-6a registra o cursor e ignora o evento no MVP; o recálculo do pool entra na Fase 2. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.elegibilidade_atualizada/1.0.0", "title": "conselheiro.elegibilidade_atualizada", "type": "object", "required": ["unidade_civica_id", "versao_parametros"], "properties": { "unidade_civica_id": { "type": "string", "format": "uuid", "description": "Unidade cívica cuja elegibilidade foi recalculada." }, "versao_parametros": { "type": "string", "description": "Versão dos parâmetros usados no recálculo." } }}3.4.35 lugar.validado
Seção intitulada “3.4.35 lugar.validado”| Propriedade | Valor |
|---|---|
| Domínio | lugar |
| Entidade | lugar |
| Ação | validado |
| Produtor | L-3 (Validação e Qualidade) |
| Consumidores | D-7 (Transparência) |
| Descrição | Lugar com status de confiança avaliado por validação automática, social ou de operador. Publicado a cada confirmação e denúncia aceitas. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "lugar.validado/1.0.0", "title": "lugar.validado", "type": "object", "required": ["lugar_id", "status_confianca", "total_confirmacoes", "total_denuncias", "versao_dados"], "properties": { "lugar_id": { "type": "string", "format": "uuid" }, "status_confianca": { "type": "string", "enum": ["provisorio", "confirmado", "disputado"], "description": "Status de confiança do lugar após a validação." }, "metodo_validacao": { "type": "string", "enum": ["automatico", "social", "operador"], "description": "Método que determinou o status. Presente quando a transição veio de uma validação." }, "total_confirmacoes": { "type": "integer", "minimum": 0, "description": "Total de confirmações de cidadãos distintos." }, "total_denuncias": { "type": "integer", "minimum": 0, "description": "Total de denúncias de cidadãos distintos." }, "versao_dados": { "type": "integer", "minimum": 1, "description": "Versão incremental do registro do lugar, usada na ordem de aplicação." } }}3.4.36 lugar.desativado
Seção intitulada “3.4.36 lugar.desativado”| Propriedade | Valor |
|---|---|
| Domínio | lugar |
| Entidade | lugar |
| Ação | desativado |
| Produtor | L-3 (Validação e Qualidade) |
| Consumidores | D-7 (Transparência) |
| Descrição | Lugar retirado do mapa público por denúncias, retirada do autor ou decisão de operador. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "lugar.desativado/1.0.0", "title": "lugar.desativado", "type": "object", "required": ["lugar_id", "motivo"], "properties": { "lugar_id": { "type": "string", "format": "uuid" }, "motivo": { "type": "string", "enum": ["denuncias", "retirado_pelo_autor", "operador"], "description": "Motivo da desativação." } }}3.4.37 duplicidade.candidata_detectada
Seção intitulada “3.4.37 duplicidade.candidata_detectada”| Propriedade | Valor |
|---|---|
| Domínio | duplicidade |
| Entidade | candidata |
| Ação | detectada |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-7 (Transparência) |
| Descrição | Par de demandas equivalentes detectado por heurística determinística. A decisão de agregação permanece humana. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "duplicidade.candidata_detectada/1.0.0", "title": "duplicidade.candidata_detectada", "type": "object", "required": ["demanda_id", "candidatas", "origem", "versao_modelo", "criterios"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "candidatas": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["demanda_id", "similaridade", "distancia_metros", "categoria_id", "unidade_civica_id"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "similaridade": { "type": "number", "minimum": 0, "maximum": 1 }, "distancia_metros": { "type": "number", "minimum": 0 }, "categoria_id": { "type": "string" }, "unidade_civica_id": { "type": "string", "format": "uuid" } } }, "description": "Demandas candidatas com similaridade, distância e contexto." }, "origem": { "type": "string", "enum": ["automatico"], "description": "Output marcado como automático, conforme a regra de IA do projeto." }, "versao_modelo": { "type": "string", "description": "Versão do método de detecção. MVP: `d12-mvp-heuristica-v1`." }, "criterios": { "type": "object", "required": ["raio_metros", "similaridade_minima"], "properties": { "raio_metros": { "type": "number", "minimum": 0 }, "similaridade_minima": { "type": "number", "minimum": 0, "maximum": 1 } }, "description": "Critérios vigentes no momento da detecção." } }}3.4.38 demanda.confirmada
Seção intitulada “3.4.38 demanda.confirmada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | confirmada |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-7 (Transparência) |
| Descrição | Cidadão confirmou que também presenciou a demanda. O payload não carrega identificação do cidadão. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.confirmada/1.0.0", "title": "demanda.confirmada", "type": "object", "required": ["demanda_id", "confirmacao_id", "total_confirmacoes", "mecanismo"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "confirmacao_id": { "type": "string", "format": "uuid", "description": "Identificador da confirmação registrada." }, "total_confirmacoes": { "type": "integer", "minimum": 1, "description": "Total de confirmações válidas da demanda." }, "mecanismo": { "type": "string", "enum": ["mapa", "captura"], "description": "Origem da confirmação: marcador do mapa ou sugestão de candidatas na captura." } }}3.4.39 demanda.evidencia_adicionada
Seção intitulada “3.4.39 demanda.evidencia_adicionada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | evidência |
| Ação | adicionada |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-1c (Gestão de Anexos), D-7 (Transparência) |
| Descrição | Evidência anexada por terceiro a uma demanda confirmada. A versão corrente é a 1.3.0: a 1.1.0 estendeu via com conclusao, a 1.2.0 adicionou object_key e a 1.3.0 removeu o campo, com a chave derivada da URL validada pela D-1c. |
Schema do payload (versão 1.3.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.evidencia_adicionada/1.3.0", "title": "demanda.evidencia_adicionada", "type": "object", "required": ["demanda_id", "evidencia_id", "tipo", "url", "via"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "evidencia_id": { "type": "string", "format": "uuid" }, "tipo": { "type": "string", "enum": ["imagem", "audio"] }, "url": { "type": "string", "format": "uri", "description": "URL da mídia. A D-1c valida e deriva a chave do objeto." }, "via": { "type": "string", "enum": ["confirmacao", "conclusao"], "description": "Origem da evidência: confirmação ou conclusão da demanda." } }}3.4.40 demanda.conclusao_confirmada
Seção intitulada “3.4.40 demanda.conclusao_confirmada”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | demanda |
| Ação | conclusao_confirmada |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-5 (Agenda), D-6b (Relatoria e Acompanhamento), D-7 (Transparência), D-12 (própria saída, no rebuild) |
| Descrição | Três conclusões de cidadãos distintos confirmaram a demanda como resolvida. O payload não carrega identificação dos cidadãos nem evidências. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.conclusao_confirmada/1.0.0", "title": "demanda.conclusao_confirmada", "type": "object", "required": ["demanda_id", "unidade_civica_id", "conclusao_id", "total_conclusoes", "data_conclusao", "mecanismo", "ratificacao_necessaria"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid", "description": "Unidade cívica resolvida da demanda." }, "conclusao_id": { "type": "string", "format": "uuid", "description": "Identificador da conclusão que cruzou o limiar." }, "total_conclusoes": { "type": "integer", "minimum": 3, "description": "Total de conclusões válidas, no mínimo o limiar de 3." }, "data_conclusao": { "type": "string", "format": "date-time" }, "mecanismo": { "type": "string", "enum": ["confirmacao_coletiva"] }, "ratificacao_necessaria": { "type": "boolean", "description": "Verdadeiro quando há acompanhamento ativo e o conselheiro precisa ratificar." } }}3.4.41 duplicidade.agregada
Seção intitulada “3.4.41 duplicidade.agregada”| Propriedade | Valor |
|---|---|
| Domínio | duplicidade |
| Entidade | duplicidade |
| Ação | agregada |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-4 (Priorização e Ranking), D-5 (Agenda), D-6a (Sorteio e Atribuição), D-7 (Transparência), D-12 (própria saída) |
| Descrição | Demandas equivalentes foram agregadas sob um representante por confirmação coletiva de cidadãos distintos. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "duplicidade.agregada/1.0.0", "title": "duplicidade.agregada", "type": "object", "required": ["agregado_id", "representante_demanda_id", "membros", "mecanismo", "confirmacoes_gatilho", "criterios"], "properties": { "agregado_id": { "type": "string", "format": "uuid" }, "representante_demanda_id": { "type": "string", "format": "uuid", "description": "Demanda que representa o agregado no ranking, na agenda e no sorteio." }, "membros": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["demanda_id"], "properties": { "demanda_id": { "type": "string", "format": "uuid" } } }, "description": "Demandas membros que saem do mapa, do ranking e do backlog." }, "mecanismo": { "type": "string", "enum": ["confirmacao_coletiva"] }, "confirmacoes_gatilho": { "type": "integer", "minimum": 1, "description": "Total de confirmações que disparou a agregação." }, "criterios": { "type": "object", "description": "Critérios vigentes no momento da agregação." } }}3.4.42 moderacao.decidida
Seção intitulada “3.4.42 moderacao.decidida”| Propriedade | Valor |
|---|---|
| Domínio | moderacao |
| Entidade | moderacao |
| Ação | decidida |
| Produtor | D-1d (Moderação) |
| Consumidores | D-1c (Gestão de Anexos), D-7 (Transparência), N-0d (Direitos do Titular) |
| Descrição | Decisão humana registrada sobre um item da fila de moderação. A versão corrente é a 1.1.0, com a decisão removido, os campos de reversão e o tipo relato; a 1.0.0 permanece no catálogo. |
Schema do payload (versão 1.1.0):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "moderacao.decidida/1.1.0", "title": "moderacao.decidida", "type": "object", "required": ["item_id", "tipo", "referencia_id", "decisao", "moderador_id"], "properties": { "item_id": { "type": "string", "format": "uuid" }, "tipo": { "type": "string", "enum": ["anexo", "texto", "relato"], "description": "Tipo do item decidido. No relato, a decisão aceita apenas aprovado e bloqueado." }, "referencia_id": { "type": "string", "format": "uuid", "description": "Identificador do anexo, da demanda ou da atualização do relato alvo da decisão." }, "decisao": { "type": "string", "enum": ["aprovado", "bloqueado", "removido"], "description": "Aprovar libera o conteúdo; bloquear mantém oculto e é reversível; remover apaga a mídia e limpa o texto, sem reversão." }, "moderador_id": { "type": "string", "format": "uuid" }, "motivo": { "type": "string", "maxLength": 500, "description": "Obrigatório na decisão `removido`." }, "demanda_id": { "type": "string", "format": "uuid", "description": "Presente no tipo `anexo`." }, "remover_texto_demanda": { "type": "boolean", "description": "Só entra no payload quando a decisão é `removido` e o item é `anexo`." }, "reaberto": { "type": "boolean", "description": "Verdadeiro quando a decisão reverte uma decisão anterior." } }}3.4.43 anexo.moderado
Seção intitulada “3.4.43 anexo.moderado”| Propriedade | Valor |
|---|---|
| Domínio | anexo |
| Entidade | anexo |
| Ação | moderado |
| Produtor | D-1c (Gestão de Anexos) |
| Consumidores | D-7 (Transparência) |
| Descrição | Anexo com decisão de moderação humana aplicada ao status e à visibilidade. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "anexo.moderado/1.0.0", "title": "anexo.moderado", "type": "object", "required": ["anexo_id", "status", "moderacao_status"], "properties": { "anexo_id": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": ["ativo", "bloqueado"], "description": "Status do anexo na vitrine." }, "moderacao_status": { "type": "string", "enum": ["aprovado", "bloqueado"] } }}3.4.44 anexo.removido
Seção intitulada “3.4.44 anexo.removido”| Propriedade | Valor |
|---|---|
| Domínio | anexo |
| Entidade | anexo |
| Ação | removido |
| Produtor | D-1c (Gestão de Anexos) |
| Consumidores | D-7 (Transparência) |
| Descrição | Anexo removido fisicamente do armazenamento por decisão de moderação, com a impressão digital do conteúdo preservada para auditoria. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "anexo.removido/1.0.0", "title": "anexo.removido", "type": "object", "required": ["anexo_id", "demanda_id", "hash_sha256"], "properties": { "anexo_id": { "type": "string", "format": "uuid" }, "demanda_id": { "type": "string", "format": "uuid" }, "hash_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Impressão digital do conteúdo removido, sem permitir a recuperação." } }}3.4.45 conselheiro.atualização_transcrita
Seção intitulada “3.4.45 conselheiro.atualização_transcrita”| Propriedade | Valor |
|---|---|
| Domínio | conselheiro |
| Entidade | atualização |
| Ação | transcrita |
| Schema version | 1.0.0 |
| Produtor | D-1b (Normalização) |
| Consumidores | D-6b (Relatoria e Acompanhamento) |
| Descrição | Transcrição do relato em áudio do conselheiro concluída em segundo plano. O atualizacao_event_id é o event_id do conselheiro.atualização_registrada correspondente e é a chave de correlação entre a D-1b e a D-6b. Com sucesso, o texto transcrito é aplicado à atualização pendente; sem sucesso, a atualização fica marcada com falha e não é publicada. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "conselheiro.atualização_transcrita/1.0.0", "title": "conselheiro.atualização_transcrita", "type": "object", "required": ["atualizacao_event_id", "conselheiro_id", "demanda_id", "unidade_civica_id", "sucesso", "texto_transcrito", "confianca", "timestamp"], "properties": { "atualizacao_event_id": { "type": "string", "format": "uuid", "description": "event_id do conselheiro.atualização_registrada transcrito." }, "conselheiro_id": { "type": "string", "format": "uuid" }, "demanda_id": { "type": "string", "format": "uuid" }, "unidade_civica_id": { "type": "string", "format": "uuid" }, "sucesso": { "type": "boolean", "description": "Se a transcrição foi concluída." }, "texto_transcrito": { "type": "string", "maxLength": 10000, "description": "Texto transcrito. Vazio quando sucesso = false." }, "confianca": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confiança da transcrição, conforme o limiar publicado." }, "motivo_falha": { "type": "string", "maxLength": 500, "description": "Motivo da falha. Removido pela redação na persistência." }, "timestamp": { "type": "string", "format": "date-time", "description": "Momento da conclusão da transcrição." } }}3.4.46 caminho.atualizado
Seção intitulada “3.4.46 caminho.atualizado”| Propriedade | Valor |
|---|---|
| Domínio | caminho |
| Entidade | caminho |
| Ação | atualizado |
| Schema version | 1.0.0 |
| Produtor | D-24 (Memória de Caminhos) |
| Consumidores | D-7 (Transparência) |
| Descrição | Perfil agregado do caminho de resolução por município e subcategoria, com o dossiê determinístico dos casos anteriores. Publicado apenas quando o perfil muda. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "caminho.atualizado/1.0.0", "title": "caminho.atualizado", "type": "object", "required": ["municipio_id", "subcategoria_id", "total_casos", "canais", "orgaos", "gargalos", "documentos", "dossie", "versao_metodo", "gerado_em"], "properties": { "municipio_id": { "type": "string", "format": "uuid" }, "subcategoria_id": { "type": "string", "maxLength": 50 }, "categoria_id": { "type": "string", "maxLength": 20 }, "total_casos": { "type": "integer", "minimum": 0 }, "prazo_mediano_dias": { "type": ["integer", "null"], "minimum": 0 }, "canais": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }, "orgaos": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }, "gargalos": { "type": "array", "items": { "type": "object", "required": ["descricao", "ocorrencias"], "properties": { "descricao": { "type": "string", "maxLength": 500 }, "ocorrencias": { "type": "integer", "minimum": 1 } } } }, "documentos": { "type": "array", "maxItems": 200, "items": { "type": "string", "maxLength": 500 } }, "dossie": { "type": "string", "maxLength": 4000 }, "versao_metodo": { "type": "string", "maxLength": 20 }, "gerado_em": { "type": "string", "format": "date-time" } }}3.4.47 demanda.resumo_agregado_atualizado
Seção intitulada “3.4.47 demanda.resumo_agregado_atualizado”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | resumo_agregado |
| Ação | atualizado |
| Schema version | 1.0.0 |
| Produtor | D-12 (Detecção de Duplicidade) |
| Consumidores | D-7 (Transparência) |
| Descrição | Resumo público e determinístico dos relatos agregados por duplicidade, com o representante, os membros e o período. Republicado quando o agregado ganha membro. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.resumo_agregado_atualizado/1.0.0", "title": "demanda.resumo_agregado_atualizado", "type": "object", "required": ["demanda_id", "membros", "resumo", "total_relatos", "periodo", "versao_metodo", "gerado_em", "fontes"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "membros": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["demanda_id"], "properties": { "demanda_id": { "type": "string", "format": "uuid" } } } }, "resumo": { "type": "string", "maxLength": 3000 }, "total_relatos": { "type": "integer", "minimum": 1 }, "periodo": { "type": "object", "required": ["inicio", "fim"], "properties": { "inicio": { "type": "string", "format": "date-time" }, "fim": { "type": "string", "format": "date-time" } } }, "versao_metodo": { "type": "string", "maxLength": 20 }, "gerado_em": { "type": "string", "format": "date-time" }, "fontes": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["demanda_id"], "properties": { "demanda_id": { "type": "string", "format": "uuid" } } } } }}3.4.48 demanda.resumo_ciclo_atualizado
Seção intitulada “3.4.48 demanda.resumo_ciclo_atualizado”| Propriedade | Valor |
|---|---|
| Domínio | demanda |
| Entidade | resumo_ciclo |
| Ação | atualizado |
| Schema version | 1.0.0 |
| Produtor | D-6b (Relatoria e Acompanhamento) |
| Consumidores | D-7 (Transparência) |
| Descrição | Resumo público e determinístico do ciclo de acompanhamento, com marcos, cronologia e desfecho. Publicado a cada atualização publicada e no fecho do ciclo. |
Schema do payload:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "demanda.resumo_ciclo_atualizado/1.0.0", "title": "demanda.resumo_ciclo_atualizado", "type": "object", "required": ["demanda_id", "resumo", "total_atualizacoes", "data_inicio", "data_fim", "versao_metodo", "gerado_em", "fontes"], "properties": { "demanda_id": { "type": "string", "format": "uuid" }, "resumo": { "type": "string", "maxLength": 6000 }, "total_atualizacoes": { "type": "integer", "minimum": 1 }, "data_inicio": { "type": ["string", "null"], "format": "date-time" }, "data_fim": { "type": ["string", "null"], "format": "date-time" }, "versao_metodo": { "type": "string", "maxLength": 20 }, "gerado_em": { "type": "string", "format": "date-time" }, "fontes": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["atualizacao_id"], "properties": { "atualizacao_id": { "type": "string", "format": "uuid" } } } } }}4. Lógica de Negócio — Algoritmos e Fluxos
Seção intitulada “4. Lógica de Negócio — Algoritmos e Fluxos”4.1 RegistryService.registrar()
Seção intitulada “4.1 RegistryService.registrar()”Registra um novo tipo de evento no catálogo. Chamado no boot pelo sincronizarCatalogo() e disponível para uso programático.
função registrar(dto: EventoDefinicao) → EventType:
1. Validar dto.tipo contra PADRAO_TIPO (/^[\p{Ll}_]+(\.[\p{Ll}_]+){1,2}$/u) → se falha, lançar ErroNomeTipoInvalido 2. Validar dto.versao_inicial contra PADRAO_SEMVER (/^\d+\.\d+\.\d+$/) → se falha, lançar ErroVersaoInvalida 3. Buscar o tipo; se já existe, lançar ErroTipoDuplicado 4. Inserir o tipo com status 'active' 5. Inserir a versão inicial com changelog "Versão inicial." e is_latest = true 6. Invalidar o cache do tipo
retornar o registroO registrar() não valida o schema contra a meta-schema do JSON Schema. A compilação Ajv acontece na validação de payload.
4.2 RegistryService.adicionarVersaoSchema()
Seção intitulada “4.2 RegistryService.adicionarVersaoSchema()”Adiciona uma nova versão de schema para um tipo existente. A versão anterior passa a is_latest = false.
função adicionarVersaoSchema(tipo, versao, schema, changelog?) → SchemaVersion:
1. Buscar o tipo; se não existe, lançar ErroTipoNaoEncontrado 2. Validar a versão como semver; se falha, lançar ErroVersaoInvalida 3. Buscar o par (tipo, versao); se existe, lançar ErroVersaoDuplicada 4. Marcar a versão anterior do tipo como não recente 5. Inserir a nova versão com is_latest = true 6. Invalidar os caches `tipo@versao` e `tipo@latest`
retornar a nova versãoNão há validação automática de regras de compatibilidade semver. A convenção PATCH/MINOR/MAJOR da seção 3.3 é disciplina de revisão do catálogo, não uma checagem do serviço.
4.3 RegistryService.depreciarTipo()
Seção intitulada “4.3 RegistryService.depreciarTipo()”Marca um tipo como deprecated. O registro permanece no catálogo e no log.
função depreciarTipo(tipo, motivo):
1. Buscar o tipo; se não existe, lançar ErroTipoNaoEncontrado 2. Se já está deprecated, lançar ErroTipoJaDepreciado 3. Se o motivo é vazio, lançar ErroValidacao 4. Atualizar status para 'deprecated' com deprecated_at e deprecated_motivo 5. Invalidar o cache do tipoTipos deprecated não são publicáveis: validar() retorna false para eles.
4.4 RegistryService.buscarPorTipo() — caminho quente
Seção intitulada “4.4 RegistryService.buscarPorTipo() — caminho quente”O método mais chamado do caminho de publicação.
função buscarPorTipo(tipo) → TipoEventoConsultado | null:
1. Consultar o cache in-memory (cacheTipos) 2. Se ausente, consultar o repositório 3. Popular o cache e retornar4.5 RegistryService.obterSchema() — caminho quente
Seção intitulada “4.5 RegistryService.obterSchema() — caminho quente”Resolve o JSON Schema para um tipo e versão específicos.
função obterSchema(tipo, versao?) → JSONSchema | null:
1. Buscar o tipo; se não existe, retornar null 2. Chave de cache: `tipo@versao` ou `tipo@latest` 3. Buscar no repositório pela versão informada ou pela mais recente 4. Popular o cache e retornar o schema4.6 Validação de tipo e payload
Seção intitulada “4.6 Validação de tipo e payload”função validar(evento: { tipo, versao_schema? }) → boolean:
buscar o tipo; false se não existe ou está deprecated se a versão informada não existe no tipo: false caso contrário: true
função validarPayload(tipo, versao, payload) → string[]:
obter o schema de tipo@versao se o schema não existe: retornar ["Schema não encontrado para tipo@versao"] compilar o validador Ajv draft 2020-12 e guardar em cacheValidadores (chave tipo@versao) validar o payload e retornar a lista de erros, vazia quando o payload é válido4.7 Casos de borda
Seção intitulada “4.7 Casos de borda”| Caso | Comportamento |
|---|---|
| Tipo registrado com nome fora do padrão | registrar() lança ErroNomeTipoInvalido. O regex é ^[\p{Ll}_]+(\.[\p{Ll}_]+){1,2}$/u. |
Tipo duplicado (mesmo tipo) |
registrar() lança ErroTipoDuplicado. O índice único em event_type.tipo é a segunda linha de defesa. |
| Versão duplicada para o mesmo tipo | adicionarVersaoSchema() lança ErroVersaoDuplicada. O índice único (event_type_id, versao) no banco é a segunda linha de defesa. |
| Versão fora do formato semver | registrar() e adicionarVersaoSchema() lançam ErroVersaoInvalida. |
| Schema que não é JSON Schema válido | O registrar() não valida o schema contra a meta-schema. O erro aparece na compilação Ajv do validarPayload(), que retorna Schema inválido para tipo@versao. |
Tipo deprecated recebe nova versão de schema |
Permitido. Tipos deprecated podem ganhar correções ou adições durante a transição. A publicação de eventos do tipo é que fica bloqueada. |
depreciarTipo() chamado em tipo já deprecated |
Lança ErroTipoJaDepreciado. |
depreciarTipo() sem motivo |
Lança ErroValidacao. |
buscarPorTipo() com tipo inexistente |
Retorna null. O Event Bus trata e rejeita a publicação. |
obterSchema() com versão inexistente |
Retorna null. validar() retorna false e a publicação é rejeitada. |
| Cache stale após escrita | registrar(), adicionarVersaoSchema() e depreciarTipo() invalidam as chaves afetadas. sincronizarCatalogo() limpa os caches de schema e de validadores quando atualiza um schema_json. |
Tipo migrado para deprecated por SQL manual |
Possível, porque não há CHECK de status no banco. O caminho correto é depreciarTipo(), que preenche deprecated_at e deprecated_motivo. |
4.8 Decisões de design com justificativa
Seção intitulada “4.8 Decisões de design com justificativa”Cache in-memory de tipos, schemas e validadores, não Redis.
O catálogo tem 45 tipos e poucas versões por tipo. Cache com Map é mais rápido que Redis, sem latência de rede nem infraestrutura adicional. Os validadores Ajv compilados ficam em cacheValidadores para não recompilar o schema a cada validação de payload. Não há TTL: a invalidação acontece nas operações de escrita e no boot.
Catálogo em TypeScript, não arquivos .json.
A fonte canônica é schemas/index.ts (CATALOGO_EVENTOS). O catálogo é revisado em PR, versionado junto do código e sincronizado no banco no boot. Não há build step de cópia nem arquivos JSON paralelos.
ON DELETE RESTRICT em vez de CASCADE.
Se um tipo de evento fosse removido, todos os schemas associados também seriam com CASCADE, o que quebraria o princípio de append-only. Schemas históricos devem permanecer para auditoria. RESTRICT força o caminho correto: depreciar, não deletar.
Sincronização idempotente no boot, não migration de seed.
O sincronizarCatalogo() registra tipos novos, atualiza schema_json divergente por comparação de JSON.stringify e adiciona versões suplementares ausentes. Não remove nada. O boot é o ponto único de sincronização entre o catálogo em código e o banco.
Convenção semver em revisão, sem validação automática. O serviço valida o formato semver e a duplicidade de versão. A compatibilidade entre versões (o que pode entrar em PATCH, MINOR e MAJOR) segue a convenção da seção 3.3 e é verificada na revisão do catálogo, não por código.
5. Integração com o Barramento e Outras Colônias
Seção intitulada “5. Integração com o Barramento e Outras Colônias”5.1 Como o Event Bus (N-0a) consome o Registry
Seção intitulada “5.1 Como o Event Bus (N-0a) consome o Registry”O EventBusService.publicar() chama o Registry em dois pontos:
validar({ tipo, versao_schema })— cobre tipo inexistente, tipo deprecated e versão sem schema.validarPayload(tipo, versao, payload)— valida o payload contra o JSON Schema e retorna a lista de erros.
A versão default é o literal '1.0.0' quando a colônia não informa. A versão gravada no event_log é a vigente no momento da publicação. As duas chamadas ocorrem no caminho quente, antes do INSERT.
5.2 Como as colônias consomem schemas do Registry
Seção intitulada “5.2 Como as colônias consomem schemas do Registry”No MVP, o RegistryService é injetado pelo Event Bus. As colônias podem importar RegistryModule para validação de payload próprio ou introspecção, mas não são obrigadas.
Para validação na própria colônia:
// Exemplo na D-1b (Normalização)const erros = await this.registryService.validarPayload('demanda.normalizada', '1.4.0', payload);if (erros.length > 0) { throw new Error(`Payload inválido: ${erros.join('; ')}`);}5.3 Ausência de API REST pública
Seção intitulada “5.3 Ausência de API REST pública”O Registry não expõe controller REST no MVP. O catálogo está disponível como código-fonte aberto em schemas/index.ts. A interface programática (RegistryService) atende o Event Bus e as colônias internas.
5.4 Dependência de outras colônias
Seção intitulada “5.4 Dependência de outras colônias”O Registry não consome eventos e não depende de nenhuma colônia de negócio. A única dependência é a infraestrutura: PostgreSQL (schema core) e sistema de cache in-memory. É o módulo com menor superfície de dependência do sistema.
6. Performance e Limites
Seção intitulada “6. Performance e Limites”6.1 Rate limiting
Seção intitulada “6.1 Rate limiting”O Registry não aplica rate limiting. As consultas vêm exclusivamente do Event Bus, que é interno e síncrono. O volume de chamadas é proporcional ao volume de publicações.
6.2 Limites de tamanho
Seção intitulada “6.2 Limites de tamanho”| Limite | Valor | Justificativa |
|---|---|---|
tipo (VARCHAR) |
255 caracteres | Suficiente para {dominio}.{entidade}.{acao} com nomes longos. |
schema_json (JSONB) |
~10 KB típico, máximo 1 MB (limite PostgreSQL JSONB) | Schemas JSON são pequenos. Maior schema da Fase 1 (~3 KB). |
descricao (TEXT) |
~500 caracteres recomendado | Descrições concisas. |
versao (VARCHAR) |
20 caracteres | semver cabe em 11 caracteres. 20 dá folga para pre-release tags. |
6.3 Índices e padrões de query
Seção intitulada “6.3 Índices e padrões de query”| Índice | Query atendida |
|---|---|
event_types_pkey (id) |
JOIN com schema_versions |
event_types_tipo_key (UNIQUE) |
buscarPorTipo() — a consulta mais frequente do caminho de publicação |
event_types_status_idx |
listarTiposAtivos() — WHERE status = 'active' |
event_types_dominio_idx |
Agrupamento por domínio para documentação |
schema_versions_pkey (id) |
Acesso direto por ID |
schema_versions_event_type_id_versao_key (UNIQUE) |
Garantia de unicidade por tipo e versão |
schema_versions_event_type_id_idx |
obterVersaoMaisRecente() e obterSchema() — WHERE event_type_id = ? |
6.4 Estratégia de cache
Seção intitulada “6.4 Estratégia de cache”Cache in-memory com três mapas:
| Mapa | Chave | Valor |
|---|---|---|
cacheTipos |
tipo (ex: demanda.recebida) |
registro do tipo |
cacheSchemas |
tipo@versao e tipo@latest |
JSON Schema |
cacheValidadores |
tipo@versao |
validador Ajv compilado |
Justificativa para não usar Redis:
- O catálogo é dado de referência e muda apenas em deploy.
- O volume é pequeno: 45 tipos e poucas versões por tipo.
- O acesso é frequente (a cada publicação) e precisa de latência mínima.
- Redis adicionaria latência de rede e ponto de falha sem benefício.
Invalidação: carregarCache() limpa e repovoa o cache de tipos no boot, antes do sincronizarCatalogo(). As operações de escrita (registrar, adicionarVersaoSchema, depreciarTipo) invalidam as chaves afetadas, e sincronizarCatalogo() limpa os caches de schema e de validadores ao atualizar um schema_json.
6.5 Projeção de volume
Seção intitulada “6.5 Projeção de volume”| Cenário | Tipos ativos | Versões por tipo | Total schemas | Memória |
|---|---|---|---|---|
| MVP (Fase 1) | 44 | 1-4 | ~60 | ~200 KB |
| Fase 2 (MLP) | ~60 | 1-4 | ~150 | ~500 KB |
| Fase 3 (Consolidação) | ~80 | 1-6 | ~300 | ~1 MB |
O crescimento é linear com o número de tipos e versões. Memória não é preocupação.
7. Testabilidade
Seção intitulada “7. Testabilidade”7.1 Como testar o módulo isolado
Seção intitulada “7.1 Como testar o módulo isolado”O módulo de teste injeta mocks para os repositórios. O RegistryService é a unidade sob teste — sem dependência do Event Bus ou de outras colônias.
beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ RegistryService, { provide: EventTypeRepository, useValue: mockEventTypeRepo }, { provide: SchemaVersionRepository, useValue: mockSchemaVersionRepo }, ], }).compile();
service = module.get(RegistryService);});
// Mock do EventTypeRepositorymockEventTypeRepo.inserir.mockImplementation((data) => Promise.resolve({ id: 'uuid-1', ...data, created_at: new Date(), updated_at: new Date() }));mockEventTypeRepo.buscarPorTipo.mockResolvedValue({ id: 'uuid-1', tipo: 'demanda.recebida', status: 'active',});mockEventTypeRepo.listarAtivos.mockResolvedValue([]);
// Mock do SchemaVersionRepositorymockSchemaVersionRepo.inserir.mockImplementation((data) => Promise.resolve({ id: 'uuid-2', ...data, created_at: new Date() }));mockSchemaVersionRepo.buscarPorTipoEVersao.mockResolvedValue({ id: 'uuid-2', event_type_id: 'uuid-1', versao: '1.0.0', schema_json: schemaDemandaRecebida, is_latest: true,});mockSchemaVersionRepo.buscarMaisRecente.mockResolvedValue({ id: 'uuid-2', event_type_id: 'uuid-1', versao: '1.0.0', schema_json: schemaDemandaRecebida, is_latest: true,});mockSchemaVersionRepo.definirNaoRecente.mockResolvedValue(undefined);7.2 Cenários de teste críticos
Seção intitulada “7.2 Cenários de teste críticos”Happy path:
| # | Cenário | Verificação |
|---|---|---|
| T1 | registrar() com dados válidos |
Tipo criado com status: 'active'. Versão inicial com is_latest: true. |
| T2 | adicionarVersaoSchema() para tipo existente |
Nova versão criada. Versão anterior com is_latest: false. |
| T3 | buscarPorTipo() com tipo existente |
Retorna o tipo. A segunda consulta usa o cache. |
| T4 | obterVersaoMaisRecente() |
Retorna a versão mais recente do tipo. |
| T5 | obterSchema() com tipo e versão existentes |
Retorna o JSON Schema completo. |
| T6 | depreciarTipo() com tipo ativo |
Status muda para deprecated, com deprecated_at e deprecated_motivo. |
| T7 | listarTiposAtivos() |
Retorna apenas tipos com status: 'active'. |
| T8 | validar() e validarPayload() |
Tipo e versão registrados retornam true; payload válido retorna lista vazia. |
Falhas e bordas:
| # | Cenário | Verificação |
|---|---|---|
| T9 | registrar() com nome fora do padrão |
Lança ErroNomeTipoInvalido. Ex: "Demanda.Recebida", "demanda-recebida". |
| T10 | registrar() com tipo duplicado |
Lança ErroTipoDuplicado. |
| T11 | registrar() com versão não semver |
Lança ErroVersaoInvalida. Ex: "v1", "latest". |
| T12 | adicionarVersaoSchema() com tipo inexistente |
Lança ErroTipoNaoEncontrado. |
| T13 | adicionarVersaoSchema() com versão duplicada |
Lança ErroVersaoDuplicada. |
| T14 | adicionarVersaoSchema() com versão não semver |
Lança ErroVersaoInvalida. |
| T15 | validar() com tipo deprecated |
Retorna false. |
| T16 | validarPayload() com payload fora do schema |
Retorna a lista de erros. |
| T17 | validarPayload() com schema inexistente |
Retorna a mensagem de schema não encontrado. |
| T18 | buscarPorTipo() com tipo inexistente |
Retorna null. |
| T19 | obterSchema() com versão inexistente |
Retorna null. |
| T20 | depreciarTipo() com tipo já deprecated |
Lança ErroTipoJaDepreciado. |
| T21 | depreciarTipo() com tipo inexistente |
Lança ErroTipoNaoEncontrado. |
| T22 | depreciarTipo() sem motivo |
Lança ErroValidacao. |
Teste de integração (com PostgreSQL de teste):
| # | Cenário | Verificação |
|---|---|---|
| T23 | Ciclo completo: registrar, adicionar versão, consultar, depreciar | Tipo criado, versão adicionada, consulta correta, depreciação aplicada. |
| T24 | Duas versões de schema — a mais recente é a última | obterVersaoMaisRecente() retorna a segunda versão. obterSchema() com versão explícita retorna a correta. |
| T25 | UNIQUE violado no banco | Mesmo tipo ou mesmo par (event_type_id, versao) rejeitado pelo PostgreSQL. |
Testes do catálogo (catalogo.spec.ts):
O arquivo cobre os schemas por tipo de evento. Cada bloco monta um payload válido e verifica aceitação, rejeição por campo obrigatório ausente e rejeição por valor fora do enum ou do limite. Casos cobertos incluem demanda.georreferenciada (payload sem coordenadas é aceito na 1.0.0; coordenada fora dos limites é rejeitada), duplicidade.candidata_detectada, demanda.confirmada, demanda.evidencia_adicionada (das versões 1.1.0 a 1.3.0) e os demais tipos do catálogo.
7.3 Dados de seed para desenvolvimento local
Seção intitulada “7.3 Dados de seed para desenvolvimento local”O catálogo é semeado no boot pelo sincronizarCatalogo(), sem migration de seed. O método percorre CATALOGO_EVENTOS, registra os tipos ausentes, adiciona as versões suplementares ausentes e atualiza os schemas divergentes. Exemplo de definição do catálogo:
{ tipo: 'demanda.recebida', dominio: 'demanda', entidade: 'demanda', acao: 'recebida', descricao: 'Input bruto do cidadão aceito pelo sistema.', versao_inicial: '1.0.0', schema_inicial: { /* JSON Schema draft 2020-12 */ }, versoes_suplementares: [ { versao: '1.1.0', changelog: 'Adição dos campos categoria_id e subcategoria_id.', schema: { /* ... */ } }, ],}Resultado no banco após o boot:
core.event_types: 44 registros, todos com status 'active'core.schema_versions: versão inicial e suplementares de cada tipo, com a mais recente marcada como is_latest8. Alinhamento com o MVP
Seção intitulada “8. Alinhamento com o MVP”8.1 O que é MVP obrigatório
Seção intitulada “8.1 O que é MVP obrigatório”| Funcionalidade | Status |
|---|---|
registrar() — registro de tipos, chamado pelo sincronizarCatalogo() no boot |
MVP obrigatório |
adicionarVersaoSchema() — versionamento de schemas com semver |
MVP obrigatório |
buscarPorTipo() — consulta de tipo no caminho de publicação |
MVP obrigatório |
obterVersaoMaisRecente() — resolução da versão corrente |
MVP obrigatório |
obterSchema() — consulta de schema por tipo e versão |
MVP obrigatório |
depreciarTipo() — depreciação com motivo |
MVP obrigatório |
validar() e validarPayload() — validação de tipo e payload com Ajv |
MVP obrigatório |
| Cache in-memory de tipos, schemas e validadores | MVP obrigatório |
sincronizarCatalogo() — sincronização idempotente do catálogo no boot |
MVP obrigatório |
catalogo.spec.ts — testes de schema por tipo de evento |
MVP obrigatório |
| Catálogo de 45 tipos registrados, com a versão inicial e as suplementares de cada um | MVP obrigatório |
8.2 Simplificações válidas no MVP
Seção intitulada “8.2 Simplificações válidas no MVP”| Simplificação | Justificativa | Quando remover |
|---|---|---|
Catálogo em TypeScript (schemas/index.ts) sincronizado no boot, sem API de upload de schema |
Tipos de evento são definidos em código, não em runtime. A via de entrada é PR mais o sincronizarCatalogo() no boot. |
Se surgir necessidade de schemas definidos por usuário (ex: webhooks externos), adicionar API de registro com aprovação. |
| Sem geração automática de TypeScript types a partir dos JSON Schemas | Build step adicional adiciona complexidade de toolchain. No MVP, colônias definem seus próprios DTOs manualmente, mantendo consistência por code review. | Adicionar npm run generate:types que gera os types a partir de schemas/index.ts. |
| Sem pacote npm separado para schemas | No monolito, colônias importam RegistryService diretamente. A extração como pacote é necessária apenas no particionamento em microsserviços. |
Na Fase 2, publicar @rede-civica/registry como pacote npm com types e schemas. |
Cache in-memory simples (Map), sem invalidação por TTL |
Schemas mudam apenas em migrations (deploy). Invalidação manual no hook OnModuleInit é suficiente. |
Se surgir hot-reload de schemas, migrar para cache com TTL configurável. |
8.3 O que vai para a Fase 2
Seção intitulada “8.3 O que vai para a Fase 2”- Geração automática de TypeScript types a partir dos schemas
- Pacote npm
@rede-civica/registrycom schemas e types exportados - Índice GIN no
schema_jsonpara consultas como “quais tipos têm campo X?” - Documentação gerada automaticamente (JSON Schema para Markdown/HTML)
- Schemas dos eventos da Fase 2:
vínculo.validado,votação.*,anomalia.detectada,demanda.risco_classificado,demanda.escalada,hash.checkpoint_publicado,progressão.habilitada,snapshot.publicado,dataset.exportado, e demais tipos introduzidos nas colônias 8 a 22 e L-4, L-5.
8.4 Verificação de conflitos com outras colônias
Seção intitulada “8.4 Verificação de conflitos com outras colônias”Nenhum conflito detectado. O Registry é autocontido:
- A interface
RegistryServiceé consumida peloEventBusService(N-0a) e, de forma opcional, pelas colônias para validação de payload próprio. - As colônias não precisam importar
RegistryModulepara publicar:EventBusService.publicar()consulta o Registry internamente. - A validação de payload é responsabilidade compartilhada: o Registry valida com Ajv e devolve a lista de erros; o Event Bus rejeita a publicação quando há erro.
- Os schemas definidos neste documento foram sincronizados com as especificações técnicas de cada colônia. As fichas do Apêndice B são o resumo conceitual; os documentos
D-*.md,E-*.md,L-*.mdemsds/são a especificação implementável. - O schema
corecompartilhado com N-0a segue a regra de ouro: tabelas sem FK entre módulos, acoplamento lógico via serviço.
Referências
Seção intitulada “Referências”- Especificação base: Apêndice B - Colônias.md, seção “N-0b — Registry (Mapa do Formigueiro)”
- Colônia irmã (núcleo): N-0a - Event Bus.md
- Colônia irmã (núcleo): N-0c - Observabilidade.md
- Stack de referência e arquitetura do MVP: Apêndice B - Colônias.md, seção “Arquitetura do MVP — Monolito Modular”
- Mapa de dependências de eventos: Apêndice B - Colônias.md, seção “Mapa de Dependências de Eventos entre Colônias”
- Princípios do Formigueiro: Apêndice B - Colônias.md, seção “Princípios herdados do Formigueiro”
- Taxonomia de categorias: D-3 - Taxonomia.md
- Fichas técnicas das colônias produtoras/consumidoras: D-1a - Captura.md, D-1b - Normalização.md, D-1c - Gestão de Anexos.md, D-2 - Georreferenciamento.md, D-4 - Priorização e Ranking.md, D-5 - Agenda.md, D-6a - Sorteio e Atribuição.md, D-6b - Relatoria e Acompanhamento.md, D-7 - Transparência.md, L-1 - Cadastro de Lugares.md, L-2 - Georreferenciamento e Tipificação.md, E-1 - Cadastro Institucional.md, E-2 - Transparência Salarial e Folha.md, E-3 - Simulação Econômica.md
Documento de especificação técnica de implementação. Aprovado e integrado.